mirror of
https://github.com/kodjodevf/mangayomi-extensions.git
synced 2026-02-14 10:51:17 +00:00
rewrite MangaDex source code in JS
This commit is contained in:
@@ -4,12 +4,10 @@ import 'multisrc/mangabox/sources.dart';
|
|||||||
import 'multisrc/mangareader/sources.dart';
|
import 'multisrc/mangareader/sources.dart';
|
||||||
import 'multisrc/mmrcms/sources.dart';
|
import 'multisrc/mmrcms/sources.dart';
|
||||||
import 'multisrc/nepnep/sources.dart';
|
import 'multisrc/nepnep/sources.dart';
|
||||||
import 'src/all/mangadex/sources.dart';
|
|
||||||
import 'src/en/mangahere/source.dart';
|
import 'src/en/mangahere/source.dart';
|
||||||
|
|
||||||
List<Source> dartMangasourceList = [
|
List<Source> dartMangasourceList = [
|
||||||
...madaraSourcesList,
|
...madaraSourcesList,
|
||||||
...mangaDexSourcesList,
|
|
||||||
...mangareaderSourcesList,
|
...mangareaderSourcesList,
|
||||||
...mmrcmsSourcesList,
|
...mmrcmsSourcesList,
|
||||||
mangahereSource,
|
mangahereSource,
|
||||||
|
|||||||
@@ -1,580 +0,0 @@
|
|||||||
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
|
|
||||||
Map<String, String> get headers =>
|
|
||||||
{"user-agent": getPreferenceValue(source.id, "custom_user_agent")};
|
|
||||||
@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&contentRating[]=safe&contentRating[]=suggestive${preferenceOriginalLanguages(source.id)}&order[followedCount]=desc";
|
|
||||||
final res = (await client.get(Uri.parse(url), headers: headers)).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), headers: headers)).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}&contentRating[]=safe&contentRating[]=suggestive${preferenceOriginalLanguages(source.id)}$mangaIdss";
|
|
||||||
final res = (await client.get(Uri.parse(newUrl), headers: headers)).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), headers: headers)).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"),
|
|
||||||
headers: headers))
|
|
||||||
.body;
|
|
||||||
MManga manga = MManga();
|
|
||||||
final coverUrl = jsonPathToString(
|
|
||||||
res, r'$..data.relationships[*].attributes.fileName', '');
|
|
||||||
if (coverUrl != null) {
|
|
||||||
manga.imageUrl = "https://uploads.mangadex.org/covers/${url.replaceAll("/manga/", "")}/${coverUrl}";
|
|
||||||
}
|
|
||||||
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"),
|
|
||||||
headers: headers))
|
|
||||||
.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&contentRating[]=safe&contentRating[]=suggestive';
|
|
||||||
final res = (await client.get(Uri.parse(url), headers: headers)).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),
|
|
||||||
]),
|
|
||||||
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: "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: []),
|
|
||||||
EditTextPreference(
|
|
||||||
key: "custom_user_agent",
|
|
||||||
title: "Set custom User-Agent",
|
|
||||||
summary: "",
|
|
||||||
value:
|
|
||||||
"Dalvik/2.1.0 (Linux; U; Android 14; 22081212UG Build/UKQ1.230917.001)",
|
|
||||||
dialogTitle: "Set custom User-Agent",
|
|
||||||
dialogMessage: "Specify a custom user agent",
|
|
||||||
text:
|
|
||||||
"Dalvik/2.1.0 (Linux; U; Android 14; 22081212UG Build/UKQ1.230917.001)"),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
import '../../../../../model/source.dart';
|
|
||||||
|
|
||||||
const _apiUrl = 'https://api.mangadex.org';
|
|
||||||
const _baseUrl = 'https://mangadex.org';
|
|
||||||
const _isNsfw = true;
|
|
||||||
const _mangadexVersion = "0.1.1";
|
|
||||||
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,
|
|
||||||
itemType: ItemType.manga,
|
|
||||||
sourceCodeUrl: _mangadexSourceCodeUrl))
|
|
||||||
.toList();
|
|
||||||
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 3.9 KiB |
544
javascript/manga/src/all/mangadex.js
Normal file
544
javascript/manga/src/all/mangadex.js
Normal file
@@ -0,0 +1,544 @@
|
|||||||
|
const mangayomiSources = [{
|
||||||
|
"name": "MangaDex",
|
||||||
|
"langs": ["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"],
|
||||||
|
"ids": {
|
||||||
|
"ar": 202373705,
|
||||||
|
"bn": 860658373,
|
||||||
|
"bg": 722270529,
|
||||||
|
"my": 978675083,
|
||||||
|
"ca": 689496451,
|
||||||
|
"zh": 593575397,
|
||||||
|
"zh-hk": 115179159,
|
||||||
|
"cs": 869144666,
|
||||||
|
"da": 846142909,
|
||||||
|
"nl": 841149659,
|
||||||
|
"en": 810342358,
|
||||||
|
"tl": 309024312,
|
||||||
|
"fi": 164642544,
|
||||||
|
"fr": 545017689,
|
||||||
|
"de": 110023605,
|
||||||
|
"el": 767687578,
|
||||||
|
"he": 511907642,
|
||||||
|
"hi": 986826068,
|
||||||
|
"hu": 128441350,
|
||||||
|
"id": 183977130,
|
||||||
|
"it": 127887438,
|
||||||
|
"ja": 204112007,
|
||||||
|
"kk": 1063442064,
|
||||||
|
"ko": 898061477,
|
||||||
|
"la": 387646759,
|
||||||
|
"lt": 270482698,
|
||||||
|
"ms": 284400542,
|
||||||
|
"mn": 525041874,
|
||||||
|
"ne": 613632949,
|
||||||
|
"no": 441032670,
|
||||||
|
"fa": 693311514,
|
||||||
|
"pl": 683661227,
|
||||||
|
"pt-br": 417850874,
|
||||||
|
"pt": 1027115198,
|
||||||
|
"ro": 399589398,
|
||||||
|
"ru": 367421943,
|
||||||
|
"sh": 254140838,
|
||||||
|
"es-419": 823535267,
|
||||||
|
"es": 736630443,
|
||||||
|
"sv": 146351677,
|
||||||
|
"ta": 739930809,
|
||||||
|
"th": 385031783,
|
||||||
|
"tr": 1008587213,
|
||||||
|
"uk": 778357609,
|
||||||
|
"vi": 88174952
|
||||||
|
},
|
||||||
|
"baseUrl": "https://mangadex.org",
|
||||||
|
"apiUrl": "https://api.mangadex.org",
|
||||||
|
"iconUrl": "https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/main/javascript/icon/all.mangadex.png",
|
||||||
|
"typeSource": "single",
|
||||||
|
"itemType": 0,
|
||||||
|
"version": "0.1.2",
|
||||||
|
"pkgPath": "manga/src/all/mangadex.js"
|
||||||
|
}];
|
||||||
|
|
||||||
|
class DefaultExtension extends MProvider {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.client = new Client();
|
||||||
|
}
|
||||||
|
getHeaders(url) {
|
||||||
|
return {
|
||||||
|
"user-agent": this.getPreference("custom_user_agent"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
async getPopular(page) {
|
||||||
|
const offset = 20 * (page - 1);
|
||||||
|
const url = `${this.source.apiUrl}/manga?limit=20&offset=${offset}&availableTranslatedLanguage[]=${this.source.lang}&includes[]=cover_art&contentRating[]=safe&contentRating[]=suggestive${this.preferenceOriginalLanguages()}&order[followedCount]=desc`;
|
||||||
|
const response = await this.client.get(url, this.getHeaders());
|
||||||
|
return this.mangaRes(response.body);
|
||||||
|
}
|
||||||
|
async getLatestUpdates(page) {
|
||||||
|
const offset = 20 * (page - 1);
|
||||||
|
const url = `${this.source.apiUrl}/chapter?limit=20&offset=${offset}&translatedLanguage[]=${this.source.lang}&includeFutureUpdates=0&order[publishAt]=desc&includeFuturePublishAt=0&includeEmptyPages=0`;
|
||||||
|
const response = await this.client.get(url, this.getHeaders());
|
||||||
|
const mangaIds = Array.from(
|
||||||
|
new Set(
|
||||||
|
JSON.parse(response.body).data
|
||||||
|
.flatMap(item => item.relationships)
|
||||||
|
.filter(relationship => relationship.type === "manga")
|
||||||
|
.map(mangaData => mangaData.id)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
const mangaIdss = mangaIds.map(id => `&ids[]=${id}`).join("");
|
||||||
|
const newUrl = `${this.source.apiUrl}/manga?includes[]=cover_art&limit=${mangaIds.length}&contentRating[]=safe&contentRating[]=suggestive${this.preferenceOriginalLanguages()}${mangaIdss}`;
|
||||||
|
const newResponse = await this.client.get(newUrl, this.getHeaders());
|
||||||
|
return this.mangaRes(newResponse.body);
|
||||||
|
}
|
||||||
|
async search(query, page, filters) {
|
||||||
|
let offset = 20 * (page - 1);
|
||||||
|
let url = `${this.source.apiUrl}/manga?includes[]=cover_art&offset=${offset}&limit=20&title=${query}`;
|
||||||
|
|
||||||
|
filters.forEach(filter => {
|
||||||
|
if (filter.type === "HasAvailableChaptersFilter") {
|
||||||
|
if (filter.state) {
|
||||||
|
url += `${this.ll(url)}hasAvailableChapters=true`;
|
||||||
|
url += `${this.ll(url)}availableTranslatedLanguage[]=${source.lang}`;
|
||||||
|
}
|
||||||
|
} else if (filter.type === "OriginalLanguageList") {
|
||||||
|
const langs = filter.state.filter(e => e.state);
|
||||||
|
langs.forEach(lang => {
|
||||||
|
url += `${this.ll(url)}${lang.value}`;
|
||||||
|
});
|
||||||
|
} else if (filter.type === "ContentRatingList") {
|
||||||
|
const ratings = filter.state.filter(e => e.state);
|
||||||
|
ratings.forEach(rating => {
|
||||||
|
url += `${this.ll(url)}${rating.value}`;
|
||||||
|
});
|
||||||
|
} else if (filter.type === "DemographicList") {
|
||||||
|
const demographics = filter.state.filter(e => e.state);
|
||||||
|
demographics.forEach(demographic => {
|
||||||
|
url += `${this.ll(url)}${demographic.value}`;
|
||||||
|
});
|
||||||
|
} else if (filter.type === "StatusList") {
|
||||||
|
const statuses = filter.state.filter(e => e.state);
|
||||||
|
statuses.forEach(status => {
|
||||||
|
url += `${this.ll(url)}${status.value}`;
|
||||||
|
});
|
||||||
|
} else if (filter.type === "SortFilter") {
|
||||||
|
const value = filter.state.ascending ? "asc" : "desc";
|
||||||
|
url += `${this.ll(url)}order[${filter.values[filter.state.index].value}]=${value}`;
|
||||||
|
} else if (filter.type === "TagsFilter") {
|
||||||
|
filter.state.forEach(tag => {
|
||||||
|
url += `${this.ll(url)}${tag.values[tag.state].value}`;
|
||||||
|
});
|
||||||
|
} else if (filter.type === "FormatFilter") {
|
||||||
|
const included = filter.state.filter(e => e.state === 1);
|
||||||
|
const excluded = filter.state.filter(e => e.state === 2);
|
||||||
|
included.forEach(val => {
|
||||||
|
url += `${this.ll(url)}includedTags[]=${val.value}`;
|
||||||
|
});
|
||||||
|
excluded.forEach(val => {
|
||||||
|
url += `${this.ll(url)}excludedTags[]=${val.value}`;
|
||||||
|
});
|
||||||
|
} else if (filter.type === "GenreFilter" || filter.type === "ThemeFilter") {
|
||||||
|
const included = filter.state.filter(e => e.state === 1);
|
||||||
|
const excluded = filter.state.filter(e => e.state === 2);
|
||||||
|
included.forEach(val => {
|
||||||
|
url += `${this.ll(url)}includedTags[]=${val.value}`;
|
||||||
|
});
|
||||||
|
excluded.forEach(val => {
|
||||||
|
url += `${this.ll(url)}excludedTags[]=${val.value}`;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await this.client.get(url, this.getHeaders());
|
||||||
|
return this.mangaRes(response.body);
|
||||||
|
}
|
||||||
|
async getDetail(url) {
|
||||||
|
const detailUrl = `${this.source.apiUrl}${url}?includes[]=cover_art&includes[]=author&includes[]=artist`;
|
||||||
|
const response = await this.client.get(detailUrl, this.getHeaders());
|
||||||
|
const data = JSON.parse(response.body).data;
|
||||||
|
const manga = {};
|
||||||
|
const coverRel = data.relationships.find(rel => rel.type === "cover_art");
|
||||||
|
if (coverRel && coverRel.attributes && coverRel.attributes.fileName) {
|
||||||
|
manga.imageUrl = `https://uploads.mangadex.org/covers/${data.id}/${coverRel.attributes.fileName}`;
|
||||||
|
}
|
||||||
|
const authors = data.relationships
|
||||||
|
.filter(rel => rel.type === "author")
|
||||||
|
.map(rel => rel.attributes.name);
|
||||||
|
manga.author = authors.join(", ");
|
||||||
|
manga.description = data.attributes.description[this.source.lang] ?? data.attributes.description.en ?? "";
|
||||||
|
manga.genre = data.attributes.tags.map(tag => tag.attributes.name.en);
|
||||||
|
if (data.attributes.contentRating && data.attributes.contentRating !== "safe") {
|
||||||
|
manga.genre.push(data.attributes.contentRating);
|
||||||
|
}
|
||||||
|
if (data.attributes.publicationDemographic && data.attributes.publicationDemographic !== "null") {
|
||||||
|
manga.genre.push(data.attributes.publicationDemographic);
|
||||||
|
}
|
||||||
|
manga.status = { "ongoing": 0, "completed": 1, "hiatus": 2, "cancelled": 3 }[data.attributes.status];
|
||||||
|
const mangaId = url.split("/").pop();
|
||||||
|
const chapterData = await this.fetchPaginatedChapters(mangaId, this.source.lang);
|
||||||
|
|
||||||
|
manga.chapters = chapterData;
|
||||||
|
return manga;
|
||||||
|
}
|
||||||
|
async fetchPaginatedChapters(mangaId, lang) {
|
||||||
|
const chapters = [];
|
||||||
|
let offset = 0;
|
||||||
|
let hasMoreResults = true;
|
||||||
|
|
||||||
|
while (hasMoreResults) {
|
||||||
|
const url = `${this.source.apiUrl}/manga/${mangaId}/feed?limit=500&offset=${offset}&includes[]=user&includes[]=scanlation_group&order[volume]=desc&order[chapter]=desc&translatedLanguage[]=${lang}&includeFuturePublishAt=0&includeEmptyPages=0&contentRating[]=safe&contentRating[]=suggestive`;
|
||||||
|
const res = await this.client.get(url, this.getHeaders());
|
||||||
|
const paginatedData = JSON.parse(res.body);
|
||||||
|
const limit = paginatedData?.limit ?? 0;
|
||||||
|
const total = paginatedData?.total ?? 0;
|
||||||
|
const newChapters = this.extractChapters(paginatedData);
|
||||||
|
chapters.push(...newChapters);
|
||||||
|
offset += limit;
|
||||||
|
hasMoreResults = offset < total;
|
||||||
|
}
|
||||||
|
|
||||||
|
return chapters;
|
||||||
|
}
|
||||||
|
|
||||||
|
extractChapters(paginatedData) {
|
||||||
|
const chaptersList = [];
|
||||||
|
|
||||||
|
const dataList = paginatedData.data ?? [];
|
||||||
|
for (const res of dataList) {
|
||||||
|
let scan = "";
|
||||||
|
const groups = res?.relationships?.filter(
|
||||||
|
rel => rel.id !== "00e03853-1b96-4f41-9542-c71b8692033b"
|
||||||
|
);
|
||||||
|
for (const group of groups) {
|
||||||
|
const groupData = group?.attributes ?? {};
|
||||||
|
const name = groupData?.name ?? "";
|
||||||
|
if (name) {
|
||||||
|
scan += name;
|
||||||
|
}
|
||||||
|
if (scan === "") {
|
||||||
|
const username = groupData?.username ?? "";
|
||||||
|
if (username) {
|
||||||
|
scan += `Uploaded by ${username}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (scan === "") {
|
||||||
|
scan = "No Group";
|
||||||
|
}
|
||||||
|
const attributes = res?.attributes ?? {};
|
||||||
|
const volume = attributes?.volume;
|
||||||
|
const chapter = attributes?.chapter;
|
||||||
|
let title = attributes?.title ?? "";
|
||||||
|
if (volume === null && chapter === null && title === "") {
|
||||||
|
title = "Oneshot"
|
||||||
|
}
|
||||||
|
const chapName = `${volume ? `Vol.${volume} ` : ""}${chapter ? `Ch.${chapter} ` : ""}${title}`;
|
||||||
|
chaptersList.push({
|
||||||
|
name: chapName,
|
||||||
|
url: res?.id ?? "",
|
||||||
|
scanlator: scan,
|
||||||
|
dateUpload: new Date(attributes?.publishAt).valueOf().toString(),
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return chaptersList;
|
||||||
|
}
|
||||||
|
async getPageList(url) {
|
||||||
|
const pageUrl = `${this.source.apiUrl}/at-home/server/${url}`;
|
||||||
|
const response = await this.client.get(pageUrl, this.getHeaders());
|
||||||
|
const bodyJson = JSON.parse(response.body);
|
||||||
|
const host = bodyJson.baseUrl;
|
||||||
|
const chapter = bodyJson.chapter;
|
||||||
|
const hash = chapter.hash;
|
||||||
|
const chapterDatas = chapter.data;
|
||||||
|
return chapterDatas.map(file => `${host}/data/${hash}/${file}`);
|
||||||
|
}
|
||||||
|
getFilterList() {
|
||||||
|
throw new Error("getFilterList not implemented");
|
||||||
|
}
|
||||||
|
mangaRes(res) {
|
||||||
|
const data = JSON.parse(res).data;
|
||||||
|
return {
|
||||||
|
list: data.map(e => ({
|
||||||
|
name: this.findTitle(e, this.source.lang),
|
||||||
|
imageUrl: this.getCover(e),
|
||||||
|
link: `/manga/${e.id}`,
|
||||||
|
})),
|
||||||
|
hasNextPage: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
findTitle(data, lang) {
|
||||||
|
const title = data.attributes.title[lang] ?? data.attributes.title.en;
|
||||||
|
return title ?? data.attributes.altTitles.find(t => t[lang])[lang] ?? data.attributes.altTitles.find(t => t.en).en ?? "";
|
||||||
|
}
|
||||||
|
getCover(data) {
|
||||||
|
const coverArt = data.relationships?.find(r => r.type === "cover_art");
|
||||||
|
return coverArt ? `https://uploads.mangadex.org/covers/${data.id}/${coverArt.attributes.fileName}` : "";
|
||||||
|
}
|
||||||
|
preferenceOriginalLanguages() {
|
||||||
|
const originalLanguages = this.getPreference("original_languages", []);
|
||||||
|
return originalLanguages.length ? `&${originalLanguages.join("&")}` : "";
|
||||||
|
}
|
||||||
|
getPreference(key, defaulValue) {
|
||||||
|
const preferences = new SharedPreferences();
|
||||||
|
return preferences.get(key, defaulValue);
|
||||||
|
}
|
||||||
|
ll(url) {
|
||||||
|
return url.includes("?") ? "&" : "?";
|
||||||
|
}
|
||||||
|
getSourcePreferences() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
"key": "cover_quality",
|
||||||
|
"listPreference": {
|
||||||
|
"title": "Cover quality",
|
||||||
|
"summary": "Select the quality of the covers to load",
|
||||||
|
"valueIndex": 0,
|
||||||
|
"entries": [
|
||||||
|
"Original",
|
||||||
|
"Medium",
|
||||||
|
"Low"],
|
||||||
|
"entryValues": [
|
||||||
|
"",
|
||||||
|
".512.jpg",
|
||||||
|
".256.jpg"],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "original_languages",
|
||||||
|
"multiSelectListPreference": {
|
||||||
|
"title": "Filter original languages",
|
||||||
|
"summary": "Only show content that was originaly published in the selected languages in both latest and browse",
|
||||||
|
"entries": [
|
||||||
|
"Japanese",
|
||||||
|
"Chinese",
|
||||||
|
"Korean"],
|
||||||
|
"entryValues": [
|
||||||
|
"originalLanguage[]=ja",
|
||||||
|
"originalLanguage[]=zh&originalLanguage[]=zh-hk",
|
||||||
|
"originalLanguage[]=ko"],
|
||||||
|
"values": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "custom_user_agent",
|
||||||
|
"editTextPreference": {
|
||||||
|
"title": "Set custom User-Agent",
|
||||||
|
"summary": "",
|
||||||
|
"value": "Dalvik/2.1.0 (Linux; U; Android 14; 22081212UG Build/UKQ1.230917.001)",
|
||||||
|
"dialogTitle": "Set custom User-Agent",
|
||||||
|
"dialogMessage": "Specify a custom user agent",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
getFilterList() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
type_name: "CheckBox",
|
||||||
|
type: "HasAvailableChaptersFilter",
|
||||||
|
name: "Has available chapters",
|
||||||
|
value: ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type_name: "GroupFilter",
|
||||||
|
type: "OriginalLanguageList",
|
||||||
|
name: "Original language",
|
||||||
|
state: [
|
||||||
|
["Japanese (Manga)", "originalLanguage[]=ja"],
|
||||||
|
["Chinese (Manhua)",
|
||||||
|
"originalLanguage[]=zh&originalLanguage[]=zh-hk"],
|
||||||
|
["Korean (Manhwa)", "originalLanguage[]=ko"]
|
||||||
|
].map(x => ({ type_name: 'CheckBox', name: x[0], value: x[1] }))
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type_name: "GroupFilter",
|
||||||
|
type: "ContentRatingList",
|
||||||
|
name: "Content rating",
|
||||||
|
state: [
|
||||||
|
["Safe", "contentRating[]=safe"],
|
||||||
|
["Suggestive", "contentRating[]=suggestive"]
|
||||||
|
].map(x => ({ type_name: 'CheckBox', name: x[0], value: x[1], state: true }))
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type_name: "GroupFilter",
|
||||||
|
type: "DemographicList",
|
||||||
|
name: "Publication demographic",
|
||||||
|
state: [
|
||||||
|
["None", "publicationDemographic[]=none"],
|
||||||
|
["Shounen", "publicationDemographic[]=shounen"],
|
||||||
|
["Shoujo", "publicationDemographic[]=shoujo"],
|
||||||
|
["Seinen", "publicationDemographic[]=seinen"],
|
||||||
|
["Josei", "publicationDemographic[]=josei"]
|
||||||
|
].map(x => ({ type_name: 'CheckBox', name: x[0], value: x[1] }))
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type_name: "GroupFilter",
|
||||||
|
type: "StatusList",
|
||||||
|
name: "Status",
|
||||||
|
state: [
|
||||||
|
["Ongoing", "status[]=ongoing"],
|
||||||
|
["Completed", "status[]=completed"],
|
||||||
|
["Hiatus", "status[]=hiatus"],
|
||||||
|
["Cancelled", "status[]=cancelled"]
|
||||||
|
].map(x => ({ type_name: 'CheckBox', name: x[0], value: x[1] }))
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type_name: "SortFilter",
|
||||||
|
type: "SortFilter",
|
||||||
|
name: "Sort",
|
||||||
|
state: {
|
||||||
|
type_name: "SortState",
|
||||||
|
index: 5,
|
||||||
|
ascending: false
|
||||||
|
},
|
||||||
|
values: [
|
||||||
|
["Alphabetic", "title"],
|
||||||
|
["Chapter uploded at", "latestUploadedChapter"],
|
||||||
|
["Number of follows", "followedCount"],
|
||||||
|
["Content created at", "createdAt"],
|
||||||
|
["Content info updated at", "updatedAt"],
|
||||||
|
["Relevance", "relevance"],
|
||||||
|
["Year", "year"],
|
||||||
|
["Rating", "rating"]
|
||||||
|
].map(x => ({ type_name: 'SelectOption', name: x[0], value: x[1] }))
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type_name: "GroupFilter",
|
||||||
|
type: "TagsFilter",
|
||||||
|
name: "Tags mode",
|
||||||
|
state: [
|
||||||
|
{
|
||||||
|
type_name: "SelectFilter",
|
||||||
|
type: "TagInclusionMode",
|
||||||
|
name: "Included tags mode",
|
||||||
|
state: 0,
|
||||||
|
values: [
|
||||||
|
["AND", "includedTagsMode=AND"],
|
||||||
|
["OR", "includedTagsMode=OR"]
|
||||||
|
].map(x => ({ type_name: 'SelectOption', name: x[0], value: x[1] }))
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type_name: "SelectFilter",
|
||||||
|
type: "TagExclusionMode",
|
||||||
|
name: "Excluded tags mode",
|
||||||
|
state: 1,
|
||||||
|
values: [
|
||||||
|
["AND", "excludedTagsMode=AND"],
|
||||||
|
["OR", "excludedTagsMode=OR"]
|
||||||
|
].map(x => ({ type_name: 'SelectOption', name: x[0], value: x[1] }))
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type_name: "GroupFilter",
|
||||||
|
type: "ContentsFilter",
|
||||||
|
name: "Content",
|
||||||
|
state: [
|
||||||
|
["Gore", "b29d6a3d-1569-4e7a-8caf-7557bc92cd5d"],
|
||||||
|
["Sexual Violence", "97893a4c-12af-4dac-b6be-0dffb353568e"]
|
||||||
|
].map(x => ({ type_name: 'TriState', name: x[0], value: x[1] }))
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type_name: "GroupFilter",
|
||||||
|
type: "FormatFilter",
|
||||||
|
name: "Format",
|
||||||
|
state: [
|
||||||
|
["4-Koma", "b11fda93-8f1d-4bef-b2ed-8803d3733170"],
|
||||||
|
["Adaptation", "f4122d1c-3b44-44d0-9936-ff7502c39ad3"],
|
||||||
|
["Anthology", "51d83883-4103-437c-b4b1-731cb73d786c"],
|
||||||
|
["Award Winning", "0a39b5a1-b235-4886-a747-1d05d216532d"],
|
||||||
|
["Doujinshi", "b13b2a48-c720-44a9-9c77-39c9979373fb"],
|
||||||
|
["Fan Colored", "7b2ce280-79ef-4c09-9b58-12b7c23a9b78"],
|
||||||
|
["Full Color", "f5ba408b-0e7a-484d-8d49-4e9125ac96de"],
|
||||||
|
["Long Strip", "3e2b8dae-350e-4ab8-a8ce-016e844b9f0d"],
|
||||||
|
[
|
||||||
|
"Official Colored", "320831a8-4026-470b-94f6-8353740e6f04"],
|
||||||
|
["Oneshot", "0234a31e-a729-4e28-9d6a-3f87c4966b9e"],
|
||||||
|
["User Created", "891cf039-b895-47f0-9229-bef4c96eccd4"],
|
||||||
|
["Web Comic", "e197df38-d0e7-43b5-9b09-2842d0c326dd"]
|
||||||
|
].map(x => ({ type_name: 'TriState', name: x[0], value: x[1] }))
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type_name: "GroupFilter",
|
||||||
|
type: "GenreFilter",
|
||||||
|
name: "Genre",
|
||||||
|
state: [
|
||||||
|
["Action", "391b0423-d847-456f-aff0-8b0cfc03066b"],
|
||||||
|
["Adventure", "87cc87cd-a395-47af-b27a-93258283bbc6"],
|
||||||
|
["Boys' Love", "5920b825-4181-4a17-beeb-9918b0ff7a30"],
|
||||||
|
["Comedy", "4d32cc48-9f00-4cca-9b5a-a839f0764984"],
|
||||||
|
["Crime", "5ca48985-9a9d-4bd8-be29-80dc0303db72"],
|
||||||
|
["Drama", "b9af3a63-f058-46de-a9a0-e0c13906197a"],
|
||||||
|
["Fantasy", "cdc58593-87dd-415e-bbc0-2ec27bf404cc"],
|
||||||
|
["Girls' Love", "a3c67850-4684-404e-9b7f-c69850ee5da6"],
|
||||||
|
["Historical", "33771934-028e-4cb3-8744-691e866a923e"],
|
||||||
|
["Horror", "cdad7e68-1419-41dd-bdce-27753074a640"],
|
||||||
|
["Isekai", "ace04997-f6bd-436e-b261-779182193d3d"],
|
||||||
|
["Magical Girls", "81c836c9-914a-4eca-981a-560dad663e73"],
|
||||||
|
["Mecha", "50880a9d-5440-4732-9afb-8f457127e836"],
|
||||||
|
["Medical", "c8cbe35b-1b2b-4a3f-9c37-db84c4514856"],
|
||||||
|
["Mystery", "ee968100-4191-4968-93d3-f82d72be7e46"],
|
||||||
|
["Philosophical", "b1e97889-25b4-4258-b28b-cd7f4d28ea9b"],
|
||||||
|
["Psychological", "3b60b75c-a2d7-4860-ab56-05f391bb889c"],
|
||||||
|
["Romance", "423e2eae-a7a2-4a8b-ac03-a8351462d71d"],
|
||||||
|
["Sci-Fi", "256c8bd9-4904-4360-bf4f-508a76d67183"],
|
||||||
|
["Slice of Life", "e5301a23-ebd9-49dd-a0cb-2add944c7fe9"],
|
||||||
|
["Sports", "69964a64-2f90-4d33-beeb-f3ed2875eb4c"],
|
||||||
|
["Superhero", "7064a261-a137-4d3a-8848-2d385de3a99c"],
|
||||||
|
["Thriller", "07251805-a27e-4d59-b488-f0bfbec15168"],
|
||||||
|
["Tragedy", "f8f62932-27da-4fe4-8ee1-6779a8c5edba"],
|
||||||
|
["Wuxia", "acc803a4-c95a-4c22-86fc-eb6b582d82a2"]
|
||||||
|
].map(x => ({ type_name: 'TriState', name: x[0], value: x[1] }))
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type_name: "GroupFilter",
|
||||||
|
type: "ThemeFilter",
|
||||||
|
name: "Theme",
|
||||||
|
state: [
|
||||||
|
["Aliens", "e64f6742-c834-471d-8d72-dd51fc02b835"],
|
||||||
|
["Animals", "3de8c75d-8ee3-48ff-98ee-e20a65c86451"],
|
||||||
|
["Cooking", "ea2bc92d-1c26-4930-9b7c-d5c0dc1b6869"],
|
||||||
|
["Crossdressing", "9ab53f92-3eed-4e9b-903a-917c86035ee3"],
|
||||||
|
["Delinquents", "da2d50ca-3018-4cc0-ac7a-6b7d472a29ea"],
|
||||||
|
["Demons", "39730448-9a5f-48a2-85b0-a70db87b1233"],
|
||||||
|
["Genderswap", "2bd2e8d0-f146-434a-9b51-fc9ff2c5fe6a"],
|
||||||
|
["Ghosts", "3bb26d85-09d5-4d2e-880c-c34b974339e9"],
|
||||||
|
["Gyaru", "fad12b5e-68ba-460e-b933-9ae8318f5b65"],
|
||||||
|
["Harem", "aafb99c1-7f60-43fa-b75f-fc9502ce29c7"],
|
||||||
|
["Loli", "2d1f5d56-a1e5-4d0d-a961-2193588b08ec"],
|
||||||
|
["Mafia", "85daba54-a71c-4554-8a28-9901a8b0afad"],
|
||||||
|
["Magic", "a1f53773-c69a-4ce5-8cab-fffcd90b1565"],
|
||||||
|
["Martial Arts", "799c202e-7daa-44eb-9cf7-8a3c0441531e"],
|
||||||
|
["Military", "ac72833b-c4e9-4878-b9db-6c8a4a99444a"],
|
||||||
|
["Monster Girls", "dd1f77c5-dea9-4e2b-97ae-224af09caf99"],
|
||||||
|
["Monsters", "36fd93ea-e8b8-445e-b836-358f02b3d33d"],
|
||||||
|
["Music", "f42fbf9e-188a-447b-9fdc-f19dc1e4d685"],
|
||||||
|
["Ninja", "489dd859-9b61-4c37-af75-5b18e88daafc"],
|
||||||
|
[
|
||||||
|
"Office Workers", "92d6d951-ca5e-429c-ac78-451071cbf064"],
|
||||||
|
["Police", "df33b754-73a3-4c54-80e6-1a74a8058539"],
|
||||||
|
[
|
||||||
|
"Post-Apocalyptic", "9467335a-1b83-4497-9231-765337a00b96"],
|
||||||
|
["Reincarnation", "0bc90acb-ccc1-44ca-a34a-b9f3a73259d0"],
|
||||||
|
["Reverse Harem", "65761a2a-415e-47f3-bef2-a9dababba7a6"],
|
||||||
|
["Samurai", "81183756-1453-4c81-aa9e-f6e1b63be016"],
|
||||||
|
["School Life", "caaa44eb-cd40-4177-b930-79d3ef2afe87"],
|
||||||
|
["Shota", "ddefd648-5140-4e5f-ba18-4eca4071d19b"],
|
||||||
|
["Supernatural", "eabc5b4c-6aff-42f3-b657-3e90cbd00b75"],
|
||||||
|
["Survival", "5fff9cde-849c-4d78-aab0-0d52b2ee1d25"],
|
||||||
|
["Time Travel", "292e862b-2d17-4062-90a2-0356caa4ae27"],
|
||||||
|
[
|
||||||
|
"Traditional Games", "31932a7e-5b8e-49a6-9f12-2afa39dc544c"],
|
||||||
|
["Vampires", "d7d1730f-6eb0-4ba6-9437-602cac38664c"],
|
||||||
|
["Video Games", "9438db5a-7e2a-4ac0-b39e-e0d95a34b8a8"],
|
||||||
|
["Villainess", "d14322ac-4d6f-4e9b-afd9-629d5f4d8a41"],
|
||||||
|
[
|
||||||
|
"Virtual Reality", "8c86611e-fab7-4986-9dec-d1a2f44acdd5"],
|
||||||
|
["Zombies", "631ef465-9aba-4afb-b0fc-ea10efe274a8"]
|
||||||
|
].map(x => ({ type_name: 'TriState', name: x[0], value: x[1] }))
|
||||||
|
},
|
||||||
|
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user