Skip to content

Commit 253d7b3

Browse files
test
1 parent 1699664 commit 253d7b3

1 file changed

Lines changed: 79 additions & 91 deletions

File tree

AnimeInWebProvider/src/main/kotlin/com/animeinweb/AnimeInWebProvider.kt

Lines changed: 79 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package com.animeinweb
22

33
import com.lagradost.cloudstream3.*
44
import com.lagradost.cloudstream3.utils.*
5+
import com.lagradost.cloudstream3.utils.AppUtils.parseJson
56

67
class AnimeInWebProvider : MainAPI() {
78
override var mainUrl = "https://animeinweb.com"
@@ -26,24 +27,30 @@ class AnimeInWebProvider : MainAPI() {
2627
"Accept" to "application/json",
2728
"User-Agent" to "AnimeInWeb/$apkVer Android",
2829
)
29-
private val baseParams get() = mapOf(
30-
"id_user" to idUser,
31-
"key_client" to keyClient,
32-
"apk_ver" to apkVer,
33-
)
3430

35-
private suspend fun apiGet(path: String, extra: Map<String, String> = emptyMap()): NiceResponse {
36-
val params = (baseParams + extra).entries.joinToString("&") { "${it.key}=${it.value}" }
37-
return app.get("$apiBase/$path?$params", referer = mainUrl, headers = defaultHeaders)
31+
private fun buildParams(extra: Map<String, String> = emptyMap()): String {
32+
val base = mapOf(
33+
"id_user" to idUser,
34+
"key_client" to keyClient,
35+
"apk_ver" to apkVer,
36+
)
37+
return (base + extra).entries.joinToString("&") { "${it.key}=${it.value}" }
38+
}
39+
40+
private suspend fun apiGet(path: String, extra: Map<String, String> = emptyMap()): String {
41+
return app.get(
42+
"$apiBase/$path?${buildParams(extra)}",
43+
referer = mainUrl,
44+
headers = defaultHeaders,
45+
).text
3846
}
3947

40-
/** Fix relative image path → absolute URL */
4148
private fun String?.fixImage(): String? {
4249
if (this.isNullOrEmpty()) return null
4350
return if (startsWith("http")) this else "$cdnBase$this"
4451
}
4552

46-
// ─── Type / Status ────────────────────────────────────────────────────────
53+
// ─── Helpers ──────────────────────────────────────────────────────────────
4754
companion object {
4855
fun getType(t: String): TvType = when {
4956
t.equals("OVA", ignoreCase = true) -> TvType.OVA
@@ -55,6 +62,13 @@ class AnimeInWebProvider : MainAPI() {
5562
s.equals("FINISHED", ignoreCase = true) -> ShowStatus.Completed
5663
else -> ShowStatus.Completed
5764
}
65+
fun parseQuality(q: String): Int = when {
66+
q.contains("1080") -> Qualities.P1080.value
67+
q.contains("720") -> Qualities.P720.value
68+
q.contains("480") -> Qualities.P480.value
69+
q.contains("360") -> Qualities.P360.value
70+
else -> Qualities.Unknown.value
71+
}
5872
}
5973

6074
// ─── Main Page ────────────────────────────────────────────────────────────
@@ -68,28 +82,26 @@ class AnimeInWebProvider : MainAPI() {
6882
)
6983

7084
override suspend fun getMainPage(page: Int, request: MainPageRequest): HomePageResponse {
71-
val resp = apiGet(request.data, extra = mapOf("limit" to "24", "page" to "$page", "genre_in" to ""))
72-
val items = resp.parsedSafe<ApiListResponse>()?.data?.allItems() ?: emptyList()
85+
val json = apiGet(request.data, extra = mapOf("limit" to "24", "page" to "$page", "genre_in" to ""))
86+
val items = parseJson<ApiListResponse>(json).data?.allItems() ?: emptyList()
7387
return newHomePageResponse(request.name, items.map { it.toSearchResponse() })
7488
}
7589

7690
// ─── Search ───────────────────────────────────────────────────────────────
7791
override suspend fun search(query: String): List<SearchResponse> {
78-
val resp = apiGet("search", extra = mapOf("q" to query, "limit" to "30"))
79-
return resp.parsedSafe<ApiListResponse>()?.data?.allItems()
92+
val json = apiGet("search", extra = mapOf("q" to query, "limit" to "30"))
93+
return parseJson<ApiListResponse>(json).data?.allItems()
8094
?.map { it.toSearchResponse() } ?: emptyList()
8195
}
8296

8397
// ─── Load Detail ──────────────────────────────────────────────────────────
8498
// Endpoint: GET /3/2/movie/detail/{id}
85-
// Response: { data: { movie, episode (latest), season[] } }
8699
override suspend fun load(url: String): LoadResponse? {
87100
val movieId = url.substringAfterLast("/")
88-
89-
val detail = apiGet("movie/detail/$movieId")
90-
.parsedSafe<ApiDetailResponse>()?.data ?: return null
91-
val movie = detail.movie ?: return null
92-
val seasons = detail.season ?: emptyList()
101+
val json = apiGet("movie/detail/$movieId")
102+
val detail = parseJson<ApiDetailResponse>(json).data ?: return null
103+
val movie = detail.movie ?: return null
104+
val seasons = detail.season ?: emptyList()
93105

94106
val episodes = buildEpisodeList(movieId)
95107

@@ -111,33 +123,25 @@ class AnimeInWebProvider : MainAPI() {
111123
year = movie.year?.toIntOrNull()
112124
duration = movie.duration?.toIntOrNull()
113125

114-
movie.studio?.takeIf { it.isNotEmpty() }?.let {
115-
this.actors = listOf(ActorData(Actor(it)))
116-
}
117-
118126
if (seasons.size > 1) {
119127
this.recommendations = seasons
120-
.filter { it.id != movieId }
121-
.map { it.toSearchResponse() }
128+
.filter { season -> season.id != movieId }
129+
.map { season -> season.toSearchResponse() }
122130
}
123131

124132
addEpisodes(DubStatus.Subbed, episodes)
125133
}
126134
}
127135

128136
private suspend fun buildEpisodeList(movieId: String): List<Episode> {
129-
val resp = apiGet(
130-
"movie/$movieId/episode",
131-
extra = mapOf("limit" to "999", "sort" to "asc")
132-
)
133-
val list = resp.parsedSafe<ApiEpisodeListResponse>()?.data?.allItems()
134-
?: return emptyList()
137+
val json = apiGet("movie/$movieId/episode", extra = mapOf("limit" to "999", "sort" to "asc"))
138+
val list = parseJson<ApiEpisodeListResponse>(json).data?.allItems() ?: return emptyList()
135139

136140
return list.mapIndexed { idx, ep ->
137141
val epNum = ep.index?.toIntOrNull() ?: ep.number ?: (idx + 1)
138-
// data = episodeId (that's all we need for streamnew)
142+
val epTitle = if (!ep.title.isNullOrBlank()) ep.title else "Episode $epNum"
139143
newEpisode(ep.id ?: "") {
140-
this.name = ep.title?.takeIf { it.isNotBlank() } ?: "Episode $epNum"
144+
this.name = epTitle
141145
this.episode = epNum
142146
this.posterUrl = ep.image.fixImage()
143147
this.addDate(ep.key_time ?: ep.aired)
@@ -147,50 +151,39 @@ class AnimeInWebProvider : MainAPI() {
147151

148152
// ─── Load Links ───────────────────────────────────────────────────────────
149153
// Confirmed endpoint: GET /3/2/episode/streamnew/{episodeId}
150-
// Response: { data: { episode, episode_next, server: [ {link, quality, type, name} ] } }
154+
// server[].link = direct MP4 on storages.animein.net
155+
// server[].quality = "360p" | "480p" | "720p" | "1080p"
156+
// server[].type = "direct" | "embed"
151157
override suspend fun loadLinks(
152-
data: String, // = episodeId e.g. "313448"
158+
data: String, // episodeId e.g. "313448"
153159
isCasting: Boolean,
154160
subtitleCallback: (SubtitleFile) -> Unit,
155161
callback: (ExtractorLink) -> Unit
156162
): Boolean {
157-
val episodeId = data.ifEmpty { return false }
163+
if (data.isEmpty()) return false
158164

159-
val resp = apiGet("episode/streamnew/$episodeId")
160-
val servers = resp.parsedSafe<ApiStreamResponse>()?.data?.server
161-
?: return false
165+
val json = apiGet("episode/streamnew/$data")
166+
val servers = parseJson<ApiStreamResponse>(json).data?.server ?: return false
162167

163168
servers.forEach { server ->
164-
val link = server.link?.ifEmpty { null } ?: return@forEach
169+
val link = server.link ?: return@forEach
165170
val quality = server.quality ?: ""
166-
val label = buildString {
167-
append(server.name ?: "Server")
168-
if (quality.isNotEmpty()) append(" $quality")
169-
}
171+
val label = "${server.name ?: "Server"} $quality".trim()
170172

171173
when (server.type?.lowercase()) {
172174
"direct" -> {
173-
// Direct MP4 from storages.animein.net — confirmed
174175
callback(
175-
newExtractorLink(
176-
source = name,
177-
name = label,
178-
url = link,
179-
type = ExtractorLinkType.VIDEO,
180-
) {
181-
this.quality = when (quality) {
182-
"1080p" -> Qualities.P1080.value
183-
"720p" -> Qualities.P720.value
184-
"480p" -> Qualities.P480.value
185-
"360p" -> Qualities.P360.value
186-
else -> Qualities.Unknown.value
187-
}
188-
this.isM3u8 = false
189-
}
176+
ExtractorLink(
177+
source = name,
178+
name = label,
179+
url = link,
180+
referer = mainUrl,
181+
quality = parseQuality(quality),
182+
isM3u8 = false,
183+
)
190184
)
191185
}
192186
else -> {
193-
// Embed / iframe server
194187
loadExtractor(link, mainUrl, subtitleCallback, callback)
195188
}
196189
}
@@ -202,7 +195,6 @@ class AnimeInWebProvider : MainAPI() {
202195

203196
// ─── Data Classes ─────────────────────────────────────────────────────────────
204197

205-
// LIST (home/search)
206198
data class ApiListResponse(
207199
val status: Int? = null,
208200
val error: Boolean? = null,
@@ -238,18 +230,20 @@ data class AnimeItem(
238230
val total_episode: Int? = null,
239231
val latest_episode: Int? = null,
240232
) {
241-
fun toSearchResponse() = newAnimeSearchResponse(
242-
name = title ?: "Unknown",
243-
url = "https://animeinweb.com/anime/$id",
244-
type = AnimeInWebProvider.getType(type ?: "SERIES")
245-
) {
246-
posterUrl = if (image_poster?.startsWith("http") == true) image_poster
247-
else "https://xyz-api.animein.net$image_poster"
248-
addSub(latest_episode ?: total_episode)
233+
fun toSearchResponse(): AnimeSearchResponse {
234+
val poster = if (image_poster?.startsWith("http") == true) image_poster
235+
else "https://xyz-api.animein.net$image_poster"
236+
return newAnimeSearchResponse(
237+
name = title ?: "Unknown",
238+
url = "https://animeinweb.com/anime/$id",
239+
type = AnimeInWebProvider.getType(type ?: "SERIES"),
240+
) {
241+
posterUrl = poster
242+
addSub(latest_episode ?: total_episode)
243+
}
249244
}
250245
}
251246

252-
// DETAIL → { data: { movie, episode, season[] } }
253247
data class ApiDetailResponse(
254248
val status: Int? = null,
255249
val error: Boolean? = null,
@@ -261,7 +255,6 @@ data class DetailData(
261255
val season: List<AnimeItem>? = null,
262256
)
263257

264-
// EPISODE LIST → { data: { episode[] } }
265258
data class ApiEpisodeListResponse(
266259
val status: Int? = null,
267260
val data: EpisodeListData? = null,
@@ -274,37 +267,32 @@ data class EpisodeListData(
274267
data class EpisodeItem(
275268
val id: String? = null,
276269
val title: String? = null,
277-
val index: String? = null, // "1", "2", ...
270+
val index: String? = null,
278271
val number: Int? = null,
279-
val episode_number: Int? = null,
280272
val views: String? = null,
281273
val id_movie: String? = null,
282-
val key_time: String? = null, // "12 Jan 2026"
274+
val key_time: String? = null,
283275
val aired: String? = null,
284-
val image: String? = null, // may be relative
285-
val thumbnail: String? = null,
276+
val image: String? = null,
286277
)
287278

288-
// STREAM → GET /episode/streamnew/{id}
289-
// { data: { episode, episode_next, server: [{id, link, quality, type, name, ...}] } }
290279
data class ApiStreamResponse(
291280
val status: Int? = null,
292281
val error: Boolean? = null,
293282
val data: StreamData? = null,
294283
)
295284
data class StreamData(
296-
val episode: EpisodeItem? = null,
297-
val episode_next: EpisodeItem? = null,
285+
val episode: EpisodeItem? = null,
286+
val episode_next: EpisodeItem? = null,
298287
val server: List<StreamServer>? = null,
299288
)
300289
data class StreamServer(
301-
val id: String? = null,
302-
val link: String? = null, // confirmed: direct MP4 URL
303-
val quality: String? = null, // "360p" | "480p" | "720p" | "1080p"
304-
val key_file_size: String? = null,
305-
val name: String? = null, // e.g. "RAPSODI"
306-
val type: String? = null, // "direct" | "embed"
307-
val domain: String? = null,
308-
val username: String? = null,
309-
val server_id: String? = null,
290+
val id: String? = null,
291+
val link: String? = null,
292+
val quality: String? = null,
293+
val name: String? = null,
294+
val type: String? = null,
295+
val domain: String? = null,
296+
val username: String? = null,
297+
val server_id: String? = null,
310298
)

0 commit comments

Comments
 (0)