mirror of
https://github.com/kodjodevf/mangayomi-extensions.git
synced 2026-02-14 10:51:17 +00:00
Reorganize folders
This commit is contained in:
1676
dart/manga/src/all/batoto/batoto.dart
Normal file
1676
dart/manga/src/all/batoto/batoto.dart
Normal file
File diff suppressed because it is too large
Load Diff
BIN
dart/manga/src/all/batoto/icon.png
Normal file
BIN
dart/manga/src/all/batoto/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.6 KiB |
132
dart/manga/src/all/batoto/sources.dart
Normal file
132
dart/manga/src/all/batoto/sources.dart
Normal file
@@ -0,0 +1,132 @@
|
||||
import '../../../../../model/source.dart';
|
||||
|
||||
const _batotoVersion = "0.0.75";
|
||||
const _batotoSourceCodeUrl =
|
||||
"https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/$branchName/dart/manga/src/all/batoto/batoto.dart";
|
||||
|
||||
String _iconUrl =
|
||||
"https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/$branchName/dart/manga/src/all/batoto/icon.png";
|
||||
const _baseUrl = 'https://bato.to';
|
||||
const _isNsfw = true;
|
||||
|
||||
List<String> _languages = [
|
||||
"all",
|
||||
"en",
|
||||
"ar",
|
||||
"bg",
|
||||
"zh",
|
||||
"cs",
|
||||
"da",
|
||||
"nl",
|
||||
"fil",
|
||||
"fi",
|
||||
"fr",
|
||||
"de",
|
||||
"el",
|
||||
"he",
|
||||
"hi",
|
||||
"hu",
|
||||
"id",
|
||||
"it",
|
||||
"ja",
|
||||
"ko",
|
||||
"ms",
|
||||
"pl",
|
||||
"pt",
|
||||
"pt-br",
|
||||
"ro",
|
||||
"ru",
|
||||
"es",
|
||||
"es-419",
|
||||
"sv",
|
||||
"th",
|
||||
"tr",
|
||||
"uk",
|
||||
"vi",
|
||||
"af",
|
||||
"sq",
|
||||
"am",
|
||||
"hy",
|
||||
"az",
|
||||
"be",
|
||||
"bn",
|
||||
"bs",
|
||||
"my",
|
||||
"km",
|
||||
"ca",
|
||||
"ceb",
|
||||
"zh-hk",
|
||||
"zh-tw",
|
||||
"hr",
|
||||
"en-us",
|
||||
"eo",
|
||||
"et",
|
||||
"fo",
|
||||
"ka",
|
||||
"gn",
|
||||
"gu",
|
||||
"ht",
|
||||
"ha",
|
||||
"is",
|
||||
"ig",
|
||||
"ga",
|
||||
"jv",
|
||||
"kn",
|
||||
"kk",
|
||||
"ku",
|
||||
"ky",
|
||||
"lo",
|
||||
"lv",
|
||||
"lt",
|
||||
"lb",
|
||||
"mk",
|
||||
"mg",
|
||||
"ml",
|
||||
"mt",
|
||||
"mi",
|
||||
"mr",
|
||||
"mn",
|
||||
"ne",
|
||||
"no",
|
||||
"ny",
|
||||
"ps",
|
||||
"fa",
|
||||
"rm",
|
||||
"sm",
|
||||
"sr",
|
||||
"sh",
|
||||
"st",
|
||||
"sn",
|
||||
"sd",
|
||||
"si",
|
||||
"sk",
|
||||
"sl",
|
||||
"so",
|
||||
"sw",
|
||||
"tg",
|
||||
"ta",
|
||||
"ti",
|
||||
"to",
|
||||
"tk",
|
||||
"ur",
|
||||
"uz",
|
||||
"yo",
|
||||
"zu",
|
||||
"eu",
|
||||
"pt-PT",
|
||||
];
|
||||
|
||||
List<Source> get batotoSourcesList => _batotoSourcesList;
|
||||
List<Source> _batotoSourcesList = _languages
|
||||
.map((e) => Source(
|
||||
name: 'Bato.to',
|
||||
baseUrl: _baseUrl,
|
||||
lang: e,
|
||||
typeSource: "bato.to",
|
||||
iconUrl: _iconUrl,
|
||||
dateFormat: "MMM dd,yyyy",
|
||||
isNsfw: _isNsfw,
|
||||
dateFormatLocale: "en",
|
||||
version: _batotoVersion,
|
||||
sourceCodeUrl: _batotoSourceCodeUrl))
|
||||
.toList();
|
||||
658
dart/manga/src/all/comick/comick.dart
Normal file
658
dart/manga/src/all/comick/comick.dart
Normal file
@@ -0,0 +1,658 @@
|
||||
import 'package:mangayomi/bridge_lib.dart';
|
||||
import 'dart:convert';
|
||||
|
||||
class ComickFun extends MProvider {
|
||||
ComickFun({required this.source});
|
||||
|
||||
MSource source;
|
||||
|
||||
final Client client = Client(source);
|
||||
|
||||
@override
|
||||
Future<MPages> getPopular(int page) async {
|
||||
final res = (await client.get(
|
||||
Uri.parse(
|
||||
"${source.apiUrl}/v1.0/search?sort=follow&page=$page&tachiyomi=true"),
|
||||
headers: getHeader(source.baseUrl)))
|
||||
.body;
|
||||
return mangaRes(res);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<MPages> getLatestUpdates(int page) async {
|
||||
final res = (await client.get(
|
||||
Uri.parse(
|
||||
"${source.apiUrl}/v1.0/search?sort=uploaded&page=$page&tachiyomi=true"),
|
||||
headers: getHeader(source.baseUrl)))
|
||||
.body;
|
||||
return mangaRes(res);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<MPages> search(String query, int page, FilterList filterList) async {
|
||||
final filters = filterList.filters;
|
||||
String url = "";
|
||||
if (query.isNotEmpty) {
|
||||
url = "${source.apiUrl}/v1.0/search?q=$query&tachiyomi=true";
|
||||
} else {
|
||||
url = "${source.apiUrl}/v1.0/search";
|
||||
for (var filter in filters) {
|
||||
if (filter.type == "CompletedFilter") {
|
||||
if (filter.state) {
|
||||
url += "${ll(url)}completed=true";
|
||||
}
|
||||
} else if (filter.type == "GenreFilter") {
|
||||
final included = (filter.state as List)
|
||||
.where((e) => e.state == 1 ? true : false)
|
||||
.toList();
|
||||
final excluded = (filter.state as List)
|
||||
.where((e) => e.state == 2 ? true : false)
|
||||
.toList();
|
||||
if (included.isNotEmpty) {
|
||||
for (var val in included) {
|
||||
url += "${ll(url)}genres=${val.value}";
|
||||
}
|
||||
}
|
||||
if (excluded.isNotEmpty) {
|
||||
for (var val in excluded) {
|
||||
url += "${ll(url)}excludes=${val.value}";
|
||||
}
|
||||
}
|
||||
} else if (filter.type == "DemographicFilter") {
|
||||
final included = (filter.state as List)
|
||||
.where((e) => e.state == 1 ? true : false)
|
||||
.toList();
|
||||
if (included.isNotEmpty) {
|
||||
for (var val in included) {
|
||||
url += "${ll(url)}demographic=${val.value}";
|
||||
}
|
||||
}
|
||||
} else if (filter.type == "TypeFilter") {
|
||||
final country = (filter.state as List).where((e) => e.state).toList();
|
||||
if (country.isNotEmpty) {
|
||||
for (var coun in country) {
|
||||
url += "${ll(url)}country=${coun.value}";
|
||||
}
|
||||
}
|
||||
} else if (filter.type == "SortFilter") {
|
||||
url += "${ll(url)}sort=${filter.values[filter.state].value}";
|
||||
} else if (filter.type == "StatusFilter") {
|
||||
url += "${ll(url)}status=${filter.values[filter.state].value}";
|
||||
} else if (filter.type == "CreatedAtFilter") {
|
||||
if (filter.state > 0) {
|
||||
url += "${ll(url)}time=${filter.values[filter.state].value}";
|
||||
}
|
||||
} else if (filter.type == "MinimumFilter") {
|
||||
if (filter.state.isNotEmpty) {
|
||||
url += "${ll(url)}minimum=${filter.state}";
|
||||
}
|
||||
} else if (filter.type == "FromYearFilter") {
|
||||
if (filter.state.isNotEmpty) {
|
||||
url += "${ll(url)}from=${filter.state}";
|
||||
}
|
||||
} else if (filter.type == "ToYearFilter") {
|
||||
if (filter.state.isNotEmpty) {
|
||||
url += "${ll(url)}to=${filter.state}";
|
||||
}
|
||||
} else if (filter.type == "TagFilter") {
|
||||
if (filter.state.isNotEmpty) {
|
||||
final tags = (filter.state as String).split(",");
|
||||
for (var tag in tags) {
|
||||
url += "${ll(url)}tags=$tag";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
url += "${ll(url)}page=$page&tachiyomi=true";
|
||||
}
|
||||
|
||||
final res =
|
||||
(await client.get(Uri.parse(url), headers: getHeader(source.baseUrl)))
|
||||
.body;
|
||||
return mangaRes(res);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<MManga> getDetail(String url) async {
|
||||
final statusList = [
|
||||
{"1": 0, "2": 1, "3": 3, "4": 2}
|
||||
];
|
||||
final headers = getHeader(source.baseUrl);
|
||||
final res = (await client.get(
|
||||
Uri.parse(
|
||||
"${source.apiUrl}${url.replaceAll("#", '')}?tachiyomi=true"),
|
||||
headers: headers))
|
||||
.body;
|
||||
MManga manga = MManga();
|
||||
manga.author = jsonPathToString(res, r'$.authors[*].name', '');
|
||||
manga.genre = jsonPathToString(
|
||||
res, r'$.comic.md_comic_md_genres[*].md_genres.name', "_.")
|
||||
.split("_.");
|
||||
manga.description = jsonPathToString(res, r'$..desc', '');
|
||||
manga.status =
|
||||
parseStatus(jsonPathToString(res, r'$..comic.status', ''), statusList);
|
||||
final chapUrlReq =
|
||||
"${source.apiUrl}${url.replaceAll("#", '')}chapters?lang=${source.lang}&tachiyomi=true&page=1";
|
||||
final request =
|
||||
(await client.get(Uri.parse(chapUrlReq), headers: headers)).body;
|
||||
var total = jsonPathToString(request, r'$.total', '');
|
||||
final chapterLimit = int.parse(total);
|
||||
final newChapUrlReq =
|
||||
"${source.apiUrl}${url.replaceAll("#", '')}chapters?limit=$chapterLimit&lang=${source.lang}&tachiyomi=true&page=1";
|
||||
|
||||
final newRequest =
|
||||
(await client.get(Uri.parse(newChapUrlReq), headers: headers)).body;
|
||||
|
||||
final chapsUrls =
|
||||
jsonPathToString(newRequest, r'$.chapters[*].hid', "_.").split("_.");
|
||||
final chapDate =
|
||||
jsonPathToString(newRequest, r'$.chapters[*].created_at', "_.")
|
||||
.split("_.");
|
||||
final chaptersVolumes =
|
||||
jsonPathToString(newRequest, r'$.chapters[*].vol', "_.").split("_.");
|
||||
final chaptersScanlators =
|
||||
jsonPathToString(newRequest, r'$.chapters[*].group_name', "_.")
|
||||
.split("_.");
|
||||
final chapsNames =
|
||||
jsonPathToString(newRequest, r'$.chapters[*].title', "_.").split("_.");
|
||||
final chaptersChaps =
|
||||
jsonPathToString(newRequest, r'$.chapters[*].chap', "_.").split("_.");
|
||||
|
||||
var dateUploads =
|
||||
parseDates(chapDate, source.dateFormat, source.dateFormatLocale);
|
||||
List<MChapter>? chaptersList = [];
|
||||
for (var i = 0; i < chapsNames.length; i++) {
|
||||
String title = "";
|
||||
String scanlator = "";
|
||||
if (chaptersChaps.isNotEmpty && chaptersVolumes.isNotEmpty) {
|
||||
title = beautifyChapterName(
|
||||
chaptersVolumes[i], chaptersChaps[i], chapsNames[i], source.lang);
|
||||
} else {
|
||||
title = chapsNames[i];
|
||||
}
|
||||
if (chaptersScanlators.isNotEmpty) {
|
||||
scanlator = chaptersScanlators[i]
|
||||
.toString()
|
||||
.replaceAll(']', "")
|
||||
.replaceAll("[", "");
|
||||
}
|
||||
MChapter chapter = MChapter();
|
||||
chapter.name = title;
|
||||
chapter.url = chapsUrls[i];
|
||||
chapter.scanlator = scanlator == "null" ? "" : scanlator;
|
||||
chapter.dateUpload = dateUploads[i];
|
||||
chaptersList.add(chapter);
|
||||
}
|
||||
manga.chapters = chaptersList;
|
||||
return manga;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<String>> getPageList(String url) async {
|
||||
final res = (await client.get(
|
||||
Uri.parse("${source.apiUrl}/chapter/$url?tachiyomi=true"),
|
||||
headers: getHeader(url)))
|
||||
.body;
|
||||
return jsonPathToString(res, r'$.chapter.images[*].url', '_.').split('_.');
|
||||
}
|
||||
|
||||
MPages mangaRes(String res) async {
|
||||
final names = jsonPathToList(res, r'$.title', 0);
|
||||
List<String> ids = jsonPathToList(res, r'$.hid', 0);
|
||||
List<String> mangaUrls = [];
|
||||
for (var id in ids) {
|
||||
mangaUrls.add("/comic/$id/#");
|
||||
}
|
||||
final urls = mangaUrls;
|
||||
final images = jsonPathToList(res, r'$.cover_url', 0);
|
||||
List<MManga> mangaList = [];
|
||||
for (var i = 0; i < urls.length; i++) {
|
||||
MManga manga = MManga();
|
||||
manga.name = names[i];
|
||||
manga.imageUrl = images[i];
|
||||
manga.link = urls[i];
|
||||
mangaList.add(manga);
|
||||
}
|
||||
|
||||
return MPages(mangaList, true);
|
||||
}
|
||||
|
||||
String ll(String url) {
|
||||
if (url.contains("?")) {
|
||||
return "&";
|
||||
}
|
||||
return "?";
|
||||
}
|
||||
|
||||
@override
|
||||
List<dynamic> getFilterList() {
|
||||
return [
|
||||
HeaderFilter("The filter is ignored when using text search."),
|
||||
GroupFilter("GenreFilter", "Genre", [
|
||||
{
|
||||
"type": "TriState",
|
||||
"filter": {"name": "4-Koma", "value": "4-koma"}
|
||||
},
|
||||
{
|
||||
"type": "TriState",
|
||||
"filter": {"name": "Action", "value": "action"}
|
||||
},
|
||||
{
|
||||
"type": "TriState",
|
||||
"filter": {"name": "Adaptation", "value": "adaptation"}
|
||||
},
|
||||
{
|
||||
"type": "TriState",
|
||||
"filter": {"name": "Adult", "value": "adult"}
|
||||
},
|
||||
{
|
||||
"type": "TriState",
|
||||
"filter": {"name": "Adventure", "value": "adventure"}
|
||||
},
|
||||
{
|
||||
"type": "TriState",
|
||||
"filter": {"name": "Aliens", "value": "aliens"}
|
||||
},
|
||||
{
|
||||
"type": "TriState",
|
||||
"filter": {"name": "Animals", "value": "animals"}
|
||||
},
|
||||
{
|
||||
"type": "TriState",
|
||||
"filter": {"name": "Anthology", "value": "anthology"}
|
||||
},
|
||||
{
|
||||
"type": "TriState",
|
||||
"filter": {"name": "Award Winning", "value": "award-winning"}
|
||||
},
|
||||
{
|
||||
"type": "TriState",
|
||||
"filter": {"name": "Comedy", "value": "comedy"}
|
||||
},
|
||||
{
|
||||
"type": "TriState",
|
||||
"filter": {"name": "Cooking", "value": "cooking"}
|
||||
},
|
||||
{
|
||||
"type": "TriState",
|
||||
"filter": {"name": "Crime", "value": "crime"}
|
||||
},
|
||||
{
|
||||
"type": "TriState",
|
||||
"filter": {"name": "Crossdressing", "value": "crossdressing"}
|
||||
},
|
||||
{
|
||||
"type": "TriState",
|
||||
"filter": {"name": "Delinquents", "value": "delinquents"}
|
||||
},
|
||||
{
|
||||
"type": "TriState",
|
||||
"filter": {"name": "Demons", "value": "demons"}
|
||||
},
|
||||
{
|
||||
"type": "TriState",
|
||||
"filter": {"name": "Doujinshi", "value": "doujinshi"}
|
||||
},
|
||||
{
|
||||
"type": "TriState",
|
||||
"filter": {"name": "Drama", "value": "drama"}
|
||||
},
|
||||
{
|
||||
"type": "TriState",
|
||||
"filter": {"name": "Ecchi", "value": "ecchi"}
|
||||
},
|
||||
{
|
||||
"type": "TriState",
|
||||
"filter": {"name": "Fan Colored", "value": "fan-colored"}
|
||||
},
|
||||
{
|
||||
"type": "TriState",
|
||||
"filter": {"name": "Fantasy", "value": "fantasy"}
|
||||
},
|
||||
{
|
||||
"type": "TriState",
|
||||
"filter": {"name": "Full Color", "value": "full-color"}
|
||||
},
|
||||
{
|
||||
"type": "TriState",
|
||||
"filter": {"name": "Gender Bender", "value": "gender-bender"}
|
||||
},
|
||||
{
|
||||
"type": "TriState",
|
||||
"filter": {"name": "Genderswap", "value": "genderswap"}
|
||||
},
|
||||
{
|
||||
"type": "TriState",
|
||||
"filter": {"name": "Ghosts", "value": "ghosts"}
|
||||
},
|
||||
{
|
||||
"type": "TriState",
|
||||
"filter": {"name": "Gore", "value": "gore"}
|
||||
},
|
||||
{
|
||||
"type": "TriState",
|
||||
"filter": {"name": "Gyaru", "value": "gyaru"}
|
||||
},
|
||||
{
|
||||
"type": "TriState",
|
||||
"filter": {"name": "Harem", "value": "harem"}
|
||||
},
|
||||
{
|
||||
"type": "TriState",
|
||||
"filter": {"name": "Historical", "value": "historical"}
|
||||
},
|
||||
{
|
||||
"type": "TriState",
|
||||
"filter": {"name": "Horror", "value": "horror"}
|
||||
},
|
||||
{
|
||||
"type": "TriState",
|
||||
"filter": {"name": "Incest", "value": "incest"}
|
||||
},
|
||||
{
|
||||
"type": "TriState",
|
||||
"filter": {"name": "Isekai", "value": "isekai"}
|
||||
},
|
||||
{
|
||||
"type": "TriState",
|
||||
"filter": {"name": "Loli", "value": "loli"}
|
||||
},
|
||||
{
|
||||
"type": "TriState",
|
||||
"filter": {"name": "Long Strip", "value": "long-strip"}
|
||||
},
|
||||
{
|
||||
"type": "TriState",
|
||||
"filter": {"name": "Mafia", "value": "mafia"}
|
||||
},
|
||||
{
|
||||
"type": "TriState",
|
||||
"filter": {"name": "Magic", "value": "magic"}
|
||||
},
|
||||
{
|
||||
"type": "TriState",
|
||||
"filter": {"name": "Magical Girls", "value": "magical-girls"}
|
||||
},
|
||||
{
|
||||
"type": "TriState",
|
||||
"filter": {"name": "Martial Arts", "value": "martial-arts"}
|
||||
},
|
||||
{
|
||||
"type": "TriState",
|
||||
"filter": {"name": "Mature", "value": "mature"}
|
||||
},
|
||||
{
|
||||
"type": "TriState",
|
||||
"filter": {"name": "Mecha", "value": "mecha"}
|
||||
},
|
||||
{
|
||||
"type": "TriState",
|
||||
"filter": {"name": "Medical", "value": "medical"}
|
||||
},
|
||||
{
|
||||
"type": "TriState",
|
||||
"filter": {"name": "Military", "value": "military"}
|
||||
},
|
||||
{
|
||||
"type": "TriState",
|
||||
"filter": {"name": "Monster Girls", "value": "monster-girls"}
|
||||
},
|
||||
{
|
||||
"type": "TriState",
|
||||
"filter": {"name": "Monsters", "value": "monsters"}
|
||||
},
|
||||
{
|
||||
"type": "TriState",
|
||||
"filter": {"name": "Music", "value": "music"}
|
||||
},
|
||||
{
|
||||
"type": "TriState",
|
||||
"filter": {"name": "Mystery", "value": "mystery"}
|
||||
},
|
||||
{
|
||||
"type": "TriState",
|
||||
"filter": {"name": "Ninja", "value": "ninja"}
|
||||
},
|
||||
{
|
||||
"type": "TriState",
|
||||
"filter": {"name": "Office Workers", "value": "office-workers"}
|
||||
},
|
||||
{
|
||||
"type": "TriState",
|
||||
"filter": {"name": "Official Colored", "value": "official-colored"}
|
||||
},
|
||||
{
|
||||
"type": "TriState",
|
||||
"filter": {"name": "Oneshot", "value": "oneshot"}
|
||||
},
|
||||
{
|
||||
"type": "TriState",
|
||||
"filter": {"name": "Philosophical", "value": "philosophical"}
|
||||
},
|
||||
{
|
||||
"type": "TriState",
|
||||
"filter": {"name": "Police", "value": "police"}
|
||||
},
|
||||
{
|
||||
"type": "TriState",
|
||||
"filter": {"name": "Post-Apocalyptic", "value": "post-apocalyptic"}
|
||||
},
|
||||
{
|
||||
"type": "TriState",
|
||||
"filter": {"name": "Psychological", "value": "psychological"}
|
||||
},
|
||||
{
|
||||
"type": "TriState",
|
||||
"filter": {"name": "Reincarnation", "value": "reincarnation"}
|
||||
},
|
||||
{
|
||||
"type": "TriState",
|
||||
"filter": {"name": "Reverse Harem", "value": "reverse-harem"}
|
||||
},
|
||||
{
|
||||
"type": "TriState",
|
||||
"filter": {"name": "Romance", "value": "romance"}
|
||||
},
|
||||
{
|
||||
"type": "TriState",
|
||||
"filter": {"name": "Samurai", "value": "samurai"}
|
||||
},
|
||||
{
|
||||
"type": "TriState",
|
||||
"filter": {"name": "School Life", "value": "school-life"}
|
||||
},
|
||||
{
|
||||
"type": "TriState",
|
||||
"filter": {"name": "Sci-Fi", "value": "sci-fi"}
|
||||
},
|
||||
{
|
||||
"type": "TriState",
|
||||
"filter": {"name": "Sexual Violence", "value": "sexual-violence"}
|
||||
},
|
||||
{
|
||||
"type": "TriState",
|
||||
"filter": {"name": "Shota", "value": "shota"}
|
||||
},
|
||||
{
|
||||
"type": "TriState",
|
||||
"filter": {"name": "Shoujo Ai", "value": "shoujo-ai"}
|
||||
},
|
||||
{
|
||||
"type": "TriState",
|
||||
"filter": {"name": "Shounen Ai", "value": "shounen-ai"}
|
||||
},
|
||||
{
|
||||
"type": "TriState",
|
||||
"filter": {"name": "Slice of Life", "value": "slice-of-life"}
|
||||
},
|
||||
{
|
||||
"type": "TriState",
|
||||
"filter": {"name": "Smut", "value": "smut"}
|
||||
},
|
||||
{
|
||||
"type": "TriState",
|
||||
"filter": {"name": "Sports", "value": "sports"}
|
||||
},
|
||||
{
|
||||
"type": "TriState",
|
||||
"filter": {"name": "Superhero", "value": "superhero"}
|
||||
},
|
||||
{
|
||||
"type": "TriState",
|
||||
"filter": {"name": "Supernatural", "value": "supernatural"}
|
||||
},
|
||||
{
|
||||
"type": "TriState",
|
||||
"filter": {"name": "Survival", "value": "survival"}
|
||||
},
|
||||
{
|
||||
"type": "TriState",
|
||||
"filter": {"name": "Thriller", "value": "thriller"}
|
||||
},
|
||||
{
|
||||
"type": "TriState",
|
||||
"filter": {"name": "Time Travel", "value": "time-travel"}
|
||||
},
|
||||
{
|
||||
"type": "TriState",
|
||||
"filter": {"name": "Traditional Games", "value": "traditional-games"}
|
||||
},
|
||||
{
|
||||
"type": "TriState",
|
||||
"filter": {"name": "Tragedy", "value": "tragedy"}
|
||||
},
|
||||
{
|
||||
"type": "TriState",
|
||||
"filter": {"name": "User Created", "value": "user-created"}
|
||||
},
|
||||
{
|
||||
"type": "TriState",
|
||||
"filter": {"name": "Vampires", "value": "vampires"}
|
||||
},
|
||||
{
|
||||
"type": "TriState",
|
||||
"filter": {"name": "Video Games", "value": "video-games"}
|
||||
},
|
||||
{
|
||||
"type": "TriState",
|
||||
"filter": {"name": "Villainess", "value": "villainess"}
|
||||
},
|
||||
{
|
||||
"type": "TriState",
|
||||
"filter": {"name": "Virtual Reality", "value": "virtual-reality"}
|
||||
},
|
||||
{
|
||||
"type": "TriState",
|
||||
"filter": {"name": "Web Comic", "value": "web-comic"}
|
||||
},
|
||||
{
|
||||
"type": "TriState",
|
||||
"filter": {"name": "Wuxia", "value": "wuxia"}
|
||||
},
|
||||
{
|
||||
"type": "TriState",
|
||||
"filter": {"name": "Yaoi", "value": "yaoi"}
|
||||
},
|
||||
{
|
||||
"type": "TriState",
|
||||
"filter": {"name": "Yuri", "value": "yuri"}
|
||||
},
|
||||
{
|
||||
"type": "TriState",
|
||||
"filter": {"name": "Zombies", "value": "zombies"}
|
||||
}
|
||||
]),
|
||||
GroupFilter("DemographicFilter", "Demographic", [
|
||||
TriStateFilter("Shounen", "1"),
|
||||
TriStateFilter("Shoujo", "2"),
|
||||
TriStateFilter("Seinen", "3"),
|
||||
TriStateFilter("Josei", "4"),
|
||||
]),
|
||||
GroupFilter("TypeFilter", "Type", [
|
||||
CheckBoxFilter("Manga", "jp"),
|
||||
CheckBoxFilter("Manhwa", "kr"),
|
||||
CheckBoxFilter("Manhua", "cn"),
|
||||
]),
|
||||
SelectFilter("SortFilter", "Sort", 0, [
|
||||
SelectFilterOption("Most popular", "follow"),
|
||||
SelectFilterOption("Most follows", "user_follow_count"),
|
||||
SelectFilterOption("Most views", "view"),
|
||||
SelectFilterOption("High rating", "rating"),
|
||||
SelectFilterOption("Last updated", "uploaded"),
|
||||
SelectFilterOption("Newest", "created_at"),
|
||||
]),
|
||||
SelectFilter("StatusFilter", "Status", 0, [
|
||||
SelectFilterOption("All", "0"),
|
||||
SelectFilterOption("Ongoing", "1"),
|
||||
SelectFilterOption("Completed", "2"),
|
||||
SelectFilterOption("Cancelled", "3"),
|
||||
SelectFilterOption("Hiatus", "4"),
|
||||
]),
|
||||
CheckBoxFilter("Completely Scanlated?", "", "CompletedFilter"),
|
||||
SelectFilter("CreatedAtFilter", "Created at", 0, [
|
||||
SelectFilterOption("", ""),
|
||||
SelectFilterOption("3 days", "3"),
|
||||
SelectFilterOption("7 days", "7"),
|
||||
SelectFilterOption("30 days", "30"),
|
||||
SelectFilterOption("3 months", "90"),
|
||||
SelectFilterOption("6 months", "180"),
|
||||
SelectFilterOption("1 year", "365"),
|
||||
]),
|
||||
TextFilter("MinimumFilter", "Minimum Chapters"),
|
||||
HeaderFilter("From Year, ex: 2010"),
|
||||
TextFilter("FromYearFilter", "From"),
|
||||
HeaderFilter("To Year, ex: 2021"),
|
||||
TextFilter("ToYearFilter", "To"),
|
||||
HeaderFilter("Separate tags with commas"),
|
||||
TextFilter("TagFilter", "Tags")
|
||||
];
|
||||
}
|
||||
|
||||
String beautifyChapterName(
|
||||
String vol, String chap, String title, String lang) {
|
||||
String result = "";
|
||||
|
||||
if (vol != "null" && vol.isNotEmpty) {
|
||||
if (chap != "null" && chap.isEmpty) {
|
||||
result += "Volume $vol ";
|
||||
} else {
|
||||
result += "Vol. $vol ";
|
||||
}
|
||||
}
|
||||
|
||||
if (chap != "null" && chap.isNotEmpty) {
|
||||
if (vol != "null" && vol.isEmpty) {
|
||||
if (lang != "null" && lang == "fr") {
|
||||
result += "Chapitre $chap";
|
||||
} else {
|
||||
result += "Chapter $chap";
|
||||
}
|
||||
} else {
|
||||
result += "Ch. $chap ";
|
||||
}
|
||||
}
|
||||
|
||||
if (title != "null" && title.isNotEmpty) {
|
||||
if (chap != "null" && chap.isEmpty) {
|
||||
result += title;
|
||||
} else {
|
||||
result += " : $title";
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, String> getHeader(String url) {
|
||||
final headers = {
|
||||
"Referer": "$url/",
|
||||
'User-Agent':
|
||||
"Tachiyomi Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:110.0) Gecko/20100101 Firefox/110.0"
|
||||
};
|
||||
return headers;
|
||||
}
|
||||
|
||||
ComickFun main(MSource source) {
|
||||
return ComickFun(source: source);
|
||||
}
|
||||
BIN
dart/manga/src/all/comick/icon.png
Normal file
BIN
dart/manga/src/all/comick/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.9 KiB |
71
dart/manga/src/all/comick/sources.dart
Normal file
71
dart/manga/src/all/comick/sources.dart
Normal file
@@ -0,0 +1,71 @@
|
||||
import '../../../../../model/source.dart';
|
||||
|
||||
const _comickVersion = "0.0.7";
|
||||
const _comickSourceCodeUrl =
|
||||
"https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/$branchName/dart/manga/src/all/comick/comick.dart";
|
||||
|
||||
String _iconUrl =
|
||||
"https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/$branchName/dart/manga/src/all/comick/icon.png";
|
||||
const _apiUrl = 'https://api.comick.fun';
|
||||
const _baseUrl = 'https://comick.app';
|
||||
const _isNsfw = true;
|
||||
|
||||
List<String> _languages = [
|
||||
"all",
|
||||
"en",
|
||||
"pt-br",
|
||||
"ru",
|
||||
"fr",
|
||||
"es-419",
|
||||
"pl",
|
||||
"tr",
|
||||
"it",
|
||||
"es",
|
||||
"id",
|
||||
"hu",
|
||||
"vi",
|
||||
"zh-hk",
|
||||
"ar",
|
||||
"de",
|
||||
"zh",
|
||||
"ca",
|
||||
"bg",
|
||||
"th",
|
||||
"fa",
|
||||
"uk",
|
||||
"mn",
|
||||
"ro",
|
||||
"he",
|
||||
"ms",
|
||||
"tl",
|
||||
"ja",
|
||||
"hi",
|
||||
"my",
|
||||
"ko",
|
||||
"cs",
|
||||
"pt",
|
||||
"nl",
|
||||
"sv",
|
||||
"bn",
|
||||
"no",
|
||||
"lt",
|
||||
"el",
|
||||
"sr",
|
||||
"da",
|
||||
];
|
||||
|
||||
List<Source> get comickSourcesList => _comickSourcesList;
|
||||
List<Source> _comickSourcesList = _languages
|
||||
.map((e) => Source(
|
||||
name: 'Comick',
|
||||
apiUrl: _apiUrl,
|
||||
baseUrl: _baseUrl,
|
||||
lang: e,
|
||||
typeSource: "comick",
|
||||
iconUrl: _iconUrl,
|
||||
dateFormat: "yyyy-MM-dd'T'HH:mm:ss'Z'",
|
||||
isNsfw: _isNsfw,
|
||||
dateFormatLocale: "en",
|
||||
version: _comickVersion,
|
||||
sourceCodeUrl: _comickSourceCodeUrl))
|
||||
.toList();
|
||||
BIN
dart/manga/src/all/mangadex/icon.png
Normal file
BIN
dart/manga/src/all/mangadex/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.9 KiB |
595
dart/manga/src/all/mangadex/mangadex.dart
Normal file
595
dart/manga/src/all/mangadex/mangadex.dart
Normal file
@@ -0,0 +1,595 @@
|
||||
import 'package:mangayomi/bridge_lib.dart';
|
||||
import 'dart:convert';
|
||||
|
||||
class MangaDex extends MProvider {
|
||||
MangaDex({required this.source});
|
||||
|
||||
MSource source;
|
||||
|
||||
final Client client = Client(source);
|
||||
|
||||
@override
|
||||
Future<MPages> getPopular(int page) async {
|
||||
page = (20 * (page - 1));
|
||||
final url =
|
||||
"https://api.mangadex.org/manga?limit=20&offset=$page&availableTranslatedLanguage[]=${source.lang}&includes[]=cover_art${preferenceContentRating(source.id)}${preferenceOriginalLanguages(source.id)}&order[followedCount]=desc";
|
||||
final res = (await client.get(Uri.parse(url))).body;
|
||||
return mangaRes(res);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<MPages> getLatestUpdates(int page) async {
|
||||
page = (20 * (page - 1));
|
||||
final url =
|
||||
"https://api.mangadex.org/chapter?limit=20&offset=$page&translatedLanguage[]=${source.lang}&includeFutureUpdates=0&order[publishAt]=desc&includeFuturePublishAt=0&includeEmptyPages=0";
|
||||
final ress = (await client.get(Uri.parse(url))).body;
|
||||
final mangaIds =
|
||||
jsonPathToString(ress, r'$.data[*].relationships[*].id', '.--')
|
||||
.split('.--');
|
||||
String mangaIdss = "";
|
||||
for (var id in mangaIds) {
|
||||
mangaIdss += "&ids[]=$id";
|
||||
}
|
||||
final newUrl =
|
||||
"https://api.mangadex.org/manga?includes[]=cover_art&limit=${mangaIds.length}${preferenceContentRating(source.id)}${preferenceOriginalLanguages(source.id)}$mangaIdss";
|
||||
final res = (await client.get(Uri.parse(newUrl))).body;
|
||||
return mangaRes(res);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<MPages> search(String query, int page, FilterList filterList) async {
|
||||
page = (20 * (page - 1));
|
||||
final filters = filterList.filters;
|
||||
String url = "";
|
||||
|
||||
url =
|
||||
"https://api.mangadex.org/manga?includes[]=cover_art&offset=$page&limit=20&title=$query";
|
||||
for (var filter in filters) {
|
||||
if (filter.type == "HasAvailableChaptersFilter") {
|
||||
if (filter.state) {
|
||||
url += "${ll(url)}hasAvailableChapters=true";
|
||||
url += "${ll(url)}availableTranslatedLanguage[]=${source.lang}";
|
||||
}
|
||||
} else if (filter.type == "OriginalLanguageList") {
|
||||
final langs = (filter.state as List).where((e) => e.state).toList();
|
||||
if (langs.isNotEmpty) {
|
||||
for (var lang in langs) {
|
||||
url += "${ll(url)}${lang.value}";
|
||||
}
|
||||
}
|
||||
} else if (filter.type == "ContentRatingList") {
|
||||
final ctns = (filter.state as List).where((e) => e.state).toList();
|
||||
if (ctns.isNotEmpty) {
|
||||
for (var ctn in ctns) {
|
||||
url += "${ll(url)}${ctn.value}";
|
||||
}
|
||||
}
|
||||
} else if (filter.type == "DemographicList") {
|
||||
final demogr = (filter.state as List).where((e) => e.state).toList();
|
||||
if (demogr.isNotEmpty) {
|
||||
for (var demog in demogr) {
|
||||
url += "${ll(url)}${demog.value}";
|
||||
}
|
||||
}
|
||||
} else if (filter.type == "StatusList") {
|
||||
final statusL = (filter.state as List).where((e) => e.state).toList();
|
||||
if (statusL.isNotEmpty) {
|
||||
for (var status in statusL) {
|
||||
url += "${ll(url)}${status.value}";
|
||||
}
|
||||
}
|
||||
} else if (filter.type == "SortFilter") {
|
||||
final value = filter.state.ascending ? "asc" : "desc";
|
||||
url +=
|
||||
"${ll(url)}order[${filter.values[filter.state.index].value}]=$value";
|
||||
} else if (filter.type == "TagsFilter") {
|
||||
for (var tag in filter.state) {
|
||||
url += "${ll(url)}${tag.values[tag.state].value}";
|
||||
}
|
||||
} else if (filter.type == "FormatFilter") {
|
||||
final included = (filter.state as List)
|
||||
.where((e) => e.state == 1 ? true : false)
|
||||
.toList();
|
||||
final excluded = (filter.state as List)
|
||||
.where((e) => e.state == 2 ? true : false)
|
||||
.toList();
|
||||
if (included.isNotEmpty) {
|
||||
for (var val in included) {
|
||||
url += "${ll(url)}includedTags[]=${val.value}";
|
||||
}
|
||||
}
|
||||
if (excluded.isNotEmpty) {
|
||||
for (var val in excluded) {
|
||||
url += "${ll(url)}excludedTags[]=${val.value}";
|
||||
}
|
||||
}
|
||||
} else if (filter.type == "GenreFilter") {
|
||||
final included = (filter.state as List)
|
||||
.where((e) => e.state == 1 ? true : false)
|
||||
.toList();
|
||||
final excluded = (filter.state as List)
|
||||
.where((e) => e.state == 2 ? true : false)
|
||||
.toList();
|
||||
if (included.isNotEmpty) {
|
||||
for (var val in included) {
|
||||
url += "${ll(url)}includedTags[]=${val.value}";
|
||||
}
|
||||
}
|
||||
if (excluded.isNotEmpty) {
|
||||
for (var val in excluded) {
|
||||
url += "${ll(url)}excludedTags[]=${val.value}";
|
||||
}
|
||||
}
|
||||
} else if (filter.type == "ThemeFilter") {
|
||||
final included = (filter.state as List)
|
||||
.where((e) => e.state == 1 ? true : false)
|
||||
.toList();
|
||||
final excluded = (filter.state as List)
|
||||
.where((e) => e.state == 2 ? true : false)
|
||||
.toList();
|
||||
if (included.isNotEmpty) {
|
||||
for (var val in included) {
|
||||
url += "${ll(url)}includedTags[]=${val.value}";
|
||||
}
|
||||
}
|
||||
if (excluded.isNotEmpty) {
|
||||
for (var val in excluded) {
|
||||
url += "${ll(url)}excludedTags[]=${val.value}";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final res = (await client.get(Uri.parse(url))).body;
|
||||
return mangaRes(res);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<MManga> getDetail(String url) async {
|
||||
final statusList = [
|
||||
{"ongoing": 0, "completed": 1, "hiatus": 2, "cancelled": 3}
|
||||
];
|
||||
|
||||
final res = (await client.get(Uri.parse(
|
||||
"https://api.mangadex.org$url?includes[]=cover_art&includes[]=author&includes[]=artist")))
|
||||
.body;
|
||||
MManga manga = MManga();
|
||||
manga.author = jsonPathToString(
|
||||
res, r'$..data.relationships[*].attributes.name', ', ');
|
||||
|
||||
String expressionDescriptionA = r'$..data.attributes.description.en';
|
||||
String expressionDescription = regExp(r'$..data.attributes.description[a]',
|
||||
r'\[a\]', ".${source.lang}", 0, 1);
|
||||
|
||||
String description = jsonPathToString(res, expressionDescription, '');
|
||||
if (description.isEmpty) {
|
||||
description = jsonPathToString(res, expressionDescriptionA, '');
|
||||
}
|
||||
manga.description = description;
|
||||
List<String> genres = [];
|
||||
|
||||
genres = jsonPathToString(
|
||||
res, r'$..data.attributes.tags[*].attributes.name.en', '.-')
|
||||
.split('.-');
|
||||
|
||||
String contentRating =
|
||||
jsonPathToString(res, r'$..data.attributes.contentRating', '');
|
||||
if (contentRating != "safe") {
|
||||
genres.add(contentRating);
|
||||
}
|
||||
String publicationDemographic =
|
||||
jsonPathToString(res, r'$..data.attributes.publicationDemographic', '');
|
||||
if (publicationDemographic == "null") {
|
||||
} else {
|
||||
genres.add(publicationDemographic);
|
||||
}
|
||||
manga.genre = genres;
|
||||
String statusRes = jsonPathToString(res, r'$..data.attributes.status', '');
|
||||
manga.status = parseStatus(statusRes, statusList);
|
||||
final mangaId = url.split('/').last;
|
||||
|
||||
final paginatedChapterList =
|
||||
await paginatedChapterListRequest(mangaId, 0, source.lang, source.id);
|
||||
final chapterList =
|
||||
jsonPathToString(paginatedChapterList, r'$.data[*]', '_.').split('_.');
|
||||
int limit =
|
||||
int.parse(jsonPathToString(paginatedChapterList, r'$.limit', ''));
|
||||
int offset =
|
||||
int.parse(jsonPathToString(paginatedChapterList, r'$.offset', ''));
|
||||
int total =
|
||||
int.parse(jsonPathToString(paginatedChapterList, r'$.total', ''));
|
||||
List<MChapter> chapterListA = [];
|
||||
|
||||
final list =
|
||||
getChapters(int.parse("${chapterList.length}"), paginatedChapterList);
|
||||
|
||||
chapterListA.addAll(list);
|
||||
var hasMoreResults = (limit + offset) < total;
|
||||
while (hasMoreResults) {
|
||||
offset += limit;
|
||||
var newRequest = await paginatedChapterListRequest(
|
||||
mangaId, offset, source.lang, source.id);
|
||||
int total = int.parse(jsonPathToString(newRequest, r'$.total', ''));
|
||||
final chapterList =
|
||||
jsonPathToString(paginatedChapterList, r'$.data[*]', '_.')
|
||||
.split('_.');
|
||||
final list = getChapters(int.parse("${chapterList.length}"), newRequest);
|
||||
chapterListA.addAll(list);
|
||||
hasMoreResults = (limit + offset) < total;
|
||||
}
|
||||
|
||||
manga.chapters = chapterListA;
|
||||
return manga;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<String>> getPageList(String url) async {
|
||||
final res = (await client
|
||||
.get(Uri.parse("https://api.mangadex.org/at-home/server/$url")))
|
||||
.body;
|
||||
|
||||
final host = getMapValue(res, "baseUrl");
|
||||
final chapter = getMapValue(res, "chapter", encode: true);
|
||||
final hash = getMapValue(chapter, "hash");
|
||||
final chapterDatas =
|
||||
json.decode(getMapValue(chapter, "data", encode: true)) as List;
|
||||
return chapterDatas.map((e) => "$host/data/$hash/$e").toList();
|
||||
}
|
||||
|
||||
MPages mangaRes(String res) {
|
||||
final datasRes = getMapValue(res, "data", encode: true);
|
||||
|
||||
final resJson = json.decode(datasRes) as List;
|
||||
List<MManga> mangaList = [];
|
||||
for (var e in resJson) {
|
||||
MManga manga = MManga();
|
||||
manga.name = findTitle(json.encode(e), source.lang);
|
||||
manga.imageUrl = getCover(json.encode(e), source.id);
|
||||
manga.link = "/manga/${getMapValue(json.encode(e), "id")}";
|
||||
mangaList.add(manga);
|
||||
}
|
||||
return MPages(mangaList, true);
|
||||
}
|
||||
|
||||
List<MChapter> getChapters(int length, String paginatedChapterListA) {
|
||||
List<MChapter> chaptersList = [];
|
||||
String paginatedChapterList = paginatedChapterListA;
|
||||
final dataList = jsonPathToList(paginatedChapterList, r'$.data[*]', 0);
|
||||
for (var res in dataList) {
|
||||
String scan = "";
|
||||
final groups = jsonPathToList(res,
|
||||
r'$.relationships[?@.id!="00e03853-1b96-4f41-9542-c71b8692033b"]', 0);
|
||||
String chapName = "";
|
||||
for (var element in groups) {
|
||||
final data = getMapValue(element, "attributes", encode: true);
|
||||
if (data.isNotEmpty) {
|
||||
final name = getMapValue(data, "name");
|
||||
scan += "$name";
|
||||
final username = getMapValue(data, "username");
|
||||
if (username.isNotEmpty) {
|
||||
if (scan.isEmpty) {
|
||||
scan += "Uploaded by $username";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (scan.isEmpty) {
|
||||
scan = "No Group";
|
||||
}
|
||||
final dataRes = getMapValue(res, "attributes", encode: true);
|
||||
if (dataRes.isNotEmpty) {
|
||||
final data = getMapValue(res, "attributes", encode: true);
|
||||
final volume = getMapValue(data, "volume");
|
||||
if (volume.isNotEmpty) {
|
||||
if (volume != "null") {
|
||||
chapName = "Vol.$volume ";
|
||||
}
|
||||
}
|
||||
final chapter = getMapValue(data, "chapter");
|
||||
if (chapter.isNotEmpty) {
|
||||
if (chapter != "null") {
|
||||
chapName += "Ch.$chapter ";
|
||||
}
|
||||
}
|
||||
final title = getMapValue(data, "title");
|
||||
if (title.isNotEmpty) {
|
||||
if (title != "null") {
|
||||
if (chapName.isNotEmpty) {
|
||||
chapName += "- ";
|
||||
}
|
||||
chapName += "$title";
|
||||
}
|
||||
}
|
||||
if (chapName.isEmpty) {
|
||||
chapName += "Oneshot";
|
||||
}
|
||||
final date = getMapValue(data, "publishAt");
|
||||
final id = getMapValue(res, "id");
|
||||
MChapter chapterr = MChapter();
|
||||
chapterr.name = chapName;
|
||||
chapterr.url = id;
|
||||
chapterr.scanlator = scan;
|
||||
chapterr.dateUpload =
|
||||
parseDates([date], "yyyy-MM-dd'T'HH:mm:ss+SSS", "en_US").first;
|
||||
chaptersList.add(chapterr);
|
||||
}
|
||||
}
|
||||
|
||||
return chaptersList;
|
||||
}
|
||||
|
||||
Future<String> paginatedChapterListRequest(
|
||||
String mangaId, int offset, String lang, int sourceId) async {
|
||||
final url =
|
||||
'https://api.mangadex.org/manga/$mangaId/feed?limit=500&offset=$offset&includes[]=user&includes[]=scanlation_group&order[volume]=desc&order[chapter]=desc&translatedLanguage[]=$lang&includeFuturePublishAt=0&includeEmptyPages=0${preferenceContentRating(sourceId)}';
|
||||
final res = (await client.get(Uri.parse(url))).body;
|
||||
return res;
|
||||
}
|
||||
|
||||
String findTitle(String dataRes, String lang) {
|
||||
final attributes = getMapValue(dataRes, "attributes", encode: true);
|
||||
final altTitlesJ =
|
||||
json.decode(getMapValue(attributes, "altTitles", encode: true));
|
||||
final titleJ = getMapValue(attributes, "title", encode: true);
|
||||
final title = getMapValue(titleJ, "en");
|
||||
if (title.isEmpty) {
|
||||
for (var r in altTitlesJ) {
|
||||
final altTitle = getMapValue(json.encode(r), "en");
|
||||
if (altTitle.isNotEmpty) {
|
||||
return altTitle;
|
||||
}
|
||||
}
|
||||
}
|
||||
return title;
|
||||
}
|
||||
|
||||
String getCover(String dataRes, int sourceId) {
|
||||
final coverQuality = getPreferenceValue(sourceId, "cover_quality");
|
||||
final relationships = json
|
||||
.decode(getMapValue(dataRes, "relationships", encode: true)) as List;
|
||||
String coverFileName = "".toString();
|
||||
for (var a in relationships) {
|
||||
final relationType = getMapValue(json.encode(a), "type");
|
||||
if (relationType == "cover_art") {
|
||||
if (coverFileName.isEmpty) {
|
||||
final attributes =
|
||||
getMapValue(json.encode(a), "attributes", encode: true);
|
||||
coverFileName =
|
||||
"https://uploads.mangadex.org/covers/${getMapValue(dataRes, "id")}/${getMapValue(attributes, "fileName")}$coverQuality";
|
||||
}
|
||||
}
|
||||
}
|
||||
return coverFileName;
|
||||
}
|
||||
|
||||
@override
|
||||
List<dynamic> getFilterList() {
|
||||
return [
|
||||
CheckBoxFilter(
|
||||
"Has available chapters", "", "HasAvailableChaptersFilter"),
|
||||
GroupFilter("OriginalLanguageList", "Original language", [
|
||||
CheckBoxFilter("Japanese (Manga)", "originalLanguage[]=ja"),
|
||||
CheckBoxFilter("Chinese (Manhua)",
|
||||
"originalLanguage[]=zh&originalLanguage[]=zh-hk"),
|
||||
CheckBoxFilter("Korean (Manhwa)", "originalLanguage[]=ko"),
|
||||
]),
|
||||
GroupFilter("ContentRatingList", "Content rating", [
|
||||
CheckBoxFilter("Safe", "contentRating[]=safe", state: true),
|
||||
CheckBoxFilter("Suggestive", "contentRating[]=suggestive", state: true),
|
||||
CheckBoxFilter("Erotica", "contentRating[]=erotica"),
|
||||
CheckBoxFilter("Pornographic", "contentRating[]=pornographic"),
|
||||
]),
|
||||
GroupFilter("DemographicList", "Publication demographic", [
|
||||
CheckBoxFilter("None", "publicationDemographic[]=none"),
|
||||
CheckBoxFilter("Shounen", "publicationDemographic[]=shounen"),
|
||||
CheckBoxFilter("Shoujo", "publicationDemographic[]=shoujo"),
|
||||
CheckBoxFilter("Seinen", "publicationDemographic[]=seinen"),
|
||||
CheckBoxFilter("Josei", "publicationDemographic[]=josei"),
|
||||
]),
|
||||
GroupFilter("StatusList", "Status", [
|
||||
CheckBoxFilter("Ongoing", "status[]=ongoing"),
|
||||
CheckBoxFilter("Completed", "status[]=completed"),
|
||||
CheckBoxFilter("Hiatus", "status[]=hiatus"),
|
||||
CheckBoxFilter("Cancelled", "status[]=cancelled"),
|
||||
]),
|
||||
SortFilter("SortFilter", "Sort", SortState(5, false), [
|
||||
SelectFilterOption("Alphabetic", "title"),
|
||||
SelectFilterOption("Chapter uploded at", "latestUploadedChapter"),
|
||||
SelectFilterOption("Number of follows", "followedCount"),
|
||||
SelectFilterOption("Content created at", "createdAt"),
|
||||
SelectFilterOption("Content info updated at", "updatedAt"),
|
||||
SelectFilterOption("Relevance", "relevance"),
|
||||
SelectFilterOption("Year", "year"),
|
||||
SelectFilterOption("Rating", "rating"),
|
||||
]),
|
||||
GroupFilter("TagsFilter", "Tags mode", [
|
||||
SelectFilter("TagInclusionMode", "Included tags mode", 0, [
|
||||
SelectFilterOption("AND", "includedTagsMode=AND"),
|
||||
SelectFilterOption("OR", "includedTagsMode=OR"),
|
||||
]),
|
||||
SelectFilter("TagExclusionMode", "Excluded tags mode", 1, [
|
||||
SelectFilterOption("AND", "excludedTagsMode=AND"),
|
||||
SelectFilterOption("OR", "excludedTagsMode=OR"),
|
||||
]),
|
||||
]),
|
||||
GroupFilter("ContentsFilter", "Content", [
|
||||
TriStateFilter("Gore", "b29d6a3d-1569-4e7a-8caf-7557bc92cd5d"),
|
||||
TriStateFilter(
|
||||
"Sexual Violence", "97893a4c-12af-4dac-b6be-0dffb353568e"),
|
||||
]),
|
||||
GroupFilter("FormatFilter", "Format", [
|
||||
TriStateFilter("4-Koma", "b11fda93-8f1d-4bef-b2ed-8803d3733170"),
|
||||
TriStateFilter("Adaptation", "f4122d1c-3b44-44d0-9936-ff7502c39ad3"),
|
||||
TriStateFilter("Anthology", "51d83883-4103-437c-b4b1-731cb73d786c"),
|
||||
TriStateFilter("Award Winning", "0a39b5a1-b235-4886-a747-1d05d216532d"),
|
||||
TriStateFilter("Doujinshi", "b13b2a48-c720-44a9-9c77-39c9979373fb"),
|
||||
TriStateFilter("Fan Colored", "7b2ce280-79ef-4c09-9b58-12b7c23a9b78"),
|
||||
TriStateFilter("Full Color", "f5ba408b-0e7a-484d-8d49-4e9125ac96de"),
|
||||
TriStateFilter("Long Strip", "3e2b8dae-350e-4ab8-a8ce-016e844b9f0d"),
|
||||
TriStateFilter(
|
||||
"Official Colored", "320831a8-4026-470b-94f6-8353740e6f04"),
|
||||
TriStateFilter("Oneshot", "0234a31e-a729-4e28-9d6a-3f87c4966b9e"),
|
||||
TriStateFilter("User Created", "891cf039-b895-47f0-9229-bef4c96eccd4"),
|
||||
TriStateFilter("Web Comic", "e197df38-d0e7-43b5-9b09-2842d0c326dd"),
|
||||
]),
|
||||
GroupFilter("GenreFilter", "Genre", [
|
||||
TriStateFilter("Action", "391b0423-d847-456f-aff0-8b0cfc03066b"),
|
||||
TriStateFilter("Adventure", "87cc87cd-a395-47af-b27a-93258283bbc6"),
|
||||
TriStateFilter("Boys' Love", "5920b825-4181-4a17-beeb-9918b0ff7a30"),
|
||||
TriStateFilter("Comedy", "4d32cc48-9f00-4cca-9b5a-a839f0764984"),
|
||||
TriStateFilter("Crime", "5ca48985-9a9d-4bd8-be29-80dc0303db72"),
|
||||
TriStateFilter("Drama", "b9af3a63-f058-46de-a9a0-e0c13906197a"),
|
||||
TriStateFilter("Fantasy", "cdc58593-87dd-415e-bbc0-2ec27bf404cc"),
|
||||
TriStateFilter("Girls' Love", "a3c67850-4684-404e-9b7f-c69850ee5da6"),
|
||||
TriStateFilter("Historical", "33771934-028e-4cb3-8744-691e866a923e"),
|
||||
TriStateFilter("Horror", "cdad7e68-1419-41dd-bdce-27753074a640"),
|
||||
TriStateFilter("Isekai", "ace04997-f6bd-436e-b261-779182193d3d"),
|
||||
TriStateFilter("Magical Girls", "81c836c9-914a-4eca-981a-560dad663e73"),
|
||||
TriStateFilter("Mecha", "50880a9d-5440-4732-9afb-8f457127e836"),
|
||||
TriStateFilter("Medical", "c8cbe35b-1b2b-4a3f-9c37-db84c4514856"),
|
||||
TriStateFilter("Mystery", "ee968100-4191-4968-93d3-f82d72be7e46"),
|
||||
TriStateFilter("Philosophical", "b1e97889-25b4-4258-b28b-cd7f4d28ea9b"),
|
||||
TriStateFilter("Psychological", "3b60b75c-a2d7-4860-ab56-05f391bb889c"),
|
||||
TriStateFilter("Romance", "423e2eae-a7a2-4a8b-ac03-a8351462d71d"),
|
||||
TriStateFilter("Sci-Fi", "256c8bd9-4904-4360-bf4f-508a76d67183"),
|
||||
TriStateFilter("Slice of Life", "e5301a23-ebd9-49dd-a0cb-2add944c7fe9"),
|
||||
TriStateFilter("Sports", "69964a64-2f90-4d33-beeb-f3ed2875eb4c"),
|
||||
TriStateFilter("Superhero", "7064a261-a137-4d3a-8848-2d385de3a99c"),
|
||||
TriStateFilter("Thriller", "07251805-a27e-4d59-b488-f0bfbec15168"),
|
||||
TriStateFilter("Tragedy", "f8f62932-27da-4fe4-8ee1-6779a8c5edba"),
|
||||
TriStateFilter("Wuxia", "acc803a4-c95a-4c22-86fc-eb6b582d82a2"),
|
||||
]),
|
||||
GroupFilter("ThemeFilter", "Theme", [
|
||||
TriStateFilter("Aliens", "e64f6742-c834-471d-8d72-dd51fc02b835"),
|
||||
TriStateFilter("Animals", "3de8c75d-8ee3-48ff-98ee-e20a65c86451"),
|
||||
TriStateFilter("Cooking", "ea2bc92d-1c26-4930-9b7c-d5c0dc1b6869"),
|
||||
TriStateFilter("Crossdressing", "9ab53f92-3eed-4e9b-903a-917c86035ee3"),
|
||||
TriStateFilter("Delinquents", "da2d50ca-3018-4cc0-ac7a-6b7d472a29ea"),
|
||||
TriStateFilter("Demons", "39730448-9a5f-48a2-85b0-a70db87b1233"),
|
||||
TriStateFilter("Genderswap", "2bd2e8d0-f146-434a-9b51-fc9ff2c5fe6a"),
|
||||
TriStateFilter("Ghosts", "3bb26d85-09d5-4d2e-880c-c34b974339e9"),
|
||||
TriStateFilter("Gyaru", "fad12b5e-68ba-460e-b933-9ae8318f5b65"),
|
||||
TriStateFilter("Harem", "aafb99c1-7f60-43fa-b75f-fc9502ce29c7"),
|
||||
TriStateFilter("Incest", "5bd0e105-4481-44ca-b6e7-7544da56b1a3"),
|
||||
TriStateFilter("Loli", "2d1f5d56-a1e5-4d0d-a961-2193588b08ec"),
|
||||
TriStateFilter("Mafia", "85daba54-a71c-4554-8a28-9901a8b0afad"),
|
||||
TriStateFilter("Magic", "a1f53773-c69a-4ce5-8cab-fffcd90b1565"),
|
||||
TriStateFilter("Martial Arts", "799c202e-7daa-44eb-9cf7-8a3c0441531e"),
|
||||
TriStateFilter("Military", "ac72833b-c4e9-4878-b9db-6c8a4a99444a"),
|
||||
TriStateFilter("Monster Girls", "dd1f77c5-dea9-4e2b-97ae-224af09caf99"),
|
||||
TriStateFilter("Monsters", "36fd93ea-e8b8-445e-b836-358f02b3d33d"),
|
||||
TriStateFilter("Music", "f42fbf9e-188a-447b-9fdc-f19dc1e4d685"),
|
||||
TriStateFilter("Ninja", "489dd859-9b61-4c37-af75-5b18e88daafc"),
|
||||
TriStateFilter(
|
||||
"Office Workers", "92d6d951-ca5e-429c-ac78-451071cbf064"),
|
||||
TriStateFilter("Police", "df33b754-73a3-4c54-80e6-1a74a8058539"),
|
||||
TriStateFilter(
|
||||
"Post-Apocalyptic", "9467335a-1b83-4497-9231-765337a00b96"),
|
||||
TriStateFilter("Reincarnation", "0bc90acb-ccc1-44ca-a34a-b9f3a73259d0"),
|
||||
TriStateFilter("Reverse Harem", "65761a2a-415e-47f3-bef2-a9dababba7a6"),
|
||||
TriStateFilter("Samurai", "81183756-1453-4c81-aa9e-f6e1b63be016"),
|
||||
TriStateFilter("School Life", "caaa44eb-cd40-4177-b930-79d3ef2afe87"),
|
||||
TriStateFilter("Shota", "ddefd648-5140-4e5f-ba18-4eca4071d19b"),
|
||||
TriStateFilter("Supernatural", "eabc5b4c-6aff-42f3-b657-3e90cbd00b75"),
|
||||
TriStateFilter("Survival", "5fff9cde-849c-4d78-aab0-0d52b2ee1d25"),
|
||||
TriStateFilter("Time Travel", "292e862b-2d17-4062-90a2-0356caa4ae27"),
|
||||
TriStateFilter(
|
||||
"Traditional Games", "31932a7e-5b8e-49a6-9f12-2afa39dc544c"),
|
||||
TriStateFilter("Vampires", "d7d1730f-6eb0-4ba6-9437-602cac38664c"),
|
||||
TriStateFilter("Video Games", "9438db5a-7e2a-4ac0-b39e-e0d95a34b8a8"),
|
||||
TriStateFilter("Villainess", "d14322ac-4d6f-4e9b-afd9-629d5f4d8a41"),
|
||||
TriStateFilter(
|
||||
"Virtual Reality", "8c86611e-fab7-4986-9dec-d1a2f44acdd5"),
|
||||
TriStateFilter("Zombies", "631ef465-9aba-4afb-b0fc-ea10efe274a8"),
|
||||
]),
|
||||
];
|
||||
}
|
||||
|
||||
@override
|
||||
List<dynamic> getSourcePreferences() {
|
||||
return [
|
||||
ListPreference(
|
||||
key: "cover_quality",
|
||||
title: "Cover quality",
|
||||
summary: "",
|
||||
valueIndex: 0,
|
||||
entries: ["Original", "Medium", "Low"],
|
||||
entryValues: ["", ".512.jpg", ".256.jpg"]),
|
||||
MultiSelectListPreference(
|
||||
key: "content_rating",
|
||||
title: "Default content rating",
|
||||
summary: "Show content with the selected rating by default",
|
||||
valueIndex: 0,
|
||||
entries: [
|
||||
"safe",
|
||||
"suggestive",
|
||||
"erotica",
|
||||
"pornographic"
|
||||
],
|
||||
entryValues: [
|
||||
"contentRating[]=safe",
|
||||
"contentRating[]=suggestive",
|
||||
"contentRating[]=erotica",
|
||||
"contentRating[]=pornographic"
|
||||
],
|
||||
values: [
|
||||
"contentRating[]=safe",
|
||||
"contentRating[]=suggestive"
|
||||
]),
|
||||
MultiSelectListPreference(
|
||||
key: "original_languages",
|
||||
title: "Filter original languages",
|
||||
summary:
|
||||
"Only show content that was originaly published in the selected languages in both latest and browse",
|
||||
valueIndex: 0,
|
||||
entries: [
|
||||
"Japanese",
|
||||
"Chinese",
|
||||
"Korean"
|
||||
],
|
||||
entryValues: [
|
||||
"originalLanguage[]=ja",
|
||||
"originalLanguage[]=zh&originalLanguage[]=zh-hk",
|
||||
"originalLanguage[]=ko"
|
||||
],
|
||||
values: []),
|
||||
];
|
||||
}
|
||||
|
||||
String preferenceContentRating(int sourceId) {
|
||||
final contentRating =
|
||||
getPreferenceValue(sourceId, "content_rating") as List<String>;
|
||||
String contentRatingStr = "";
|
||||
if (contentRating.isNotEmpty) {
|
||||
contentRatingStr = "&";
|
||||
for (var ctn in contentRating) {
|
||||
contentRatingStr += "&$ctn";
|
||||
}
|
||||
}
|
||||
return contentRatingStr;
|
||||
}
|
||||
|
||||
String preferenceOriginalLanguages(int sourceId) {
|
||||
final originalLanguages =
|
||||
getPreferenceValue(sourceId, "original_languages") as List<String>;
|
||||
String originalLanguagesStr = "";
|
||||
if (originalLanguages.isNotEmpty) {
|
||||
originalLanguagesStr = "&";
|
||||
for (var language in originalLanguages) {
|
||||
originalLanguagesStr += "&$language";
|
||||
}
|
||||
}
|
||||
return originalLanguagesStr;
|
||||
}
|
||||
|
||||
String ll(String url) {
|
||||
if (url.contains("?")) {
|
||||
return "&";
|
||||
}
|
||||
return "?";
|
||||
}
|
||||
}
|
||||
|
||||
MangaDex main(MSource source) {
|
||||
return MangaDex(source: source);
|
||||
}
|
||||
74
dart/manga/src/all/mangadex/sources.dart
Normal file
74
dart/manga/src/all/mangadex/sources.dart
Normal file
@@ -0,0 +1,74 @@
|
||||
import '../../../../../model/source.dart';
|
||||
|
||||
const _apiUrl = 'https://api.mangadex.org';
|
||||
const _baseUrl = 'https://mangadex.org';
|
||||
const _isNsfw = true;
|
||||
const _mangadexVersion = "0.0.75";
|
||||
const _mangadexSourceCodeUrl =
|
||||
"https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/$branchName/dart/manga/src/all/mangadex/mangadex.dart";
|
||||
String _iconUrl =
|
||||
"https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/$branchName/dart/manga/src/all/mangadex/icon.png";
|
||||
|
||||
final _languages = [
|
||||
"ar",
|
||||
"bn",
|
||||
"bg",
|
||||
"my",
|
||||
"ca",
|
||||
"zh",
|
||||
"zh-hk",
|
||||
"cs",
|
||||
"da",
|
||||
"nl",
|
||||
"en",
|
||||
"tl",
|
||||
"fi",
|
||||
"fr",
|
||||
"de",
|
||||
"el",
|
||||
"he",
|
||||
"hi",
|
||||
"hu",
|
||||
"id",
|
||||
"it",
|
||||
"ja",
|
||||
"kk",
|
||||
"ko",
|
||||
"la",
|
||||
"lt",
|
||||
"ms",
|
||||
"mn",
|
||||
"ne",
|
||||
"no",
|
||||
"fa",
|
||||
"pl",
|
||||
"pt-br",
|
||||
"pt",
|
||||
"ro",
|
||||
"ru",
|
||||
"sh",
|
||||
"es-419",
|
||||
"es",
|
||||
"sv",
|
||||
"ta",
|
||||
"th",
|
||||
"tr",
|
||||
"uk",
|
||||
"vi"
|
||||
];
|
||||
|
||||
List<Source> get mangaDexSourcesList => _mangaDexSourcesList;
|
||||
List<Source> _mangaDexSourcesList = _languages
|
||||
.map((e) => Source(
|
||||
name: 'MangaDex',
|
||||
apiUrl: _apiUrl,
|
||||
baseUrl: _baseUrl,
|
||||
lang: e,
|
||||
typeSource: "mangadex",
|
||||
iconUrl: _iconUrl,
|
||||
dateFormat: "yyyy-MM-dd'T'HH:mm:ss+SSS",
|
||||
isNsfw: _isNsfw,
|
||||
dateFormatLocale: 'en_Us',
|
||||
version: _mangadexVersion,
|
||||
sourceCodeUrl: _mangadexSourceCodeUrl))
|
||||
.toList();
|
||||
BIN
dart/manga/src/all/nhentai/icon.png
Normal file
BIN
dart/manga/src/all/nhentai/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.0 KiB |
245
dart/manga/src/all/nhentai/nhentai.dart
Normal file
245
dart/manga/src/all/nhentai/nhentai.dart
Normal file
@@ -0,0 +1,245 @@
|
||||
import 'package:mangayomi/bridge_lib.dart';
|
||||
|
||||
class NHentai extends MProvider {
|
||||
NHentai(this.source);
|
||||
|
||||
final MSource source;
|
||||
|
||||
final Client client = Client(source);
|
||||
|
||||
@override
|
||||
bool get supportsLatest => true;
|
||||
|
||||
@override
|
||||
Future<MPages> getPopular(int page) async {
|
||||
final nhLang = source.lang == "all"
|
||||
? "/search/?q=\"\"&sort=popular&"
|
||||
: "/language/${getLanguage()}/popular?";
|
||||
final res =
|
||||
(await client.get(Uri.parse("${source.baseUrl}${nhLang}page=$page")))
|
||||
.body;
|
||||
return parseMangaList(res);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<MPages> getLatestUpdates(int page) async {
|
||||
final nhLang = source.lang == "all" ? "/?" : "/language/${getLanguage()}/?";
|
||||
final res =
|
||||
(await client.get(Uri.parse("${source.baseUrl}${nhLang}page=$page")))
|
||||
.body;
|
||||
return parseMangaList(res);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<MPages> search(String query, int page, FilterList filterList) async {
|
||||
final filters = filterList.filters;
|
||||
final fixedQuery = query.isEmpty ? "\"\"" : query.trim();
|
||||
final nhLang = source.lang == "all" ? "" : "+${getLanguage()} ";
|
||||
String url = "${source.baseUrl}/";
|
||||
final isFavoriteFilter =
|
||||
filters.where((e) => e.type == "FavoriteFilter").toList().first.state;
|
||||
final isOkayToSort = filters
|
||||
.where((e) => e.type == "UploadedFilter")
|
||||
.toList()
|
||||
.first
|
||||
.state
|
||||
.isEmpty;
|
||||
|
||||
String advQuery = "";
|
||||
for (var filter in filters) {
|
||||
if (filter.type == "TextFilter" || filter.type == "UploadedFilter") {
|
||||
if (filter.state.isNotEmpty) {
|
||||
final splitState = (filter.state as String)
|
||||
.split(",")
|
||||
.where((e) => e.isNotEmpty ? true : false)
|
||||
.toList();
|
||||
String name = filter.name;
|
||||
for (var state in splitState) {
|
||||
final exclude = (state as String).startsWith("-");
|
||||
final text = (state as String).replaceFirst("-", "");
|
||||
if (exclude) advQuery += "-";
|
||||
advQuery += "$name:${text.trim()} ";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isFavoriteFilter) {
|
||||
url += "favorites/?q=$fixedQuery $advQuery&page=$page";
|
||||
} else {
|
||||
url += "search/?q=$fixedQuery $nhLang$advQuery&page=$page";
|
||||
if (isOkayToSort) {
|
||||
final sort =
|
||||
filters.where((e) => e.type == "SortFilter").toList().first;
|
||||
url += sort.values[sort.state].value;
|
||||
}
|
||||
}
|
||||
final res = (await client.get(Uri.parse(url))).body;
|
||||
return parseMangaList(res);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<MManga> getDetail(String url) async {
|
||||
final cleanTagRegExp = RegExp("\\(.*\\)");
|
||||
MManga manga = MManga();
|
||||
final res = (await client
|
||||
.get(Uri.parse("${source.baseUrl}${getUrlWithoutDomain(url)}")))
|
||||
.body;
|
||||
final document = parseHtml(res);
|
||||
final fullTitle =
|
||||
getTitle(document.selectFirst("#info > h1").text.replaceAll("\"", ""));
|
||||
final imageElement =
|
||||
document.selectFirst("#cover > a > img")?.attr("data-src") ?? "";
|
||||
manga.name = fullTitle;
|
||||
manga.imageUrl = imageElement;
|
||||
final artists =
|
||||
document.select("#tags > div:nth-child(3) > span > a .name");
|
||||
manga.artist = artists
|
||||
.map((MElement e) => e.text.replaceAll(cleanTagRegExp, ""))
|
||||
.toList()
|
||||
.join(", ");
|
||||
manga.author = manga.artist;
|
||||
manga.status = MStatus.completed;
|
||||
String description = "Full English and Japanese titles:\n";
|
||||
description += "$fullTitle\n";
|
||||
description += "${document.selectFirst("div#info h2").text}\n\n";
|
||||
description +=
|
||||
"Pages: ${document.selectFirst("#tags > div:nth-child(7) > span > a .name")?.text.replaceAll(cleanTagRegExp, "") ?? ""}\n";
|
||||
description +=
|
||||
"Favorited by: ${document.selectFirst("div#info i.fa-heart + span span").text.replaceAll("(", "").replaceAll(")", "")}\n";
|
||||
final categories =
|
||||
document.select("#tags > div:nth-child(6) > span > a .name");
|
||||
if (categories.isNotEmpty) {
|
||||
description +=
|
||||
"Categories: ${categories.map((MElement e) => e.text.replaceAll(cleanTagRegExp, "")).toList().join(", ")}\n\n";
|
||||
}
|
||||
manga.description = description;
|
||||
final tags = document.select("#tags > div:nth-child(2) > span > a .name");
|
||||
if (tags.isNotEmpty) {
|
||||
manga.genre = tags
|
||||
.map((MElement e) => e.text.replaceAll(cleanTagRegExp, ""))
|
||||
.toList();
|
||||
}
|
||||
final groups = document
|
||||
.select("#tags > div:nth-child(4) > span > a .name")
|
||||
.map((MElement e) => e.text.replaceAll(cleanTagRegExp, ""))
|
||||
.toList()
|
||||
.join(", ");
|
||||
final timeString =
|
||||
substringBefore(substringAfter(res, "datetime=\""), "\">")
|
||||
.replaceAll("T", " ");
|
||||
MChapter chapter = MChapter();
|
||||
chapter.name = "Chapter";
|
||||
chapter.scanlator = groups;
|
||||
chapter.dateUpload =
|
||||
parseDates([timeString], "yyyy-MM-dd HH:mm:ss.SSSSSSZ", "en")[0];
|
||||
chapter.url = getUrlWithoutDomain(url);
|
||||
manga.chapters = [chapter];
|
||||
return manga;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<String>> getPageList(String url) async {
|
||||
final res = (await client
|
||||
.get(Uri.parse("${source.baseUrl}${getUrlWithoutDomain(url)}")))
|
||||
.body;
|
||||
final document = parseHtml(res);
|
||||
final script = document
|
||||
.select("script")
|
||||
.where((MElement e) => e.outerHtml.contains("media_server"))
|
||||
.toList()
|
||||
.first;
|
||||
final mediaServer = RegExp(r"media_server\s*:\s*(\d+)")
|
||||
.firstMatch(script.outerHtml)
|
||||
?.group(1);
|
||||
List<String> pages = [];
|
||||
final pageDocs = document.select("div.thumbs a > img");
|
||||
for (var pageElement in pageDocs) {
|
||||
pages.add(pageElement.getDataSrc
|
||||
.replaceAll("t.nh", "i.nh")
|
||||
.replaceAll(RegExp("t\\d+.nh"), "i$mediaServer.nh")
|
||||
.replaceAll("t.", "."));
|
||||
}
|
||||
return pages;
|
||||
}
|
||||
|
||||
String getLanguage() {
|
||||
return {"en": "english", "ja": "japanese", "zh": "chinese"}[source.lang];
|
||||
}
|
||||
|
||||
MPages parseMangaList(String res) {
|
||||
List<MManga> mangaList = [];
|
||||
final document = parseHtml(res);
|
||||
final result = document
|
||||
.select("#content .container")
|
||||
.where((MElement e) => e.className == "container index-container")
|
||||
.toList();
|
||||
if (result.isNotEmpty) {
|
||||
for (var element in (result.first as MElement).select(".gallery")) {
|
||||
MManga manga = MManga();
|
||||
manga.name =
|
||||
getTitle(element.selectFirst("a > div").text.replaceAll("\"", ""));
|
||||
manga.link = getUrlWithoutDomain(element.selectFirst("a").getHref);
|
||||
final imageElement = element.selectFirst(".cover img");
|
||||
manga.imageUrl =
|
||||
imageElement?.attr("data-src") ?? imageElement?.attr("src");
|
||||
mangaList.add(manga);
|
||||
}
|
||||
}
|
||||
return MPages(mangaList,
|
||||
document.selectFirst("#content > section.pagination > a.next") != null);
|
||||
}
|
||||
|
||||
@override
|
||||
List<dynamic> getFilterList() {
|
||||
return [
|
||||
HeaderFilter("Separate tags with commas (,)"),
|
||||
HeaderFilter("Prepend with dash (-) to exclude"),
|
||||
TextFilter("TextFilter", "Tags"),
|
||||
TextFilter("TextFilter", "Categories"),
|
||||
TextFilter("TextFilter", "Groups"),
|
||||
TextFilter("TextFilter", "Artists"),
|
||||
TextFilter("TextFilter", "Parodies"),
|
||||
TextFilter("TextFilter", "Characters"),
|
||||
HeaderFilter("Uploaded valid units are h, d, w, m, y."),
|
||||
HeaderFilter("example: (>20d)"),
|
||||
TextFilter("UploadedFilter", "Uploaded"),
|
||||
HeaderFilter("Filter by pages, for example: (>20)"),
|
||||
TextFilter("TextFilter", "Pages"),
|
||||
SeparatorFilter(),
|
||||
SelectFilter("SortFilter", "Sort By", 0, [
|
||||
SelectFilterOption("Popular: All Time", "&sort=popular"),
|
||||
SelectFilterOption("Popular: Week", "&sort=popular-week"),
|
||||
SelectFilterOption("Popular: Today", "&sort=popular-today"),
|
||||
SelectFilterOption("Recent", "&sort=date")
|
||||
]),
|
||||
HeaderFilter("Sort is ignored if favorites only"),
|
||||
CheckBoxFilter("Show favorites only", "true", "FavoriteFilter")
|
||||
];
|
||||
}
|
||||
|
||||
@override
|
||||
List<dynamic> getSourcePreferences() {
|
||||
return [
|
||||
ListPreference(
|
||||
key: "preferred_display_title",
|
||||
title: "Display manga title as:",
|
||||
summary: "",
|
||||
valueIndex: 0,
|
||||
entries: ["Full Title", "Short Title"],
|
||||
entryValues: ["full", "short"]),
|
||||
];
|
||||
}
|
||||
|
||||
String getTitle(String title) {
|
||||
bool displayFullTitle =
|
||||
getPreferenceValue(source.id, "preferred_display_title") == "full";
|
||||
if (displayFullTitle) {
|
||||
return title.trim();
|
||||
}
|
||||
return title.replaceAll(RegExp(r"(\[[^]]*]|[({][^)}]*[)}])"), "").trim();
|
||||
}
|
||||
}
|
||||
|
||||
NHentai main(MSource source) {
|
||||
return NHentai(source);
|
||||
}
|
||||
26
dart/manga/src/all/nhentai/sources.dart
Normal file
26
dart/manga/src/all/nhentai/sources.dart
Normal file
@@ -0,0 +1,26 @@
|
||||
import '../../../../../model/source.dart';
|
||||
|
||||
const _nhentaiVersion = "0.0.15";
|
||||
const _nhentaiSourceCodeUrl =
|
||||
"https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/$branchName/dart/manga/src/all/nhentai/nhentai.dart";
|
||||
|
||||
String _iconUrl =
|
||||
"https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/$branchName/dart/manga/src/all/nhentai/icon.png";
|
||||
const _baseUrl = 'https://nhentai.net';
|
||||
const _isNsfw = true;
|
||||
|
||||
List<String> _languages = ["all", "en", "zh", "ja"];
|
||||
|
||||
List<Source> get nhentaiSourcesList => _nhentaiSourcesList;
|
||||
List<Source> _nhentaiSourcesList = _languages
|
||||
.map((e) => Source(
|
||||
name: 'NHentai',
|
||||
baseUrl: _baseUrl,
|
||||
hasCloudflare: true,
|
||||
lang: e,
|
||||
typeSource: "NHentai",
|
||||
iconUrl: _iconUrl,
|
||||
isNsfw: _isNsfw,
|
||||
version: _nhentaiVersion,
|
||||
sourceCodeUrl: _nhentaiSourceCodeUrl))
|
||||
.toList();
|
||||
BIN
dart/manga/src/en/mangahere/icon.png
Normal file
BIN
dart/manga/src/en/mangahere/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.7 KiB |
341
dart/manga/src/en/mangahere/mangahere.dart
Normal file
341
dart/manga/src/en/mangahere/mangahere.dart
Normal file
@@ -0,0 +1,341 @@
|
||||
import 'package:mangayomi/bridge_lib.dart';
|
||||
import 'dart:convert';
|
||||
|
||||
class MangaHere extends MProvider {
|
||||
MangaHere({required this.source});
|
||||
|
||||
MSource source;
|
||||
|
||||
final Client client = Client(source);
|
||||
|
||||
@override
|
||||
Future<MPages> getPopular(int page) async {
|
||||
final res = (await client.get(
|
||||
Uri.parse("${source.baseUrl}/directory/$page.htm"),
|
||||
headers: getHeader(source.baseUrl)))
|
||||
.body;
|
||||
|
||||
List<MManga> mangaList = [];
|
||||
final names =
|
||||
xpath(res, '//*[ contains(@class, "manga-list-1-list")]/li/a/@title');
|
||||
final images = xpath(res,
|
||||
'//*[ contains(@class, "manga-list-1-list")]/li/a/img[@class="manga-list-1-cover"]/@src');
|
||||
final urls =
|
||||
xpath(res, '//*[ contains(@class, "manga-list-1-list")]/li/a/@href');
|
||||
|
||||
for (var i = 0; i < names.length; i++) {
|
||||
MManga manga = MManga();
|
||||
manga.name = names[i];
|
||||
manga.imageUrl = images[i];
|
||||
manga.link = urls[i];
|
||||
mangaList.add(manga);
|
||||
}
|
||||
|
||||
return MPages(mangaList, true);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<MPages> getLatestUpdates(int page) async {
|
||||
final res = (await client.get(
|
||||
Uri.parse("${source.baseUrl}/directory/$page.htm?latest"),
|
||||
headers: getHeader(source.baseUrl)))
|
||||
.body;
|
||||
|
||||
List<MManga> mangaList = [];
|
||||
final names =
|
||||
xpath(res, '//*[ contains(@class, "manga-list-1-list")]/li/a/@title');
|
||||
final images = xpath(res,
|
||||
'//*[ contains(@class, "manga-list-1-list")]/li/a/img[@class="manga-list-1-cover"]/@src');
|
||||
final urls =
|
||||
xpath(res, '//*[ contains(@class, "manga-list-1-list")]/li/a/@href');
|
||||
|
||||
for (var i = 0; i < names.length; i++) {
|
||||
MManga manga = MManga();
|
||||
manga.name = names[i];
|
||||
manga.imageUrl = images[i];
|
||||
manga.link = urls[i];
|
||||
mangaList.add(manga);
|
||||
}
|
||||
|
||||
return MPages(mangaList, true);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<MPages> search(String query, int page, FilterList filterList) async {
|
||||
final filters = filterList.filters;
|
||||
String url = "${source.baseUrl}/search";
|
||||
|
||||
for (var filter in filters) {
|
||||
if (filter.type == "TypeList") {
|
||||
final type = filter.values[filter.state].value;
|
||||
url += "${ll(url)}type=$type";
|
||||
} else if (filter.type == "CompletionList") {
|
||||
final cmp = filter.values[filter.state].value;
|
||||
url += "${ll(url)}st=$cmp";
|
||||
} else if (filter.type == "RatingList") {
|
||||
url += "${ll(url)}rating_method=gt";
|
||||
final rt = filter.values[filter.state].value;
|
||||
url += "${ll(url)}rating=$rt";
|
||||
} else if (filter.type == "GenreList") {
|
||||
final included = (filter.state as List)
|
||||
.where((e) => e.state == 1 ? true : false)
|
||||
.toList();
|
||||
final excluded = (filter.state as List)
|
||||
.where((e) => e.state == 2 ? true : false)
|
||||
.toList();
|
||||
if (included.isNotEmpty) {
|
||||
url += "${ll(url)}genres=";
|
||||
for (var val in included) {
|
||||
url += "${val.value},";
|
||||
}
|
||||
}
|
||||
if (excluded.isNotEmpty) {
|
||||
url += "${ll(url)}nogenres=";
|
||||
for (var val in excluded) {
|
||||
url += "${val.value},";
|
||||
}
|
||||
}
|
||||
} else if (filter.type == "ArtistFilter") {
|
||||
url += "${ll(url)}artist_method=cw";
|
||||
url += "${ll(url)}artist=${Uri.encodeComponent(filter.state)}";
|
||||
} else if (filter.type == "AuthorFilter") {
|
||||
url += "${ll(url)}author_method=cw";
|
||||
url += "${ll(url)}author=${Uri.encodeComponent(filter.state)}";
|
||||
} else if (filter.type == "YearFilter") {
|
||||
url += "${ll(url)}released_method=cw";
|
||||
url += "${ll(url)}released=${Uri.encodeComponent(filter.state)}";
|
||||
}
|
||||
}
|
||||
url += "${ll(url)}title=$query&page=$page";
|
||||
final res =
|
||||
(await client.get(Uri.parse(url), headers: getHeader(source.baseUrl)))
|
||||
.body;
|
||||
|
||||
List<MManga> mangaList = [];
|
||||
final names =
|
||||
xpath(res, '//*[contains(@class, "manga-list-4-list")]/li/a/@title');
|
||||
final images = xpath(res,
|
||||
'//*[contains(@class, "manga-list-4-list")]/li/a/img[@class="manga-list-4-cover"]/@src');
|
||||
final urls =
|
||||
xpath(res, '//*[contains(@class, "manga-list-4-list")]/li/a/@href');
|
||||
|
||||
for (var i = 0; i < names.length; i++) {
|
||||
MManga manga = MManga();
|
||||
manga.name = names[i];
|
||||
manga.imageUrl = images[i];
|
||||
manga.link = urls[i];
|
||||
mangaList.add(manga);
|
||||
}
|
||||
|
||||
return MPages(mangaList, true);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<MManga> getDetail(String url) async {
|
||||
final statusList = [
|
||||
{"Ongoing": 0, "Completed": 1}
|
||||
];
|
||||
final res = (await client.get(Uri.parse("${source.baseUrl}/$url"),
|
||||
headers: getHeader(source.baseUrl)))
|
||||
.body;
|
||||
MManga manga = MManga();
|
||||
manga.author =
|
||||
xpath(res, '//*[@class="detail-info-right-say"]/a/text()').first;
|
||||
manga.description = xpath(res, '//*[@class="fullcontent"]/text()').first;
|
||||
final status =
|
||||
xpath(res, '//*[@class="detail-info-right-title-tip"]/text()').first;
|
||||
manga.status = parseStatus(status, statusList);
|
||||
manga.genre =
|
||||
xpath(res, '//*[@class="detail-info-right-tag-list"]/a/text()');
|
||||
|
||||
var chapUrls = xpath(res, '//*[@class="detail-main-list"]/li/a/@href');
|
||||
var chaptersNames = xpath(res,
|
||||
'//*[@class="detail-main-list"]/li/a/div/p[@class="title3"]/text()');
|
||||
final chapterDates = xpath(res,
|
||||
'//*[@class="detail-main-list"]/li/a/div/p[@class="title2"]/text()');
|
||||
var dateUploads =
|
||||
parseDates(chapterDates, source.dateFormat, source.dateFormatLocale);
|
||||
|
||||
List<MChapter>? chaptersList = [];
|
||||
for (var i = 0; i < chaptersNames.length; i++) {
|
||||
MChapter chapter = MChapter();
|
||||
chapter.name = chaptersNames[i];
|
||||
chapter.url = chapUrls[i];
|
||||
chapter.dateUpload = dateUploads[i];
|
||||
chaptersList.add(chapter);
|
||||
}
|
||||
manga.chapters = chaptersList;
|
||||
return manga;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<String>> getPageList(String url) async {
|
||||
final headers = getHeader(source.baseUrl);
|
||||
final urll = "${source.baseUrl}$url";
|
||||
final res = (await client.get(Uri.parse(urll), headers: headers)).body;
|
||||
final pages = xpath(res, "//body/div/div/span/a/text()");
|
||||
List<String> pageUrls = [];
|
||||
if (pages.isEmpty) {
|
||||
final script = xpath(
|
||||
res, "//script[contains(text(),'function(p,a,c,k,e,d)')]/text()")
|
||||
.first
|
||||
.replaceAll("eval", "");
|
||||
String deobfuscatedScript = unpackJs(script);
|
||||
int a = deobfuscatedScript.indexOf("newImgs=['") + 10;
|
||||
int b = deobfuscatedScript.indexOf("'];");
|
||||
List<String> urls = deobfuscatedScript.substring(a, b).split("','");
|
||||
for (var url in urls) {
|
||||
pageUrls.add("https:$url");
|
||||
}
|
||||
} else {
|
||||
final pagesNumberList = pages;
|
||||
int pagesNumber = int.parse(pagesNumberList[pagesNumberList.length - 2]);
|
||||
int secretKeyScriptLocation = res.indexOf("eval(function(p,a,c,k,e,d)");
|
||||
int secretKeyScriptEndLocation =
|
||||
res.indexOf("</script>", secretKeyScriptLocation);
|
||||
String secretKeyScript = res
|
||||
.substring(secretKeyScriptLocation, secretKeyScriptEndLocation)
|
||||
.replaceAll("eval", "");
|
||||
String secretKeyDeobfuscatedScript = unpackJs(secretKeyScript);
|
||||
int secretKeyStartLoc = secretKeyDeobfuscatedScript.indexOf("'");
|
||||
int secretKeyEndLoc = secretKeyDeobfuscatedScript.indexOf(";");
|
||||
|
||||
String secretKey = secretKeyDeobfuscatedScript.substring(
|
||||
secretKeyStartLoc, secretKeyEndLoc);
|
||||
int chapterIdStartLoc = res.indexOf("chapterid");
|
||||
String chapterId = res.substring(
|
||||
chapterIdStartLoc + 11, res.indexOf(";", chapterIdStartLoc));
|
||||
String pageBase = urll.substring(0, urll.lastIndexOf("/"));
|
||||
for (int i = 1; i <= pagesNumber; i++) {
|
||||
String pageLink =
|
||||
"$pageBase/chapterfun.ashx?cid=$chapterId&page=$i&key=$secretKey";
|
||||
String responseText = "".toString();
|
||||
for (int tr = 1; tr <= 3; tr++) {
|
||||
if (responseText.isEmpty) {
|
||||
final headers = {
|
||||
"Referer": urll,
|
||||
"Accept": "*/*",
|
||||
"Accept-Language": "en-US,en;q=0.9",
|
||||
"Connection": "keep-alive",
|
||||
"Host": "www.mangahere.cc",
|
||||
"X-Requested-With": "XMLHttpRequest"
|
||||
};
|
||||
|
||||
final ress =
|
||||
(await client.get(Uri.parse(pageLink), headers: headers)).body;
|
||||
|
||||
responseText = ress;
|
||||
|
||||
if (responseText.isEmpty) {
|
||||
secretKey = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
String deobfuscatedScript =
|
||||
unpackJs(responseText.replaceAll("eval", ""));
|
||||
|
||||
int baseLinkStartPos = deobfuscatedScript.indexOf("pix=") + 5;
|
||||
int baseLinkEndPos =
|
||||
deobfuscatedScript.indexOf(";", baseLinkStartPos) - 1;
|
||||
String baseLink =
|
||||
deobfuscatedScript.substring(baseLinkStartPos, baseLinkEndPos);
|
||||
|
||||
int imageLinkStartPos = deobfuscatedScript.indexOf("pvalue=") + 9;
|
||||
int imageLinkEndPos =
|
||||
deobfuscatedScript.indexOf("\"", imageLinkStartPos);
|
||||
String imageLink =
|
||||
deobfuscatedScript.substring(imageLinkStartPos, imageLinkEndPos);
|
||||
pageUrls.add("https:$baseLink$imageLink");
|
||||
}
|
||||
}
|
||||
|
||||
return pageUrls;
|
||||
}
|
||||
|
||||
String ll(String url) {
|
||||
if (url.contains("?")) {
|
||||
return "&";
|
||||
}
|
||||
return "?";
|
||||
}
|
||||
|
||||
@override
|
||||
List<dynamic> getFilterList() {
|
||||
return [
|
||||
SelectFilter("TypeList", "Type", 1, [
|
||||
SelectFilterOption("American Manga", "5"),
|
||||
SelectFilterOption("Any", "0"),
|
||||
SelectFilterOption("Chinese Manhua", "3"),
|
||||
SelectFilterOption("European Manga", "4"),
|
||||
SelectFilterOption("Hong Kong Manga", "6"),
|
||||
SelectFilterOption("Japanese Manga", "1"),
|
||||
SelectFilterOption("Korean Manhwa", "2"),
|
||||
SelectFilterOption("Other Manga", "7"),
|
||||
]),
|
||||
TextFilter("ArtistFilter", "Artist"),
|
||||
TextFilter("AuthorFilter", "Author"),
|
||||
GroupFilter("GenreList", "Genres", [
|
||||
TriStateFilter("Action", "1"),
|
||||
TriStateFilter("Adventure", "2"),
|
||||
TriStateFilter("Comedy", "3"),
|
||||
TriStateFilter("Fantasy", "4"),
|
||||
TriStateFilter("Historical", "5"),
|
||||
TriStateFilter("Horror", "6"),
|
||||
TriStateFilter("Martial Arts", "7"),
|
||||
TriStateFilter("Mystery", "8"),
|
||||
TriStateFilter("Romance", "9"),
|
||||
TriStateFilter("Shounen Ai", "10"),
|
||||
TriStateFilter("Supernatural", "11"),
|
||||
TriStateFilter("Drama", "12"),
|
||||
TriStateFilter("Shounen", "13"),
|
||||
TriStateFilter("School Life", "14"),
|
||||
TriStateFilter("Shoujo", "15"),
|
||||
TriStateFilter("Gender Bender", "16"),
|
||||
TriStateFilter("Josei", "17"),
|
||||
TriStateFilter("Psychological", "18"),
|
||||
TriStateFilter("Seinen", "19"),
|
||||
TriStateFilter("Slice of Life", "20"),
|
||||
TriStateFilter("Sci-fi", "21"),
|
||||
TriStateFilter("Ecchi", "22"),
|
||||
TriStateFilter("Harem", "23"),
|
||||
TriStateFilter("Shoujo Ai", "24"),
|
||||
TriStateFilter("Yuri", "25"),
|
||||
TriStateFilter("Mature", "26"),
|
||||
TriStateFilter("Tragedy", "27"),
|
||||
TriStateFilter("Yaoi", "28"),
|
||||
TriStateFilter("Doujinshi", "29"),
|
||||
TriStateFilter("Sports", "30"),
|
||||
TriStateFilter("Adult", "31"),
|
||||
TriStateFilter("One Shot", "32"),
|
||||
TriStateFilter("Smut", "33"),
|
||||
TriStateFilter("Mecha", "34"),
|
||||
TriStateFilter("Shotacon", "35"),
|
||||
TriStateFilter("Lolicon", "36"),
|
||||
TriStateFilter("Webtoons", "37"),
|
||||
]),
|
||||
SelectFilter("RatingList", "Minimum rating", 0, [
|
||||
SelectFilterOption("No Stars", "0"),
|
||||
SelectFilterOption("1 Star", "1"),
|
||||
SelectFilterOption("2 Stars", "2"),
|
||||
SelectFilterOption("3 Stars", "3"),
|
||||
SelectFilterOption("4 Stars", "4"),
|
||||
SelectFilterOption("5 Stars", "5"),
|
||||
]),
|
||||
TextFilter("YearFilter", "Year released"),
|
||||
SelectFilter("CompletionList", "Completed series", 0, [
|
||||
SelectFilterOption("Either", "0"),
|
||||
SelectFilterOption("No", "1"),
|
||||
SelectFilterOption("Yes", "2"),
|
||||
]),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, String> getHeader(String url) {
|
||||
final headers = {'Referer': '$url/', "Cookie": "isAdult=1"};
|
||||
return headers;
|
||||
}
|
||||
|
||||
MangaHere main(MSource source) {
|
||||
return MangaHere(source: source);
|
||||
}
|
||||
18
dart/manga/src/en/mangahere/source.dart
Normal file
18
dart/manga/src/en/mangahere/source.dart
Normal file
@@ -0,0 +1,18 @@
|
||||
import '../../../../../model/source.dart';
|
||||
|
||||
Source get mangahereSource => _mangahereSource;
|
||||
const _mangahereVersion = "0.0.7";
|
||||
const _mangahereSourceCodeUrl =
|
||||
"https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/$branchName/dart/manga/src/en/mangahere/mangahere.dart";
|
||||
Source _mangahereSource = Source(
|
||||
name: "MangaHere",
|
||||
baseUrl: "http://www.mangahere.cc",
|
||||
lang: "en",
|
||||
typeSource: "single",
|
||||
iconUrl:
|
||||
"https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/$branchName/dart/manga/src/en/mangahere/icon.png",
|
||||
sourceCodeUrl: _mangahereSourceCodeUrl,
|
||||
version: _mangahereVersion,
|
||||
dateFormat: "MMM dd,yyyy",
|
||||
dateFormatLocale: "en",
|
||||
);
|
||||
Reference in New Issue
Block a user