diff --git a/dart/manga/manga_source_list.dart b/dart/manga/manga_source_list.dart index e27d4b68..81588c69 100644 --- a/dart/manga/manga_source_list.dart +++ b/dart/manga/manga_source_list.dart @@ -4,12 +4,10 @@ import 'multisrc/mangabox/sources.dart'; import 'multisrc/mangareader/sources.dart'; import 'multisrc/mmrcms/sources.dart'; import 'multisrc/nepnep/sources.dart'; -import 'src/all/mangadex/sources.dart'; import 'src/en/mangahere/source.dart'; List dartMangasourceList = [ ...madaraSourcesList, - ...mangaDexSourcesList, ...mangareaderSourcesList, ...mmrcmsSourcesList, mangahereSource, diff --git a/dart/manga/src/all/mangadex/mangadex.dart b/dart/manga/src/all/mangadex/mangadex.dart deleted file mode 100644 index 7ba8814f..00000000 --- a/dart/manga/src/all/mangadex/mangadex.dart +++ /dev/null @@ -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 get headers => - {"user-agent": getPreferenceValue(source.id, "custom_user_agent")}; - @override - Future 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 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 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 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 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 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> 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 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 getChapters(int length, String paginatedChapterListA) { - List 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 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 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 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 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); -} diff --git a/dart/manga/src/all/mangadex/sources.dart b/dart/manga/src/all/mangadex/sources.dart deleted file mode 100644 index ff1def04..00000000 --- a/dart/manga/src/all/mangadex/sources.dart +++ /dev/null @@ -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 get mangaDexSourcesList => _mangaDexSourcesList; -List _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(); diff --git a/dart/manga/src/all/mangadex/icon.png b/javascript/icon/all.mangadex.png similarity index 100% rename from dart/manga/src/all/mangadex/icon.png rename to javascript/icon/all.mangadex.png diff --git a/javascript/manga/src/all/mangadex.js b/javascript/manga/src/all/mangadex.js new file mode 100644 index 00000000..f1e0a8f0 --- /dev/null +++ b/javascript/manga/src/all/mangadex.js @@ -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] })) + }, + + ]; + } + +}