fix(lib/lycoris&lulu): big change v1

This commit is contained in:
Hayanek 2025-03-26 01:36:23 +01:00
parent 9a8ea456b4
commit de4383ff6f
3 changed files with 70 additions and 67 deletions

View file

@ -7,22 +7,22 @@ import okhttp3.OkHttpClient
import java.util.regex.Pattern
class LuluExtractor(private val client: OkHttpClient) {
//Credit: https://github.com/skoruppa/docchi-stremio-addon/blob/main/app/players/lulustream.py
fun videosFromUrl(url: String, prefix: String, headers: Headers): List<Video> {
val luluHeaders = headers.newBuilder()
.add("Referer", "https://luluvdo.com")
.add("Origin", "https://luluvdo.com")
.build()
private val headers = Headers.Builder()
.add("Referer", "https://luluvdo.com")
.add("Origin", "https://luluvdo.com")
.build()
fun videosFromUrl(url: String, prefix: String): List<Video> {
val videos = mutableListOf<Video>()
try {
val html = client.newCall(GET(url, headers)).execute().use { it.body.string() }
val html = client.newCall(GET(url, luluHeaders)).execute().use { it.body.string() }
val m3u8Url = extractM3u8Url(html) ?: return emptyList()
val fixedUrl = fixM3u8Link(m3u8Url)
val quality = getResolution(fixedUrl)
val quality = getResolution(fixedUrl, luluHeaders)
videos.add(Video(fixedUrl, "${prefix}Lulu - $quality", fixedUrl))
videos.add(Video(fixedUrl, "${prefix}Lulu - $quality", fixedUrl, luluHeaders))
} catch (e: Exception) {
e.printStackTrace()
}
@ -50,37 +50,42 @@ class LuluExtractor(private val client: OkHttpClient) {
private fun fixM3u8Link(link: String): String {
val paramOrder = listOf("t", "s", "e", "f")
val baseUrl = link.split("?").first()
val params = link.split("?").getOrNull(1)?.split("&") ?: emptyList()
val params = Pattern.compile("[?&]([^=]*)=([^&]*)").matcher(link).let { matcher ->
generateSequence { if (matcher.find()) matcher.group(1) to matcher.group(2) else null }.toList()
}
val paramMap = mutableMapOf<String, String>()
val extraParams = mutableMapOf(
"i" to "0.3",
"sp" to "0"
)
val paramDict = mutableMapOf<String, String>()
val extraParams = mutableMapOf<String, String>()
params.forEachIndexed { index, param ->
val parts = param.split("=")
when {
parts.size == 2 -> {
val (key, value) = parts
if (key in paramOrder) paramMap[key] = value
else extraParams[key] = value
params.forEachIndexed { index, (key , value) ->
if (key.isNullOrEmpty()) {
if (index < paramOrder.size) {
if (value != null) {
paramDict[paramOrder[index]] = value
}
}
} else {
if (value != null) {
extraParams[key] = value
}
index < paramOrder.size -> paramMap[paramOrder[index]] = parts.firstOrNull() ?: ""
}
}
return buildString {
append(baseUrl)
append("?")
append(paramOrder.joinToString("&") { "$it=${paramMap[it]}" })
append("&")
append(extraParams.map { "${it.key}=${it.value}" }.joinToString("&"))
}
extraParams["i"] = "0.3"
extraParams["sp"] = "0"
val baseUrl = link.split("?")[0]
val fixedLink = StringBuilder(baseUrl)
val orderedParams = paramOrder.filter { paramDict.containsKey(it) }.map { "$it=${paramDict[it]}" }
fixedLink.append("?").append(orderedParams.joinToString("&"))
fixedLink.append("&").append(extraParams.entries.joinToString("&") { "${it.key}=${it.value}" })
return fixedLink.toString()
}
private fun getResolution(m3u8Url: String): String {
private fun getResolution(m3u8Url: String, headers: Headers): String {
return try {
val content = client.newCall(GET(m3u8Url, headers)).execute()
.use { it.body.string() }
@ -98,31 +103,29 @@ class LuluExtractor(private val client: OkHttpClient) {
}
object JavaScriptUnpacker {
private val UNPACK_REGEX = Regex(
"""}\('(.*)', *(\d+), *(\d+), *'(.*?)'\.split\('\|'\)""",
RegexOption.DOT_MATCHES_ALL
)
fun unpack(encodedJs: String): String? {
val match = UNPACK_REGEX.find(encodedJs) ?: return null
val (payload, radixStr, countStr, symtabStr) = match.destructured
val radix = radixStr.toIntOrNull() ?: return null
val count = countStr.toIntOrNull() ?: return null
val symtab = symtabStr.split('|')
if (symtab.size != count) throw IllegalArgumentException("Invalid symtab size")
val baseDict = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
.take(radix)
.withIndex()
.associate { it.value to it.index }
return Regex("""\b\w+\b""").replace(payload) { mr ->
symtab.getOrNull(unbase(mr.value, radix, baseDict)) ?: mr.value
}.replace("\\", "")
private val UNPACK_REGEX by lazy {
Regex("""\}\('(.*)', *(\d+), *(\d+), *'(.*?)'\.split\('\|'\)""")
}
fun unpack(encodedJs: String): String? {
val match = UNPACK_REGEX.find(encodedJs) ?: return null
val (payload, radixStr, countStr, symtabStr) = match.destructured
val radix = radixStr.toIntOrNull() ?: return null
val count = countStr.toIntOrNull() ?: return null
val symtab = symtabStr.split('|')
if (symtab.size != count) throw IllegalArgumentException("Invalid symtab size")
val baseDict = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
.take(radix)
.withIndex()
.associate { it.value to it.index }
return Regex("""\b\w+\b""").replace(payload) { mr ->
symtab.getOrNull(unbase(mr.value, radix, baseDict)) ?: mr.value
}.replace("\\", "")
}
private fun unbase(value: String, radix: Int, dict: Map<Char, Int>): Int {
var result = 0
var multiplier = 1

View file

@ -18,6 +18,16 @@ class LycorisCafeExtractor(private val client: OkHttpClient) {
private val GETLNKURL = "https://www.lycoris.cafe/api/watch/getLink"
private val wordsRegex by lazy {
Regex(
"""\\U([0-9a-fA-F]{8})|""" + // \UXXXXXXXX
"""\\u([0-9a-fA-F]{4})|""" + // \uXXXX
"""\\x([0-9a-fA-F]{2})|""" + // \xHH
"""\\([0-7]{1,3})|""" + // \OOO (octal)
"""\\([btnfr"'$\\])""" // \n, \t, itd.
)
}
// Credit: https://github.com/skoruppa/docchi-stremio-addon/blob/main/app/players/lycoris.py
fun getVideosFromUrl(url: String, headers: Headers, prefix: String): List<Video> {
@ -145,17 +155,7 @@ class LycorisCafeExtractor(private val client: OkHttpClient) {
// 1. Obsługa kontynuacji linii (backslash + newline)
val withoutLineContinuation = text.replace("\\\n", "")
// 2. Regex do wykrywania wszystkich sekwencji escape
val regex = Regex(
"""\\U([0-9a-fA-F]{8})|""" + // \UXXXXXXXX
"""\\u([0-9a-fA-F]{4})|""" + // \uXXXX
"""\\x([0-9a-fA-F]{2})|""" + // \xHH
"""\\([0-7]{1,3})|""" + // \OOO (octal)
"""\\([btnfr"'$\\])""" // \n, \t, itd.
)
return regex.replace(withoutLineContinuation) { match ->
return wordsRegex.replace(withoutLineContinuation) { match ->
val (u8, u4, x2, octal, simple) = match.destructured
when {
u8.isNotEmpty() -> handleUnicode8(u8)

View file

@ -203,7 +203,7 @@ class Docchi : ConfigurableAnimeSource, AnimeHttpSource() {
}
serverUrl.contains("luluvdo.com") -> {
luluExtractor.videosFromUrl(serverUrl, prefix)
luluExtractor.videosFromUrl(serverUrl, prefix, headers)
}
serverUrl.contains("drive.google.com") -> {