From 85d977e407777a846c057870a6304bbd6f480abb Mon Sep 17 00:00:00 2001 From: KenjieDec <65448230+KenjieDec@users.noreply.github.com> Date: Mon, 11 Nov 2024 14:25:35 +0700 Subject: [PATCH] Koharu: Moved to src/all | Spyfakku: Fixed Errors, Added "Per page" Filter | Pururin: Re-added, Fixed Some Details (#5956) * Koharu: Moved to src/all | Fixed Spyfakku - Added 2 language options for Koharu: japanese and english - Spyfakku: use a cleaner api if available * Delete src/en/koharu directory * Fixed #5957 * Added Pururin | Koharu fixed language - Pururin: Added back, Fixed tags not showing properly - Koharu: Fixed "Multi" language search not showing anything, added Chinese language as an option * Fixed Tag Separation in Description - Fixed: tags were listed with spaces as the separator, instead of commas * Added Chinese language as an option - also: applied FourTOne5's suggestion - I forgor * Applied suggestion - Applied FourTOne5's suggestion * Deeplink support for mirror links --- src/{en => all}/koharu/AndroidManifest.xml | 14 +- src/{en => all}/koharu/build.gradle | 4 +- .../koharu/res/mipmap-hdpi/ic_launcher.png | Bin .../koharu/res/mipmap-mdpi/ic_launcher.png | Bin .../koharu/res/mipmap-xhdpi/ic_launcher.png | Bin .../koharu/res/mipmap-xxhdpi/ic_launcher.png | Bin .../koharu/res/mipmap-xxxhdpi/ic_launcher.png | Bin .../tachiyomi/extension/all}/koharu/Koharu.kt | 21 +- .../extension/all}/koharu/KoharuDto.kt | 2 +- .../extension/all/koharu/KoharuFactory.kt | 13 + .../extension/all}/koharu/KoharuFilters.kt | 2 +- .../all}/koharu/KoharuUrlActivity.kt | 2 +- src/all/pururin/build.gradle | 8 + .../pururin/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 3244 bytes .../pururin/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 1848 bytes .../pururin/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 4081 bytes .../pururin/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 7099 bytes .../res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 9249 bytes .../extension/all/pururin/Pururin.kt | 271 ++++++++++++++++++ .../extension/all/pururin/PururinFactory.kt | 24 ++ .../extension/all/pururin/PururinFilters.kt | 57 ++++ src/en/spyfakku/build.gradle | 2 +- .../extension/en/spyfakku/Filters.kt | 10 + .../extension/en/spyfakku/SpyFakku.kt | 138 +++++---- .../extension/en/spyfakku/SpyFakkuDto.kt | 33 ++- 25 files changed, 518 insertions(+), 83 deletions(-) rename src/{en => all}/koharu/AndroidManifest.xml (54%) rename src/{en => all}/koharu/build.gradle (59%) rename src/{en => all}/koharu/res/mipmap-hdpi/ic_launcher.png (100%) rename src/{en => all}/koharu/res/mipmap-mdpi/ic_launcher.png (100%) rename src/{en => all}/koharu/res/mipmap-xhdpi/ic_launcher.png (100%) rename src/{en => all}/koharu/res/mipmap-xxhdpi/ic_launcher.png (100%) rename src/{en => all}/koharu/res/mipmap-xxxhdpi/ic_launcher.png (100%) rename src/{en/koharu/src/eu/kanade/tachiyomi/extension/en => all/koharu/src/eu/kanade/tachiyomi/extension/all}/koharu/Koharu.kt (93%) rename src/{en/koharu/src/eu/kanade/tachiyomi/extension/en => all/koharu/src/eu/kanade/tachiyomi/extension/all}/koharu/KoharuDto.kt (92%) create mode 100644 src/all/koharu/src/eu/kanade/tachiyomi/extension/all/koharu/KoharuFactory.kt rename src/{en/koharu/src/eu/kanade/tachiyomi/extension/en => all/koharu/src/eu/kanade/tachiyomi/extension/all}/koharu/KoharuFilters.kt (94%) rename src/{en/koharu/src/eu/kanade/tachiyomi/extension/en => all/koharu/src/eu/kanade/tachiyomi/extension/all}/koharu/KoharuUrlActivity.kt (95%) create mode 100644 src/all/pururin/build.gradle create mode 100644 src/all/pururin/res/mipmap-hdpi/ic_launcher.png create mode 100644 src/all/pururin/res/mipmap-mdpi/ic_launcher.png create mode 100644 src/all/pururin/res/mipmap-xhdpi/ic_launcher.png create mode 100644 src/all/pururin/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 src/all/pururin/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 src/all/pururin/src/eu/kanade/tachiyomi/extension/all/pururin/Pururin.kt create mode 100644 src/all/pururin/src/eu/kanade/tachiyomi/extension/all/pururin/PururinFactory.kt create mode 100644 src/all/pururin/src/eu/kanade/tachiyomi/extension/all/pururin/PururinFilters.kt diff --git a/src/en/koharu/AndroidManifest.xml b/src/all/koharu/AndroidManifest.xml similarity index 54% rename from src/en/koharu/AndroidManifest.xml rename to src/all/koharu/AndroidManifest.xml index 5f565204f..e1aa90b82 100644 --- a/src/en/koharu/AndroidManifest.xml +++ b/src/all/koharu/AndroidManifest.xml @@ -3,7 +3,7 @@ @@ -13,10 +13,14 @@ - + + + + + + + + diff --git a/src/en/koharu/build.gradle b/src/all/koharu/build.gradle similarity index 59% rename from src/en/koharu/build.gradle rename to src/all/koharu/build.gradle index 8703ea56e..0bd0a7ad2 100644 --- a/src/en/koharu/build.gradle +++ b/src/all/koharu/build.gradle @@ -1,7 +1,7 @@ ext { extName = 'SchaleNetwork' - extClass = '.Koharu' - extVersionCode = 9 + extClass = '.KoharuFactory' + extVersionCode = 10 isNsfw = true } diff --git a/src/en/koharu/res/mipmap-hdpi/ic_launcher.png b/src/all/koharu/res/mipmap-hdpi/ic_launcher.png similarity index 100% rename from src/en/koharu/res/mipmap-hdpi/ic_launcher.png rename to src/all/koharu/res/mipmap-hdpi/ic_launcher.png diff --git a/src/en/koharu/res/mipmap-mdpi/ic_launcher.png b/src/all/koharu/res/mipmap-mdpi/ic_launcher.png similarity index 100% rename from src/en/koharu/res/mipmap-mdpi/ic_launcher.png rename to src/all/koharu/res/mipmap-mdpi/ic_launcher.png diff --git a/src/en/koharu/res/mipmap-xhdpi/ic_launcher.png b/src/all/koharu/res/mipmap-xhdpi/ic_launcher.png similarity index 100% rename from src/en/koharu/res/mipmap-xhdpi/ic_launcher.png rename to src/all/koharu/res/mipmap-xhdpi/ic_launcher.png diff --git a/src/en/koharu/res/mipmap-xxhdpi/ic_launcher.png b/src/all/koharu/res/mipmap-xxhdpi/ic_launcher.png similarity index 100% rename from src/en/koharu/res/mipmap-xxhdpi/ic_launcher.png rename to src/all/koharu/res/mipmap-xxhdpi/ic_launcher.png diff --git a/src/en/koharu/res/mipmap-xxxhdpi/ic_launcher.png b/src/all/koharu/res/mipmap-xxxhdpi/ic_launcher.png similarity index 100% rename from src/en/koharu/res/mipmap-xxxhdpi/ic_launcher.png rename to src/all/koharu/res/mipmap-xxxhdpi/ic_launcher.png diff --git a/src/en/koharu/src/eu/kanade/tachiyomi/extension/en/koharu/Koharu.kt b/src/all/koharu/src/eu/kanade/tachiyomi/extension/all/koharu/Koharu.kt similarity index 93% rename from src/en/koharu/src/eu/kanade/tachiyomi/extension/en/koharu/Koharu.kt rename to src/all/koharu/src/eu/kanade/tachiyomi/extension/all/koharu/Koharu.kt index 1eb002be0..06cbbe945 100644 --- a/src/en/koharu/src/eu/kanade/tachiyomi/extension/en/koharu/Koharu.kt +++ b/src/all/koharu/src/eu/kanade/tachiyomi/extension/all/koharu/Koharu.kt @@ -1,4 +1,4 @@ -package eu.kanade.tachiyomi.extension.en.koharu +package eu.kanade.tachiyomi.extension.all.koharu import android.app.Application import android.content.SharedPreferences @@ -28,19 +28,21 @@ import uy.kohesive.injekt.injectLazy import java.text.SimpleDateFormat import java.util.Locale -class Koharu : HttpSource(), ConfigurableSource { +class Koharu( + override val lang: String = "all", + private val searchLang: String = "", +) : HttpSource(), ConfigurableSource { + override val name = "SchaleNetwork" - override val id = 1484902275639232927 - override val baseUrl = "https://schale.network" + override val id = if (lang == "en") 1484902275639232927 else super.id + private val apiUrl = baseUrl.replace("://", "://api.") private val apiBooksUrl = "$apiUrl/books" - override val lang = "en" - override val supportsLatest = true override val client: OkHttpClient = network.cloudflareClient.newBuilder() @@ -112,12 +114,12 @@ class Koharu : HttpSource(), ConfigurableSource { // Latest - override fun latestUpdatesRequest(page: Int) = GET("$apiBooksUrl?page=$page", headers) + override fun latestUpdatesRequest(page: Int) = GET("$apiBooksUrl?page=$page" + if (searchLang.isNotBlank()) "&s=language!:\"$searchLang\"" else "", headers) override fun latestUpdatesParse(response: Response) = popularMangaParse(response) // Popular - override fun popularMangaRequest(page: Int) = GET("$apiBooksUrl?sort=8&page=$page", headers) + override fun popularMangaRequest(page: Int) = GET("$apiBooksUrl?sort=8&page=$page" + if (searchLang.isNotBlank()) "&s=language!:\"$searchLang\"" else "", headers) override fun popularMangaParse(response: Response): MangasPage { val data = response.parseAs() @@ -143,6 +145,7 @@ class Koharu : HttpSource(), ConfigurableSource { val url = apiBooksUrl.toHttpUrl().newBuilder().apply { val terms: MutableList = mutableListOf() + if (lang != "all") terms += "language!:\"$searchLang\"" filters.forEach { filter -> when (filter) { is SortFilter -> addQueryParameter("sort", filter.getValue()) @@ -158,7 +161,7 @@ class Koharu : HttpSource(), ConfigurableSource { if (filter.state.isNotEmpty()) { val tags = filter.state.split(",").filter(String::isNotBlank).joinToString(",") if (tags.isNotBlank()) { - terms += "${filter.type}!:" + '"' + tags + '"' + terms += "${filter.type}!:" + if (filter.type == "pages") tags else '"' + tags + '"' } } } diff --git a/src/en/koharu/src/eu/kanade/tachiyomi/extension/en/koharu/KoharuDto.kt b/src/all/koharu/src/eu/kanade/tachiyomi/extension/all/koharu/KoharuDto.kt similarity index 92% rename from src/en/koharu/src/eu/kanade/tachiyomi/extension/en/koharu/KoharuDto.kt rename to src/all/koharu/src/eu/kanade/tachiyomi/extension/all/koharu/KoharuDto.kt index cf54c190f..fb67dc6e9 100644 --- a/src/en/koharu/src/eu/kanade/tachiyomi/extension/en/koharu/KoharuDto.kt +++ b/src/all/koharu/src/eu/kanade/tachiyomi/extension/all/koharu/KoharuDto.kt @@ -1,4 +1,4 @@ -package eu.kanade.tachiyomi.extension.en.koharu +package eu.kanade.tachiyomi.extension.all.koharu import kotlinx.serialization.Serializable diff --git a/src/all/koharu/src/eu/kanade/tachiyomi/extension/all/koharu/KoharuFactory.kt b/src/all/koharu/src/eu/kanade/tachiyomi/extension/all/koharu/KoharuFactory.kt new file mode 100644 index 000000000..4a3edcb52 --- /dev/null +++ b/src/all/koharu/src/eu/kanade/tachiyomi/extension/all/koharu/KoharuFactory.kt @@ -0,0 +1,13 @@ +package eu.kanade.tachiyomi.extension.all.koharu + +import eu.kanade.tachiyomi.source.Source +import eu.kanade.tachiyomi.source.SourceFactory + +class KoharuFactory : SourceFactory { + override fun createSources(): List = listOf( + Koharu(), + Koharu("en", "english"), + Koharu("ja", "japanese"), + Koharu("zh", "chinese"), + ) +} diff --git a/src/en/koharu/src/eu/kanade/tachiyomi/extension/en/koharu/KoharuFilters.kt b/src/all/koharu/src/eu/kanade/tachiyomi/extension/all/koharu/KoharuFilters.kt similarity index 94% rename from src/en/koharu/src/eu/kanade/tachiyomi/extension/en/koharu/KoharuFilters.kt rename to src/all/koharu/src/eu/kanade/tachiyomi/extension/all/koharu/KoharuFilters.kt index b871bc400..96bc1d1d0 100644 --- a/src/en/koharu/src/eu/kanade/tachiyomi/extension/en/koharu/KoharuFilters.kt +++ b/src/all/koharu/src/eu/kanade/tachiyomi/extension/all/koharu/KoharuFilters.kt @@ -1,4 +1,4 @@ -package eu.kanade.tachiyomi.extension.en.koharu +package eu.kanade.tachiyomi.extension.all.koharu import eu.kanade.tachiyomi.source.model.Filter import eu.kanade.tachiyomi.source.model.FilterList diff --git a/src/en/koharu/src/eu/kanade/tachiyomi/extension/en/koharu/KoharuUrlActivity.kt b/src/all/koharu/src/eu/kanade/tachiyomi/extension/all/koharu/KoharuUrlActivity.kt similarity index 95% rename from src/en/koharu/src/eu/kanade/tachiyomi/extension/en/koharu/KoharuUrlActivity.kt rename to src/all/koharu/src/eu/kanade/tachiyomi/extension/all/koharu/KoharuUrlActivity.kt index 56799263d..cb008c2eb 100644 --- a/src/en/koharu/src/eu/kanade/tachiyomi/extension/en/koharu/KoharuUrlActivity.kt +++ b/src/all/koharu/src/eu/kanade/tachiyomi/extension/all/koharu/KoharuUrlActivity.kt @@ -1,4 +1,4 @@ -package eu.kanade.tachiyomi.extension.en.koharu +package eu.kanade.tachiyomi.extension.all.koharu import android.app.Activity import android.content.ActivityNotFoundException diff --git a/src/all/pururin/build.gradle b/src/all/pururin/build.gradle new file mode 100644 index 000000000..b52cf0832 --- /dev/null +++ b/src/all/pururin/build.gradle @@ -0,0 +1,8 @@ +ext { + extName = 'Pururin' + extClass = '.PururinFactory' + extVersionCode = 10 + isNsfw = true +} + +apply from: "$rootDir/common.gradle" diff --git a/src/all/pururin/res/mipmap-hdpi/ic_launcher.png b/src/all/pururin/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..35093ef9b4e06f5556dbc8e24f5d4a95da6f4d4b GIT binary patch literal 3244 zcmV;d3{&%oP)?)dwYCf7_#QdQ#hpO72_z(h5DpIrD1w5@+A0Eq_LyyF)oxo4P`lH{Znagh zwTEb}M-liYpmt|}+23|{|C7${%uau6JFopAA^9fx=7NBAme0)dS7!2<_ve#uzWEYD zHrB@4SQ~3&ZLI&7^#UPe({tVn!7_((rf7%cNz2f#9~$?a{J}an`Ga+E@=3$M)Z>PM zsmBd_0$HN#3s;+@>fTU`@v7VzTY0x|=5Ri9*ObrPHRU&ZCjF(J$;Gm+cl?(Aofj*b z=pfjt*5*r9t(w2u1{a@}cK`U)suSn(unxm(e>|8Ys@zoT(tfa2k#0wjc!E|79%K%C6+->>wp zJ%o^#(w-hdNY;a*t!JJT@Ax?(&RR@QDnLThlL`>a17aP&+*UNcPYB5(X&&602qB_- zhSm>Lo}Bf>NdYL?^!Qi@IL3}!RQ*SU5HU$}n{6V5NWL|=y{ny^H3wSZ_F4w|hO&4G zAySg&VZE6T6a$mt>4^)p!s$r|=xfSyi4a1q5Xj|C3ue8i=Qj@&L=n7hF$7Q+^=-Ub z!|B|i{90ZbH~gBGK)Jj!48$?^3HzIw32^30XN#$|`V@Eyr0|B`z!K@SV~DpSn4Hpaj#idLW(48xJOg zV`#^Mp$*^ZT=-n;z*S8nJjz0J8LKf^ZVhK{2sOZKZ&`h~QVaGhXR5<)V=db9%TSYR zK(S1PN=A)#Mvn>BglTy_?#u1)DV!_bUTXuyJ8zcqI^Z|7MA&u z`M9?202cSWhQ)qfea9p6_Z8VQY~(>^=VH}27sJiA(+rRR1oM}T{EU~Ef`mtG0X?b!1P4p_;!uM zxASNyu4fEqY@M)BYFuOL(!$~BMtm))%`lK59#CC;phP&U1oHJ91=l?Wud^2}nE_Ym z8gP7^bp?vP#xZojuW!S*S|_H|VIZqQkE>e`WDF-65Fcqbm4|z*ZJmHLPH!{{{{nDP z-wq!iNMXidMQIo))ory(oaG*QMleuZi%F{D*#YsdjAMbyctHCzKRF42xSla^J!AO9 z)rS^J1CF&P#mP8OOk zla6k9BznBdl;ad*#+$SWBUBMyp$ai1EkIq49Dl6uNEztCj>AxI$;K>WOAJKWRtwbR zjYi?$u2|Hy!l!lPPOc*abVz1IgG`IhmVu(DM&Qhua&+SmRfMk&6|m08snBl-|adfqE$e&a#y_%~s$orVIzDLbxOvl(T9~ z+C3@baGp`TXW53`S(>CcYXZc-J?huCg46JTSQB1lOJHF&VW1>9{@w}P+&YAbYFDUZ z)h@hM*MNd@NU#*}bY52Uht)w>k zngGxsIgLt}a5W91);iqmvCjzj|` zd+X){<>)ZK{8dfVw-HHUR)~{XAZ2VmWiu8OkwCwf6rft7z?GIgDdBM4W0-E`0kP|c zvw9$z%_{^W2t<|Rh@?;uC?3pmiGX5oGO(@I9%%VF9EB4L@+RD38*rL3#{eaR2?GtF zKU<4=#-94rWB?~~K)i>OBA(rrnW1Nv9@5I2izH@W#W5 z0g{?AEG`fR5`y8~s^9~yC(fDxMLfYJZ^FFHfm=*H-j4=y^(RE(H|^ewzASC{KF&J8 zWE3bGWjQVyNK}9!Q68#9GF)=*#%$-2sD?4SBeu_)`_Y%Bi3C~)oYey{4c>S-(Lkpq zWf&IaV@Q+-OBM^Agh2r%4=0tylrNPbU&10^%wRmb01H9~@pA{ndoYQUIWW)KF+9dk@$Y;+?Y(Kf;6XdC92an=bX0!V88^@h0m0$IVoLv132h%QLW zhv!)Pf8xQU^syk>)$lXI;V2vCDC#bt*HA(C`~ULb^ISzeYIZ&G!CUlmFsm}iK? zQ8wI^RR5!rh>e7hY_fv?rQbpbky*D$Y&WE}|9r&Qewt_Ozsxgs%m=ql(kNTvq}FFp z!>z~RsO4%NNj`<8Ck(U^%4@;X4!-grIx%@Igyor{G6!tGkK=JX zs*Mwbkaq|n@4aQ-;a%z=}F z1YSE5cXSy@8;a8N4u7B?3&MG*L;5e!v$aP${6D?aOCx8c}6)pMv(6qK~6VBXg z?J#oMVeDv5bEt(GX>{SvP?96c6G`AsB!Mp?agJ~t zpNHf4JT!^h!SlE|a0W}=gIF~8V^Lwl9l8fBFMinUA1$D;WQ;N74KX~|595wz0BMyA z7j6B>&He$oi##(ok(;@RotcmJxxh1hql9egI@qZdd=;6(l%@+08BdD?1mcA?pEty? z)iI3EH2wIU%840Uf0=wqKn?k9>N>JhE67f*Ad^hvvdxQsQ0|)YEH!w90O7@Ih+(Va zG(J=JaRJu;hUF^-WRojk6KTwKy783pw&7 zQg&RGS#U*WMo6T_qtS)R0f{u8k1u1sO{cZ?v{F@nwZC~hlz@u9p6 z)8^iqYktw@!MZAhr^+C{q&;R9BM0W~hz zOEwR(>LI*TMRA|;;F{C|zetHEu}f70*3W%_D{Y2~@{I)u#D!;@O>Gpb3N9ce45)Cy zF4(=8r_Gq9J2A!xd~fTpz7>57H~j5rya(Qb{HG#Fm5qiz?7-GCGwBY z%wvo(;)%?I3|)EVj~2k+fD~^;iUBTsKv{|bhly8`&&03c!SF2RI{T1n)8St-FJ20l zP16Af<@vj)4OIxYXg^*T0R~sqC6i6AVA1ZyL#iK-X%8M#9z3Bu_@4G* zlM3JkJ%|h)LZ-@o-+VyfepNt8fYgkH!C6tfu+|stUTiTzu<{TGQ$TP`Xh6QX0M!t( zsv$g64B``+gA33*t6!+eG#2b$JZFL>XZvgu@{I+k1WyZ8DTqYgy`>r4P2O9TNG#0$*Z2KJ8zEs$?0Kqy^+Ee3SqeUTAf znFe#V0S@Z`=B)iiW-a~jG3|Ik1xv-7kUx3=|CE50hp|Bi@P)*QYobp4USz^@n+eNp z#v&h{GUDnfBmO3KVoT<4nRvqiY~j(y0k)YzJf(biDC@%msSr2rOE|ufxbbhP7u&+w zZ-IQK&VUny5N2B*+B>*^XPGcqCX5Wj{e!-w2RSGqLA<1b$WTFKS|ncJg)Fc~2$9zl zAblho`2L`L*>+fMcWJSBfp5fpPYEGXQpXFwlR9$Ja!=Cx<2vQv+oS@hip*b*)Ynpe zlz8{qh+&kJ+gnO|j meoKgWhxZzhKOV=Uf$@KNBc*><6>4<=0000m5= literal 0 HcmV?d00001 diff --git a/src/all/pururin/res/mipmap-xhdpi/ic_launcher.png b/src/all/pururin/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..21db9a1cfe32290b64d1e74db1d95136d25d4b92 GIT binary patch literal 4081 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 z zrv++YZmL)uoc-EMAPEj}gXq~mCP0uk8pMV?$bRWIn`n^Dzy2zxpANQ+sJL|qOc+ZB z&g0DWj4`vBYC+MScy0q8MoU%90??8SfEndSVnV4&4D+wT5#5ywFW5>tfp8X+NxU5| zO0LqaDfP<0a%NedlSrwBxQf#vzpngIH7%_|O^0}n39(Lp(n!JbZm1BuU_5@0gk!cN zQOkbmk5lUs{rMis>2Nrxl|4`+2Kqvql#3UbKRjUgrM3|Na#QBm#x+yJep&{TTHsJr z2{V?y|5R31)&!{)z5BVKwfJn2{T#*j_5D6urUC;@@54f15C{FjIIZC@dz0_!(U`|2 z*W~=V42!fJhJFWJl zB5e6eA>#*W?kdI)$a-3Pt027#HQelBF-2T+oTgl^13lbi|LnC}Y|`Pnye67g^pa1% z|Jlx9Mm0;OpyK)P;MkFNN*qbxO|=kBa}htg!Q5#$jVOF$Hybn(u%+2q)-<%R#T;v_ zw#u-W6ObijM&_}8$wfE^^0S!AUJw-H)8Od#)(VZ^>3L|brBks$_3!l`sIgm%HGIAj zPU2nPRCn>w3@P61Z*<@Hh@%0&k`rzUVB%GQut4#xUf6oyxtUahnZ)l6;MqA#7ha*k zb8@q1F47U`7^b^fTyrn(aEiPVNkltsM+}u3V6-3-+`zmPPS2||2PXvhW8+*?2rb|s zxV>(3P;NGvm+RI0zuaRPA(fw<(WqO^Zy&u=UOj9B7@I+?eR%SI*NyG#+Fasc<0{>K z3e~=vs%v{8zS))9rbExX2x9Rx3T*c9LlS3ls#hthwKcMhfmXa9wZ8(uUODzNJbysmm@KGS~w=g(Vpzp+2V zjuashmU?N))EMXFaback?u6(0)a&LJ2gR?mIah9*`)JXwk`8ZnduW~R$gB_CXmC3- z#!CoF8~T82(Tiy|ejRGEc(S)ww{=%j`Z+;7=_A+4mKhdOmZWxWn2)$TeZ~T!#Cf8V zP=Bp_R|~09dfVPU<|`xV@NS&m%M*=x5z2NAsI=~-hu!Q=SXQ~^rj9At@G2rAK-l*g`@mdd$uHt8TKf;YdA z>9nh>SFj^l%^yDV_I!hY*+GmhJ^UYhjctzN>|RWkoaDMw;=rjVoW#jbsk#Ue>#Qwm zsJ1?M`HX+2JcTOcliBP*9Io}Q2hMv1)F&5`sT9B7y=}E5aFx_?s-jE`^yGCw@^kmM+9>bd$6w)e z74qKanL_VgHnlw9sFyyDtMF_Ut7>KrqnDh9eNY-hJkDeedWk{IiksVCNy99J&Gc|QrGAs1d&2(!GaVD` literal 0 HcmV?d00001 diff --git a/src/all/pururin/res/mipmap-xxxhdpi/ic_launcher.png b/src/all/pururin/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..96424681f4c9b62118153bcd2c82767184f6aaf0 GIT binary patch literal 9249 zcmbVS`9D-|^uIG^F!p`l*GQJ6>?5+vzK5}8iR`kQ!IbP0B3nh2tdV4$u`eNH-^La` zwh?8@;5&W)g7545dOi2vU+z8UIp;p-ocH@YPm-yzE-e)o6#xLV`g(WFNl(nbi;|pl zH(&In0003%|ITg8@WTC~nP6_umI^pE@Js~^q09n=^MU~mZ3D~l4S^^RU2D2BDZAVa z{igfFxecmKn<5KYqH_~h3~7WUqBKYL^5>L1n|{aEj`mK!KW5)6e%YY%Y4tPv675Yv zw2lthx%GKXxE4$C*8b^P%Q(NFz4zN3>;LC1Y0AyM@u>tM3ps1`al4TvWc zie_l%NXW~oSCTNHIDZjz-Wd5!D`M>MNj=;@=Hl}ufpC0DoDokyUqF;7J~8FXC_>Kvj^6WC z;}X|(^}PP*B5*i((DQh8c8=xhWPL-REau;4)<02xsUkFcYao)6ShuYv~YCx z;~}94DIa_9v%cPbU-O^4BTr`EJMpVn?Yj1}tHHy?*W4fOS=J=IJiNMA7Ofho>Lzzw z=No#`tn1d#D*RShu3>{Gov+{Y3caB&zTK0i^ZgtO9`dlir0+Me7M_0CPSC#efHT(6 zO|S|cie`2rG|`4Hf1TZ>2)mrzSna&5KkO$wY^NdM%czi*;2so$Ss)m!%CsdLyR{Mz zr$4+{*u$rLK9!Zszf=CxI$jUI5c~Q5-;t%V0?l9g+5@yF4MqE&;v*)q8ukwM#6y58_kS z#dqV<{b7Qs19@id(p#mOZD$2A!S4kL+!Pnz&Ko0fW2*-lH!rU;J16mX9TM;1*I&}X z1<_1)!zmv$!cRRtb;8<8=m?Y7|8BILeF(i6(*orZ1RegHdsN8K#pJ@0g@6W`PmG$p zJ7d=5fkTidh>u_-TMN=D1O8a?vY*kpJoFeW*rRXl1P!`LuD@;qWAw9?MKjF!&+}c* zw4o6DOYrB%$POw7vJ25z%3}rQ%N&L^HoJz*P2SgqRL&w@qDjK&@Yt2+Un}x-om?%* z*Q1w8EV$R6#7<^SRWw6!6;DEt5bDNd!P7e3T>-RZRd|~q2tP@Md>wWvRCvk$<+hYk z;%&f^ZNgDyY0_-TJONm-ZoJ}hbp?u+PkJ9#IKr_e)mSt~Xl{Gqm@?PZ7iz|fx z(1_l3laA5LAt;_v)4@H0H}muoCX5|zO5*voh8M}M~D4;X1TTy2|BBZ=MWB-rv zybZ9nf@Lxg?4H26mk``Yb8e*NHPqi#n~2k~O@hT&un0|T%j($kaq%s%djN58gIH@1 z$^yeF@IFfjwc}1n$bce25{>u;-WViUG=tx9UI-mt%<&+pEKn@COF_+!TKmMq80O@k z-&(D2PxlyahA&E4_2~bZ6^^h~@8B7mP2PA8{CnRJbq7PA0OuhjS~1)HgxQ_kb;h>- zc&3W4!4|6KtOv^9lVgK#2Cmuh%F7pzv*!OweR6SA{KxIND{R9$_1m1iS)RX=RV|MX zOw6b}5IY<2Ub`>lgOr+#2Rw`yPDiOY3z_;%u0sA&ov??V_CCSM4+l`40+lF9SyB5;$jM zb1fAwSaN|u6#sqf)zC}r6$l?;+EoW~**fInP|49X!lm-#Ldjd$Sc5f$!HpLCY45$mSc>z5HtXP^R0vhW3^Lh3p}h**>9E9W+7N;eblC z+Jz8ed;+>#xn5jN4D4`a;D138*wn^aXDpcvD%q~JYg!0aFOqJble#4 zi&XdH^MiA#m`Dsf*LC&SN!pU_e8?3hPLG=}S8HxOu#0Z~D`6B$HkB^$Ao^j#o%mg_L#kh+< zp5Z^0vKzi-bRTdzl`#gf?ClTtm)IRD)oLaj5GPGchrgWS#W&CN0=~VM7&j2^*4HnT1zS8zZ z6jq0GOo&>RbNH2xOGyT`?e276EtW2n^66&`yIQ_foyT-s)?l0u%*DeEm*81|`H#}zbcH7QQ zugs8gKvfm%EAj;RnyGyoJS~Ku%rX9czz~~IJ>h#jV0as^JIb1@JRgeM>^F*zg|I3VlqUJnfPGm zA_%mh^!+AHMOys07kb94zrw+0ISu|FyMR9at!I=FEWnrr=Xo6@LRhED^v8SFpCx(* zF5IZ8S$TY&J0UBJY1Yt4aky#A>eQwdkcj7x48LZC8h!)#4?r`q{1zxM8qNwTTU-bN zm^*Z`v$HX_=JCuS31S)(;4*M52hjkc_@6-oK!vk8+dkH7;_X|T_Exo8;mpF_FPD(% zE75+W0R@*)#X#_^Fr1%_G-~?1v`c2}kr~)9iIM%!a$(rz8iT;}@!2!T6?$CSN54(5 z7yc@nbKvfco7M`i%gP{y=Ws0LAGt;fy!mxq_}FccI2gkip0f48IoY?Zd>Q{gxux7aXJy@ch2& zcTtU$nE)XO#1}cU78mAF4=nH~3_(o=iTQgMug$y`Zm9Fbf`2dx?~x1F+_9&3HwPFm zx)=mt__xuAMS6Ou^S5`0paVCFk~hkO zW^_h+KMw*;z>w-`>fZldm>>AfQ;hm1j5}k_j9SGsj6mjS^@#%RY$^e4tsaomRWfoQ zGxmD=HU|y4W%jJ>A;(uq;(rVRWH@ml%xz9$AoPkmVe;F#Fbuy-+p19@CCfn*p5r$S z(fbV4FOM@DqDork0~;H)zW`rQh|ML$(DDy}hMY`i@UDn_7g~$&x%^=LsUM)Fc`F)r3!zdFzQ& zZNHO`l{uch9Y{j)o1v0>Ae522R`kdkUH*vl#teb&c6OGTe7>9Qe=E zKVJq6{CgB?NYE`8cpu*>g_-Yx;y$oaXTk{l>Jbp}$eAmVU#zN{_i1$6W81!W@=<4) zY&D+A__0p>z`wdv+wW;)V~w*(19rgbMA{J;HO|*kN~0Y2g4)#YE#s@jt8QW1MkUoO z)i)_BrDzt}RQAMzw{ygJYYd|0>xDf@&YOODbNg4Ms%PyaBoi_KA^a0Tqn!lgqY=`m zl|DCqn@*vqIpw|kyLI9QkK{ki>d&l+htS5~WUqPyd6dFhlCJi=;h?iQluLI$@%7BE zockkp=!FoIz|SaBY7EdI?_>g9r|on=>2POw*MKG+$jTMr<+G7IuDgMm)%~C*zB7_^ z=Et*FnKg>{Em?C0uc( zGM#@SJo*C-86YPHf$V+hc(w`x3jMpN9fZ>Z)542=ddzA-y3OP~Y;QB&XS=_!X$MXbVVL=9*xkXEs0+Kt~LvE58$)~gmh z=&ng?ll-MR@uTuzy)R9EHhQRGU;iIa=lI7Ws33ePEMQRnX~ythCMJOe{4jL;Q)|HV z__cECLzNbMaVfLEit>@zO1jOTo5U<1tbPql*A`AE7dk6rz#xtBD`zVl1oyf2wqK+8 zU-7NpNIle8{^)m{3>}CmQT+|T>elS2o>liRNTe~SrL>f{=dYt3=QC;z&hU>L=t~r% z@NuQo{z}Sts+nZ=IWZnq(OLE#NsXB|6Yh!QUOi#EVfRB%o&}k(XjDwDHdsPUfldRc z9_?RNuqBmekf1CggDcTgdr$Q;T{bzHZJi;)hh+QG^dT*euz z4>ZiBnzd3i9JqWP2wG7?w;l*CKB8TF84R%V#zXv%_6g91)U7#k9xDC>FDTf(`OFZ! z9n$h0YD$Sbf-%G{dV#@v!8e>sN(JRW*MZA!@eEw}nBrFt`MxU|0kcI?)CY{J0U6Gt zYN@SnwVHp>tX=iwSI4ymB>VKjniwaI&VO>;k?DW<5$k$=b-FypE)hw}+p^*U`*-dzwi+P zF@fMPQY_VN@b$ilVhOHcT$JGL$(aDuLqpbSYiQx?FYyi#5Z8!Qs*Z5vqPo<;A({L8 zGZ|y!)zVdQP4L%1GS0bEnP||OMy(}^y{I~gIulA*hmqvvz+_VwNb}sI!7-bmN({i? zhQYo~Rv6bCSl=6K``E#~ZW*e879?4SGOt5qaG!2T7n1;Jdz}h*(7Q7s0vTW;K7-gw za`&3&1Ltq<*;0IM)d^TpKy#0R90Go?YIRW8Cac`G2vPez{%KZw%DJeT$K%NI#kEh> zFl-Ob-Yiz=%pF+xu!q`L z*vXkYAoU@Ju`ffSoUe7;URC>#tc#3pZEupYJXxO?UD4)dquJ~Xlfxf^799>zSg1*j zAWtw(9NQkP3PFZaW_oOE;ccq0-N_q}I{WJF+Nu#4DN&Vmf5^E5UiP{X-!N}BqI}fY zsp5IBcoo00a^*tE$R^&$7h&CkG^4`D^af=yI%oY7mDb=|MG`g`3popWG_+~sH;?4k zs{3316I0zk?2?I^UL96|_obyp$*Dx($1Bt)6;8zkG4;RAwnLP&cdq5JhoL6Gonjb+ z?dPn{GKc+6%cPR$O^)Ex?%cY&o^kIw?2`B+!RyE3x1Eq#?<0@X;{8qq;U14F@r=+4+sUCTuk&7N=cAPO()~N7rS6OjorO>gXv_Y6@6<%BdgZ%*_ z>80V~wf*XPD#HO{ic>EJ_`gI3%+Eb51UMm`rmLMd>zRKuF_V^*Q`0S1xs)ZuC$I>0 zv=M0N2|i6FuML1wA;ok{dIu%;{OL3+CSPm%V`RR`Bd*?wp} zhPIDQKnwo?fr7-@NMd}_ACU-RgP2C2PO)nI&YGGNlrZ9RPvbv!?7<3|I`59Pc9fisvqQR zZpPm`z*hY;N5)qPouOMy85(Qt-&0^Ie-H%v`U6Dly@v9np;vQLVia(}Pk}p)P+vwU z0xH9J4SLueBWT#72SZkGE@W`tWSIYxB&orMlTin$x?Rnx0z4WxV>YM$%=|irTr6<^ z7Qp?)zGBAFb45fOgsq7!WJLJ_!4VVZDOxDmZ3{+c`g@7j8gEH}t}p*O1}?aF!`%EW zfT}@okuk-GQB=hUDcgaoI=+ClrGG>`7ZD0+YBmxvNx zeq)apoJyD3k#=y241B)dt>*n>tdy7kE}%r5XuV;b+jsz=#=gKC*3BeDI++r75z_*T07|N`ARw9uHeeqBgqK;T9Q~vfm#g z&0&xKAy3p6*XX$=Augd&TcP~H5urXY0QI`wuq}I>QMT0cnlyxsBS%!hVtff=;(a61 zzD?nHH$&0ibCrPuP6^&o*V1Qq@$pL;CG+RubIYh(?>yQJKdrg7@NVTxJFsn?t2hAi zg+T0lAKzWniB?H6P$02-qqhvAM^2JkS*M*h zJ>aRE!o6xIxA<+(!}c2PQQNOSlz}j%RlHBKr;_u~teT()xn!D5@bTsM3q0={=%&Si z;2g*jxCu^153~j z-&=d3ijrVt*FnF@J}m({_0;B_Sex-D+_g%m=lh$5r78i%2VJ-Rc`hn2VIML6i#@xm zgrd|0OPP5EpG3tFTjtdTE_=VL%j{gy&eiV)FRu8{?MGf7`xSV)MJ&$?=L#Z0X$pl% z#&qf^tkW#ThizL&V;a{DZUO!=oiIX&?L9SEeSVbkwnaeHe?_NX(8a(Jjq*0MWaJuZJ{m@AW69 z*L;WQu@ND^e{X`@^b~Mvb?wbvzt<`$&+j(zhuOYf7)vf4C3E5k`%c#>rZM+Gszq0Bm#eROBJMk608C=T zW&6W3`PR|Bp}He8=C&YwiZ8wNM>WEeeXM8`yG^rGbpNkau9?JL2e@C*dGobLk+8o? zjDIPvZ)=&!5qqzqqFCtfdwRbW(U8Q6gDxkUx|=^okbrlCkxI?+x!kdjpVG{i*hSn~ z;Pb=&YaRTb{{RO+K_phuJVqhcP5X5HE(?xX?Tcu}s*E(~&i1ChHmdkzWmSTV+Oz;W zj=72Xl$UH=I6kI?-c{#ol~iyWPIWviFp;`9RlcV()-v zA<%C)nIMqEuwBqweH07EgoT9ccfOpHXd8qe`Gize1GJ5Y!0w#HOrRi(K_j8_DdZ_Q z+8$DuQ%dTPEcqd(u`gl^i!*_It_UjNqKi>O5dWUCE~OGHT`49C$|8sF-K9Gx2}u5)1ScJQ+|7oWf~ zL3k3eI(k-t!pS@Utgl*DCy&2x0GwWnsdBs{WbV2*QpU$C(|t~nVsrpbXlA$s;NNC z8Cg9S9cZl{A#=Q>aE}MrO=;|#Q9cZ zQ<{_un-tdEDrr}~Ej16Gtg3TAjU~P#CncmCxbB849_joT-VXkiz!=4`N2@U{v=!SP zg=Jg;-<=Y>*Ia@o&92}7pl?WL_d!ndhmsGi@$7&2tZLBV>^;2FRNPe~`UBaRyrd99 z$aNgM67jKw2H0Q!IV5k3A|Qhv*P=)s(-pGD+rnCFKJu@guP5G@l6Y#Tu#k+_sHQ@E8eG^J!~%aYx4S7JbCOh>db8wxl%p622X)Y*nS4;iDyNqg4lQx^42M2yD0(-4*RVF)$ptfX)?3Y(On@po~5{qN!CiW2cu^D zin^cIgImZD6Gen4JAGCpL9^eqDwU`f$ zX9>~qYKO08Feb|WiYhCjXhM&NVaguo9kyU0R563A5ZV;YATC%Iq;go&+)WV#j?j5+ zLP6jn>TiiHrO>AO7c#7|+Ld-e1|rqONE?>uS(u=zU|Au9`6+1W_d+!7m$E)-Z`1wa1+~CFj=;W_X|G+q6oZ8YqRjkC_5PJ1bpUz`C=T3zL z%hDLu*z<#)ZK_}<2+yDYK}h~N2X-Vej4mb9y69o+g$Jc>yWEz;6P_Vkmw$5idmWEz zc3f||%wWcbrDSh^y?)+&_jL98)Z^f$rq0d>-p%YcJ{C|4?h9#I-N`$T_}E5J$mB_f z|FqX8Z6IF}awu25L+C$8XIg9O**s4w4qvuEUcP5}j8r%Le7@Ts(%)I~y!f5vy}gI_ z6PXzgt1mC&sy7kKjS3f=b#MQU*tdHZGE9hG7sehk=D!&nnuAv?k!Ea<(aUA;$%3eB z>hTJeCXaqibb#~@!yWvEX~ zdycktTK?n}^uA}Ag}F=lUZhK&=NCFtOi1d%JWP-mooOr-jhEKc`<(mc=}Al&Q(W95 zp%9{&kP(f-9p}EgxhkCybVi!QHOAR@BAPQYqF@ykM%%3J682J)vMiCzK0$fX-ukyY1X*Jt+kL?<-gX0_ zs)Lx1mfjKsy_HWdC}}Ji;$XKJIaZ%yQq$g1#=b8L)H6Z%CMQwycj-V1IEA7$C`kI= zbG*oXlYG*yl7s>4qOTbzOC|UVfwdyDD%?yOBU7u|xUw@huf~fc6jAc$3dQf+C(BHn z`il>LD@ej5^AOwY@sid31EoS710QL0ggE?!x)P_&3+0~O=QDVU(NiQ;CLo-G7)3Zm z1j{%YS?+Jp=sD;WzT+c${o;tQq`GFm;o$XuJw=lfevY*G=C9au%-Kac`UB`|8{etX HbdLWYNj5z$ literal 0 HcmV?d00001 diff --git a/src/all/pururin/src/eu/kanade/tachiyomi/extension/all/pururin/Pururin.kt b/src/all/pururin/src/eu/kanade/tachiyomi/extension/all/pururin/Pururin.kt new file mode 100644 index 000000000..8275e55eb --- /dev/null +++ b/src/all/pururin/src/eu/kanade/tachiyomi/extension/all/pururin/Pururin.kt @@ -0,0 +1,271 @@ +package eu.kanade.tachiyomi.extension.all.pururin + +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.source.model.FilterList +import eu.kanade.tachiyomi.source.model.Page +import eu.kanade.tachiyomi.source.model.SChapter +import eu.kanade.tachiyomi.source.model.SManga +import eu.kanade.tachiyomi.source.online.ParsedHttpSource +import eu.kanade.tachiyomi.util.asJsoup +import kotlinx.serialization.Serializable +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.json.Json +import okhttp3.FormBody +import okhttp3.HttpUrl.Companion.toHttpUrl +import okhttp3.Request +import okhttp3.Response +import org.jsoup.nodes.Document +import org.jsoup.nodes.Element +import uy.kohesive.injekt.injectLazy + +abstract class Pururin( + override val lang: String = "all", + private val searchLang: Pair? = null, + private val langPath: String = "", +) : ParsedHttpSource() { + override val name = "Pururin" + + final override val baseUrl = "https://pururin.me" + + override val supportsLatest = true + + override val client = network.cloudflareClient + + private val json: Json by injectLazy() + + // Popular + override fun popularMangaRequest(page: Int): Request { + return GET("$baseUrl/browse$langPath?sort=most-popular&page=$page", headers) + } + + override fun popularMangaSelector(): String = "a.card" + + override fun popularMangaFromElement(element: Element): SManga { + return SManga.create().apply { + title = element.attr("title") + setUrlWithoutDomain(element.attr("abs:href")) + thumbnail_url = element.select("img").attr("abs:src") + } + } + + override fun popularMangaNextPageSelector(): String = ".page-item [rel=next]" + + // Latest + override fun latestUpdatesRequest(page: Int): Request { + return GET("$baseUrl/browse$langPath?page=$page", headers) + } + + override fun latestUpdatesSelector(): String = popularMangaSelector() + + override fun latestUpdatesFromElement(element: Element): SManga = popularMangaFromElement(element) + + override fun latestUpdatesNextPageSelector(): String = popularMangaNextPageSelector() + + // Search + + private fun List>.toValue(): String { + return "[${this.joinToString(",") { "{\"id\":${it.first},\"name\":\"${it.second}\"}" }}]" + } + + private fun parsePageRange(query: String, minPages: Int = 1, maxPages: Int = 9999): Pair { + val num = query.filter(Char::isDigit).toIntOrNull() ?: -1 + fun limitedNum(number: Int = num): Int = number.coerceIn(minPages, maxPages) + + if (num < 0) return minPages to maxPages + return when (query.firstOrNull()) { + '<' -> 1 to if (query[1] == '=') limitedNum() else limitedNum(num + 1) + '>' -> limitedNum(if (query[1] == '=') num else num + 1) to maxPages + '=' -> when (query[1]) { + '>' -> limitedNum() to maxPages + '<' -> 1 to limitedNum(maxPages) + else -> limitedNum() to limitedNum() + } + else -> limitedNum() to limitedNum() + } + } + + @Serializable + class Tag( + val id: Int, + val name: String, + ) + + private fun findTagByNameSubstring(tags: List, substring: String): Pair? { + val tag = tags.find { it.name.contains(substring, ignoreCase = true) } + return tag?.let { Pair(tag.id.toString(), tag.name) } + } + + private fun tagSearch(tag: String, type: String): Pair? { + val requestBody = FormBody.Builder() + .add("text", tag) + .build() + + val request = Request.Builder() + .url("$baseUrl/api/get/tags/search") + .headers(headers) + .post(requestBody) + .build() + + val response = client.newCall(request).execute() + return findTagByNameSubstring(response.parseAs>(), type) + } + + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { + val includeTags = mutableListOf>() + val excludeTags = mutableListOf>() + var pagesMin = 1 + var pagesMax = 9999 + var sortBy = "newest" + + if (searchLang != null) includeTags.add(searchLang) + + filters.forEach { + when (it) { + is SelectFilter -> sortBy = it.getValue() + + is TypeFilter -> { + val (_, inactiveFilters) = it.state.partition { stIt -> stIt.state } + excludeTags += inactiveFilters.map { fil -> Pair(fil.value, "${fil.name} [Category]") } + } + + is PageFilter -> { + if (it.state.isNotEmpty()) { + val (min, max) = parsePageRange(it.state) + pagesMin = min + pagesMax = max + } + } + + is TextFilter -> { + if (it.state.isNotEmpty()) { + it.state.split(",").filter(String::isNotBlank).map { tag -> + val trimmed = tag.trim() + if (trimmed.startsWith('-')) { + tagSearch(trimmed.lowercase().removePrefix("-"), it.type)?.let { tagInfo -> + excludeTags.add(tagInfo) + } + } else { + tagSearch(trimmed.lowercase(), it.type)?.let { tagInfo -> + includeTags.add(tagInfo) + } + } + } + } + } + else -> {} + } + } + + // Searching with just one tag usually gives wrong results + if (query.isEmpty()) { + when { + excludeTags.size == 1 && includeTags.isEmpty() -> excludeTags.addAll(excludeTags) + includeTags.size == 1 && excludeTags.isEmpty() -> { + val url = baseUrl.toHttpUrl().newBuilder().apply { + addPathSegment("browse") + addPathSegment("tags") + addPathSegment("content") + addPathSegment(includeTags[0].first) + addQueryParameter("sort", sortBy) + addQueryParameter("start_page", pagesMin.toString()) + addQueryParameter("last_page", pagesMax.toString()) + if (page > 1) addQueryParameter("page", page.toString()) + }.build() + return GET(url, headers) + } + } + } + + val url = baseUrl.toHttpUrl().newBuilder().apply { + addPathSegment("search") + addQueryParameter("q", query) + addQueryParameter("sort", sortBy) + addQueryParameter("start_page", pagesMin.toString()) + addQueryParameter("last_page", pagesMax.toString()) + if (includeTags.isNotEmpty()) addQueryParameter("included_tags", includeTags.toValue()) + if (excludeTags.isNotEmpty()) addQueryParameter("excluded_tags", excludeTags.toValue()) + if (page > 1) addQueryParameter("page", page.toString()) + }.build() + + return GET(url, headers) + } + + override fun searchMangaSelector(): String = popularMangaSelector() + + override fun searchMangaFromElement(element: Element): SManga = popularMangaFromElement(element) + + override fun searchMangaNextPageSelector(): String = popularMangaNextPageSelector() + + // Details + + override fun mangaDetailsParse(document: Document): SManga { + return SManga.create().apply { + document.select(".box-gallery").let { e -> + initialized = true + title = e.select(".title").text() + author = e.select("a[href*=/circle/]").eachText().joinToString().ifEmpty { e.select("[itemprop=author]").text() } + artist = e.select("[itemprop=author]").eachText().joinToString() + genre = e.select("a[href*=/content/]").eachText().joinToString() + description = e.select(".box-gallery .table-info tr") + .filter { tr -> + tr.select("td").let { td -> + td.isNotEmpty() && + td.none { it.text().contains("content", ignoreCase = true) || it.text().contains("ratings", ignoreCase = true) } + } + } + .joinToString("\n") { tr -> + tr.select("td").let { td -> + var a = td.select("a").toList() + if (a.isEmpty()) a = td.drop(1) + td.first()!!.text() + ": " + a.joinToString { it.text() } + } + } + status = SManga.COMPLETED + thumbnail_url = e.select("img").attr("abs:src") + } + } + } + + // Chapters + + override fun chapterListSelector(): String = ".table-collection tbody tr a" + + override fun chapterFromElement(element: Element): SChapter { + return SChapter.create().apply { + name = element.text() + setUrlWithoutDomain(element.attr("abs:href")) + } + } + + override fun chapterListParse(response: Response): List { + return response.asJsoup().select(chapterListSelector()) + .map { chapterFromElement(it) } + .reversed() + .let { list -> + list.ifEmpty { + listOf( + SChapter.create().apply { + setUrlWithoutDomain(response.request.url.toString()) + name = "Chapter" + }, + ) + } + } + } + + // Pages + + override fun pageListParse(document: Document): List { + return document.select(".gallery-preview a img") + .mapIndexed { i, img -> + Page(i, "", (if (img.hasAttr("abs:src")) img.attr("abs:src") else img.attr("abs:data-src")).replace("t.", ".")) + } + } + + override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException() + + private inline fun Response.parseAs(): T { + return json.decodeFromString(body.string()) + } + override fun getFilterList() = getFilters() +} diff --git a/src/all/pururin/src/eu/kanade/tachiyomi/extension/all/pururin/PururinFactory.kt b/src/all/pururin/src/eu/kanade/tachiyomi/extension/all/pururin/PururinFactory.kt new file mode 100644 index 000000000..95117dcd2 --- /dev/null +++ b/src/all/pururin/src/eu/kanade/tachiyomi/extension/all/pururin/PururinFactory.kt @@ -0,0 +1,24 @@ +package eu.kanade.tachiyomi.extension.all.pururin + +import eu.kanade.tachiyomi.source.Source +import eu.kanade.tachiyomi.source.SourceFactory + +class PururinFactory : SourceFactory { + override fun createSources(): List = listOf( + PururinAll(), + PururinEN(), + PururinJA(), + ) +} + +class PururinAll : Pururin() +class PururinEN : Pururin( + "en", + Pair("13010", "english"), + "/tags/language/13010/english", +) +class PururinJA : Pururin( + "ja", + Pair("13011", "japanese"), + "/tags/language/13011/japanese", +) diff --git a/src/all/pururin/src/eu/kanade/tachiyomi/extension/all/pururin/PururinFilters.kt b/src/all/pururin/src/eu/kanade/tachiyomi/extension/all/pururin/PururinFilters.kt new file mode 100644 index 000000000..40bc4006a --- /dev/null +++ b/src/all/pururin/src/eu/kanade/tachiyomi/extension/all/pururin/PururinFilters.kt @@ -0,0 +1,57 @@ +package eu.kanade.tachiyomi.extension.all.pururin + +import eu.kanade.tachiyomi.source.model.Filter +import eu.kanade.tachiyomi.source.model.FilterList + +fun getFilters(): FilterList { + return FilterList( + SelectFilter("Sort by", getSortsList), + TypeFilter("Types"), + Filter.Separator(), + Filter.Header("Separate tags with commas (,)"), + Filter.Header("Prepend with dash (-) to exclude"), + TextFilter("Tags", "[Content]"), + TextFilter("Artists", "[Artist]"), + TextFilter("Circles", "[Circle]"), + TextFilter("Parodies", "[Parody]"), + TextFilter("Languages", "[Language]"), + TextFilter("Scanlators", "[Scanlator]"), + TextFilter("Conventions", "[Convention]"), + TextFilter("Collections", "[Collections]"), + TextFilter("Categories", "[Category]"), + TextFilter("Uploaders", "[Uploader]"), + Filter.Separator(), + Filter.Header("Filter by pages, for example: (>20)"), + PageFilter("Pages"), + ) +} +internal class TypeFilter(name: String) : + Filter.Group( + name, + listOf( + Pair("Artbook", "17783"), + Pair("Artist CG", "13004"), + Pair("Doujinshi", "13003"), + Pair("Game CG", "13008"), + Pair("Manga", "13004"), + Pair("Webtoon", "27939"), + ).map { CheckBoxFilter(it.first, it.second, true) }, + ) + +internal open class CheckBoxFilter(name: String, val value: String, state: Boolean) : Filter.CheckBox(name, state) + +internal open class PageFilter(name: String) : Filter.Text(name) + +internal open class TextFilter(name: String, val type: String) : Filter.Text(name) + +internal open class SelectFilter(name: String, val vals: List>, state: Int = 0) : + Filter.Select(name, vals.map { it.first }.toTypedArray(), state) { + fun getValue() = vals[state].second +} +private val getSortsList: List> = listOf( + Pair("Newest", "newest"), + Pair("Most Popular", "most-popular"), + Pair("Highest Rated", "highest-rated"), + Pair("Most Viewed", "most-viewed"), + Pair("Title", "title"), +) diff --git a/src/en/spyfakku/build.gradle b/src/en/spyfakku/build.gradle index 0d857e931..8622e707e 100644 --- a/src/en/spyfakku/build.gradle +++ b/src/en/spyfakku/build.gradle @@ -1,7 +1,7 @@ ext { extName = 'SpyFakku' extClass = '.SpyFakku' - extVersionCode = 9 + extVersionCode = 10 isNsfw = true } diff --git a/src/en/spyfakku/src/eu/kanade/tachiyomi/extension/en/spyfakku/Filters.kt b/src/en/spyfakku/src/eu/kanade/tachiyomi/extension/en/spyfakku/Filters.kt index 6f6de5ee9..d67b03351 100644 --- a/src/en/spyfakku/src/eu/kanade/tachiyomi/extension/en/spyfakku/Filters.kt +++ b/src/en/spyfakku/src/eu/kanade/tachiyomi/extension/en/spyfakku/Filters.kt @@ -7,6 +7,7 @@ import eu.kanade.tachiyomi.source.model.FilterList fun getFilters(): FilterList { return FilterList( SortFilter("Sort by", Selection(0, false), getSortsList), + SelectFilter("Per page", getLimits), Filter.Separator(), Filter.Header("Separate tags with commas (,)"), Filter.Header("Prepend with dash (-) to exclude"), @@ -25,7 +26,16 @@ internal open class SortFilter(name: String, selection: Selection, private val v Filter.Sort(name, vals.map { it.first }.toTypedArray(), selection) { fun getValue() = vals[state!!.index].second } +internal open class SelectFilter(name: String, val vals: List, state: Int = 2) : + Filter.Select(name, vals.map { it }.toTypedArray(), state) +private val getLimits = listOf( + "6", + "12", + "24", + "36", + "48", +) private val getSortsList: List> = listOf( Pair("Title", "title"), Pair("Relevance", "relevance"), diff --git a/src/en/spyfakku/src/eu/kanade/tachiyomi/extension/en/spyfakku/SpyFakku.kt b/src/en/spyfakku/src/eu/kanade/tachiyomi/extension/en/spyfakku/SpyFakku.kt index 1f3cb2d05..034c7f355 100644 --- a/src/en/spyfakku/src/eu/kanade/tachiyomi/extension/en/spyfakku/SpyFakku.kt +++ b/src/en/spyfakku/src/eu/kanade/tachiyomi/extension/en/spyfakku/SpyFakku.kt @@ -83,6 +83,10 @@ class SpyFakku : HttpSource() { addQueryParameter("order", if (filter.state!!.ascending) "asc" else "desc") } + is SelectFilter -> { + addQueryParameter("limit", filter.vals[filter.state]) + } + is TextFilter -> { if (filter.state.isNotEmpty()) { terms += filter.state.split(",").filter { it.isNotBlank() }.map { tag -> @@ -101,11 +105,6 @@ class SpyFakku : HttpSource() { return GET(url, headers) } - override fun mangaDetailsRequest(manga: SManga): Request { - manga.url = Regex("^/archive/(\\d+)/.*").replace(manga.url) { "/g/${it.groupValues[1]}" } - return GET(baseUrl + manga.url.substringBefore("?") + "/__data.json", headers) - } - override fun getFilterList() = getFilters() // Details @@ -118,8 +117,8 @@ class SpyFakku : HttpSource() { } private fun getAdditionals(data: List): ShortHentai { - fun Collection.getTags(): List = this.map { - data[it.jsonPrimitive.int + 2].jsonPrimitive.content + fun Collection.getTags(): List = this.map { + Name(data[it.jsonPrimitive.int + 2].jsonPrimitive.content, data[it.jsonPrimitive.int + 3].jsonPrimitive.content) } val hentaiIndexes = json.decodeFromJsonElement(data[1]) @@ -132,22 +131,14 @@ class SpyFakku : HttpSource() { val size = data[hentaiIndexes.size].jsonPrimitive.long val pages = data[hentaiIndexes.pages].jsonPrimitive.int - val circles = data[hentaiIndexes.circles].jsonArray.emptyToNull()?.getTags() - val publishers = data[hentaiIndexes.publishers].jsonArray.emptyToNull()?.getTags() - val magazines = data[hentaiIndexes.magazines].jsonArray.emptyToNull()?.getTags() - val events = data[hentaiIndexes.events].jsonArray.emptyToNull()?.getTags() - val parodies = data[hentaiIndexes.parodies].jsonArray.emptyToNull()?.getTags() + val tags = data[hentaiIndexes.tags].jsonArray.emptyToNull()?.getTags() return ShortHentai( hash = hash, thumbnail = thumbnail, description = description, released_at = released_at, created_at = created_at, - publishers = publishers, - circles = circles, - magazines = magazines, - parodies = parodies, - events = events, + tags = tags, size = size, pages = pages, ) @@ -159,62 +150,79 @@ class SpyFakku : HttpSource() { private fun Hentai.toSManga() = SManga.create().apply { title = this@toSManga.title url = "/g/$id?$pages&hash=$hash" - artist = artists?.joinToString() - genre = tags?.joinToString() + author = tags?.filter { it.namespace == "circle" }?.joinToString { it.name } + artist = tags?.filter { it.namespace == "artist" }?.joinToString { it.name } + genre = tags?.filter { it.namespace == "tag" }?.joinToString { it.name } thumbnail_url = "$baseImageUrl/$hash/$thumbnail?type=cover" status = SManga.COMPLETED } override fun fetchMangaDetails(manga: SManga): Observable { - var response: Response = client.newCall(mangaDetailsRequest(manga)).execute() - var attempts = 0 - while (attempts < 3 && response.code != 200) { - try { - response = client.newCall(mangaDetailsRequest(manga)).execute() - } catch (_: Exception) { - } finally { - attempts++ + val response1: Response = client.newCall(mangaDetailsRequest(manga)).execute() + val add: ShortHentai + + if (response1.isSuccessful) { + add = response1.parseAs() + } else { + var response: Response = client.newCall(mangaDetailsRequest(manga)).execute() + var attempts = 0 + while (attempts < 3 && response.code != 200) { + try { + response = client.newCall(mangaDetailsRequest(manga)).execute() + } catch (_: Exception) { + } finally { + attempts++ + } } + add = getAdditionals(response.parseAs().nodes.last().data) } - val add = getAdditionals(response.parseAs().nodes.last().data) + return Observable.just( manga.apply { with(add) { + val tags = tags?.groupBy { it.namespace } + url = "/g/$id?$pages&hash=$hash" - author = (circles ?: listOf(manga.artist)).joinToString() + author = (tags?.get("circle") ?: tags?.get("artist"))?.joinToString { it.name } + artist = tags?.get("artist")?.joinToString { it.name } thumbnail_url = "$baseImageUrl/$hash/$thumbnail?type=cover" + genre = tags?.get("tag")?.joinToString { it.name } this@apply.description = buildString { description?.let { append(it, "\n\n") } - circles?.emptyToNull()?.joinToString()?.let { + tags?.get("circle")?.emptyToNull()?.joinToString { it.name }?.let { append("Circles: ", it, "\n") } - publishers?.emptyToNull()?.joinToString()?.let { + tags?.get("publisher")?.emptyToNull()?.joinToString { it.name }?.let { append("Publishers: ", it, "\n") } - magazines?.emptyToNull()?.joinToString()?.let { + tags?.get("magazine")?.emptyToNull()?.joinToString { it.name }?.let { append("Magazines: ", it, "\n") } - events?.emptyToNull()?.joinToString()?.let { + tags?.get("event")?.emptyToNull()?.joinToString { it.name }?.let { append("Events: ", it, "\n\n") } - parodies?.emptyToNull()?.joinToString()?.let { + tags?.get("parody")?.emptyToNull()?.joinToString { it.name }?.let { append("Parodies: ", it, "\n") } append("Pages: ", pages, "\n\n") try { - releasedAtFormat.parse(released_at)?.let { - append("Released: ", dateReformat.format(it.time), "\n") + releasedAt?.let { + releasedAtFormat.parse(it)?.let { + append("Released: ", dateReformat.format(it.time), "\n") + } } } catch (_: Exception) { } try { - createdAtFormat.parse(created_at)?.let { - append("Added: ", dateReformat.format(it.time), "\n") + createdAt?.let { + createdAtFormat.parse(it)?.let { + append("Added: ", dateReformat.format(it.time), "\n") + } } } catch (_: Exception) { } @@ -235,22 +243,39 @@ class SpyFakku : HttpSource() { }, ) } + + override fun mangaDetailsRequest(manga: SManga): Request { + manga.url = Regex("^/archive/(\\d+)/.*").replace(manga.url) { "/g/${it.groupValues[1]}" } + return GET(baseApiUrl + manga.url.substringBefore("?"), headers) + } + private fun mangaDetailsRequest2(manga: SManga): Request { + manga.url = Regex("^/archive/(\\d+)/.*").replace(manga.url) { "/g/${it.groupValues[1]}" } + return GET(baseUrl + manga.url.substringBefore("?") + "/__data.json", headers) + } + override fun mangaDetailsParse(response: Response): SManga = throw UnsupportedOperationException() override fun getMangaUrl(manga: SManga) = baseUrl + manga.url.substringBefore("?") // Chapters override fun fetchChapterList(manga: SManga): Observable> { - var response: Response = client.newCall(chapterListRequest(manga)).execute() - var attempts = 0 - while (attempts < 3 && response.code != 200) { - try { - response = client.newCall(chapterListRequest(manga)).execute() - } catch (_: Exception) { - } finally { - attempts++ + val response1: Response = client.newCall(chapterListRequest(manga)).execute() + val add: ShortHentai + + if (response1.isSuccessful) { + add = response1.parseAs() + } else { + var response: Response = client.newCall(chapterListRequest2(manga)).execute() + var attempts = 0 + while (attempts < 3 && response.code != 200) { + try { + response = client.newCall(mangaDetailsRequest(manga)).execute() + } catch (_: Exception) { + } finally { + attempts++ + } } + add = getAdditionals(response.parseAs().nodes.last().data) } - val add = getAdditionals(response.parseAs().nodes.last().data) return Observable.just( listOf( SChapter.create().apply { @@ -267,13 +292,25 @@ class SpyFakku : HttpSource() { } override fun getChapterUrl(chapter: SChapter) = baseUrl + chapter.url.substringBefore("?") + override fun chapterListRequest(manga: SManga) = mangaDetailsRequest(manga) + private fun chapterListRequest2(manga: SManga) = mangaDetailsRequest2(manga) + override fun chapterListParse(response: Response): List = throw UnsupportedOperationException() // Pages override fun fetchPageList(chapter: SChapter): Observable> { if (!chapter.url.contains("&hash=") && !chapter.url.contains("?")) { - val response = client.newCall(pageListRequest(chapter)).execute() + val response1 = client.newCall(pageListRequest(chapter)).execute() + if (response1.isSuccessful) { + val hentai = response1.parseAs() + return Observable.just( + List(hentai.pages) { index -> + Page(index, imageUrl = "$baseImageUrl/${hentai.hash}/${index + 1}") + }, + ) + } + val response = client.newCall(pageListRequest2(chapter)).execute() val add = getAdditionals(response.parseAs().nodes.last().data) return Observable.just( List(add.pages) { index -> @@ -292,9 +329,14 @@ class SpyFakku : HttpSource() { } override fun pageListRequest(chapter: SChapter): Request { + chapter.url = Regex("^/archive/(\\d+)/.*").replace(chapter.url) { "/g/${it.groupValues[1]}" } + return GET(baseApiUrl + chapter.url.substringBefore("?"), headers) + } + private fun pageListRequest2(chapter: SChapter): Request { chapter.url = Regex("^/archive/(\\d+)/.*").replace(chapter.url) { "/g/${it.groupValues[1]}" } return GET(baseUrl + chapter.url.substringBefore("?") + "/__data.json", headers) } + override fun pageListParse(response: Response): List = throw UnsupportedOperationException() // Others diff --git a/src/en/spyfakku/src/eu/kanade/tachiyomi/extension/en/spyfakku/SpyFakkuDto.kt b/src/en/spyfakku/src/eu/kanade/tachiyomi/extension/en/spyfakku/SpyFakkuDto.kt index 8cdce1a12..bb2dfb796 100644 --- a/src/en/spyfakku/src/eu/kanade/tachiyomi/extension/en/spyfakku/SpyFakkuDto.kt +++ b/src/en/spyfakku/src/eu/kanade/tachiyomi/extension/en/spyfakku/SpyFakkuDto.kt @@ -18,9 +18,7 @@ class Hentai( val title: String, val thumbnail: Int, val pages: Int, - val artists: List?, - val circles: List?, - val tags: List?, + val tags: List?, ) @Serializable @@ -28,15 +26,24 @@ class ShortHentai( val hash: String, val thumbnail: Int, val description: String?, - val released_at: String, - val created_at: String, - val publishers: List?, - val circles: List?, - val magazines: List?, - val parodies: List?, - val events: List?, + val released_at: String? = null, + val created_at: String? = null, + var releasedAt: String? = null, + var createdAt: String? = null, + val tags: List?, val size: Long, val pages: Int, +) { + init { + releasedAt = released_at ?: releasedAt + createdAt = created_at ?: createdAt + } +} + +@Serializable +class Name( + val namespace: String, + val name: String, ) @Serializable @@ -56,11 +63,7 @@ class HentaiIndexes( val description: Int, val released_at: Int, val created_at: Int, - val publishers: Int, - val circles: Int, - val magazines: Int, - val parodies: Int, - val events: Int, + val tags: Int, val size: Int, val pages: Int, )