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 import java.util.regex.Pattern
class LuluExtractor(private val client: OkHttpClient) { class LuluExtractor(private val client: OkHttpClient) {
//Credit: https://github.com/skoruppa/docchi-stremio-addon/blob/main/app/players/lulustream.py
private val headers = Headers.Builder() fun videosFromUrl(url: String, prefix: String, headers: Headers): List<Video> {
val luluHeaders = headers.newBuilder()
.add("Referer", "https://luluvdo.com") .add("Referer", "https://luluvdo.com")
.add("Origin", "https://luluvdo.com") .add("Origin", "https://luluvdo.com")
.build() .build()
fun videosFromUrl(url: String, prefix: String): List<Video> {
val videos = mutableListOf<Video>() val videos = mutableListOf<Video>()
try { 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 m3u8Url = extractM3u8Url(html) ?: return emptyList()
val fixedUrl = fixM3u8Link(m3u8Url) 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) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
} }
@ -50,37 +50,42 @@ class LuluExtractor(private val client: OkHttpClient) {
private fun fixM3u8Link(link: String): String { private fun fixM3u8Link(link: String): String {
val paramOrder = listOf("t", "s", "e", "f") val paramOrder = listOf("t", "s", "e", "f")
val baseUrl = link.split("?").first() val params = Pattern.compile("[?&]([^=]*)=([^&]*)").matcher(link).let { matcher ->
val params = link.split("?").getOrNull(1)?.split("&") ?: emptyList() generateSequence { if (matcher.find()) matcher.group(1) to matcher.group(2) else null }.toList()
}
val paramMap = mutableMapOf<String, String>()
val extraParams = mutableMapOf( val paramDict = mutableMapOf<String, String>()
"i" to "0.3", val extraParams = mutableMapOf<String, String>()
"sp" to "0"
) params.forEachIndexed { index, (key , value) ->
if (key.isNullOrEmpty()) {
params.forEachIndexed { index, param -> if (index < paramOrder.size) {
val parts = param.split("=") if (value != null) {
when { paramDict[paramOrder[index]] = value
parts.size == 2 -> { }
val (key, value) = parts }
if (key in paramOrder) paramMap[key] = value } else {
else extraParams[key] = value if (value != null) {
extraParams[key] = value
} }
index < paramOrder.size -> paramMap[paramOrder[index]] = parts.firstOrNull() ?: ""
} }
} }
return buildString { extraParams["i"] = "0.3"
append(baseUrl) extraParams["sp"] = "0"
append("?")
append(paramOrder.joinToString("&") { "$it=${paramMap[it]}" }) val baseUrl = link.split("?")[0]
append("&")
append(extraParams.map { "${it.key}=${it.value}" }.joinToString("&")) 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 { return try {
val content = client.newCall(GET(m3u8Url, headers)).execute() val content = client.newCall(GET(m3u8Url, headers)).execute()
.use { it.body.string() } .use { it.body.string() }
@ -98,11 +103,9 @@ class LuluExtractor(private val client: OkHttpClient) {
} }
object JavaScriptUnpacker { object JavaScriptUnpacker {
private val UNPACK_REGEX = Regex( private val UNPACK_REGEX by lazy {
"""}\('(.*)', *(\d+), *(\d+), *'(.*?)'\.split\('\|'\)""", Regex("""\}\('(.*)', *(\d+), *(\d+), *'(.*?)'\.split\('\|'\)""")
RegexOption.DOT_MATCHES_ALL }
)
fun unpack(encodedJs: String): String? { fun unpack(encodedJs: String): String? {
val match = UNPACK_REGEX.find(encodedJs) ?: return null val match = UNPACK_REGEX.find(encodedJs) ?: return null
val (payload, radixStr, countStr, symtabStr) = match.destructured val (payload, radixStr, countStr, symtabStr) = match.destructured
@ -121,8 +124,8 @@ object JavaScriptUnpacker {
return Regex("""\b\w+\b""").replace(payload) { mr -> return Regex("""\b\w+\b""").replace(payload) { mr ->
symtab.getOrNull(unbase(mr.value, radix, baseDict)) ?: mr.value symtab.getOrNull(unbase(mr.value, radix, baseDict)) ?: mr.value
}.replace("\\", "") }.replace("\\", "")
}
}
private fun unbase(value: String, radix: Int, dict: Map<Char, Int>): Int { private fun unbase(value: String, radix: Int, dict: Map<Char, Int>): Int {
var result = 0 var result = 0
var multiplier = 1 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 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 // Credit: https://github.com/skoruppa/docchi-stremio-addon/blob/main/app/players/lycoris.py
fun getVideosFromUrl(url: String, headers: Headers, prefix: String): List<Video> { 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) // 1. Obsługa kontynuacji linii (backslash + newline)
val withoutLineContinuation = text.replace("\\\n", "") val withoutLineContinuation = text.replace("\\\n", "")
return wordsRegex.replace(withoutLineContinuation) { match ->
// 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 ->
val (u8, u4, x2, octal, simple) = match.destructured val (u8, u4, x2, octal, simple) = match.destructured
when { when {
u8.isNotEmpty() -> handleUnicode8(u8) u8.isNotEmpty() -> handleUnicode8(u8)

View file

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