diff --git a/lib/main.dart b/lib/main.dart index da40158a..efd9f52c 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,5 +1,4 @@ // ignore_for_file: depend_on_referenced_packages -import 'dart:developer'; import 'dart:io'; import 'package:fast_cached_network_image/fast_cached_network_image.dart'; import 'package:flex_color_scheme/flex_color_scheme.dart'; @@ -16,7 +15,7 @@ import 'package:mangayomi/models/history.dart'; import 'package:mangayomi/utils/constant.dart'; import 'package:mangayomi/models/manga.dart'; import 'package:mangayomi/router/router.dart'; -import 'package:mangayomi/source/source_model.dart'; +import 'package:mangayomi/models/source_model.dart'; import 'package:mangayomi/views/manga/reader/providers/reader_controller_provider.dart'; import 'package:mangayomi/views/more/settings/appearance/providers/blend_level_state_provider.dart'; import 'package:mangayomi/views/more/settings/appearance/providers/flex_scheme_color_state_provider.dart'; diff --git a/lib/source/source_model.dart b/lib/models/source_model.dart similarity index 100% rename from lib/source/source_model.dart rename to lib/models/source_model.dart diff --git a/lib/source/source_model.g.dart b/lib/models/source_model.g.dart similarity index 100% rename from lib/source/source_model.g.dart rename to lib/models/source_model.g.dart diff --git a/lib/providers/hive_provider.dart b/lib/providers/hive_provider.dart index a19be69a..19dae2c3 100644 --- a/lib/providers/hive_provider.dart +++ b/lib/providers/hive_provider.dart @@ -1,7 +1,7 @@ import 'package:hive_flutter/hive_flutter.dart'; import 'package:mangayomi/models/download_model.dart'; import 'package:mangayomi/utils/constant.dart'; -import 'package:mangayomi/source/source_model.dart'; +import 'package:mangayomi/models/source_model.dart'; import 'package:mangayomi/views/manga/reader/providers/reader_controller_provider.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; diff --git a/lib/services/get_manga_chapter_url.dart b/lib/services/get_manga_chapter_url.dart index 87c4db56..5fe93e94 100644 --- a/lib/services/get_manga_chapter_url.dart +++ b/lib/services/get_manga_chapter_url.dart @@ -1,31 +1,28 @@ -// ignore_for_file: depend_on_referenced_packages +// ignore_for_file: depend_o import 'dart:async'; -import 'dart:convert'; import 'dart:io'; -import 'package:html/dom.dart'; -import 'package:http/http.dart' as http; -import 'package:html/dom.dart' as dom; import 'package:mangayomi/models/chapter.dart'; -import 'package:mangayomi/models/comick/chapter_page_comick.dart'; +import 'package:mangayomi/models/source_model.dart'; import 'package:mangayomi/providers/hive_provider.dart'; import 'package:mangayomi/providers/storage_provider.dart'; -import 'package:mangayomi/services/http_service/cloudflare/cloudflare_bypass.dart'; -import 'package:mangayomi/services/get_popular_manga.dart'; -import 'package:mangayomi/services/http_service/http_service.dart'; -import 'package:mangayomi/source/source_model.dart'; +import 'package:mangayomi/sources/src/all/comick/src/comick.dart'; +import 'package:mangayomi/sources/src/en/mangahere/src/mangahere.dart'; +import 'package:mangayomi/sources/src/fr/japscan/src/japscan.dart'; +import 'package:mangayomi/sources/src/fr/mangakawaii/src/mangakawaii.dart'; +import 'package:mangayomi/sources/src/multi/mangathemesia/src/mangathemesia.dart'; +import 'package:mangayomi/sources/src/multi/mmrcms/src/mmrcms.dart'; +import 'package:mangayomi/sources/utils/utils.dart'; import 'package:mangayomi/utils/reg_exp_matcher.dart'; import 'package:mangayomi/views/more/settings/providers/incognito_mode_state_provider.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; -import 'package:flutter_js/flutter_js.dart'; -import 'package:collection/collection.dart'; part 'get_manga_chapter_url.g.dart'; class GetMangaChapterUrlModel { Directory? path; - List urll = []; + List pageUrls = []; List isLocaleList = []; GetMangaChapterUrlModel( - {required this.path, required this.urll, required this.isLocaleList}); + {required this.path, required this.pageUrls, required this.isLocaleList}); } @riverpod @@ -33,127 +30,35 @@ Future getMangaChapterUrl( GetMangaChapterUrlRef ref, { required Chapter chapter, }) async { - bool isOk = false; Directory? path; - List urll = []; - String? baseUrl; - String? zjsUrl; + List pageUrls = []; final manga = chapter.manga.value!; - zjs() async { - final html = await cloudflareBypassHtml( - url: zjsUrl!, source: manga.source!.toLowerCase(), useUserAgent: true); - dom.Document htmll = dom.Document.html(baseUrl!); - final strings = html - .replaceAll(RegExp(r'\\[(.*?)\\]'), '') - .split(",") - .map((s) => s.trim().replaceAll("'", "").split('').reversed.join()); - final stringLookupTables = strings - .where((s) => - s.length == 62 && - s.split('').toSet().toList().sorted().join() == - "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz") - .toList(); - - if (stringLookupTables.length != 2) { - throw Exception("Expected only two lookup tables in ZJS"); - } - - final scrambledData = - htmll.getElementById("data")!.attributes['data-data']!; - - for (var i = 0; i <= 1; i++) { - final otherIndex = i == 0 ? 1 : 0; - - final lookupTable = Map.fromIterables(stringLookupTables[i].split(''), - stringLookupTables[otherIndex].split('')); - try { - final unscrambledData = scrambledData - .split('') - .map((char) => lookupTable[char] ?? char) - .join(); - final decoded = utf8.decode(base64.decode(unscrambledData)); - final data = jsonDecode(decoded); - urll = data["imagesLink"].map((it) => it).toList(); - } catch (_) {} - } - isOk = true; - } - List isLocaleList = []; String source = manga.source!.toLowerCase(); - List pagesUrl = ref.watch(hiveBoxMangaProvider).get( + List hivePagesUrls = ref.watch(hiveBoxMangaProvider).get( "${manga.lang}-${manga.source}/${manga.name}/${chapter.name}-pageurl", defaultValue: []); final incognitoMode = ref.watch(incognitoModeStateProvider); path = await StorageProvider().getMangaChapterDirectory(chapter); - if (pagesUrl.isNotEmpty) { - urll = pagesUrl; + if (hivePagesUrls.isNotEmpty) { + pageUrls = hivePagesUrls; } /*********/ /*comick*/ /********/ - else if (getWpMangTypeSource(source) == TypeSource.comick) { - String mangaId = chapter.url!.split('/').last.split('-').first; - final response = await httpGet( - url: 'https://api.comick.fun/chapter/$mangaId?tachiyomi=true', - source: source, - resDom: false) as String?; - var data = jsonDecode(response!) as Map; - var page = ChapterPageComick.fromJson(data); - for (var url in page.chapter!.images!) { - urll.add(url.url); - } + else if (getWpMangTypeSource(source) == TypeSource.comick) { + pageUrls = await Comick().getMangaChapterUrl(chapter: chapter); } + /*************/ /*mangathemesia*/ /**************/ else if (getWpMangTypeSource(source) == TypeSource.mangathemesia) { - final dom = await httpGet( - useUserAgent: true, - url: chapter.url!, - source: source, - resDom: true) as Document?; - if (dom!.querySelectorAll('#readerarea').isNotEmpty) { - final ta = - dom.querySelectorAll('#readerarea').map((e) => e.outerHtml).toList(); - final RegExp regex = RegExp(r']+src="([^"]+)"'); - final Iterable matches = regex.allMatches(ta.first); - - final List urls = matches.map((m) => m.group(1)).toList(); - Iterable matchess = []; - if (dom.querySelectorAll(' #select-paged ').isNotEmpty) { - final ee = dom - .querySelectorAll(' #select-paged ') - .map((e) => e.outerHtml) - .toList(); - final RegExp regexx = RegExp(r'value="([^"]+)"'); - matchess = regexx.allMatches(ee.first); - } - - final List urlss = matchess.map((m) => m.group(1)).toList(); - if (urls.length == 1 && urls.isNotEmpty) { - for (var i = 0; i < urlss.length; i++) { - if (urlss[i]!.length == 1) { - urll.add( - urls.first!.replaceAll("001", '00${int.parse(urlss[i]!) + 1}')); - } else if (urlss[i]!.length == 2) { - urll.add( - urls.first!.replaceAll("001", '0${int.parse(urlss[i]!) + 1}')); - } else if (urlss[i]!.length == 3) { - urll.add( - urls.first!.replaceAll("001", '${int.parse(urlss[i]!) + 1}')); - } - } - } else if (urls.length > 1 && urls.isNotEmpty) { - for (var tt in urls) { - urll.add(tt); - } - } - } + pageUrls = await MangaThemeSia().getMangaChapterUrl(chapter: chapter); } /***********/ @@ -161,25 +66,7 @@ Future getMangaChapterUrl( /***********/ else if (source == 'mangakawaii') { - final response = - await httpGet(url: chapter.url!, source: source, resDom: false) - as String?; - var chapterSlug = RegExp("""var chapter_slug = "([^"]*)";""") - .allMatches(response!) - .last - .group(1); - var mangaSlug = RegExp("""var oeuvre_slug = "([^"]*)";""") - .allMatches(response) - .last - .group(1); - var pages = RegExp('''"page_image":"([^"]*)"''') - .allMatches(response) - .map((e) => e.group(1)); - - for (var tt in pages) { - urll.add( - 'https://cdn.mangakawaii.pics/uploads/manga/$mangaSlug/chapters_fr/$chapterSlug/$tt'); - } + pageUrls = await MangaKawaii().getMangaChapterUrl(chapter: chapter); } /***********/ @@ -187,168 +74,32 @@ Future getMangaChapterUrl( /***********/ else if (getWpMangTypeSource(source) == TypeSource.mmrcms) { - final dom = await httpGet( - useUserAgent: true, - url: chapter.url!, - source: source, - resDom: true) as Document?; - if (dom!.querySelectorAll('#all > .img-responsive').isNotEmpty) { - urll = dom.querySelectorAll('#all > .img-responsive').map((e) { - final RegExp regexx = RegExp(r'data-src="([^"]+)"'); - if (manga.source!.toLowerCase() == 'jpmangas' || - manga.source!.toLowerCase() == 'fr scan') { - return regexx - .allMatches(e.outerHtml) - .first - .group(1)! - .replaceAll('//', 'https://') - .replaceAll(RegExp(r"\s+\b|\b\s"), ""); - } - return regexx - .allMatches(e.outerHtml) - .first - .group(1)! - .replaceAll(RegExp(r"\s+\b|\b\s"), ""); - }).toList(); - } + pageUrls = await Mmrcms().getMangaChapterUrl(chapter: chapter); } /***********/ /*mangahere*/ /***********/ + else if (source == 'mangahere') { - JavascriptRuntime? flutterJs; - flutterJs = getJavascriptRuntime(); - extractSecretKey(String response, JavascriptRuntime? flutterJs) { - var secretKeyScriptLocation = - response.indexOf("eval(function(p,a,c,k,e,d)"); - var secretKeyScriptEndLocation = - response.indexOf("", secretKeyScriptLocation); - var secretKeyScript = response - .substring(secretKeyScriptLocation, secretKeyScriptEndLocation) - .replaceAll("eval", ""); - var secretKeyDeobfuscatedScript = - flutterJs!.evaluate(secretKeyScript).toString(); - var secretKeyStartLoc = secretKeyDeobfuscatedScript.indexOf("'"); - var secretKeyEndLoc = secretKeyDeobfuscatedScript.indexOf(";"); - - var secretKeyResultScript = secretKeyDeobfuscatedScript.substring( - secretKeyStartLoc, secretKeyEndLoc); - - return secretKeyResultScript; - } - - var link = "http://www.mangahere.cc${chapter.url!}"; - final response = - await httpGet(url: link, source: source, resDom: false) as String?; - - dom.Document htmll = dom.Document.html(response!); - int? pagesNumber = -1; - if (htmll.querySelectorAll('body > div > div > span > a:').isNotEmpty) { - final ta = htmll - .querySelectorAll('body > div > div > span > a:') - .map((e) => e.text.trim()) - .toList(); - ta.removeLast(); - pagesNumber = int.parse(ta.last); - } - if (pagesNumber == -1) { - final script = htmll - .getElementsByTagName("script") - .firstWhere((e) => e.innerHtml.contains( - "function(p,a,c,k,e,d)", - )) - .innerHtml - .replaceAll("eval", ""); - - String deobfuscatedScript = flutterJs.evaluate(script).toString(); - List urlss = deobfuscatedScript - .substring( - deobfuscatedScript.indexOf("newImgs=['") + "newImgs=['".length, - deobfuscatedScript.indexOf("'];")) - .split("','"); - for (var tt in urlss) { - urll.add("https:$tt"); - } - } else { - var secretKey = extractSecretKey(response, flutterJs); - - var chapterIdStartLoc = response.indexOf("chapterid"); - var chapterId = response - .substring( - chapterIdStartLoc + 11, response.indexOf(";", chapterIdStartLoc)) - .trim(); - - var pageBase = link.substring(0, link.lastIndexOf("/")); - - for (int i = 1; i <= pagesNumber; i++) { - var pageLink = - "$pageBase/chapterfun.ashx?cid=$chapterId&page=$i&key=$secretKey"; - var responseText = ""; - - for (int tr = 1; tr <= 3; tr++) { - var response = await http.get(Uri.parse(pageLink), headers: { - "Referer": link, - "Accept": "*/*", - "Accept-Language": "en-US,en;q=0.9", - "Connection": "keep-alive", - "Host": "www.mangahere.cc", - "X-Requested-With": "XMLHttpRequest" - }); - responseText = response.body; - if (responseText.isNotEmpty) { - break; - } else { - secretKey = ""; - } - } - - var deobfuscatedScript = - flutterJs.evaluate(responseText.replaceAll("eval", "")).toString(); - - var baseLinkStartPos = deobfuscatedScript.indexOf("pix=") + 5; - var baseLinkEndPos = - deobfuscatedScript.indexOf(";", baseLinkStartPos) - 1; - var baseLink = - deobfuscatedScript.substring(baseLinkStartPos, baseLinkEndPos); - - var imageLinkStartPos = deobfuscatedScript.indexOf("pvalue=") + 9; - var imageLinkEndPos = - deobfuscatedScript.indexOf("\"", imageLinkStartPos); - var imageLink = - deobfuscatedScript.substring(imageLinkStartPos, imageLinkEndPos); - urll.add("https:$baseLink$imageLink"); - } - - flutterJs.dispose(); - } - } else if (source == 'japscan') { - final response = await httpGet( - useUserAgent: true, - url: chapter.url!, - source: source, - resDom: false) as String?; - RegExp regex = RegExp(r'", secretKeyScriptLocation); + var secretKeyScript = response + .substring(secretKeyScriptLocation, secretKeyScriptEndLocation) + .replaceAll("eval", ""); + var secretKeyDeobfuscatedScript = + flutterJs!.evaluate(secretKeyScript).toString(); + var secretKeyStartLoc = secretKeyDeobfuscatedScript.indexOf("'"); + var secretKeyEndLoc = secretKeyDeobfuscatedScript.indexOf(";"); + + var secretKeyResultScript = secretKeyDeobfuscatedScript.substring( + secretKeyStartLoc, secretKeyEndLoc); + + return secretKeyResultScript; + } + + var link = "http://www.mangahere.cc${chapter.url!}"; + final response = + await httpGet(url: link, source: "managhere", resDom: false) as String?; + + dom.Document htmll = dom.Document.html(response!); + int? pagesNumber = -1; + if (htmll.querySelectorAll('body > div > div > span > a:').isNotEmpty) { + final ta = htmll + .querySelectorAll('body > div > div > span > a:') + .map((e) => e.text.trim()) + .toList(); + ta.removeLast(); + pagesNumber = int.parse(ta.last); + } + if (pagesNumber == -1) { + final script = htmll + .getElementsByTagName("script") + .firstWhere((e) => e.innerHtml.contains( + "function(p,a,c,k,e,d)", + )) + .innerHtml + .replaceAll("eval", ""); + + String deobfuscatedScript = flutterJs.evaluate(script).toString(); + List urlss = deobfuscatedScript + .substring( + deobfuscatedScript.indexOf("newImgs=['") + "newImgs=['".length, + deobfuscatedScript.indexOf("'];")) + .split("','"); + for (var tt in urlss) { + pageUrls.add("https:$tt"); + } + flutterJs.dispose(); + } else { + var secretKey = extractSecretKey(response, flutterJs); + + var chapterIdStartLoc = response.indexOf("chapterid"); + var chapterId = response + .substring( + chapterIdStartLoc + 11, response.indexOf(";", chapterIdStartLoc)) + .trim(); + + var pageBase = link.substring(0, link.lastIndexOf("/")); + + for (int i = 1; i <= pagesNumber; i++) { + var pageLink = + "$pageBase/chapterfun.ashx?cid=$chapterId&page=$i&key=$secretKey"; + var responseText = ""; + + for (int tr = 1; tr <= 3; tr++) { + var response = await http.get(Uri.parse(pageLink), headers: { + "Referer": link, + "Accept": "*/*", + "Accept-Language": "en-US,en;q=0.9", + "Connection": "keep-alive", + "Host": "www.mangahere.cc", + "X-Requested-With": "XMLHttpRequest" + }); + responseText = response.body; + if (responseText.isNotEmpty) { + break; + } else { + secretKey = ""; + } + } + + var deobfuscatedScript = + flutterJs.evaluate(responseText.replaceAll("eval", "")).toString(); + + var baseLinkStartPos = deobfuscatedScript.indexOf("pix=") + 5; + var baseLinkEndPos = + deobfuscatedScript.indexOf(";", baseLinkStartPos) - 1; + var baseLink = + deobfuscatedScript.substring(baseLinkStartPos, baseLinkEndPos); + + var imageLinkStartPos = deobfuscatedScript.indexOf("pvalue=") + 9; + var imageLinkEndPos = + deobfuscatedScript.indexOf("\"", imageLinkStartPos); + var imageLink = + deobfuscatedScript.substring(imageLinkStartPos, imageLinkEndPos); + pageUrls.add("https:$baseLink$imageLink"); + } + + flutterJs.dispose(); + } + return pageUrls; + } +} diff --git a/lib/sources/src/fr/japscan/japscan_source.dart b/lib/sources/src/fr/japscan/japscan_source.dart new file mode 100644 index 00000000..c4eecba2 --- /dev/null +++ b/lib/sources/src/fr/japscan/japscan_source.dart @@ -0,0 +1,11 @@ +import 'package:mangayomi/models/source_model.dart'; + +SourceModel japscanSource = SourceModel( + sourceName: "Japscan", + url: "https://japscan.lol", + lang: "fr", + typeSource: TypeSource.single, + logoUrl: '', + isCloudflare: true, + dateFormat: "d MMM yyyy", + dateFormatLocale: "en_US"); diff --git a/lib/sources/src/fr/japscan/src/japscan.dart b/lib/sources/src/fr/japscan/src/japscan.dart new file mode 100644 index 00000000..30e9dda2 --- /dev/null +++ b/lib/sources/src/fr/japscan/src/japscan.dart @@ -0,0 +1,238 @@ +// ignore_for_file: depend_on_referenced_packages + +import 'dart:convert'; +import 'dart:developer'; +import 'package:html/dom.dart' as dom; +import 'package:html/dom.dart'; +import 'package:mangayomi/models/chapter.dart'; +import 'package:mangayomi/services/http_service/cloudflare/cloudflare_bypass.dart'; +import 'package:mangayomi/services/http_service/http_service.dart'; +import 'package:mangayomi/sources/service/service.dart'; +import 'package:mangayomi/sources/utils/utils.dart'; +import 'package:collection/collection.dart'; + +class Japscan extends MangaYomiServices { + @override + Future getMangaDetail( + {required String imageUrl, + required String url, + required String title, + required String lang, + required String source}) async { + final dom = + await httpGet(url: url, source: source, resDom: true) as Document?; + if (dom!.querySelectorAll('.col-7 > p').isNotEmpty) { + final images = + dom.querySelectorAll('.col-5 ').map((e) => e.outerHtml).toList(); + RegExp exp = RegExp(r'src="([^"]+)"'); + + String? srcValue = exp.firstMatch(images[0])?.group(1); + imageUrl = 'https://www.japscan.lol$srcValue'; + + if (dom.querySelectorAll('.col-7 > p').isNotEmpty) { + final stat = dom + .querySelectorAll('.col-7 > p') + .where((element) => element.innerHtml.contains('Statut:')) + .map((e) => e.text) + .toList(); + if (stat.isNotEmpty) { + status = stat[0].replaceAll('Statut:', '').trim(); + } + + final auth = dom + .querySelectorAll('.col-7 > p') + .where((element) => element.innerHtml.contains('Auteur(s):')) + .map((e) => e.text) + .toList(); + if (auth.isNotEmpty) { + author = auth[0].replaceAll('Auteur(s):', '').trim(); + } + } else { + author = ""; + status = ""; + } + + final genres = dom + .querySelectorAll('.col-7 > p') + .where((element) => element.innerHtml.contains('Genre(s):')) + .map((e) => e.text.replaceAll('Genre(s):', '').trim()) + .toList(); + if (genres.isNotEmpty) { + for (var ok in genres[0].split(',')) { + genre.add(ok); + } + } + + final synop = dom + .querySelectorAll('p.list-group-item ') + .map((e) => e.text.trim()) + .toList(); + if (synop.isNotEmpty) { + description = synop[0]; + } + } + + final urls = + dom.querySelectorAll('.col-8 ').map((e) => e.outerHtml).toList(); + + for (var ok in urls) { + RegExp exp = RegExp(r'href="([^"]+)"'); + + String? srcValue = exp.firstMatch(ok)?.group(1); + chapterUrl.add('https://www.japscan.lol$srcValue'); + } + + final chapterTitlee = + dom.querySelectorAll('.col-8').map((e) => e.text.trim()).toList(); + + if (chapterTitlee.isNotEmpty) { + for (var ok in chapterTitlee) { + chapterTitle.add(ok); + } + } + + final chapterDatee = + dom.querySelectorAll('.col-4').map((e) => e.text.trim()).toList(); + if (chapterDatee.isNotEmpty) { + for (var ok in chapterDatee) { + chapterDate.add(parseDate(ok, source)); + } + } + return mangadetailRes( + imageUrl: imageUrl, url: url, title: title, source: source); + } + + @override + Future getPopularManga( + {required String source, required int page}) async { + final dom = await httpGet( + url: "https://www.japscan.lol/", + source: source, + resDom: true) as Document?; + if (dom!.querySelectorAll('#top_mangas_week > ul > li ').isNotEmpty) { + final urls = dom + .querySelectorAll('#top_mangas_week > ul > li > a') + .where((e) => e.attributes['href'].toString().contains('manga')) + .map((e) => e.attributes['href']) + .toList(); + for (var ok in urls) { + url.add("https://www.japscan.lol$ok"); + } + name = dom + .querySelectorAll( + '#top_mangas_week > ul > li > a.text-dark.font-weight-bold') + .map((e) => e.innerHtml) + .toList(); + for (var ia in name) { + image.add(""); + } + } + return mangaRes(); + } + + @override + Future searchManga( + {required String source, required String query}) async { + final dom = await httpGet( + url: "https://www.google.com/search?q=${query.toLowerCase()}+japscan", + source: source, + resDom: true) as Document?; + + if (dom!.querySelectorAll("div > div > div > div > div > a").isNotEmpty) { + final urls = dom + .querySelectorAll("div > div > div > div > div > a") + .where((e) => e.attributes.containsKey('href')) + .where((element) => + element.text.toLowerCase().contains("https://www.japscan.")) + .map((e) => e.attributes['href'] + .toString() + .replaceAll('lecture-en-ligne', 'manga') + .split("/")) + .toList(); + List tt = []; + List ta = []; + for (var ok in urls) { + tt.add("${ok[0]}//${ok[2]}/${ok[3]}/${ok[4]}/"); + ta.add(ok[4] + .replaceAll('-', " ") + .toString() + .split(' ') + .map((word) => + word.substring(0, 1).toUpperCase() + word.substring(1)) + .join(' ')); + } + name = ta.toSet().toList(); + url = tt.toSet().toList(); + for (var a in url) { + image.add(""); + } + } + return mangaRes(); + } + + @override + Future> getMangaChapterUrl({required Chapter chapter}) async { + final response = await httpGet( + useUserAgent: true, + url: chapter.url!, + source: "japscan", + resDom: false) as String?; + RegExp regex = RegExp(r'