Skip to content

Commit 371d0ea

Browse files
Last Sync: 2026-02-24 03:03 (Mobile)
1 parent 4e19312 commit 371d0ea

1 file changed

Lines changed: 73 additions & 110 deletions

File tree

KuramanimeProvider/src/main/kotlin/com/kuramanime/KuramanimeProvider.kt

Lines changed: 73 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ package com.kuramanime
33
import com.lagradost.cloudstream3.*
44
import com.lagradost.cloudstream3.LoadResponse.Companion.addAniListId
55
import com.lagradost.cloudstream3.LoadResponse.Companion.addMalId
6+
import com.lagradost.cloudstream3.amap
67
import com.lagradost.cloudstream3.utils.ExtractorLink
8+
import com.lagradost.cloudstream3.utils.INFER_TYPE
79
import com.lagradost.cloudstream3.utils.Qualities
810
import com.lagradost.cloudstream3.utils.loadExtractor
911
import com.lagradost.cloudstream3.utils.newExtractorLink
@@ -12,7 +14,7 @@ import org.jsoup.nodes.Element
1214
import java.net.URI
1315

1416
class KuramanimeProvider : MainAPI() {
15-
override var mainUrl = "https://v15.kuramanime.tel"
17+
override var mainUrl = "https://v9.kuramanime.tel"
1618
override var name = "Kuramanime"
1719
override val hasQuickSearch = false
1820
override val hasMainPage = true
@@ -27,7 +29,6 @@ class KuramanimeProvider : MainAPI() {
2729

2830
companion object {
2931
private var cookies: Map<String, String> = mapOf()
30-
private const val USER_AGENT = "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Mobile Safari/537.36"
3132

3233
fun getType(t: String, s: Int): TvType {
3334
return if (t.contains("OVA", true) || t.contains("Special")) TvType.OVA
@@ -55,7 +56,7 @@ class KuramanimeProvider : MainAPI() {
5556
page: Int,
5657
request: MainPageRequest
5758
): HomePageResponse {
58-
val document = app.get(request.data + page, headers = mapOf("User-Agent" to USER_AGENT)).document
59+
val document = app.get(request.data + page).document
5960
val home = document.select("div#animeList div.product__item").mapNotNull {
6061
it.toSearchResult()
6162
}
@@ -83,30 +84,30 @@ class KuramanimeProvider : MainAPI() {
8384
this.posterUrl = posterUrl
8485
addSub(episode)
8586
}
87+
8688
}
8789

8890
override suspend fun search(query: String): List<SearchResponse> {
8991
return app.get(
90-
"$mainUrl/anime?search=$query&order_by=latest",
91-
headers = mapOf("User-Agent" to USER_AGENT)
92+
"$mainUrl/anime?search=$query&order_by=latest"
9293
).document.select("div#animeList div.product__item").mapNotNull {
9394
it.toSearchResult()
9495
}
9596
}
9697

9798
override suspend fun load(url: String): LoadResponse {
98-
val document = app.get(url, headers = mapOf("User-Agent" to USER_AGENT)).document
99+
val document = app.get(url).document
99100

100101
val title = document.selectFirst(".anime__details__title > h3")!!.text().trim()
101102
val poster = document.selectFirst(".anime__details__pic")?.attr("data-setbg")
102-
val tags = document.select("div.anime__details__widget > div > div:nth-child(2) > ul > li:nth-child(1)")
103-
.text().trim().replace("Genre: ", "").split(", ")
103+
val tags =
104+
document.select("div.anime__details__widget > div > div:nth-child(2) > ul > li:nth-child(1)")
105+
.text().trim().replace("Genre: ", "").split(", ")
104106

105107
val year = Regex("\\D").replace(
106108
document.select("div.anime__details__widget > div > div:nth-child(1) > ul > li:nth-child(5)")
107109
.text().trim().replace("Musim: ", ""), ""
108110
).toIntOrNull()
109-
110111
val status = getStatus(
111112
document.select("div.anime__details__widget > div > div:nth-child(1) > ul > li:nth-child(3)")
112113
.text().trim().replace("Status: ", "")
@@ -116,21 +117,22 @@ class KuramanimeProvider : MainAPI() {
116117
val episodes = mutableListOf<Episode>()
117118

118119
for (i in 1..30) {
119-
val doc = app.get("$url?page=$i", headers = mapOf("User-Agent" to USER_AGENT)).document
120+
val doc = app.get("$url?page=$i").document
120121
val eps = Jsoup.parse(doc.select("#episodeLists").attr("data-content"))
121122
.select("a.btn.btn-sm.btn-danger")
122123
.mapNotNull {
123124
val name = it.text().trim()
124-
val episode = Regex("(\\d+[.,]?\\d*)").find(name)?.groupValues?.getOrNull(0)?.toIntOrNull()
125+
val episode = Regex("(\\d+[.,]?\\d*)").find(name)?.groupValues?.getOrNull(0)
126+
?.toIntOrNull()
125127
val link = it.attr("href")
126128
newEpisode(link) { this.episode = episode }
127129
}
128130
if (eps.isEmpty()) break else episodes.addAll(eps)
129131
}
130132

131133
val type = getType(
132-
document.selectFirst("div.col-lg-6.col-md-6 ul li:contains(Tipe:) a")?.text()?.lowercase() ?: "tv",
133-
episodes.size
134+
document.selectFirst("div.col-lg-6.col-md-6 ul li:contains(Tipe:) a")?.text()
135+
?.lowercase() ?: "tv", episodes.size
134136
)
135137
val recommendations = document.select("div#randomList > a").mapNotNull {
136138
val epHref = it.attr("href")
@@ -157,6 +159,7 @@ class KuramanimeProvider : MainAPI() {
157159
addMalId(tracker?.malId)
158160
addAniListId(tracker?.aniId?.toIntOrNull())
159161
}
162+
160163
}
161164

162165
private suspend fun invokeLocalSource(
@@ -168,72 +171,32 @@ class KuramanimeProvider : MainAPI() {
168171
) {
169172
val document = app.get(
170173
url,
171-
headers = headers + mapOf("Referer" to mainUrl),
174+
headers = headers,
172175
cookies = cookies
173176
).document
174-
175-
var linkFound = false
176-
177-
document.select("video#player > source, video > source").forEach { source ->
178-
val link = fixUrl(source.attr("src"))
179-
val qualityStr = source.attr("size").ifEmpty { source.attr("res") }
180-
val quality = qualityStr.toIntOrNull() ?: Qualities.Unknown.value
181-
val isM3u8 = link.contains(".m3u8")
182-
183-
if (link.isNotBlank()) {
184-
linkFound = true
185-
callback.invoke(
186-
newExtractorLink(
187-
source = name,
188-
name = "Kurama Internal - $server",
189-
url = link,
190-
referer = url,
191-
quality = quality,
192-
isM3u8 = isM3u8,
193-
headers = headers + mapOf("Origin" to mainUrl, "Referer" to url)
194-
)
195-
)
196-
}
197-
}
198-
199-
if (!linkFound) {
200-
val scripts = document.select("script").map { it.data() }.joinToString("\n")
201-
val linkRegex = Regex("[\"'](https?://[^\"']+(?:\\.mp4|\\.m3u8)[^\"']*)[\"']")
202-
203-
linkRegex.findAll(scripts).forEach { match ->
204-
val link = match.groupValues[1].replace("\\/", "/")
205-
val isM3u8 = link.contains(".m3u8")
206-
207-
if (!link.contains("popads") && !link.contains("doubleclick")) {
208-
callback.invoke(
209-
newExtractorLink(
210-
source = name,
211-
name = "Kurama Extracted - $server",
212-
url = link,
213-
referer = url,
214-
quality = Qualities.Unknown.value,
215-
isM3u8 = isM3u8,
216-
headers = headers + mapOf("Origin" to mainUrl, "Referer" to url)
217-
)
177+
document.select("video#player > source").map {
178+
val link = fixUrl(it.attr("src"))
179+
val quality = it.attr("size").toIntOrNull()
180+
callback.invoke(
181+
newExtractorLink(
182+
fixTitle(server),
183+
fixTitle(server),
184+
link,
185+
INFER_TYPE
186+
) {
187+
this.headers = mapOf(
188+
"Accept" to "video/webm,video/ogg,video/*;q=0.9,application/ogg;q=0.7,audio/*;q=0.6,*/*;q=0.5",
189+
"Range" to "bytes=0-",
190+
"Sec-Fetch-Dest" to "video",
191+
"Sec-Fetch-Mode" to "no-cors",
218192
)
193+
this.quality = quality ?: Qualities.Unknown.value
219194
}
220-
}
195+
)
221196
}
222-
223-
document.select("video#player > track, video > track").forEach { track ->
224-
val subLink = fixUrl(track.attr("src"))
225-
val lang = track.attr("label").ifEmpty { "Indonesian" }
226-
if (subLink.isNotBlank()) {
227-
subtitleCallback.invoke(SubtitleFile(lang, subLink))
228-
}
229-
}
230-
231-
if (server.contains("kuramadrive", true)) {
232-
document.select("div#animeDownloadLink a, a.btn-download").amap {
233-
val downloadLink = it.attr("href")
234-
if (downloadLink.isNotBlank()) {
235-
loadExtractor(downloadLink, "$mainUrl/", subtitleCallback, callback)
236-
}
197+
if (server == "kuramadrive") {
198+
document.select("div#animeDownloadLink a").amap {
199+
loadExtractor(it.attr("href"), "$mainUrl/", subtitleCallback, callback)
237200
}
238201
}
239202
}
@@ -244,71 +207,70 @@ class KuramanimeProvider : MainAPI() {
244207
subtitleCallback: (SubtitleFile) -> Unit,
245208
callback: (ExtractorLink) -> Unit
246209
): Boolean {
247-
val req = app.get(data, headers = mapOf("User-Agent" to USER_AGENT))
210+
211+
val req = app.get(data)
248212
val res = req.document
249213
cookies = req.cookies
250214

251215
val token = res.selectFirst("meta[name=csrf-token]")?.attr("content") ?: return false
252-
val dataKps = res.selectFirst("div[data-kk]")?.attr("data-kk")
253-
?: res.selectFirst("div.col-lg-12.mt-3")?.attr("data-kk") ?: return false
216+
val dataKps = res.selectFirst("div.col-lg-12.mt-3")?.attr("data-kk") ?: return false
254217

255218
val assets = getAssets(dataKps)
256219

257-
val baseHeaders = mapOf(
258-
"User-Agent" to USER_AGENT,
220+
var headers = mapOf(
259221
"X-CSRF-TOKEN" to token,
222+
"X-Fuck-ID" to "${assets.MIX_AUTH_KEY}:${assets.MIX_AUTH_TOKEN}",
223+
"X-Request-ID" to randomId(),
224+
"X-Request-Index" to "0",
260225
"X-Requested-With" to "XMLHttpRequest",
261-
"Referer" to data
262226
)
263227

264-
val tokenKeyResponse = app.get(
228+
val tokenKey = app.get(
265229
"$mainUrl/${assets.MIX_PREFIX_AUTH_ROUTE_PARAM}${assets.MIX_AUTH_ROUTE_PARAM}",
266-
headers = baseHeaders + mapOf(
267-
"X-Fuck-ID" to "${assets.MIX_AUTH_KEY}:${assets.MIX_AUTH_TOKEN}",
268-
"X-Request-ID" to randomId(),
269-
"X-Request-Index" to "0"
270-
),
230+
headers = headers,
271231
cookies = cookies
232+
).text
233+
234+
headers = mapOf(
235+
"Alt-Used" to URI(mainUrl).host,
236+
"Authorization" to "Bearer 39F25KMTgDv0EQCqwRF9kBWxcSrHOGKc",
237+
"X-CSRF-TOKEN" to token,
238+
"X-Requested-With" to "XMLHttpRequest",
272239
)
273-
274-
val tokenKey = tokenKeyResponse.text.trim()
275240

276241
res.select("select#changeServer option").amap { source ->
277242
val server = source.attr("value")
278-
val streamingUrl = "$data?${assets.MIX_PAGE_TOKEN_KEY}=$tokenKey&${assets.MIX_STREAM_SERVER_KEY}=$server"
279-
280-
val streamResponse = app.get(
281-
streamingUrl,
282-
headers = baseHeaders,
283-
cookies = cookies
284-
)
285-
286-
val streamDoc = streamResponse.document
287-
243+
val link =
244+
"$data?${assets.MIX_PAGE_TOKEN_KEY}=$tokenKey&${assets.MIX_STREAM_SERVER_KEY}=$server"
288245
if (server.contains(Regex("(?i)kuramadrive|archive"))) {
289-
invokeLocalSource(streamingUrl, server, baseHeaders, subtitleCallback, callback)
246+
invokeLocalSource(link, server, headers, subtitleCallback, callback)
290247
} else {
291-
val iframeSrc = streamDoc.selectFirst("div.iframe-container iframe")?.attr("src")
292-
?: streamDoc.selectFirst("iframe")?.attr("src")
293-
294-
if (iframeSrc != null) {
295-
loadExtractor(fixUrl(iframeSrc), "$mainUrl/", subtitleCallback, callback)
248+
app.get(
249+
link,
250+
referer = data,
251+
headers = headers,
252+
cookies = cookies
253+
).document.select("div.iframe-container iframe").attr("src").let { videoUrl ->
254+
loadExtractor(fixUrl(videoUrl), "$mainUrl/", subtitleCallback, callback)
296255
}
297256
}
298257
}
299258

259+
300260
return true
301261
}
302262

303263
private suspend fun getAssets(bpjs: String?): Assets {
304-
val env = app.get("$mainUrl/assets/js/$bpjs.js", headers = mapOf("User-Agent" to USER_AGENT)).text
305-
val MIX_PREFIX_AUTH_ROUTE_PARAM = env.substringAfter("MIX_PREFIX_AUTH_ROUTE_PARAM: '").substringBefore("',")
306-
val MIX_AUTH_ROUTE_PARAM = env.substringAfter("MIX_AUTH_ROUTE_PARAM: '").substringBefore("',")
264+
val env = app.get("$mainUrl/assets/js/$bpjs.js").text
265+
val MIX_PREFIX_AUTH_ROUTE_PARAM =
266+
env.substringAfter("MIX_PREFIX_AUTH_ROUTE_PARAM: '").substringBefore("',")
267+
val MIX_AUTH_ROUTE_PARAM =
268+
env.substringAfter("MIX_AUTH_ROUTE_PARAM: '").substringBefore("',")
307269
val MIX_AUTH_KEY = env.substringAfter("MIX_AUTH_KEY: '").substringBefore("',")
308270
val MIX_AUTH_TOKEN = env.substringAfter("MIX_AUTH_TOKEN: '").substringBefore("',")
309271
val MIX_PAGE_TOKEN_KEY = env.substringAfter("MIX_PAGE_TOKEN_KEY: '").substringBefore("',")
310-
val MIX_STREAM_SERVER_KEY = env.substringAfter("MIX_STREAM_SERVER_KEY: '").substringBefore("',")
311-
272+
val MIX_STREAM_SERVER_KEY =
273+
env.substringAfter("MIX_STREAM_SERVER_KEY: '").substringBefore("',")
312274
return Assets(
313275
MIX_PREFIX_AUTH_ROUTE_PARAM,
314276
MIX_AUTH_ROUTE_PARAM,
@@ -334,4 +296,5 @@ class KuramanimeProvider : MainAPI() {
334296
val MIX_PAGE_TOKEN_KEY: String?,
335297
val MIX_STREAM_SERVER_KEY: String?,
336298
)
337-
}
299+
300+
}

0 commit comments

Comments
 (0)