Skip to content

Commit 60f5e19

Browse files
Last Sync: 2025-12-29 22:02 (Mobile)
1 parent 4e406f2 commit 60f5e19

1 file changed

Lines changed: 68 additions & 52 deletions

File tree

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

Lines changed: 68 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import org.jsoup.nodes.Element
1414
import java.net.URI
1515

1616
class KuramanimeProvider : MainAPI() {
17-
override var mainUrl = "https://v5.kuramanime.blog"
17+
override var mainUrl = "https://v9.kuramanime.tel"
1818
override var name = "Kuramanime"
1919
override val hasQuickSearch = false
2020
override val hasMainPage = true
@@ -84,7 +84,6 @@ class KuramanimeProvider : MainAPI() {
8484
this.posterUrl = posterUrl
8585
addSub(episode)
8686
}
87-
8887
}
8988

9089
override suspend fun search(query: String): List<SearchResponse> {
@@ -159,7 +158,6 @@ class KuramanimeProvider : MainAPI() {
159158
addMalId(tracker?.malId)
160159
addAniListId(tracker?.aniId?.toIntOrNull())
161160
}
162-
163161
}
164162

165163
private suspend fun invokeLocalSource(
@@ -169,11 +167,16 @@ class KuramanimeProvider : MainAPI() {
169167
subtitleCallback: (SubtitleFile) -> Unit,
170168
callback: (ExtractorLink) -> Unit
171169
) {
172-
val document = app.get(
170+
val response = app.get(
173171
url,
174172
headers = headers,
175173
cookies = cookies
176-
).document
174+
)
175+
176+
// Handle error response gracefuly
177+
if(response.code != 200) return
178+
179+
val document = response.document
177180
document.select("video#player > source").map {
178181
val link = fixUrl(it.attr("src"))
179182
val quality = it.attr("size").toIntOrNull()
@@ -185,15 +188,15 @@ class KuramanimeProvider : MainAPI() {
185188
INFER_TYPE
186189
) {
187190
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",
191+
"Referer" to url, // Important for streaming
192+
"User-Agent" to "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36"
192193
)
193194
this.quality = quality ?: Qualities.Unknown.value
194195
}
195196
)
196197
}
198+
199+
// Fallback for download links if video tag fails
197200
if (server == "kuramadrive") {
198201
document.select("div#animeDownloadLink a").amap {
199202
loadExtractor(it.attr("href"), "$mainUrl/", subtitleCallback, callback)
@@ -212,72 +215,86 @@ class KuramanimeProvider : MainAPI() {
212215
val res = req.document
213216
cookies = req.cookies
214217

215-
val token = res.selectFirst("meta[name=csrf-token]")?.attr("content") ?: return false
216-
val dataKps = res.selectFirst("div.col-lg-12.mt-3")?.attr("data-kk") ?: return false
218+
// Get CSRF Token
219+
val token = res.selectFirst("meta[name=csrf-token]")?.attr("content")
220+
?: return false
221+
222+
// Get JS Asset Key (support data-kk or data-kps)
223+
val scriptData = res.selectFirst("div[data-kk]")?.attr("data-kk")
224+
?: res.selectFirst("div[data-kps]")?.attr("data-kps")
225+
?: return false
217226

218-
val assets = getAssets(dataKps)
227+
val assets = getAssets(scriptData)
219228

220-
var headers = mapOf(
229+
// Base Headers
230+
val baseHeaders = mapOf(
221231
"X-CSRF-TOKEN" to token,
232+
"X-Requested-With" to "XMLHttpRequest",
233+
"Referer" to data
234+
)
235+
236+
// Auth headers for the token request
237+
val authHeaders = baseHeaders + mapOf(
222238
"X-Fuck-ID" to "${assets.MIX_AUTH_KEY}:${assets.MIX_AUTH_TOKEN}",
223239
"X-Request-ID" to randomId(),
224240
"X-Request-Index" to "0",
225-
"X-Requested-With" to "XMLHttpRequest",
226241
)
227242

243+
val tokenKeyUrl = "$mainUrl/${assets.MIX_PREFIX_AUTH_ROUTE_PARAM}${assets.MIX_AUTH_ROUTE_PARAM}"
244+
245+
// Get the dynamic key
228246
val tokenKey = app.get(
229-
"$mainUrl/${assets.MIX_PREFIX_AUTH_ROUTE_PARAM}${assets.MIX_AUTH_ROUTE_PARAM}",
230-
headers = headers,
247+
tokenKeyUrl,
248+
headers = authHeaders,
231249
cookies = cookies
232250
).text
233251

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",
252+
// Stream headers (DO NOT include hardcoded Bearer token)
253+
val streamHeaders = baseHeaders + mapOf(
254+
"Alt-Used" to URI(mainUrl).host
239255
)
240256

241257
res.select("select#changeServer option").amap { source ->
242258
val server = source.attr("value")
243-
val link =
244-
"$data?${assets.MIX_PAGE_TOKEN_KEY}=$tokenKey&${assets.MIX_STREAM_SERVER_KEY}=$server"
259+
val link = "$data?${assets.MIX_PAGE_TOKEN_KEY}=$tokenKey&${assets.MIX_STREAM_SERVER_KEY}=$server"
260+
245261
if (server.contains(Regex("(?i)kuramadrive|archive"))) {
246-
invokeLocalSource(link, server, headers, subtitleCallback, callback)
262+
invokeLocalSource(link, server, streamHeaders, subtitleCallback, callback)
247263
} else {
248-
app.get(
264+
val iframeReq = app.get(
249265
link,
250266
referer = data,
251-
headers = headers,
267+
headers = streamHeaders,
252268
cookies = cookies
253-
).document.select("div.iframe-container iframe").attr("src").let { videoUrl ->
254-
loadExtractor(fixUrl(videoUrl), "$mainUrl/", subtitleCallback, callback)
269+
)
270+
271+
// Check if it's an iframe source
272+
val iframeSrc = iframeReq.document.select("div.iframe-container iframe").attr("src")
273+
if (iframeSrc.isNotEmpty()) {
274+
loadExtractor(fixUrl(iframeSrc), "$mainUrl/", subtitleCallback, callback)
255275
}
256276
}
257277
}
258278

259-
260279
return true
261280
}
262281

263282
private suspend fun getAssets(bpjs: String?): Assets {
264283
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("',")
269-
val MIX_AUTH_KEY = env.substringAfter("MIX_AUTH_KEY: '").substringBefore("',")
270-
val MIX_AUTH_TOKEN = env.substringAfter("MIX_AUTH_TOKEN: '").substringBefore("',")
271-
val MIX_PAGE_TOKEN_KEY = env.substringAfter("MIX_PAGE_TOKEN_KEY: '").substringBefore("',")
272-
val MIX_STREAM_SERVER_KEY =
273-
env.substringAfter("MIX_STREAM_SERVER_KEY: '").substringBefore("',")
284+
285+
// Robust Regex extraction
286+
fun extract(key: String): String {
287+
val regex = Regex("""$key\s*:\s*['"]([^'"]+)['"]""")
288+
return regex.find(env)?.groupValues?.get(1) ?: ""
289+
}
290+
274291
return Assets(
275-
MIX_PREFIX_AUTH_ROUTE_PARAM,
276-
MIX_AUTH_ROUTE_PARAM,
277-
MIX_AUTH_KEY,
278-
MIX_AUTH_TOKEN,
279-
MIX_PAGE_TOKEN_KEY,
280-
MIX_STREAM_SERVER_KEY
292+
MIX_PREFIX_AUTH_ROUTE_PARAM = extract("MIX_PREFIX_AUTH_ROUTE_PARAM"),
293+
MIX_AUTH_ROUTE_PARAM = extract("MIX_AUTH_ROUTE_PARAM"),
294+
MIX_AUTH_KEY = extract("MIX_AUTH_KEY"),
295+
MIX_AUTH_TOKEN = extract("MIX_AUTH_TOKEN"),
296+
MIX_PAGE_TOKEN_KEY = extract("MIX_PAGE_TOKEN_KEY"),
297+
MIX_STREAM_SERVER_KEY = extract("MIX_STREAM_SERVER_KEY")
281298
)
282299
}
283300

@@ -289,12 +306,11 @@ class KuramanimeProvider : MainAPI() {
289306
}
290307

291308
data class Assets(
292-
val MIX_PREFIX_AUTH_ROUTE_PARAM: String?,
293-
val MIX_AUTH_ROUTE_PARAM: String?,
294-
val MIX_AUTH_KEY: String?,
295-
val MIX_AUTH_TOKEN: String?,
296-
val MIX_PAGE_TOKEN_KEY: String?,
297-
val MIX_STREAM_SERVER_KEY: String?,
309+
val MIX_PREFIX_AUTH_ROUTE_PARAM: String,
310+
val MIX_AUTH_ROUTE_PARAM: String,
311+
val MIX_AUTH_KEY: String,
312+
val MIX_AUTH_TOKEN: String,
313+
val MIX_PAGE_TOKEN_KEY: String,
314+
val MIX_STREAM_SERVER_KEY: String,
298315
)
299-
300-
}
316+
}

0 commit comments

Comments
 (0)