diff --git a/lib/services/anime_extractors/okru_extractor.dart b/lib/services/anime_extractors/okru_extractor.dart index 9d884b0..33555aa 100644 --- a/lib/services/anime_extractors/okru_extractor.dart +++ b/lib/services/anime_extractors/okru_extractor.dart @@ -4,6 +4,7 @@ import 'package:mangayomi/models/video.dart'; import 'package:mangayomi/services/http/m_client.dart'; import 'package:mangayomi/utils/extensions/dom_extensions.dart'; import 'package:mangayomi/utils/extensions/string_extensions.dart'; +import 'package:path/path.dart' as path; class OkruExtractor { final InterceptedClient client = @@ -25,6 +26,7 @@ class OkruExtractor { .substringAfter("ondemandHls\\\":\\\"") .substringBefore("\\\"") .replaceAll("\\\\u0026", "&")); + final masterPlaylistResponse = await client.get(playlistUrl); final masterPlaylist = masterPlaylistResponse.body; @@ -35,12 +37,10 @@ class OkruExtractor { .map((it) { final resolution = "${it.substringAfter("RESOLUTION=").substringBefore("\n").substringAfter("x").substringBefore(",")}p"; - final videoUrl = "${Uri( - scheme: playlistUrl.scheme, - host: playlistUrl.host, - pathSegments: playlistUrl.pathSegments - .sublist(0, playlistUrl.pathSegments.length - 1), - ).toString()}/${it.substringAfter("\n").substringBefore("\n")}"; + final m3u8Host = + "${playlistUrl.scheme}://${playlistUrl.host}${path.dirname(playlistUrl.path)}"; + final videoUrl = + "$m3u8Host/${it.substringAfter("\n").substringBefore("\n")}"; return Video(videoUrl, "${prefix.isNotEmpty ? prefix : ""}Okru:$resolution", videoUrl); }).toList(); diff --git a/lib/services/anime_extractors/voe_extractor.dart b/lib/services/anime_extractors/voe_extractor.dart index 717a59f..5ae12a6 100644 --- a/lib/services/anime_extractors/voe_extractor.dart +++ b/lib/services/anime_extractors/voe_extractor.dart @@ -1,9 +1,12 @@ import 'dart:convert'; +import 'package:html/dom.dart'; +import 'package:html/parser.dart'; import 'package:http_interceptor/http_interceptor.dart'; import 'package:mangayomi/models/video.dart'; import 'package:mangayomi/services/http/m_client.dart'; +import 'package:mangayomi/utils/extensions/dom_extensions.dart'; import 'package:mangayomi/utils/extensions/string_extensions.dart'; -import 'package:mangayomi/utils/xpath_selector.dart'; +import 'package:path/path.dart' as path; class VoeExtractor { final InterceptedClient client = @@ -12,32 +15,60 @@ class VoeExtractor { r'(http|https)://([\w_-]+(?:\.[\w_-]+)+)([\w.,@?^=%&:/~+#-]*[\w@?^=%&/~+#-])'); final base64Regex = RegExp(r"'.*'"); + final RegExp scriptBase64Regex = RegExp( + r"(let|var)\s+\w+\s*=\s*'(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)';"); + Future> videosFromUrl(String url, String? prefix) async { try { - final response = await client.get(Uri.parse(url)); - final script = xpathSelector(response.body) - .queryXPath( - '//script[contains(text(), "const sources") or contains(text(), "var sources") or contains(text(), "wc0")]/text()') - .attrs; - if (script.isEmpty) { - return []; + Document document = parse((await client.get(Uri.parse(url))).body); + var scriptElement = document.selectFirst("script"); + if (scriptElement?.text + .contains("if (typeof localStorage !== 'undefined')") ?? + false) { + var originalUrl = scriptElement?.text + .substringAfter("window.location.href = '") + .substringBefore("';"); + if (originalUrl == null) { + return []; + } + document = parse((await client.get(Uri.parse(originalUrl))).body); } + var alternativeScript = document + .select('script') + ?.where( + (script) => scriptBase64Regex.hasMatch(script.text), + ) + .toList(); - final scriptContent = script.first!; + Element? script = document.selectFirst( + "script:contains(const sources), script:contains(var sources), script:contains(wc0)"); + if (script == null) { + if (alternativeScript?.isNotEmpty ?? false) { + script = alternativeScript!.first; + } else { + return []; + } + } + final scriptContent = script.text; String playlistUrl = ""; if (scriptContent.contains('sources')) { final link = - RegExp(r"hls': '([^']+)'").firstMatch(scriptContent)?.group(1); + scriptContent.substringAfter("hls': '").substringBefore("'"); + playlistUrl = - linkRegex.hasMatch(link!) ? link : utf8.decode(base64.decode(link)); - } else if (scriptContent.contains('wc0')) { + linkRegex.hasMatch(link) ? link : utf8.decode(base64.decode(link)); + } else if (scriptContent.contains('wc0') || alternativeScript != null) { final base64Match = base64Regex.firstMatch(scriptContent)!.group(0)!; final decoded = utf8.decode(base64.decode(base64Match)); - playlistUrl = json.decode(decoded)['file']; + playlistUrl = json.decode(alternativeScript != null + ? String.fromCharCodes(decoded.runes.toList().reversed) + : decoded)['file']; } else { return []; } - final masterPlaylistResponse = await client.get(Uri.parse(playlistUrl)); + final uri = Uri.parse(playlistUrl); + final m3u8Host = "${uri.scheme}://${uri.host}${path.dirname(uri.path)}"; + final masterPlaylistResponse = await client.get(uri); final masterPlaylist = masterPlaylistResponse.body; const separator = "#EXT-X-STREAM-INF"; @@ -47,7 +78,10 @@ class VoeExtractor { .map((it) { final resolution = "${it.substringAfter("RESOLUTION=").substringBefore("\n").substringAfter("x").substringBefore(",")}p"; - final videoUrl = it.substringAfter("\n").substringBefore("\n"); + final line = it.substringAfter("\n").substringBefore("\n"); + final videoUrl = line.startsWith("http") + ? line + : "$m3u8Host/${line.replaceFirst("/", "")}"; return Video(videoUrl, '${prefix ?? ""}Voe: $resolution', videoUrl); }).toList(); } catch (_) {