Reorganize folders

This commit is contained in:
kodjomoustapha
2024-03-28 11:13:42 +01:00
parent abdc9cab62
commit 67109cdbbc
565 changed files with 988 additions and 1863 deletions

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

View 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();

View 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);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

View 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();

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

View 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);
}

View 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();

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

View 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);
}

View 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();

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

View 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);
}

View 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",
);