import 'dart:convert'; import 'dart:io'; import 'package:bot_toast/bot_toast.dart'; import 'package:dart_eval/dart_eval.dart'; import 'package:dart_eval/dart_eval_bridge.dart'; import 'package:dart_eval/stdlib/core.dart'; import 'package:desktop_webview_window/desktop_webview_window.dart'; import 'package:flutter/material.dart'; import 'package:flutter_inappwebview/flutter_inappwebview.dart'; import 'package:flutter_js/flutter_js.dart'; import 'package:intl/date_symbol_data_local.dart'; import 'package:intl/intl.dart'; import 'package:json_path/json_path.dart'; import 'package:mangayomi/eval/bridge_class/model.dart'; import 'package:mangayomi/eval/bridge_class/video_model.dart'; import 'package:mangayomi/services/anime_extractors/dood_extractor.dart'; import 'package:mangayomi/services/anime_extractors/gogo_cdn_extractor.dart'; import 'package:mangayomi/services/anime_extractors/mp4_upload_extractor.dart'; import 'package:mangayomi/services/anime_extractors/my_tv_extractor.dart'; import 'package:mangayomi/services/anime_extractors/send_vid_extractor.dart'; import 'package:mangayomi/services/anime_extractors/sibnet_extractor.dart'; import 'package:mangayomi/services/anime_extractors/stream_tape_extractor.dart'; import 'package:mangayomi/main.dart'; import 'package:mangayomi/models/source.dart'; import 'package:mangayomi/models/video.dart'; import 'package:mangayomi/modules/webview/webview.dart'; import 'package:mangayomi/services/http_service/cloudflare/cloudflare_bypass.dart'; import 'package:mangayomi/utils/constant.dart'; import 'package:mangayomi/utils/reg_exp_matcher.dart'; import 'package:mangayomi/utils/xpath_selector.dart'; import 'package:xpath_selector_html_parser/xpath_selector_html_parser.dart'; import 'package:html/parser.dart' as parser; import 'package:http/http.dart' as hp; class WordSet { final List words; WordSet(this.words); bool anyWordIn(String dateString) { return words .any((word) => dateString.toLowerCase().contains(word.toLowerCase())); } bool startsWith(String dateString) { return words .any((word) => dateString.toLowerCase().startsWith(word.toLowerCase())); } bool endsWith(String dateString) { return words .any((word) => dateString.toLowerCase().endsWith(word.toLowerCase())); } } class MBridge { static String querySelector( String html, String selector, int typeElement, String attributes, ) { try { var parse = parser.parse(html); if (typeElement == 0) { return parse .querySelector(selector)! .text .trim() .trimLeft() .trimRight(); } else if (typeElement == 1) { return parse .querySelector(selector)! .innerHtml .trim() .trimLeft() .trimRight(); } else if (typeElement == 2) { return parse .querySelector(selector)! .outerHtml .trim() .trimLeft() .trimRight(); } return parse .querySelector(selector)! .attributes[attributes]! .trim() .trimLeft() .trimRight(); } catch (e) { _botToast(e.toString()); throw Exception(e); } } static String querySelectorAll(String html, String selector, int typeElement, String attributes, int typeRegExp, int position, String join) { try { var parse = parser.parse(html); final a = parse.querySelectorAll(selector); List res = []; for (var element in a) { if (typeElement == 0) { res.add(element.text.trim().trimLeft().trimRight()); } else if (typeElement == 1) { res.add(element.innerHtml.trim().trimLeft().trimRight()); } else if (typeElement == 2) { res.add(element.outerHtml.trim().trimLeft().trimRight()); } else if (typeElement == 3) { res.add( element.attributes[attributes]!.trim().trimLeft().trimRight()); } } if (typeRegExp == 0) { if (position == 0) { return res.join(join); } else if (position == 1) { return res.first; } return res.last; } List resRegExp = []; for (var element in res) { if (typeRegExp == 1) { resRegExp.add(regHrefMatcher(element.trim().trimLeft().trimRight())); } else if (typeRegExp == 2) { resRegExp.add(regSrcMatcher(element.trim().trimLeft().trimRight())); } else if (typeRegExp == 3) { resRegExp .add(regDataSrcMatcher(element.trim().trimLeft().trimRight())); } else if (typeRegExp == 4) { resRegExp.add(regImgMatcher(element.trim().trimLeft().trimRight())); } else if (typeRegExp == 5) { resRegExp.add(regImgMatcher(element.trim().trimLeft().trimRight())); } } if (position == 0) { return resRegExp.join(join); } else if (position == 1) { return resRegExp.first.trim().trimLeft().trimRight(); } return resRegExp.last.trim().trimLeft().trimRight(); } catch (e) { _botToast(e.toString()); throw Exception(e); } } static String xpath(String html, String xpath, String join) { try { List attrs = []; var htmlXPath = HtmlXPath.html(html); var query = htmlXPath.query(xpath); if (query.nodes.length > 1) { for (var element in query.attrs) { attrs.add(element!.trim().trimLeft().trimRight()); } return attrs.join(join); } else { String? attr = query.attr != null ? query.attr!.trim().trimLeft().trimRight() : ""; return attr; } } catch (e) { // _botToast(e.toString()); return ""; } } static List listParse(List value, int type) { List val = []; for (var element in value) { if (element is $Value) { val.add(element.$reified.toString()); } else { val.add(element); } } if (type == 3) { return val.toSet().toList(); } else if (type == 1) { return [val.first]; } else if (type == 2) { return [val.last]; } else if (type == 4) { return val.where((element) => element.toString().isNotEmpty).toList(); } else if (type == 5) { return val.reversed.toList(); } return val; } static int parseStatus(String status, List statusList) { for (var element in statusList) { Map statusMap = {}; if (element is $Map<$Value, $Value>) { statusMap = element.$reified; } else { statusMap = element; } for (var element in statusMap.entries) { if (element.key .toString() .toLowerCase() .contains(status.toLowerCase().trim().trimLeft().trimRight())) { return element.value as int; } } } return 5; } static Future getHtmlViaWebview(String url, String rule) async { bool isOk = false; String? html; if (Platform.isWindows || Platform.isLinux) { final webview = await WebviewWindow.create( configuration: CreateConfiguration( windowHeight: 500, windowWidth: 500, userDataFolderWindows: await getWebViewPath(), ), ); webview ..setApplicationNameForUserAgent(defaultUserAgent) ..launch(url); await Future.doWhile(() async { await Future.delayed(const Duration(seconds: 10)); html = await decodeHtml(webview); if (xpathSelector(html!).query(rule).attrs.isEmpty) { html = await decodeHtml(webview); return true; } return false; }); html = await decodeHtml(webview); isOk = true; await Future.doWhile(() async { await Future.delayed(const Duration(seconds: 1)); if (isOk == true) { return false; } return true; }); html = await decodeHtml(webview); webview.close(); } else { HeadlessInAppWebView? headlessWebView; headlessWebView = HeadlessInAppWebView( onLoadStop: (controller, u) async { html = await controller.evaluateJavascript( source: "window.document.getElementsByTagName('html')[0].outerHTML;"); await Future.doWhile(() async { html = await controller.evaluateJavascript( source: "window.document.getElementsByTagName('html')[0].outerHTML;"); if (xpathSelector(html!).query(rule).attrs.isEmpty) { html = await controller.evaluateJavascript( source: "window.document.getElementsByTagName('html')[0].outerHTML;"); return true; } return false; }); html = await controller.evaluateJavascript( source: "window.document.getElementsByTagName('html')[0].outerHTML;"); isOk = true; headlessWebView!.dispose(); }, initialUrlRequest: URLRequest( url: WebUri.uri(Uri.parse(url)), ), ); headlessWebView.run(); await Future.doWhile(() async { await Future.delayed(const Duration(seconds: 1)); if (isOk == true) { return false; } return true; }); } return html!; } static List jsonDecodeToList(String source, int type) { return type == 0 ? jsonDecode(source) as List : (jsonDecode(source) as List).map((e) => jsonEncode(e)).toList(); } static String evalJs(String code) { try { JavascriptRuntime? flutterJs; flutterJs = getJavascriptRuntime(); final res = flutterJs.evaluate(code).stringResult; flutterJs.dispose(); return res; } catch (e) { _botToast(e.toString()); throw Exception(e); } } static List jsonPathToList( String source, String expression, int type) { try { if (jsonDecode(source) is List) { List values = []; final val = jsonDecode(source) as List; for (var element in val) { final mMap = element as Map?; Map map = {}; if (mMap != null) { map = mMap.map((key, value) => MapEntry(key.toString(), value)); } values.add(map); } List list = []; for (var data in values) { final jsonRes = JsonPath(expression).read(data); String val = ""; if (type == 0) { val = jsonRes.first.value.toString(); } else { val = jsonEncode(jsonRes.first.value); } list.add(val); } return list; } else { var map = json.decode(source); var values = JsonPath(expression).readValues(map); return values.map((e) { return e == null ? "{}" : json.encode(e); }).toList(); } } catch (e) { _botToast(e.toString()); throw Exception(e); } } static String getMapValue(String source, String attr, int type) { var map = json.decode(source) as Map; if (type == 0) { return map[attr] != null ? map[attr].toString() : ""; } return map[attr] != null ? jsonEncode(map[attr]) : ""; } static String jsonPathToString( String source, String expression, String join) { try { List values = []; if (jsonDecode(source) is List) { final val = jsonDecode(source) as List; for (var element in val) { final mMap = element as Map?; Map map = {}; if (mMap != null) { map = mMap.map((key, value) => MapEntry(key.toString(), value)); } values.add(map); } } else { final mMap = jsonDecode(source) as Map?; Map map = {}; if (mMap != null) { map = mMap.map((key, value) => MapEntry(key.toString(), value)); } values.add(map); } List listRg = []; for (var data in values) { final jsonRes = JsonPath(expression).readValues(data); List list = []; for (var element in jsonRes) { list.add(element); } listRg.add(list.join(join)); } return listRg.first; } catch (e) { _botToast(e.toString()); throw Exception(e); } } static Map jsonPathToMap(String source) { final mMap = jsonDecode(source) as Map?; Map map = {}; if (mMap != null) { map = mMap.map((key, value) => MapEntry(key.toString(), value)); } return map; } static List listParseDateTime( List value, String dateFormat, String dateFormatLocale) { List val = []; for (var element in value) { if (element is $Value) { val.add(element.$reified.toString()); } else { val.add(element); } } List valD = []; for (var date in val) { if (date.toString().isNotEmpty) { valD.add(parseChapterDate(date, dateFormat, dateFormatLocale)); } } return valD; } static String stringParse(String value) { return value; } static dynamic stringParseValue(dynamic value) { return value; } static String regExp( //RegExp(r'\[a\]'), "[123]") String expression, String source, String replace, int type, int group) { if (type == 0) { return expression.replaceAll(RegExp(source), replace); } return regCustomMatcher(expression, source, group); } static int intParse(String value) { return int.parse(value); } static bool listContain(List value, String element) { List val = []; for (var element in value) { val.add(element.$reified); } return val.contains(element); } static Future http(String url, int method) async { try { hp.StreamedResponse? res; String result = ""; final headersMap = jsonDecode(url)["headers"] as Map?; final sourceId = jsonDecode(url)["sourceId"] as int?; final bodyMap = jsonDecode(url)["body"] as Map?; Map body = {}; if (bodyMap != null) { body = bodyMap.map((key, value) => MapEntry(key.toString(), value)); } Map headers = {}; if (headersMap != null) { headers = headersMap .map((key, value) => MapEntry(key.toString(), value.toString())); } final source = sourceId != null ? isar.sources.getSync(sourceId) : null; if (source != null && source.hasCloudflare!) { final res = await cloudflareBypass( url: jsonDecode(url)["url"], sourceId: source.id.toString(), method: method); return res; } var request = hp.Request( method == 0 ? 'GET' : method == 1 ? 'POST' : method == 2 ? 'PUT' : 'DELETE', Uri.parse(jsonDecode(url)["url"])); if (bodyMap != null) { request.body = json.encode(body); } request.headers.addAll(headers); res = await request.send(); if (res.statusCode != 200) { result = "400"; } else if (res.statusCode == 200) { result = await res.stream.bytesToString(); } else { result = res.reasonPhrase!; } return result; } catch (e) { _botToast(e.toString()); return ""; } } static Future> gogoCdnExtractor(String url) async { return await GogoCdnExtractor().videosFromUrl( url, ); } static Future> doodExtractor(String url) async { return await DoodExtractor().videosFromUrl( url, ); } static Future> mp4UploadExtractor( String url, String? headers, String prefix, String suffix) async { Map newHeaders = {}; if (headers != null) { newHeaders = (jsonDecode(headers) as Map) .map((key, value) => MapEntry(key.toString(), value.toString())); } return await Mp4uploadExtractor() .videosFromUrl(url, newHeaders, prefix: prefix, suffix: suffix); } static Future> streamTapeExtractor(String url) async { return await StreamTapeExtractor().videosFromUrl( url, ); } static String subString(String text, String pattern, int type) { String result = ""; //substring before if (type == 0) { result = text.split(pattern).first; } else // substring after last if (type == 1) { result = text.split(pattern).last; } else if (type == 2) { result = text.substring(text.lastIndexOf(pattern) + 1); } return result; } static String parseChapterDate( String date, String dateFormat, String dateFormatLocale) { int parseRelativeDate(String date) { final number = int.tryParse(RegExp(r"(\d+)").firstMatch(date)!.group(0)!); if (number == null) return 0; final cal = DateTime.now(); if (WordSet([ "hari", "gün", "jour", "día", "dia", "day", "วัน", "ngày", "giorni", "أيام", "天" ]).anyWordIn(date)) { return cal.subtract(Duration(days: number)).millisecondsSinceEpoch; } else if (WordSet([ "jam", "saat", "heure", "hora", "hour", "ชั่วโมง", "giờ", "ore", "ساعة", "小时" ]).anyWordIn(date)) { return cal.subtract(Duration(hours: number)).millisecondsSinceEpoch; } else if (WordSet( ["menit", "dakika", "min", "minute", "minuto", "นาที", "دقائق"]) .anyWordIn(date)) { return cal.subtract(Duration(minutes: number)).millisecondsSinceEpoch; } else if (WordSet(["detik", "segundo", "second", "วินาที"]) .anyWordIn(date)) { return cal.subtract(Duration(seconds: number)).millisecondsSinceEpoch; } else if (WordSet(["week", "semana"]).anyWordIn(date)) { return cal.subtract(Duration(days: number * 7)).millisecondsSinceEpoch; } else if (WordSet(["month", "mes"]).anyWordIn(date)) { return cal.subtract(Duration(days: number * 30)).millisecondsSinceEpoch; } else if (WordSet(["year", "año"]).anyWordIn(date)) { return cal .subtract(Duration(days: number * 365)) .millisecondsSinceEpoch; } else { return 0; } } try { if (WordSet(["yesterday", "يوم واحد"]).startsWith(date)) { DateTime cal = DateTime.now().subtract(const Duration(days: 1)); cal = DateTime(cal.year, cal.month, cal.day); return cal.millisecondsSinceEpoch.toString(); } else if (WordSet(["today"]).startsWith(date)) { DateTime cal = DateTime.now(); cal = DateTime(cal.year, cal.month, cal.day); return cal.millisecondsSinceEpoch.toString(); } else if (WordSet(["يومين"]).startsWith(date)) { DateTime cal = DateTime.now().subtract(const Duration(days: 2)); cal = DateTime(cal.year, cal.month, cal.day); return cal.millisecondsSinceEpoch.toString(); } else if (WordSet(["ago", "atrás", "önce", "قبل"]).endsWith(date)) { return parseRelativeDate(date).toString(); } else if (WordSet(["hace"]).startsWith(date)) { return parseRelativeDate(date).toString(); } else if (date.contains(RegExp(r"\d(st|nd|rd|th)"))) { final cleanedDate = date .split(" ") .map((it) => it.contains(RegExp(r"\d\D\D")) ? it.replaceAll(RegExp(r"\D"), "") : it) .join(" "); return DateFormat(dateFormat, dateFormatLocale) .parse(cleanedDate) .millisecondsSinceEpoch .toString(); } else { return DateFormat(dateFormat, dateFormatLocale) .parse(date) .millisecondsSinceEpoch .toString(); } } catch (e) { final supportedLocales = DateFormat.allLocalesWithSymbols(); for (var locale in supportedLocales) { for (var dateFormat in _dateFormats) { try { initializeDateFormatting(locale); if (WordSet(["yesterday", "يوم واحد"]).startsWith(date)) { DateTime cal = DateTime.now().subtract(const Duration(days: 1)); cal = DateTime(cal.year, cal.month, cal.day); return cal.millisecondsSinceEpoch.toString(); } else if (WordSet(["today"]).startsWith(date)) { DateTime cal = DateTime.now(); cal = DateTime(cal.year, cal.month, cal.day); return cal.millisecondsSinceEpoch.toString(); } else if (WordSet(["يومين"]).startsWith(date)) { DateTime cal = DateTime.now().subtract(const Duration(days: 2)); cal = DateTime(cal.year, cal.month, cal.day); return cal.millisecondsSinceEpoch.toString(); } else if (WordSet(["ago", "atrás", "önce", "قبل"]) .endsWith(date)) { return parseRelativeDate(date).toString(); } else if (WordSet(["hace"]).startsWith(date)) { return parseRelativeDate(date).toString(); } else if (date.contains(RegExp(r"\d(st|nd|rd|th)"))) { final cleanedDate = date .split(" ") .map((it) => it.contains(RegExp(r"\d\D\D")) ? it.replaceAll(RegExp(r"\D"), "") : it) .join(" "); return DateFormat(dateFormat, locale) .parse(cleanedDate) .millisecondsSinceEpoch .toString(); } else { return DateFormat(dateFormat, locale) .parse(date) .millisecondsSinceEpoch .toString(); } } catch (_) {} } } _botToast(e.toString()); throw Exception(e); } } static Future> sibnetExtractor(String url) async { return await SibnetExtractor().videosFromUrl( url, ); } static Future> sendVidExtractor( String url, String? headers, String prefix) async { Map newHeaders = {}; if (headers != null) { newHeaders = (jsonDecode(headers) as Map) .map((key, value) => MapEntry(key.toString(), value.toString())); } return await SendvidExtractor(newHeaders) .videosFromUrl(url, prefix: prefix); } static Future> myTvExtractor(String url) async { return await MytvExtractor().videosFromUrl( url, ); } static List