From 4535fb3ef059722e05908c97bfd16e39a89d8e2d Mon Sep 17 00:00:00 2001 From: AwkwardPeak7 <48650614+AwkwardPeak7@users.noreply.github.com> Date: Fri, 17 Jan 2025 18:11:29 +0500 Subject: [PATCH 01/12] update deps & lint --- .../src/main/kotlin/lib-android.gradle.kts | 10 ++++++++ .../src/main/kotlin/lib-multisrc.gradle.kts | 14 +++++------ common.gradle | 23 ++++++++++--------- gradle/libs.versions.toml | 14 +++++------ 4 files changed, 35 insertions(+), 26 deletions(-) diff --git a/buildSrc/src/main/kotlin/lib-android.gradle.kts b/buildSrc/src/main/kotlin/lib-android.gradle.kts index e3618980b8..1a9557cb30 100644 --- a/buildSrc/src/main/kotlin/lib-android.gradle.kts +++ b/buildSrc/src/main/kotlin/lib-android.gradle.kts @@ -16,6 +16,16 @@ android { buildFeatures { androidResources = false } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_17.toString() + freeCompilerArgs += "-opt-in=kotlinx.serialization.ExperimentalSerializationApi" + } } dependencies { diff --git a/buildSrc/src/main/kotlin/lib-multisrc.gradle.kts b/buildSrc/src/main/kotlin/lib-multisrc.gradle.kts index 402ebebf65..b4b6706dbb 100644 --- a/buildSrc/src/main/kotlin/lib-multisrc.gradle.kts +++ b/buildSrc/src/main/kotlin/lib-multisrc.gradle.kts @@ -23,19 +23,17 @@ android { } } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + kotlinOptions { + jvmTarget = JavaVersion.VERSION_17.toString() freeCompilerArgs += "-opt-in=kotlinx.serialization.ExperimentalSerializationApi" } } -kotlinter { - experimentalRules = true - disabledRules = arrayOf( - "experimental:argument-list-wrapping", // Doesn't play well with Android Studio - "experimental:comment-wrapping", - ) -} - dependencies { compileOnly(versionCatalogs.named("libs").findBundle("common").get()) } diff --git a/common.gradle b/common.gradle index f916f509aa..234847d465 100644 --- a/common.gradle +++ b/common.gradle @@ -18,6 +18,13 @@ android { sourceSets { main { manifest.srcFile "AndroidManifest.xml" + + if (!manifest.srcFile.exists()) { + def build = layout.buildDirectory.get().asFile + def tempManifest = new File(build, "tempAndroidManifest.xml").path + manifest.srcFile tempManifest + } + java.srcDirs = ['src'] res.srcDirs = ['res'] assets.srcDirs = ['assets'] @@ -81,22 +88,14 @@ android { } compileOptions { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 } kotlinOptions { - jvmTarget = JavaVersion.VERSION_1_8.toString() + jvmTarget = JavaVersion.VERSION_17.toString() freeCompilerArgs += "-opt-in=kotlinx.serialization.ExperimentalSerializationApi" } - - kotlinter { - experimentalRules = true - disabledRules = [ - "experimental:argument-list-wrapping", // Doesn't play well with Android Studio - "experimental:comment-wrapping", - ] - } } dependencies { @@ -107,6 +106,7 @@ dependencies { tasks.register("writeManifestFile") { doLast { + def manifest = android.sourceSets.getByName("main").manifest if (!manifest.srcFile.exists()) { File tempFile = layout.buildDirectory.get().file("tempAndroidManifest.xml").getAsFile() @@ -121,6 +121,7 @@ tasks.register("writeManifestFile") { } preBuild.dependsOn(writeManifestFile, lintKotlin) + if (System.getenv("CI") != "true") { lintKotlin.dependsOn(formatKotlin) } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e5127d7fd4..894fc03e29 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,13 +1,13 @@ [versions] -kotlin_version = "1.7.21" -coroutines_version = "1.6.4" -serialization_version = "1.4.0" +kotlin_version = "2.1.0" +coroutines_version = "1.10.1" +serialization_version = "1.8.0" [libraries] -gradle-agp = { module = "com.android.tools.build:gradle", version = "8.6.1" } +gradle-agp = { module = "com.android.tools.build:gradle", version = "8.8.0" } gradle-kotlin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin_version" } gradle-serialization = { module = "org.jetbrains.kotlin:kotlin-serialization", version.ref = "kotlin_version" } -gradle-kotlinter = { module = "org.jmailen.gradle:kotlinter-gradle", version = "3.13.0" } +gradle-kotlinter = { module = "org.jmailen.gradle:kotlinter-gradle", version = "5.0.1" } tachiyomi-lib = { module = "com.github.tachiyomiorg:extensions-lib", version = "1.4.2" } @@ -20,8 +20,8 @@ coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-androi injekt-core = { module = "com.github.null2264.injekt:injekt-core", version = "4135455a2a" } rxjava = { module = "io.reactivex:rxjava", version = "1.3.8" } -jsoup = { module = "org.jsoup:jsoup", version = "1.15.1" } -okhttp = { module = "com.squareup.okhttp3:okhttp", version = "5.0.0-alpha.11" } +jsoup = { module = "org.jsoup:jsoup", version = "1.18.3" } +okhttp = { module = "com.squareup.okhttp3:okhttp", version = "5.0.0-alpha.14" } quickjs = { module = "app.cash.quickjs:quickjs-android", version = "0.9.2" } [bundles] From 54cbaa919eaefd7f4cc96917c472f63a0ad01ea2 Mon Sep 17 00:00:00 2001 From: AwkwardPeak7 <48650614+AwkwardPeak7@users.noreply.github.com> Date: Fri, 17 Jan 2025 18:11:29 +0500 Subject: [PATCH 02/12] run `formatKotlin` --- .../tachiyomi/multisrc/a3manga/A3Manga.kt | 176 +- .../tachiyomi/multisrc/bakkin/BakkinJSON.kt | 18 +- .../multisrc/bakkin/BakkinReaderX.kt | 134 +- .../multisrc/blogtruyen/BlogTruyen.kt | 449 +- .../tachiyomi/multisrc/colamanga/ColaManga.kt | 271 +- .../multisrc/colamanga/ColaMangaFilters.kt | 21 +- .../colamanga/ColaMangaImageInterceptor.kt | 17 +- .../multisrc/colamanga/ColaMangaIntl.kt | 107 +- .../colamanga/ColaMangaUrlActivity.kt | 11 +- .../multisrc/colorlibanime/ColorlibAnime.kt | 105 +- .../multisrc/comicgamma/ComicGamma.kt | 92 +- .../tachiyomi/multisrc/eromuse/EroMuse.kt | 467 ++- .../eu/kanade/tachiyomi/multisrc/Etoshore.kt | 219 +- .../tachiyomi/multisrc/EtoshoreUrlActivity.kt | 12 +- .../multisrc/fansubscat/FansubsCat.kt | 381 +- .../tachiyomi/multisrc/fmreader/FMReader.kt | 519 ++- .../tachiyomi/multisrc/foolslide/FoolSlide.kt | 164 +- .../tachiyomi/multisrc/fuzzydoodle/Filters.kt | 47 +- .../multisrc/fuzzydoodle/FuzzyDoodle.kt | 224 +- .../multisrc/galleryadults/GalleryAdults.kt | 619 +-- .../galleryadults/GalleryAdultsFilters.kt | 43 +- .../galleryadults/GalleryAdultsUrlActivity.kt | 11 +- .../galleryadults/GalleryAdultsUtils.kt | 104 +- .../tachiyomi/multisrc/gattsu/Gattsu.kt | 129 +- .../multisrc/gigaviewer/GigaViewer.kt | 219 +- .../tachiyomi/multisrc/gmanga/CryptoUtils.kt | 17 +- .../kanade/tachiyomi/multisrc/gmanga/Dto.kt | 113 +- .../tachiyomi/multisrc/gmanga/Filters.kt | 109 +- .../tachiyomi/multisrc/gmanga/Gmanga.kt | 329 +- .../eu/kanade/tachiyomi/multisrc/goda/GoDa.kt | 137 +- .../multisrc/gravureblogger/GravureBlogger.kt | 116 +- .../tachiyomi/multisrc/grouple/GroupLe.kt | 394 +- .../multisrc/grouple/GroupLeUrlActivity.kt | 13 +- .../eu/kanade/tachiyomi/multisrc/guya/Guya.kt | 249 +- .../multisrc/guya/GuyaUrlActivity.kt | 17 +- .../tachiyomi/multisrc/heancms/HeanCms.kt | 385 +- .../tachiyomi/multisrc/heancms/HeanCmsDto.kt | 121 +- .../multisrc/heancms/HeanCmsFilters.kt | 48 +- .../multisrc/heancms/HeanCmsUrlActivity.kt | 16 +- .../multisrc/hentaihand/HentaiHand.kt | 439 +- .../tachiyomi/multisrc/hotcomics/HotComics.kt | 181 +- .../eu/kanade/tachiyomi/multisrc/iken/Dto.kt | 102 +- .../kanade/tachiyomi/multisrc/iken/Filters.kt | 85 +- .../eu/kanade/tachiyomi/multisrc/iken/Iken.kt | 90 +- .../tachiyomi/multisrc/kemono/Kemono.kt | 294 +- .../tachiyomi/multisrc/kemono/KemonoDto.kt | 105 +- .../tachiyomi/multisrc/keyoapp/Keyoapp.kt | 297 +- .../tachiyomi/multisrc/lectormoe/LectorMoe.kt | 65 +- .../multisrc/lectormoe/LectorMoeDto.kt | 59 +- .../tachiyomi/multisrc/libgroup/LibGroup.kt | 658 +-- .../multisrc/libgroup/LibGroupDto.kt | 179 +- .../multisrc/libgroup/LibUrlActivity.kt | 12 +- .../tachiyomi/multisrc/liliana/Filters.kt | 65 +- .../tachiyomi/multisrc/liliana/Liliana.kt | 265 +- .../MachineTranslations.kt | 126 +- .../MachineTranslationsDto.kt | 28 +- .../MachineTranslationsFactoryUtils.kt | 6 +- .../MachineTranslationsFilters.kt | 117 +- .../MachineTranslationsUrlActivity.kt | 13 +- .../interceptors/ComposedImageInterceptor.kt | 117 +- .../tachiyomi/multisrc/madara/Madara.kt | 1007 +++-- .../multisrc/madara/MadaraUrlActivity.kt | 16 +- .../tachiyomi/multisrc/madtheme/MadTheme.kt | 337 +- .../tachiyomi/multisrc/makaru/Makaru.kt | 217 +- .../multisrc/makaru/MakaruFilters.kt | 130 +- .../tachiyomi/multisrc/makaru/MakaruUtils.kt | 24 +- .../tachiyomi/multisrc/manga18/Manga18.kt | 205 +- .../multisrc/manga18/Manga18Filters.kt | 21 +- .../tachiyomi/multisrc/mangabox/MangaBox.kt | 325 +- .../multisrc/mangacatalog/MangaCatalog.kt | 96 +- .../multisrc/mangadventure/MangAdventure.kt | 187 +- .../mangadventure/MangAdventureAPI.kt | 9 +- .../mangadventure/MangAdventureActivity.kt | 11 +- .../mangadventure/MangAdventureFilters.kt | 51 +- .../tachiyomi/multisrc/mangaesp/MangaEsp.kt | 223 +- .../multisrc/mangaesp/MangaEspDto.kt | 29 +- .../tachiyomi/multisrc/mangahub/MangaHub.kt | 467 ++- .../multisrc/mangahub/MangaHubQueries.kt | 17 +- .../multisrc/mangaraw/ImageListParser.kt | 103 +- .../multisrc/mangaraw/MangaRawTheme.kt | 69 +- .../multisrc/mangareader/MangaReader.kt | 106 +- .../multisrc/mangathemesia/MangaThemesia.kt | 664 +-- .../mangathemesia/MangaThemesiaAlt.kt | 72 +- .../MangaThemesiaPaidChapterHelper.kt | 23 +- .../mangathemesia/MangaThemesiaUrlActivity.kt | 12 +- .../multisrc/mangaworld/MangaWorld.kt | 273 +- .../tachiyomi/multisrc/manhwaz/ManhwaZ.kt | 278 +- .../tachiyomi/multisrc/masonry/Masonry.kt | 181 +- .../multisrc/masonry/MasonryFilters.kt | 21 +- .../kanade/tachiyomi/multisrc/mccms/MCCMS.kt | 104 +- .../tachiyomi/multisrc/mccms/MCCMSDto.kt | 54 +- .../tachiyomi/multisrc/mccms/MCCMSFilters.kt | 93 +- .../tachiyomi/multisrc/mccms/MCCMSWeb.kt | 109 +- .../tachiyomi/multisrc/mmrcms/MMRCMS.kt | 844 ++-- .../multisrc/mmrcms/MMRCMSFilters.kt | 20 +- .../multisrc/monochrome/MonochromeAPI.kt | 11 +- .../multisrc/monochrome/MonochromeActivity.kt | 11 +- .../multisrc/monochrome/MonochromeCMS.kt | 73 +- .../tachiyomi/multisrc/multichan/MultiChan.kt | 70 +- .../multisrc/mymangacms/MyMangaCMS.kt | 432 +- .../multisrc/otakusanctuary/OtakuSanctuary.kt | 207 +- .../otakusanctuary/OtakuSanctuaryHelper.kt | 143 +- .../tachiyomi/multisrc/paprika/Paprika.kt | 322 +- .../tachiyomi/multisrc/paprika/PaprikaAlt.kt | 29 +- .../tachiyomi/multisrc/peachscan/PeachScan.kt | 157 +- .../peachscan/PeachScanUrlActivity.kt | 11 +- .../multisrc/pizzareader/PizzaReader.kt | 127 +- .../multisrc/readerfront/ReaderFront.kt | 120 +- .../multisrc/readerfront/ReaderFrontAPI.kt | 33 +- .../readerfront/ReaderFrontActivity.kt | 11 +- .../multisrc/readerfront/ReaderFrontI18N.kt | 132 +- .../readerfront/ReaderFrontQueries.kt | 24 +- .../kanade/tachiyomi/multisrc/senkuro/Dto.kt | 1 + .../tachiyomi/multisrc/senkuro/Senkuro.kt | 478 ++- .../multisrc/senkuro/SenkuroQueries.kt | 40 +- .../multisrc/sinmh/ProgressiveParser.kt | 21 +- .../kanade/tachiyomi/multisrc/sinmh/SinMH.kt | 226 +- .../multisrc/slimereadtheme/SlimeReadTheme.kt | 184 +- .../slimereadtheme/SlimeReadThemeFilters.kt | 187 +- .../SlimeReadThemeUrlActivity.kt | 12 +- .../slimereadtheme/dto/SlimeReadDto.kt | 40 +- .../tachiyomi/multsrc/terrascan/TerraScan.kt | 151 +- .../multsrc/terrascan/TerraScanFilters.kt | 11 +- .../multsrc/terrascan/TerraScanUrlActivity.kt | 15 +- .../tachiyomi/multisrc/vercomics/VerComics.kt | 110 +- .../tachiyomi/multisrc/webtoons/Webtoons.kt | 160 +- .../multisrc/webtoons/WebtoonsTranslate.kt | 175 +- .../multisrc/webtoons/WebtoonsUrlActivity.kt | 12 +- .../tachiyomi/multisrc/wpcomics/WPComics.kt | 86 +- .../kanade/tachiyomi/multisrc/zbulu/Zbulu.kt | 275 +- .../multisrc/zeistmanga/ZeistManga.kt | 401 +- .../multisrc/zeistmanga/ZeistMangaDto.kt | 46 +- .../multisrc/zeistmanga/ZeistMangaFilters.kt | 49 +- .../multisrc/zeistmanga/ZeistMangaIntl.kt | 263 +- .../tachiyomi/multisrc/zmanga/ZManga.kt | 292 +- .../extension/all/ahottie/AHottie.kt | 95 +- .../tachiyomi/extension/all/akuma/Akuma.kt | 235 +- .../extension/all/akuma/AkumaFactory.kt | 59 +- .../extension/all/akuma/AkumaFilters.kt | 65 +- .../extension/all/akuma/AkumaUrlActivity.kt | 11 +- .../all/akuma/DDosGuardInterceptor.kt | 46 +- .../extension/all/asmhentai/ASMHFactory.kt | 13 +- .../extension/all/asmhentai/AsmHentai.kt | 79 +- .../tachiyomi/extension/all/batoto/BatoTo.kt | 1402 ++++--- .../extension/all/batoto/BatoToFactory.kt | 227 +- .../extension/all/batoto/BatoToUrlActivity.kt | 28 +- .../all/beauty3600000/Beauty3600000.kt | 155 +- .../extension/all/buondua/BuonDua.kt | 45 +- .../extension/all/comicfury/ComicFury.kt | 229 +- .../all/comicfury/ComicFuryFactory.kt | 33 +- .../extension/all/comickfun/Comick.kt | 540 +-- .../extension/all/comickfun/ComickFactory.kt | 108 +- .../all/comickfun/ComickUrlActivity.kt | 11 +- .../tachiyomi/extension/all/comickfun/Dto.kt | 108 +- .../extension/all/comickfun/Filters.kt | 364 +- .../extension/all/comickfun/Helpers.kt | 35 +- .../tachiyomi/extension/all/comico/Comico.kt | 254 +- .../extension/all/comico/ComicoFactory.kt | 18 +- .../extension/all/comico/ComicoModels.kt | 38 +- .../all/comicskingdom/ComicsKingdom.kt | 292 +- .../all/comicsvalley/ComicsValley.kt | 11 +- .../extension/all/comikey/Comikey.kt | 426 +- .../extension/all/comikey/ComikeyFactory.kt | 15 +- .../extension/all/comikey/ComikeyFilters.kt | 78 +- .../all/comikey/ComikeyUrlActivity.kt | 11 +- .../extension/all/commitstrip/CommitStrip.kt | 140 +- .../all/commitstrip/CommitStripFactory.kt | 14 +- .../tachiyomi/extension/all/coomer/Coomer.kt | 11 +- .../extension/all/cosplaytele/CosplayTele.kt | 125 +- .../tachiyomi/extension/all/cubari/Cubari.kt | 375 +- .../extension/all/cubari/CubariFactory.kt | 11 +- .../extension/all/cubari/CubariUrlActivity.kt | 39 +- .../all/cubari/RemoteStorageUtils.kt | 47 +- .../extension/all/danbooru/Danbooru.kt | 190 +- .../extension/all/danbooru/Filters.kt | 7 +- .../extension/all/deviantart/DeviantArt.kt | 87 +- .../all/deviantart/DeviantArtUrlActivity.kt | 11 +- .../all/dragonballmultiverse/DbMFactory.kt | 115 +- .../all/dragonballmultiverse/DbMultiverse.kt | 66 +- .../extension/all/ehentai/EHFactory.kt | 55 +- .../extension/all/ehentai/EHUrlActivity.kt | 11 +- .../tachiyomi/extension/all/ehentai/EHUtil.kt | 21 +- .../extension/all/ehentai/EHentai.kt | 666 +-- .../all/ehentai/ExGalleryMetadata.kt | 23 +- .../extension/all/ehentai/MetadataCopier.kt | 37 +- .../tachiyomi/extension/all/ehentai/Tag.kt | 5 +- .../extension/all/ehentai/UriGroup.kt | 6 +- .../extension/all/eromanhwa/Eromanhwa.kt | 11 +- .../all/eternalmangas/EternalMangas.kt | 65 +- .../all/eternalmangas/EternalMangasFactory.kt | 13 +- .../extension/all/everiaclub/EveriaClub.kt | 73 +- .../all/everiaclubcom/EveriaClubCom.kt | 150 +- .../extension/all/foamgirl/FoamGirl.kt | 142 +- .../FoolSlideCustomizableFactory.kt | 35 +- .../all/freleinbooks/FreleinBooks.kt | 237 +- .../tachiyomi/extension/all/galaxy/Galaxy.kt | 297 +- .../extension/all/galaxy/GalaxyFactory.kt | 37 +- .../tachiyomi/extension/all/galaxy/Genre.kt | 12 +- .../extension/all/grabberzone/GrabberZone.kt | 18 +- .../extension/all/hennojin/Hennojin.kt | 117 +- .../extension/all/hennojin/HennojinFactory.kt | 9 +- .../extension/all/hentai3/Hentai3.kt | 430 +- .../extension/all/hentai3/Hentai3Factory.kt | 63 +- .../extension/all/hentai3/Hentai3Filters.kt | 85 +- .../all/hentaicosplay/HentaiCosplay.kt | 191 +- .../extension/all/hentaiera/HentaiEra.kt | 219 +- .../all/hentaiera/HentaiEraFactory.kt | 38 +- .../extension/all/hentaifox/HentaiFox.kt | 159 +- .../all/hentaifox/HentaiFoxFactory.kt | 15 +- .../all/hentaihand/HentaiHandFactory.kt | 117 +- .../tachiyomi/extension/all/hitomi/Filters.kt | 53 +- .../tachiyomi/extension/all/hitomi/Hitomi.kt | 534 +-- .../extension/all/hitomi/HitomiDto.kt | 15 +- .../extension/all/hitomi/HitomiFactory.kt | 57 +- .../extension/all/hniscantrad/HNIScantrad.kt | 14 +- .../all/holonometria/Holonometria.kt | 152 +- .../all/holonometria/HolonometriaFactory.kt | 11 +- .../extension/all/imhentai/IMHentai.kt | 91 +- .../extension/all/imhentai/IMHentaiFactory.kt | 22 +- .../extension/all/izneo/ImageInterceptor.kt | 31 +- .../tachiyomi/extension/all/izneo/Izneo.kt | 190 +- .../tachiyomi/extension/all/izneo/IzneoAPI.kt | 17 +- .../extension/all/izneo/IzneoFactory.kt | 15 +- .../extension/all/junmeitu/Junmeitu.kt | 131 +- .../extension/all/junmeitu/PageDto.kt | 4 +- .../extension/all/kdtscans/KdtScans.kt | 28 +- .../tachiyomi/extension/all/kemono/Kemono.kt | 21 +- .../extension/all/kiutaku/Kiutaku.kt | 81 +- .../all/kiutaku/KiutakuUrlActivity.kt | 12 +- .../tachiyomi/extension/all/koharu/Koharu.kt | 806 ++-- .../extension/all/koharu/KoharuDto.kt | 167 +- .../extension/all/koharu/KoharuFactory.kt | 27 +- .../extension/all/koharu/KoharuFilters.kt | 115 +- .../extension/all/koharu/KoharuUrlActivity.kt | 11 +- .../tachiyomi/extension/all/komga/Komga.kt | 361 +- .../extension/all/komga/KomgaFactory.kt | 7 +- .../extension/all/komga/KomgaFilters.kt | 81 +- .../extension/all/komga/KomgaUtils.kt | 124 +- .../tachiyomi/extension/all/komga/dto/Dto.kt | 78 +- .../extension/all/lanraragi/LANraragi.kt | 298 +- .../all/leagueoflegends/LOLFactory.kt | 45 +- .../all/leagueoflegends/LOLModels.kt | 8 +- .../all/leagueoflegends/LOLUniverse.kt | 126 +- .../all/littlegarden/LittleGarden.kt | 142 +- .../extension/all/luscious/Luscious.kt | 846 ++-- .../extension/all/luscious/LusciousFactory.kt | 41 +- .../magicaltranslators/MagicalTranslators.kt | 56 +- .../extension/all/manga18me/Filters.kt | 185 +- .../extension/all/manga18me/Manga18Me.kt | 410 +- .../extension/all/mangadex/MDConstants.kt | 99 +- .../extension/all/mangadex/MangaDex.kt | 865 ++-- .../extension/all/mangadex/MangaDexFactory.kt | 187 +- .../extension/all/mangadex/MangaDexFilters.kt | 515 ++- .../extension/all/mangadex/MangaDexHelper.kt | 425 +- .../extension/all/mangadex/MangaDexIntl.kt | 17 +- .../all/mangadex/MangadexUrlActivity.kt | 26 +- .../all/mangadex/MdAtHomeReportInterceptor.kt | 73 +- .../extension/all/mangadex/dto/AuthorDto.kt | 12 +- .../extension/all/mangadex/dto/ChapterDto.kt | 5 +- .../extension/all/mangadex/dto/CoverArtDto.kt | 4 +- .../extension/all/mangadex/dto/EntityDto.kt | 4 +- .../extension/all/mangadex/dto/ListDto.kt | 4 +- .../extension/all/mangadex/dto/MangaDto.kt | 34 +- .../extension/all/mangadex/dto/ResponseDto.kt | 1 - .../all/mangadex/dto/ScanlationGroupDto.kt | 8 +- .../extension/all/mangadex/dto/UserDto.kt | 8 +- .../extension/all/mangafire/Filters.kt | 238 +- .../all/mangafire/ImageInterceptor.kt | 26 +- .../extension/all/mangafire/MangaFire.kt | 119 +- .../all/mangafire/MangaFireFactory.kt | 19 +- .../all/mangaforfree/MangaForFreeFactory.kt | 23 +- .../extension/all/mangapark/MangaPark.kt | 263 +- .../extension/all/mangapark/MangaParkDto.kt | 103 +- .../all/mangapark/MangaParkFactory.kt | 203 +- .../all/mangapark/MangaParkFilters.kt | 172 +- .../all/mangapark/MangaParkPayloadDto.kt | 8 +- .../all/mangapark/MangaParkQueries.kt | 33 +- .../extension/all/mangaplus/MangaPlus.kt | 249 +- .../extension/all/mangaplus/MangaPlusDto.kt | 172 +- .../all/mangaplus/MangaPlusFactory.kt | 23 +- .../all/mangaplus/MangaPlusUrlActivity.kt | 33 +- .../mangapluscreators/MangaPlusCreators.kt | 216 +- .../mangapluscreators/MangaPlusCreatorsDto.kt | 29 +- .../MangaPlusCreatorsFactory.kt | 9 +- .../extension/all/mangareaderto/Filters.kt | 244 +- .../all/mangareaderto/ImageInterceptor.kt | 41 +- .../all/mangareaderto/MangaReader.kt | 112 +- .../all/mangareaderto/Preferences.kt | 26 +- .../extension/all/mangatoon/MangaToon.kt | 234 +- .../all/mangatoon/MangaToonFactory.kt | 32 +- .../extension/all/mangaup/MangaUp.kt | 140 +- .../extension/all/mangaup/MangaUpDto.kt | 43 +- .../all/mangaup/MangaUpUrlActivity.kt | 12 +- .../tachiyomi/extension/all/mango/Mango.kt | 195 +- .../all/manhwa18cc/Manhwa18CcFactory.kt | 24 +- .../extension/all/manhwa18net/Manhwa18Net.kt | 77 +- .../all/manhwaclubnet/ManhwaClubNet.kt | 12 +- .../all/manhwaclubnet/ManhwaClubNetFactory.kt | 9 +- .../all/manhwadashraw/ManhwaDashRaw.kt | 13 +- .../extension/all/meituatop/MeituaTop.kt | 251 +- .../extension/all/miauscan/MiauScanFactory.kt | 80 +- .../extension/all/mihentai/Mihentai.kt | 61 +- .../tachiyomi/extension/all/mitaku/Mitaku.kt | 138 +- .../all/myreadingmanga/MyReadingManga.kt | 284 +- .../myreadingmanga/MyReadingMangaFactory.kt | 37 +- .../all/myrockmanga/MyRockMangaFactory.kt | 15 +- .../extension/all/namicomi/NamiComi.kt | 258 +- .../all/namicomi/NamiComiConstants.kt | 11 +- .../extension/all/namicomi/NamiComiFactory.kt | 133 +- .../extension/all/namicomi/NamiComiFilters.kt | 421 +- .../extension/all/namicomi/NamiComiHelper.kt | 144 +- .../all/namicomi/NamiComiUrlActivity.kt | 11 +- .../extension/all/namicomi/dto/ChapterDto.kt | 4 +- .../extension/all/namicomi/dto/CoverArtDto.kt | 4 +- .../extension/all/namicomi/dto/EntityDto.kt | 4 +- .../extension/all/namicomi/dto/MangaDto.kt | 20 +- .../all/namicomi/dto/OrganizationDto.kt | 8 +- .../extension/all/nhentai/NHFactory.kt | 13 +- .../extension/all/nhentai/NHUrlActivity.kt | 11 +- .../extension/all/nhentai/NHentai.kt | 301 +- .../all/nhentaicom/NHentaiComFactory.kt | 116 +- .../extension/all/ninemanga/NineManga.kt | 354 +- .../all/ninemanga/NineMangaFactory.kt | 1443 +++---- .../all/ninenineninehentai/AnimeH.kt | 267 +- .../all/ninenineninehentai/AnimeHDto.kt | 140 +- .../all/ninenineninehentai/AnimeHFactory.kt | 39 +- .../all/ninenineninehentai/AnimeHFilters.kt | 93 +- .../ninenineninehentai/AnimeHPayloadDto.kt | 4 +- .../all/ninenineninehentai/AnimeHQueries.kt | 33 +- .../ninenineninehentai/AnimeHUrlActivity.kt | 11 +- .../extension/all/noisemanga/NoiseManga.kt | 117 +- .../all/noisemanga/NoiseMangaFactory.kt | 9 +- .../extension/all/novelcool/NovelCool.kt | 480 ++- .../all/novelcool/NovelCoolFactory.kt | 19 +- .../otakusanctuary/OtakuSanctuaryFactory.kt | 17 +- .../extension/all/pandachaika/PandaChaika.kt | 308 +- .../all/pandachaika/PandaChaikaDto.kt | 127 +- .../all/pandachaika/PandaChaikaFactory.kt | 45 +- .../all/pandachaika/PandaChaikaFilters.kt | 74 +- .../all/pandachaika/PandaChaikaUrlActivity.kt | 11 +- .../all/pandachaika/PandaChaikaUtils.kt | 101 +- .../extension/all/peppercarrot/Filters.kt | 25 +- .../all/peppercarrot/PepperCarrot.kt | 213 +- .../extension/all/peppercarrot/Preferences.kt | 104 +- .../extension/all/photos18/Photos18.kt | 151 +- .../tachiyomi/extension/all/pixiv/Pixiv.kt | 295 +- .../extension/all/pixiv/PixivFactory.kt | 3 +- .../extension/all/pixiv/PixivFilters.kt | 5 +- .../tachiyomi/extension/all/pixiv/Util.kt | 32 +- .../all/projectsuki/DataExtractor.kt | 646 +-- .../extension/all/projectsuki/PathPattern.kt | 38 +- .../extension/all/projectsuki/ProjectSuki.kt | 404 +- .../all/projectsuki/ProjectSukiAPI.kt | 185 +- .../all/projectsuki/ProjectSukiFilters.kt | 105 +- .../all/projectsuki/ProjectSukiPreferences.kt | 158 +- .../all/projectsuki/SmartBookSearchHandler.kt | 151 +- .../activities/ProjectSukiBookUrlActivity.kt | 17 +- .../activities/ProjectSukiReadUrlActivity.kt | 17 +- .../ProjectSukiSearchUrlActivity.kt | 17 +- .../extension/all/pururin/Pururin.kt | 571 +-- .../extension/all/pururin/PururinFactory.kt | 53 +- .../extension/all/pururin/PururinFilters.kt | 129 +- .../all/ravensscans/RavensScansFactory.kt | 13 +- .../all/sandraandwoo/SandraAndWoo.kt | 61 +- .../all/sandraandwoo/SandraAndWooFactory.kt | 9 +- .../all/simplycosplay/SimplyCosplay.kt | 266 +- .../all/simplycosplay/SimplyCosplayDto.kt | 74 +- .../simplycosplay/SimplyCosplayUrlActivity.kt | 11 +- .../all/simplyhentai/SimplyHentai.kt | 134 +- .../all/simplyhentai/SimplyHentaiAPI.kt | 51 +- .../all/simplyhentai/SimplyHentaiFactory.kt | 25 +- .../all/simplyhentai/SimplyHentaiFilters.kt | 8 +- .../extension/all/snowmtl/Snowmtl.kt | 30 +- .../extension/all/snowmtl/SnowmtlFactory.kt | 15 +- .../interceptors/TranslationInterceptor.kt | 72 +- .../all/snowmtl/translator/BingTranslator.kt | 98 +- .../snowmtl/translator/TranslatorEngine.kt | 7 +- .../extension/all/solarmtl/Solarmtl.kt | 42 +- .../extension/all/solarmtl/SolarmtlFactory.kt | 35 +- .../extension/all/taddyink/TaddyInk.kt | 123 +- .../extension/all/taddyink/TaddyInkFactory.kt | 7 +- .../all/taddyink/TaddyInkUrlActivity.kt | 11 +- .../extension/all/taddyink/TaddyUtils.kt | 77 +- .../extension/all/tappytoon/Tappytoon.kt | 185 +- .../extension/all/tappytoon/TappytoonAPI.kt | 31 +- .../thelibraryofohara/TheLibraryOfOhara.kt | 119 +- .../TheLibraryOfOharaFactory.kt | 24 +- .../all/thunderscans/ThunderScansFactory.kt | 40 +- .../extension/all/toomics/ToomicsFactory.kt | 30 +- .../extension/all/toomics/ToomicsGlobal.kt | 177 +- .../extension/all/twicomi/Twicomi.kt | 219 +- .../extension/all/twicomi/TwicomiDto.kt | 58 +- .../all/vinnieVeritas/vinnieVeritas.kt | 60 +- .../all/vinnieVeritas/vinnieVeritasFactory.kt | 1 - .../extension/all/webtoons/WebtoonsFactory.kt | 84 +- .../extension/all/webtoons/WebtoonsSrc.kt | 48 +- .../WebtoonsTranslateFactory.kt | 101 +- .../all/xasiatalbums/XAsiatAlbums.kt | 179 +- .../extension/all/xinmeitulu/Xinmeitulu.kt | 72 +- .../all/xinmeitulu/XinmeituluUrlActivity.kt | 17 +- .../tachiyomi/extension/all/xkcd/Xkcd.kt | 83 +- .../extension/all/xkcd/XkcdFactory.kt | 15 +- .../extension/all/xkcd/translations/XkcdES.kt | 3 +- .../all/yaoimangaonline/YaoiMangaFilters.kt | 824 ++-- .../all/yaoimangaonline/YaoiMangaOnline.kt | 67 +- .../extension/ar/arabsdoujin/ArabsDoujin.kt | 8 +- .../extension/ar/arabshentai/ArabsHentai.kt | 35 +- .../ar/arabshentai/ArabsHentaiFilters.kt | 29 +- .../extension/ar/arabtoons/ArabToons.kt | 13 +- .../extension/ar/areamanga/AreaManga.kt | 13 +- .../extension/ar/areascans/AreaScans.kt | 13 +- .../extension/ar/aresnov/ScarManga.kt | 15 +- .../tachiyomi/extension/ar/azora/Azora.kt | 2 + .../extension/ar/beastscans/UmiManga.kt | 21 +- .../extension/ar/comicarab/ComicArab.kt | 13 +- .../extension/ar/crowscans/Hadess.kt | 21 +- .../tachiyomi/extension/ar/dilar/Dilar.kt | 50 +- .../tachiyomi/extension/ar/dilar/Dto.kt | 18 +- .../ar/empirewebtoon/EmpireWebtoon.kt | 27 +- .../extension/ar/gatemanga/Gatemanga.kt | 13 +- .../extension/ar/gmangasite/GmangaSite.kt | 13 +- .../extension/ar/hentaislayer/HentaiSlayer.kt | 77 +- .../tachiyomi/extension/ar/hijala/Hijala.kt | 13 +- .../extension/ar/iimanga/ARESManga.kt | 39 +- .../extension/ar/manga3asq/Manga3asq.kt | 15 +- .../tachiyomi/extension/ar/mangaae/MangaAe.kt | 200 +- .../extension/ar/mangaailand/MangaAiLand.kt | 42 +- .../extension/ar/mangaflame/MangaFlame.kt | 13 +- .../extension/ar/mangahub/MangaHub.kt | 19 +- .../extension/ar/mangalek/Mangalek.kt | 65 +- .../extension/ar/mangalink/Mangalink.kt | 28 +- .../extension/ar/mangalionz/MangaLionz.kt | 14 +- .../extension/ar/manganoon/MangaNoon.kt | 17 +- .../extension/ar/mangapeak/MangaPeak.kt | 11 +- .../extension/ar/mangapro/MangaPro.kt | 11 +- .../extension/ar/mangarose/MangaRose.kt | 13 +- .../extension/ar/mangaspark/MangaSpark.kt | 13 +- .../extension/ar/mangastarz/MangaStarz.kt | 13 +- .../extension/ar/mangastorm/MangaStorm.kt | 91 +- .../extension/ar/mangaswat/MangaSwat.kt | 129 +- .../tachiyomi/extension/ar/mangatales/Dto.kt | 26 +- .../extension/ar/mangatales/MangaTales.kt | 40 +- .../extension/ar/mangatime/MangaTime.kt | 13 +- .../extension/ar/mangatrend/MangaTrend.kt | 21 +- .../extension/ar/mangatuk/MangaTuk.kt | 13 +- .../extension/ar/manhatok/Manhatok.kt | 8 +- .../extension/ar/noonscan/NoonScan.kt | 13 +- .../tachiyomi/extension/ar/olaoe/Olaoe.kt | 15 +- .../tachiyomi/extension/ar/onma/Onma.kt | 32 +- .../extension/ar/paradisebl/ParadiseBL.kt | 11 +- .../extension/ar/remangaarabic/REManga.kt | 282 +- .../extension/ar/rocksmanga/RocksManga.kt | 23 +- .../tachiyomi/extension/ar/scans4u/Scans4u.kt | 1 - .../tachiyomi/extension/ar/shqqaa/Shqqaa.kt | 54 +- .../extension/ar/stellarsaber/StellarSaber.kt | 13 +- .../tachiyomi/extension/ar/teamx/TeamX.kt | 164 +- .../extension/ar/vexmanga/VexManga.kt | 65 +- .../tachiyomi/extension/ar/yokai/Yokai.kt | 37 +- .../tachiyomi/extension/ar/yonabar/YonaBar.kt | 21 +- .../extension/ar/yurimoonsub/YuriMoonSub.kt | 28 +- .../extension/bg/utsukushii/Utsukushii.kt | 4 +- .../extension/ca/fansubscat/FansubsCat.kt | 15 +- .../ca/fansubscathentai/HentaiCat.kt | 15 +- .../extension/de/mangatube/MangaTube.kt | 120 +- .../extension/en/adultwebtoon/AdultWebtoon.kt | 12 +- .../tachiyomi/extension/en/aisha/Aisha.kt | 30 +- .../tachiyomi/extension/en/alandal/Alandal.kt | 197 +- .../tachiyomi/extension/en/alandal/Dto.kt | 59 +- .../tachiyomi/extension/en/alandal/Filters.kt | 104 +- .../extension/en/allanime/AllManga.kt | 239 +- .../en/allanime/AllMangaUrlActivity.kt | 11 +- .../tachiyomi/extension/en/allanime/Dto.kt | 76 +- .../extension/en/allanime/Filters.kt | 202 +- .../extension/en/allanime/Queries.kt | 40 +- .../tachiyomi/extension/en/allanime/Utils.kt | 48 +- .../extension/en/altayscans/AltayScans.kt | 19 +- .../en/aniglisnovels/AniglisNovels.kt | 11 +- .../AnimatedGlitchedComics.kt | 11 +- .../extension/en/anisascans/AnisaScans.kt | 19 +- .../extension/en/aquamanga/AquaManga.kt | 55 +- .../extension/en/arcrelight/ArcRelight.kt | 37 +- .../extension/en/arvencomics/ArvenComics.kt | 11 +- .../extension/en/arvenscans/VortexScans.kt | 26 +- .../extension/en/aryascans/AryaScans.kt | 11 +- .../extension/en/ascalonscans/AscalonScans.kt | 24 +- .../extension/en/asmotoon/Asmotoon.kt | 11 +- .../extension/en/astrascans/AstraScans.kt | 21 +- .../extension/en/asurascans/AsuraScans.kt | 262 +- .../en/asurascans/AsuraScansFilters.kt | 32 +- .../en/asurascansfree/AsuraScansFree.kt | 13 +- .../extension/en/atsumaru/Atsumaru.kt | 118 +- .../tachiyomi/extension/en/atsumaru/Dto.kt | 80 +- .../tachiyomi/extension/en/aurora/Aurora.kt | 157 +- .../extension/en/babelwuxia/BabelWuxia.kt | 1 - .../en/bakkinselfhosted/BakkinSelfHosted.kt | 23 +- .../tachiyomi/extension/en/batcave/BatCave.kt | 217 +- .../tachiyomi/extension/en/batcave/Dto.kt | 1 - .../tachiyomi/extension/en/batcave/Filters.kt | 94 +- .../BattleInFiveSecondsAfterMeeting.kt | 30 +- .../extension/en/buttsmithy/Buttsmithy.kt | 114 +- .../extension/en/casacomic/CasaComic.kt | 11 +- .../extension/en/clonemanga/CloneManga.kt | 105 +- .../extension/en/clowncorps/ClownCorps.kt | 160 +- .../tachiyomi/extension/en/cocomic/Cocomic.kt | 9 +- .../extension/en/coffeemanga/CoffeeManga.kt | 5 +- .../en/collectedcurios/Collectedcurios.kt | 47 +- .../extension/en/comicextra/ComicExtra.kt | 275 +- .../extension/en/comicfans/ComicFans.kt | 205 +- .../tachiyomi/extension/en/comicfans/Dto.kt | 78 +- .../extension/en/comicfans/Filters.kt | 109 +- .../extension/en/comickiba/Manhuagold.kt | 21 +- .../en/constellarscans/ConstellarScans.kt | 81 +- .../extension/en/cookiekiara/CookieKiara.kt | 4 +- .../en/culturedworks/CulturedWorks.kt | 39 +- .../extension/en/cutiecomics/CutieComics.kt | 124 +- .../en/cutiecomics/CutieComicsUrlActivity.kt | 12 +- .../en/darklegacycomics/DarkLegacyComics.kt | 75 +- .../extension/en/darkscancom/DarkScanCom.kt | 11 +- .../extension/en/darkscans/DarkScans.kt | 9 +- .../extension/en/darkscience/DarkScience.kt | 116 +- .../extension/en/darthsdroids/DarthsDroids.kt | 138 +- .../extension/en/daycomicsme/DAYcomicsMe.kt | 52 +- .../digitalcomicmuseum/DigitalComicMuseum.kt | 60 +- .../en/disasterscans/DisasterScans.kt | 125 +- .../en/disasterscans/DisasterScansDto.kt | 61 +- .../disasterscans/DisasterScansUrlActivity.kt | 11 +- .../extension/en/dmcscans/DMCScans.kt | 72 +- .../extension/en/doujinio/Doujinio.kt | 111 +- .../extension/en/doujinio/DoujinioDto.kt | 66 +- .../extension/en/doujinio/DoujinioHelper.kt | 118 +- .../tachiyomi/extension/en/doujins/Doujins.kt | 217 +- .../extension/en/dragontea/DragonTea.kt | 21 +- .../extension/en/drakescans/DrakeScans.kt | 23 +- .../en/dynasty/DynastyAnthologies.kt | 9 +- .../extension/en/dynasty/DynastyChapters.kt | 20 +- .../extension/en/dynasty/DynastyDoujins.kt | 66 +- .../extension/en/dynasty/DynastyIssues.kt | 9 +- .../extension/en/dynasty/DynastyScanlator.kt | 13 +- .../extension/en/dynasty/DynastyScans.kt | 138 +- .../extension/en/dynasty/DynastySeries.kt | 24 +- .../en/dynasty/DynastyUrlActivity.kt | 11 +- .../en/eggporncomics/Eggporncomics.kt | 283 +- .../extension/en/elanschool/ElanSchool.kt | 72 +- .../extension/en/elarcpage/ElarcPage.kt | 54 +- .../tachiyomi/extension/en/erofus/Erofus.kt | 235 +- .../extension/en/erosscans/ErosScans.kt | 37 +- .../extension/en/evilflowers/EvilFlowers.kt | 11 +- .../en/existentialcomics/ExistentialComics.kt | 45 +- .../tachiyomi/extension/en/explosm/Explosm.kt | 107 +- .../tachiyomi/extension/en/ezmanga/EZmanga.kt | 11 +- .../extension/en/firescans/Firescans.kt | 48 +- .../en/firstkissmanganet/FirstKissMangaNet.kt | 11 +- .../en/firstkissmanhua/FirstKissManhua.kt | 19 +- .../extension/en/flamecomics/FlameComics.kt | 418 +- .../en/flamecomics/FlameComicsDto.kt | 16 +- .../en/freecomiconline/FreeComicOnline.kt | 11 +- .../extension/en/freemanga/FreeManga.kt | 9 +- .../extension/en/freemangatop/FreeMangaTop.kt | 95 +- .../extension/en/gedecomix/GEDEComix.kt | 16 +- .../extension/en/gourmetscans/GourmetScans.kt | 78 +- .../extension/en/grrlpower/GrrlPower.kt | 117 +- .../en/gunnerkriggcourt/GunnerkriggCourt.kt | 63 +- .../tachiyomi/extension/en/gwtb/GWTB.kt | 56 +- .../tachiyomi/extension/en/hachi/Hachi.kt | 257 +- .../tachiyomi/extension/en/hachi/HachiDto.kt | 9 +- .../extension/en/hachi/HachiUrlActivity.kt | 11 +- .../extension/en/harimanga/Harimanga.kt | 19 +- .../extension/en/hentai20/Hentai20.kt | 9 +- .../extension/en/hentai2read/Hentai2Read.kt | 3727 +++++++++-------- .../en/hentai2read/Hentai2ReadActivity.kt | 11 +- .../extension/en/hentai3zcc/Hentai3zCC.kt | 19 +- .../extension/en/hentaidex/HentaiDex.kt | 69 +- .../extension/en/hentaidexy/Hentaidexy.kt | 89 +- .../en/hentaidexy/HentaidexyUrlActivity.kt | 11 +- .../extension/en/hentaihere/HentaiHere.kt | 433 +- .../en/hentaihere/HentaiHereUrlActivity.kt | 11 +- .../extension/en/hentaimanga/HentaiManga.kt | 31 +- .../extension/en/hentainexus/HentaiNexus.kt | 189 +- .../en/hentainexus/HentaiNexusActivity.kt | 11 +- .../en/hentainexus/HentaiNexusFilters.kt | 31 +- .../en/hentainexus/HentaiNexusUtils.kt | 11 +- .../en/hentairead/HentaiReadFilters.kt | 50 +- .../extension/en/hentairead/Hentairead.kt | 280 +- .../en/hentaiwebtoon/HentaiWebtoon.kt | 20 +- .../extension/en/hentaixcomic/HentaiXComic.kt | 13 +- .../en/hentaixdickgirl/HentaiXDickgirl.kt | 6 +- .../extension/en/hentaixyuri/HentaiXYuri.kt | 13 +- .../extension/en/hiperdex/Hiperdex.kt | 44 +- .../extension/en/hiveworks/Hiveworks.kt | 424 +- .../tachiyomi/extension/en/hm2d/HM2D.kt | 11 +- .../extension/en/holymanga/HolyManga.kt | 11 +- .../extension/en/honkaiimpact/Honkaiimpact.kt | 43 +- .../extension/en/hotcomics/HotComics.kt | 50 +- .../en/hunlightscans/HunlightScans.kt | 11 +- .../extension/en/igniscomic/IgnisComic.kt | 11 +- .../en/infernalvoidscans/HiveScans.kt | 39 +- .../en/infinityscans/InfinityScans.kt | 202 +- .../en/infinityscans/InfinityScansDto.kt | 39 +- .../en/infinityscans/InfinityScansFilters.kt | 46 +- .../en/infinityscans/WebviewInterceptor.kt | 25 +- .../extension/en/irisscans/IrisScans.kt | 19 +- .../extension/en/irovedout/IRovedOut.kt | 83 +- .../en/isekaiscanmanga/IsekaiScanManga.kt | 3 +- .../en/isekaiscantop/IsekaiScanTop.kt | 29 +- .../extension/en/kaiscans/LuaScans.kt | 8 +- .../tachiyomi/extension/en/kami18/Filters.kt | 143 +- .../tachiyomi/extension/en/kami18/Kami18.kt | 346 +- .../extension/en/kappabeast/KappaBeast.kt | 24 +- .../extension/en/keenspot/TwoKinds.kt | 97 +- .../KillSixBillionDemons.kt | 72 +- .../extension/en/kingofscans/KingOfScans.kt | 13 +- .../extension/en/kumascans/KumaScans.kt | 9 +- .../extension/en/latisbooks/Latisbooks.kt | 67 +- .../extension/en/lemonfont/LemonFont.kt | 67 +- .../en/leviatanscans/LeviatanScans.kt | 23 +- .../extension/en/likemanga/LikeManga.kt | 288 +- .../en/likemanga/LikeMangaFilters.kt | 94 +- .../en/likemanga/LikeMangaUrlActivity.kt | 11 +- .../extension/en/likemangain/LikeMangaIn.kt | 13 +- .../en/loadingartist/LoadingArtist.kt | 50 +- .../extension/en/luascans/LuaScans.kt | 11 +- .../extension/en/lunarscans/LunarScans.kt | 45 +- .../extension/en/madaradex/MadaraDex.kt | 20 +- .../extension/en/madokami/Madokami.kt | 101 +- .../extension/en/magusmanga/MagusManga.kt | 30 +- .../extension/en/manga18fx/Manga18fx.kt | 112 +- .../extension/en/manga18x/Manga18x.kt | 8 +- .../tachiyomi/extension/en/manga1k/Manga1k.kt | 11 +- .../tachiyomi/extension/en/manga1s/manga1s.kt | 211 +- .../extension/en/mangabat/Mangabat.kt | 11 +- .../extension/en/mangabtt/MangaBTT.kt | 291 +- .../extension/en/mangabtt/MangaBTTFilters.kt | 137 +- .../extension/en/mangaclash/MangaClash.kt | 22 +- .../extension/en/mangademon/MangaDemon.kt | 181 +- .../en/mangademon/MangaDemonFilters.kt | 123 +- .../en/mangadistrict/MangaDistrict.kt | 149 +- .../extension/en/mangadoom/MangaDoom.kt | 304 +- .../extension/en/mangafastcom/Mangafastcom.kt | 13 +- .../en/mangaforfreecom/Mangaforfreecom.kt | 12 +- .../extension/en/mangafox/MangaFox.kt | 476 ++- .../extension/en/mangafreak/Mangafreak.kt | 323 +- .../en/mangafreakonline/MangaFreakOnline.kt | 13 +- .../extension/en/mangafun/DecompressJson.kt | 43 +- .../extension/en/mangafun/MangaFun.kt | 166 +- .../extension/en/mangafun/MangaFunDto.kt | 5 +- .../extension/en/mangafun/MangaFunFilters.kt | 25 +- .../extension/en/mangafun/MangaFunUtils.kt | 89 +- .../extension/en/mangagalaxy/MangaGalaxy.kt | 11 +- .../tachiyomi/extension/en/mangago/Mangago.kt | 548 ++- .../extension/en/mangagojo/MangaGojo.kt | 11 +- .../extension/en/mangahasu/Mangahasu.kt | 379 +- .../extension/en/mangahen/MangaHen.kt | 375 +- .../extension/en/mangahen/MangaHenFilters.kt | 92 +- .../extension/en/mangahere/Mangahere.kt | 347 +- .../extension/en/mangairo/Mangairo.kt | 16 +- .../extension/en/mangakakalot/Mangakakalot.kt | 2 + .../extension/en/mangakatana/MangaKatana.kt | 392 +- .../extension/en/mangakomi/MangaKomi.kt | 20 +- .../en/mangaleveling/MangaLeveling.kt | 8 +- .../en/mangamo/FirestoreRequestFactory.kt | 161 +- .../tachiyomi/extension/en/mangamo/Mangamo.kt | 528 +-- .../extension/en/mangamo/MangamoAuth.kt | 85 +- .../extension/en/mangamo/MangamoHelper.kt | 53 +- .../extension/en/mangamo/cachedBy.kt | 11 +- .../extension/en/mangamo/dto/DocumentDto.kt | 22 +- .../extension/en/manganelo/Manganato.kt | 11 +- .../extension/en/mangapill/MangaPill.kt | 245 +- .../extension/en/mangaplanet/Filters.kt | 195 +- .../extension/en/mangaplanet/MangaPlanet.kt | 203 +- .../extension/en/mangaplex/MangaPlex.kt | 60 +- .../extension/en/mangarawclub/MangaRawClub.kt | 210 +- .../en/mangarawclub/MangaRawClubFilters.kt | 122 +- .../extension/en/mangareadorg/MangaReadOrg.kt | 13 +- .../extension/en/mangasail/Mangasail.kt | 260 +- .../extension/en/mangasaki/MangaSaki.kt | 206 +- .../extension/en/mangasect/MangaSect.kt | 21 +- .../extension/en/mangatop/Filters.kt | 223 +- .../extension/en/mangatop/MangaScans.kt | 314 +- .../extension/en/mangatown/Mangatown.kt | 71 +- .../tachiyomi/extension/en/mangatx/MangaTX.kt | 23 +- .../en/mangatxunoriginal/MangaEmpress.kt | 11 +- .../extension/en/mangaweebs/MangaWeebs.kt | 9 +- .../extension/en/manhuaaz/ManhuaAZ.kt | 10 +- .../extension/en/manhuaes/ManhuaES.kt | 10 +- .../extension/en/manhuafast/ManhuaFast.kt | 9 +- .../extension/en/manhuaga/Manhuaga.kt | 31 +- .../extension/en/manhuamanhwa/ManhuaManhwa.kt | 13 +- .../extension/en/manhuanext/Manhuanext.kt | 11 +- .../extension/en/manhuaplus/ManhuaPlus.kt | 1 - .../en/manhuaplusorg/ManhuaPlusOrg.kt | 11 +- .../extension/en/manhuascanus/ManhuascanUs.kt | 32 +- .../extension/en/manhuatop/ManhuaTop.kt | 13 +- .../extension/en/manhuaus/ManhuaUS.kt | 1 - .../extension/en/manhuazone/ManhuaZone.kt | 13 +- .../extension/en/manhwa18/Manhwa18.kt | 128 +- .../extension/en/manhwa18/Manhwa18Dto.kt | 36 +- .../extension/en/manhwa18/Manhwa18Filters.kt | 181 +- .../extension/en/manhwa18org/Manhwa18Org.kt | 1 - .../extension/en/manhwa68/Manhwa68.kt | 14 +- .../extension/en/manhwabuddy/ManhwaBuddy.kt | 167 +- .../extension/en/manhwaden/ManhwaDen.kt | 11 +- .../extension/en/manhwafreake/ManhwaFreake.kt | 21 +- .../en/manhwafreakxyz/ManhwaFreakXyz.kt | 11 +- .../extension/en/manhwahentai/ManhwaHentai.kt | 60 +- .../en/manhwahentaime/ManhwahentaiMe.kt | 165 +- .../extension/en/manhwahub/ManhwaHub.kt | 11 +- .../extension/en/manhwalike/Manhwalike.kt | 222 +- .../en/manhwalike/ManhwalikeHelper.kt | 38 +- .../extension/en/manhwanew/ManhwaNew.kt | 13 +- .../extension/en/manhwasmen/ManhwasMen.kt | 354 +- .../extension/en/manhwatoon/ManhwaToon.kt | 11 +- .../extension/en/manhwatop/Manhwatop.kt | 1 - .../extension/en/manhwaworld/ManhwaWorld.kt | 19 +- .../extension/en/manhwaxxl/ManhwaXXL.kt | 179 +- .../extension/en/manhwaz/ManhwaZCom.kt | 19 +- .../tachiyomi/extension/en/manta/MantaAPI.kt | 37 +- .../extension/en/manta/MantaComics.kt | 130 +- .../extension/en/manta/MantaFilters.kt | 25 +- .../extension/en/manycomic/ManyComic.kt | 99 +- .../extension/en/manytoon/ManyToon.kt | 162 +- .../extension/en/manytoonme/ManyToonMe.kt | 1 - .../extension/en/megatokyo/Megatokyo.kt | 71 +- .../en/meowmeowcomics/MeowMeowComics.kt | 29 +- .../en/monochromecustom/MonochromeCustom.kt | 49 +- .../extension/en/multporn/Multporn.kt | 420 +- .../en/myhentaicomics/MyHentaiComics.kt | 151 +- .../en/myhentaigallery/MyHentaiGallery.kt | 254 +- .../MyHentaiGalleryUrlActivity.kt | 11 +- .../en/mysticalmerries/MysticalMerries.kt | 3 + .../extension/en/necroscans/NecroScans.kt | 11 +- .../extension/en/newmanhua/NewManhua.kt | 20 +- .../extension/en/nightscans/NightScans.kt | 14 +- .../extension/en/nineanime/NineAnime.kt | 335 +- .../extension/en/ninehentai/NineHentai.kt | 246 +- .../en/ninehentai/NineHentaiUrlActivity.kt | 11 +- .../extension/en/nuxscans/NuxScans.kt | 34 +- .../extension/en/nvmanga/OneManhwa.kt | 13 +- .../extension/en/nyraxmanga/NyraxManga.kt | 40 +- .../tachiyomi/extension/en/oglaf/Oglaf.kt | 35 +- .../extension/en/omegascans/OmegaScans.kt | 9 +- .../tachiyomi/extension/en/oots/oots.kt | 34 +- .../extension/en/oppaistream/OppaiStream.kt | 340 +- .../en/oppaistream/OppaiStreamUrlActivity.kt | 11 +- .../extension/en/paragonscans/ParagonScans.kt | 18 +- .../extension/en/paritehaber/Paritehaber.kt | 11 +- .../extension/en/patchfriday/PatchFriday.kt | 56 +- .../extension/en/philiascans/PhiliaScans.kt | 11 +- .../tachiyomi/extension/en/pmscans/Rackus.kt | 11 +- .../extension/en/porncomix/PornComix.kt | 39 +- .../extension/en/quantumscans/QuantumScans.kt | 19 +- .../extension/en/queenscans/QueenScans.kt | 8 +- .../QuestionableContent.kt | 71 +- .../extension/en/rainofsnow/RainOfSnow.kt | 92 +- .../extension/en/randowiz/Randowiz.kt | 54 +- .../en/readallcomicscom/ReadAllComics.kt | 168 +- .../ReadAttackOnTitanShingekiNoKyojinManga.kt | 50 +- .../en/readberserkmanga/ReadBerserkManga.kt | 43 +- .../ReadBlackCloverMangaOnline.kt | 13 +- ...adBokuNoHeroAcademiaMyHeroAcademiaManga.kt | 26 +- .../ReadChainsawManMangaOnline.kt | 33 +- .../extension/en/readcomicnet/ReadcomicNet.kt | 257 +- .../en/readcomiconline/Readcomiconline.kt | 481 ++- .../en/readcomicsonline/ReadComicsOnline.kt | 15 +- .../extension/en/readcomictop/ReadComicTop.kt | 300 +- .../ReadDragonBallSuperChouMangaOnline.kt | 33 +- .../ReadDrStoneMangaOnline.kt | 15 +- .../ReadFairyTailEdensZeroMangaOnline.kt | 37 +- .../ReadGoblinSlayerMangaOnline.kt | 37 +- .../ReadHaikyuuMangaOnline.kt | 13 +- .../ReadHunterxHunterMangaOnline.kt | 13 +- .../ReadJujutsuKaisenMangaOnline.kt | 17 +- .../ReadKaguyaSamaMangaOnline.kt | 21 +- .../ReadKingdomMangaOnline.kt | 11 +- ...adNanatsuNoTaizai7DeadlySinsMangaOnline.kt | 28 +- .../ReadNarutoBorutoSamurai8MangaOnline.kt | 15 +- .../ReadNoblesseManhwaOnline.kt | 46 +- .../ReadOnePieceMangaOnline.kt | 29 +- .../ReadOnePunchManMangaOnlineTwo.kt | 46 +- .../ReadSoloLevelingMangaManhwaOnline.kt | 9 +- .../ReadThePromisedNeverlandMangaOnline.kt | 20 +- .../ReadTokyoGhoulReTokyoGhoulMangaOnline.kt | 24 +- .../ReadTowerOfGodManhwaMangaOnline.kt | 15 +- .../ReadVinlandSagaMangaOnline.kt | 11 +- .../en/reallifecomics/RealLifeComics.kt | 94 +- .../extension/en/reaperscans/ReaperScans.kt | 51 +- .../en/reaperscans/ReaperScansDto.kt | 12 +- .../en/reaperscansunoriginal/Filters.kt | 136 +- .../ReaperScansUnoriginal.kt | 145 +- .../extension/en/resetscans/ResetScans.kt | 13 +- .../tachiyomi/extension/en/retsu/Retsu.kt | 12 +- .../extension/en/rezoscans/RezoScans.kt | 11 +- .../extension/en/rizzcomic/Filters.kt | 133 +- .../extension/en/rizzcomic/RizzComic.kt | 160 +- .../tachiyomi/extension/en/rmanga/Rmanga.kt | 158 +- .../extension/en/rmanga/RmangaFilters.kt | 163 +- .../extension/en/roliascan/RoliaScan.kt | 300 +- .../en/roliascan/RoliaScanUrlActivity.kt | 12 +- .../en/rosesquadscans/RoseSquadScans.kt | 16 +- .../tachiyomi/extension/en/s2manga/S2Manga.kt | 1 - .../en/schlockmercenary/Schlockmercenary.kt | 64 +- .../extension/en/scyllascans/ScyllaComics.kt | 1 - .../extension/en/sectscans/SectScans.kt | 71 +- .../extension/en/setsuscans/SetsuScans.kt | 68 +- .../extension/en/shibamanga/ShibaManga.kt | 13 +- .../extension/en/shojoscans/VioletScans.kt | 13 +- .../en/silentmangaaudition/ExtensionData.kt | 289 +- .../SilentMangaAudition.kt | 77 +- .../extension/en/sitemanga/SiteManga.kt | 13 +- .../extension/en/skymanga/SkyManga.kt | 23 +- .../extension/en/snowscans/SnowScans.kt | 13 +- .../en/solarandsundry/SolarAndSundry.kt | 56 +- .../extension/en/spiderscans/SpiderScans.kt | 19 +- .../extension/en/spyfakku/Filters.kt | 58 +- .../extension/en/spyfakku/SpyFakku.kt | 266 +- .../extension/en/stonescape/StoneScape.kt | 13 +- .../en/sunshinebutterflyscans/Dto.kt | 64 +- .../SunshineButterflyScans.kt | 277 +- .../extension/en/supermega/Supermega.kt | 71 +- .../extension/en/suryascans/GenzToons.kt | 26 +- .../extension/en/swordscomic/SwordsComic.kt | 48 +- .../tachiyomi/extension/en/taadd/Filters.kt | 151 +- .../tachiyomi/extension/en/taadd/Taadd.kt | 386 +- .../extension/en/tapastic/Tapastic.kt | 562 +-- .../extension/en/tappytoonnet/Tappytoonnet.kt | 2 + .../extension/en/tcbscans/TCBScans.kt | 133 +- .../extension/en/tecnoscans/TecnoScans.kt | 34 +- .../tachiyomi/extension/en/templescan/Dto.kt | 11 +- .../extension/en/templescan/Filters.kt | 60 +- .../extension/en/templescan/TempleScan.kt | 267 +- .../extension/en/theblank/TheBlank.kt | 17 +- .../en/theduckwebcomics/TheDuckFilters.kt | 123 +- .../en/theduckwebcomics/TheDuckWebcomics.kt | 61 +- .../en/thepropertyofhate/ThePropertyOfHate.kt | 82 +- .../extension/en/todaymanga/TodayManga.kt | 439 +- .../tachiyomi/extension/en/toonily/Toonily.kt | 74 +- .../tachiyomi/extension/en/toonizy/Toonizy.kt | 13 +- .../extension/en/topmanhua/TopManhua.kt | 8 +- .../extension/en/topmanhuanet/TopManhuaNet.kt | 11 +- .../en/topreadmanhwa/TopReadManhwa.kt | 48 +- .../tachiyomi/extension/en/tsumino/Tsumino.kt | 196 +- .../en/tsumino/TsuminoUrlActivity.kt | 11 +- .../extension/en/tsumino/TsuminoUtils.kt | 64 +- .../tachiyomi/extension/en/utoon/Utoon.kt | 18 +- .../extension/en/varnascan/VarnaScan.kt | 11 +- .../extension/en/vgperson/Vgperson.kt | 130 +- .../extension/en/vizshonenjump/Viz.kt | 298 +- .../extension/en/vizshonenjump/VizFactory.kt | 9 +- .../en/vizshonenjump/VizImageInterceptor.kt | 90 +- .../en/vizshonenjump/VizUrlActivity.kt | 44 +- .../en/vortexscansfree/VortexScansFree.kt | 11 +- .../tachiyomi/extension/en/voyceme/VoyceMe.kt | 233 +- .../extension/en/voyceme/VoyceMeDto.kt | 58 +- .../extension/en/voyceme/VoyceMeQueries.kt | 54 +- .../extension/en/vyvymanga/VyvyManga.kt | 104 +- .../en/vyvymanga/VyvyMangaFilters.kt | 117 +- .../extension/en/vyvymangaorg/VyvyMangaOrg.kt | 11 +- .../extension/en/warforrayuba/WarForRayuba.kt | 149 +- .../extension/en/webcomics/Webcomics.kt | 170 +- .../extension/en/webnovel/WebNovel.kt | 212 +- .../extension/en/webnovel/WebNovelDto.kt | 59 +- .../extension/en/webnovel/WebNovelFilter.kt | 113 +- .../extension/en/weebcentral/Filters.kt | 236 +- .../extension/en/weebcentral/WeebCentral.kt | 235 +- .../en/xcalibrscans/AntiScrapInterceptor.kt | 31 +- .../extension/en/xcalibrscans/xCaliBRScans.kt | 36 +- .../tachiyomi/extension/en/xmanhwa/Xmanhwa.kt | 11 +- .../extension/en/xoxocomics/XoxoComics.kt | 77 +- .../extension/en/yakshascans/YakshaScans.kt | 36 +- .../tachiyomi/extension/en/yaoihub/Yaoihub.kt | 9 +- .../extension/en/ydcomics/YDComics.kt | 33 +- .../tachiyomi/extension/en/zahard/Zahard.kt | 32 +- .../en/zandynofansub/ZandynoFansub.kt | 1 + .../extension/en/zeroscans/ZeroScans.kt | 226 +- .../extension/en/zeroscans/ZeroScansDto.kt | 9 +- .../extension/en/zeroscans/ZeroScansHelper.kt | 87 +- .../extension/en/zinchanmanga/ZinChanManga.kt | 24 +- .../en/zinchanmangacom/ZinChanMangaCom.kt | 24 +- .../extension/en/zinmanga/Zinmanga.kt | 1 - .../extension/en/zinmangacom/ZinMangaCom.kt | 11 +- .../extension/en/zinmangams/ZinmangaMs.kt | 11 +- .../extension/en/zinmanganet/ZinmangaNet.kt | 13 +- .../extension/es/aiyumanga/AiYuManhua.kt | 19 +- .../tachiyomi/extension/es/akaya/Akaya.kt | 222 +- .../extension/es/akaya/AkayaFilters.kt | 85 +- .../extension/es/apollcomics/ApollComics.kt | 13 +- .../extension/es/asialotus/AsiaLotus.kt | 19 +- .../extension/es/barmanga/BarManga.kt | 13 +- .../es/begatranslation/BegaTranslation.kt | 24 +- .../bokugentranslation/BokugenTranslation.kt | 78 +- .../extension/es/brakeout/Brakeout.kt | 103 +- .../extension/es/brakeout/BrakeoutDto.kt | 11 +- .../extension/es/bymichiscan/BymichiScan.kt | 21 +- .../es/carteldemanhwas/CarteldeManhwas.kt | 22 +- .../es/catharsisfantasy/CatharsisFantasy.kt | 72 +- .../es/catharsisworld/CatharsisWorld.kt | 53 +- .../es/celestialmoon/CelestialMoon.kt | 23 +- .../es/cerberusseries/CerberusSeries.kt | 11 +- .../tachiyomi/extension/es/chochox/Chochox.kt | 1 - .../tachiyomi/extension/es/daprob/DapRob.kt | 13 +- .../extension/es/darknebulus/DarkNebulus.kt | 21 +- .../es/darkroomfansub/DarkRoomFansub.kt | 87 +- .../es/datgarscanlation/DatGarScanlation.kt | 19 +- .../extension/es/doujinhentai/DoujinHentai.kt | 120 +- .../extension/es/doujinshell/DoujinsHell.kt | 23 +- .../extension/es/doujinslat/DoujinsLat.kt | 63 +- .../DragonTranslationNet.kt | 55 +- .../extension/es/dtupscan/DtupScan.kt | 21 +- .../extension/es/emperorscan/EmperorScan.kt | 16 +- .../extension/es/esmi2manga/EsMi2Manga.kt | 25 +- .../tachiyomi/extension/es/foyscan/FoyScan.kt | 13 +- .../es/gistamishouse/GistamisHouse.kt | 172 +- .../es/gremorymangas/GremoryMangas.kt | 13 +- .../es/hadesnofansub/HadesNoFansub.kt | 13 +- .../extension/es/haremscann/HaremDeKira.kt | 21 +- .../extension/es/heavenmanga/HeavenManga.kt | 426 +- .../extension/es/hentaimode/HentaiMode.kt | 141 +- .../es/hentaimode/HentaiModeUrlActivity.kt | 12 +- .../extension/es/herenscan/HerenScan.kt | 13 +- .../tachiyomi/extension/es/ikifeng/Ikifeng.kt | 4 +- .../extension/es/ikigaimangas/IkigaiMangas.kt | 240 +- .../es/ikigaimangas/IkigaiMangasDto.kt | 70 +- .../es/ikigaimangas/IkigaiMangasFilters.kt | 40 +- .../extension/es/ikuhentai/Ikuhentai.kt | 234 +- .../extension/es/inarimanga/VisorInari.kt | 21 +- .../extension/es/infrafandub/InfraFandub.kt | 21 +- .../tachiyomi/extension/es/inmanga/InManga.kt | 140 +- .../es/inmoralnofansub/InmoralNoFansub.kt | 21 +- .../extension/es/inmortalscan/InmortalScan.kt | 13 +- .../extension/es/jeazscans/JeazScans.kt | 21 +- .../es/kingsofdarkness/KingsOfDarkness.kt | 42 +- .../knightnoscanlation/KnightNoScanlation.kt | 21 +- .../es/koinoboriscan/KoinoboriScan.kt | 123 +- .../es/koinoboriscan/KoinoboriScanDto.kt | 43 +- .../extension/es/ladroncorps/LadronCorps.kt | 140 +- .../es/ladroncorps/LadronCorpsDto.kt | 24 +- .../es/ladroncorps/LadronCorpsUrlActivity.kt | 12 +- .../es/lazonadellirio/LaZonadelLirio.kt | 13 +- .../extension/es/lectorjpg/LectorJpg.kt | 13 +- .../es/lectormangalat/LectorMangaLat.kt | 21 +- .../extension/es/lectortmo/LectorTmo.kt | 612 +-- .../es/lectortmo/LectorTmoFactory.kt | 87 +- .../es/lectortmo/LectorTmoFilters.kt | 24 +- .../es/lectortmo/LectorTmoUrlActivity.kt | 11 +- .../extension/es/leercapitulo/LeerCapitulo.kt | 182 +- .../es/leercapitulo/LeerCapituloFilters.kt | 223 +- .../es/leercomicsonline/LeerComicsOnline.kt | 145 +- .../extension/es/leermanga/LeerManga.kt | 71 +- .../es/leermangasxyz/LeerMangasXYZ.kt | 105 +- .../es/legendscanlations/LegendScanlations.kt | 13 +- .../es/legendsnofansub/LegnMangas.kt | 21 +- .../extension/es/lmtoonline/Jobsibe.kt | 22 +- .../extension/es/mangacrab/MangaCrab.kt | 17 +- .../extension/es/mangafenix/MMFenix.kt | 22 +- .../extension/es/mangaland/Mangaland.kt | 21 +- .../extension/es/mangalatino/MangaLatino.kt | 92 +- .../es/mangalatino/MangaLatinoFilters.kt | 113 +- .../extension/es/mangamx/MangaOni.kt | 414 +- .../extension/es/mangashiina/MangaMukai.kt | 13 +- .../extension/es/mangasin/MangasIn.kt | 146 +- .../extension/es/mangasin/MangasInDto.kt | 5 +- .../es/mangasnosekai/MangasNoSekai.kt | 195 +- .../es/mangasnosekai/MangasNoSekaiDto.kt | 19 +- .../extension/es/mangaxico/Mangaxico.kt | 13 +- .../extension/es/manhuaonline/SamuraiScan.kt | 21 +- .../extension/es/manhwalatino/ManhwaLatino.kt | 72 +- .../extension/es/manhwases/ManhwasEs.kt | 13 +- .../extension/es/manhwasnet/ManhwasNet.kt | 99 +- .../es/manhwasnet/ManhwasNetFilters.kt | 156 +- .../extension/es/manhwaweb/ManhwaWeb.kt | 81 +- .../extension/es/manhwaweb/ManhwaWebDto.kt | 73 +- .../es/manhwaweb/ManhwaWebFilters.kt | 178 +- .../extension/es/mantrazscan/MantrazScan.kt | 21 +- .../tachiyomi/extension/es/marmota/Marmota.kt | 13 +- .../tachiyomi/extension/es/mhscans/MHScans.kt | 21 +- .../extension/es/mundomanhwa/MundoManhwa.kt | 13 +- .../tachiyomi/extension/es/nartag/Nartag.kt | 19 +- .../extension/es/nekoscans/NekoScans.kt | 19 +- .../NoblesseTranslations.kt | 29 +- .../extension/es/novatoscans/NovatoScans.kt | 19 +- .../es/olympusscanlation/OlympusScanlation.kt | 271 +- .../olympusscanlation/OlympusScanlationDto.kt | 79 +- .../es/plottwistnofansub/PlotTwistNoFansub.kt | 211 +- .../es/princediciones/PrinceEdiciones.kt | 13 +- .../es/ragnaroknochikara/RagnarokNoChikara.kt | 21 +- .../ragnarokscanlation/RagnarokScanlation.kt | 13 +- .../extension/es/ravenmanga/RavenManga.kt | 101 +- .../extension/es/richtoscan/RichtoScan.kt | 21 +- .../extension/es/ryujinmanga/RyujinManga.kt | 13 +- .../extension/es/sapphirescan/SapphireScan.kt | 26 +- .../es/scambertraslator/ScamberTraslator.kt | 13 +- .../es/senpaiediciones/SenpaiEdiciones.kt | 13 +- .../extension/es/senshimanga/SenshiManga.kt | 11 +- .../extension/es/shadowmangas/ShadowMangas.kt | 13 +- .../extension/es/skymangas/SkyMangas.kt | 37 +- .../extension/es/stickhorse/StickHorse.kt | 13 +- .../extension/es/taikutsu/Taikutsu.kt | 11 +- .../extension/es/taurusfansub/TaurusFansub.kt | 45 +- .../es/tecnoprojects/TecnoProjects.kt | 13 +- .../es/templescanesp/TempleScanEsp.kt | 1 - .../extension/es/tenkaiscan/TenkaiScan.kt | 306 +- .../es/territoriolealtad/TerritorioLealtad.kt | 21 +- .../extension/es/tmohentai/TMOHentai.kt | 406 +- .../es/tmohentai/TMOHentaiUrlActivity.kt | 11 +- .../extension/es/tmomanga/TMOManga.kt | 90 +- .../extension/es/tmomanga/TMOMangaFilters.kt | 113 +- .../extension/es/tojimangas/Tojimangas.kt | 222 +- .../es/topcomicporno/TopComicPorno.kt | 13 +- .../TraduccionesMoonlight.kt | 11 +- .../extension/es/tresdaosscan/TresDaosScan.kt | 21 +- .../extension/es/tumangasnet/TuMangasNet.kt | 107 +- .../es/tumangasnet/TuMangasNetFilters.kt | 113 +- .../es/uchuujinprojects/UchuujinProjects.kt | 21 +- .../extension/es/vcpvmp/VCPVMPFactory.kt | 11 +- .../extension/es/vermanhwas/VerManhwas.kt | 17 +- .../extension/es/yaoimanga/YaoiManga.kt | 20 +- .../tachiyomi/extension/es/zevep/Zevep.kt | 13 +- .../extension/fr/animesama/AnimeSama.kt | 157 +- .../extension/fr/aralosbd/AralosBD.kt | 63 +- .../extension/fr/epsilonscan/EpsilonScan.kt | 13 +- .../fr/etheralradiance/EtheralRadiance.kt | 21 +- .../extension/fr/flamescansfr/LegacyScans.kt | 122 +- .../fr/flamescansfr/LegacyScansFilter.kt | 205 +- .../fr/flamescansfr/LegacyScansUrlActivity.kt | 12 +- .../extension/fr/frdashscan/FRScan.kt | 23 +- .../extension/fr/furyosquad/FuryoSquad.kt | 130 +- .../fr/hentaiorigines/HentaiOrigines.kt | 17 +- .../fr/hentaiscantrad/HentaiScantrad.kt | 3 +- .../fr/inovascanmanga/InovaScanManga.kt | 13 +- .../extension/fr/japanread/BentoManga.kt | 443 +- .../tachiyomi/extension/fr/japscan/Japscan.kt | 222 +- .../extension/fr/lelscanvf/LelscanVF.kt | 1 - .../fr/lunarscanshentai/PornhwaScans.kt | 13 +- .../extension/fr/mangahubfr/MangaHubFr.kt | 3 +- .../extension/fr/mangakawaii/MangaKawaii.kt | 168 +- .../extension/fr/mangascan/MangaScan.kt | 24 +- .../fr/mangasoriginesfr/MangasOriginesFr.kt | 13 +- src/fr/mangasscans/src/MangasScans.kt | 3 +- .../extension/fr/pantheonscan/PantheonScan.kt | 3 +- .../extension/fr/perfscan/PerfScan.kt | 9 +- .../extension/fr/phenixscans/PhenixScans.kt | 16 +- .../fr/poseidonscans/PoseidonScans.kt | 13 +- .../extension/fr/reaperscans/ReaperScans.kt | 11 +- .../extension/fr/scanmanga/ScanManga.kt | 158 +- .../fr/scantradunion/ScantradUnion.kt | 56 +- .../tachiyomi/extension/fr/scanvf/ScanVF.kt | 31 +- .../extension/fr/scanvforg/ScanVF.kt | 143 +- .../fr/scanvforg/ScanVFUrlActivity.kt | 11 +- src/fr/softepsilonscan/src/SoftEpsilonScan.kt | 21 +- .../extension/fr/sushiscan/SushiScan.kt | 65 +- .../extension/fr/sushiscanfr/SushiScanFR.kt | 23 +- .../tachiyomi/extension/fr/toonfr/ToonFr.kt | 13 +- src/fr/yaoiscan/src/YaoiScan.kt | 15 +- .../extension/id/ainzscansid/AinzScansID.kt | 1 - .../extension/id/alceascan/Alceascan.kt | 9 +- .../extension/id/bacakomik/BacaKomik.kt | 402 +- .../extension/id/birdtoon/BirdToon.kt | 11 +- .../tachiyomi/extension/id/comic21/Comic21.kt | 32 +- .../extension/id/comicaso/Comicaso.kt | 57 +- .../tachiyomi/extension/id/comicfx/ComicFx.kt | 327 +- .../extension/id/comicsekai/Comicsekai.kt | 24 +- .../id/cosmicscansid/CosmicScansID.kt | 36 +- .../extension/id/crotpedia/CrotPedia.kt | 1 - .../extension/id/dojingnet/DojingNet.kt | 9 +- .../extension/id/doujindesu/DoujinDesu.kt | 665 +-- .../extension/id/doujinku/Doujinku.kt | 26 +- .../tachiyomi/extension/id/futari/Futari.kt | 13 +- .../tachiyomi/extension/id/hwago/Hwago.kt | 13 +- .../extension/id/ichiromanga/IchiroManga.kt | 9 +- .../tachiyomi/extension/id/indo18h/Indo18h.kt | 13 +- .../extension/id/izanamiscans/IzanamiScans.kt | 21 +- .../extension/id/kanzenin/Kanzenin.kt | 13 +- .../tachiyomi/extension/id/kiryuu/Kiryuu.kt | 43 +- .../extension/id/kishisan/Kishisan.kt | 8 +- .../extension/id/klikmanga/KlikManga.kt | 13 +- .../extension/id/klmanhua/KLManhua.kt | 1 - .../extension/id/kofiscans/KofiScans.kt | 21 +- .../tachiyomi/extension/id/komikav/Apkomik.kt | 21 +- .../extension/id/komikcast/KomikCast.kt | 283 +- .../extension/id/komikgan/KomikGan.kt | 1 - .../tachiyomi/extension/id/komikgo/KomikGo.kt | 13 +- .../tachiyomi/extension/id/komikid/Komikid.kt | 13 +- .../extension/id/komikindo/Komikindo.kt | 52 +- .../extension/id/komikindoco/KomikindoCo.kt | 11 +- .../extension/id/komikindoid/KomikIndoID.kt | 481 ++- .../id/komikindoinfo/KomikIndoInfo.kt | 1 - .../extension/id/komiklovers/KomikLovers.kt | 13 +- .../extension/id/komikmama/KomikMama.kt | 15 +- .../extension/id/komikpix/KomikPix.kt | 23 +- .../extension/id/komikplay/KomikPlay.kt | 31 +- .../extension/id/komikrealm/KomikRealm.kt | 101 +- .../extension/id/komiksan/Komiksan.kt | 15 +- .../extension/id/komiksay/KomikSay.kt | 19 +- .../extension/id/komikstation/KomikStation.kt | 23 +- .../extension/id/komiktap/Komiktap.kt | 70 +- .../tachiyomi/extension/id/komiku/Komiku.kt | 343 +- .../extension/id/komikuzan/Komikuzan.kt | 13 +- .../tachiyomi/extension/id/kumapoi/KumaPoi.kt | 8 +- .../extension/id/lianscans/LianScans.kt | 9 +- .../extension/id/lumoskomik/LumosKomik.kt | 13 +- .../tachiyomi/extension/id/magerin/Magerin.kt | 8 +- .../extension/id/mangacan/MangaCan.kt | 88 +- .../tachiyomi/extension/id/mangaku/Mangaku.kt | 167 +- .../extension/id/mangakyo/Mangakyo.kt | 24 +- .../extension/id/mangalay/Mangalay.kt | 36 +- .../extension/id/mangasusu/Mangasusu.kt | 56 +- .../tachiyomi/extension/id/mangatale/Ikiru.kt | 44 +- .../extension/id/mangayaro/Mangayaro.kt | 9 +- .../tachiyomi/extension/id/mangayu/MangaYu.kt | 21 +- .../extension/id/mangkomik/SirenKomik.kt | 32 +- .../extension/id/manhwadesu/ManhwaDesu.kt | 24 +- .../extension/id/manhwaindo/ManhwaIndo.kt | 15 +- .../id/manhwalandmom/ManhwaLandMom.kt | 22 +- .../extension/id/manhwalistid/ManhwaList.kt | 19 +- .../id/manhwalistorg/ManhwalistOrg.kt | 15 +- .../extension/id/masterkomik/TenshiId.kt | 24 +- .../extension/id/melokomik/MELOKOMIK.kt | 1 - .../tachiyomi/extension/id/mgkomik/MGKomik.kt | 111 +- .../extension/id/mikoroku/MikoRoku.kt | 67 +- .../extension/id/mirrordesu/MirrorDesu.kt | 49 +- .../extension/id/monzeekomik/MonzeeKomik.kt | 11 +- .../tachiyomi/extension/id/natsu/Natsu.kt | 16 +- .../extension/id/ngamenkomik/NgamenKomik.kt | 73 +- .../tachiyomi/extension/id/ngomik/Ngomik.kt | 24 +- .../tachiyomi/extension/id/nimemob/Nimemob.kt | 8 +- .../tachiyomi/extension/id/noromax/Noromax.kt | 1 - .../id/onepieceberwarna/OnePieceBerwarna.kt | 43 +- .../extension/id/otascans/OtaScans.kt | 24 +- .../extension/id/otsugami/Otsugami.kt | 9 +- .../extension/id/pojokmanga/PojokManga.kt | 47 +- .../extension/id/pornhwa18/Pornhwa18.kt | 2 + .../extension/id/sekaikomik/Sekaikomik.kt | 14 +- .../extension/id/sektedoujin/SekteDoujin.kt | 17 +- .../extension/id/shinigami/Shinigami.kt | 136 +- .../extension/id/shirakami/Shirakami.kt | 13 +- .../extension/id/shiyurasub/ShiyuraSub.kt | 8 +- .../extension/id/siimanga/Siikomik.kt | 19 +- .../extension/id/siimanga2/Siimanga.kt | 18 +- .../extension/id/sobatmanku/SobatManKu.kt | 13 +- .../extension/id/soulscans/SoulScans.kt | 59 +- .../extension/id/tooncubus/Tooncubus.kt | 6 +- .../extension/id/westmanga/WestManga.kt | 66 +- .../extension/id/yubikiri/Yubikiri.kt | 16 +- .../extension/id/yuramanga/YuraManga.kt | 39 +- .../extension/it/animegdrclub/AnimeGDRClub.kt | 165 +- .../extension/it/digitalteam/DigitalTeam.kt | 157 +- .../extension/it/hastateam/HastaTeam.kt | 52 +- .../it/hentaiarchive/HentaiArchive.kt | 136 +- .../it/hentaifantasy/HentaiFantasy.kt | 208 +- .../it/novelleleggere/NovelleLeggere.kt | 76 +- .../it/tuttoanimemanga/TuttoAnimeManga.kt | 9 +- .../it/walpurgisscan/WalpurgisScan.kt | 3 +- .../extension/it/zeurelscan/ZeurelScan.kt | 94 +- .../extension/ja/comicdays/ComicDays.kt | 44 +- .../extension/ja/comicfuz/ComicFuz.kt | 226 +- .../tachiyomi/extension/ja/comicfuz/Dto.kt | 49 +- .../extension/ja/comicfuz/Filters.kt | 131 +- .../extension/ja/comicfuz/ImageInterceptor.kt | 40 +- .../extension/ja/comicfuz/PayloadDto.kt | 4 +- .../extension/ja/comicgardo/ComicGardo.kt | 42 +- .../extension/ja/comicmeteor/ComicMeteor.kt | 114 +- .../extension/ja/comicnewtype/ComicNewtype.kt | 115 +- .../ja/comicnewtype/ComicNewtypeFilters.kt | 46 +- .../ja/comicnewtype/ComicNewtypeUtils.kt | 9 +- .../extension/ja/comiplex/Comiplex.kt | 52 +- .../ja/corocoroonline/CorocoroOnline.kt | 60 +- .../tachiyomi/extension/ja/ganma/Ganma.kt | 76 +- .../tachiyomi/extension/ja/ganma/GanmaDto.kt | 72 +- .../ja/gaugaumonsterplus/GaugauMonsterPlus.kt | 433 +- .../extension/ja/hachiraw/Hachiraw.kt | 172 +- .../extension/ja/hachiraw/HachirawFilters.kt | 160 +- .../ja/hachiraw/HachirawUrlActivity.kt | 11 +- .../IdolGravureprincessDate.kt | 182 +- .../extension/ja/kadocomi/KadoComi.kt | 177 +- .../extension/ja/kisslove/KissLove.kt | 68 +- .../extension/ja/kuragebunch/KurageBunch.kt | 48 +- .../ja/magazinepocket/MagazinePocket.kt | 44 +- .../tachiyomi/extension/ja/magcomi/MagComi.kt | 42 +- .../extension/ja/manga9co/MangaRaw.kt | 51 +- .../ja/manga9co/MangaRawConstants.kt | 36 +- .../extension/ja/mangacross/MangaCross.kt | 41 +- .../extension/ja/mangacross/MangaCrossDto.kt | 110 +- .../extension/ja/mangagun/MangaGun.kt | 68 +- .../extension/ja/mangajikan/Mangajikan.kt | 13 +- .../extension/ja/mangakoinu/MangaKoinu.kt | 13 +- .../extension/ja/mangakuro/MangaKuro.kt | 78 +- .../extension/ja/mangamate/MangaMate.kt | 16 +- .../extension/ja/mangaraworg/MangaRawOrg.kt | 47 +- .../extension/ja/mangarawplus/MangaRawPlus.kt | 25 +- .../extension/ja/mangatoshokanz/Crypto.kt | 14 +- .../ja/mangatoshokanz/MangaToshokanZ.kt | 289 +- .../extension/ja/micmicidol/MicMicIdol.kt | 110 +- .../extension/ja/nicomanga/Nicomanga.kt | 62 +- .../ja/nicovideoseiga/ApiResponse.kt | 13 +- .../ja/nicovideoseiga/NicovideoSeiga.kt | 123 +- .../ja/nikkangecchan/Nikkangecchan.kt | 57 +- .../extension/ja/ohtawebcomic/OhtaWebComic.kt | 166 +- .../extension/ja/pixivcomic/PixivComic.kt | 247 +- .../extension/ja/pixivcomic/PixivComicUtil.kt | 38 +- .../ja/pixivcomic/ShuffledImageInterceptor.kt | 44 +- .../tachiyomi/extension/ja/raw18/Raw18.kt | 50 +- .../extension/ja/rawdevartart/Rawdevartart.kt | 103 +- .../ja/rawdevartart/RawdevartartFilters.kt | 170 +- .../ja/rawdevartart/RawdevartartUtils.kt | 93 +- .../tachiyomi/extension/ja/rawinu/RawINU.kt | 32 +- .../tachiyomi/extension/ja/rawkuma/Rawkuma.kt | 9 +- .../extension/ja/rawlh/WeLoveManga.kt | 33 +- .../extension/ja/rawmanga/MangaRAW.kt | 11 +- .../extension/ja/rawotaku/RawOtaku.kt | 235 +- .../extension/ja/rawotaku/RawOtakuFilters.kt | 98 +- .../tachiyomi/extension/ja/rawxz/RawXZ.kt | 13 +- .../extension/ja/senmanga/SenManga.kt | 351 +- .../ja/shonenjumpplus/ShonenJumpPlus.kt | 33 +- .../ja/sundaywebevery/SundayWebEvery.kt | 44 +- .../tachiyomi/extension/ja/syosetu/SyoSetu.kt | 36 +- .../ja/tonarinoyoungjump/TonariNoYoungJump.kt | 51 +- .../tachiyomi/extension/ja/twi4/Twi4.kt | 128 +- .../extension/ja/twi4/Twi4Activity.kt | 11 +- .../ja/welovemangaone/WeLoveMangaOne.kt | 61 +- .../ja/yanmaga/ParseInsertAdjacentHTML.kt | 34 +- .../tachiyomi/extension/ja/yanmaga/Yanmaga.kt | 177 +- .../extension/ja/yanmaga/YanmagaComics.kt | 78 +- .../extension/ja/yanmaga/YanmagaFactory.kt | 9 +- .../extension/ja/yanmaga/YanmagaGravures.kt | 47 +- .../extension/ko/blacktoon/BlackToon.kt | 205 +- .../tachiyomi/extension/ko/blacktoon/Data.kt | 109 +- .../tachiyomi/extension/ko/blacktoon/Dto.kt | 68 +- .../extension/ko/blacktoon/Filters.kt | 65 +- .../extension/ko/manhwaraw/ManhwaRaw.kt | 1 - .../extension/ko/manytoonclub/ManyToonClub.kt | 1 - .../extension/ko/navercomic/NaverComic.kt | 25 +- .../extension/ko/navercomic/NaverComicBase.kt | 111 +- .../ko/navercomic/NaverComicFactory.kt | 11 +- .../extension/ko/newtoki/DomainNumber.kt | 48 +- .../extension/ko/newtoki/ManaToki.kt | 197 +- .../tachiyomi/extension/ko/newtoki/NewToki.kt | 135 +- .../extension/ko/newtoki/NewTokiWebtoon.kt | 157 +- .../extension/ko/newtoki/Preferences.kt | 66 +- .../tachiyomi/extension/ko/newtoki/Strings.kt | 90 +- .../tachiyomi/extension/ko/toon11/Toon11.kt | 242 +- .../tachiyomi/extension/ko/toonkor/Toonkor.kt | 134 +- .../extension/ko/wolfdotcom/Filters.kt | 25 +- .../tachiyomi/extension/ko/wolfdotcom/Wolf.kt | 255 +- .../extension/ko/wolfdotcom/WolfFactory.kt | 23 +- .../extension/pl/mangahona/MangaHoNa.kt | 13 +- .../extension/pt/akimanga/Akimanga.kt | 22 +- .../extension/pt/algodaodoce/AlgodaoDoce.kt | 13 +- .../pt/alonescanlator/AloneScanlator.kt | 13 +- .../tachiyomi/extension/pt/amuy/Amuy.kt | 22 +- .../extension/pt/animexnovel/AnimeXNovel.kt | 68 +- .../extension/pt/apecomics/ApeComics.kt | 13 +- .../extension/pt/apenasumafa/ApenasUmaFa.kt | 46 +- .../extension/pt/arcticscan/ArcticScan.kt | 13 +- .../extension/pt/argoscomics/ArgosComics.kt | 84 +- .../pt/argoscomicscombr/ArgosComicsComBr.kt | 13 +- .../extension/pt/argoshentai/ArgosHentai.kt | 21 +- .../extension/pt/argosscan/ArgosScan.kt | 109 +- .../pt/argosscan/ArgosScanQueries.kt | 119 +- .../extension/pt/arthurscan/ArthurScan.kt | 48 +- .../extension/pt/atemporal/Atemporal.kt | 21 +- .../tachiyomi/extension/pt/bakai/Bakai.kt | 174 +- .../extension/pt/bakai/BakaiUrlActivity.kt | 12 +- .../pt/blackoutcomics/BlackoutComics.kt | 247 +- .../BlackoutComicsUrlActivity.kt | 12 +- .../extension/pt/blackscans/BlackScans.kt | 78 +- .../pt/blackscans/BlackScansUrlActivity.kt | 12 +- .../pt/borutoexplorer/BorutoExplorer.kt | 22 +- .../extension/pt/brasilhentai/BrasilHentai.kt | 137 +- .../brasilhentai/BrasilHentaiUrlActivity.kt | 12 +- .../extension/pt/brmangastop/BRMangasTop.kt | 21 +- .../tachiyomi/extension/pt/bruttal/Bruttal.kt | 161 +- .../extension/pt/bruttal/BruttalDto.kt | 48 +- .../extension/pt/cafecomyaoi/CafeComYaoi.kt | 21 +- .../extension/pt/cerisescans/CeriseScan.kt | 19 +- .../extension/pt/covenscan/CovenScan.kt | 22 +- .../pt/crystalcomics/CrystalComics.kt | 19 +- .../pt/dianxiatraducoes/DianxiaTraducoes.kt | 13 +- .../extension/pt/diskusscan/DiskusScan.kt | 161 +- .../extension/pt/dreamscan/DreamScan.kt | 13 +- .../extension/pt/euphoriascan/EuphoriaScan.kt | 13 +- .../pt/exhentainetbr/ExHentaiNetBR.kt | 138 +- .../exhentainetbr/ExHentaiNetBRUrlActivity.kt | 12 +- .../extension/pt/fayscans/FayScans.kt | 22 +- .../extension/pt/fenixmanhwas/FenixManhwas.kt | 11 +- .../extension/pt/fenixproject/FenixProject.kt | 21 +- .../extension/pt/fleurblanche/FleurBlanche.kt | 29 +- .../pt/flowermanga/FlowerMangaDotNet.kt | 22 +- .../pt/flowermangadotcom/FlowerMangaDotCom.kt | 21 +- .../extension/pt/franxxmangas/FranxxMangas.kt | 22 +- .../pt/galaxscanlator/GalaxScanlator.kt | 19 +- .../galinhasamuraiscan/GalinhaSamuraiScan.kt | 13 +- .../extension/pt/ghostscan/GhostScan.kt | 22 +- .../extension/pt/gooffansub/GoofFansub.kt | 22 +- .../extension/pt/hentaiseason/HentaiSeason.kt | 20 +- .../extension/pt/hentaiteca/HentaiTeca.kt | 25 +- .../extension/pt/hentaitokyo/HentaiTokyo.kt | 20 +- .../extension/pt/hianatoscan/HianatoScan.kt | 13 +- .../extension/pt/hikariganai/HikariGaNai.kt | 19 +- .../extension/pt/hikariscan/HikariScan.kt | 21 +- .../extension/pt/hipercool/Hipercool.kt | 9 +- .../pt/hotcabaretscan/HotCabaretScan.kt | 24 +- .../tachiyomi/extension/pt/hqnow/HQNow.kt | 214 +- .../extension/pt/huntersscans/HuntersScans.kt | 23 +- .../extension/pt/illusionscan/IllusionScan.kt | 22 +- .../extension/pt/imaginescan/ImagineScan.kt | 22 +- .../imperiodabritannia/ImperioDaBritannia.kt | 22 +- .../extension/pt/imperioscans/ImperioScans.kt | 21 +- .../extension/pt/infinyxscan/InfinyxScan.kt | 13 +- .../pt/irisscanlator/IrisScanlator.kt | 13 +- .../pt/kakuseiproject/KakuseiProject.kt | 22 +- .../pt/kamisamaexplorer/KamiSamaExplorer.kt | 22 +- .../pt/leitordemanga/LeitorDeManga.kt | 21 +- .../tachiyomi/extension/pt/ler999/Ler999.kt | 8 +- .../extension/pt/lerhentai/LerHentai.kt | 18 +- .../extension/pt/lermangas/LerMangas.kt | 21 +- .../tachiyomi/extension/pt/leryaoi/LerYaoi.kt | 22 +- .../extension/pt/lichmangas/LichMangas.kt | 13 +- .../extension/pt/limboscan/LimboScan.kt | 22 +- .../limitedtimeproject/LimitedTimeProject.kt | 21 +- .../pt/linkstartscan/LinkStartScan.kt | 22 +- .../tachiyomi/extension/pt/lscans/LScans.kt | 19 +- .../extension/pt/lunarscan/LunarScan.kt | 21 +- .../extension/pt/mahouscan/MahouScan.kt | 21 +- .../extension/pt/maidscan/MaidScan.kt | 21 +- .../extension/pt/maidsecret/MaidSecret.kt | 21 +- .../tachiyomi/extension/pt/mangabr/MangaBR.kt | 19 +- .../extension/pt/mangakun/MangaKun.kt | 19 +- .../extension/pt/mangamania/MangaMania.kt | 13 +- .../extension/pt/manganinja/MangaNinja.kt | 22 +- .../extension/pt/mangaonline/MangaOnline.kt | 157 +- .../pt/mangaonlineblog/MangaOnlineBlog.kt | 21 +- .../extension/pt/mangaterra/MangaTerra.kt | 19 +- .../extension/pt/manhastro/Manhastro.kt | 70 +- .../extension/pt/minitwoscan/MiniTwoScan.kt | 22 +- .../pt/momonohanascan/MomoNoHanaScan.kt | 22 +- .../extension/pt/montetai/MonteTai.kt | 13 +- .../pt/moonloversscan/MoonLoversScan.kt | 22 +- .../pt/moonwitchscan/MoonWitchScan.kt | 21 +- .../extension/pt/mryaoifansub/MrYaoiFansub.kt | 29 +- .../pt/mugiwarasoficial/MugiwarasOficial.kt | 13 +- .../extension/pt/muitohentai/MuitoHentai.kt | 108 +- .../extension/pt/mundohentai/MundoHentai.kt | 172 +- .../extension/pt/mysticmoon/MysticMoon.kt | 13 +- .../extension/pt/ninjascan/NinjaScan.kt | 25 +- .../extension/pt/nirvanascan/NirvanaScan.kt | 21 +- .../pt/nocturnesummer/NocturneSummer.kt | 22 +- .../extension/pt/noindexscan/NoIndexScan.kt | 21 +- .../extension/pt/onepieceteca/OnePieceTeca.kt | 13 +- .../pt/origamiorpheans/OrigamiOrpheans.kt | 22 +- .../extension/pt/passamaoscan/PassaMaoScan.kt | 22 +- .../pt/pinkseaunicorn/PinkSeaUnicorn.kt | 24 +- .../extension/pt/pirulitorosa/PirulitoRosa.kt | 22 +- .../extension/pt/plumacomics/PlumaComics.kt | 16 +- .../extension/pt/portalyaoi/PortalYaoi.kt | 22 +- .../pt/prismascans/SeitaCelestial.kt | 34 +- .../extension/pt/pussytoons/PussyToons.kt | 13 +- .../pt/rainbowfairyscan/RainbowFairyScan.kt | 22 +- .../extension/pt/randomscan/LuraToon.kt | 159 +- .../pt/randomscan/LuraZipInterceptor.kt | 27 +- .../extension/pt/readmangas/ReadMangas.kt | 299 +- .../extension/pt/readmangas/ReadMangasDto.kt | 8 +- .../extension/pt/remangas/Remangas.kt | 16 +- .../extension/pt/riothentai/RiotHentai.kt | 13 +- .../extension/pt/saikaiscan/SaikaiScan.kt | 358 +- .../extension/pt/saikaiscan/SaikaiScanDto.kt | 60 +- .../pt/saikaiscan/SaikaiScanFilters.kt | 58 +- .../extension/pt/shindaiscan/ShindaiScan.kt | 19 +- .../extension/pt/silencescan/SilenceScan.kt | 22 +- .../extension/pt/sinensis/SinensisScan.kt | 19 +- .../extension/pt/slimeread/SlimeRead.kt | 25 +- .../extension/pt/ssreading/SSReading.kt | 25 +- .../extension/pt/sssscanlator/SSSScanlator.kt | 37 +- .../pt/starlightscan/StarlightScan.kt | 101 +- .../extension/pt/sussyscan/SussyToons.kt | 558 ++- .../extension/pt/sussyscan/SussyToonsDto.kt | 5 +- .../pt/sweettimescan/SweetTimeScan.kt | 26 +- .../tachiyomi/extension/pt/taiyo/Taiyo.kt | 337 +- .../extension/pt/taiyo/TaiyoUrlActivity.kt | 12 +- .../extension/pt/taiyo/dto/TaiyoDto.kt | 23 +- .../extension/pt/tankouhentai/TankouHentai.kt | 22 +- .../tachiyomi/extension/pt/taosect/TaoSect.kt | 448 +- .../extension/pt/taosect/TaoSectFilters.kt | 86 +- .../pt/taosect/TaoSectUrlActivity.kt | 12 +- .../extension/pt/tatakaescan/TatakaeScan.kt | 22 +- .../extension/pt/temakimangas/TemakiMangas.kt | 39 +- .../pt/traducoesdolipe/TraducoesDoLipe.kt | 64 +- .../extension/pt/tsukimangas/TsukiMangas.kt | 225 +- .../pt/tsukimangas/TsukiMangasFilters.kt | 840 ++-- .../pt/tsukimangas/TsukiMangasUrlActivity.kt | 12 +- .../pt/tsukimangas/dto/TsukiMangasDto.kt | 25 +- .../pt/tsundokutraducoes/TsundokuTraducoes.kt | 25 +- .../extension/pt/tyrantscans/TyrantScans.kt | 1 - .../pt/universohentai/UniversoHentai.kt | 84 +- .../extension/pt/valkyriescan/ValkyrieScan.kt | 22 +- .../extension/pt/vaposcans/VapoScans.kt | 138 +- .../extension/pt/vaposcans/VapoScansDto.kt | 4 +- .../pt/vaposcans/VapoScansUrlActivity.kt | 12 +- .../pt/wickedwitchscannovo/WickedWitchScan.kt | 8 +- .../extension/pt/winterscan/WinterScan.kt | 22 +- .../extension/pt/wintersun/WinterSun.kt | 13 +- .../pt/wonderlandscan/WonderlandScan.kt | 22 +- .../tachiyomi/extension/pt/xsscan/XsScan.kt | 21 +- .../tachiyomi/extension/pt/xxxyaoi/XXXYaoi.kt | 22 +- .../extension/pt/yanpfansub/YANPFansub.kt | 24 +- .../extension/pt/yaoicomics/YaoiComics.kt | 22 +- .../extension/pt/yaoifanclub/YaoiFanClub.kt | 130 +- .../extension/pt/yugenmangas/YugenMangas.kt | 82 +- .../pt/yugenmangas/YugenMangasDto.kt | 66 +- .../extension/pt/yuriverso/YuriVerso.kt | 22 +- .../pt/yushukemangas/YushukeMangas.kt | 276 +- .../yushukemangas/YushukeMangasUrlActivity.kt | 12 +- .../tachiyomi/extension/pt/zettahq/ZettaHQ.kt | 177 +- .../pt/zettahq/ZettaHQUrlActivity.kt | 12 +- .../tachiyomi/extension/ru/acomics/AComics.kt | 211 +- .../extension/ru/allhentai/AllHentai.kt | 515 +-- .../tachiyomi/extension/ru/comx/ComX.kt | 777 ++-- .../tachiyomi/extension/ru/desu/Desu.kt | 451 +- .../extension/ru/desu/DesuActivity.kt | 13 +- .../tachiyomi/extension/ru/henchan/HenChan.kt | 625 +-- .../extension/ru/hentailib/HentaiLib.kt | 26 +- .../extension/ru/mangabook/MangaBook.kt | 462 +- .../extension/ru/mangabuff/MangaBuff.kt | 389 +- .../ru/mangabuff/MangaBuffFilters.kt | 443 +- .../ru/mangabuff/MangaBuffUrlActivity.kt | 11 +- .../extension/ru/mangachan/MangaChan.kt | 262 +- .../extension/ru/mangaclub/MangaClub.kt | 323 +- .../extension/ru/mangahub/Mangahub.kt | 109 +- .../extension/ru/mangalib/MangaLib.kt | 26 +- .../extension/ru/mangamammy/MangaMammy.kt | 13 +- .../extension/ru/mangapoisk/MangaPoisk.kt | 300 +- .../extension/ru/mangazavr/Mangazavr.kt | 13 +- .../extension/ru/mintmanga/MintManga.kt | 316 +- .../extension/ru/multimanga/MultiManga.kt | 518 +-- .../extension/ru/nudemoon/Nudemoon.kt | 468 ++- .../extension/ru/readmanga/ReadManga.kt | 309 +- .../tachiyomi/extension/ru/rumix/RuMIX.kt | 60 +- .../extension/ru/seimanga/SeiManga.kt | 316 +- .../extension/ru/selfmanga/SelfManga.kt | 192 +- .../extension/ru/senkognito/Senkognito.kt | 23 +- .../extension/ru/unicomics/UniComics.kt | 335 +- .../ru/unicomics/UniComicsActivity.kt | 13 +- .../tachiyomi/extension/ru/usagi/Usagi.kt | 308 +- .../extension/ru/mangabook/YagamiProject.kt | 322 +- .../extension/ru/yaoichan/YaoiChan.kt | 274 +- .../tachiyomi/extension/ru/yaoilib/YaoiLib.kt | 26 +- .../th/cattranslator/CatTranslator.kt | 16 +- .../tachiyomi/extension/th/catzaa/Catzaa.kt | 13 +- .../extension/th/doodmanga/Doodmanga.kt | 55 +- .../th/doodmanga/ScrambledImageInterceptor.kt | 21 +- .../extension/th/doujinlc/DoujinLc.kt | 13 +- .../extension/th/doujinza/DoujinZa.kt | 13 +- .../extension/th/dragonmanga/DragonManga.kt | 13 +- .../extension/th/ecchidoujin/EcchiDoujin.kt | 15 +- .../extension/th/kumotran/KumoTran.kt | 13 +- .../extension/th/lamimanga/LamiManga.kt | 13 +- .../extension/th/makimaaaaa/Makimaaaaa.kt | 13 +- .../extension/th/manga168/Manga168.kt | 23 +- .../th/mangaisekaithai/MangaIsekaiThai.kt | 78 +- .../extension/th/mangakimi/MangaKimi.kt | 18 +- .../tachiyomi/extension/th/mangalc/MangaLc.kt | 13 +- .../extension/th/mangatitan/MangaTitan.kt | 13 +- .../extension/th/manhuabug/ManhuaBug.kt | 77 +- .../extension/th/manhuakey/ManhuaKey.kt | 67 +- .../extension/th/manhuathai/ManhuaThai.kt | 77 +- .../th/manhwabreakup/ManhwaBreakup.kt | 78 +- .../extension/th/mikudoujin/MikuDoujin.kt | 118 +- .../extension/th/nekopost/Nekopost.kt | 101 +- .../extension/th/nekopostco/SuperdoujinOrg.kt | 18 +- .../extension/th/niceoppai/Niceoppai.kt | 300 +- .../extension/th/ntrmanga/NTRManga.kt | 13 +- .../extension/th/popsmanga/PopsManga.kt | 13 +- .../extension/th/reapertrans/ReaperTrans.kt | 11 +- .../extension/th/sodsaime/Sodsaime.kt | 3 +- .../extension/th/tanukimanga/TanukiManga.kt | 13 +- .../extension/th/toomtammanga/ToomTamManga.kt | 13 +- .../extension/tr/adonisfansub/AdonisFansub.kt | 1 + .../extension/tr/adumanga/AduManga.kt | 11 +- .../extension/tr/afroditscans/AfroditScans.kt | 13 +- .../extension/tr/araznovel/ArazNovel.kt | 13 +- .../extension/tr/arcurafansub/ArcuraFansub.kt | 15 +- .../extension/tr/asemifansub/AsemiFansub.kt | 21 +- .../extension/tr/asurascanstr/AsuraScansTR.kt | 13 +- .../extension/tr/athenamanga/AthenaManga.kt | 21 +- .../extension/tr/atikrost/Atikrost.kt | 13 +- .../tachiyomi/extension/tr/ayatoon/Ayatoon.kt | 13 +- .../extension/tr/culturesubs/CultureSubs.kt | 21 +- .../extension/tr/deccalscans/DeccalScans.kt | 21 +- .../tr/diamondfansub/DiamondFansub.kt | 13 +- .../extension/tr/domalfansub/DomalFansub.kt | 25 +- .../tachiyomi/extension/tr/epikman/Epikman.kt | 38 +- .../extension/tr/evascans/EvaScans.kt | 13 +- .../extension/tr/gaiatoon/Gaiatoon.kt | 13 +- .../extension/tr/garciamanga/GarciaManga.kt | 13 +- .../extension/tr/golgebahcesi/GolgeBahcesi.kt | 16 +- .../extension/tr/hattorimanga/HattoriManga.kt | 255 +- .../hattorimanga/HattoriMangaUrlActivity.kt | 12 +- .../extension/tr/hayalistic/Hayalistic.kt | 13 +- .../extension/tr/hentaizm/HentaiZM.kt | 56 +- .../extension/tr/hoifansub/HoiFansub.kt | 13 +- .../tr/hyperionscans/HyperionScans.kt | 21 +- .../tr/imparatormanga/ImparatorManga.kt | 13 +- .../extension/tr/jellyring/Jellyring.kt | 26 +- .../extension/tr/jiangzaitoon/Jiangzaitoon.kt | 16 +- .../extension/tr/kabusmanga/KabusManga.kt | 13 +- .../tachiyomi/extension/tr/kedito/Kedito.kt | 21 +- .../extension/tr/koreliscans/KoreliScans.kt | 13 +- .../extension/tr/kuroimanga/KuroiManga.kt | 18 +- .../tr/laviniafansub/LaviniaFansub.kt | 13 +- .../extension/tr/lichsubs/LichSubs.kt | 21 +- .../extension/tr/lilyumfansub/LilyumFansub.kt | 13 +- .../extension/tr/lowerworld/LowerWorld.kt | 13 +- .../extension/tr/lunascans/LunaScans.kt | 13 +- .../extension/tr/mangabari/MangaBari.kt | 13 +- .../extension/tr/mangacim/Mangacim.kt | 13 +- .../extension/tr/mangadenizi/MangaDenizi.kt | 100 +- .../tr/mangaefendisi/MangaEfendisi.kt | 28 +- .../extension/tr/mangagezgini/MangaGezgini.kt | 16 +- .../extension/tr/mangakazani/MangaKazani.kt | 15 +- .../extension/tr/mangakings/MangaKings.kt | 35 +- .../extension/tr/mangakoleji/MangaKoleji.kt | 13 +- .../extension/tr/mangaokusana/MangaOkusana.kt | 13 +- .../extension/tr/mangaokutr/MangaOkuTr.kt | 24 +- .../extension/tr/mangaruhu/MangaRuhu.kt | 13 +- .../extension/tr/mangasehri/MangaSehri.kt | 13 +- .../tr/mangasehrinet/MangaSehriNet.kt | 13 +- .../extension/tr/mangaship/MangaShip.kt | 45 +- .../extension/tr/mangatilkisi/MangaTilkisi.kt | 21 +- .../tr/mangatr/DDoSGuardInterceptor.kt | 46 +- .../tachiyomi/extension/tr/mangatr/MangaTR.kt | 108 +- .../extension/tr/mangatrnet/MangaTRNet.kt | 11 +- .../extension/tr/mangawow/MangaWOW.kt | 13 +- .../tachiyomi/extension/tr/mangawt/MangaWT.kt | 13 +- .../extension/tr/meowsubs/MeowSubs.kt | 13 +- .../extension/tr/merlinscans/MerlinScans.kt | 13 +- .../tr/mikrokosmosfansub/MikrokosmosFansub.kt | 1 - .../tachiyomi/extension/tr/milasub/MilaSub.kt | 13 +- .../extension/tr/mindafansub/MindaFansub.kt | 13 +- .../tr/moondaisyscans/MoonDaisyScans.kt | 21 +- .../extension/tr/mugimanga/MugiManga.kt | 26 +- .../extension/tr/nabiscans/NabiScans.kt | 13 +- .../extension/tr/nirvanamanga/NirvanaManga.kt | 13 +- .../extension/tr/niverafansub/NiveraFansub.kt | 13 +- .../extension/tr/noxscans/NoxScans.kt | 64 +- .../extension/tr/opiatoon/Opiatoon.kt | 25 +- .../extension/tr/patimanga/PatiManga.kt | 13 +- .../tr/piedpiperfansub/PiedPiperFansub.kt | 13 +- .../tr/piedpiperfansubyy/PiedPiperFansubyy.kt | 13 +- .../extension/tr/pijamalikoi/PijamaliKoi.kt | 31 +- .../extension/tr/prunusscans/PrunusScans.kt | 19 +- .../extension/tr/ragnarscans/RagnarScans.kt | 13 +- .../tr/raindropfansub/RaindropFansub.kt | 30 +- .../tr/romantikmanga/RomantikManga.kt | 13 +- .../extension/tr/ruyamanga/RuyaManga.kt | 13 +- .../extension/tr/sadscans/Sadscans.kt | 86 +- .../tr/sadscans/SadscansUrlActivity.kt | 11 +- .../extension/tr/sarcasmscans/SarcasmScans.kt | 13 +- .../extension/tr/sereinscan/SereinScan.kt | 13 +- .../extension/tr/serimanga/SeriManga.kt | 92 +- .../extension/tr/shadowceviri/ShadowCeviri.kt | 16 +- .../extension/tr/shijiescans/ShijieScans.kt | 15 +- .../extension/tr/siyahmelek/Siyahmelek.kt | 13 +- .../extension/tr/strayfansub/StrayFansub.kt | 21 +- .../extension/tr/summertoon/SummerToon.kt | 21 +- .../extension/tr/tarotscans/TarotScans.kt | 3 +- .../extension/tr/tempestscans/TempestScans.kt | 11 +- .../extension/tr/titanmanga/TitanManga.kt | 11 +- .../extension/tr/tonizutoon/TonizuToon.kt | 21 +- .../tr/tortugaceviri/TortugaCeviri.kt | 13 +- .../tr/turkcemangaoku/TurkceMangaOku.kt | 13 +- .../extension/tr/uzaymanga/UzayManga.kt | 147 +- .../tr/uzaymanga/UzayMangaUrlActivity.kt | 12 +- .../extension/tr/webtoonhatti/WebtoonHatti.kt | 13 +- .../extension/tr/webtoontr/WebtoonTR.kt | 13 +- .../tachiyomi/extension/tr/yaoibar/Yaoibar.kt | 13 +- .../extension/tr/yaoiflix/YaoiFlix.kt | 21 +- .../extension/tr/yaoimangaoku/YaoiMangaOku.kt | 13 +- .../tachiyomi/extension/tr/yaoitr/YaoiTR.kt | 13 +- .../extension/tr/zenithscans/ZenithScans.kt | 13 +- .../extension/uk/honeymanga/HoneyManga.kt | 129 +- .../extension/uk/mangainua/Mangainua.kt | 171 +- .../extension/vi/blogtruyen/BlogTruyenMoi.kt | 121 +- .../extension/vi/blogtruyenvn/BlogTruyenVn.kt | 101 +- .../extension/vi/comanhua/CoManhua.kt | 89 +- .../extension/vi/doctruyen3q/DocTruyen3Q.kt | 80 +- .../extension/vi/dualeotruyen/DuaLeoTruyen.kt | 250 +- .../tachiyomi/extension/vi/fecomic/Fecomic.kt | 44 +- .../extension/vi/hentaicube/HentaiCB.kt | 5 +- .../extension/vi/hentaivn/HentaiVN.kt | 848 ++-- .../extension/vi/hentaivnplus/HentaiVNPlus.kt | 26 +- .../extension/vi/lxhentai/LxHentai.kt | 456 +- .../extension/vi/manhuarock/ManhuaRock.kt | 259 +- .../extension/vi/nettruyenco/NetTruyenCO.kt | 20 +- .../extension/vi/nettruyenx/NetTruyenX.kt | 20 +- .../extension/vi/nhattruyen/NhatTruyen.kt | 128 +- .../extension/vi/nhattruyens/NhatTruyenS.kt | 39 +- .../extension/vi/pinkteacomic/PinkTeaComic.kt | 53 +- .../vi/ruahapchanhday/RuaHapChanhDay.kt | 13 +- .../extension/vi/sayhentai/SayHentai.kt | 26 +- .../extension/vi/toptruyen/TopTruyen.kt | 68 +- .../extension/vi/truyengg/TruyenGG.kt | 343 +- .../extension/vi/truyengihot/TruyenGiHot.kt | 253 +- .../vi/truyengihot/TruyenGiHotFilters.kt | 197 +- .../vi/truyengihot/TruyenGiHotUtils.kt | 64 +- .../vi/truyenhentai18/TruyenHentai18.kt | 130 +- .../TruyenHentai18UrlActivity.kt | 11 +- .../extension/vi/truyenqq/TruyenQQ.kt | 341 +- .../vi/truyentranhdammy/TruyenTranhDamMy.kt | 53 +- .../extension/vi/truyenvn/TruyenVN.kt | 26 +- .../extension/vi/umetruyen/UmeTruyen.kt | 15 +- .../extension/vi/ungtycomics/UngTyComics.kt | 311 +- .../extension/vi/vlogtruyen/VlogTruyen.kt | 174 +- .../extension/vi/xxmanhwa/XxManhwa.kt | 236 +- .../extension/vi/xxmanhwa/XxManhwaDto.kt | 22 +- .../extension/vi/yurineko/YuriNeko.kt | 594 +-- .../extension/vi/yurineko/dto/ChapterDto.kt | 49 +- .../extension/vi/yurineko/dto/MangaDto.kt | 68 +- .../extension/zh/baimangu/Baimangu.kt | 242 +- .../tachiyomi/extension/zh/bakamh/Bakamh.kt | 13 +- .../extension/zh/baozimanhua/Baozi.kt | 291 +- .../extension/zh/baozimanhua/BaoziFilters.kt | 236 +- .../zh/baozimanhua/BaoziUrlActivity.kt | 22 +- .../baozimanhua/RedirectDomainInterceptor.kt | 12 +- .../tachiyomi/extension/zh/baozimhorg/Dto.kt | 39 +- .../extension/zh/baozimhorg/GoDaManhua.kt | 51 +- .../kanade/tachiyomi/extension/zh/bh3/BH3.kt | 54 +- .../extension/zh/bilibilimanga/Bilibili.kt | 453 +- .../extension/zh/bilibilimanga/BilibiliDto.kt | 6 +- .../zh/bilibilimanga/BilibiliFilters.kt | 52 +- .../zh/bilibilimanga/BilibiliIntl.kt | 342 +- .../zh/bilibilimanga/BilibiliManga.kt | 95 +- .../zh/bilibilimanga/BilibiliUrlActivity.kt | 12 +- .../tachiyomi/extension/zh/boylove/BoyLove.kt | 176 +- .../extension/zh/boylove/BoyLoveDto.kt | 65 +- .../extension/zh/boylove/BoyLoveFilters.kt | 9 +- .../zh/boylove/UnscramblerInterceptor.kt | 17 +- .../extension/zh/cartoon18/Cartoon18.kt | 144 +- .../extension/zh/comicabc/Comicabc.kt | 84 +- .../extension/zh/damaomanhua/DamaoManhua.kt | 14 +- .../extension/zh/didamanhua/DidaManhua.kt | 14 +- .../extension/zh/dm5/CommentsInterceptor.kt | 44 +- .../kanade/tachiyomi/extension/zh/dm5/Dm5.kt | 194 +- .../tachiyomi/extension/zh/dmzj/ApiSearch.kt | 18 +- .../tachiyomi/extension/zh/dmzj/ApiV3.kt | 83 +- .../tachiyomi/extension/zh/dmzj/ApiV4.kt | 85 +- .../extension/zh/dmzj/CommentsInterceptor.kt | 38 +- .../tachiyomi/extension/zh/dmzj/Common.kt | 15 +- .../tachiyomi/extension/zh/dmzj/Dmzj.kt | 139 +- .../extension/zh/dmzj/DmzjUrlActivity.kt | 22 +- .../tachiyomi/extension/zh/dmzj/Filters.kt | 304 +- .../extension/zh/dmzj/ImageUrlInterceptor.kt | 5 +- .../extension/zh/dmzj/Preferences.kt | 48 +- .../tachiyomi/extension/zh/dmzj/utils/RSA.kt | 5 +- .../zh/dongmanmanhua/DongmanManhua.kt | 26 +- .../extension/zh/gufengmh/Gufengmh.kt | 53 +- .../Filters.kt | 19 +- .../Hanime1.kt | 59 +- .../tachiyomi/extension/zh/happymh/Happymh.kt | 219 +- .../extension/zh/happymh/dto/HappymhDto.kt | 22 +- .../tachiyomi/extension/zh/iqiyi/Iqiyi.kt | 96 +- .../zh/jinmantiantang/Jinmantiantang.kt | 437 +- .../JinmantiantangPreferences.kt | 80 +- .../JinmantiantangUrlActivity.kt | 12 +- .../ScrambledImageInterceptor.kt | 53 +- .../extension/zh/jiuermanhua/JiuerManhua.kt | 1 - .../tachiyomi/extension/zh/komiic/Komiic.kt | 195 +- .../tachiyomi/extension/zh/komiic/Queries.kt | 54 +- .../tachiyomi/extension/zh/komiic/Response.kt | 45 +- .../extension/zh/komiic/UrlActivity.kt | 11 +- .../tachiyomi/extension/zh/komiic/Utils.kt | 5 +- .../zh/kuaikanmanhua/Kuaikanmanhua.kt | 230 +- .../kuaikanmanhua/KuaikanmanhuaUrlActivity.kt | 20 +- .../tachiyomi/extension/zh/mangabz/Date.kt | 16 +- .../tachiyomi/extension/zh/mangabz/Filters.kt | 69 +- .../tachiyomi/extension/zh/mangabz/Mangabz.kt | 118 +- .../extension/zh/mangabz/MangabzTheme.kt | 97 +- .../zh/mangabz/MangabzUrlActivity.kt | 12 +- .../extension/zh/mangabz/Preferences.kt | 51 +- .../tachiyomi/extension/zh/manhuadb/MDB.kt | 121 +- .../extension/zh/manhuadb/ManhuaDB.kt | 45 +- .../extension/zh/manhuagui/Manhuagui.kt | 770 ++-- .../zh/manhuagui/ManhuaguiUrlActivity.kt | 12 +- .../zh/manhuaren/ErrorResponseInterceptor.kt | 11 +- .../extension/zh/manhuaren/Manhuaren.kt | 697 +-- .../zh/manhuaren/SimpleEditTextPreference.kt | 8 +- .../tachiyomi/extension/zh/manwa/Manwa.kt | 244 +- .../tachiyomi/extension/zh/noyacg/Dto.kt | 37 +- .../tachiyomi/extension/zh/noyacg/Filters.kt | 31 +- .../tachiyomi/extension/zh/noyacg/NoyAcg.kt | 93 +- .../extension/zh/noyacg/Preferences.kt | 21 +- .../extension/zh/onemanhua/Onemanhua.kt | 202 +- .../extension/zh/picacomic/HmacSHA256.kt | 21 +- .../extension/zh/picacomic/Picacomic.kt | 547 ++- .../tachiyomi/extension/zh/qinqin/Qinqin.kt | 10 +- .../extension/zh/roumanwu/Roumanwu.kt | 221 +- .../zh/roumanwu/ScrambledImageInterceptor.kt | 14 +- .../tachiyomi/extension/zh/sixmh/Crypto.kt | 19 +- .../tachiyomi/extension/zh/sixmh/Image.kt | 5 +- .../tachiyomi/extension/zh/sixmh/SixMH.kt | 7 +- .../zh/tencentcomics/TencentComics.kt | 266 +- .../tencentcomics/TencentComicsUrlActivity.kt | 11 +- .../zh/terrahistoricus/TerraHistoricus.kt | 28 +- .../zh/terrahistoricus/TerraHistoricusDto.kt | 74 +- .../tachiyomi/extension/zh/vomic/Dto.kt | 46 +- .../tachiyomi/extension/zh/vomic/Filters.kt | 10 +- .../tachiyomi/extension/zh/vomic/Vomic.kt | 120 +- .../extension/zh/wnacg/Preferences.kt | 45 +- .../tachiyomi/extension/zh/wnacg/wnacg.kt | 137 +- .../tachiyomi/extension/zh/yidan/Dto.kt | 59 +- .../tachiyomi/extension/zh/yidan/Filters.kt | 72 +- .../tachiyomi/extension/zh/yidan/Yidan.kt | 95 +- .../extension/zh/zaimanhua/Common.kt | 19 +- .../extension/zh/zaimanhua/Filter.kt | 61 +- .../extension/zh/zaimanhua/Zaimanhua.kt | 230 +- .../extension/zh/zaimanhua/ZaimanhuaDto.kt | 66 +- .../extension/zh/zerobyw/UpdateUrl.kt | 78 +- .../tachiyomi/extension/zh/zerobyw/Zerobyw.kt | 219 +- 1715 files changed, 89052 insertions(+), 69139 deletions(-) diff --git a/lib-multisrc/a3manga/src/eu/kanade/tachiyomi/multisrc/a3manga/A3Manga.kt b/lib-multisrc/a3manga/src/eu/kanade/tachiyomi/multisrc/a3manga/A3Manga.kt index 532d1a16f1..1c047c4c9e 100644 --- a/lib-multisrc/a3manga/src/eu/kanade/tachiyomi/multisrc/a3manga/A3Manga.kt +++ b/lib-multisrc/a3manga/src/eu/kanade/tachiyomi/multisrc/a3manga/A3Manga.kt @@ -35,7 +35,6 @@ open class A3Manga( override val baseUrl: String, override val lang: String, ) : ParsedHttpSource() { - override val supportsLatest: Boolean = false override val client: OkHttpClient = network.cloudflareClient @@ -48,11 +47,12 @@ open class A3Manga( override fun popularMangaSelector() = ".comic-list .comic-item" - override fun popularMangaFromElement(element: Element) = SManga.create().apply { - setUrlWithoutDomain(element.select(".comic-title-link a").attr("href")) - title = element.select(".comic-title").text().trim() - thumbnail_url = element.select(".img-thumbnail").attr("abs:src") - } + override fun popularMangaFromElement(element: Element) = + SManga.create().apply { + setUrlWithoutDomain(element.select(".comic-title-link a").attr("href")) + title = element.select(".comic-title").text().trim() + thumbnail_url = element.select(".img-thumbnail").attr("abs:src") + } override fun popularMangaNextPageSelector() = "li.next:not(.disabled)" @@ -64,29 +64,36 @@ open class A3Manga( override fun latestUpdatesNextPageSelector() = throw UnsupportedOperationException() - override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable { - return when { + override fun fetchSearchManga( + page: Int, + query: String, + filters: FilterList, + ): Observable = + when { query.startsWith(PREFIX_ID_SEARCH) -> { val id = query.removePrefix(PREFIX_ID_SEARCH).trim() fetchMangaDetails( SManga.create().apply { url = "/truyen-tranh/$id/" }, - ) - .map { - it.url = "/truyen-tranh/$id/" - MangasPage(listOf(it), false) - } + ).map { + it.url = "/truyen-tranh/$id/" + MangasPage(listOf(it), false) + } } else -> super.fetchSearchManga(page, query, filters) } - } - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = + override fun searchMangaRequest( + page: Int, + query: String, + filters: FilterList, + ): Request = POST( "$baseUrl/wp-admin/admin-ajax.php", headers, - FormBody.Builder() + FormBody + .Builder() .add("action", "searchtax") .add("keyword", query) .build(), @@ -105,66 +112,77 @@ open class A3Manga( return MangasPage(emptyList(), false) } - val manga = dto.data - .filter { it.cstatus != "Nhóm dịch" } - .map { - SManga.create().apply { - setUrlWithoutDomain(it.link) - title = it.title - thumbnail_url = it.img + val manga = + dto.data + .filter { it.cstatus != "Nhóm dịch" } + .map { + SManga.create().apply { + setUrlWithoutDomain(it.link) + title = it.title + thumbnail_url = it.img + } } - } return MangasPage(manga, false) } - override fun mangaDetailsParse(document: Document) = SManga.create().apply { - title = document.select(".info-title").text() - author = document.select(".comic-info strong:contains(Tác giả) + span").text().trim() - description = document.select(".intro-container .text-justify").text().substringBefore("— Xem Thêm —") - genre = document.select(".comic-info .tags a").joinToString { tag -> - tag.text().split(' ').joinToString(separator = " ") { word -> - word.replaceFirstChar { it.titlecase() } - } - } - thumbnail_url = document.select(".img-thumbnail").attr("abs:src") - - val statusString = document.select(".comic-info strong:contains(Tình trạng) + span").text() - status = when (statusString) { - "Đang tiến hành" -> SManga.ONGOING - "Trọn bộ " -> SManga.COMPLETED - else -> SManga.UNKNOWN + override fun mangaDetailsParse(document: Document) = + SManga.create().apply { + title = document.select(".info-title").text() + author = document.select(".comic-info strong:contains(Tác giả) + span").text().trim() + description = document.select(".intro-container .text-justify").text().substringBefore("— Xem Thêm —") + genre = + document.select(".comic-info .tags a").joinToString { tag -> + tag.text().split(' ').joinToString(separator = " ") { word -> + word.replaceFirstChar { it.titlecase() } + } + } + thumbnail_url = document.select(".img-thumbnail").attr("abs:src") + + val statusString = document.select(".comic-info strong:contains(Tình trạng) + span").text() + status = + when (statusString) { + "Đang tiến hành" -> SManga.ONGOING + "Trọn bộ " -> SManga.COMPLETED + else -> SManga.UNKNOWN + } } - } override fun chapterListSelector(): String = ".chapter-table table tbody tr" - override fun chapterFromElement(element: Element) = SChapter.create().apply { - setUrlWithoutDomain(element.select("a").attr("href")) - name = element.select("a .hidden-sm").text() - date_upload = runCatching { - dateFormat.parse(element.select("td").last()!!.text())?.time - }.getOrNull() ?: 0 - } + override fun chapterFromElement(element: Element) = + SChapter.create().apply { + setUrlWithoutDomain(element.select("a").attr("href")) + name = element.select("a .hidden-sm").text() + date_upload = runCatching { + dateFormat.parse(element.select("td").last()!!.text())?.time + }.getOrNull() ?: 0 + } protected fun decodeImgList(document: Document): String { - val htmlContentScript = document.selectFirst("script:containsData(htmlContent)")?.html() - ?.substringAfter("var htmlContent=\"") - ?.substringBefore("\";") - ?.replace("\\\"", "\"") - ?.replace("\\\\", "\\") - ?.replace("\\/", "/") - ?: throw Exception("Couldn't find script with image data.") + val htmlContentScript = + document + .selectFirst("script:containsData(htmlContent)") + ?.html() + ?.substringAfter("var htmlContent=\"") + ?.substringBefore("\";") + ?.replace("\\\"", "\"") + ?.replace("\\\\", "\\") + ?.replace("\\/", "/") + ?: throw Exception("Couldn't find script with image data.") val htmlContent = json.decodeFromString(htmlContentScript) val ciphertext = Base64.decode(htmlContent.ciphertext, Base64.DEFAULT) val iv = htmlContent.iv.decodeHex() val salt = htmlContent.salt.decodeHex() - val passwordScript = document.selectFirst("script:containsData(chapterHTML)")?.html() - ?: throw Exception("Couldn't find password to decrypt image data.") - val passphrase = passwordScript.substringAfter("var chapterHTML=CryptoJSAesDecrypt('") - .substringBefore("',htmlContent") - .replace("'+'", "") + val passwordScript = + document.selectFirst("script:containsData(chapterHTML)")?.html() + ?: throw Exception("Couldn't find password to decrypt image data.") + val passphrase = + passwordScript + .substringAfter("var chapterHTML=CryptoJSAesDecrypt('") + .substringBefore("',htmlContent") + .replace("'+'", "") val keyFactory = SecretKeyFactory.getInstance(KEY_ALGORITHM) val spec = PBEKeySpec(passphrase.toCharArray(), salt, 999, 256) @@ -192,12 +210,13 @@ open class A3Manga( // We expect the URL to start with `https://`, where the last 3 characters are encoded. // The length of the encoded character is not known, but it is the same across all. // Essentially we are looking for the two encoded slashes, which tells us the length. - val patternIdx = patternsLengthCheck.indexOfFirst { pattern -> - val matchResult = pattern.find(this) - val g1 = matchResult?.groupValues?.get(1) - val g2 = matchResult?.groupValues?.get(2) - g1 == g2 && g1 != null - } + val patternIdx = + patternsLengthCheck.indexOfFirst { pattern -> + val matchResult = pattern.find(this) + val g1 = matchResult?.groupValues?.get(1) + val g2 = matchResult?.groupValues?.get(2) + g1 == g2 && g1 != null + } if (patternIdx == -1) { return null } @@ -215,9 +234,7 @@ open class A3Manga( override fun imageUrlParse(document: Document) = throw UnsupportedOperationException() - private inline fun Response.parseAs(): T { - return json.decodeFromString(body.string()) - } + private inline fun Response.parseAs(): T = json.decodeFromString(body.string()) // https://stackoverflow.com/a/66614516 private fun String.decodeHex(): ByteArray { @@ -233,15 +250,18 @@ open class A3Manga( const val CIPHER_TRANSFORMATION = "AES/CBC/PKCS7PADDING" const val PREFIX_ID_SEARCH = "id:" - val dateFormat = SimpleDateFormat("dd/MM/yyyy", Locale.US).apply { - timeZone = TimeZone.getTimeZone("Asia/Ho_Chi_Minh") - } + val dateFormat = + SimpleDateFormat("dd/MM/yyyy", Locale.US).apply { + timeZone = TimeZone.getTimeZone("Asia/Ho_Chi_Minh") + } - private val patternsLengthCheck: List = (20 downTo 1).map { i -> - """^https.{$i}(.{$i})(.{$i})""".toRegex() - } - private val patternsSubstitution: List = (20 downTo 1).map { i -> - """^https(.{$i})(.{$i}).*(.{$i})(?:webp|jpeg|tiff|.{3})$""".toRegex() - } + private val patternsLengthCheck: List = + (20 downTo 1).map { i -> + """^https.{$i}(.{$i})(.{$i})""".toRegex() + } + private val patternsSubstitution: List = + (20 downTo 1).map { i -> + """^https(.{$i})(.{$i}).*(.{$i})(?:webp|jpeg|tiff|.{3})$""".toRegex() + } } } diff --git a/lib-multisrc/bakkin/src/eu/kanade/tachiyomi/multisrc/bakkin/BakkinJSON.kt b/lib-multisrc/bakkin/src/eu/kanade/tachiyomi/multisrc/bakkin/BakkinJSON.kt index 8b5d0e3085..c8aaea27d9 100644 --- a/lib-multisrc/bakkin/src/eu/kanade/tachiyomi/multisrc/bakkin/BakkinJSON.kt +++ b/lib-multisrc/bakkin/src/eu/kanade/tachiyomi/multisrc/bakkin/BakkinJSON.kt @@ -9,14 +9,16 @@ internal data class Series( val thumb: String?, val volumes: List, ) : Iterable { - override fun iterator() = volumes.flatMap { vol -> - vol.map { - it.copy( - name = "$vol - $it", - dir = "$dir/${vol.dir}/${it.dir}", - ) - } - }.iterator() + override fun iterator() = + volumes + .flatMap { vol -> + vol.map { + it.copy( + name = "$vol - $it", + dir = "$dir/${vol.dir}/${it.dir}", + ) + } + }.iterator() val cover: String get() = thumb ?: "static/nocover.png" diff --git a/lib-multisrc/bakkin/src/eu/kanade/tachiyomi/multisrc/bakkin/BakkinReaderX.kt b/lib-multisrc/bakkin/src/eu/kanade/tachiyomi/multisrc/bakkin/BakkinReaderX.kt index dc89a458a0..eb49cf0a34 100644 --- a/lib-multisrc/bakkin/src/eu/kanade/tachiyomi/multisrc/bakkin/BakkinReaderX.kt +++ b/lib-multisrc/bakkin/src/eu/kanade/tachiyomi/multisrc/bakkin/BakkinReaderX.kt @@ -26,12 +26,14 @@ abstract class BakkinReaderX( override val name: String, override val baseUrl: String, override val lang: String, -) : ConfigurableSource, HttpSource() { +) : HttpSource(), + ConfigurableSource { override val supportsLatest = false - private val userAgent = "Mozilla/5.0 (" + - "Android ${Build.VERSION.RELEASE}; Mobile) " + - "Tachiyomi/${AppInfo.getVersionName()}" + private val userAgent = + "Mozilla/5.0 (" + + "Android ${Build.VERSION.RELEASE}; Mobile) " + + "Tachiyomi/${AppInfo.getVersionName()}" protected val preferences by lazy { Injekt.get().getSharedPreferences("source_$id", 0x0000)!! @@ -49,31 +51,36 @@ abstract class BakkinReaderX( rx.Observable.just(block(seriesCache))!! } else { client.newCall(GET(mainUrl, headers)).asObservableSuccess().map { - seriesCache = json.parseToJsonElement(it.body.string()) - .jsonObject.values.map(json::decodeFromJsonElement) + seriesCache = + json + .parseToJsonElement(it.body.string()) + .jsonObject.values + .map(json::decodeFromJsonElement) block(seriesCache) }!! } - private fun List.search(query: String) = - if (query.isBlank()) this else filter { it.toString().contains(query, true) } + private fun List.search(query: String) = if (query.isBlank()) this else filter { it.toString().contains(query, true) } - override fun headersBuilder() = - Headers.Builder().add("User-Agent", userAgent) + override fun headersBuilder() = Headers.Builder().add("User-Agent", userAgent) - override fun fetchPopularManga(page: Int) = - fetchSearchManga(page, "", FilterList()) + override fun fetchPopularManga(page: Int) = fetchSearchManga(page, "", FilterList()) - override fun fetchSearchManga(page: Int, query: String, filters: FilterList) = - observableSeries { series -> - series.search(query).map { + override fun fetchSearchManga( + page: Int, + query: String, + filters: FilterList, + ) = observableSeries { series -> + series + .search(query) + .map { SManga.create().apply { url = it.dir title = it.toString() thumbnail_url = baseUrl + it.cover } }.let { MangasPage(it, false) } - } + } override fun fetchMangaDetails(manga: SManga) = observableSeries { series -> @@ -84,30 +91,35 @@ abstract class BakkinReaderX( thumbnail_url = baseUrl + it.cover initialized = true author = it.author - status = when (it.status) { - "Ongoing" -> SManga.ONGOING - "Completed" -> SManga.COMPLETED - else -> SManga.UNKNOWN - } + status = + when (it.status) { + "Ongoing" -> SManga.ONGOING + "Completed" -> SManga.COMPLETED + else -> SManga.UNKNOWN + } } } } override fun fetchChapterList(manga: SManga) = observableSeries { series -> - series.first { it.dir == manga.url }.map { chapter -> - SChapter.create().apply { - url = chapter.dir - name = chapter.toString() - chapter_number = chapter.number - date_upload = 0L - } - }.reversed() + series + .first { it.dir == manga.url } + .map { chapter -> + SChapter.create().apply { + url = chapter.dir + name = chapter.toString() + chapter_number = chapter.number + date_upload = 0L + } + }.reversed() } override fun fetchPageList(chapter: SChapter) = observableSeries { series -> - series.flatten().first { it.dir == chapter.url } + series + .flatten() + .first { it.dir == chapter.url } .mapIndexed { idx, page -> Page(idx, "", baseUrl + page) } } @@ -119,50 +131,44 @@ abstract class BakkinReaderX( } override fun setupPreferenceScreen(screen: PreferenceScreen) { - ListPreference(screen.context).apply { - key = "quality" - summary = "%s" - title = "Image quality" - entries = arrayOf("Original", "Compressed") - entryValues = arrayOf("?fullsize", "") - setDefaultValue("") - - setOnPreferenceChangeListener { _, newValue -> - preferences.edit().putString(key, newValue as String).commit() - } - }.let(screen::addPreference) + ListPreference(screen.context) + .apply { + key = "quality" + summary = "%s" + title = "Image quality" + entries = arrayOf("Original", "Compressed") + entryValues = arrayOf("?fullsize", "") + setDefaultValue("") + + setOnPreferenceChangeListener { _, newValue -> + preferences.edit().putString(key, newValue as String).commit() + } + }.let(screen::addPreference) } - override fun searchMangaRequest(page: Int, query: String, filters: FilterList) = - throw UnsupportedOperationException() + override fun searchMangaRequest( + page: Int, + query: String, + filters: FilterList, + ) = throw UnsupportedOperationException() - override fun popularMangaRequest(page: Int) = - throw UnsupportedOperationException() + override fun popularMangaRequest(page: Int) = throw UnsupportedOperationException() - override fun latestUpdatesRequest(page: Int) = - throw UnsupportedOperationException() + override fun latestUpdatesRequest(page: Int) = throw UnsupportedOperationException() - override fun mangaDetailsRequest(manga: SManga) = - throw UnsupportedOperationException() + override fun mangaDetailsRequest(manga: SManga) = throw UnsupportedOperationException() - override fun searchMangaParse(response: Response) = - throw UnsupportedOperationException() + override fun searchMangaParse(response: Response) = throw UnsupportedOperationException() - override fun popularMangaParse(response: Response) = - throw UnsupportedOperationException() + override fun popularMangaParse(response: Response) = throw UnsupportedOperationException() - override fun latestUpdatesParse(response: Response) = - throw UnsupportedOperationException() + override fun latestUpdatesParse(response: Response) = throw UnsupportedOperationException() - override fun mangaDetailsParse(response: Response) = - throw UnsupportedOperationException() + override fun mangaDetailsParse(response: Response) = throw UnsupportedOperationException() - override fun chapterListParse(response: Response) = - throw UnsupportedOperationException() + override fun chapterListParse(response: Response) = throw UnsupportedOperationException() - override fun pageListParse(response: Response) = - throw UnsupportedOperationException() + override fun pageListParse(response: Response) = throw UnsupportedOperationException() - override fun imageUrlParse(response: Response) = - throw UnsupportedOperationException() + override fun imageUrlParse(response: Response) = throw UnsupportedOperationException() } diff --git a/lib-multisrc/blogtruyen/src/eu/kanade/tachiyomi/multisrc/blogtruyen/BlogTruyen.kt b/lib-multisrc/blogtruyen/src/eu/kanade/tachiyomi/multisrc/blogtruyen/BlogTruyen.kt index 1c99c835a6..449490065a 100644 --- a/lib-multisrc/blogtruyen/src/eu/kanade/tachiyomi/multisrc/blogtruyen/BlogTruyen.kt +++ b/lib-multisrc/blogtruyen/src/eu/kanade/tachiyomi/multisrc/blogtruyen/BlogTruyen.kt @@ -34,31 +34,33 @@ abstract class BlogTruyen( override val baseUrl: String, override val lang: String, ) : ParsedHttpSource() { - override val supportsLatest = true override val client = network.cloudflareClient - override fun headersBuilder() = super.headersBuilder() - .add("Referer", "$baseUrl/") + override fun headersBuilder() = + super + .headersBuilder() + .add("Referer", "$baseUrl/") private val json: Json by injectLazy() - private val dateFormat: SimpleDateFormat = SimpleDateFormat("dd/MM/yyyy HH:mm", Locale.US).apply { - timeZone = TimeZone.getTimeZone("Asia/Ho_Chi_Minh") - } + private val dateFormat: SimpleDateFormat = + SimpleDateFormat("dd/MM/yyyy HH:mm", Locale.US).apply { + timeZone = TimeZone.getTimeZone("Asia/Ho_Chi_Minh") + } - override fun popularMangaRequest(page: Int) = - GET("$baseUrl/ajax/Search/AjaxLoadListManga?key=tatca&orderBy=3&p=$page", headers) + override fun popularMangaRequest(page: Int) = GET("$baseUrl/ajax/Search/AjaxLoadListManga?key=tatca&orderBy=3&p=$page", headers) override fun popularMangaParse(response: Response): MangasPage { val document = response.asJsoup() - val manga = document.select(popularMangaSelector()).map { - val tiptip = it.attr("data-tiptip") + val manga = + document.select(popularMangaSelector()).map { + val tiptip = it.attr("data-tiptip") - popularMangaFromElement(it, document.getElementById(tiptip)!!) - } + popularMangaFromElement(it, document.getElementById(tiptip)!!) + } val hasNextPage = document.selectFirst(popularMangaNextPageSelector()) != null @@ -69,7 +71,10 @@ abstract class BlogTruyen( override fun popularMangaFromElement(element: Element) = throw UnsupportedOperationException() - private fun popularMangaFromElement(element: Element, tiptip: Element) = SManga.create().apply { + private fun popularMangaFromElement( + element: Element, + tiptip: Element, + ) = SManga.create().apply { val anchor = element.selectFirst("a")!! setUrlWithoutDomain(anchor.attr("href")) @@ -80,18 +85,18 @@ abstract class BlogTruyen( override fun popularMangaNextPageSelector() = ".paging:last-child:not(.current_page)" - override fun latestUpdatesRequest(page: Int): Request = - GET(baseUrl + if (page > 1) "/page-$page" else "", headers) + override fun latestUpdatesRequest(page: Int): Request = GET(baseUrl + if (page > 1) "/page-$page" else "", headers) override fun latestUpdatesSelector() = ".storyitem .fl-l" - override fun latestUpdatesFromElement(element: Element): SManga = SManga.create().apply { - val anchor = element.selectFirst("a")!! + override fun latestUpdatesFromElement(element: Element): SManga = + SManga.create().apply { + val anchor = element.selectFirst("a")!! - setUrlWithoutDomain(anchor.absUrl("href")) - title = anchor.attr("title") - thumbnail_url = element.selectFirst("img")?.absUrl("src") - } + setUrlWithoutDomain(anchor.absUrl("href")) + title = anchor.attr("title") + thumbnail_url = element.selectFirst("img")?.absUrl("src") + } override fun latestUpdatesNextPageSelector() = "select.slcPaging option:last-child:not([selected])" @@ -99,79 +104,91 @@ abstract class BlogTruyen( page: Int, query: String, filters: FilterList, - ): Observable = when { - query.startsWith(PREFIX_ID_SEARCH) -> { - var id = query.removePrefix(PREFIX_ID_SEARCH).trimStart() + ): Observable = + when { + query.startsWith(PREFIX_ID_SEARCH) -> { + var id = query.removePrefix(PREFIX_ID_SEARCH).trimStart() - // it's a chapter, resolve to manga ID - if (id.startsWith("c")) { - val document = client.newCall(GET("$baseUrl/$id", headers)).execute().asJsoup() + // it's a chapter, resolve to manga ID + if (id.startsWith("c")) { + val document = client.newCall(GET("$baseUrl/$id", headers)).execute().asJsoup() - id = document.selectFirst(".breadcrumbs a:last-child")!!.attr("href").removePrefix("/") - } + id = document.selectFirst(".breadcrumbs a:last-child")!!.attr("href").removePrefix("/") + } - fetchMangaDetails( - SManga.create().apply { - url = "/$id" - }, - ) - .map { MangasPage(listOf(it), false) } + fetchMangaDetails( + SManga.create().apply { + url = "/$id" + }, + ).map { MangasPage(listOf(it), false) } + } + else -> super.fetchSearchManga(page, query, filters) } - else -> super.fetchSearchManga(page, query, filters) - } - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { + override fun searchMangaRequest( + page: Int, + query: String, + filters: FilterList, + ): Request { ajaxSearchUrls.keys .firstOrNull { query.startsWith(it) } ?.let { val id = extractIdFromQuery(it, query) - val url = "$baseUrl/ajax/${ajaxSearchUrls[it]!!}".toHttpUrl().newBuilder() - .addQueryParameter("id", id) - .addQueryParameter("p", page.toString()) - .build() + val url = + "$baseUrl/ajax/${ajaxSearchUrls[it]!!}" + .toHttpUrl() + .newBuilder() + .addQueryParameter("id", id) + .addQueryParameter("p", page.toString()) + .build() return GET(url, headers) } - val url = "$baseUrl/timkiem/nangcao/1".toHttpUrl().newBuilder().apply { - if (query.isNotBlank()) { - addQueryParameter("txt", query) - } - - if (page > 1) { - addQueryParameter("p", page.toString()) - } + val url = + "$baseUrl/timkiem/nangcao/1" + .toHttpUrl() + .newBuilder() + .apply { + if (query.isNotBlank()) { + addQueryParameter("txt", query) + } - val inclGenres = mutableListOf() - val exclGenres = mutableListOf() - var status = 0 + if (page > 1) { + addQueryParameter("p", page.toString()) + } - (if (filters.isEmpty()) getFilterList() else filters).forEach { filter -> - when (filter) { - is GenreList -> filter.state.forEach { - when (it.state) { - Filter.TriState.STATE_INCLUDE -> inclGenres.add(it.id) - Filter.TriState.STATE_EXCLUDE -> exclGenres.add(it.id) + val inclGenres = mutableListOf() + val exclGenres = mutableListOf() + var status = 0 + + (if (filters.isEmpty()) getFilterList() else filters).forEach { filter -> + when (filter) { + is GenreList -> + filter.state.forEach { + when (it.state) { + Filter.TriState.STATE_INCLUDE -> inclGenres.add(it.id) + Filter.TriState.STATE_EXCLUDE -> exclGenres.add(it.id) + else -> {} + } + } + is Author -> { + addQueryParameter("aut", filter.state) + } + is Scanlator -> { + addQueryParameter("gr", filter.state) + } + is Status -> { + status = filter.state + } else -> {} } } - is Author -> { - addQueryParameter("aut", filter.state) - } - is Scanlator -> { - addQueryParameter("gr", filter.state) - } - is Status -> { - status = filter.state - } - else -> {} - } - } - addPathSegment(status.toString()) - addPathSegment(inclGenres.joinToString(",").ifEmpty { "-1" }) - addPathSegment(exclGenres.joinToString(",").ifEmpty { "-1" }) - }.build() + addPathSegment(status.toString()) + addPathSegment(inclGenres.joinToString(",").ifEmpty { "-1" }) + addPathSegment(exclGenres.joinToString(",").ifEmpty { "-1" }) + }.build() return GET(url, headers) } @@ -179,11 +196,12 @@ abstract class BlogTruyen( override fun searchMangaParse(response: Response): MangasPage { val document = response.asJsoup() - val manga = document.select(searchMangaSelector()).map { - val tiptip = it.attr("data-tiptip") + val manga = + document.select(searchMangaSelector()).map { + val tiptip = it.attr("data-tiptip") - searchMangaFromElement(it, document.getElementById(tiptip)!!) - } + searchMangaFromElement(it, document.getElementById(tiptip)!!) + } val hasNextPage = document.selectFirst(searchMangaNextPageSelector()) != null @@ -192,82 +210,88 @@ abstract class BlogTruyen( override fun searchMangaSelector() = popularMangaSelector() - override fun searchMangaFromElement(element: Element): SManga = - throw UnsupportedOperationException() + override fun searchMangaFromElement(element: Element): SManga = throw UnsupportedOperationException() - private fun searchMangaFromElement(element: Element, tiptip: Element) = - popularMangaFromElement(element, tiptip) + private fun searchMangaFromElement( + element: Element, + tiptip: Element, + ) = popularMangaFromElement(element, tiptip) override fun searchMangaNextPageSelector() = ".pagination .glyphicon-step-forward" - override fun mangaDetailsParse(document: Document) = SManga.create().apply { - val anchor = document.selectFirst(".entry-title a")!! - val descriptionBlock = document.selectFirst("div.description")!! - - setUrlWithoutDomain(anchor.absUrl("href")) - title = getMangaTitle(document) - thumbnail_url = document.selectFirst(".thumbnail img")?.absUrl("src") - author = descriptionBlock.select("p:contains(Tác giả) a").joinToString { it.text() } - genre = descriptionBlock.select("span.category").joinToString { it.text() } - status = when (descriptionBlock.selectFirst("p:contains(Trạng thái) span.color-red")?.text()) { - "Đang tiến hành" -> SManga.ONGOING - "Đã hoàn thành" -> SManga.COMPLETED - "Tạm ngưng" -> SManga.ON_HIATUS - else -> SManga.UNKNOWN - } - description = buildString { - document.selectFirst(".manga-detail .detail .content")?.let { - // replace the facebook blockquote in synopsis with the link (if there is one) - it.selectFirst(".fb-page, .fb-group")?.let { fb -> - val link = fb.attr("data-href") - val node = document.createElement("p") - - node.appendText(link) - fb.replaceWith(node) + override fun mangaDetailsParse(document: Document) = + SManga.create().apply { + val anchor = document.selectFirst(".entry-title a")!! + val descriptionBlock = document.selectFirst("div.description")!! + + setUrlWithoutDomain(anchor.absUrl("href")) + title = getMangaTitle(document) + thumbnail_url = document.selectFirst(".thumbnail img")?.absUrl("src") + author = descriptionBlock.select("p:contains(Tác giả) a").joinToString { it.text() } + genre = descriptionBlock.select("span.category").joinToString { it.text() } + status = + when (descriptionBlock.selectFirst("p:contains(Trạng thái) span.color-red")?.text()) { + "Đang tiến hành" -> SManga.ONGOING + "Đã hoàn thành" -> SManga.COMPLETED + "Tạm ngưng" -> SManga.ON_HIATUS + else -> SManga.UNKNOWN } + description = + buildString { + document.selectFirst(".manga-detail .detail .content")?.let { + // replace the facebook blockquote in synopsis with the link (if there is one) + it.selectFirst(".fb-page, .fb-group")?.let { fb -> + val link = fb.attr("data-href") + val node = document.createElement("p") + + node.appendText(link) + fb.replaceWith(node) + } - appendLine(it.textWithNewlines().trim()) - appendLine() - } - - descriptionBlock.select("p:not(:contains(Thể loại)):not(:contains(Tác giả))") - .forEach { e -> - val text = e.text() - - if (text.isBlank()) { - return@forEach - } - - // Uploader and status share the same

- if (text.contains("Trạng thái")) { - appendLine(text.substringBefore("Trạng thái").trim()) - return@forEach - } - - // "Source", "Updaters" and "Scanlators" use badges with links - if (text.contains("Nguồn") || - text.contains("Tham gia update") || - text.contains("Nhóm dịch") - ) { - val key = text.substringBefore(":") - val value = e.select("a").joinToString { el -> el.text() } - appendLine("$key: $value") - return@forEach + appendLine(it.textWithNewlines().trim()) + appendLine() } - // Generic paragraphs i.e. view count and follower count for this series - // Basically the same trick as [Element.textWithNewlines], just applied to - // different elements. - e.select("a, span").append("\\n") - appendLine( - e.text() - .replace("\\n", "\n") - .replace("\n ", "\n") - .trim(), - ) - } - }.trim() - } + descriptionBlock + .select("p:not(:contains(Thể loại)):not(:contains(Tác giả))") + .forEach { e -> + val text = e.text() + + if (text.isBlank()) { + return@forEach + } + + // Uploader and status share the same

+ if (text.contains("Trạng thái")) { + appendLine(text.substringBefore("Trạng thái").trim()) + return@forEach + } + + // "Source", "Updaters" and "Scanlators" use badges with links + if (text.contains("Nguồn") || + text.contains("Tham gia update") || + text.contains("Nhóm dịch") + ) { + val key = text.substringBefore(":") + val value = e.select("a").joinToString { el -> el.text() } + appendLine("$key: $value") + return@forEach + } + + // Generic paragraphs i.e. view count and follower count for this series + // Basically the same trick as [Element.textWithNewlines], just applied to + // different elements. + e.select("a, span").append("\\n") + appendLine( + e + .text() + .replace("\\n", "\n") + .replace("\n ", "\n") + .trim(), + ) + } + }.trim() + } override fun chapterListParse(response: Response): List { val document = response.asJsoup() @@ -279,17 +303,24 @@ abstract class BlogTruyen( override fun chapterFromElement(element: Element): SChapter = throw UnsupportedOperationException() - private fun chapterFromElement(element: Element, title: String): SChapter = SChapter.create().apply { - val anchor = element.select("span > a").first()!! - - setUrlWithoutDomain(anchor.attr("href")) - name = anchor.text().removePrefix("$title ") - date_upload = runCatching { - dateFormat.parse( - element.selectFirst("span.publishedDate")!!.text(), - )!!.time - }.getOrDefault(0L) - } + private fun chapterFromElement( + element: Element, + title: String, + ): SChapter = + SChapter.create().apply { + val anchor = element.select("span > a").first()!! + + setUrlWithoutDomain(anchor.attr("href")) + name = anchor.text().removePrefix("$title ") + date_upload = + runCatching { + dateFormat + .parse( + element.selectFirst("span.publishedDate")!!.text(), + )!! + .time + }.getOrDefault(0L) + } override fun pageListParse(document: Document): List { val pages = mutableListOf() @@ -299,9 +330,16 @@ abstract class BlogTruyen( } // Some chapters use js script to render images - document.select("#content > script:containsData(listImageCaption)").lastOrNull() + document + .select("#content > script:containsData(listImageCaption)") + .lastOrNull() ?.let { script -> - val imagesStr = script.data().substringBefore(";").substringAfterLast("=").trim() + val imagesStr = + script + .data() + .substringBefore(";") + .substringAfterLast("=") + .trim() val imageArr = json.parseToJsonElement(imagesStr).jsonArray imageArr.forEach { val imageUrl = it.jsonObject["url"]!!.jsonPrimitive.content @@ -316,11 +354,12 @@ abstract class BlogTruyen( override fun imageUrlParse(document: Document) = throw UnsupportedOperationException() override fun getFilterList(): FilterList { - val filters = mutableListOf>( - Author(), - Scanlator(), - Status(), - ) + val filters = + mutableListOf>( + Author(), + Scanlator(), + Status(), + ) val genres = getGenreList() if (genres.isNotEmpty()) { @@ -333,14 +372,24 @@ abstract class BlogTruyen( // copy([...document.querySelectorAll(".CategoryFilter li")].map((e) => `Genre("${e.textContent.trim()}", "${e.dataset.id}"),`).join("\n")) open fun getGenreList(): List = emptyList() - private class Status : Filter.Select( - "Status", - arrayOf("Sao cũng được", "Đang tiến hành", "Đã hoàn thành", "Tạm ngưng"), - ) + private class Status : + Filter.Select( + "Status", + arrayOf("Sao cũng được", "Đang tiến hành", "Đã hoàn thành", "Tạm ngưng"), + ) + private class Author : Filter.Text("Tác giả") + private class Scanlator : Filter.Text("Nhóm dịch") - class Genre(name: String, val id: String) : Filter.TriState(name) - private class GenreList(genres: List) : Filter.Group("Thể loại", genres) + + class Genre( + name: String, + val id: String, + ) : Filter.TriState(name) + + private class GenreList( + genres: List, + ) : Filter.Group("Thể loại", genres) private fun getMangaTitle(document: Document) = document @@ -348,42 +397,48 @@ abstract class BlogTruyen( .attr("title") .removePrefix("truyện tranh ") - private fun Element.textWithNewlines() = run { - select("p, br").prepend("\\n") - text().replace("\\n", "\n").replace("\n ", "\n") - } + private fun Element.textWithNewlines() = + run { + select("p, br").prepend("\\n") + text().replace("\\n", "\n").replace("\n ", "\n") + } - private fun extractIdFromQuery(prefix: String, query: String): String = - query.substringAfter(prefix).trimStart().substringAfterLast("-") + private fun extractIdFromQuery( + prefix: String, + query: String, + ): String = query.substringAfter(prefix).trimStart().substringAfterLast("-") private fun countView(document: Document) { val mangaId = document.getElementById("MangaId")!!.attr("value") val chapterId = document.getElementById("ChapterId")!!.attr("value") - val request = POST( - "$baseUrl/Chapter/UpdateView", - headers, - FormBody.Builder() - .add("mangaId", mangaId) - .add("chapterId", chapterId) - .build(), - ) + val request = + POST( + "$baseUrl/Chapter/UpdateView", + headers, + FormBody + .Builder() + .add("mangaId", mangaId) + .add("chapterId", chapterId) + .build(), + ) - Single.fromCallable { - try { - client.newCall(request).execute().close() - } catch (e: Exception) { - Log.e("BlogTruyen", "Error updating view count", e) - } - } - .subscribeOn(Schedulers.io()) + Single + .fromCallable { + try { + client.newCall(request).execute().close() + } catch (e: Exception) { + Log.e("BlogTruyen", "Error updating view count", e) + } + }.subscribeOn(Schedulers.io()) .observeOn(Schedulers.io()) .subscribe() } - private val ajaxSearchUrls: Map = mapOf( - PREFIX_AUTHOR_SEARCH to "Author/AjaxLoadMangaByAuthor?orderBy=3", - PREFIX_TEAM_SEARCH to "TranslateTeam/AjaxLoadMangaByTranslateTeam", - ) + private val ajaxSearchUrls: Map = + mapOf( + PREFIX_AUTHOR_SEARCH to "Author/AjaxLoadMangaByAuthor?orderBy=3", + PREFIX_TEAM_SEARCH to "TranslateTeam/AjaxLoadMangaByTranslateTeam", + ) companion object { internal const val PREFIX_ID_SEARCH = "id:" diff --git a/lib-multisrc/colamanga/src/eu/kanade/tachiyomi/multisrc/colamanga/ColaManga.kt b/lib-multisrc/colamanga/src/eu/kanade/tachiyomi/multisrc/colamanga/ColaManga.kt index 88c609f8fe..097f3f565a 100644 --- a/lib-multisrc/colamanga/src/eu/kanade/tachiyomi/multisrc/colamanga/ColaManga.kt +++ b/lib-multisrc/colamanga/src/eu/kanade/tachiyomi/multisrc/colamanga/ColaManga.kt @@ -38,8 +38,8 @@ abstract class ColaManga( final override val name: String, final override val baseUrl: String, final override val lang: String, -) : ParsedHttpSource(), ConfigurableSource { - +) : ParsedHttpSource(), + ConfigurableSource { override val supportsLatest = true private val json: Json by injectLazy() @@ -50,35 +50,37 @@ abstract class ColaManga( Injekt.get().getSharedPreferences("source_$id", 0x0000) } - override val client = network.cloudflareClient.newBuilder() - .rateLimitHost( - baseUrl.toHttpUrl(), - preferences.getString(RATE_LIMIT_PREF_KEY, RATE_LIMIT_PREF_DEFAULT)!!.toInt(), - preferences.getString(RATE_LIMIT_PERIOD_PREF_KEY, RATE_LIMIT_PERIOD_PREF_DEFAULT)!!.toLong(), - TimeUnit.MILLISECONDS, - ) - .addInterceptor(ColaMangaImageInterceptor()) - .build() - - override fun headersBuilder() = super.headersBuilder() - .add("Origin", baseUrl) - .add("Referer", "$baseUrl/") - - override fun popularMangaRequest(page: Int) = - GET("$baseUrl/show?orderBy=dailyCount&page=$page", headers) + override val client = + network.cloudflareClient + .newBuilder() + .rateLimitHost( + baseUrl.toHttpUrl(), + preferences.getString(RATE_LIMIT_PREF_KEY, RATE_LIMIT_PREF_DEFAULT)!!.toInt(), + preferences.getString(RATE_LIMIT_PERIOD_PREF_KEY, RATE_LIMIT_PERIOD_PREF_DEFAULT)!!.toLong(), + TimeUnit.MILLISECONDS, + ).addInterceptor(ColaMangaImageInterceptor()) + .build() + + override fun headersBuilder() = + super + .headersBuilder() + .add("Origin", baseUrl) + .add("Referer", "$baseUrl/") + + override fun popularMangaRequest(page: Int) = GET("$baseUrl/show?orderBy=dailyCount&page=$page", headers) override fun popularMangaSelector() = "li.fed-list-item" - override fun popularMangaFromElement(element: Element) = SManga.create().apply { - element.selectFirst("a.fed-list-title")!!.let { - setUrlWithoutDomain(it.attr("href")) - title = it.text() + override fun popularMangaFromElement(element: Element) = + SManga.create().apply { + element.selectFirst("a.fed-list-title")!!.let { + setUrlWithoutDomain(it.attr("href")) + title = it.text() + } + thumbnail_url = element.selectFirst("a.fed-list-pics")?.absUrl("data-original") } - thumbnail_url = element.selectFirst("a.fed-list-pics")?.absUrl("data-original") - } - override fun latestUpdatesRequest(page: Int) = - GET("$baseUrl/show?orderBy=update&page=$page", headers) + override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/show?orderBy=update&page=$page", headers) override fun latestUpdatesSelector() = popularMangaSelector() @@ -86,26 +88,39 @@ abstract class ColaManga( override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector() - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val url = if (query.isNotEmpty()) { - "$baseUrl/search".toHttpUrl().newBuilder().apply { - filters.ifEmpty { getFilterList() } - .firstOrNull { it is SearchTypeFilter } - ?.let { (it as SearchTypeFilter).addToUri(this) } - - addQueryParameter("searchString", query) - addQueryParameter("page", page.toString()) - }.build() - } else { - "$baseUrl/show".toHttpUrl().newBuilder().apply { - filters.ifEmpty { getFilterList() } - .filterIsInstance() - .filterNot { it is SearchTypeFilter } - .forEach { it.addToUri(this) } - - addQueryParameter("page", page.toString()) - }.build() - } + override fun searchMangaRequest( + page: Int, + query: String, + filters: FilterList, + ): Request { + val url = + if (query.isNotEmpty()) { + "$baseUrl/search" + .toHttpUrl() + .newBuilder() + .apply { + filters + .ifEmpty { getFilterList() } + .firstOrNull { it is SearchTypeFilter } + ?.let { (it as SearchTypeFilter).addToUri(this) } + + addQueryParameter("searchString", query) + addQueryParameter("page", page.toString()) + }.build() + } else { + "$baseUrl/show" + .toHttpUrl() + .newBuilder() + .apply { + filters + .ifEmpty { getFilterList() } + .filterIsInstance() + .filterNot { it is SearchTypeFilter } + .forEach { it.addToUri(this) } + + addQueryParameter("page", page.toString()) + }.build() + } return GET(url, headers) } @@ -114,8 +129,8 @@ abstract class ColaManga( page: Int, query: String, filters: FilterList, - ): Observable { - return if (query.startsWith(PREFIX_SLUG_SEARCH)) { + ): Observable = + if (query.startsWith(PREFIX_SLUG_SEARCH)) { val slug = query.removePrefix(PREFIX_SLUG_SEARCH) val url = "/$slug/" @@ -124,7 +139,6 @@ abstract class ColaManga( } else { super.fetchSearchManga(page, query, filters) } - } override fun searchMangaSelector() = "dl.fed-deta-info, ${popularMangaSelector()}" @@ -151,27 +165,31 @@ abstract class ColaManga( protected abstract val statusOngoing: String protected abstract val statusCompleted: String - override fun mangaDetailsParse(document: Document) = SManga.create().apply { - title = document.selectFirst("h1.fed-part-eone")!!.text() - thumbnail_url = document.selectFirst("a.fed-list-pics")?.absUrl("data-original") - author = document.selectFirst("span.fed-text-muted:contains($authorTitle) + a")?.text() - genre = document.select("span.fed-text-muted:contains($genreTitle) ~ a").joinToString { it.text() } - description = document - .selectFirst("ul.fed-part-rows li.fed-col-xs12.fed-show-md-block .fed-part-esan") - ?.ownText() - status = when (document.selectFirst("span.fed-text-muted:contains($statusTitle) + a")?.text()) { - statusOngoing -> SManga.ONGOING - statusCompleted -> SManga.COMPLETED - else -> SManga.UNKNOWN + override fun mangaDetailsParse(document: Document) = + SManga.create().apply { + title = document.selectFirst("h1.fed-part-eone")!!.text() + thumbnail_url = document.selectFirst("a.fed-list-pics")?.absUrl("data-original") + author = document.selectFirst("span.fed-text-muted:contains($authorTitle) + a")?.text() + genre = document.select("span.fed-text-muted:contains($genreTitle) ~ a").joinToString { it.text() } + description = + document + .selectFirst("ul.fed-part-rows li.fed-col-xs12.fed-show-md-block .fed-part-esan") + ?.ownText() + status = + when (document.selectFirst("span.fed-text-muted:contains($statusTitle) + a")?.text()) { + statusOngoing -> SManga.ONGOING + statusCompleted -> SManga.COMPLETED + else -> SManga.UNKNOWN + } } - } override fun chapterListSelector(): String = "div:not(.fed-hidden) > div.all_data_list > ul.fed-part-rows a" - override fun chapterFromElement(element: Element) = SChapter.create().apply { - setUrlWithoutDomain(element.attr("href")) - name = element.attr("title") - } + override fun chapterFromElement(element: Element) = + SChapter.create().apply { + setUrlWithoutDomain(element.attr("href")) + name = element.attr("title") + } @SuppressLint("SetJavaScriptEnabled") override fun pageListParse(document: Document): List { @@ -222,80 +240,96 @@ abstract class ColaManga( throw Exception(intl.timedOutDecryptingImageLinks) } - val key = if (jsInterface.keyType.isNotEmpty()) { - keyMapping[jsInterface.keyType] - ?: throw Exception(intl.couldNotFindKey(jsInterface.keyType)) - } else { - jsInterface.key - } + val key = + if (jsInterface.keyType.isNotEmpty()) { + keyMapping[jsInterface.keyType] + ?: throw Exception(intl.couldNotFindKey(jsInterface.keyType)) + } else { + jsInterface.key + } return jsInterface.images.mapIndexed { i, it -> - val imageUrl = buildString(it.length + 6) { - if (it.startsWith("//")) { - append("https:") + val imageUrl = + buildString(it.length + 6) { + if (it.startsWith("//")) { + append("https:") + } + + append(it) + + if (key.isNotEmpty()) { + append("#") + append(ColaMangaImageInterceptor.KEY_PREFIX) + append(key) + } } - append(it) - - if (key.isNotEmpty()) { - append("#") - append(ColaMangaImageInterceptor.KEY_PREFIX) - append(key) - } - } - Page(i, imageUrl = imageUrl) } } override fun imageUrlParse(document: Document) = throw UnsupportedOperationException() - override fun getFilterList() = FilterList( - SearchTypeFilter(intl), - ) + override fun getFilterList() = + FilterList( + SearchTypeFilter(intl), + ) override fun setupPreferenceScreen(screen: PreferenceScreen) { - ListPreference(screen.context).apply { - key = RATE_LIMIT_PREF_KEY - title = intl.rateLimitPrefTitle - summary = intl.rateLimitPrefSummary(RATE_LIMIT_PREF_DEFAULT) - entries = RATE_LIMIT_PREF_ENTRIES - entryValues = RATE_LIMIT_PREF_ENTRIES - - setDefaultValue(RATE_LIMIT_PREF_DEFAULT) - }.also(screen::addPreference) - - ListPreference(screen.context).apply { - key = RATE_LIMIT_PERIOD_PREF_KEY - title = intl.rateLimitPeriodPrefTitle - summary = intl.rateLimitPeriodPrefSummary(RATE_LIMIT_PERIOD_PREF_DEFAULT) - entries = RATE_LIMIT_PERIOD_PREF_ENTRIES - entryValues = RATE_LIMIT_PERIOD_PREF_ENTRIES - - setDefaultValue(RATE_LIMIT_PERIOD_PREF_DEFAULT) - }.also(screen::addPreference) + ListPreference(screen.context) + .apply { + key = RATE_LIMIT_PREF_KEY + title = intl.rateLimitPrefTitle + summary = intl.rateLimitPrefSummary(RATE_LIMIT_PREF_DEFAULT) + entries = RATE_LIMIT_PREF_ENTRIES + entryValues = RATE_LIMIT_PREF_ENTRIES + + setDefaultValue(RATE_LIMIT_PREF_DEFAULT) + }.also(screen::addPreference) + + ListPreference(screen.context) + .apply { + key = RATE_LIMIT_PERIOD_PREF_KEY + title = intl.rateLimitPeriodPrefTitle + summary = intl.rateLimitPeriodPrefSummary(RATE_LIMIT_PERIOD_PREF_DEFAULT) + entries = RATE_LIMIT_PERIOD_PREF_ENTRIES + entryValues = RATE_LIMIT_PERIOD_PREF_ENTRIES + + setDefaultValue(RATE_LIMIT_PERIOD_PREF_DEFAULT) + }.also(screen::addPreference) } - private val keyMappingRegex = Regex("""if\s*\(\s*([a-zA-Z0-9_]+)\s*==\s*(?\d+)\s*\)\s*\{\s*return\s*'(?[a-zA-Z0-9_]+)'\s*;""") + private val keyMappingRegex = + Regex("""if\s*\(\s*([a-zA-Z0-9_]+)\s*==\s*(?\d+)\s*\)\s*\{\s*return\s*'(?[a-zA-Z0-9_]+)'\s*;""") private val keyMapping by lazy { - val obfuscatedReadJs = client.newCall(GET("$baseUrl/js/manga.read.js")).execute().body.string() - val readJs = Deobfuscator.deobfuscateScript(obfuscatedReadJs) - ?: throw Exception(intl.couldNotDeobufscateScript) + val obfuscatedReadJs = + client + .newCall(GET("$baseUrl/js/manga.read.js")) + .execute() + .body + .string() + val readJs = + Deobfuscator.deobfuscateScript(obfuscatedReadJs) + ?: throw Exception(intl.couldNotDeobufscateScript) keyMappingRegex.findAll(readJs).associate { it.groups["keyType"]!!.value to it.groups["key"]!!.value } } - private fun randomString() = buildString(15) { - val charPool = ('a'..'z') + ('A'..'Z') + private fun randomString() = + buildString(15) { + val charPool = ('a'..'z') + ('A'..'Z') - for (i in 0 until 15) { - append(charPool.random()) + for (i in 0 until 15) { + append(charPool.random()) + } } - } @Suppress("UNUSED") - private class JsInterface(private val latch: CountDownLatch, private val json: Json) { + private class JsInterface( + private val latch: CountDownLatch, + private val json: Json, + ) { var images: List = listOf() private set @@ -306,7 +340,10 @@ abstract class ColaManga( private set @JavascriptInterface - fun passData(rawData: String, keyType: String) { + fun passData( + rawData: String, + keyType: String, + ) { val data = json.parseToJsonElement(rawData).jsonObject images = data["images"]!!.jsonArray.map { it.jsonPrimitive.content } diff --git a/lib-multisrc/colamanga/src/eu/kanade/tachiyomi/multisrc/colamanga/ColaMangaFilters.kt b/lib-multisrc/colamanga/src/eu/kanade/tachiyomi/multisrc/colamanga/ColaMangaFilters.kt index 8f21403da6..901317833b 100644 --- a/lib-multisrc/colamanga/src/eu/kanade/tachiyomi/multisrc/colamanga/ColaMangaFilters.kt +++ b/lib-multisrc/colamanga/src/eu/kanade/tachiyomi/multisrc/colamanga/ColaMangaFilters.kt @@ -12,7 +12,8 @@ open class UriPartFilter( private val param: String, private val vals: Array>, state: Int = 0, -) : Filter.Select(name, vals.map { it.first }.toTypedArray(), state), UriFilter { +) : Filter.Select(name, vals.map { it.first }.toTypedArray(), state), + UriFilter { override fun addToUri(builder: HttpUrl.Builder) { val uriPart = vals[state].second @@ -22,11 +23,13 @@ open class UriPartFilter( } } -class SearchTypeFilter(intl: ColaMangaIntl) : UriPartFilter( - intl.searchType, - "type", - arrayOf( - intl.searchTypeFuzzy to "1", - intl.searchTypeExact to "2", - ), -) +class SearchTypeFilter( + intl: ColaMangaIntl, +) : UriPartFilter( + intl.searchType, + "type", + arrayOf( + intl.searchTypeFuzzy to "1", + intl.searchTypeExact to "2", + ), + ) diff --git a/lib-multisrc/colamanga/src/eu/kanade/tachiyomi/multisrc/colamanga/ColaMangaImageInterceptor.kt b/lib-multisrc/colamanga/src/eu/kanade/tachiyomi/multisrc/colamanga/ColaMangaImageInterceptor.kt index 0a7d451264..a120dd7d86 100644 --- a/lib-multisrc/colamanga/src/eu/kanade/tachiyomi/multisrc/colamanga/ColaMangaImageInterceptor.kt +++ b/lib-multisrc/colamanga/src/eu/kanade/tachiyomi/multisrc/colamanga/ColaMangaImageInterceptor.kt @@ -20,13 +20,18 @@ class ColaMangaImageInterceptor : Interceptor { return response } - val key = request.url.fragment!!.substringAfter(KEY_PREFIX).toByteArray() - val output = Cipher.getInstance("AES/CBC/PKCS7Padding").let { - it.init(Cipher.DECRYPT_MODE, SecretKeySpec(key, "AES"), IvParameterSpec(iv)) - it.doFinal(response.body.bytes()) - } + val key = + request.url.fragment!! + .substringAfter(KEY_PREFIX) + .toByteArray() + val output = + Cipher.getInstance("AES/CBC/PKCS7Padding").let { + it.init(Cipher.DECRYPT_MODE, SecretKeySpec(key, "AES"), IvParameterSpec(iv)) + it.doFinal(response.body.bytes()) + } - return response.newBuilder() + return response + .newBuilder() .body(output.toResponseBody(mediaType)) .build() } diff --git a/lib-multisrc/colamanga/src/eu/kanade/tachiyomi/multisrc/colamanga/ColaMangaIntl.kt b/lib-multisrc/colamanga/src/eu/kanade/tachiyomi/multisrc/colamanga/ColaMangaIntl.kt index 0bebd6aeef..2a0de60d35 100644 --- a/lib-multisrc/colamanga/src/eu/kanade/tachiyomi/multisrc/colamanga/ColaMangaIntl.kt +++ b/lib-multisrc/colamanga/src/eu/kanade/tachiyomi/multisrc/colamanga/ColaMangaIntl.kt @@ -1,51 +1,62 @@ package eu.kanade.tachiyomi.multisrc.colamanga -class ColaMangaIntl(private val lang: String) { - - val rateLimitPrefTitle = when (lang) { - "zh" -> "主站连接限制" - else -> "Rate limit" - } - - fun rateLimitPrefSummary(defaultValue: String) = when (lang) { - "zh" -> "此值影响主站的连接请求量。降低此值可以减少获得HTTP 403错误的几率,但加载速度也会变慢。需要重启软件以生效。\n默认值:$defaultValue\n当前值:%s" - else -> "Number of requests made to the website. Lowering this value may reduce the chance of getting HTTP 403. Tachiyomi restart required.\nDefault value: $defaultValue\nCurrent value: %s" - } - - val rateLimitPeriodPrefTitle = when (lang) { - "zh" -> "主站连接限制期" - else -> "Rate limit period" - } - - fun rateLimitPeriodPrefSummary(defaultValue: String) = when (lang) { - "zh" -> "此值影响主站点连接限制时的延迟(毫秒)。增加这个值可能会减少出现HTTP 403错误的机会,但加载速度也会变慢。需要重启软件以生效。\n默认值:$defaultValue\n当前值:%s" - else -> "Time in milliseconds to wait after using up all allowed requests. Lowering this value may reduce the chance of getting HTTP 403. Tachiyomi restart required.\nDefault value: $defaultValue\nCurrent value: %s" - } - - val timedOutDecryptingImageLinks = when (lang) { - else -> "Timed out decrypting image links" - } - - val couldNotDeobufscateScript = when (lang) { - else -> "Could not deobfuscate script" - } - - fun couldNotFindKey(forKeyType: String) = when (lang) { - else -> "Could not find key for keyType $forKeyType" - } - - val searchType = when (lang) { - "zh" -> "搜索类型" - else -> "Search type" - } - - val searchTypeFuzzy = when (lang) { - "zh" -> "模糊" - else -> "Fuzzy" - } - - val searchTypeExact = when (lang) { - "zh" -> "精确" - else -> "Exact" - } +class ColaMangaIntl( + private val lang: String, +) { + val rateLimitPrefTitle = + when (lang) { + "zh" -> "主站连接限制" + else -> "Rate limit" + } + + fun rateLimitPrefSummary(defaultValue: String) = + when (lang) { + "zh" -> "此值影响主站的连接请求量。降低此值可以减少获得HTTP 403错误的几率,但加载速度也会变慢。需要重启软件以生效。\n默认值:$defaultValue\n当前值:%s" + else -> "Number of requests made to the website. Lowering this value may reduce the chance of getting HTTP 403. Tachiyomi restart required.\nDefault value: $defaultValue\nCurrent value: %s" + } + + val rateLimitPeriodPrefTitle = + when (lang) { + "zh" -> "主站连接限制期" + else -> "Rate limit period" + } + + fun rateLimitPeriodPrefSummary(defaultValue: String) = + when (lang) { + "zh" -> "此值影响主站点连接限制时的延迟(毫秒)。增加这个值可能会减少出现HTTP 403错误的机会,但加载速度也会变慢。需要重启软件以生效。\n默认值:$defaultValue\n当前值:%s" + else -> "Time in milliseconds to wait after using up all allowed requests. Lowering this value may reduce the chance of getting HTTP 403. Tachiyomi restart required.\nDefault value: $defaultValue\nCurrent value: %s" + } + + val timedOutDecryptingImageLinks = + when (lang) { + else -> "Timed out decrypting image links" + } + + val couldNotDeobufscateScript = + when (lang) { + else -> "Could not deobfuscate script" + } + + fun couldNotFindKey(forKeyType: String) = + when (lang) { + else -> "Could not find key for keyType $forKeyType" + } + + val searchType = + when (lang) { + "zh" -> "搜索类型" + else -> "Search type" + } + + val searchTypeFuzzy = + when (lang) { + "zh" -> "模糊" + else -> "Fuzzy" + } + + val searchTypeExact = + when (lang) { + "zh" -> "精确" + else -> "Exact" + } } diff --git a/lib-multisrc/colamanga/src/eu/kanade/tachiyomi/multisrc/colamanga/ColaMangaUrlActivity.kt b/lib-multisrc/colamanga/src/eu/kanade/tachiyomi/multisrc/colamanga/ColaMangaUrlActivity.kt index 309b1e7655..4cdd79f320 100644 --- a/lib-multisrc/colamanga/src/eu/kanade/tachiyomi/multisrc/colamanga/ColaMangaUrlActivity.kt +++ b/lib-multisrc/colamanga/src/eu/kanade/tachiyomi/multisrc/colamanga/ColaMangaUrlActivity.kt @@ -14,11 +14,12 @@ class ColaMangaUrlActivity : Activity() { val pathSegments = intent?.data?.pathSegments if (pathSegments != null && pathSegments.size > 0) { - val intent = Intent().apply { - action = "eu.kanade.tachiyomi.SEARCH" - putExtra("query", "${ColaManga.PREFIX_SLUG_SEARCH}${pathSegments[0]}") - putExtra("filter", packageName) - } + val intent = + Intent().apply { + action = "eu.kanade.tachiyomi.SEARCH" + putExtra("query", "${ColaManga.PREFIX_SLUG_SEARCH}${pathSegments[0]}") + putExtra("filter", packageName) + } try { startActivity(intent) diff --git a/lib-multisrc/colorlibanime/src/eu/kanade/tachiyomi/multisrc/colorlibanime/ColorlibAnime.kt b/lib-multisrc/colorlibanime/src/eu/kanade/tachiyomi/multisrc/colorlibanime/ColorlibAnime.kt index a0888fe543..eb97b8e4fb 100644 --- a/lib-multisrc/colorlibanime/src/eu/kanade/tachiyomi/multisrc/colorlibanime/ColorlibAnime.kt +++ b/lib-multisrc/colorlibanime/src/eu/kanade/tachiyomi/multisrc/colorlibanime/ColorlibAnime.kt @@ -21,46 +21,47 @@ abstract class ColorlibAnime( override val baseUrl: String, override val lang: String, ) : ParsedHttpSource() { - override val supportsLatest = true - override val client: OkHttpClient = network.cloudflareClient.newBuilder() - .rateLimit(3) - .build() + override val client: OkHttpClient = + network.cloudflareClient + .newBuilder() + .rateLimit(3) + .build() - private fun Element.toThumbnail(): String { - return this.select(".set-bg").attr("abs:data-setbg").substringBeforeLast("?") - } + private fun Element.toThumbnail(): String = this.select(".set-bg").attr("abs:data-setbg").substringBeforeLast("?") // Search - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val url = baseUrl.toHttpUrl().newBuilder().apply { - addPathSegment("manga") - addQueryParameter("page", page.toString()) - addQueryParameter("sort", filters.findInstance()!!.toUriPart()) - addQueryParameter("search", query) - } + override fun searchMangaRequest( + page: Int, + query: String, + filters: FilterList, + ): Request { + val url = + baseUrl.toHttpUrl().newBuilder().apply { + addPathSegment("manga") + addQueryParameter("page", page.toString()) + addQueryParameter("sort", filters.findInstance()!!.toUriPart()) + addQueryParameter("search", query) + } return GET(url.build(), headers) } override fun searchMangaSelector(): String = ".product__page__content > [style]:has(.col-6) .product__item" - override fun searchMangaFromElement(element: Element): SManga { - return SManga.create().apply { + override fun searchMangaFromElement(element: Element): SManga = + SManga.create().apply { setUrlWithoutDomain(element.select("a.img-link").attr("abs:href")) title = element.select("h5").text() thumbnail_url = element.toThumbnail() } - } override fun searchMangaNextPageSelector(): String? = ".fa-angle-right" // Popular - override fun popularMangaRequest(page: Int): Request { - return searchMangaRequest(page, "", FilterList(OrderFilter(0))) - } + override fun popularMangaRequest(page: Int): Request = searchMangaRequest(page, "", FilterList(OrderFilter(0))) override fun popularMangaSelector(): String = searchMangaSelector() @@ -70,9 +71,7 @@ abstract class ColorlibAnime( // Latest - override fun latestUpdatesRequest(page: Int): Request { - return searchMangaRequest(page, "", FilterList(OrderFilter(1))) - } + override fun latestUpdatesRequest(page: Int): Request = searchMangaRequest(page, "", FilterList(OrderFilter(1))) override fun latestUpdatesSelector(): String = searchMangaSelector() @@ -89,11 +88,12 @@ abstract class ColorlibAnime( author = element.select("h3 + span").text() description = element.select("p").text() thumbnail_url = element.first()?.toThumbnail() - status = when (element.select("li:contains(status)").text().substringAfter(" ")) { - "Ongoing" -> SManga.ONGOING - "Complete" -> SManga.COMPLETED - else -> SManga.UNKNOWN - } + status = + when (element.select("li:contains(status)").text().substringAfter(" ")) { + "Ongoing" -> SManga.ONGOING + "Complete" -> SManga.COMPLETED + else -> SManga.UNKNOWN + } } } } @@ -105,54 +105,57 @@ abstract class ColorlibAnime( override fun chapterListParse(response: Response): List { val doc = response.asJsoup() - val time = timeRegex.find(doc.select("script:containsData(lastUpdated)").html()) - ?.let { it.groupValues[1].toLong() } ?: 0 + val time = + timeRegex + .find(doc.select("script:containsData(lastUpdated)").html()) + ?.let { it.groupValues[1].toLong() } ?: 0 - return doc.select(chapterListSelector()) + return doc + .select(chapterListSelector()) .map { chapterFromElement(it) } .apply { this.first().date_upload = time } } override fun chapterListSelector(): String = ".anime__details__episodes a" - override fun chapterFromElement(element: Element): SChapter { - return SChapter.create().apply { + override fun chapterFromElement(element: Element): SChapter = + SChapter.create().apply { setUrlWithoutDomain(element.attr("abs:href")) name = element.text() date_upload = 0L } - } // Pages - override fun pageListParse(document: Document): List { - return document.select(".container .read-img > img").mapIndexed { i, element -> + override fun pageListParse(document: Document): List = + document.select(".container .read-img > img").mapIndexed { i, element -> Page(i, "", element.attr("abs:src")) } - } override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException() // Filters - override fun getFilterList() = FilterList( - OrderFilter(), - ) - - class OrderFilter(state: Int = 0) : UriPartFilter( - "Order By", - arrayOf( - Pair("Views", "view"), - Pair("Updated", "updated"), - ), - state, - ) + override fun getFilterList() = + FilterList( + OrderFilter(), + ) + + class OrderFilter( + state: Int = 0, + ) : UriPartFilter( + "Order By", + arrayOf( + Pair("Views", "view"), + Pair("Updated", "updated"), + ), + state, + ) open class UriPartFilter( displayName: String, private val vals: Array>, state: Int = 0, - ) : - Filter.Select(displayName, vals.map { it.first }.toTypedArray(), state) { + ) : Filter.Select(displayName, vals.map { it.first }.toTypedArray(), state) { fun toUriPart() = vals[state].second } diff --git a/lib-multisrc/comicgamma/src/eu/kanade/tachiyomi/multisrc/comicgamma/ComicGamma.kt b/lib-multisrc/comicgamma/src/eu/kanade/tachiyomi/multisrc/comicgamma/ComicGamma.kt index deb83274e5..84ac8a5d9c 100644 --- a/lib-multisrc/comicgamma/src/eu/kanade/tachiyomi/multisrc/comicgamma/ComicGamma.kt +++ b/lib-multisrc/comicgamma/src/eu/kanade/tachiyomi/multisrc/comicgamma/ComicGamma.kt @@ -28,39 +28,58 @@ open class ComicGamma( private val json = Injekt.get() - override val client = network.client.newBuilder() - .addInterceptor(SpeedBinbInterceptor(json)) - .build() + override val client = + network.client + .newBuilder() + .addInterceptor(SpeedBinbInterceptor(json)) + .build() override fun popularMangaRequest(page: Int) = GET("$baseUrl/manga/", headers) + override fun popularMangaNextPageSelector(): String? = null + override fun popularMangaSelector() = ".tab_panel.active .manga_item" - override fun popularMangaFromElement(element: Element) = SManga.create().apply { - url = element.selectFirst(Evaluator.Tag("a"))!!.attr("href") - title = element.selectFirst(Evaluator.Class("manga_title"))!!.text() - author = element.selectFirst(Evaluator.Class("manga_author"))!!.text() - val genreList = element.select(Evaluator.Tag("li")).map { it.text() } - genre = genreList.joinToString() - status = when { - genreList.contains("完結") && !genreList.contains("リピート配信") -> SManga.COMPLETED - else -> SManga.ONGOING + + override fun popularMangaFromElement(element: Element) = + SManga.create().apply { + url = element.selectFirst(Evaluator.Tag("a"))!!.attr("href") + title = element.selectFirst(Evaluator.Class("manga_title"))!!.text() + author = element.selectFirst(Evaluator.Class("manga_author"))!!.text() + val genreList = element.select(Evaluator.Tag("li")).map { it.text() } + genre = genreList.joinToString() + status = + when { + genreList.contains("完結") && !genreList.contains("リピート配信") -> SManga.COMPLETED + else -> SManga.ONGOING + } + thumbnail_url = element.selectFirst(Evaluator.Tag("img"))!!.absUrl("src") } - thumbnail_url = element.selectFirst(Evaluator.Tag("img"))!!.absUrl("src") - } override fun latestUpdatesRequest(page: Int) = throw UnsupportedOperationException() + override fun latestUpdatesNextPageSelector() = throw UnsupportedOperationException() + override fun latestUpdatesSelector() = throw UnsupportedOperationException() + override fun latestUpdatesFromElement(element: Element) = throw UnsupportedOperationException() - override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable = - fetchPopularManga(page).map { p -> MangasPage(p.mangas.filter { it.title.contains(query) }, false) } + override fun fetchSearchManga( + page: Int, + query: String, + filters: FilterList, + ): Observable = fetchPopularManga(page).map { p -> MangasPage(p.mangas.filter { it.title.contains(query) }, false) } override fun searchMangaNextPageSelector() = throw UnsupportedOperationException() + override fun searchMangaSelector() = throw UnsupportedOperationException() + override fun searchMangaFromElement(element: Element) = throw UnsupportedOperationException() - override fun searchMangaRequest(page: Int, query: String, filters: FilterList) = - throw UnsupportedOperationException() + + override fun searchMangaRequest( + page: Int, + query: String, + filters: FilterList, + ) = throw UnsupportedOperationException() private val reader by lazy { SpeedBinbReader(client, headers, json) } @@ -69,10 +88,11 @@ open class ComicGamma( override fun mangaDetailsParse(document: Document): SManga { val titleElement = document.selectFirst(Evaluator.Class("manga__title"))!! val titleName = titleElement.child(0).text() - val desc = document.selectFirst(".detail__item > p:not(:empty)")?.run { - select(Evaluator.Tag("br")).prepend("\\n") - this.text().replace("\\n", "\n").replace("\n ", "\n") - } + val desc = + document.selectFirst(".detail__item > p:not(:empty)")?.run { + select(Evaluator.Tag("br")).prepend("\\n") + this.text().replace("\\n", "\n").replace("\n ", "\n") + } val listResponse = client.newCall(popularMangaRequest(0)).execute() val manga = popularMangaParse(listResponse).mangas.find { it.title == titleName } return manga?.apply { description = desc } ?: SManga.create().apply { @@ -85,25 +105,27 @@ open class ComicGamma( } override fun chapterListSelector() = ".read__area .read__outer > a:not([href=#comics])" - override fun chapterFromElement(element: Element) = SChapter.create().apply { - url = element.attr("href").toOldChapterUrl() - val number = url.removeSuffix("/").substringAfterLast('/').replace('_', '.') - val list = element.selectFirst(Evaluator.Class("read__contents"))!!.children() - name = "[$number] ${list[0].text()}" - if (list.size >= 3) { - date_upload = dateFormat.parseJST(list[2].text())?.time ?: 0L + + override fun chapterFromElement(element: Element) = + SChapter.create().apply { + url = element.attr("href").toOldChapterUrl() + val number = url.removeSuffix("/").substringAfterLast('/').replace('_', '.') + val list = element.selectFirst(Evaluator.Class("read__contents"))!!.children() + name = "[$number] ${list[0].text()}" + if (list.size >= 3) { + date_upload = dateFormat.parseJST(list[2].text())?.time ?: 0L + } } - } - override fun pageListRequest(chapter: SChapter) = - GET(baseUrl + chapter.url.toNewChapterUrl(), headers) + override fun pageListRequest(chapter: SChapter) = GET(baseUrl + chapter.url.toNewChapterUrl(), headers) override fun imageUrlParse(document: Document) = throw UnsupportedOperationException() companion object { - internal fun SimpleDateFormat.parseJST(date: String) = parse(date)?.apply { - time += 12 * 3600 * 1000 // updates at 12 noon - } + internal fun SimpleDateFormat.parseJST(date: String) = + parse(date)?.apply { + time += 12 * 3600 * 1000 // updates at 12 noon + } private fun getJSTFormat(datePattern: String) = SimpleDateFormat(datePattern, Locale.JAPANESE).apply { diff --git a/lib-multisrc/eromuse/src/eu/kanade/tachiyomi/multisrc/eromuse/EroMuse.kt b/lib-multisrc/eromuse/src/eu/kanade/tachiyomi/multisrc/eromuse/EroMuse.kt index 358fe9133c..12a87d257a 100644 --- a/lib-multisrc/eromuse/src/eu/kanade/tachiyomi/multisrc/eromuse/EroMuse.kt +++ b/lib-multisrc/eromuse/src/eu/kanade/tachiyomi/multisrc/eromuse/EroMuse.kt @@ -19,8 +19,10 @@ import org.jsoup.nodes.Element import rx.Observable @ExperimentalStdlibApi -open class EroMuse(override val name: String, override val baseUrl: String) : HttpSource() { - +open class EroMuse( + override val name: String, + override val baseUrl: String, +) : HttpSource() { override val lang = "en" override val supportsLatest = true @@ -35,14 +37,20 @@ open class EroMuse(override val name: String, override val baseUrl: String) : Ht */ // the stack - shouldn't need to touch these except for visibility - protected data class StackItem(val url: String, val pageType: Int) + protected data class StackItem( + val url: String, + val pageType: Int, + ) + private lateinit var stackItem: StackItem protected val pageStack = ArrayDeque() + companion object { const val VARIOUS_AUTHORS = 0 const val AUTHOR = 1 const val SEARCH_RESULTS_OR_BASE = 2 } + protected lateinit var currentSortingMode: String private val albums = getAlbumList() @@ -60,11 +68,12 @@ open class EroMuse(override val name: String, override val baseUrl: String) : Ht url.replace(pageQueryRegex, "page=$int") } else { val httpUrl = url.toHttpUrl() - val builder = if (httpUrl.pathSegments.last().toIntOrNull() is Int) { - httpUrl.newBuilder().removePathSegment(httpUrl.pathSegments.lastIndex) - } else { - httpUrl.newBuilder() - } + val builder = + if (httpUrl.pathSegments.last().toIntOrNull() is Int) { + httpUrl.newBuilder().removePathSegment(httpUrl.pathSegments.lastIndex) + } else { + httpUrl.newBuilder() + } builder.addPathSegment(int.toString()).toString() } } @@ -76,112 +85,141 @@ open class EroMuse(override val name: String, override val baseUrl: String) : Ht protected fun Element.imgAttr(): String = if (this.hasAttr("data-src")) this.attr("abs:data-src") else this.attr("abs:src") - private fun mangaFromElement(element: Element): SManga { - return SManga.create().apply { + private fun mangaFromElement(element: Element): SManga = + SManga.create().apply { setUrlWithoutDomain(element.attr("href")) title = element.text() thumbnail_url = element.select("img").firstOrNull()?.imgAttr() } - } - protected fun getAlbumType(url: String, default: Int = AUTHOR): Int { - return albums.filter { it.third != SEARCH_RESULTS_OR_BASE && url.contains(it.second, true) } - .getOrElse(0) { Triple(null, null, default) }.third - } + protected fun getAlbumType( + url: String, + default: Int = AUTHOR, + ): Int = + albums + .filter { it.third != SEARCH_RESULTS_OR_BASE && url.contains(it.second, true) } + .getOrElse(0) { Triple(null, null, default) } + .third protected fun parseManga(document: Document): MangasPage { fun internalParse(internalDocument: Document): List { - val authorDocument = if (stackItem.pageType == VARIOUS_AUTHORS) { - internalDocument.select(albumSelector).let { - elements -> - elements.reversed().map { pageStack.addLast(StackItem(it.attr("abs:href"), AUTHOR)) } + val authorDocument = + if (stackItem.pageType == VARIOUS_AUTHORS) { + internalDocument.select(albumSelector).let { elements -> + elements.reversed().map { pageStack.addLast(StackItem(it.attr("abs:href"), AUTHOR)) } + } + client.newCall(stackRequest()).execute().asJsoup() + } else { + internalDocument } - client.newCall(stackRequest()).execute().asJsoup() - } else { - internalDocument - } authorDocument.addNextPageToStack() return authorDocument.select(albumSelector).map { mangaFromElement(it) } } if (stackItem.pageType in listOf(VARIOUS_AUTHORS, SEARCH_RESULTS_OR_BASE)) document.addNextPageToStack() - val mangas = when (stackItem.pageType) { - VARIOUS_AUTHORS -> { - document.select(albumSelector).let { - elements -> - elements.reversed().map { pageStack.addLast(StackItem(it.attr("abs:href"), AUTHOR)) } + val mangas = + when (stackItem.pageType) { + VARIOUS_AUTHORS -> { + document.select(albumSelector).let { elements -> + elements.reversed().map { pageStack.addLast(StackItem(it.attr("abs:href"), AUTHOR)) } + } + internalParse(document) } - internalParse(document) - } - AUTHOR -> { - internalParse(document) - } - SEARCH_RESULTS_OR_BASE -> { - val searchMangas = mutableListOf() - document.select(albumSelector) - .map { element -> - val url = element.attr("abs:href") - val depth = url.removePrefix("$baseUrl/$topLevelPathSegment/").split("/").count() - - when (getAlbumType(url)) { - VARIOUS_AUTHORS -> { - when (depth) { - 1 -> { // eg. /comics/album/Fakku-Comics - pageStack.addLast(StackItem(url, VARIOUS_AUTHORS)) - if (searchMangas.isEmpty()) searchMangas += internalParse(client.newCall(stackRequest()).execute().asJsoup()) else null + AUTHOR -> { + internalParse(document) + } + SEARCH_RESULTS_OR_BASE -> { + val searchMangas = mutableListOf() + document + .select(albumSelector) + .map { element -> + val url = element.attr("abs:href") + val depth = url.removePrefix("$baseUrl/$topLevelPathSegment/").split("/").count() + + when (getAlbumType(url)) { + VARIOUS_AUTHORS -> { + when (depth) { + 1 -> { // eg. /comics/album/Fakku-Comics + pageStack.addLast(StackItem(url, VARIOUS_AUTHORS)) + if (searchMangas.isEmpty()) { + searchMangas += + internalParse(client.newCall(stackRequest()).execute().asJsoup()) + } else { + null + } + } + 2 -> { // eg. /comics/album/Fakku-Comics/Bosshi + pageStack.addLast(StackItem(url, AUTHOR)) + if (searchMangas.isEmpty()) { + searchMangas += + internalParse(client.newCall(stackRequest()).execute().asJsoup()) + } else { + null + } + } + else -> { + // eg. 3 -> /comics/album/Fakku-Comics/Bosshi/After-Summer-After + // eg. 5 -> /comics/album/Various-Authors/Firollian/Reward/Reward-22/ElfAlfie + // eg. 6 -> /comics/album/Various-Authors/Firollian/Area69/Area69-no_1/SamusAran/001_Dialogue + searchMangas.add(mangaFromElement(element)) + } } - 2 -> { // eg. /comics/album/Fakku-Comics/Bosshi + } + AUTHOR -> { + if (depth == 1) { // eg. /comics/album/ShadBase-Comics pageStack.addLast(StackItem(url, AUTHOR)) - if (searchMangas.isEmpty()) searchMangas += internalParse(client.newCall(stackRequest()).execute().asJsoup()) else null - } - else -> { - // eg. 3 -> /comics/album/Fakku-Comics/Bosshi/After-Summer-After - // eg. 5 -> /comics/album/Various-Authors/Firollian/Reward/Reward-22/ElfAlfie - // eg. 6 -> /comics/album/Various-Authors/Firollian/Area69/Area69-no_1/SamusAran/001_Dialogue + if (searchMangas.isEmpty()) { + searchMangas += + internalParse(client.newCall(stackRequest()).execute().asJsoup()) + } else { + null + } + } else { + // eg. 2 -> /comics/album/ShadBase-Comics/RickMorty + // eg. 3 -> /comics/album/Incase-Comics/Comic/Alfie searchMangas.add(mangaFromElement(element)) } } + else -> null // SEARCH_RESULTS_OR_BASE shouldn't be a case } - AUTHOR -> { - if (depth == 1) { // eg. /comics/album/ShadBase-Comics - pageStack.addLast(StackItem(url, AUTHOR)) - if (searchMangas.isEmpty()) searchMangas += internalParse(client.newCall(stackRequest()).execute().asJsoup()) else null - } else { - // eg. 2 -> /comics/album/ShadBase-Comics/RickMorty - // eg. 3 -> /comics/album/Incase-Comics/Comic/Alfie - searchMangas.add(mangaFromElement(element)) - } - } - else -> null // SEARCH_RESULTS_OR_BASE shouldn't be a case } - } - searchMangas + searchMangas + } + else -> emptyList() } - else -> emptyList() - } return MangasPage(mangas, pageStack.isNotEmpty()) } protected fun stackRequest(): Request { stackItem = pageStack.removeLast() - val url = if (stackItem.pageType == AUTHOR && currentSortingMode.isNotEmpty() && !stackItem.url.contains("sort")) { - stackItem.url.toHttpUrl().newBuilder().addQueryParameter("sort", currentSortingMode).toString() - } else { - stackItem.url - } + val url = + if (stackItem.pageType == AUTHOR && currentSortingMode.isNotEmpty() && !stackItem.url.contains("sort")) { + stackItem.url + .toHttpUrl() + .newBuilder() + .addQueryParameter("sort", currentSortingMode) + .toString() + } else { + stackItem.url + } return GET(url, headers) } // Popular - protected fun fetchManga(url: String, page: Int, sortingMode: String): Observable { + protected fun fetchManga( + url: String, + page: Int, + sortingMode: String, + ): Observable { if (page == 1) { pageStack.clear() pageStack.addLast(StackItem(url, VARIOUS_AUTHORS)) currentSortingMode = sortingMode } - return client.newCall(stackRequest()) + return client + .newCall(stackRequest()) .asObservableSuccess() .map { response -> parseManga(response.asJsoup()) } } @@ -189,18 +227,25 @@ open class EroMuse(override val name: String, override val baseUrl: String) : Ht override fun fetchPopularManga(page: Int): Observable = fetchManga("$baseUrl/comics/album/Various-Authors", page, "") override fun popularMangaRequest(page: Int): Request = throw UnsupportedOperationException() + override fun popularMangaParse(response: Response): MangasPage = throw UnsupportedOperationException() // Latest - override fun fetchLatestUpdates(page: Int): Observable = fetchManga("$baseUrl/comics/album/Various-Authors?sort=date", page, "date") + override fun fetchLatestUpdates(page: Int): Observable = + fetchManga("$baseUrl/comics/album/Various-Authors?sort=date", page, "date") override fun latestUpdatesRequest(page: Int): Request = throw UnsupportedOperationException() + override fun latestUpdatesParse(response: Response): MangasPage = throw UnsupportedOperationException() // Search - override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable { + override fun fetchSearchManga( + page: Int, + query: String, + filters: FilterList, + ): Observable { if (page == 1) { pageStack.clear() @@ -208,51 +253,59 @@ open class EroMuse(override val name: String, override val baseUrl: String) : Ht currentSortingMode = filterList.filterIsInstance().first().toQueryValue() if (query.isNotBlank()) { - val url = "$baseUrl/search?q=$query".toHttpUrl().newBuilder().apply { - if (currentSortingMode.isNotEmpty()) addQueryParameter("sort", currentSortingMode) - addQueryParameter("page", "1") - } + val url = + "$baseUrl/search?q=$query".toHttpUrl().newBuilder().apply { + if (currentSortingMode.isNotEmpty()) addQueryParameter("sort", currentSortingMode) + addQueryParameter("page", "1") + } pageStack.addLast(StackItem(url.toString(), SEARCH_RESULTS_OR_BASE)) } else { val albumFilter = filterList.filterIsInstance().first().selection() - val url = "$baseUrl/comics/${albumFilter.pathSegments}".toHttpUrl().newBuilder().apply { - if (currentSortingMode.isNotEmpty()) addQueryParameter("sort", currentSortingMode) - if (albumFilter.pageType != AUTHOR) addQueryParameter("page", "1") - } + val url = + "$baseUrl/comics/${albumFilter.pathSegments}".toHttpUrl().newBuilder().apply { + if (currentSortingMode.isNotEmpty()) addQueryParameter("sort", currentSortingMode) + if (albumFilter.pageType != AUTHOR) addQueryParameter("page", "1") + } pageStack.addLast(StackItem(url.toString(), albumFilter.pageType)) } } - return client.newCall(stackRequest()) + return client + .newCall(stackRequest()) .asObservableSuccess() .map { response -> parseManga(response.asJsoup()) } } - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = throw UnsupportedOperationException() + override fun searchMangaRequest( + page: Int, + query: String, + filters: FilterList, + ): Request = throw UnsupportedOperationException() + override fun searchMangaParse(response: Response): MangasPage = throw UnsupportedOperationException() // Details - override fun mangaDetailsParse(response: Response): SManga { - return SManga.create().apply { + override fun mangaDetailsParse(response: Response): SManga = + SManga.create().apply { with(response.asJsoup()) { setUrlWithoutDomain(response.request.url.toString()) thumbnail_url = select("$albumSelector img").firstOrNull()?.imgAttr() - author = when (getAlbumType(url)) { - AUTHOR -> { - // eg. https://comics.8muses.com/comics/album/ShadBase-Comics/RickMorty - // eg. https://comics.8muses.com/comics/album/Incase-Comics/Comic/Alfie - select("div.top-menu-breadcrumb li:nth-child(2)").text() - } - VARIOUS_AUTHORS -> { - // eg. https://comics.8muses.com/comics/album/Various-Authors/NLT-Media/A-Sunday-Schooling - select("div.top-menu-breadcrumb li:nth-child(3)").text() + author = + when (getAlbumType(url)) { + AUTHOR -> { + // eg. https://comics.8muses.com/comics/album/ShadBase-Comics/RickMorty + // eg. https://comics.8muses.com/comics/album/Incase-Comics/Comic/Alfie + select("div.top-menu-breadcrumb li:nth-child(2)").text() + } + VARIOUS_AUTHORS -> { + // eg. https://comics.8muses.com/comics/album/Various-Authors/NLT-Media/A-Sunday-Schooling + select("div.top-menu-breadcrumb li:nth-child(3)").text() + } + else -> null } - else -> null - } } } - } // Chapters @@ -260,9 +313,14 @@ open class EroMuse(override val name: String, override val baseUrl: String) : Ht protected open val pageThumbnailSelector = "a.c-tile:has(img)[href*=/comics/picture/] img" override fun chapterListParse(response: Response): List { - fun parseChapters(document: Document, isFirstPage: Boolean, chapters: ArrayDeque): List { + fun parseChapters( + document: Document, + isFirstPage: Boolean, + chapters: ArrayDeque, + ): List { // Linked chapters - document.select(linkedChapterSelector) + document + .select(linkedChapterSelector) .mapNotNull { chapters.addFirst( SChapter.create().apply { @@ -303,7 +361,8 @@ open class EroMuse(override val name: String, override val baseUrl: String) : Ht pages: ArrayList = ArrayList(), ): List { // Nested chapters aka folders - document.select(linkedChapterSelector) + document + .select(linkedChapterSelector) .mapNotNull { nestedChapterDocuments.add( client.newCall(GET(it.attr("abs:href"), headers)).execute().asJsoup(), @@ -317,8 +376,7 @@ open class EroMuse(override val name: String, override val baseUrl: String) : Ht }, ) - document.nextPageOrNull()?.let { - url -> + document.nextPageOrNull()?.let { url -> pages.addAll(parsePages(client.newCall(GET(url, headers)).execute().asJsoup(), nestedChapterDocuments, pages)) } @@ -336,102 +394,125 @@ open class EroMuse(override val name: String, override val baseUrl: String) : Ht // Filters - override fun getFilterList(): FilterList { - return FilterList( + override fun getFilterList(): FilterList = + FilterList( Filter.Header("Text search only combines with sort!"), Filter.Separator(), AlbumFilter(getAlbumList()), SortFilter(getSortList()), ) - } - protected class AlbumFilter(private val vals: Array>) : Filter.Select("Album", vals.map { it.first }.toTypedArray()) { + protected class AlbumFilter( + private val vals: Array>, + ) : Filter.Select( + "Album", + vals + .map { + it.first + }.toTypedArray(), + ) { fun selection() = AlbumFilterData(vals[state].second, vals[state].third) - data class AlbumFilterData(val pathSegments: String, val pageType: Int) + + data class AlbumFilterData( + val pathSegments: String, + val pageType: Int, + ) } - protected open fun getAlbumList() = arrayOf( - Triple("All Authors", "", SEARCH_RESULTS_OR_BASE), - Triple("Various Authors", "album/Various-Authors", VARIOUS_AUTHORS), - Triple("Fakku Comics", "album/Fakku-Comics", VARIOUS_AUTHORS), - Triple("Hentai and Manga English", "album/Hentai-and-Manga-English", VARIOUS_AUTHORS), - Triple("Fake Celebrities Sex Pictures", "album/Fake-Celebrities-Sex-Pictures", AUTHOR), - Triple("MilfToon Comics", "album/MilfToon-Comics", AUTHOR), - Triple("BE Story Club Comics", "album/BE-Story-Club-Comics", AUTHOR), - Triple("ShadBase Comics", "album/ShadBase-Comics", AUTHOR), - Triple("ZZZ Comics", "album/ZZZ-Comics", AUTHOR), - Triple("PalComix Comics", "album/PalComix-Comics", AUTHOR), - Triple("MCC Comics", "album/MCC-Comics", AUTHOR), - Triple("Expansionfan Comics", "album/Expansionfan-Comics", AUTHOR), - Triple("JAB Comics", "album/JAB-Comics", AUTHOR), - Triple("Giantess Fan Comics", "album/Giantess-Fan-Comics", AUTHOR), - Triple("Renderotica Comics", "album/Renderotica-Comics", AUTHOR), - Triple("IllustratedInterracial.com Comics", "album/IllustratedInterracial_com-Comics", AUTHOR), - Triple("Giantess Club Comics", "album/Giantess-Club-Comics", AUTHOR), - Triple("Innocent Dickgirls Comics", "album/Innocent-Dickgirls-Comics", AUTHOR), - Triple("Locofuria Comics", "album/Locofuria-Comics", AUTHOR), - Triple("PigKing - CrazyDad Comics", "album/PigKing-CrazyDad-Comics", AUTHOR), - Triple("Cartoon Reality Comics", "album/Cartoon-Reality-Comics", AUTHOR), - Triple("Affect3D Comics", "album/Affect3D-Comics", AUTHOR), - Triple("TG Comics", "album/TG-Comics", AUTHOR), - Triple("Melkormancin.com Comics", "album/Melkormancin_com-Comics", AUTHOR), - Triple("Seiren.com.br Comics", "album/Seiren_com_br-Comics", AUTHOR), - Triple("Tracy Scops Comics", "album/Tracy-Scops-Comics", AUTHOR), - Triple("Fred Perry Comics", "album/Fred-Perry-Comics", AUTHOR), - Triple("Witchking00 Comics", "album/Witchking00-Comics", AUTHOR), - Triple("8muses Comics", "album/8muses-Comics", AUTHOR), - Triple("KAOS Comics", "album/KAOS-Comics", AUTHOR), - Triple("Vaesark Comics", "album/Vaesark-Comics", AUTHOR), - Triple("Fansadox Comics", "album/Fansadox-Comics", AUTHOR), - Triple("DreamTales Comics", "album/DreamTales-Comics", AUTHOR), - Triple("Croc Comics", "album/Croc-Comics", AUTHOR), - Triple("Jay Marvel Comics", "album/Jay-Marvel-Comics", AUTHOR), - Triple("JohnPersons.com Comics", "album/JohnPersons_com-Comics", AUTHOR), - Triple("MuscleFan Comics", "album/MuscleFan-Comics", AUTHOR), - Triple("Taboolicious.xxx Comics", "album/Taboolicious_xxx-Comics", AUTHOR), - Triple("MongoBongo Comics", "album/MongoBongo-Comics", AUTHOR), - Triple("Slipshine Comics", "album/Slipshine-Comics", AUTHOR), - Triple("Everfire Comics", "album/Everfire-Comics", AUTHOR), - Triple("PrismGirls Comics", "album/PrismGirls-Comics", AUTHOR), - Triple("Abimboleb Comics", "album/Abimboleb-Comics", AUTHOR), - Triple("Y3DF - Your3DFantasy.com Comics", "album/Y3DF-Your3DFantasy_com-Comics", AUTHOR), - Triple("Grow Comics", "album/Grow-Comics", AUTHOR), - Triple("OkayOkayOKOk Comics", "album/OkayOkayOKOk-Comics", AUTHOR), - Triple("Tufos Comics", "album/Tufos-Comics", AUTHOR), - Triple("Cartoon Valley", "album/Cartoon-Valley", AUTHOR), - Triple("3DMonsterStories.com Comics", "album/3DMonsterStories_com-Comics", AUTHOR), - Triple("Kogeikun Comics", "album/Kogeikun-Comics", AUTHOR), - Triple("The Foxxx Comics", "album/The-Foxxx-Comics", AUTHOR), - Triple("Theme Collections", "album/Theme-Collections", AUTHOR), - Triple("Interracial-Comics", "album/Interracial-Comics", AUTHOR), - Triple("Expansion Comics", "album/Expansion-Comics", AUTHOR), - Triple("Moiarte Comics", "album/Moiarte-Comics", AUTHOR), - Triple("Incognitymous Comics", "album/Incognitymous-Comics", AUTHOR), - Triple("DizzyDills Comics", "album/DizzyDills-Comics", AUTHOR), - Triple("DukesHardcoreHoneys.com Comics", "album/DukesHardcoreHoneys_com-Comics", AUTHOR), - Triple("Stormfeder Comics", "album/Stormfeder-Comics", AUTHOR), - Triple("Bimbo Story Club Comics", "album/Bimbo-Story-Club-Comics", AUTHOR), - Triple("Smudge Comics", "album/Smudge-Comics", AUTHOR), - Triple("Dollproject Comics", "album/Dollproject-Comics", AUTHOR), - Triple("SuperHeroineComixxx", "album/SuperHeroineComixxx", AUTHOR), - Triple("Karmagik Comics", "album/Karmagik-Comics", AUTHOR), - Triple("Blacknwhite.com Comics", "album/Blacknwhite_com-Comics", AUTHOR), - Triple("ArtOfJaguar Comics", "album/ArtOfJaguar-Comics", AUTHOR), - Triple("Kirtu.com Comics", "album/Kirtu_com-Comics", AUTHOR), - Triple("UberMonkey Comics", "album/UberMonkey-Comics", AUTHOR), - Triple("DarkSoul3D Comics", "album/DarkSoul3D-Comics", AUTHOR), - Triple("Markydaysaid Comics", "album/Markydaysaid-Comics", AUTHOR), - Triple("Central Comics", "album/Central-Comics", AUTHOR), - Triple("Frozen Parody Comics", "album/Frozen-Parody-Comics", AUTHOR), - Triple("Blacknwhitecomics.com Comix", "album/Blacknwhitecomics_com-Comix", AUTHOR), - ) - protected class SortFilter(private val vals: Array>) : Filter.Select("Sort Order", vals.map { it.first }.toTypedArray()) { + protected open fun getAlbumList() = + arrayOf( + Triple("All Authors", "", SEARCH_RESULTS_OR_BASE), + Triple("Various Authors", "album/Various-Authors", VARIOUS_AUTHORS), + Triple("Fakku Comics", "album/Fakku-Comics", VARIOUS_AUTHORS), + Triple("Hentai and Manga English", "album/Hentai-and-Manga-English", VARIOUS_AUTHORS), + Triple("Fake Celebrities Sex Pictures", "album/Fake-Celebrities-Sex-Pictures", AUTHOR), + Triple("MilfToon Comics", "album/MilfToon-Comics", AUTHOR), + Triple("BE Story Club Comics", "album/BE-Story-Club-Comics", AUTHOR), + Triple("ShadBase Comics", "album/ShadBase-Comics", AUTHOR), + Triple("ZZZ Comics", "album/ZZZ-Comics", AUTHOR), + Triple("PalComix Comics", "album/PalComix-Comics", AUTHOR), + Triple("MCC Comics", "album/MCC-Comics", AUTHOR), + Triple("Expansionfan Comics", "album/Expansionfan-Comics", AUTHOR), + Triple("JAB Comics", "album/JAB-Comics", AUTHOR), + Triple("Giantess Fan Comics", "album/Giantess-Fan-Comics", AUTHOR), + Triple("Renderotica Comics", "album/Renderotica-Comics", AUTHOR), + Triple("IllustratedInterracial.com Comics", "album/IllustratedInterracial_com-Comics", AUTHOR), + Triple("Giantess Club Comics", "album/Giantess-Club-Comics", AUTHOR), + Triple("Innocent Dickgirls Comics", "album/Innocent-Dickgirls-Comics", AUTHOR), + Triple("Locofuria Comics", "album/Locofuria-Comics", AUTHOR), + Triple("PigKing - CrazyDad Comics", "album/PigKing-CrazyDad-Comics", AUTHOR), + Triple("Cartoon Reality Comics", "album/Cartoon-Reality-Comics", AUTHOR), + Triple("Affect3D Comics", "album/Affect3D-Comics", AUTHOR), + Triple("TG Comics", "album/TG-Comics", AUTHOR), + Triple("Melkormancin.com Comics", "album/Melkormancin_com-Comics", AUTHOR), + Triple("Seiren.com.br Comics", "album/Seiren_com_br-Comics", AUTHOR), + Triple("Tracy Scops Comics", "album/Tracy-Scops-Comics", AUTHOR), + Triple("Fred Perry Comics", "album/Fred-Perry-Comics", AUTHOR), + Triple("Witchking00 Comics", "album/Witchking00-Comics", AUTHOR), + Triple("8muses Comics", "album/8muses-Comics", AUTHOR), + Triple("KAOS Comics", "album/KAOS-Comics", AUTHOR), + Triple("Vaesark Comics", "album/Vaesark-Comics", AUTHOR), + Triple("Fansadox Comics", "album/Fansadox-Comics", AUTHOR), + Triple("DreamTales Comics", "album/DreamTales-Comics", AUTHOR), + Triple("Croc Comics", "album/Croc-Comics", AUTHOR), + Triple("Jay Marvel Comics", "album/Jay-Marvel-Comics", AUTHOR), + Triple("JohnPersons.com Comics", "album/JohnPersons_com-Comics", AUTHOR), + Triple("MuscleFan Comics", "album/MuscleFan-Comics", AUTHOR), + Triple("Taboolicious.xxx Comics", "album/Taboolicious_xxx-Comics", AUTHOR), + Triple("MongoBongo Comics", "album/MongoBongo-Comics", AUTHOR), + Triple("Slipshine Comics", "album/Slipshine-Comics", AUTHOR), + Triple("Everfire Comics", "album/Everfire-Comics", AUTHOR), + Triple("PrismGirls Comics", "album/PrismGirls-Comics", AUTHOR), + Triple("Abimboleb Comics", "album/Abimboleb-Comics", AUTHOR), + Triple("Y3DF - Your3DFantasy.com Comics", "album/Y3DF-Your3DFantasy_com-Comics", AUTHOR), + Triple("Grow Comics", "album/Grow-Comics", AUTHOR), + Triple("OkayOkayOKOk Comics", "album/OkayOkayOKOk-Comics", AUTHOR), + Triple("Tufos Comics", "album/Tufos-Comics", AUTHOR), + Triple("Cartoon Valley", "album/Cartoon-Valley", AUTHOR), + Triple("3DMonsterStories.com Comics", "album/3DMonsterStories_com-Comics", AUTHOR), + Triple("Kogeikun Comics", "album/Kogeikun-Comics", AUTHOR), + Triple("The Foxxx Comics", "album/The-Foxxx-Comics", AUTHOR), + Triple("Theme Collections", "album/Theme-Collections", AUTHOR), + Triple("Interracial-Comics", "album/Interracial-Comics", AUTHOR), + Triple("Expansion Comics", "album/Expansion-Comics", AUTHOR), + Triple("Moiarte Comics", "album/Moiarte-Comics", AUTHOR), + Triple("Incognitymous Comics", "album/Incognitymous-Comics", AUTHOR), + Triple("DizzyDills Comics", "album/DizzyDills-Comics", AUTHOR), + Triple("DukesHardcoreHoneys.com Comics", "album/DukesHardcoreHoneys_com-Comics", AUTHOR), + Triple("Stormfeder Comics", "album/Stormfeder-Comics", AUTHOR), + Triple("Bimbo Story Club Comics", "album/Bimbo-Story-Club-Comics", AUTHOR), + Triple("Smudge Comics", "album/Smudge-Comics", AUTHOR), + Triple("Dollproject Comics", "album/Dollproject-Comics", AUTHOR), + Triple("SuperHeroineComixxx", "album/SuperHeroineComixxx", AUTHOR), + Triple("Karmagik Comics", "album/Karmagik-Comics", AUTHOR), + Triple("Blacknwhite.com Comics", "album/Blacknwhite_com-Comics", AUTHOR), + Triple("ArtOfJaguar Comics", "album/ArtOfJaguar-Comics", AUTHOR), + Triple("Kirtu.com Comics", "album/Kirtu_com-Comics", AUTHOR), + Triple("UberMonkey Comics", "album/UberMonkey-Comics", AUTHOR), + Triple("DarkSoul3D Comics", "album/DarkSoul3D-Comics", AUTHOR), + Triple("Markydaysaid Comics", "album/Markydaysaid-Comics", AUTHOR), + Triple("Central Comics", "album/Central-Comics", AUTHOR), + Triple("Frozen Parody Comics", "album/Frozen-Parody-Comics", AUTHOR), + Triple("Blacknwhitecomics.com Comix", "album/Blacknwhitecomics_com-Comix", AUTHOR), + ) + + protected class SortFilter( + private val vals: Array>, + ) : Filter.Select( + "Sort Order", + vals + .map { + it.first + }.toTypedArray(), + ) { fun toQueryValue() = vals[state].second } - protected open fun getSortList() = arrayOf( - Pair("Views", ""), - Pair("Likes", "like"), - Pair("Date", "date"), - Pair("A-Z", "az"), - ) + + protected open fun getSortList() = + arrayOf( + Pair("Views", ""), + Pair("Likes", "like"), + Pair("Date", "date"), + Pair("A-Z", "az"), + ) } diff --git a/lib-multisrc/etoshore/src/eu/kanade/tachiyomi/multisrc/Etoshore.kt b/lib-multisrc/etoshore/src/eu/kanade/tachiyomi/multisrc/Etoshore.kt index 441b12ce5f..81c53f5e70 100644 --- a/lib-multisrc/etoshore/src/eu/kanade/tachiyomi/multisrc/Etoshore.kt +++ b/lib-multisrc/etoshore/src/eu/kanade/tachiyomi/multisrc/Etoshore.kt @@ -21,42 +21,56 @@ abstract class Etoshore( override val baseUrl: String, final override val lang: String, ) : ParsedHttpSource() { - override val supportsLatest = true override val client = network.cloudflareClient // ============================== Popular ============================== - open val popularFilter = FilterList( - SelectionList("", listOf(Tag(value = "views", query = "sort"))), - ) + open val popularFilter = + FilterList( + SelectionList("", listOf(Tag(value = "views", query = "sort"))), + ) override fun popularMangaRequest(page: Int) = searchMangaRequest(page, "", popularFilter) + override fun popularMangaParse(response: Response) = searchMangaParse(response) override fun popularMangaSelector() = throw UnsupportedOperationException() + override fun popularMangaNextPageSelector() = throw UnsupportedOperationException() + override fun popularMangaFromElement(element: Element) = throw UnsupportedOperationException() // ============================== Latest =============================== - open val latestFilter = FilterList( - SelectionList("", listOf(Tag(value = "date", query = "sort"))), - ) + open val latestFilter = + FilterList( + SelectionList("", listOf(Tag(value = "date", query = "sort"))), + ) override fun latestUpdatesRequest(page: Int) = searchMangaRequest(page, "", latestFilter) + override fun latestUpdatesParse(response: Response) = searchMangaParse(response) override fun latestUpdatesSelector() = throw UnsupportedOperationException() + override fun latestUpdatesNextPageSelector() = throw UnsupportedOperationException() + override fun latestUpdatesFromElement(element: Element) = throw UnsupportedOperationException() // ============================== Search =============================== - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val url = "$baseUrl/page/$page".toHttpUrl().newBuilder() - .addQueryParameter("s", query) + override fun searchMangaRequest( + page: Int, + query: String, + filters: FilterList, + ): Request { + val url = + "$baseUrl/page/$page" + .toHttpUrl() + .newBuilder() + .addQueryParameter("s", query) filters.forEach { filter -> when (filter) { @@ -71,7 +85,11 @@ abstract class Etoshore( return GET(url.build(), headers) } - override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable { + override fun fetchSearchManga( + page: Int, + query: String, + filters: FilterList, + ): Observable { if (query.startsWith(PREFIX_SEARCH)) { val slug = query.substringAfter(PREFIX_SEARCH) return fetchMangaDetails(SManga.create().apply { url = "/manga/$slug/" }) @@ -84,11 +102,12 @@ abstract class Etoshore( override fun searchMangaNextPageSelector() = ".navigation .naviright:has(a)" - override fun searchMangaFromElement(element: Element) = SManga.create().apply { - title = element.attr("title") - thumbnail_url = element.selectFirst("img")?.let(::imageFromElement) - setUrlWithoutDomain(element.absUrl("href")) - } + override fun searchMangaFromElement(element: Element) = + SManga.create().apply { + title = element.attr("title") + thumbnail_url = element.selectFirst("img")?.let(::imageFromElement) + setUrlWithoutDomain(element.absUrl("href")) + } override fun searchMangaParse(response: Response): MangasPage { if (filterList.isEmpty()) { @@ -99,82 +118,89 @@ abstract class Etoshore( // ============================== Details =============================== - override fun mangaDetailsParse(document: Document) = SManga.create().apply { - title = document.selectFirst("h1")!!.text() - description = document.selectFirst(".excerpt p")?.text() - document.selectFirst(".details-right-con img")?.let { thumbnail_url = imageFromElement(it) } - genre = document.select("div.meta-item span.meta-title:contains(Genres) + span a") - .joinToString { it.text() } - author = document.selectFirst("div.meta-item span.meta-title:contains(Author) + span a") - ?.text() - document.selectFirst(".status")?.text()?.let { - status = it.toMangaStatus() - } + override fun mangaDetailsParse(document: Document) = + SManga.create().apply { + title = document.selectFirst("h1")!!.text() + description = document.selectFirst(".excerpt p")?.text() + document.selectFirst(".details-right-con img")?.let { thumbnail_url = imageFromElement(it) } + genre = + document + .select("div.meta-item span.meta-title:contains(Genres) + span a") + .joinToString { it.text() } + author = + document + .selectFirst("div.meta-item span.meta-title:contains(Author) + span a") + ?.text() + document.selectFirst(".status")?.text()?.let { + status = it.toMangaStatus() + } - setUrlWithoutDomain(document.location()) - } + setUrlWithoutDomain(document.location()) + } - protected open fun imageFromElement(element: Element): String? { - return when { + protected open fun imageFromElement(element: Element): String? = + when { element.hasAttr("data-src") -> element.attr("abs:data-src") element.hasAttr("data-lazy-src") -> element.attr("abs:data-lazy-src") element.hasAttr("srcset") -> element.attr("abs:srcset").getSrcSetImage() element.hasAttr("data-cfsrc") -> element.attr("abs:data-cfsrc") else -> element.attr("abs:src") } - } - protected open fun String.getSrcSetImage(): String? { - return this.split(" ") + protected open fun String.getSrcSetImage(): String? = + this + .split(" ") .filter(URL_REGEX::matches) .maxOfOrNull(String::toString) - } - protected val completedStatusList: Array = arrayOf( - "Finished", - "Completo", - ) - - protected open val ongoingStatusList: Array = arrayOf( - "Publishing", - "Ativo", - ) - - protected val hiatusStatusList: Array = arrayOf( - "on hiatus", - ) - - protected val canceledStatusList: Array = arrayOf( - "Canceled", - "Discontinued", - ) - - open fun String.toMangaStatus(): Int { - return when { + protected val completedStatusList: Array = + arrayOf( + "Finished", + "Completo", + ) + + protected open val ongoingStatusList: Array = + arrayOf( + "Publishing", + "Ativo", + ) + + protected val hiatusStatusList: Array = + arrayOf( + "on hiatus", + ) + + protected val canceledStatusList: Array = + arrayOf( + "Canceled", + "Discontinued", + ) + + open fun String.toMangaStatus(): Int = + when { containsIn(completedStatusList) -> SManga.COMPLETED containsIn(ongoingStatusList) -> SManga.ONGOING containsIn(hiatusStatusList) -> SManga.ON_HIATUS containsIn(canceledStatusList) -> SManga.CANCELLED else -> SManga.UNKNOWN } - } // ============================== Chapters ============================ override fun chapterListSelector() = ".chapter-list li a" - override fun chapterFromElement(element: Element) = SChapter.create().apply { - name = element.selectFirst(".title")!!.text() - setUrlWithoutDomain(element.absUrl("href")) - } + override fun chapterFromElement(element: Element) = + SChapter.create().apply { + name = element.selectFirst(".title")!!.text() + setUrlWithoutDomain(element.absUrl("href")) + } // ============================== Pages =============================== - override fun pageListParse(document: Document): List { - return document.select(".chapter-images .chapter-item > img").mapIndexed { index, element -> + override fun pageListParse(document: Document): List = + document.select(".chapter-images .chapter-item > img").mapIndexed { index, element -> Page(index, imageUrl = imageFromElement(element)) } - } override fun imageUrlParse(document: Document) = "" @@ -185,55 +211,66 @@ abstract class Etoshore( override fun getFilterList(): FilterList { val filters = mutableListOf>() - filters += if (filterList.isNotEmpty()) { - filterList.map { SelectionList(it.first, it.second) } - } else { - listOf(Filter.Header("Aperte 'Redefinir' para tentar mostrar os filtros")) - } + filters += + if (filterList.isNotEmpty()) { + filterList.map { SelectionList(it.first, it.second) } + } else { + listOf(Filter.Header("Aperte 'Redefinir' para tentar mostrar os filtros")) + } return FilterList(filters) } - protected open fun parseSelection(document: Document, selector: String): Pair>? { + protected open fun parseSelection( + document: Document, + selector: String, + ): Pair>? { val selectorFilter = "#filter-form $selector .select-item-head .text" return document.selectFirst(selectorFilter)?.text()?.let { displayName -> - displayName to document.select("#filter-form $selector li").map { element -> - element.selectFirst("input")!!.let { input -> - Tag( - name = element.selectFirst(".text")!!.text(), - value = input.attr("value"), - query = input.attr("name"), - ) + displayName to + document.select("#filter-form $selector li").map { element -> + element.selectFirst("input")!!.let { input -> + Tag( + name = element.selectFirst(".text")!!.text(), + value = input.attr("value"), + query = input.attr("name"), + ) + } } - } } } - open val filterListSelector: List = listOf( - ".filter-genre", - ".filter-status", - ".filter-type", - ".filter-year", - ".filter-sort", - ) + open val filterListSelector: List = + listOf( + ".filter-genre", + ".filter-status", + ".filter-type", + ".filter-year", + ".filter-sort", + ) open fun filterParse(response: Response) { val document = Jsoup.parseBodyFragment(response.peekBody(Long.MAX_VALUE).string()) filterList = filterListSelector.mapNotNull { selector -> parseSelection(document, selector) } } - protected data class Tag(val name: String = "", val value: String = "", val query: String = "") + protected data class Tag( + val name: String = "", + val value: String = "", + val query: String = "", + ) - private open class SelectionList(displayName: String, private val vals: List, state: Int = 0) : - Filter.Select(displayName, vals.map { it.name }.toTypedArray(), state) { + private open class SelectionList( + displayName: String, + private val vals: List, + state: Int = 0, + ) : Filter.Select(displayName, vals.map { it.name }.toTypedArray(), state) { fun selected() = vals[state] } // ============================= Utils ============================== - private fun String.containsIn(array: Array): Boolean { - return this.lowercase() in array.map { it.lowercase() } - } + private fun String.containsIn(array: Array): Boolean = this.lowercase() in array.map { it.lowercase() } companion object { const val PREFIX_SEARCH = "id:" diff --git a/lib-multisrc/etoshore/src/eu/kanade/tachiyomi/multisrc/EtoshoreUrlActivity.kt b/lib-multisrc/etoshore/src/eu/kanade/tachiyomi/multisrc/EtoshoreUrlActivity.kt index 89c5dd58e3..55b0fe5e46 100644 --- a/lib-multisrc/etoshore/src/eu/kanade/tachiyomi/multisrc/EtoshoreUrlActivity.kt +++ b/lib-multisrc/etoshore/src/eu/kanade/tachiyomi/multisrc/EtoshoreUrlActivity.kt @@ -8,7 +8,6 @@ import android.util.Log import kotlin.system.exitProcess class EtoshoreUrlActivity : Activity() { - private val tag = javaClass.simpleName override fun onCreate(savedInstanceState: Bundle?) { @@ -16,11 +15,12 @@ class EtoshoreUrlActivity : Activity() { val pathSegments = intent?.data?.pathSegments if (pathSegments != null && pathSegments.size > 1) { val item = pathSegments[1] - val mainIntent = Intent().apply { - action = "eu.kanade.tachiyomi.SEARCH" - putExtra("query", "${Etoshore.PREFIX_SEARCH}$item") - putExtra("filter", packageName) - } + val mainIntent = + Intent().apply { + action = "eu.kanade.tachiyomi.SEARCH" + putExtra("query", "${Etoshore.PREFIX_SEARCH}$item") + putExtra("filter", packageName) + } try { startActivity(mainIntent) diff --git a/lib-multisrc/fansubscat/src/eu/kanade/tachiyomi/multisrc/fansubscat/FansubsCat.kt b/lib-multisrc/fansubscat/src/eu/kanade/tachiyomi/multisrc/fansubscat/FansubsCat.kt index feee849e4c..20c972f518 100644 --- a/lib-multisrc/fansubscat/src/eu/kanade/tachiyomi/multisrc/fansubscat/FansubsCat.kt +++ b/lib-multisrc/fansubscat/src/eu/kanade/tachiyomi/multisrc/fansubscat/FansubsCat.kt @@ -33,11 +33,12 @@ abstract class FansubsCat( val apiBaseUrl: String, val isHentaiSite: Boolean, ) : HttpSource() { - override val supportsLatest = true - override fun headersBuilder(): Headers.Builder = Headers.Builder() - .add("User-Agent", "Tachiyomi/${AppInfo.getVersionName()}") + override fun headersBuilder(): Headers.Builder = + Headers + .Builder() + .add("User-Agent", "Tachiyomi/${AppInfo.getVersionName()}") override val client: OkHttpClient = network.client @@ -46,17 +47,21 @@ abstract class FansubsCat( private fun parseMangaFromJson(response: Response): MangasPage { val jsonObject = json.decodeFromString(response.body.string()) - val mangas = jsonObject["result"]!!.jsonArray.map { json -> - SManga.create().apply { - url = json.jsonObject["slug"]!!.jsonPrimitive.content - title = json.jsonObject["name"]!!.jsonPrimitive.content - thumbnail_url = json.jsonObject["thumbnail_url"]!!.jsonPrimitive.content - author = json.jsonObject["author"]!!.jsonPrimitive.contentOrNull - description = json.jsonObject["synopsis"]!!.jsonPrimitive.contentOrNull - status = json.jsonObject["status"]!!.jsonPrimitive.content.toStatus() - genre = json.jsonObject["genres"]!!.jsonPrimitive.contentOrNull + val mangas = + jsonObject["result"]!!.jsonArray.map { json -> + SManga.create().apply { + url = json.jsonObject["slug"]!!.jsonPrimitive.content + title = json.jsonObject["name"]!!.jsonPrimitive.content + thumbnail_url = json.jsonObject["thumbnail_url"]!!.jsonPrimitive.content + author = json.jsonObject["author"]!!.jsonPrimitive.contentOrNull + description = json.jsonObject["synopsis"]!!.jsonPrimitive.contentOrNull + status = + json.jsonObject["status"]!! + .jsonPrimitive.content + .toStatus() + genre = json.jsonObject["genres"]!!.jsonPrimitive.contentOrNull + } } - } return MangasPage(mangas, mangas.size >= 20) } @@ -89,23 +94,23 @@ abstract class FansubsCat( // Popular - override fun popularMangaRequest(page: Int): Request { - return GET("$apiBaseUrl/manga/popular/$page", headers) - } + override fun popularMangaRequest(page: Int): Request = GET("$apiBaseUrl/manga/popular/$page", headers) override fun popularMangaParse(response: Response): MangasPage = parseMangaFromJson(response) // Latest - override fun latestUpdatesRequest(page: Int): Request { - return GET("$apiBaseUrl/manga/recent/$page", headers) - } + override fun latestUpdatesRequest(page: Int): Request = GET("$apiBaseUrl/manga/recent/$page", headers) override fun latestUpdatesParse(response: Response): MangasPage = parseMangaFromJson(response) // Search - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { + override fun searchMangaRequest( + page: Int, + query: String, + filters: FilterList, + ): Request { val filterList = if (filters.isEmpty()) getFilterList() else filters val mangaTypeFilter = filterList.find { it is MangaTypeFilter } as MangaTypeFilter val stateFilter = filterList.find { it is StateFilter } as StateFilter @@ -130,16 +135,13 @@ abstract class FansubsCat( // Details - override fun mangaDetailsRequest(manga: SManga): Request { - return GET( + override fun mangaDetailsRequest(manga: SManga): Request = + GET( "$apiBaseUrl/manga/details/${manga.url.substringAfterLast('/')}", headers, ) - } - override fun getMangaUrl(manga: SManga): String { - return "$baseUrl/${manga.url}" - } + override fun getMangaUrl(manga: SManga): String = "$baseUrl/${manga.url}" override fun mangaDetailsParse(response: Response): SManga { val jsonObject = json.decodeFromString(response.body.string()) @@ -156,176 +158,193 @@ abstract class FansubsCat( } } - private fun String?.toStatus() = when { - this == null -> SManga.UNKNOWN - this.contains("ongoing", ignoreCase = true) -> SManga.ONGOING - this.contains("finished", ignoreCase = true) -> SManga.COMPLETED - else -> SManga.UNKNOWN - } + private fun String?.toStatus() = + when { + this == null -> SManga.UNKNOWN + this.contains("ongoing", ignoreCase = true) -> SManga.ONGOING + this.contains("finished", ignoreCase = true) -> SManga.COMPLETED + else -> SManga.UNKNOWN + } // Chapters - override fun chapterListRequest(manga: SManga): Request { - return GET( + override fun chapterListRequest(manga: SManga): Request = + GET( "$apiBaseUrl/manga/chapters/${manga.url.substringAfterLast('/')}", headers, ) - } - override fun chapterListParse(response: Response): List = - parseChapterListFromJson(response) + override fun chapterListParse(response: Response): List = parseChapterListFromJson(response) // Pages - override fun pageListRequest(chapter: SChapter): Request { - return GET( + override fun pageListRequest(chapter: SChapter): Request = + GET( "$apiBaseUrl/manga/pages/${chapter.url.substringAfterLast('/')}", headers, ) - } - override fun getChapterUrl(chapter: SChapter): String { - return "$baseUrl/${chapter.url.replace("/", "?f=")}" - } + override fun getChapterUrl(chapter: SChapter): String = "$baseUrl/${chapter.url.replace("/", "?f=")}" override fun pageListParse(response: Response): List = parsePageListFromJson(response) - override fun imageUrlParse(response: Response): String = - throw UnsupportedOperationException() + override fun imageUrlParse(response: Response): String = throw UnsupportedOperationException() // Filter - override fun getFilterList() = FilterList( + override fun getFilterList() = + FilterList( + listOfNotNull( + MangaTypeFilter("Tipus", getMangaTypeList()), + StateFilter("Estat", getStateList()), + if (!isHentaiSite) { + DemographyFilter("Demografies", getDemographyList()) + } else { + null + }, + GenreTagFilter("Gèneres (inclou/exclou)", getGenreList()), + ThemeTagFilter("Temàtiques (inclou/exclou)", getThemeList()), + ), + ) + + private fun getMangaTypeList() = + listOf( + MangaType("oneshot", "One-shots"), + MangaType("serialized", "Serialitzats"), + ) + + private fun getStateList() = + listOf( + State(1, "Completat"), + State(2, "En procés"), + State(3, "Parcialment completat"), + State(4, "Abandonat"), + State(5, "Cancel·lat"), + ) + + private fun getDemographyList() = + listOf( + Demography(35, "Infantil"), + Demography(27, "Josei"), + Demography(12, "Seinen"), + Demography(16, "Shōjo"), + Demography(1, "Shōnen"), + Demography(-1, "No definida"), + ) + + private fun getGenreList() = listOfNotNull( - MangaTypeFilter("Tipus", getMangaTypeList()), - StateFilter("Estat", getStateList()), - if (!isHentaiSite) { - DemographyFilter("Demografies", getDemographyList()) + Tag(4, "Acció"), + Tag(7, "Amor"), + Tag(38, "Amor entre noies"), + Tag(23, "Amor entre nois"), + Tag(31, "Avantguardisme"), + Tag(6, "Aventura"), + Tag(10, "Ciència-ficció"), + Tag(2, "Comèdia"), + Tag(47, "De prestigi"), + Tag(3, "Drama"), + Tag(19, "Ecchi"), + Tag(46, "Erotisme"), + Tag(20, "Esports"), + Tag(5, "Fantasia"), + Tag(48, "Gastronomia"), + if (isHentaiSite) { + Tag(34, "Hentai") } else { null }, - GenreTagFilter("Gèneres (inclou/exclou)", getGenreList()), - ThemeTagFilter("Temàtiques (inclou/exclou)", getThemeList()), - ), - ) - - private fun getMangaTypeList() = listOf( - MangaType("oneshot", "One-shots"), - MangaType("serialized", "Serialitzats"), - ) - - private fun getStateList() = listOf( - State(1, "Completat"), - State(2, "En procés"), - State(3, "Parcialment completat"), - State(4, "Abandonat"), - State(5, "Cancel·lat"), - ) - - private fun getDemographyList() = listOf( - Demography(35, "Infantil"), - Demography(27, "Josei"), - Demography(12, "Seinen"), - Demography(16, "Shōjo"), - Demography(1, "Shōnen"), - Demography(-1, "No definida"), - ) - - private fun getGenreList() = listOfNotNull( - Tag(4, "Acció"), - Tag(7, "Amor"), - Tag(38, "Amor entre noies"), - Tag(23, "Amor entre nois"), - Tag(31, "Avantguardisme"), - Tag(6, "Aventura"), - Tag(10, "Ciència-ficció"), - Tag(2, "Comèdia"), - Tag(47, "De prestigi"), - Tag(3, "Drama"), - Tag(19, "Ecchi"), - Tag(46, "Erotisme"), - Tag(20, "Esports"), - Tag(5, "Fantasia"), - Tag(48, "Gastronomia"), - if (isHentaiSite) { - Tag(34, "Hentai") - } else { - null - }, - Tag(11, "Misteri"), - Tag(8, "Sobrenatural"), - Tag(17, "Suspens"), - Tag(21, "Terror"), - Tag(42, "Vida quotidiana"), - ) - - private fun getThemeList() = listOf( - Tag(71, "Animals de companyia"), - Tag(50, "Antropomorfisme"), - Tag(70, "Arts escèniques"), - Tag(18, "Arts marcials"), - Tag(81, "Arts visuals"), - Tag(64, "Canvi de gènere màgic"), - Tag(56, "Comèdia de gags"), - Tag(68, "Crim organitzat"), - Tag(69, "Cultura otaku"), - Tag(30, "Curses"), - Tag(54, "Delinqüència"), - Tag(43, "Detectivesc"), - Tag(55, "Educatiu"), - Tag(9, "Escolar"), - Tag(39, "Espai"), - Tag(77, "Esports d’equip"), - Tag(53, "Esports de combat"), - Tag(25, "Harem"), - Tag(73, "Harem invers"), - Tag(15, "Històric"), - Tag(59, "Idols femenines"), - Tag(60, "Idols masculins"), - Tag(75, "Indústria de l’entreteniment"), - Tag(61, "Isekai"), - Tag(58, "Joc d’alt risc"), - Tag(33, "Joc d’estratègia"), - Tag(82, "Laboral"), - Tag(29, "Mecha"), - Tag(66, "Medicina"), - Tag(67, "Memòries"), - Tag(22, "Militar"), - Tag(32, "Mitologia"), - Tag(26, "Música"), - Tag(65, "Noies màgiques"), - Tag(36, "Paròdia"), - Tag(49, "Personatges adults"), - Tag(51, "Personatges bufons"), - Tag(63, "Polígon amorós"), - Tag(13, "Psicològic"), - Tag(52, "Puericultura"), - Tag(72, "Reencarnació"), - Tag(62, "Relaxant"), - Tag(74, "Rerefons romàntic"), - Tag(37, "Samurais"), - Tag(57, "Sang i fetge"), - Tag(40, "Superpoders"), - Tag(76, "Supervivència"), - Tag(80, "Tirana"), - Tag(45, "Transformisme"), - Tag(41, "Vampirs"), - Tag(78, "Viatges en el temps"), - Tag(79, "Videojocs"), - ) + Tag(11, "Misteri"), + Tag(8, "Sobrenatural"), + Tag(17, "Suspens"), + Tag(21, "Terror"), + Tag(42, "Vida quotidiana"), + ) + + private fun getThemeList() = + listOf( + Tag(71, "Animals de companyia"), + Tag(50, "Antropomorfisme"), + Tag(70, "Arts escèniques"), + Tag(18, "Arts marcials"), + Tag(81, "Arts visuals"), + Tag(64, "Canvi de gènere màgic"), + Tag(56, "Comèdia de gags"), + Tag(68, "Crim organitzat"), + Tag(69, "Cultura otaku"), + Tag(30, "Curses"), + Tag(54, "Delinqüència"), + Tag(43, "Detectivesc"), + Tag(55, "Educatiu"), + Tag(9, "Escolar"), + Tag(39, "Espai"), + Tag(77, "Esports d’equip"), + Tag(53, "Esports de combat"), + Tag(25, "Harem"), + Tag(73, "Harem invers"), + Tag(15, "Històric"), + Tag(59, "Idols femenines"), + Tag(60, "Idols masculins"), + Tag(75, "Indústria de l’entreteniment"), + Tag(61, "Isekai"), + Tag(58, "Joc d’alt risc"), + Tag(33, "Joc d’estratègia"), + Tag(82, "Laboral"), + Tag(29, "Mecha"), + Tag(66, "Medicina"), + Tag(67, "Memòries"), + Tag(22, "Militar"), + Tag(32, "Mitologia"), + Tag(26, "Música"), + Tag(65, "Noies màgiques"), + Tag(36, "Paròdia"), + Tag(49, "Personatges adults"), + Tag(51, "Personatges bufons"), + Tag(63, "Polígon amorós"), + Tag(13, "Psicològic"), + Tag(52, "Puericultura"), + Tag(72, "Reencarnació"), + Tag(62, "Relaxant"), + Tag(74, "Rerefons romàntic"), + Tag(37, "Samurais"), + Tag(57, "Sang i fetge"), + Tag(40, "Superpoders"), + Tag(76, "Supervivència"), + Tag(80, "Tirana"), + Tag(45, "Transformisme"), + Tag(41, "Vampirs"), + Tag(78, "Viatges en el temps"), + Tag(79, "Videojocs"), + ) private interface UrlQueryFilter { fun addQueryParameter(url: HttpUrl.Builder) } - internal class MangaType(val id: String, name: String) : Filter.CheckBox(name) - internal class State(val id: Int, name: String) : Filter.CheckBox(name) - internal class Tag(val id: Int, name: String) : Filter.TriState(name) - internal class Demography(val id: Int, name: String) : Filter.CheckBox(name) - - private class MangaTypeFilter(collection: String, mangaTypes: List) : - Filter.Group(collection, mangaTypes), + internal class MangaType( + val id: String, + name: String, + ) : Filter.CheckBox(name) + + internal class State( + val id: Int, + name: String, + ) : Filter.CheckBox(name) + + internal class Tag( + val id: Int, + name: String, + ) : Filter.TriState(name) + + internal class Demography( + val id: Int, + name: String, + ) : Filter.CheckBox(name) + + private class MangaTypeFilter( + collection: String, + mangaTypes: List, + ) : Filter.Group(collection, mangaTypes), UrlQueryFilter { - override fun addQueryParameter(url: HttpUrl.Builder) { var oneShotSelected = false var serializedSelected = false @@ -346,10 +365,11 @@ abstract class FansubsCat( } } - private class StateFilter(collection: String, states: List) : - Filter.Group(collection, states), + private class StateFilter( + collection: String, + states: List, + ) : Filter.Group(collection, states), UrlQueryFilter { - override fun addQueryParameter(url: HttpUrl.Builder) { state.forEach { state -> if (state.state) { @@ -359,10 +379,11 @@ abstract class FansubsCat( } } - private class DemographyFilter(collection: String, demographies: List) : - Filter.Group(collection, demographies), + private class DemographyFilter( + collection: String, + demographies: List, + ) : Filter.Group(collection, demographies), UrlQueryFilter { - override fun addQueryParameter(url: HttpUrl.Builder) { state.forEach { demography -> if (demography.state) { @@ -372,10 +393,11 @@ abstract class FansubsCat( } } - private class GenreTagFilter(collection: String, tags: List) : - Filter.Group(collection, tags), + private class GenreTagFilter( + collection: String, + tags: List, + ) : Filter.Group(collection, tags), UrlQueryFilter { - override fun addQueryParameter(url: HttpUrl.Builder) { state.forEach { tag -> if (tag.isIncluded()) { @@ -387,10 +409,11 @@ abstract class FansubsCat( } } - private class ThemeTagFilter(collection: String, tags: List) : - Filter.Group(collection, tags), + private class ThemeTagFilter( + collection: String, + tags: List, + ) : Filter.Group(collection, tags), UrlQueryFilter { - override fun addQueryParameter(url: HttpUrl.Builder) { state.forEach { tag -> if (tag.isIncluded()) { diff --git a/lib-multisrc/fmreader/src/eu/kanade/tachiyomi/multisrc/fmreader/FMReader.kt b/lib-multisrc/fmreader/src/eu/kanade/tachiyomi/multisrc/fmreader/FMReader.kt index 7ce035fc47..57736020cd 100644 --- a/lib-multisrc/fmreader/src/eu/kanade/tachiyomi/multisrc/fmreader/FMReader.kt +++ b/lib-multisrc/fmreader/src/eu/kanade/tachiyomi/multisrc/fmreader/FMReader.kt @@ -32,22 +32,22 @@ abstract class FMReader( override val lang: String, private val dateFormat: SimpleDateFormat = SimpleDateFormat("dd/MM/yyyy", Locale.ENGLISH), ) : ParsedHttpSource() { - override val supportsLatest = true override val client: OkHttpClient = network.cloudflareClient - override fun headersBuilder() = Headers.Builder().apply { - add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64) Gecko/20100101 Firefox/77.0") - add("Referer", baseUrl) - } + override fun headersBuilder() = + Headers.Builder().apply { + add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64) Gecko/20100101 Firefox/77.0") + add("Referer", baseUrl) + } protected fun Elements.imgAttr(): String? = getImgAttr(this.firstOrNull()) private fun Element.imgAttr(): String? = getImgAttr(this) - open fun getImgAttr(element: Element?): String? { - return when { + open fun getImgAttr(element: Element?): String? = + when { element == null -> null element.hasAttr("data-original") -> element.attr("abs:data-original") element.hasAttr("data-src") -> element.attr("abs:data-src") @@ -56,7 +56,6 @@ abstract class FMReader( element.hasAttr("style") -> element.attr("style").substringAfter("(").substringBefore(")") else -> element.attr("abs:src") } - } open val requestPath = "manga-list.html" @@ -65,10 +64,17 @@ abstract class FMReader( override fun popularMangaRequest(page: Int): Request = GET("$baseUrl/$requestPath?listType=pagination&page=$page&$popularSort&sort_type=DESC", headers) - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val url = "$baseUrl/$requestPath?".toHttpUrl().newBuilder() - .addQueryParameter("name", query) - .addQueryParameter("page", page.toString()) + override fun searchMangaRequest( + page: Int, + query: String, + filters: FilterList, + ): Request { + val url = + "$baseUrl/$requestPath?" + .toHttpUrl() + .newBuilder() + .addQueryParameter("name", query) + .addQueryParameter("page", page.toString()) (if (filters.isEmpty()) getFilterList() else filters).forEach { filter -> when (filter) { is Status -> { @@ -110,13 +116,14 @@ abstract class FMReader( val mangas = document.select(popularMangaSelector()).map { popularMangaFromElement(it) } // check if there's a next page - val hasNextPage = (document.select(popularMangaNextPageSelector()).first()?.text() ?: "").let { - if (it.contains(Regex("""\w*\s\d*\s\w*\s\d*"""))) { - it.split(" ").let { pageOf -> pageOf[1] != pageOf[3] } // current page not last page - } else { - it.isNotEmpty() // standard next page check + val hasNextPage = + (document.select(popularMangaNextPageSelector()).first()?.text() ?: "").let { + if (it.contains(Regex("""\w*\s\d*\s\w*\s\d*"""))) { + it.split(" ").let { pageOf -> pageOf[1] != pageOf[3] } // current page not last page + } else { + it.isNotEmpty() // standard next page check + } } - } return MangasPage(mangas, hasNextPage) } @@ -133,15 +140,14 @@ abstract class FMReader( open val headerSelector = "h3 a, .series-title a" - override fun popularMangaFromElement(element: Element): SManga { - return SManga.create().apply { + override fun popularMangaFromElement(element: Element): SManga = + SManga.create().apply { element.select(headerSelector).let { setUrlWithoutDomain(it.attr("abs:href")) title = it.text() } thumbnail_url = element.select("img, .thumb-wrapper .img-in-ratio").imgAttr() } - } override fun latestUpdatesFromElement(element: Element): SManga = popularMangaFromElement(element) @@ -173,9 +179,14 @@ abstract class FMReader( val infoElement = document.select(infoElementSelector).first()!! return SManga.create().apply { - author = infoElement.select(mangaDetailsSelectorAuthor).eachText().filter { - it.equals("Updating", true).not() - }.joinToString().takeIf { it.isNotBlank() } + author = + infoElement + .select(mangaDetailsSelectorAuthor) + .eachText() + .filter { + it.equals("Updating", true).not() + }.joinToString() + .takeIf { it.isNotBlank() } genre = infoElement.select(mangaDetailsSelectorGenre).joinToString { it.text() } status = parseStatus(infoElement.select(mangaDetailsSelectorStatus).first()?.text()) description = document.select(mangaDetailsSelectorDescription).text().trim() @@ -184,10 +195,11 @@ abstract class FMReader( // add alternative name to manga description infoElement.select(altNameSelector).firstOrNull()?.ownText()?.let { if (it.isBlank().not() && it.contains("Updating", true).not()) { - description = when { - description.isNullOrBlank() -> altName + it - else -> description + "\n\n$altName" + it - } + description = + when { + description.isNullOrBlank() -> altName + it + else -> description + "\n\n$altName" + it + } } } } @@ -195,18 +207,27 @@ abstract class FMReader( // languages: en, vi, tr fun parseStatus(status: String?): Int { - val completedWords = setOf( - "completed", - "complete", - "đã hoàn thành", - "hoàn thành", - "tamamlandı", - ) - val ongoingWords = setOf( - "ongoing", "on going", "updating", "incomplete", - "chưa hoàn thành", "đang cập nhật", "Đang tiến hành", - "devam ediyor", "Çevirisi Bırakıldı", "Çevirisi Yok", - ) + val completedWords = + setOf( + "completed", + "complete", + "đã hoàn thành", + "hoàn thành", + "tamamlandı", + ) + val ongoingWords = + setOf( + "ongoing", + "on going", + "updating", + "incomplete", + "chưa hoàn thành", + "đang cập nhật", + "Đang tiến hành", + "devam ediyor", + "Çevirisi Bırakıldı", + "Çevirisi Yok", + ) return when { status == null -> SManga.UNKNOWN completedWords.any { it.equals(status, ignoreCase = true) } -> SManga.COMPLETED @@ -221,9 +242,7 @@ abstract class FMReader( return document.select(chapterListSelector()).map { chapterFromElement(it, mangaTitle) }.distinctBy { it.url } } - override fun chapterFromElement(element: Element): SChapter { - return chapterFromElement(element, "") - } + override fun chapterFromElement(element: Element): SChapter = chapterFromElement(element, "") override fun chapterListSelector() = "div#list-chapters p, table.table tr, .list-chapters > a" @@ -233,8 +252,11 @@ abstract class FMReader( open val chapterNameAttrSelector = "title" - open fun chapterFromElement(element: Element, mangaTitle: String = ""): SChapter { - return SChapter.create().apply { + open fun chapterFromElement( + element: Element, + mangaTitle: String = "", + ): SChapter = + SChapter.create().apply { if (chapterUrlSelector != "") { element.select(chapterUrlSelector).first()!!.let { setUrlWithoutDomain(it.attr("abs:href")) @@ -248,7 +270,6 @@ abstract class FMReader( } date_upload = element.select(chapterTimeSelector).let { if (it.hasText()) parseRelativeDate(it.text()) else 0 } } - } // gets the number from "1 day ago" open val dateValueIndex = 0 @@ -258,63 +279,81 @@ abstract class FMReader( open fun parseRelativeDate(date: String): Long { val value = date.split(' ')[dateValueIndex].toInt() - val dateWord = date.split(' ')[dateWordIndex].let { - if (it.contains("(")) { - it.substringBefore("(") - } else { - it.substringBefore("s") + val dateWord = + date.split(' ')[dateWordIndex].let { + if (it.contains("(")) { + it.substringBefore("(") + } else { + it.substringBefore("s") + } } - } // languages: en, vi, es, tr return when (dateWord) { - "min", "minute", "phút", "minuto", "dakika" -> Calendar.getInstance().apply { - add(Calendar.MINUTE, -value) - set(Calendar.SECOND, 0) - set(Calendar.MILLISECOND, 0) - }.timeInMillis - "hour", "giờ", "hora", "saat" -> Calendar.getInstance().apply { - add(Calendar.HOUR_OF_DAY, -value) - set(Calendar.SECOND, 0) - set(Calendar.MILLISECOND, 0) - }.timeInMillis - "day", "ngày", "día", "gün" -> Calendar.getInstance().apply { - add(Calendar.DATE, -value) - set(Calendar.SECOND, 0) - set(Calendar.MILLISECOND, 0) - }.timeInMillis - "week", "tuần", "semana", "hafta" -> Calendar.getInstance().apply { - add(Calendar.DATE, -value * 7) - set(Calendar.SECOND, 0) - set(Calendar.MILLISECOND, 0) - }.timeInMillis - "month", "tháng", "mes", "ay" -> Calendar.getInstance().apply { - add(Calendar.MONTH, -value) - set(Calendar.SECOND, 0) - set(Calendar.MILLISECOND, 0) - }.timeInMillis - "year", "năm", "año", "yıl" -> Calendar.getInstance().apply { - add(Calendar.YEAR, -value) - set(Calendar.SECOND, 0) - set(Calendar.MILLISECOND, 0) - }.timeInMillis + "min", "minute", "phút", "minuto", "dakika" -> + Calendar + .getInstance() + .apply { + add(Calendar.MINUTE, -value) + set(Calendar.SECOND, 0) + set(Calendar.MILLISECOND, 0) + }.timeInMillis + "hour", "giờ", "hora", "saat" -> + Calendar + .getInstance() + .apply { + add(Calendar.HOUR_OF_DAY, -value) + set(Calendar.SECOND, 0) + set(Calendar.MILLISECOND, 0) + }.timeInMillis + "day", "ngày", "día", "gün" -> + Calendar + .getInstance() + .apply { + add(Calendar.DATE, -value) + set(Calendar.SECOND, 0) + set(Calendar.MILLISECOND, 0) + }.timeInMillis + "week", "tuần", "semana", "hafta" -> + Calendar + .getInstance() + .apply { + add(Calendar.DATE, -value * 7) + set(Calendar.SECOND, 0) + set(Calendar.MILLISECOND, 0) + }.timeInMillis + "month", "tháng", "mes", "ay" -> + Calendar + .getInstance() + .apply { + add(Calendar.MONTH, -value) + set(Calendar.SECOND, 0) + set(Calendar.MILLISECOND, 0) + }.timeInMillis + "year", "năm", "año", "yıl" -> + Calendar + .getInstance() + .apply { + add(Calendar.YEAR, -value) + set(Calendar.SECOND, 0) + set(Calendar.MILLISECOND, 0) + }.timeInMillis else -> { return 0 } } } - open fun parseAbsoluteDate(dateStr: String): Long { - return runCatching { dateFormat.parse(dateStr)?.time } + + open fun parseAbsoluteDate(dateStr: String): Long = + runCatching { dateFormat.parse(dateStr)?.time } .getOrNull() ?: 0L - } open val pageListImageSelector = "img.chapter-img" - override fun pageListParse(document: Document): List { - return document.select(pageListImageSelector).mapIndexed { i, img -> + override fun pageListParse(document: Document): List = + document.select(pageListImageSelector).mapIndexed { i, img -> Page(i, document.location(), img.imgAttr()) } - } protected fun base64PageListParse(document: Document): List { fun Element.decoded(): String { @@ -340,159 +379,175 @@ abstract class FMReader( override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException() - private class TextField(name: String, val key: String) : Filter.Text(name) + private class TextField( + name: String, + val key: String, + ) : Filter.Text(name) + private class Status : Filter.Select("Status", arrayOf("Any", "Completed", "Ongoing")) - class GenreList(genres: List) : Filter.Group("Genre", genres) - class Genre(name: String, val id: String = name.replace(' ', '+')) : Filter.TriState(name) + + class GenreList( + genres: List, + ) : Filter.Group("Genre", genres) + + class Genre( + name: String, + val id: String = name.replace(' ', '+'), + ) : Filter.TriState(name) + private class SortBy : Filter.Sort("Sorted By", arrayOf("A-Z", "Most vỉews", "Last updated"), Selection(1, false)) // TODO: Country (leftover from original LHTranslation) - override fun getFilterList() = FilterList( - TextField("Author", "author"), - TextField("Group", "group"), - Status(), - SortBy(), - GenreList(getGenreList()), - ) + override fun getFilterList() = + FilterList( + TextField("Author", "author"), + TextField("Group", "group"), + Status(), + SortBy(), + GenreList(getGenreList()), + ) // [...document.querySelectorAll("div.panel-body a")].map((el,i) => `Genre("${el.innerText.trim()}")`).join(',\n') // on https://lhtranslation.net/search - open fun getGenreList() = listOf( - Genre("Action"), - Genre("18+"), - Genre("Adult"), - Genre("Anime"), - Genre("Comedy"), - Genre("Comic"), - Genre("Doujinshi"), - Genre("Drama"), - Genre("Ecchi"), - Genre("Fantasy"), - Genre("Gender Bender"), - Genre("Harem"), - Genre("Historical"), - Genre("Horror"), - Genre("Josei"), - Genre("Live action"), - Genre("Manhua"), - Genre("Manhwa"), - Genre("Martial Art"), - Genre("Mature"), - Genre("Mecha"), - Genre("Mystery"), - Genre("One shot"), - Genre("Psychological"), - Genre("Romance"), - Genre("School Life"), - Genre("Sci-fi"), - Genre("Seinen"), - Genre("Shoujo"), - Genre("Shojou Ai"), - Genre("Shounen"), - Genre("Shounen Ai"), - Genre("Slice of Life"), - Genre("Smut"), - Genre("Sports"), - Genre("Supernatural"), - Genre("Tragedy"), - Genre("Adventure"), - Genre("Yaoi"), - ) + open fun getGenreList() = + listOf( + Genre("Action"), + Genre("18+"), + Genre("Adult"), + Genre("Anime"), + Genre("Comedy"), + Genre("Comic"), + Genre("Doujinshi"), + Genre("Drama"), + Genre("Ecchi"), + Genre("Fantasy"), + Genre("Gender Bender"), + Genre("Harem"), + Genre("Historical"), + Genre("Horror"), + Genre("Josei"), + Genre("Live action"), + Genre("Manhua"), + Genre("Manhwa"), + Genre("Martial Art"), + Genre("Mature"), + Genre("Mecha"), + Genre("Mystery"), + Genre("One shot"), + Genre("Psychological"), + Genre("Romance"), + Genre("School Life"), + Genre("Sci-fi"), + Genre("Seinen"), + Genre("Shoujo"), + Genre("Shojou Ai"), + Genre("Shounen"), + Genre("Shounen Ai"), + Genre("Slice of Life"), + Genre("Smut"), + Genre("Sports"), + Genre("Supernatural"), + Genre("Tragedy"), + Genre("Adventure"), + Genre("Yaoi"), + ) // from manhwa18.com/search, removed a few that didn't return results/wouldn't be terribly useful - fun getAdultGenreList() = listOf( - Genre("18"), - Genre("Action"), - Genre("Adult"), - Genre("Adventure"), - Genre("Anime"), - Genre("Comedy"), - Genre("Comic"), - Genre("Doujinshi"), - Genre("Drama"), - Genre("Ecchi"), - Genre("Fantasy"), - Genre("Gender Bender"), - Genre("Harem"), - Genre("Historical"), - Genre("Horror"), - Genre("Josei"), - Genre("Live action"), - Genre("Magic"), - Genre("Manhua"), - Genre("Manhwa"), - Genre("Martial Arts"), - Genre("Mature"), - Genre("Mecha"), - Genre("Mystery"), - Genre("Oneshot"), - Genre("Psychological"), - Genre("Romance"), - Genre("School Life"), - Genre("Sci-fi"), - Genre("Seinen"), - Genre("Shoujo"), - Genre("Shoujo Ai"), - Genre("Shounen"), - Genre("Shounen Ai"), - Genre("Slice of life"), - Genre("Smut"), - Genre("Soft Yaoi"), - Genre("Soft Yuri"), - Genre("Sports"), - Genre("Supernatural"), - Genre("Tragedy"), - Genre("VnComic"), - Genre("Webtoon"), - ) + fun getAdultGenreList() = + listOf( + Genre("18"), + Genre("Action"), + Genre("Adult"), + Genre("Adventure"), + Genre("Anime"), + Genre("Comedy"), + Genre("Comic"), + Genre("Doujinshi"), + Genre("Drama"), + Genre("Ecchi"), + Genre("Fantasy"), + Genre("Gender Bender"), + Genre("Harem"), + Genre("Historical"), + Genre("Horror"), + Genre("Josei"), + Genre("Live action"), + Genre("Magic"), + Genre("Manhua"), + Genre("Manhwa"), + Genre("Martial Arts"), + Genre("Mature"), + Genre("Mecha"), + Genre("Mystery"), + Genre("Oneshot"), + Genre("Psychological"), + Genre("Romance"), + Genre("School Life"), + Genre("Sci-fi"), + Genre("Seinen"), + Genre("Shoujo"), + Genre("Shoujo Ai"), + Genre("Shounen"), + Genre("Shounen Ai"), + Genre("Slice of life"), + Genre("Smut"), + Genre("Soft Yaoi"), + Genre("Soft Yuri"), + Genre("Sports"), + Genre("Supernatural"), + Genre("Tragedy"), + Genre("VnComic"), + Genre("Webtoon"), + ) // taken from readcomiconline.org/search - fun getComicsGenreList() = listOf( - Genre("Action"), - Genre("Adventure"), - Genre("Anthology"), - Genre("Anthropomorphic"), - Genre("Biography"), - Genre("Children"), - Genre("Comedy"), - Genre("Crime"), - Genre("Drama"), - Genre("Family"), - Genre("Fantasy"), - Genre("Fighting"), - Genre("GraphicNovels"), - Genre("Historical"), - Genre("Horror"), - Genre("LeadingLadies"), - Genre("LGBTQ"), - Genre("Literature"), - Genre("Manga"), - Genre("MartialArts"), - Genre("Mature"), - Genre("Military"), - Genre("Mystery"), - Genre("Mythology"), - Genre("Personal"), - Genre("Political"), - Genre("Post-Apocalyptic"), - Genre("Psychological"), - Genre("Pulp"), - Genre("Religious"), - Genre("Robots"), - Genre("Romance"), - Genre("Schoollife"), - Genre("Sci-Fi"), - Genre("Sliceoflife"), - Genre("Sport"), - Genre("Spy"), - Genre("Superhero"), - Genre("Supernatural"), - Genre("Suspense"), - Genre("Thriller"), - Genre("Vampires"), - Genre("VideoGames"), - Genre("War"), - Genre("Western"), - Genre("Zombies"), - ) + fun getComicsGenreList() = + listOf( + Genre("Action"), + Genre("Adventure"), + Genre("Anthology"), + Genre("Anthropomorphic"), + Genre("Biography"), + Genre("Children"), + Genre("Comedy"), + Genre("Crime"), + Genre("Drama"), + Genre("Family"), + Genre("Fantasy"), + Genre("Fighting"), + Genre("GraphicNovels"), + Genre("Historical"), + Genre("Horror"), + Genre("LeadingLadies"), + Genre("LGBTQ"), + Genre("Literature"), + Genre("Manga"), + Genre("MartialArts"), + Genre("Mature"), + Genre("Military"), + Genre("Mystery"), + Genre("Mythology"), + Genre("Personal"), + Genre("Political"), + Genre("Post-Apocalyptic"), + Genre("Psychological"), + Genre("Pulp"), + Genre("Religious"), + Genre("Robots"), + Genre("Romance"), + Genre("Schoollife"), + Genre("Sci-Fi"), + Genre("Sliceoflife"), + Genre("Sport"), + Genre("Spy"), + Genre("Superhero"), + Genre("Supernatural"), + Genre("Suspense"), + Genre("Thriller"), + Genre("Vampires"), + Genre("VideoGames"), + Genre("War"), + Genre("Western"), + Genre("Zombies"), + ) } diff --git a/lib-multisrc/foolslide/src/eu/kanade/tachiyomi/multisrc/foolslide/FoolSlide.kt b/lib-multisrc/foolslide/src/eu/kanade/tachiyomi/multisrc/foolslide/FoolSlide.kt index 34eacf2576..bd43b92ea3 100644 --- a/lib-multisrc/foolslide/src/eu/kanade/tachiyomi/multisrc/foolslide/FoolSlide.kt +++ b/lib-multisrc/foolslide/src/eu/kanade/tachiyomi/multisrc/foolslide/FoolSlide.kt @@ -34,17 +34,15 @@ abstract class FoolSlide( override val baseUrl: String, override val lang: String, open val urlModifier: String = "", -) : ConfigurableSource, ParsedHttpSource() { - +) : ParsedHttpSource(), + ConfigurableSource { override val supportsLatest = true private val json by lazy { Injekt.get() } override fun popularMangaSelector() = "div.group" - override fun popularMangaRequest(page: Int): Request { - return GET("$baseUrl$urlModifier/directory/$page/", headers) - } + override fun popularMangaRequest(page: Int): Request = GET("$baseUrl$urlModifier/directory/$page/", headers) private val latestUpdatesUrls = mutableSetOf() @@ -64,28 +62,34 @@ abstract class FoolSlide( return GET("$baseUrl$urlModifier/latest/$page/") } - override fun popularMangaFromElement(element: Element) = SManga.create().apply { - element.select("a[title]").first()!!.let { - setUrlWithoutDomain(it.attr("href")) - title = it.text() - } - element.select("img").first()?.let { - thumbnail_url = it.absUrl("src").replace("/thumb_", "/") + override fun popularMangaFromElement(element: Element) = + SManga.create().apply { + element.select("a[title]").first()!!.let { + setUrlWithoutDomain(it.attr("href")) + title = it.text() + } + element.select("img").first()?.let { + thumbnail_url = it.absUrl("src").replace("/thumb_", "/") + } } - } - override fun latestUpdatesFromElement(element: Element) = SManga.create().apply { - element.select("a[title]").first()!!.let { - setUrlWithoutDomain(it.attr("href")) - title = it.text() + override fun latestUpdatesFromElement(element: Element) = + SManga.create().apply { + element.select("a[title]").first()!!.let { + setUrlWithoutDomain(it.attr("href")) + title = it.text() + } } - } override fun popularMangaNextPageSelector() = "div.next" override fun latestUpdatesNextPageSelector(): String? = "div.next" - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { + override fun searchMangaRequest( + page: Int, + query: String, + filters: FilterList, + ): Request { val searchHeaders = headersBuilder().add("Content-Type", "application/x-www-form-urlencoded").build() val form = FormBody.Builder().add("search", query).build() return POST("$baseUrl$urlModifier/search/", searchHeaders, form) @@ -109,21 +113,28 @@ abstract class FoolSlide( protected open val mangaDetailsInfoSelector = "div.info" // if there's no image on the details page, get the first page of the first chapter - protected fun getDetailsThumbnail(document: Document, urlSelector: String = chapterUrlSelector): String? { - return document.select("div.thumbnail img, table.thumb img").firstOrNull()?.attr("abs:src") - ?: document.select(chapterListSelector()).last()!!.select(urlSelector).attr("abs:href") + protected fun getDetailsThumbnail( + document: Document, + urlSelector: String = chapterUrlSelector, + ): String? = + document.select("div.thumbnail img, table.thumb img").firstOrNull()?.attr("abs:src") + ?: document + .select(chapterListSelector()) + .last()!! + .select(urlSelector) + .attr("abs:href") .let { url -> client.newCall(allowAdult(GET(url))).execute() } .let { response -> pageListParse(response).first().imageUrl } - } - override fun mangaDetailsParse(document: Document) = SManga.create().apply { - document.select(mangaDetailsInfoSelector).firstOrNull()?.html()?.let { infoHtml -> - author = Regex("""(?i)(Author|Autore):\s?([^\n<]*)[\n<]""").find(infoHtml)?.groupValues?.get(2) - artist = Regex("""Artist:\s?([^\n<]*)[\n<]""").find(infoHtml)?.groupValues?.get(1) - description = Regex("""(?i)(Synopsis|Description|Trama):\s?([^\n<]*)[\n<]""").find(infoHtml)?.groupValues?.get(2) + override fun mangaDetailsParse(document: Document) = + SManga.create().apply { + document.select(mangaDetailsInfoSelector).firstOrNull()?.html()?.let { infoHtml -> + author = Regex("""(?i)(Author|Autore):\s?([^\n<]*)[\n<]""").find(infoHtml)?.groupValues?.get(2) + artist = Regex("""Artist:\s?([^\n<]*)[\n<]""").find(infoHtml)?.groupValues?.get(1) + description = Regex("""(?i)(Synopsis|Description|Trama):\s?([^\n<]*)[\n<]""").find(infoHtml)?.groupValues?.get(2) + } + thumbnail_url = getDetailsThumbnail(document) } - thumbnail_url = getDetailsThumbnail(document) - } protected open val allowAdult: Boolean get() = preferences.getBoolean("adult", true) @@ -141,13 +152,14 @@ abstract class FoolSlide( protected open val chapterUrlSelector = "a[title]" - override fun chapterFromElement(element: Element) = SChapter.create().apply { - val urlElement = element.select(chapterUrlSelector).first()!! - val dateElement = element.select(chapterDateSelector).first()!! - setUrlWithoutDomain(urlElement.attr("href")) - name = urlElement.text() - date_upload = parseChapterDate(dateElement.text().substringAfter(", ")) ?: 0 - } + override fun chapterFromElement(element: Element) = + SChapter.create().apply { + val urlElement = element.select(chapterUrlSelector).first()!! + val dateElement = element.select(chapterDateSelector).first()!! + setUrlWithoutDomain(urlElement.attr("href")) + name = urlElement.text() + date_upload = parseChapterDate(dateElement.text().substringAfter(", ")) ?: 0 + } protected open fun parseChapterDate(date: String): Long? { val lcDate = date.lowercase(Locale.ROOT) @@ -204,10 +216,13 @@ abstract class FoolSlide( if (result != null) { // Result parsed but no year, copy current year over - result = Calendar.getInstance().apply { - time = result!! - set(Calendar.YEAR, Calendar.getInstance().get(Calendar.YEAR)) - }.time + result = + Calendar + .getInstance() + .apply { + time = result!! + set(Calendar.YEAR, Calendar.getInstance().get(Calendar.YEAR)) + }.time } } else { break @@ -232,29 +247,29 @@ abstract class FoolSlide( val now = Calendar.getInstance() // Map English unit to Java unit - val javaUnit = when (unit) { - "year", "yr" -> Calendar.YEAR - "month" -> Calendar.MONTH - "week", "wk" -> Calendar.WEEK_OF_MONTH - "day" -> Calendar.DAY_OF_MONTH - "hour", "hr" -> Calendar.HOUR - "minute", "min" -> Calendar.MINUTE - "second", "sec" -> Calendar.SECOND - else -> return null - } + val javaUnit = + when (unit) { + "year", "yr" -> Calendar.YEAR + "month" -> Calendar.MONTH + "week", "wk" -> Calendar.WEEK_OF_MONTH + "day" -> Calendar.DAY_OF_MONTH + "hour", "hr" -> Calendar.HOUR + "minute", "min" -> Calendar.MINUTE + "second", "sec" -> Calendar.SECOND + else -> return null + } now.add(javaUnit, -number) return now.timeInMillis } - private fun SimpleDateFormat.parseOrNull(string: String): Date? { - return try { + private fun SimpleDateFormat.parseOrNull(string: String): Date? = + try { parse(string) } catch (e: ParseException) { null } - } override fun pageListRequest(chapter: SChapter) = allowAdult(super.pageListRequest(chapter)) @@ -264,9 +279,11 @@ abstract class FoolSlide( val pages = json.parseToJsonElement(jsonStr).jsonArray return pages.mapIndexed { i, jsonEl -> // Create dummy element to resolve relative URL - val absUrl = document.createElement("a") - .attr("href", jsonEl.jsonObject["url"]!!.jsonPrimitive.content) - .absUrl("href") + val absUrl = + document + .createElement("a") + .attr("href", jsonEl.jsonObject["url"]!!.jsonPrimitive.content) + .absUrl("href") Page(i, "", absUrl) } } @@ -278,25 +295,28 @@ abstract class FoolSlide( } override fun setupPreferenceScreen(screen: PreferenceScreen) { - CheckBoxPreference(screen.context).apply { - key = "adult" - summary = "Show adult content" - setDefaultValue(true) - - setOnPreferenceChangeListener { _, newValue -> - preferences.edit().putBoolean(key, newValue as Boolean).commit() - } - }.let(screen::addPreference) + CheckBoxPreference(screen.context) + .apply { + key = "adult" + summary = "Show adult content" + setDefaultValue(true) + + setOnPreferenceChangeListener { _, newValue -> + preferences.edit().putBoolean(key, newValue as Boolean).commit() + } + }.let(screen::addPreference) } companion object { private val ORDINAL_SUFFIXES = listOf("st", "nd", "rd", "th") private val DATE_FORMAT_1 = SimpleDateFormat("yyyy.MM.dd", Locale.US) - private val DATE_FORMATS_WITH_ORDINAL_SUFFIXES = ORDINAL_SUFFIXES.map { - SimpleDateFormat("dd'$it' MMMM, yyyy", Locale.US) - } - private val DATE_FORMATS_WITH_ORDINAL_SUFFIXES_NO_YEAR = ORDINAL_SUFFIXES.map { - SimpleDateFormat("dd'$it' MMMM", Locale.US) - } + private val DATE_FORMATS_WITH_ORDINAL_SUFFIXES = + ORDINAL_SUFFIXES.map { + SimpleDateFormat("dd'$it' MMMM, yyyy", Locale.US) + } + private val DATE_FORMATS_WITH_ORDINAL_SUFFIXES_NO_YEAR = + ORDINAL_SUFFIXES.map { + SimpleDateFormat("dd'$it' MMMM", Locale.US) + } } } diff --git a/lib-multisrc/fuzzydoodle/src/eu/kanade/tachiyomi/multisrc/fuzzydoodle/Filters.kt b/lib-multisrc/fuzzydoodle/src/eu/kanade/tachiyomi/multisrc/fuzzydoodle/Filters.kt index 3b3ec3cfcf..d3b5a63992 100644 --- a/lib-multisrc/fuzzydoodle/src/eu/kanade/tachiyomi/multisrc/fuzzydoodle/Filters.kt +++ b/lib-multisrc/fuzzydoodle/src/eu/kanade/tachiyomi/multisrc/fuzzydoodle/Filters.kt @@ -11,25 +11,30 @@ abstract class SelectFilter( name: String, private val options: List>, private val urlParameter: String, -) : UrlPartFilter, Filter.Select( - name, - options.map { it.first }.toTypedArray(), -) { +) : Filter.Select( + name, + options.map { it.first }.toTypedArray(), + ), + UrlPartFilter { override fun addUrlParameter(url: HttpUrl.Builder) { url.addQueryParameter(urlParameter, options[state].second) } } -class CheckBoxFilter(name: String, val value: String) : Filter.CheckBox(name) +class CheckBoxFilter( + name: String, + val value: String, +) : Filter.CheckBox(name) abstract class CheckBoxGroup( name: String, options: List>, private val urlParameter: String, -) : UrlPartFilter, Filter.Group( - name, - options.map { CheckBoxFilter(it.first, it.second) }, -) { +) : Filter.Group( + name, + options.map { CheckBoxFilter(it.first, it.second) }, + ), + UrlPartFilter { override fun addUrlParameter(url: HttpUrl.Builder) { state.filter { it.state }.forEach { url.addQueryParameter(urlParameter, it.value) @@ -40,23 +45,23 @@ abstract class CheckBoxGroup( class TypeFilter( options: List>, ) : SelectFilter( - "Type", - options, - "type", -) + "Type", + options, + "type", + ) class StatusFilter( options: List>, ) : SelectFilter( - "Status", - options, - "status", -) + "Status", + options, + "status", + ) class GenreFilter( options: List>, ) : CheckBoxGroup( - "Genres", - options, - "genre[]", -) + "Genres", + options, + "genre[]", + ) diff --git a/lib-multisrc/fuzzydoodle/src/eu/kanade/tachiyomi/multisrc/fuzzydoodle/FuzzyDoodle.kt b/lib-multisrc/fuzzydoodle/src/eu/kanade/tachiyomi/multisrc/fuzzydoodle/FuzzyDoodle.kt index 57883f1556..1a7b5e14c1 100644 --- a/lib-multisrc/fuzzydoodle/src/eu/kanade/tachiyomi/multisrc/fuzzydoodle/FuzzyDoodle.kt +++ b/lib-multisrc/fuzzydoodle/src/eu/kanade/tachiyomi/multisrc/fuzzydoodle/FuzzyDoodle.kt @@ -30,19 +30,20 @@ abstract class FuzzyDoodle( override val baseUrl: String, override val lang: String, ) : ParsedHttpSource() { - override val supportsLatest = true override val client = network.cloudflareClient - override fun headersBuilder() = super.headersBuilder() - .add("Referer", "$baseUrl/") + override fun headersBuilder() = + super + .headersBuilder() + .add("Referer", "$baseUrl/") // Popular - override fun popularMangaRequest(page: Int) = - GET("$baseUrl/manga?page=$page", headers) + override fun popularMangaRequest(page: Int) = GET("$baseUrl/manga?page=$page", headers) override fun popularMangaSelector() = "div#card-real" + override fun popularMangaNextPageSelector() = "ul.pagination > li:last-child:not(.pagination-disabled)" override fun popularMangaParse(response: Response): MangasPage { @@ -50,18 +51,21 @@ abstract class FuzzyDoodle( launchIO { fetchFilters(document) } - val entries = document.select(popularMangaSelector()) - .map(::popularMangaFromElement) + val entries = + document + .select(popularMangaSelector()) + .map(::popularMangaFromElement) val hasNextPage = document.selectFirst(popularMangaNextPageSelector()) != null return MangasPage(entries, hasNextPage) } - override fun popularMangaFromElement(element: Element) = SManga.create().apply { - setUrlWithoutDomain(element.selectFirst("a")!!.absUrl("href")) - title = element.selectFirst("h2.text-sm")!!.text() - thumbnail_url = element.selectFirst("img")?.imgAttr() - } + override fun popularMangaFromElement(element: Element) = + SManga.create().apply { + setUrlWithoutDomain(element.selectFirst("a")!!.absUrl("href")) + title = element.selectFirst("h2.text-sm")!!.text() + thumbnail_url = element.selectFirst("img")?.imgAttr() + } // latest protected open val latestFromHomePage = false @@ -73,11 +77,9 @@ abstract class FuzzyDoodle( latestPageRequest(page) } - protected open fun latestHomePageRequest(page: Int) = - GET("$baseUrl/?page=$page", headers) + protected open fun latestHomePageRequest(page: Int) = GET("$baseUrl/?page=$page", headers) - protected open fun latestPageRequest(page: Int) = - GET("$baseUrl/latest?page=$page", headers) + protected open fun latestPageRequest(page: Int) = GET("$baseUrl/latest?page=$page", headers) override fun latestUpdatesSelector() = if (latestFromHomePage) { @@ -88,6 +90,7 @@ abstract class FuzzyDoodle( } override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector() + override fun latestUpdatesFromElement(element: Element) = popularMangaFromElement(element) override fun latestUpdatesParse(response: Response): MangasPage { @@ -97,23 +100,34 @@ abstract class FuzzyDoodle( } // search - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val url = "$baseUrl/manga".toHttpUrl().newBuilder().apply { - addQueryParameter("title", query.trim()) - filters.filterIsInstance().forEach { - it.addUrlParameter(this) - } - if (page > 1) { - addQueryParameter("page", page.toString()) - } - }.build() + override fun searchMangaRequest( + page: Int, + query: String, + filters: FilterList, + ): Request { + val url = + "$baseUrl/manga" + .toHttpUrl() + .newBuilder() + .apply { + addQueryParameter("title", query.trim()) + filters.filterIsInstance().forEach { + it.addUrlParameter(this) + } + if (page > 1) { + addQueryParameter("page", page.toString()) + } + }.build() return GET(url, headers) } override fun searchMangaParse(response: Response) = popularMangaParse(response) + override fun searchMangaSelector() = popularMangaSelector() + override fun searchMangaFromElement(element: Element) = popularMangaFromElement(element) + override fun searchMangaNextPageSelector() = popularMangaNextPageSelector() // filters @@ -125,9 +139,11 @@ abstract class FuzzyDoodle( protected suspend fun fetchFilters(document: Document? = null) { if (fetchFilterAttempts < 3 && (typeList.isEmpty() || statusList.isEmpty() || genreList.isEmpty())) { try { - val doc = document ?: client.newCall(filtersRequest()) - .await() - .asJsoup() + val doc = + document ?: client + .newCall(filtersRequest()) + .await() + .asJsoup() parseFilters(doc) } catch (e: Exception) { @@ -140,20 +156,25 @@ abstract class FuzzyDoodle( protected open fun filtersRequest() = GET("$baseUrl/manga", headers) protected open fun parseFilters(document: Document) { - typeList = document.select("select[name=type] > option").map { - it.ownText() to it.attr("value") - } - statusList = document.select("select[name=status] > option").map { - it.ownText() to it.attr("value") - } - genreList = document.select("div.grid > div.flex:has(> input[name=genre[]])").mapNotNull { - val label = it.selectFirst("label")?.ownText() - ?: return@mapNotNull null - val value = it.selectFirst("input")?.attr("value") - ?: return@mapNotNull null - - label to value - } + typeList = + document.select("select[name=type] > option").map { + it.ownText() to it.attr("value") + } + statusList = + document.select("select[name=status] > option").map { + it.ownText() to it.attr("value") + } + genreList = + document.select("div.grid > div.flex:has(> input[name=genre[]])").mapNotNull { + val label = + it.selectFirst("label")?.ownText() + ?: return@mapNotNull null + val value = + it.selectFirst("input")?.attr("value") + ?: return@mapNotNull null + + label to value + } } override fun getFilterList(): FilterList { @@ -180,35 +201,37 @@ abstract class FuzzyDoodle( protected fun launchIO(block: suspend () -> Unit) = scope.launch { block() } // details - override fun mangaDetailsParse(document: Document) = SManga.create().apply { - val genres = mutableListOf() - with(document.selectFirst("main > section > div")!!) { - thumbnail_url = selectFirst("div.relative img")?.imgAttr() - title = selectFirst("div.flex > h1, div.flex > h2")!!.ownText() - genres.addAll(select("div.flex > a.inline-block").eachText()) - description = buildString { - selectFirst("div:has(> p#description)")?.let { - it.selectFirst("span.font-semibold")?.remove() - it.select("#show-more").remove() - append(it.text()) - } - selectFirst("div.flex > h1 + div > span.text-sm, div.flex > h2 + div > span.text-sm")?.text()?.let { - if (it.isNotEmpty()) { - append("\n\n") - append("Alternative Title: ") - append(it.trim()) - } - } - }.trim() - } - document.selectFirst("div#buttons + div.hidden, div:has(> div#buttons) + div.flex")?.run { - status = (getInfo("Status") ?: getInfo("Statut")).parseStatus() - artist = (getInfo("Artist") ?: getInfo("المؤلف") ?: getInfo("Artiste")).removePlaceHolder() - author = (getInfo("Author") ?: getInfo("الرسام") ?: getInfo("Auteur")).removePlaceHolder() - (getInfo("Type") ?: getInfo("النوع"))?.also { genres.add(0, it) } + override fun mangaDetailsParse(document: Document) = + SManga.create().apply { + val genres = mutableListOf() + with(document.selectFirst("main > section > div")!!) { + thumbnail_url = selectFirst("div.relative img")?.imgAttr() + title = selectFirst("div.flex > h1, div.flex > h2")!!.ownText() + genres.addAll(select("div.flex > a.inline-block").eachText()) + description = + buildString { + selectFirst("div:has(> p#description)")?.let { + it.selectFirst("span.font-semibold")?.remove() + it.select("#show-more").remove() + append(it.text()) + } + selectFirst("div.flex > h1 + div > span.text-sm, div.flex > h2 + div > span.text-sm")?.text()?.let { + if (it.isNotEmpty()) { + append("\n\n") + append("Alternative Title: ") + append(it.trim()) + } + } + }.trim() + } + document.selectFirst("div#buttons + div.hidden, div:has(> div#buttons) + div.flex")?.run { + status = (getInfo("Status") ?: getInfo("Statut")).parseStatus() + artist = (getInfo("Artist") ?: getInfo("المؤلف") ?: getInfo("Artiste")).removePlaceHolder() + author = (getInfo("Author") ?: getInfo("الرسام") ?: getInfo("Auteur")).removePlaceHolder() + (getInfo("Type") ?: getInfo("النوع"))?.also { genres.add(0, it) } + } + genre = genres.joinToString() } - genre = genres.joinToString() - } protected open fun String?.parseStatus(): Int { this ?: return SManga.UNKNOWN @@ -227,39 +250,42 @@ abstract class FuzzyDoodle( ?.ownText() ?.trim() - protected fun String?.removePlaceHolder(): String? = - takeUnless { it == "-" } + protected fun String?.removePlaceHolder(): String? = takeUnless { it == "-" } // chapters override fun chapterListParse(response: Response): List { val originalUrl = response.request.url.toString() - val chapterList = buildList { - var page = 1 - do { - val doc = when { - isEmpty() -> response // First page - else -> { - page++ - client.newCall(GET("$originalUrl?page=$page", headers)).execute() - } - }.asJsoup() - - addAll(doc.select(chapterListSelector()).map(::chapterFromElement)) - } while (doc.selectFirst(chapterListNextPageSelector()) != null) - } + val chapterList = + buildList { + var page = 1 + do { + val doc = + when { + isEmpty() -> response // First page + else -> { + page++ + client.newCall(GET("$originalUrl?page=$page", headers)).execute() + } + }.asJsoup() + + addAll(doc.select(chapterListSelector()).map(::chapterFromElement)) + } while (doc.selectFirst(chapterListNextPageSelector()) != null) + } return chapterList } override fun chapterListSelector() = "div#chapters-list > a[href]" + protected fun chapterListNextPageSelector() = latestUpdatesNextPageSelector() - override fun chapterFromElement(element: Element) = SChapter.create().apply { - setUrlWithoutDomain(element.attr("href")) - name = element.selectFirst("#item-title, span")!!.ownText() - date_upload = element.selectFirst("span.text-gray-500")?.text().parseRelativeDate() - } + override fun chapterFromElement(element: Element) = + SChapter.create().apply { + setUrlWithoutDomain(element.attr("href")) + name = element.selectFirst("#item-title, span")!!.ownText() + date_upload = element.selectFirst("span.text-gray-500")?.text().parseRelativeDate() + } // from madara protected open fun String?.parseRelativeDate(): Long { @@ -295,23 +321,19 @@ abstract class FuzzyDoodle( } // pages - override fun pageListParse(document: Document): List { - return document.select("div#chapter-container > img").mapIndexed { idx, img -> + override fun pageListParse(document: Document): List = + document.select("div#chapter-container > img").mapIndexed { idx, img -> Page(idx, imageUrl = img.imgAttr()) } - } - private fun Element.imgAttr(): String { - return when { + private fun Element.imgAttr(): String = + when { hasAttr("srcset") -> attr("srcset").substringBefore(" ") hasAttr("data-cfsrc") -> absUrl("data-cfsrc") hasAttr("data-src") -> absUrl("data-src") hasAttr("data-lazy-src") -> absUrl("data-lazy-src") else -> absUrl("src") } - } - override fun imageUrlParse(document: Document): String { - throw UnsupportedOperationException() - } + override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException() } diff --git a/lib-multisrc/galleryadults/src/eu/kanade/tachiyomi/multisrc/galleryadults/GalleryAdults.kt b/lib-multisrc/galleryadults/src/eu/kanade/tachiyomi/multisrc/galleryadults/GalleryAdults.kt index 6a23b9922d..a1afe367c3 100644 --- a/lib-multisrc/galleryadults/src/eu/kanade/tachiyomi/multisrc/galleryadults/GalleryAdults.kt +++ b/lib-multisrc/galleryadults/src/eu/kanade/tachiyomi/multisrc/galleryadults/GalleryAdults.kt @@ -47,15 +47,17 @@ abstract class GalleryAdults( override val lang: String = "all", protected open val mangaLang: String = LANGUAGE_MULTI, protected val simpleDateFormat: SimpleDateFormat? = null, -) : ConfigurableSource, ParsedHttpSource() { - +) : ParsedHttpSource(), + ConfigurableSource { override val client: OkHttpClient = network.cloudflareClient - protected open val xhrHeaders = headers.newBuilder() - .add("X-Requested-With", "XMLHttpRequest") - .build() + protected open val xhrHeaders = + headers + .newBuilder() + .add("X-Requested-With", "XMLHttpRequest") + .build() - /* Preferences */ + // Preferences protected val preferences: SharedPreferences by lazy { Injekt.get().getSharedPreferences("source_$id", 0x0000) } @@ -63,20 +65,21 @@ abstract class GalleryAdults( protected open val useShortTitlePreference = true override fun setupPreferenceScreen(screen: PreferenceScreen) { - SwitchPreferenceCompat(screen.context).apply { - key = PREF_SHORT_TITLE - title = "Display Short Titles" - summaryOff = "Showing Long Titles" - summaryOn = "Showing short Titles" - setDefaultValue(false) - setVisible(useShortTitlePreference) - }.also(screen::addPreference) + SwitchPreferenceCompat(screen.context) + .apply { + key = PREF_SHORT_TITLE + title = "Display Short Titles" + summaryOff = "Showing Long Titles" + summaryOn = "Showing short Titles" + setDefaultValue(false) + setVisible(useShortTitlePreference) + }.also(screen::addPreference) } protected val SharedPreferences.shortTitle get() = getBoolean(PREF_SHORT_TITLE, false) - /* List detail */ + // List detail protected class SMangaDto( val title: String, val url: String, @@ -89,18 +92,15 @@ abstract class GalleryAdults( if (preferences.shortTitle) it?.shortenTitle() else it } - protected open fun Element.mangaFullTitle(selector: String) = - selectFirst(selector)?.text() + protected open fun Element.mangaFullTitle(selector: String) = selectFirst(selector)?.text() protected open fun String.shortenTitle() = this.replace(shortenTitleRegex, "").trim() protected open val shortenTitleRegex = Regex("""(\[[^]]*]|[({][^)}]*[)}])""") - protected open fun Element.mangaUrl() = - selectFirst(".inner_thumb a")?.attr("abs:href") + protected open fun Element.mangaUrl() = selectFirst(".inner_thumb a")?.attr("abs:href") - protected open fun Element.mangaThumbnail() = - selectFirst(".inner_thumb img")?.imgAttr() + protected open fun Element.mangaThumbnail() = selectFirst(".inner_thumb img")?.imgAttr() // Overwrite this to filter other languages' manga from search result. // Default to [mangaLang] won't filter anything @@ -115,34 +115,35 @@ abstract class GalleryAdults( return this } - /* Popular */ + // Popular override fun popularMangaRequest(page: Int): Request { - val url = baseUrl.toHttpUrl().newBuilder().apply { - if (mangaLang.isNotBlank()) addPathSegments("language/$mangaLang") - if (supportsLatest) addPathSegment("popular") - addPageUri(page) - } + val url = + baseUrl.toHttpUrl().newBuilder().apply { + if (mangaLang.isNotBlank()) addPathSegments("language/$mangaLang") + if (supportsLatest) addPathSegment("popular") + addPageUri(page) + } return GET(url.build(), headers) } override fun popularMangaSelector() = "div.thumb" - override fun popularMangaFromElement(element: Element): SManga { - return SManga.create().apply { + override fun popularMangaFromElement(element: Element): SManga = + SManga.create().apply { title = element.mangaTitle()!! setUrlWithoutDomain(element.mangaUrl()!!) thumbnail_url = element.mangaThumbnail() } - } override fun popularMangaNextPageSelector() = ".pagination li.active + li:not(.disabled)" - /* Latest */ + // Latest override fun latestUpdatesRequest(page: Int): Request { - val url = baseUrl.toHttpUrl().newBuilder().apply { - if (mangaLang.isNotBlank()) addPathSegments("language/$mangaLang") - addPageUri(page) - } + val url = + baseUrl.toHttpUrl().newBuilder().apply { + if (mangaLang.isNotBlank()) addPathSegments("language/$mangaLang") + addPageUri(page) + } return GET(url.build(), headers) } @@ -152,29 +153,37 @@ abstract class GalleryAdults( override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector() - /* Search */ - override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable { + // Search + override fun fetchSearchManga( + page: Int, + query: String, + filters: FilterList, + ): Observable { val randomEntryFilter = filters.filterIsInstance().firstOrNull() return when { randomEntryFilter?.state == true -> { - client.newCall(randomEntryRequest()) + client + .newCall(randomEntryRequest()) .asObservableSuccess() .map { response -> randomEntryParse(response) } } query.startsWith(PREFIX_ID_SEARCH) -> { val id = query.removePrefix(PREFIX_ID_SEARCH) - client.newCall(searchMangaByIdRequest(id)) + client + .newCall(searchMangaByIdRequest(id)) .asObservableSuccess() .map { response -> searchMangaByIdParse(response, id) } } query.toIntOrNull() != null -> { - client.newCall(searchMangaByIdRequest(query)) + client + .newCall(searchMangaByIdRequest(query)) .asObservableSuccess() .map { response -> searchMangaByIdParse(response, query) } } else -> { - client.newCall(searchMangaRequest(page, query, filters)) + client + .newCall(searchMangaRequest(page, query, filters)) .asObservableSuccess() .map { response -> searchMangaParse(response) } } @@ -206,14 +215,18 @@ abstract class GalleryAdults( protected open val idPrefixUri = "gallery" protected open fun searchMangaByIdRequest(id: String): Request { - val url = baseUrl.toHttpUrl().newBuilder().apply { - addPathSegment(idPrefixUri) - addPathSegments("$id/") - } + val url = + baseUrl.toHttpUrl().newBuilder().apply { + addPathSegment(idPrefixUri) + addPathSegments("$id/") + } return GET(url.build(), headers) } - protected open fun searchMangaByIdParse(response: Response, id: String): MangasPage { + protected open fun searchMangaByIdParse( + response: Response, + id: String, + ): MangasPage { val details = mangaDetailsParse(response.asJsoup()) details.url = "/$idPrefixUri/$id/" return MangasPage(listOf(details), false) @@ -225,7 +238,11 @@ abstract class GalleryAdults( protected open val useBasicSearch: Boolean get() = !useIntermediateSearch - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { + override fun searchMangaRequest( + page: Int, + query: String, + filters: FilterList, + ): Request { // Basic search val sortOrderFilter = filters.filterIsInstance().firstOrNull() val genresFilter = filters.filterIsInstance().firstOrNull() @@ -263,18 +280,23 @@ abstract class GalleryAdults( /** * Basic Search: support query string with multiple-genres filter by adding genres to query string. */ - protected open fun basicSearchRequest(page: Int, query: String, filters: FilterList): Request { + protected open fun basicSearchRequest( + page: Int, + query: String, + filters: FilterList, + ): Request { // Basic search val sortOrderFilter = filters.filterIsInstance().firstOrNull() val genresFilter = filters.filterIsInstance().firstOrNull() val selectedGenres = genresFilter?.state?.filter { it.state } ?: emptyList() - val url = baseUrl.toHttpUrl().newBuilder().apply { - addPathSegments("search/") - addEncodedQueryParameter(basicSearchKey, buildQueryString(selectedGenres.map { it.name }, query)) - if (sortOrderFilter?.state == 0) addQueryParameter("sort", "popular") - addPageUri(page) - } + val url = + baseUrl.toHttpUrl().newBuilder().apply { + addPathSegments("search/") + addEncodedQueryParameter(basicSearchKey, buildQueryString(selectedGenres.map { it.name }, query)) + if (sortOrderFilter?.state == 0) addQueryParameter("sort", "popular") + addPageUri(page) + } return GET(url.build(), headers) } @@ -284,7 +306,11 @@ abstract class GalleryAdults( * This supports filter query search with languages, categories (manga, doujinshi...) * with additional sort orders. */ - protected open fun intermediateSearchRequest(page: Int, query: String, filters: FilterList): Request { + protected open fun intermediateSearchRequest( + page: Int, + query: String, + filters: FilterList, + ): Request { // Basic search val sortOrderFilter = filters.filterIsInstance().firstOrNull() val genresFilter = filters.filterIsInstance().firstOrNull() @@ -294,22 +320,23 @@ abstract class GalleryAdults( val categoryFilters = filters.filterIsInstance().firstOrNull() // Only for query string or multiple tags - val url = "$baseUrl/search/".toHttpUrl().newBuilder().apply { - getSortOrderURIs().forEachIndexed { index, pair -> - addQueryParameter(pair.second, toBinary(sortOrderFilter?.state == index)) - } - categoryFilters?.state?.forEach { - addQueryParameter(it.uri, toBinary(it.state)) - } - getLanguageURIs().forEach { pair -> - addQueryParameter( - pair.second, - toBinary(mangaLang == pair.first || mangaLang == LANGUAGE_MULTI), - ) + val url = + "$baseUrl/search/".toHttpUrl().newBuilder().apply { + getSortOrderURIs().forEachIndexed { index, pair -> + addQueryParameter(pair.second, toBinary(sortOrderFilter?.state == index)) + } + categoryFilters?.state?.forEach { + addQueryParameter(it.uri, toBinary(it.state)) + } + getLanguageURIs().forEach { pair -> + addQueryParameter( + pair.second, + toBinary(mangaLang == pair.first || mangaLang == LANGUAGE_MULTI), + ) + } + addEncodedQueryParameter(intermediateSearchKey, buildQueryString(selectedGenres.map { it.name }, query)) + addPageUri(page) } - addEncodedQueryParameter(intermediateSearchKey, buildQueryString(selectedGenres.map { it.name }, query)) - addPageUri(page) - } return GET(url.build(), headers) } @@ -320,7 +347,11 @@ abstract class GalleryAdults( * Advanced Search normally won't support search for string but allow include/exclude specific * tags/artists/groups/parodies/characters */ - protected open fun advancedSearchRequest(page: Int, query: String, filters: FilterList): Request { + protected open fun advancedSearchRequest( + page: Int, + query: String, + filters: FilterList, + ): Request { // Basic search val sortOrderFilter = filters.filterIsInstance().firstOrNull() val genresFilter = filters.filterIsInstance().firstOrNull() @@ -331,54 +362,57 @@ abstract class GalleryAdults( // Advanced search val advancedSearchFilters = filters.filterIsInstance() - val url = "$baseUrl/$advancedSearchUri/".toHttpUrl().newBuilder().apply { - getSortOrderURIs().forEachIndexed { index, pair -> - addQueryParameter(pair.second, toBinary(sortOrderFilter?.state == index)) - } - categoryFilters?.state?.forEach { - addQueryParameter(it.uri, toBinary(it.state)) - } - getLanguageURIs().forEach { pair -> - addQueryParameter( - pair.second, - toBinary( - mangaLang == pair.first || - mangaLang == LANGUAGE_MULTI, - ), - ) - } - - // Build this query string: +tag:"bat+man"+-tag:"cat"+artist:"Joe"... - // +tag must be encoded into %2Btag while the rest are not needed to encode - val keys = emptyList().toMutableList() - keys.addAll(selectedGenres.map { "%2Btag:\"${it.name}\"" }) - advancedSearchFilters.forEach { filter -> - val key = when (filter) { - is TagsFilter -> "tag" - is ParodiesFilter -> "parody" - is ArtistsFilter -> "artist" - is CharactersFilter -> "character" - is GroupsFilter -> "group" - else -> null + val url = + "$baseUrl/$advancedSearchUri/".toHttpUrl().newBuilder().apply { + getSortOrderURIs().forEachIndexed { index, pair -> + addQueryParameter(pair.second, toBinary(sortOrderFilter?.state == index)) } - if (key != null) { - keys.addAll( - filter.state.trim() - .replace(regexSpaceNotAfterComma, "+") - .replace(" ", "") - .split(',') - .mapNotNull { - val match = regexExcludeTerm.find(it) - match?.groupValues?.let { groups -> - "${if (groups[1].isNotBlank()) "-" else "%2B"}$key:\"${groups[2]}\"" - } - }, + categoryFilters?.state?.forEach { + addQueryParameter(it.uri, toBinary(it.state)) + } + getLanguageURIs().forEach { pair -> + addQueryParameter( + pair.second, + toBinary( + mangaLang == pair.first || + mangaLang == LANGUAGE_MULTI, + ), ) } + + // Build this query string: +tag:"bat+man"+-tag:"cat"+artist:"Joe"... + // +tag must be encoded into %2Btag while the rest are not needed to encode + val keys = emptyList().toMutableList() + keys.addAll(selectedGenres.map { "%2Btag:\"${it.name}\"" }) + advancedSearchFilters.forEach { filter -> + val key = + when (filter) { + is TagsFilter -> "tag" + is ParodiesFilter -> "parody" + is ArtistsFilter -> "artist" + is CharactersFilter -> "character" + is GroupsFilter -> "group" + else -> null + } + if (key != null) { + keys.addAll( + filter.state + .trim() + .replace(regexSpaceNotAfterComma, "+") + .replace(" ", "") + .split(',') + .mapNotNull { + val match = regexExcludeTerm.find(it) + match?.groupValues?.let { groups -> + "${if (groups[1].isNotBlank()) "-" else "%2B"}$key:\"${groups[2]}\"" + } + }, + ) + } + } + addEncodedQueryParameter(advancedSearchKey, keys.joinToString("+")) + addPageUri(page) } - addEncodedQueryParameter(advancedSearchKey, keys.joinToString("+")) - addPageUri(page) - } return GET(url.build(), headers) } @@ -388,28 +422,36 @@ abstract class GalleryAdults( * - use comma(,) for separate terms, as AND condition. * Plus(+) after comma(,) doesn't have any effect. */ - protected open fun buildQueryString(tags: List, query: String): String { - return (tags + query).filterNot { it.isBlank() }.joinToString(",") { + protected open fun buildQueryString( + tags: List, + query: String, + ): String = + (tags + query).filterNot { it.isBlank() }.joinToString(",") { // any space except after a comma (we're going to replace spaces only between words) - it.trim() + it + .trim() .replace(regexSpaceNotAfterComma, "+") .replace(" ", "") } - } - protected open fun tagBrowsingSearchRequest(page: Int, query: String, filters: FilterList): Request { + protected open fun tagBrowsingSearchRequest( + page: Int, + query: String, + filters: FilterList, + ): Request { // Basic search val sortOrderFilter = filters.filterIsInstance().firstOrNull() val genresFilter = filters.filterIsInstance().firstOrNull() val selectedGenres = genresFilter?.state?.filter { it.state } ?: emptyList() // Browsing single tag's catalog - val url = baseUrl.toHttpUrl().newBuilder().apply { - addPathSegment("tag") - addPathSegment(selectedGenres.single().uri) - if (sortOrderFilter?.state == 0) addPathSegment("popular") - addPageUri(page) - } + val url = + baseUrl.toHttpUrl().newBuilder().apply { + addPathSegment("tag") + addPathSegment(selectedGenres.single().uri) + if (sortOrderFilter?.state == 0) addPathSegment("popular") + addPageUri(page) + } return GET(url.build(), headers) } @@ -417,28 +459,38 @@ abstract class GalleryAdults( * Browsing speechless titles. Some sites exclude speechless titles from normal search and * allow browsing separately. */ - protected open fun speechlessFilterSearchRequest(page: Int, query: String, filters: FilterList): Request { + protected open fun speechlessFilterSearchRequest( + page: Int, + query: String, + filters: FilterList, + ): Request { // Basic search val sortOrderFilter = filters.filterIsInstance().firstOrNull() - val url = baseUrl.toHttpUrl().newBuilder().apply { - addPathSegment("language") - addPathSegment(LANGUAGE_SPEECHLESS) - if (sortOrderFilter?.state == 0) addPathSegment("popular") - addPageUri(page) - } + val url = + baseUrl.toHttpUrl().newBuilder().apply { + addPathSegment("language") + addPathSegment(LANGUAGE_SPEECHLESS) + if (sortOrderFilter?.state == 0) addPathSegment("popular") + addPageUri(page) + } return GET(url.build(), headers) } /** * Browsing user's personal favorites saved on site. This requires login in view WebView. */ - protected open fun favoriteFilterSearchRequest(page: Int, query: String, filters: FilterList): Request { + protected open fun favoriteFilterSearchRequest( + page: Int, + query: String, + filters: FilterList, + ): Request { val url = "$baseUrl/$favoritePath".toHttpUrl().newBuilder() return POST( url.build().toString(), xhrHeaders, - FormBody.Builder() + FormBody + .Builder() .add("page", page.toString()) .build(), ) @@ -446,12 +498,14 @@ abstract class GalleryAdults( protected open val favoritePath = "user/fav_pags.php" - protected open fun loginRequired(document: Document, url: String): Boolean { - return ( + protected open fun loginRequired( + document: Document, + url: String, + ): Boolean = + ( url.contains("/login/") && document.select("input[value=Login]").isNotEmpty() - ) - } + ) override fun searchMangaParse(response: Response): MangasPage { val document = response.asJsoup() @@ -459,27 +513,27 @@ abstract class GalleryAdults( throw Exception("Log in via WebView to view favorites") } else { val hasNextPage = document.select(searchMangaNextPageSelector()).isNotEmpty() - val mangas = document.select(searchMangaSelector()) - .map { - SMangaDto( - title = it.mangaTitle()!!, - url = it.mangaUrl()!!, - thumbnail = it.mangaThumbnail(), - lang = it.mangaLang(), - ) - } - .let { unfiltered -> - val results = unfiltered.filter { mangaLang.isBlank() || it.lang == mangaLang } - // return at least 1 title if all mangas in current page is of other languages - if (results.isEmpty() && hasNextPage) listOf(unfiltered[0]) else results - } - .map { - SManga.create().apply { - title = it.title - setUrlWithoutDomain(it.url) - thumbnail_url = it.thumbnail + val mangas = + document + .select(searchMangaSelector()) + .map { + SMangaDto( + title = it.mangaTitle()!!, + url = it.mangaUrl()!!, + thumbnail = it.mangaThumbnail(), + lang = it.mangaLang(), + ) + }.let { unfiltered -> + val results = unfiltered.filter { mangaLang.isBlank() || it.lang == mangaLang } + // return at least 1 title if all mangas in current page is of other languages + if (results.isEmpty() && hasNextPage) listOf(unfiltered[0]) else results + }.map { + SManga.create().apply { + title = it.title + setUrlWithoutDomain(it.url) + thumbnail_url = it.thumbnail + } } - } return MangasPage(mangas, hasNextPage) } @@ -491,11 +545,11 @@ abstract class GalleryAdults( override fun searchMangaNextPageSelector() = popularMangaNextPageSelector() - /* Details */ + // Details protected open val mangaDetailInfoSelector = ".gallery_top" - override fun mangaDetailsParse(document: Document): SManga { - return document.selectFirst(mangaDetailInfoSelector)!!.run { + override fun mangaDetailsParse(document: Document): SManga = + document.selectFirst(mangaDetailInfoSelector)!!.run { SManga.create().apply { update_strategy = UpdateStrategy.ONLY_FETCH_ONCE status = SManga.COMPLETED @@ -506,10 +560,8 @@ abstract class GalleryAdults( description = getDescription(document) } } - } - protected open fun Element.getCover() = - selectFirst(".cover img")?.imgAttr() + protected open fun Element.getCover() = selectFirst(".cover img")?.imgAttr() protected val regexTag = Regex("Tags?") @@ -518,47 +570,50 @@ abstract class GalleryAdults( */ protected abstract fun Element.getInfo(tag: String): String - protected open fun Element.getDescription(document: Document? = null): String = ( - listOf("Parodies", "Characters", "Languages", "Categories", "Category") - .mapNotNull { tag -> - getInfo(tag) - .takeIf { it.isNotBlank() } - ?.let { "$tag: $it" } - } + - listOfNotNull( - getInfoPages(document), - getInfoAlternativeTitle(), - getInfoFullTitle(), - ) - ) - .joinToString("\n\n") + protected open fun Element.getDescription(document: Document? = null): String = + ( + listOf("Parodies", "Characters", "Languages", "Categories", "Category") + .mapNotNull { tag -> + getInfo(tag) + .takeIf { it.isNotBlank() } + ?.let { "$tag: $it" } + } + + listOfNotNull( + getInfoPages(document), + getInfoAlternativeTitle(), + getInfoFullTitle(), + ) + ).joinToString("\n\n") protected open fun Element.getInfoPages(document: Document? = null): String? = - document?.inputIdValueOf(totalPagesSelector) + document + ?.inputIdValueOf(totalPagesSelector) ?.takeIf { it.isNotBlank() } ?.let { "Pages: $it" } protected open fun Element.getInfoAlternativeTitle(): String? = - selectFirst("h1 + h2, .subtitle")?.ownText() + selectFirst("h1 + h2, .subtitle") + ?.ownText() .takeIf { !it.isNullOrBlank() } ?.let { "Alternative title: $it" } - protected open fun Element.getInfoFullTitle(): String? = - if (preferences.shortTitle) "Full title: ${mangaFullTitle("h1")}" else null + protected open fun Element.getInfoFullTitle(): String? = if (preferences.shortTitle) "Full title: ${mangaFullTitle("h1")}" else null protected open fun Element.getTime(): Long = selectFirst(".uploaded") ?.ownText() .toDate(simpleDateFormat) - /* Chapters */ + // Chapters override fun chapterListParse(response: Response): List { val document = response.asJsoup() return listOf( SChapter.create().apply { name = "Chapter" - scanlator = document.selectFirst(mangaDetailInfoSelector) - ?.getInfo("Groups") + scanlator = + document + .selectFirst(mangaDetailInfoSelector) + ?.getInfo("Groups") date_upload = document.getTime() setUrlWithoutDomain(response.request.url.encodedPath) }, @@ -569,10 +624,8 @@ abstract class GalleryAdults( override fun chapterFromElement(element: Element): SChapter = throw UnsupportedOperationException() - /* Pages */ - protected open fun Element.inputIdValueOf(string: String): String { - return select("input[id=$string]").attr("value") - } + // Pages + protected open fun Element.inputIdValueOf(string: String): String = select("input[id=$string]").attr("value") protected open val pagesRequest = "inc/thumbs_loader.php" protected open val galleryIdSelector = "gallery_id" @@ -581,11 +634,16 @@ abstract class GalleryAdults( protected open val totalPagesSelector = "load_pages" protected open val serverSelector = "load_server" - protected open fun pageRequestForm(document: Document, totalPages: String, loadedPages: Int): FormBody { + protected open fun pageRequestForm( + document: Document, + totalPages: String, + loadedPages: Int, + ): FormBody { val token = document.select("[name=csrf-token]").attr("content") val serverNumber = document.serverNumber() - return FormBody.Builder() + return FormBody + .Builder() .add("u_id", document.inputIdValueOf(galleryIdSelector)) .add("g_id", document.inputIdValueOf(loadIdSelector)) .add("img_dir", document.inputIdValueOf(loadDirSelector)) @@ -595,8 +653,7 @@ abstract class GalleryAdults( .apply { if (token.isNotBlank()) add("_token", token) if (serverNumber != null) add("server", serverNumber) - } - .build() + }.build() } protected open val thumbnailSelector = ".gallery_thumb" @@ -615,9 +672,11 @@ abstract class GalleryAdults( .takeIf { it.isNotBlank() } protected open fun Element.parseJson(): String? = - selectFirst("script:containsData(parseJSON)")?.data() + selectFirst("script:containsData(parseJSON)") + ?.data() ?.substringAfter("$.parseJSON('") - ?.substringBefore("');")?.trim() + ?.substringBefore("');") + ?.trim() /** * Page URL: $baseUrl/$pageUri// @@ -642,14 +701,19 @@ abstract class GalleryAdults( // JSON string in this form: {"1":"j,1100,1148","2":"j,728,689",... for (image in images) { - val ext = image.value.toString().replace("\"", "").split(",")[0] - val imageExt = when (ext) { - "p" -> "png" - "b" -> "bmp" - "g" -> "gif" - "w" -> "webp" - else -> "jpg" - } + val ext = + image.value + .toString() + .replace("\"", "") + .split(",")[0] + val imageExt = + when (ext) { + "p" -> "png" + "b" -> "bmp" + "g" -> "gif" + "w" -> "webp" + else -> "jpg" + } val idx = image.key.toInt() pages.add( Page( @@ -690,30 +754,33 @@ abstract class GalleryAdults( val galleryId = document.inputIdValueOf(galleryIdSelector) val pageUrl = "$baseUrl/$pageUri/$galleryId" - val pages = document.select("$thumbnailSelector a") - .map { - if (parsingImagePageByPage) { - it.absUrl("href") - } else { - it.selectFirst("img")!!.imgAttr() - } - } - .toMutableList() - - if (totalPages.isNotBlank() && totalPages.toInt() > pages.size) { - val form = pageRequestForm(document, totalPages, pages.size) - - val morePages = client.newCall(POST("$baseUrl/$pagesRequest", xhrHeaders, form)) - .execute() - .asJsoup() - .select("a") + val pages = + document + .select("$thumbnailSelector a") .map { if (parsingImagePageByPage) { it.absUrl("href") } else { it.selectFirst("img")!!.imgAttr() } - } + }.toMutableList() + + if (totalPages.isNotBlank() && totalPages.toInt() > pages.size) { + val form = pageRequestForm(document, totalPages, pages.size) + + val morePages = + client + .newCall(POST("$baseUrl/$pagesRequest", xhrHeaders, form)) + .execute() + .asJsoup() + .select("a") + .map { + if (parsingImagePageByPage) { + it.absUrl("href") + } else { + it.selectFirst("img")!!.imgAttr() + } + } if (morePages.isNotEmpty()) { pages.addAll(morePages) } else { @@ -753,8 +820,11 @@ abstract class GalleryAdults( val totalPages = document.inputIdValueOf(totalPagesSelector) if (totalPages.isNotBlank() && totalPages.toInt() > thumbUrls.size) { - val imagesExt = images.first()?.imgAttr()!! - .substringAfterLast('.') + val imagesExt = + images + .first() + ?.imgAttr()!! + .substringAfterLast('.') thumbUrls.addAll( listOf((images.size + 1)..totalPages.toInt()).flatten().map { @@ -771,13 +841,13 @@ abstract class GalleryAdults( } } - override fun imageUrlParse(document: Document): String { - return document.selectFirst("img#gimg, img#fimg")?.imgAttr()!! - } + override fun imageUrlParse(document: Document): String = document.selectFirst("img#gimg, img#fimg")?.imgAttr()!! - /* Filters */ + // Filters private val scope = CoroutineScope(Dispatchers.IO) + private fun launchIO(block: () -> Unit) = scope.launch { block() } + private var tagsFetched = false private var tagsFetchAttempt = 0 @@ -787,26 +857,29 @@ abstract class GalleryAdults( protected var genres: MutableMap = mutableMapOf() protected open fun tagsRequest(page: Int): Request { - val url = baseUrl.toHttpUrl().newBuilder().apply { - addPathSegments("tags/popular") - addPageUri(page) - } + val url = + baseUrl.toHttpUrl().newBuilder().apply { + addPathSegments("tags/popular") + addPageUri(page) + } return GET(url.build(), headers) } /** * Parsing [document] to return a list of tags in pairs. */ - protected open fun tagsParser(document: Document): List { - return document.select("a.tag_btn") + protected open fun tagsParser(document: Document): List = + document + .select("a.tag_btn") .mapNotNull { Genre( it.select(".list_tag, .tag_name").text(), - it.attr("href") - .removeSuffix("/").substringAfterLast('/'), + it + .attr("href") + .removeSuffix("/") + .substringAfterLast('/'), ) } - } protected open fun requestTags() { if (!tagsFetched && tagsFetchAttempt < 3) { @@ -820,15 +893,19 @@ abstract class GalleryAdults( launchIO { runCatching { tags.addAll( - client.newCall(tagsRequest(page)) - .execute().asJsoup().let { tagsParser(it) }, + client + .newCall(tagsRequest(page)) + .execute() + .asJsoup() + .let { tagsParser(it) }, ) } }, ) } jobsPool.joinAll() - tags.sortedWith(compareBy { it.name }) + tags + .sortedWith(compareBy { it.name }) .forEach { genres[it.name] = it.uri } @@ -868,7 +945,9 @@ abstract class GalleryAdults( filters.addAll( listOf( Filter.Separator(), - Filter.Header("Advanced filters will ignore query search. Separate terms by comma (,) and precede term with minus (-) to exclude."), + Filter.Header( + "Advanced filters will ignore query search. Separate terms by comma (,) and precede term with minus (-) to exclude.", + ), TagsFilter(), ParodiesFilter(), ArtistsFilter(), @@ -890,36 +969,40 @@ abstract class GalleryAdults( return FilterList(filters) } - protected open fun getSortOrderURIs() = listOf( - Pair("Popular", "pp"), - Pair("Latest", "lt"), - ) + if (useIntermediateSearch || supportAdvancedSearch) { + protected open fun getSortOrderURIs() = listOf( - Pair("Downloads", "dl"), - Pair("Top Rated", "tr"), - ) - } else { - emptyList() - } + Pair("Popular", "pp"), + Pair("Latest", "lt"), + ) + + if (useIntermediateSearch || supportAdvancedSearch) { + listOf( + Pair("Downloads", "dl"), + Pair("Top Rated", "tr"), + ) + } else { + emptyList() + } - protected open fun getCategoryURIs() = listOf( - SearchFlagFilter("Manga", "m"), - SearchFlagFilter("Doujinshi", "d"), - SearchFlagFilter("Western", "w"), - SearchFlagFilter("Image Set", "i"), - SearchFlagFilter("Artist CG", "a"), - SearchFlagFilter("Game CG", "g"), - ) + protected open fun getCategoryURIs() = + listOf( + SearchFlagFilter("Manga", "m"), + SearchFlagFilter("Doujinshi", "d"), + SearchFlagFilter("Western", "w"), + SearchFlagFilter("Image Set", "i"), + SearchFlagFilter("Artist CG", "a"), + SearchFlagFilter("Game CG", "g"), + ) - protected open fun getLanguageURIs() = listOf( - Pair(LANGUAGE_ENGLISH, "en"), - Pair(LANGUAGE_JAPANESE, "jp"), - Pair(LANGUAGE_SPANISH, "es"), - Pair(LANGUAGE_FRENCH, "fr"), - Pair(LANGUAGE_KOREAN, "kr"), - Pair(LANGUAGE_GERMAN, "de"), - Pair(LANGUAGE_RUSSIAN, "ru"), - ) + protected open fun getLanguageURIs() = + listOf( + Pair(LANGUAGE_ENGLISH, "en"), + Pair(LANGUAGE_JAPANESE, "jp"), + Pair(LANGUAGE_SPANISH, "es"), + Pair(LANGUAGE_FRENCH, "fr"), + Pair(LANGUAGE_KOREAN, "kr"), + Pair(LANGUAGE_GERMAN, "de"), + Pair(LANGUAGE_RUSSIAN, "ru"), + ) companion object { const val PREFIX_ID_SEARCH = "id:" diff --git a/lib-multisrc/galleryadults/src/eu/kanade/tachiyomi/multisrc/galleryadults/GalleryAdultsFilters.kt b/lib-multisrc/galleryadults/src/eu/kanade/tachiyomi/multisrc/galleryadults/GalleryAdultsFilters.kt index 994414e765..ddbd2135a7 100644 --- a/lib-multisrc/galleryadults/src/eu/kanade/tachiyomi/multisrc/galleryadults/GalleryAdultsFilters.kt +++ b/lib-multisrc/galleryadults/src/eu/kanade/tachiyomi/multisrc/galleryadults/GalleryAdultsFilters.kt @@ -2,14 +2,21 @@ package eu.kanade.tachiyomi.multisrc.galleryadults import eu.kanade.tachiyomi.source.model.Filter -class Genre(name: String, val uri: String) : Filter.CheckBox(name) -class GenresFilter(genres: Map) : Filter.Group( - "Tags", - genres.map { Genre(it.key, it.value) }, -) - -class SortOrderFilter(sortOrderURIs: List>) : - Filter.Select("Sort By", sortOrderURIs.map { it.first }.toTypedArray()) +class Genre( + name: String, + val uri: String, +) : Filter.CheckBox(name) + +class GenresFilter( + genres: Map, +) : Filter.Group( + "Tags", + genres.map { Genre(it.key, it.value) }, + ) + +class SortOrderFilter( + sortOrderURIs: List>, +) : Filter.Select("Sort By", sortOrderURIs.map { it.first }.toTypedArray()) class FavoriteFilter : Filter.CheckBox("Show favorites only (login via WebView)", false) @@ -19,13 +26,27 @@ class RandomEntryFilter : Filter.CheckBox("Random manga", false) class SpeechlessFilter : Filter.CheckBox("Show speechless items only", false) // Intermediate search -class SearchFlagFilter(name: String, val uri: String, state: Boolean = true) : Filter.CheckBox(name, state) -class CategoryFilters(flags: List) : Filter.Group("Categories", flags) +class SearchFlagFilter( + name: String, + val uri: String, + state: Boolean = true, +) : Filter.CheckBox(name, state) + +class CategoryFilters( + flags: List, +) : Filter.Group("Categories", flags) // Advance search -abstract class AdvancedTextFilter(name: String) : Filter.Text(name) +abstract class AdvancedTextFilter( + name: String, +) : Filter.Text(name) + class TagsFilter : AdvancedTextFilter("Tags") + class ParodiesFilter : AdvancedTextFilter("Parodies") + class ArtistsFilter : AdvancedTextFilter("Artists") + class CharactersFilter : AdvancedTextFilter("Characters") + class GroupsFilter : AdvancedTextFilter("Groups") diff --git a/lib-multisrc/galleryadults/src/eu/kanade/tachiyomi/multisrc/galleryadults/GalleryAdultsUrlActivity.kt b/lib-multisrc/galleryadults/src/eu/kanade/tachiyomi/multisrc/galleryadults/GalleryAdultsUrlActivity.kt index 0aa18cd52a..ae12c5f895 100644 --- a/lib-multisrc/galleryadults/src/eu/kanade/tachiyomi/multisrc/galleryadults/GalleryAdultsUrlActivity.kt +++ b/lib-multisrc/galleryadults/src/eu/kanade/tachiyomi/multisrc/galleryadults/GalleryAdultsUrlActivity.kt @@ -16,11 +16,12 @@ class GalleryAdultsUrlActivity : Activity() { val pathSegments = intent?.data?.pathSegments if (pathSegments != null && pathSegments.size > 1) { val id = pathSegments[1] - val mainIntent = Intent().apply { - action = "eu.kanade.tachiyomi.SEARCH" - putExtra("query", "${GalleryAdults.PREFIX_ID_SEARCH}$id") - putExtra("filter", packageName) - } + val mainIntent = + Intent().apply { + action = "eu.kanade.tachiyomi.SEARCH" + putExtra("query", "${GalleryAdults.PREFIX_ID_SEARCH}$id") + putExtra("filter", packageName) + } try { startActivity(mainIntent) diff --git a/lib-multisrc/galleryadults/src/eu/kanade/tachiyomi/multisrc/galleryadults/GalleryAdultsUtils.kt b/lib-multisrc/galleryadults/src/eu/kanade/tachiyomi/multisrc/galleryadults/GalleryAdultsUtils.kt index 2410adf57c..b8d86ffe5f 100644 --- a/lib-multisrc/galleryadults/src/eu/kanade/tachiyomi/multisrc/galleryadults/GalleryAdultsUtils.kt +++ b/lib-multisrc/galleryadults/src/eu/kanade/tachiyomi/multisrc/galleryadults/GalleryAdultsUtils.kt @@ -17,15 +17,17 @@ val regexDate = Regex("""\d\D\D""") val regexNotNumber = Regex("""\D""") val regexRelativeDateTime = Regex("""\d*[^0-9]*(\d+)""") -fun Element.imgAttr() = when { - hasAttr("data-cfsrc") -> absUrl("data-cfsrc") - hasAttr("data-src") -> absUrl("data-src") - hasAttr("data-lazy-src") -> absUrl("data-lazy-src") - hasAttr("srcset") -> absUrl("srcset").substringBefore(" ") - else -> absUrl("src") -} +fun Element.imgAttr() = + when { + hasAttr("data-cfsrc") -> absUrl("data-cfsrc") + hasAttr("data-src") -> absUrl("data-src") + hasAttr("data-lazy-src") -> absUrl("data-lazy-src") + hasAttr("srcset") -> absUrl("srcset").substringBefore(" ") + else -> absUrl("src") + } fun Element.cleanTag(): String = text().cleanTag() + fun String.cleanTag(): String = replace(regexTagCountNumber, "").trim() // convert thumbnail URLs to full image URLs @@ -40,14 +42,14 @@ fun String?.toDate(simpleDateFormat: SimpleDateFormat?): Long { return if (simpleDateFormat != null) { if (contains(regexDateSuffix)) { // Clean date (e.g. 5th December 2019 to 5 December 2019) before parsing it - split(" ").map { - if (it.contains(regexDate)) { - it.replace(regexNotNumber, "") - } else { - it - } - } - .let { simpleDateFormat.tryParse(it.joinToString(" ")) } + split(" ") + .map { + if (it.contains(regexDate)) { + it.replace(regexNotNumber, "") + } else { + it + } + }.let { simpleDateFormat.tryParse(it.joinToString(" ")) } } else { simpleDateFormat.tryParse(this) } @@ -62,30 +64,36 @@ private fun parseDate(date: String?): Long { return when { // Handle 'yesterday' and 'today', using midnight WordSet("yesterday", "يوم واحد").startsWith(date) -> { - Calendar.getInstance().apply { - add(Calendar.DAY_OF_MONTH, -1) // yesterday - set(Calendar.HOUR_OF_DAY, 0) - set(Calendar.MINUTE, 0) - set(Calendar.SECOND, 0) - set(Calendar.MILLISECOND, 0) - }.timeInMillis + Calendar + .getInstance() + .apply { + add(Calendar.DAY_OF_MONTH, -1) // yesterday + set(Calendar.HOUR_OF_DAY, 0) + set(Calendar.MINUTE, 0) + set(Calendar.SECOND, 0) + set(Calendar.MILLISECOND, 0) + }.timeInMillis } WordSet("today", "just now").startsWith(date) -> { - Calendar.getInstance().apply { - set(Calendar.HOUR_OF_DAY, 0) - set(Calendar.MINUTE, 0) - set(Calendar.SECOND, 0) - set(Calendar.MILLISECOND, 0) - }.timeInMillis + Calendar + .getInstance() + .apply { + set(Calendar.HOUR_OF_DAY, 0) + set(Calendar.MINUTE, 0) + set(Calendar.SECOND, 0) + set(Calendar.MILLISECOND, 0) + }.timeInMillis } WordSet("يومين").startsWith(date) -> { - Calendar.getInstance().apply { - add(Calendar.DAY_OF_MONTH, -2) // day before yesterday - set(Calendar.HOUR_OF_DAY, 0) - set(Calendar.MINUTE, 0) - set(Calendar.SECOND, 0) - set(Calendar.MILLISECOND, 0) - }.timeInMillis + Calendar + .getInstance() + .apply { + add(Calendar.DAY_OF_MONTH, -2) // day before yesterday + set(Calendar.HOUR_OF_DAY, 0) + set(Calendar.MINUTE, 0) + set(Calendar.SECOND, 0) + set(Calendar.MILLISECOND, 0) + }.timeInMillis } WordSet("ago", "atrás", "önce", "قبل").endsWith(date) -> { parseRelativeDate(date) @@ -99,12 +107,15 @@ private fun parseDate(date: String?): Long { // Parses dates in this form: 21 hours ago OR "2 days ago (Updated 19 hours ago)" private fun parseRelativeDate(date: String): Long { - val number = regexRelativeDateTime.find(date)?.value?.toIntOrNull() - ?: date.split(" ").firstOrNull() - ?.replace("one", "1") - ?.replace("a", "1") - ?.toIntOrNull() - ?: return 0L + val number = + regexRelativeDateTime.find(date)?.value?.toIntOrNull() + ?: date + .split(" ") + .firstOrNull() + ?.replace("one", "1") + ?.replace("a", "1") + ?.toIntOrNull() + ?: return 0L val now = Calendar.getInstance() // Sort by order @@ -127,17 +138,20 @@ private fun parseRelativeDate(date: String): Long { } } -private fun SimpleDateFormat.tryParse(string: String): Long { - return try { +private fun SimpleDateFormat.tryParse(string: String): Long = + try { parse(string)?.time ?: 0L } catch (_: ParseException) { 0L } -} -class WordSet(private vararg val words: String) { +class WordSet( + private vararg val words: String, +) { fun anyWordIn(dateString: String): Boolean = words.any { dateString.contains(it, ignoreCase = true) } + fun startsWith(dateString: String): Boolean = words.any { dateString.startsWith(it, ignoreCase = true) } + fun endsWith(dateString: String): Boolean = words.any { dateString.endsWith(it, ignoreCase = true) } } diff --git a/lib-multisrc/gattsu/src/eu/kanade/tachiyomi/multisrc/gattsu/Gattsu.kt b/lib-multisrc/gattsu/src/eu/kanade/tachiyomi/multisrc/gattsu/Gattsu.kt index c0af74899b..b011bb7dff 100644 --- a/lib-multisrc/gattsu/src/eu/kanade/tachiyomi/multisrc/gattsu/Gattsu.kt +++ b/lib-multisrc/gattsu/src/eu/kanade/tachiyomi/multisrc/gattsu/Gattsu.kt @@ -23,15 +23,16 @@ abstract class Gattsu( override val baseUrl: String, override val lang: String, ) : ParsedHttpSource() { - override val supportsLatest = true override val client: OkHttpClient = network.cloudflareClient - override fun headersBuilder() = Headers.Builder() - .add("Accept", ACCEPT) - .add("Accept-Language", ACCEPT_LANGUAGE) - .add("Referer", baseUrl) + override fun headersBuilder() = + Headers + .Builder() + .add("Accept", ACCEPT) + .add("Accept-Language", ACCEPT_LANGUAGE) + .add("Referer", baseUrl) // Website does not have a popular, so use latest instead. override fun popularMangaRequest(page: Int): Request = latestUpdatesRequest(page) @@ -49,19 +50,27 @@ abstract class Gattsu( override fun latestUpdatesSelector() = "div.meio div.lista ul li a[href^=$baseUrl]" - override fun latestUpdatesFromElement(element: Element): SManga = SManga.create().apply { - title = element.select("span.thumb-titulo").first()!!.text() - thumbnail_url = element.select("span.thumb-imagem img.wp-post-image").first()!!.attr("src") - setUrlWithoutDomain(element.attr("href")) - } + override fun latestUpdatesFromElement(element: Element): SManga = + SManga.create().apply { + title = element.select("span.thumb-titulo").first()!!.text() + thumbnail_url = element.select("span.thumb-imagem img.wp-post-image").first()!!.attr("src") + setUrlWithoutDomain(element.attr("href")) + } override fun latestUpdatesNextPageSelector(): String = "ul.paginacao li.next > a" - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val searchUrl = "$baseUrl/page/$page/".toHttpUrl().newBuilder() - .addQueryParameter("s", query) - .addQueryParameter("post_type", "post") - .toString() + override fun searchMangaRequest( + page: Int, + query: String, + filters: FilterList, + ): Request { + val searchUrl = + "$baseUrl/page/$page/" + .toHttpUrl() + .newBuilder() + .addQueryParameter("s", query) + .addQueryParameter("post_type", "post") + .toString() return GET(searchUrl, headers) } @@ -72,22 +81,29 @@ abstract class Gattsu( override fun searchMangaNextPageSelector(): String = latestUpdatesNextPageSelector() - override fun mangaDetailsParse(document: Document): SManga = SManga.create().apply { - val postBox = document.select("div.meio div.post-box").first()!! - - title = postBox.select("h1.post-titulo").first()!!.text() - author = postBox.select("ul.post-itens li:contains(Artista) a").firstOrNull()?.text() - genre = postBox.select("ul.post-itens li:contains(Tags) a") - .joinToString(", ") { it.text() } - description = postBox.select("div.post-texto p") - .joinToString("\n\n") { it.text() } - .replace("Sinopse :", "") - .trim() - status = SManga.COMPLETED - thumbnail_url = postBox.select("div.post-capa > img.wp-post-image") - .attr("src") - .withoutSize() - } + override fun mangaDetailsParse(document: Document): SManga = + SManga.create().apply { + val postBox = document.select("div.meio div.post-box").first()!! + + title = postBox.select("h1.post-titulo").first()!!.text() + author = postBox.select("ul.post-itens li:contains(Artista) a").firstOrNull()?.text() + genre = + postBox + .select("ul.post-itens li:contains(Tags) a") + .joinToString(", ") { it.text() } + description = + postBox + .select("div.post-texto p") + .joinToString("\n\n") { it.text() } + .replace("Sinopse :", "") + .trim() + status = SManga.COMPLETED + thumbnail_url = + postBox + .select("div.post-capa > img.wp-post-image") + .attr("src") + .withoutSize() + } override fun chapterListParse(response: Response): List { val document = response.asJsoup() @@ -96,40 +112,47 @@ abstract class Gattsu( return emptyList() } - return document.select(chapterListSelector()) + return document + .select(chapterListSelector()) .map { chapterFromElement(it) } } override fun chapterListSelector() = "div.meio div.post-box:first-of-type" - override fun chapterFromElement(element: Element): SChapter = SChapter.create().apply { - name = "Capítulo único" - scanlator = element.select("ul.post-itens li:contains(Tradutor) a").firstOrNull()?.text() - date_upload = element.ownerDocument()!!.select("meta[property=article:published_time]").firstOrNull() - ?.attr("content") - .orEmpty() - .toDate() - setUrlWithoutDomain(element.ownerDocument()!!.location()) - } + override fun chapterFromElement(element: Element): SChapter = + SChapter.create().apply { + name = "Capítulo único" + scanlator = element.select("ul.post-itens li:contains(Tradutor) a").firstOrNull()?.text() + date_upload = + element + .ownerDocument()!! + .select("meta[property=article:published_time]") + .firstOrNull() + ?.attr("content") + .orEmpty() + .toDate() + setUrlWithoutDomain(element.ownerDocument()!!.location()) + } protected open fun pageListSelector(): String = "div.meio div.post-box ul.post-fotos li a > img, " + "div.meio div.post-box.listaImagens div.galeriaHtml img" - override fun pageListParse(document: Document): List { - return document.select(pageListSelector()) + override fun pageListParse(document: Document): List = + document + .select(pageListSelector()) .mapIndexed { i, el -> Page(i, document.location(), el.imgAttr().withoutSize()) } - } override fun imageUrlParse(document: Document) = "" override fun imageRequest(page: Page): Request { - val imageHeaders = headersBuilder() - .add("Accept", ACCEPT_IMAGE) - .add("Referer", page.url) - .build() + val imageHeaders = + headersBuilder() + .add("Accept", ACCEPT_IMAGE) + .add("Referer", page.url) + .build() return GET(page.imageUrl!!, imageHeaders) } @@ -141,19 +164,19 @@ abstract class Gattsu( attr("abs:src") } - protected fun String.toDate(): Long { - return try { + protected fun String.toDate(): Long = + try { DATE_FORMATTER.parse(this.substringBefore("T"))?.time ?: 0L } catch (e: ParseException) { 0L } - } protected fun String.withoutSize(): String = this.replace(THUMB_SIZE_REGEX, ".") companion object { - private const val ACCEPT = "text/html,application/xhtml+xml,application/xml;q=0.9," + - "image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9" + private const val ACCEPT = + "text/html,application/xhtml+xml,application/xml;q=0.9," + + "image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9" private const val ACCEPT_IMAGE = "image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8" private const val ACCEPT_LANGUAGE = "pt-BR,pt;q=0.9,en-US;q=0.8,en;q=0.7,es;q=0.6,gl;q=0.5" diff --git a/lib-multisrc/gigaviewer/src/eu/kanade/tachiyomi/multisrc/gigaviewer/GigaViewer.kt b/lib-multisrc/gigaviewer/src/eu/kanade/tachiyomi/multisrc/gigaviewer/GigaViewer.kt index 2ec54da003..01e3244ff8 100644 --- a/lib-multisrc/gigaviewer/src/eu/kanade/tachiyomi/multisrc/gigaviewer/GigaViewer.kt +++ b/lib-multisrc/gigaviewer/src/eu/kanade/tachiyomi/multisrc/gigaviewer/GigaViewer.kt @@ -45,20 +45,22 @@ abstract class GigaViewer( override val lang: String, private val cdnUrl: String = "", ) : ParsedHttpSource() { - override val supportsLatest = true protected val dayOfWeek: String by lazy { - Calendar.getInstance() + Calendar + .getInstance() .getDisplayName(Calendar.DAY_OF_WEEK, Calendar.LONG, Locale.US)!! .lowercase(Locale.US) } protected open val publisher: String = "" - override fun headersBuilder(): Headers.Builder = Headers.Builder() - .add("Origin", baseUrl) - .add("Referer", baseUrl) + override fun headersBuilder(): Headers.Builder = + Headers + .Builder() + .add("Origin", baseUrl) + .add("Referer", baseUrl) private val json: Json by injectLazy() @@ -66,12 +68,15 @@ abstract class GigaViewer( override fun popularMangaSelector(): String = "ul.series-list li a" - override fun popularMangaFromElement(element: Element): SManga = SManga.create().apply { - title = element.selectFirst("h2.series-list-title")!!.text() - thumbnail_url = element.selectFirst("div.series-list-thumb img")!! - .attr("data-src") - setUrlWithoutDomain(element.attr("href")) - } + override fun popularMangaFromElement(element: Element): SManga = + SManga.create().apply { + title = element.selectFirst("h2.series-list-title")!!.text() + thumbnail_url = + element + .selectFirst("div.series-list-thumb img")!! + .attr("data-src") + setUrlWithoutDomain(element.attr("href")) + } override fun popularMangaNextPageSelector(): String? = null @@ -84,16 +89,27 @@ abstract class GigaViewer( override fun latestUpdatesNextPageSelector(): String? = null // The search returns 404 when there's no results. - override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable { - return client.newCall(searchMangaRequest(page, query, filters)) + override fun fetchSearchManga( + page: Int, + query: String, + filters: FilterList, + ): Observable = + client + .newCall(searchMangaRequest(page, query, filters)) .asObservableIgnoreCode(404) .map(::searchMangaParse) - } - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { + override fun searchMangaRequest( + page: Int, + query: String, + filters: FilterList, + ): Request { if (query.isNotEmpty()) { - val url = "$baseUrl/search".toHttpUrl().newBuilder() - .addQueryParameter("q", query) + val url = + "$baseUrl/search" + .toHttpUrl() + .newBuilder() + .addQueryParameter("q", query) return GET(url.build(), headers) } @@ -104,7 +120,10 @@ abstract class GigaViewer( } override fun searchMangaParse(response: Response): MangasPage { - if (response.request.url.toString().contains("search")) { + if (response.request.url + .toString() + .contains("search") + ) { return super.searchMangaParse(response) } @@ -113,46 +132,55 @@ abstract class GigaViewer( override fun searchMangaSelector() = "ul.search-series-list li, ul.series-list li" - override fun searchMangaFromElement(element: Element): SManga = SManga.create().apply { - title = element.selectFirst("div.title-box p.series-title")!!.text() - thumbnail_url = element.selectFirst("div.thmb-container a img")!!.attr("src") - setUrlWithoutDomain(element.selectFirst("div.thmb-container a")!!.attr("href")) - } + override fun searchMangaFromElement(element: Element): SManga = + SManga.create().apply { + title = element.selectFirst("div.title-box p.series-title")!!.text() + thumbnail_url = element.selectFirst("div.thmb-container a img")!!.attr("src") + setUrlWithoutDomain(element.selectFirst("div.thmb-container a")!!.attr("href")) + } override fun searchMangaNextPageSelector(): String? = null protected open fun mangaDetailsInfoSelector(): String = "section.series-information div.series-header" - override fun mangaDetailsParse(document: Document): SManga = SManga.create().apply { - val infoElement = document.selectFirst(mangaDetailsInfoSelector())!! - - title = infoElement.selectFirst("h1.series-header-title")!!.text() - author = infoElement.selectFirst("h2.series-header-author")!!.text() - artist = author - description = infoElement.selectFirst("p.series-header-description")!!.text() - thumbnail_url = infoElement.selectFirst("div.series-header-image-wrapper img")!! - .attr("data-src") - } + override fun mangaDetailsParse(document: Document): SManga = + SManga.create().apply { + val infoElement = document.selectFirst(mangaDetailsInfoSelector())!! + + title = infoElement.selectFirst("h1.series-header-title")!!.text() + author = infoElement.selectFirst("h2.series-header-author")!!.text() + artist = author + description = infoElement.selectFirst("p.series-header-description")!!.text() + thumbnail_url = + infoElement + .selectFirst("div.series-header-image-wrapper img")!! + .attr("data-src") + } override fun chapterListParse(response: Response): List { val document = response.asJsoup() val aggregateId = document.selectFirst("script.js-valve")!!.attr("data-giga_series") - val newHeaders = headers.newBuilder() - .set("Referer", response.request.url.toString()) - .build() - - var readMoreEndpoint = baseUrl.toHttpUrl().newBuilder() - .addPathSegment("api") - .addPathSegment("viewer") - .addPathSegment("readable_products") - .addQueryParameter("aggregate_id", aggregateId) - .addQueryParameter("number_since", Int.MAX_VALUE.toString()) - .addQueryParameter("number_until", "0") - .addQueryParameter("read_more_num", "150") - .addQueryParameter("type", "episode") - .build() - .toString() + val newHeaders = + headers + .newBuilder() + .set("Referer", response.request.url.toString()) + .build() + + var readMoreEndpoint = + baseUrl + .toHttpUrl() + .newBuilder() + .addPathSegment("api") + .addPathSegment("viewer") + .addPathSegment("readable_products") + .addQueryParameter("aggregate_id", aggregateId) + .addQueryParameter("number_since", Int.MAX_VALUE.toString()) + .addQueryParameter("number_until", "0") + .addQueryParameter("read_more_num", "150") + .addQueryParameter("type", "episode") + .build() + .toString() val chapters = mutableListOf() @@ -162,10 +190,11 @@ abstract class GigaViewer( while (result.code != 404) { val jsonResult = json.parseToJsonElement(result.body.string()).jsonObject readMoreEndpoint = jsonResult["nextUrl"]!!.jsonPrimitive.content - val tempDocument = Jsoup.parse( - jsonResult["html"]!!.jsonPrimitive.content, - response.request.url.toString(), - ) + val tempDocument = + Jsoup.parse( + jsonResult["html"]!!.jsonPrimitive.content, + response.request.url.toString(), + ) tempDocument .select("ul.series-episode-list " + chapterListSelector()) @@ -195,32 +224,40 @@ abstract class GigaViewer( } else if (chapterListMode == CHAPTER_LIST_LOCKED && element.hasClass("private")) { name = LOCK + name } - date_upload = info.selectFirst("span.series-episode-list-date") - ?.text().orEmpty() - .toDate() + date_upload = + info + .selectFirst("span.series-episode-list-date") + ?.text() + .orEmpty() + .toDate() scanlator = publisher setUrlWithoutDomain(if (info.tagName() == "a") info.attr("href") else mangaUrl) } } override fun pageListParse(document: Document): List { - val episode = document.selectFirst("script#episode-json")!! - .attr("data-value") - .let { - try { - json.decodeFromString(it) - } catch (e: SerializationException) { - throw Exception("このチャプターは非公開です\nChapter is not available!") + val episode = + document + .selectFirst("script#episode-json")!! + .attr("data-value") + .let { + try { + json.decodeFromString(it) + } catch (e: SerializationException) { + throw Exception("このチャプターは非公開です\nChapter is not available!") + } } - } return episode.readableProduct.pageStructure.pages .filter { it.type == "main" } .mapIndexed { i, page -> - val imageUrl = page.src.toHttpUrl().newBuilder() - .addQueryParameter("width", page.width.toString()) - .addQueryParameter("height", page.height.toString()) - .toString() + val imageUrl = + page.src + .toHttpUrl() + .newBuilder() + .addQueryParameter("width", page.width.toString()) + .addQueryParameter("height", page.height.toString()) + .toString() Page(i, document.location(), imageUrl) } } @@ -228,21 +265,27 @@ abstract class GigaViewer( override fun imageUrlParse(document: Document) = "" override fun imageRequest(page: Page): Request { - val newHeaders = headersBuilder() - .set("Referer", page.url) - .build() + val newHeaders = + headersBuilder() + .set("Referer", page.url) + .build() return GET(page.imageUrl!!, newHeaders) } - protected data class Collection(val name: String, val path: String) { + protected data class Collection( + val name: String, + val path: String, + ) { override fun toString(): String = name } - private class CollectionFilter(val collections: List) : Filter.Select( - "コレクション", - collections.toTypedArray(), - ) { + private class CollectionFilter( + val collections: List, + ) : Filter.Select( + "コレクション", + collections.toTypedArray(), + ) { val selected: Collection get() = collections[state] } @@ -261,10 +304,12 @@ abstract class GigaViewer( val width = request.url.queryParameter("width")!!.toInt() val height = request.url.queryParameter("height")!!.toInt() - val newUrl = request.url.newBuilder() - .removeAllQueryParameters("width") - .removeAllQueryParameters("height") - .build() + val newUrl = + request.url + .newBuilder() + .removeAllQueryParameters("width") + .removeAllQueryParameters("height") + .build() request = request.newBuilder().url(newUrl).build() val response = chain.proceed(request) @@ -276,7 +321,11 @@ abstract class GigaViewer( return response.newBuilder().body(body).build() } - protected open fun decodeImage(image: InputStream, width: Int, height: Int): ByteArray { + protected open fun decodeImage( + image: InputStream, + width: Int, + height: Int, + ): ByteArray { val input = BitmapFactory.decodeStream(image) val cWidth = (floor(width.toDouble() / (DIVIDE_NUM * MULTIPLE)) * MULTIPLE).toInt() val cHeight = (floor(height.toDouble() / (DIVIDE_NUM * MULTIPLE)) * MULTIPLE).toInt() @@ -305,19 +354,17 @@ abstract class GigaViewer( return output.toByteArray() } - private fun Call.asObservableIgnoreCode(code: Int): Observable { - return asObservable().doOnNext { response -> + private fun Call.asObservableIgnoreCode(code: Int): Observable = + asObservable().doOnNext { response -> if (!response.isSuccessful && response.code != code) { response.close() throw Exception("HTTP error ${response.code}") } } - } - private fun String.toDate(): Long { - return runCatching { DATE_PARSER.parse(this)?.time } + private fun String.toDate(): Long = + runCatching { DATE_PARSER.parse(this)?.time } .getOrNull() ?: 0L - } companion object { private val DATE_PARSER by lazy { SimpleDateFormat("yyyy/MM/dd", Locale.ENGLISH) } diff --git a/lib-multisrc/gmanga/src/eu/kanade/tachiyomi/multisrc/gmanga/CryptoUtils.kt b/lib-multisrc/gmanga/src/eu/kanade/tachiyomi/multisrc/gmanga/CryptoUtils.kt index 70642e109c..807f6e38dd 100644 --- a/lib-multisrc/gmanga/src/eu/kanade/tachiyomi/multisrc/gmanga/CryptoUtils.kt +++ b/lib-multisrc/gmanga/src/eu/kanade/tachiyomi/multisrc/gmanga/CryptoUtils.kt @@ -18,23 +18,26 @@ private fun String.hexStringToByteArray(): ByteArray { val data = ByteArray(len / 2) var i = 0 while (i < len) { - data[i / 2] = ( - (Character.digit(this[i], 16) shl 4) + - Character.digit(this[i + 1], 16) + data[i / 2] = + ( + (Character.digit(this[i], 16) shl 4) + + Character.digit(this[i + 1], 16) ).toByte() i += 2 } return data } -private fun String.sha256(): String { - return MessageDigest +private fun String.sha256(): String = + MessageDigest .getInstance("SHA-256") .digest(toByteArray()) .fold("") { str, it -> str + "%02x".format(it) } -} -private fun String.aesDecrypt(secretKey: ByteArray, ivString: String): String { +private fun String.aesDecrypt( + secretKey: ByteArray, + ivString: String, +): String { val c = Cipher.getInstance("AES/CBC/PKCS5Padding") val sk = SecretKeySpec(secretKey, "AES") val iv = IvParameterSpec(Base64.decode(ivString.toByteArray(Charsets.UTF_8), Base64.DEFAULT)) diff --git a/lib-multisrc/gmanga/src/eu/kanade/tachiyomi/multisrc/gmanga/Dto.kt b/lib-multisrc/gmanga/src/eu/kanade/tachiyomi/multisrc/gmanga/Dto.kt index 0b770b1bdd..faa110e367 100644 --- a/lib-multisrc/gmanga/src/eu/kanade/tachiyomi/multisrc/gmanga/Dto.kt +++ b/lib-multisrc/gmanga/src/eu/kanade/tachiyomi/multisrc/gmanga/Dto.kt @@ -5,10 +5,14 @@ import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable -class EncryptedResponse(val data: String) +class EncryptedResponse( + val data: String, +) @Serializable -class MangaDataAction(val mangaDataAction: T) +class MangaDataAction( + val mangaDataAction: T, +) @Serializable class LatestChaptersDto( @@ -32,11 +36,12 @@ class BrowseManga( private val cover: String? = null, @SerialName("is_novel") val isNovel: Boolean, ) { - fun toSManga(createThumbnail: (String, String) -> String) = SManga.create().apply { - url = "/mangas/$id" - title = this@BrowseManga.title - thumbnail_url = cover?.let { createThumbnail(id.toString(), cover) } - } + fun toSManga(createThumbnail: (String, String) -> String) = + SManga.create().apply { + url = "/mangas/$id" + title = this@BrowseManga.title + thumbnail_url = cover?.let { createThumbnail(id.toString(), cover) } + } } @Serializable @@ -73,53 +78,61 @@ class Manga( @SerialName("japanese") private val jpTitle: String? = null, @SerialName("english") private val enTitle: String? = null, ) { - fun toSManga(createThumbnail: (String, String) -> String) = SManga.create().apply { - title = this@Manga.title - thumbnail_url = cover?.let { createThumbnail(id.toString(), cover) } - artist = artists.joinToString { it.name } - author = authors.joinToString { it.name } - status = when (this@Manga.status) { - 2 -> SManga.ONGOING - 3 -> SManga.COMPLETED - else -> SManga.UNKNOWN + fun toSManga(createThumbnail: (String, String) -> String) = + SManga.create().apply { + title = this@Manga.title + thumbnail_url = cover?.let { createThumbnail(id.toString(), cover) } + artist = artists.joinToString { it.name } + author = authors.joinToString { it.name } + status = + when (this@Manga.status) { + 2 -> SManga.ONGOING + 3 -> SManga.COMPLETED + else -> SManga.UNKNOWN + } + genre = + buildList { + type.title?.let { add(it) } + add(type.name) + categories.forEach { add(it.name) } + }.joinToString() + description = + buildString { + summary + .orEmpty() + .ifEmpty { "لم يتم اضافة قصة بعد" } + .also { append(it) } + + when (tlStatus) { + 0 -> "منتهية" + 1 -> "مستمرة" + 2 -> "متوقفة" + else -> "مجهول" + }.also { + append("\n\n") + append("حالة الترجمة") + append(":\n• ") + append(it) + } + + val titles = + listOfNotNull(synonyms, arTitle, jpTitle, enTitle) + .filterNot(String::isEmpty) + + if (titles.isNotEmpty()) { + append("\n\n") + append("مسميّات أخرى") + append(":\n• ") + append(titles.joinToString("\n• ")) + } + } } - genre = buildList { - type.title?.let { add(it) } - add(type.name) - categories.forEach { add(it.name) } - }.joinToString() - description = buildString { - summary.orEmpty() - .ifEmpty { "لم يتم اضافة قصة بعد" } - .also { append(it) } - - when (tlStatus) { - 0 -> "منتهية" - 1 -> "مستمرة" - 2 -> "متوقفة" - else -> "مجهول" - }.also { - append("\n\n") - append("حالة الترجمة") - append(":\n• ") - append(it) - } - - val titles = listOfNotNull(synonyms, arTitle, jpTitle, enTitle) - .filterNot(String::isEmpty) - - if (titles.isNotEmpty()) { - append("\n\n") - append("مسميّات أخرى") - append(":\n• ") - append(titles.joinToString("\n• ")) - } - } - } } @Serializable -class NameDto(val name: String) +class NameDto( + val name: String, +) @Serializable class TypeDto( diff --git a/lib-multisrc/gmanga/src/eu/kanade/tachiyomi/multisrc/gmanga/Filters.kt b/lib-multisrc/gmanga/src/eu/kanade/tachiyomi/multisrc/gmanga/Filters.kt index 0dbc06204f..e48e25b1d2 100644 --- a/lib-multisrc/gmanga/src/eu/kanade/tachiyomi/multisrc/gmanga/Filters.kt +++ b/lib-multisrc/gmanga/src/eu/kanade/tachiyomi/multisrc/gmanga/Filters.kt @@ -19,76 +19,95 @@ class TagFilter( state: Int = STATE_IGNORE, ) : Filter.TriState(name, state) -abstract class ValidatingTextFilter(name: String) : Filter.Text(name) { +abstract class ValidatingTextFilter( + name: String, +) : Filter.Text(name) { abstract fun isValid(): Boolean } -private val DATE_FITLER_FORMAT = SimpleDateFormat("yyyy/MM/dd", Locale.ENGLISH).apply { - isLenient = false -} +private val DATE_FITLER_FORMAT = + SimpleDateFormat("yyyy/MM/dd", Locale.ENGLISH).apply { + isLenient = false + } -private fun SimpleDateFormat.isValid(date: String): Boolean { - return try { +private fun SimpleDateFormat.isValid(date: String): Boolean = + try { parse(date) true } catch (e: ParseException) { false } -} -class DateFilter(val id: String, name: String) : ValidatingTextFilter("(yyyy/MM/dd) $name)") { +class DateFilter( + val id: String, + name: String, +) : ValidatingTextFilter("(yyyy/MM/dd) $name)") { override fun isValid(): Boolean = DATE_FITLER_FORMAT.isValid(state) } -class IntFilter(val id: String, name: String) : ValidatingTextFilter(name) { +class IntFilter( + val id: String, + name: String, +) : ValidatingTextFilter(name) { override fun isValid(): Boolean = state.toIntOrNull() != null } -class MangaTypeFilter(types: List) : Filter.Group( - "الأصل", - types.map { it.toTagFilter() }, -) +class MangaTypeFilter( + types: List, +) : Filter.Group( + "الأصل", + types.map { it.toTagFilter() }, + ) -class OneShotFilter : Filter.Group( - "ونشوت؟", - listOf( - TagFilter("oneshot", "نعم", TriState.STATE_EXCLUDE), - ), -) +class OneShotFilter : + Filter.Group( + "ونشوت؟", + listOf( + TagFilter("oneshot", "نعم", TriState.STATE_EXCLUDE), + ), + ) -class StoryStatusFilter(status: List) : Filter.Group( - "حالة القصة", - status.map { it.toTagFilter() }, -) +class StoryStatusFilter( + status: List, +) : Filter.Group( + "حالة القصة", + status.map { it.toTagFilter() }, + ) -class TranslationStatusFilter(tlStatus: List) : Filter.Group( - "حالة الترجمة", - tlStatus.map { it.toTagFilter() }, -) +class TranslationStatusFilter( + tlStatus: List, +) : Filter.Group( + "حالة الترجمة", + tlStatus.map { it.toTagFilter() }, + ) -class ChapterCountFilter : Filter.Group( - "عدد الفصول", - listOf( - IntFilter("min", "على الأقل"), - IntFilter("max", "على الأكثر"), - ), -) { +class ChapterCountFilter : + Filter.Group( + "عدد الفصول", + listOf( + IntFilter("min", "على الأقل"), + IntFilter("max", "على الأكثر"), + ), + ) { val min get() = state.first { it.id == "min" } val max get() = state.first { it.id == "max" } } -class DateRangeFilter : Filter.Group( - "تاريخ النشر", - listOf( - DateFilter("start", "تاريخ النشر"), - DateFilter("end", "تاريخ الإنتهاء"), - ), -) { +class DateRangeFilter : + Filter.Group( + "تاريخ النشر", + listOf( + DateFilter("start", "تاريخ النشر"), + DateFilter("end", "تاريخ الإنتهاء"), + ), + ) { val start get() = state.first { it.id == "start" } val end get() = state.first { it.id == "end" } } -class CategoryFilter(categories: List) : Filter.Group( - "التصنيفات", - categories.map { it.toTagFilter() }, -) +class CategoryFilter( + categories: List, +) : Filter.Group( + "التصنيفات", + categories.map { it.toTagFilter() }, + ) diff --git a/lib-multisrc/gmanga/src/eu/kanade/tachiyomi/multisrc/gmanga/Gmanga.kt b/lib-multisrc/gmanga/src/eu/kanade/tachiyomi/multisrc/gmanga/Gmanga.kt index 13064d121f..3dc2fbf850 100644 --- a/lib-multisrc/gmanga/src/eu/kanade/tachiyomi/multisrc/gmanga/Gmanga.kt +++ b/lib-multisrc/gmanga/src/eu/kanade/tachiyomi/multisrc/gmanga/Gmanga.kt @@ -30,29 +30,34 @@ abstract class Gmanga( final override val lang: String, protected open val cdnUrl: String = baseUrl, ) : HttpSource() { - override val supportsLatest = true protected val json: Json by injectLazy() override val client = network.cloudflareClient - override fun headersBuilder() = super.headersBuilder() - .set("Referer", "$baseUrl/") + override fun headersBuilder() = + super + .headersBuilder() + .set("Referer", "$baseUrl/") override fun popularMangaRequest(page: Int) = searchMangaRequest(page, "", getFilterList()) + override fun popularMangaParse(response: Response) = searchMangaParse(response) - override fun latestUpdatesRequest(page: Int): Request { - return GET("$baseUrl/api/releases?page=$page", headers) - } + override fun latestUpdatesRequest(page: Int): Request = GET("$baseUrl/api/releases?page=$page", headers) override fun latestUpdatesParse(response: Response): MangasPage { - val releases = response.parseAs().releases - .filterNot { it.manga.isNovel } + val releases = + response + .parseAs() + .releases + .filterNot { it.manga.isNovel } - val entries = releases.map { it.manga.toSManga(::createThumbnail) } - .distinctBy { it.url } + val entries = + releases + .map { it.manga.toSManga(::createThumbnail) } + .distinctBy { it.url } return MangasPage( entries, @@ -60,7 +65,11 @@ abstract class Gmanga( ) } - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { + override fun searchMangaRequest( + page: Int, + query: String, + filters: FilterList, + ): Request { val filterList = if (filters.isEmpty()) getFilterList() else filters val mangaTypeFilter = filterList.findInstance()!! @@ -71,67 +80,80 @@ abstract class Gmanga( val dateRangeFilter = filterList.findInstance()!! val categoryFilter = filterList.findInstance() ?: CategoryFilter(emptyList()) - val body = SearchPayload( - oneshot = OneShot( - value = oneShotFilter.state.first().run { - when { - isIncluded() -> true - else -> false - } - }, - ), - title = query, - page = page, - mangaTypes = IncludeExclude( - include = mangaTypeFilter.state.filter { it.isIncluded() }.map { it.id }, - exclude = mangaTypeFilter.state.filter { it.isExcluded() }.map { it.id }, - ), - storyStatus = IncludeExclude( - include = storyStatusFilter.state.filter { it.isIncluded() }.map { it.id }, - exclude = storyStatusFilter.state.filter { it.isExcluded() }.map { it.id }, - ), - tlStatus = IncludeExclude( - include = translationStatusFilter.state.filter { it.isIncluded() }.map { it.id }, - exclude = translationStatusFilter.state.filter { it.isExcluded() }.map { it.id }, - ), - categories = IncludeExclude( - // always include null, maybe to avoid shifting index in the backend - include = listOf(null) + categoryFilter.state.filter { it.isIncluded() }.map { it.id }, - exclude = categoryFilter.state.filter { it.isExcluded() }.map { it.id }, - ), - chapters = MinMax( - min = chapterCountFilter.min.run { - when { - state == "" -> "" - isValid() -> state - else -> throw Exception("الحد الأدنى لعدد الفصول غير صالح") - } - }, - max = chapterCountFilter.max.run { - when { - state == "" -> "" - isValid() -> state - else -> throw Exception("الحد الأقصى لعدد الفصول غير صالح") - } - }, - ), - dates = StartEnd( - start = dateRangeFilter.start.run { - when { - state == "" -> "" - isValid() -> state - else -> throw Exception("تاريخ بداية غير صالح") - } - }, - end = dateRangeFilter.end.run { - when { - state == "" -> "" - isValid() -> state - else -> throw Exception("تاريخ نهاية غير صالح") - } - }, - ), - ).let(json::encodeToString).toRequestBody(MEDIA_TYPE) + val body = + SearchPayload( + oneshot = + OneShot( + value = + oneShotFilter.state.first().run { + when { + isIncluded() -> true + else -> false + } + }, + ), + title = query, + page = page, + mangaTypes = + IncludeExclude( + include = mangaTypeFilter.state.filter { it.isIncluded() }.map { it.id }, + exclude = mangaTypeFilter.state.filter { it.isExcluded() }.map { it.id }, + ), + storyStatus = + IncludeExclude( + include = storyStatusFilter.state.filter { it.isIncluded() }.map { it.id }, + exclude = storyStatusFilter.state.filter { it.isExcluded() }.map { it.id }, + ), + tlStatus = + IncludeExclude( + include = translationStatusFilter.state.filter { it.isIncluded() }.map { it.id }, + exclude = translationStatusFilter.state.filter { it.isExcluded() }.map { it.id }, + ), + categories = + IncludeExclude( + // always include null, maybe to avoid shifting index in the backend + include = listOf(null) + categoryFilter.state.filter { it.isIncluded() }.map { it.id }, + exclude = categoryFilter.state.filter { it.isExcluded() }.map { it.id }, + ), + chapters = + MinMax( + min = + chapterCountFilter.min.run { + when { + state == "" -> "" + isValid() -> state + else -> throw Exception("الحد الأدنى لعدد الفصول غير صالح") + } + }, + max = + chapterCountFilter.max.run { + when { + state == "" -> "" + isValid() -> state + else -> throw Exception("الحد الأقصى لعدد الفصول غير صالح") + } + }, + ), + dates = + StartEnd( + start = + dateRangeFilter.start.run { + when { + state == "" -> "" + isValid() -> state + else -> throw Exception("تاريخ بداية غير صالح") + } + }, + end = + dateRangeFilter.end.run { + when { + state == "" -> "" + isValid() -> state + else -> throw Exception("تاريخ نهاية غير صالح") + } + }, + ), + ).let(json::encodeToString).toRequestBody(MEDIA_TYPE) return POST("$baseUrl/api/mangas/search", headers, body) } @@ -141,7 +163,9 @@ abstract class Gmanga( private var filterAttempts = 0 private enum class FilterState { - Fetching, Fetched, Unfetched + Fetching, + Fetched, + Unfetched, } private suspend fun fetchFilters() { @@ -150,15 +174,17 @@ abstract class Gmanga( filterAttempts++ try { - categories = client.newCall(GET("$baseUrl/mangas/", headers)) - .await() - .asJsoup() - .select(".js-react-on-rails-component").html() - .parseAs() - .run { - categories ?: categoryTypes!!.flatMap { it.categories!! } - } - .map { TagFilterData(it.id.toString(), it.name) } + categories = + client + .newCall(GET("$baseUrl/mangas/", headers)) + .await() + .asJsoup() + .select(".js-react-on-rails-component") + .html() + .parseAs() + .run { + categories ?: categoryTypes!!.flatMap { it.categories!! } + }.map { TagFilterData(it.id.toString(), it.name) } filtersState = FilterState.Fetched } catch (e: Exception) { @@ -168,51 +194,56 @@ abstract class Gmanga( } } - protected open fun getTypesFilter() = listOf( - TagFilterData("1", "يابانية", Filter.TriState.STATE_INCLUDE), - TagFilterData("2", "كورية", Filter.TriState.STATE_INCLUDE), - TagFilterData("3", "صينية", Filter.TriState.STATE_INCLUDE), - TagFilterData("4", "عربية", Filter.TriState.STATE_INCLUDE), - TagFilterData("5", "كوميك", Filter.TriState.STATE_INCLUDE), - TagFilterData("6", "هواة", Filter.TriState.STATE_INCLUDE), - TagFilterData("7", "إندونيسية", Filter.TriState.STATE_INCLUDE), - TagFilterData("8", "روسية", Filter.TriState.STATE_INCLUDE), - ) - - protected open fun getStatusFilter() = listOf( - TagFilterData("2", "مستمرة"), - TagFilterData("3", "منتهية"), - ) - - protected open fun getTranslationFilter() = listOf( - TagFilterData("0", "منتهية"), - TagFilterData("1", "مستمرة"), - TagFilterData("2", "متوقفة"), - TagFilterData("3", "غير مترجمة", Filter.TriState.STATE_EXCLUDE), - ) + protected open fun getTypesFilter() = + listOf( + TagFilterData("1", "يابانية", Filter.TriState.STATE_INCLUDE), + TagFilterData("2", "كورية", Filter.TriState.STATE_INCLUDE), + TagFilterData("3", "صينية", Filter.TriState.STATE_INCLUDE), + TagFilterData("4", "عربية", Filter.TriState.STATE_INCLUDE), + TagFilterData("5", "كوميك", Filter.TriState.STATE_INCLUDE), + TagFilterData("6", "هواة", Filter.TriState.STATE_INCLUDE), + TagFilterData("7", "إندونيسية", Filter.TriState.STATE_INCLUDE), + TagFilterData("8", "روسية", Filter.TriState.STATE_INCLUDE), + ) - override fun getFilterList(): FilterList { - CoroutineScope(Dispatchers.IO).launch { fetchFilters() } + protected open fun getStatusFilter() = + listOf( + TagFilterData("2", "مستمرة"), + TagFilterData("3", "منتهية"), + ) - val filters = mutableListOf>( - MangaTypeFilter(getTypesFilter()), - OneShotFilter(), - StoryStatusFilter(getStatusFilter()), - TranslationStatusFilter(getTranslationFilter()), - ChapterCountFilter(), - DateRangeFilter(), + protected open fun getTranslationFilter() = + listOf( + TagFilterData("0", "منتهية"), + TagFilterData("1", "مستمرة"), + TagFilterData("2", "متوقفة"), + TagFilterData("3", "غير مترجمة", Filter.TriState.STATE_EXCLUDE), ) - filters += if (filtersState == FilterState.Fetched) { - listOf( - CategoryFilter(categories), - ) - } else { - listOf( - Filter.Separator(), - Filter.Header("اضغط على\"إعادة تعيين\"لمحاولة تحميل التصنيفات"), + override fun getFilterList(): FilterList { + CoroutineScope(Dispatchers.IO).launch { fetchFilters() } + + val filters = + mutableListOf>( + MangaTypeFilter(getTypesFilter()), + OneShotFilter(), + StoryStatusFilter(getStatusFilter()), + TranslationStatusFilter(getTranslationFilter()), + ChapterCountFilter(), + DateRangeFilter(), ) - } + + filters += + if (filtersState == FilterState.Fetched) { + listOf( + CategoryFilter(categories), + ) + } else { + listOf( + Filter.Separator(), + Filter.Header("اضغط على\"إعادة تعيين\"لمحاولة تحميل التصنيفات"), + ) + } return FilterList(filters) } @@ -225,18 +256,21 @@ abstract class Gmanga( ) } - override fun mangaDetailsParse(response: Response): SManga { - return response.asJsoup() - .select(".js-react-on-rails-component").html() + override fun mangaDetailsParse(response: Response): SManga = + response + .asJsoup() + .select(".js-react-on-rails-component") + .html() .parseAs>() .mangaDataAction.mangaData .toSManga(::createThumbnail) - } abstract fun chaptersRequest(manga: SManga): Request + abstract fun chaptersParse(response: Response): List final override fun chapterListRequest(manga: SManga) = chaptersRequest(manga) + final override fun chapterListParse(response: Response) = chaptersParse(response).sortChapters() private fun List.sortChapters() = @@ -248,17 +282,21 @@ abstract class Gmanga( ) override fun pageListParse(response: Response): List { - val data = response.asJsoup() - .select(".js-react-on-rails-component").html() - .parseAs() - .readerDataAction.readerData.release + val data = + response + .asJsoup() + .select(".js-react-on-rails-component") + .html() + .parseAs() + .readerDataAction.readerData.release val hasWebP = data.webpPages.isNotEmpty() - val (pages, directory) = when { - hasWebP -> data.webpPages to "hq_webp" - else -> data.pages to "hq" - } + val (pages, directory) = + when { + hasWebP -> data.webpPages to "hq_webp" + else -> data.pages to "hq" + } return pages.sortedWith(pageSort).mapIndexed { index, pageUri -> Page( @@ -271,11 +309,18 @@ abstract class Gmanga( private val pageSort = compareBy({ parseNumber(0, it) ?: Double.MAX_VALUE }, { parseNumber(1, it) }, { parseNumber(2, it) }) - private fun parseNumber(index: Int, string: String): Double? = - Regex("\\d+").findAll(string).map { it.value }.toList().getOrNull(index)?.toDoubleOrNull() + private fun parseNumber( + index: Int, + string: String, + ): Double? = + Regex("\\d+") + .findAll(string) + .map { it.value } + .toList() + .getOrNull(index) + ?.toDoubleOrNull() - protected inline fun Response.decryptAs(): T = - decrypt(parseAs().data).parseAs() + protected inline fun Response.decryptAs(): T = decrypt(parseAs().data).parseAs() protected inline fun Response.parseAs(): T = body.string().parseAs() @@ -283,14 +328,16 @@ abstract class Gmanga( protected inline fun Iterable<*>.findInstance() = find { it is T } as? T - protected open fun createThumbnail(mangaId: String, cover: String): String { + protected open fun createThumbnail( + mangaId: String, + cover: String, + ): String { val thumbnail = "large_${cover.substringBeforeLast(".")}.webp" return "$cdnUrl/uploads/manga/cover/$mangaId/$thumbnail" } - override fun imageUrlParse(response: Response): String = - throw UnsupportedOperationException() + override fun imageUrlParse(response: Response): String = throw UnsupportedOperationException() companion object { private val MEDIA_TYPE = "application/json; charset=utf-8".toMediaTypeOrNull() diff --git a/lib-multisrc/goda/src/eu/kanade/tachiyomi/multisrc/goda/GoDa.kt b/lib-multisrc/goda/src/eu/kanade/tachiyomi/multisrc/goda/GoDa.kt index 33175f5454..b1d4201b8f 100644 --- a/lib-multisrc/goda/src/eu/kanade/tachiyomi/multisrc/goda/GoDa.kt +++ b/lib-multisrc/goda/src/eu/kanade/tachiyomi/multisrc/goda/GoDa.kt @@ -22,7 +22,6 @@ open class GoDa( override val baseUrl: String, override val lang: String, ) : HttpSource() { - override val supportsLatest get() = true private val enableGenres = true @@ -31,22 +30,21 @@ open class GoDa( override val client = network.cloudflareClient - private fun getKey(link: String): String { - return link.substringAfter("/manga/").removeSuffix("/") - } + private fun getKey(link: String): String = link.substringAfter("/manga/").removeSuffix("/") override fun popularMangaRequest(page: Int) = GET("$baseUrl/hots/page/$page", headers) override fun popularMangaParse(response: Response): MangasPage { val document = response.asJsoup().also(::parseGenres) - val mangas = document.select(".container > .cardlist .pb-2 a").map { element -> - SManga.create().apply { - val imgSrc = element.selectFirst("img")!!.attr("src") - url = getKey(element.attr("href")) - title = element.selectFirst("h3")!!.ownText() - thumbnail_url = if ("url=" in imgSrc) imgSrc.toHttpUrl().queryParameter("url")!! else imgSrc + val mangas = + document.select(".container > .cardlist .pb-2 a").map { element -> + SManga.create().apply { + val imgSrc = element.selectFirst("img")!!.attr("src") + url = getKey(element.attr("href")) + title = element.selectFirst("h3")!!.ownText() + thumbnail_url = if ("url=" in imgSrc) imgSrc.toHttpUrl().queryParameter("url")!! else imgSrc + } } - } val nextPage = if (lang == "zh") "下一頁" else "NEXT" val hasNextPage = document.selectFirst("a[aria-label=$nextPage] button") != null return MangasPage(mangas, hasNextPage) @@ -56,12 +54,19 @@ open class GoDa( override fun latestUpdatesParse(response: Response) = popularMangaParse(response) - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { + override fun searchMangaRequest( + page: Int, + query: String, + filters: FilterList, + ): Request { if (query.isNotEmpty()) { - val url = "$baseUrl/s".toHttpUrl().newBuilder() - .addPathSegment(query) - .addEncodedQueryParameter("page", "$page") - .build() + val url = + "$baseUrl/s" + .toHttpUrl() + .newBuilder() + .addPathSegment(query) + .addEncodedQueryParameter("page", "$page") + .build() return GET(url, headers) } for (filter in filters) { @@ -74,45 +79,50 @@ open class GoDa( override fun getMangaUrl(manga: SManga) = "$baseUrl/manga/${manga.url}" - override fun mangaDetailsRequest(manga: SManga): Request { - return GET(getMangaUrl(manga), headers) - } + override fun mangaDetailsRequest(manga: SManga): Request = GET(getMangaUrl(manga), headers) private fun Element.getMangaId() = selectFirst("#mangachapters")!!.attr("data-mid") - override fun mangaDetailsParse(response: Response) = SManga.create().apply { - val document = response.asJsoup().selectFirst("main")!! - val titleElement = document.selectFirst("h1")!! - val elements = titleElement.parent()!!.parent()!!.children() - check(elements.size == 6) - - title = titleElement.ownText() - status = when (titleElement.child(0).text()) { - "連載中", "Ongoing" -> SManga.ONGOING - "完結" -> SManga.COMPLETED - else -> SManga.UNKNOWN + override fun mangaDetailsParse(response: Response) = + SManga.create().apply { + val document = response.asJsoup().selectFirst("main")!! + val titleElement = document.selectFirst("h1")!! + val elements = titleElement.parent()!!.parent()!!.children() + check(elements.size == 6) + + title = titleElement.ownText() + status = + when (titleElement.child(0).text()) { + "連載中", "Ongoing" -> SManga.ONGOING + "完結" -> SManga.COMPLETED + else -> SManga.UNKNOWN + } + author = Entities.unescape(elements[1].children().drop(1).joinToString { it.text().removeSuffix(" ,") }) + genre = + buildList { + elements[2].children().drop(1).mapTo(this) { it.text().removeSuffix(" ,") } + elements[3].children().mapTo(this) { it.text().removePrefix("#") } + }.joinToString() + description = (elements[4].text() + "\n\nID: ${document.getMangaId()}").trim() + thumbnail_url = document.selectFirst("img.object-cover")!!.attr("src") } - author = Entities.unescape(elements[1].children().drop(1).joinToString { it.text().removeSuffix(" ,") }) - genre = buildList { - elements[2].children().drop(1).mapTo(this) { it.text().removeSuffix(" ,") } - elements[3].children().mapTo(this) { it.text().removePrefix("#") } - }.joinToString() - description = (elements[4].text() + "\n\nID: ${document.getMangaId()}").trim() - thumbnail_url = document.selectFirst("img.object-cover")!!.attr("src") - } - - override fun fetchChapterList(manga: SManga): Observable> = Observable.fromCallable { - val mangaId = manga.description - ?.substringAfterLast("ID: ", "") - ?.takeIf { it.toIntOrNull() != null } - ?: client.newCall(mangaDetailsRequest(manga)).execute().asJsoup().getMangaId() - fetchChapterList(mangaId) - } + override fun fetchChapterList(manga: SManga): Observable> = + Observable.fromCallable { + val mangaId = + manga.description + ?.substringAfterLast("ID: ", "") + ?.takeIf { it.toIntOrNull() != null } + ?: client + .newCall(mangaDetailsRequest(manga)) + .execute() + .asJsoup() + .getMangaId() + + fetchChapterList(mangaId) + } - override fun chapterListParse(response: Response): List { - throw UnsupportedOperationException() - } + override fun chapterListParse(response: Response): List = throw UnsupportedOperationException() open fun fetchChapterList(mangaId: String): List { val response = client.newCall(GET("$baseUrl/manga/get?mid=$mangaId&mode=all", headers)).execute() @@ -135,7 +145,10 @@ open class GoDa( return pageListRequest(mangaId, chapterId) } - open fun pageListRequest(mangaId: String, chapterId: String) = GET("$baseUrl/chapter/getcontent?m=$mangaId&c=$chapterId", headers) + open fun pageListRequest( + mangaId: String, + chapterId: String, + ) = GET("$baseUrl/chapter/getcontent?m=$mangaId&c=$chapterId", headers) override fun pageListParse(response: Response): List { val document = response.asJsoup() @@ -152,10 +165,11 @@ open class GoDa( if (!enableGenres || genres.isNotEmpty()) return val box = document.selectFirst("h2")?.parent()?.parent() ?: return val items = box.select("a") - genres = Array(items.size) { i -> - val item = items[i] - Pair(item.text().removePrefix("#"), item.attr("href")) - } + genres = + Array(items.size) { i -> + val item = items[i] + Pair(item.text().removePrefix("#"), item.attr("href")) + } } override fun getFilterList(): FilterList = @@ -164,15 +178,18 @@ open class GoDa( } else if (genres.isEmpty()) { FilterList(listOf(Filter.Header(if (lang == "zh") "点击“重置”刷新分类" else "Tap 'Reset' to load genres"))) } else { - val list = listOf( - Filter.Header(if (lang == "zh") "分类(搜索文本时无效)" else "Filters are ignored when using text search."), - UriPartFilter(if (lang == "zh") "分类" else "Genre", genres), - ) + val list = + listOf( + Filter.Header(if (lang == "zh") "分类(搜索文本时无效)" else "Filters are ignored when using text search."), + UriPartFilter(if (lang == "zh") "分类" else "Genre", genres), + ) FilterList(list) } - class UriPartFilter(displayName: String, private val vals: Array>) : - Filter.Select(displayName, vals.map { it.first }.toTypedArray()) { + class UriPartFilter( + displayName: String, + private val vals: Array>, + ) : Filter.Select(displayName, vals.map { it.first }.toTypedArray()) { fun toUriPart() = vals[state].second } } diff --git a/lib-multisrc/gravureblogger/src/eu/kanade/tachiyomi/multisrc/gravureblogger/GravureBlogger.kt b/lib-multisrc/gravureblogger/src/eu/kanade/tachiyomi/multisrc/gravureblogger/GravureBlogger.kt index 2979b1fda9..3d3cf5fc39 100644 --- a/lib-multisrc/gravureblogger/src/eu/kanade/tachiyomi/multisrc/gravureblogger/GravureBlogger.kt +++ b/lib-multisrc/gravureblogger/src/eu/kanade/tachiyomi/multisrc/gravureblogger/GravureBlogger.kt @@ -27,13 +27,14 @@ abstract class GravureBlogger( override val baseUrl: String, override val lang: String, ) : HttpSource() { - override val supportsLatest = false override val client = network.cloudflareClient - override fun headersBuilder() = super.headersBuilder() - .add("Referer", "$baseUrl/") + override fun headersBuilder() = + super + .headersBuilder() + .add("Referer", "$baseUrl/") private val json: Json by injectLazy() @@ -52,19 +53,20 @@ abstract class GravureBlogger( categories = data.feed.category.map { it.term } - val manga = data.feed.entry.map { entry -> - val content = Jsoup.parseBodyFragment(entry.content.t, baseUrl) - - SManga.create().apply { - setUrlWithoutDomain(entry.link.first { it.rel == "alternate" }.href + "#${entry.published.t}") - title = entry.title.t - thumbnail_url = content.selectFirst("img")?.absUrl("src") - genre = entry.category?.joinToString { it.term } - status = SManga.COMPLETED - update_strategy = UpdateStrategy.ONLY_FETCH_ONCE - initialized = true + val manga = + data.feed.entry.map { entry -> + val content = Jsoup.parseBodyFragment(entry.content.t, baseUrl) + + SManga.create().apply { + setUrlWithoutDomain(entry.link.first { it.rel == "alternate" }.href + "#${entry.published.t}") + title = entry.title.t + thumbnail_url = content.selectFirst("img")?.absUrl("src") + genre = entry.category?.joinToString { it.term } + status = SManga.COMPLETED + update_strategy = UpdateStrategy.ONLY_FETCH_ONCE + initialized = true + } } - } val hasNextPage = data.feed.entry.size == MAX_RESULTS return MangasPage(manga, hasNextPage) @@ -74,27 +76,33 @@ abstract class GravureBlogger( override fun latestUpdatesParse(response: Response) = throw UnsupportedOperationException() - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { + override fun searchMangaRequest( + page: Int, + query: String, + filters: FilterList, + ): Request { val filterList = filters.ifEmpty { getFilterList() } - val searchQuery = buildString { - filterList.filterIsInstance().forEach { - it.state - .filter { f -> f.state } - .forEach { f -> - append(" label:\"") - append(f.name) - append("\"") - } - } - - if (query.isNotEmpty()) { - append(" ") - append(query) - } - }.trim() - val url = apiUrlBuilder(page) - .addQueryParameter("q", searchQuery) - .build() + val searchQuery = + buildString { + filterList.filterIsInstance().forEach { + it.state + .filter { f -> f.state } + .forEach { f -> + append(" label:\"") + append(f.name) + append("\"") + } + } + + if (query.isNotEmpty()) { + append(" ") + append(query) + } + }.trim() + val url = + apiUrlBuilder(page) + .addQueryParameter("q", searchQuery) + .build() return GET(url, headers) } @@ -117,9 +125,10 @@ abstract class GravureBlogger( SChapter.create().apply { url = manga.url.substringBefore("#") name = "Gallery" - date_upload = runCatching { - dateFormat.parse(date)!!.time - }.getOrDefault(0L) + date_upload = + runCatching { + dateFormat.parse(date)!!.time + }.getOrDefault(0L) }, ), ) @@ -140,9 +149,14 @@ abstract class GravureBlogger( // filter name and list of values protected open val labelFilters = mapOf>() - class LabelFilter(name: String, labels: List

= emptyList(), @@ -43,6 +42,7 @@ data class Dialog( private val bgColor: List = emptyList(), ) { val text: String get() = textByLanguage["text"] ?: throw Exception("Dialog not found") + fun getTextBy(language: Language) = textByLanguage[language.target] ?: text val width get() = x2 - x1 @@ -51,14 +51,16 @@ data class Dialog( val centerX get() = (x2 + x1) / 2f val foregroundColor: Int get() { - val color = fbColor.takeIf { it.isNotEmpty() } - ?: return Color.BLACK + val color = + fbColor.takeIf { it.isNotEmpty() } + ?: return Color.BLACK return Color.rgb(color[0], color[1], color[2]) } val backgroundColor: Int get() { - val color = bgColor.takeIf { it.isNotEmpty() } - ?: return Color.WHITE + val color = + bgColor.takeIf { it.isNotEmpty() } + ?: return Color.WHITE return Color.rgb(color[0], color[1], color[2]) } } @@ -95,15 +97,16 @@ private object DialogListSerializer : ) } - private fun getCoordinates(element: JsonElement): JsonArray { - return when (element) { + private fun getCoordinates(element: JsonElement): JsonArray = + when (element) { is JsonArray -> element.jsonArray[0].jsonArray - else -> element.jsonObject["bbox"]?.jsonArray - ?: throw IOException("Dialog box position not found") + else -> + element.jsonObject["bbox"]?.jsonArray + ?: throw IOException("Dialog box position not found") } - } - private fun getDialogs(element: JsonElement): JsonObject { - return buildJsonObject { + + private fun getDialogs(element: JsonElement): JsonObject = + buildJsonObject { when (element) { is JsonArray -> put("text", element.jsonArray[1]) else -> { @@ -113,7 +116,6 @@ private object DialogListSerializer : } } } - } private val JsonElement.isArray get() = this is JsonArray private val JsonElement.isObject get() = this is JsonObject diff --git a/lib-multisrc/machinetranslations/src/eu/kanade/tachiyomi/multisrc/machinetranslations/MachineTranslationsFactoryUtils.kt b/lib-multisrc/machinetranslations/src/eu/kanade/tachiyomi/multisrc/machinetranslations/MachineTranslationsFactoryUtils.kt index 96c03fa74c..0c2b96f86d 100644 --- a/lib-multisrc/machinetranslations/src/eu/kanade/tachiyomi/multisrc/machinetranslations/MachineTranslationsFactoryUtils.kt +++ b/lib-multisrc/machinetranslations/src/eu/kanade/tachiyomi/multisrc/machinetranslations/MachineTranslationsFactoryUtils.kt @@ -2,4 +2,8 @@ package eu.kanade.tachiyomi.multisrc.machinetranslations class MachineTranslationsFactoryUtils -data class Language(val lang: String, val target: String = lang, val origin: String = "en") +data class Language( + val lang: String, + val target: String = lang, + val origin: String = "en", +) diff --git a/lib-multisrc/machinetranslations/src/eu/kanade/tachiyomi/multisrc/machinetranslations/MachineTranslationsFilters.kt b/lib-multisrc/machinetranslations/src/eu/kanade/tachiyomi/multisrc/machinetranslations/MachineTranslationsFilters.kt index a9624d1f7a..04d63d7501 100644 --- a/lib-multisrc/machinetranslations/src/eu/kanade/tachiyomi/multisrc/machinetranslations/MachineTranslationsFilters.kt +++ b/lib-multisrc/machinetranslations/src/eu/kanade/tachiyomi/multisrc/machinetranslations/MachineTranslationsFilters.kt @@ -2,58 +2,75 @@ package eu.kanade.tachiyomi.multisrc.machinetranslations import eu.kanade.tachiyomi.source.model.Filter -class SelectionList(displayName: String, private val vals: List