diff --git a/src/all/hentaihand/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/hentaihand/hentaihand/res/mipmap-hdpi/ic_launcher.png similarity index 100% rename from src/all/hentaihand/res/mipmap-hdpi/ic_launcher.png rename to multisrc/overrides/hentaihand/hentaihand/res/mipmap-hdpi/ic_launcher.png diff --git a/src/all/hentaihand/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/hentaihand/hentaihand/res/mipmap-mdpi/ic_launcher.png similarity index 100% rename from src/all/hentaihand/res/mipmap-mdpi/ic_launcher.png rename to multisrc/overrides/hentaihand/hentaihand/res/mipmap-mdpi/ic_launcher.png diff --git a/src/all/hentaihand/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/hentaihand/hentaihand/res/mipmap-xhdpi/ic_launcher.png similarity index 100% rename from src/all/hentaihand/res/mipmap-xhdpi/ic_launcher.png rename to multisrc/overrides/hentaihand/hentaihand/res/mipmap-xhdpi/ic_launcher.png diff --git a/src/all/hentaihand/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/hentaihand/hentaihand/res/mipmap-xxhdpi/ic_launcher.png similarity index 100% rename from src/all/hentaihand/res/mipmap-xxhdpi/ic_launcher.png rename to multisrc/overrides/hentaihand/hentaihand/res/mipmap-xxhdpi/ic_launcher.png diff --git a/src/all/hentaihand/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/hentaihand/hentaihand/res/mipmap-xxxhdpi/ic_launcher.png similarity index 100% rename from src/all/hentaihand/res/mipmap-xxxhdpi/ic_launcher.png rename to multisrc/overrides/hentaihand/hentaihand/res/mipmap-xxxhdpi/ic_launcher.png diff --git a/src/all/hentaihand/res/web_hi_res_512.png b/multisrc/overrides/hentaihand/hentaihand/res/web_hi_res_512.png similarity index 100% rename from src/all/hentaihand/res/web_hi_res_512.png rename to multisrc/overrides/hentaihand/hentaihand/res/web_hi_res_512.png diff --git a/multisrc/overrides/hentaihand/hentaihand/src/HentaiHandFactory.kt b/multisrc/overrides/hentaihand/hentaihand/src/HentaiHandFactory.kt new file mode 100644 index 000000000..f90d0a5df --- /dev/null +++ b/multisrc/overrides/hentaihand/hentaihand/src/HentaiHandFactory.kt @@ -0,0 +1,97 @@ +package eu.kanade.tachiyomi.extension.all.hentaihand + +import eu.kanade.tachiyomi.multisrc.hentaihand.HentaiHand +import eu.kanade.tachiyomi.source.Source +import eu.kanade.tachiyomi.source.SourceFactory +import okhttp3.OkHttpClient + +class HentaiHandFactory : SourceFactory { + override fun createSources(): List = listOf( + // https://hentaihand.com/api/languages?per_page=50 + HentaiHandOther(), + HentaiHandEn(), + HentaiHandZh(), + HentaiHandJa(), + HentaiHandNoText(), + HentaiHandEo(), + HentaiHandCeb(), + HentaiHandCs(), + HentaiHandAr(), + HentaiHandSk(), + HentaiHandMn(), + HentaiHandUk(), + HentaiHandLa(), + HentaiHandTl(), + HentaiHandEs(), + HentaiHandIt(), + HentaiHandKo(), + HentaiHandTh(), + HentaiHandPl(), + HentaiHandFr(), + HentaiHandPtBr(), + HentaiHandDe(), + HentaiHandFi(), + HentaiHandRu(), + HentaiHandHu(), + HentaiHandId(), + HentaiHandVi(), + HentaiHandNl(), + HentaiHandHi(), + HentaiHandTr(), + HentaiHandEl(), + HentaiHandSr(), + HentaiHandJv(), + HentaiHandBg(), + ) +} +abstract class HentaiHandCommon( + override val lang: String, + hhLangId: List = emptyList(), + //altLangId: Int? = null +) : HentaiHand("HentaiHand", "https://hentaihand.com", lang, false, hhLangId) { + override val client: OkHttpClient = network.cloudflareClient.newBuilder() + .addInterceptor { authIntercept(it) } + .build() +} + +class HentaiHandOther : HentaiHandCommon("all") { + override val id: Long = 1235047015955289468 +} +class HentaiHandJa : HentaiHandCommon("ja", listOf(1, 29)) +class HentaiHandEn : HentaiHandCommon("en", listOf(2, 27)) +class HentaiHandZh : HentaiHandCommon("zh", listOf(3, 50)) +class HentaiHandBg : HentaiHandCommon("bg", listOf(4)) +class HentaiHandCeb : HentaiHandCommon("ceb", listOf(5, 44)) +class HentaiHandNoText : HentaiHandCommon("other", listOf(6)) { + override val id: Long = 7302549142935671434 +} +class HentaiHandTl : HentaiHandCommon("tl", listOf(7, 55)) +class HentaiHandAr : HentaiHandCommon("ar", listOf(8, 49)) +class HentaiHandEl : HentaiHandCommon("el", listOf(9)) +class HentaiHandSr : HentaiHandCommon("sr", listOf(10)) +class HentaiHandJv : HentaiHandCommon("jv", listOf(11, 51)) +class HentaiHandUk : HentaiHandCommon("uk", listOf(12, 46)) +class HentaiHandTr : HentaiHandCommon("tr", listOf(13, 41)) +class HentaiHandFi : HentaiHandCommon("fi", listOf(14, 54)) +class HentaiHandLa : HentaiHandCommon("la", listOf(15)) +class HentaiHandMn : HentaiHandCommon("mn", listOf(16)) +class HentaiHandEo : HentaiHandCommon("eo", listOf(17, 47)) +class HentaiHandSk : HentaiHandCommon("sk", listOf(18)) +class HentaiHandCs : HentaiHandCommon("cs", listOf(19, 52)) +class HentaiHandKo : HentaiHandCommon("ko", listOf(30, 39)) +class HentaiHandRu : HentaiHandCommon("ru", listOf(31)) +class HentaiHandIt : HentaiHandCommon("it", listOf(32)) +class HentaiHandEs : HentaiHandCommon("es", listOf(33, 37)) +class HentaiHandPtBr : HentaiHandCommon("pt-BR", listOf(34)) { + // Hardcode the id because the language wasn't specific. + override val id: Long = 2516244587139644000 +} +class HentaiHandTh : HentaiHandCommon("th", listOf(35, 40)) +class HentaiHandFr : HentaiHandCommon("fr", listOf(36)) +class HentaiHandId : HentaiHandCommon("id", listOf(38)) +class HentaiHandVi : HentaiHandCommon("vi", listOf(42)) +class HentaiHandDe : HentaiHandCommon("de", listOf(43)) +class HentaiHandPl : HentaiHandCommon("pl", listOf(45)) +class HentaiHandHu : HentaiHandCommon("hu", listOf(48)) +class HentaiHandNl : HentaiHandCommon("nl", listOf(53)) +class HentaiHandHi : HentaiHandCommon("hi", listOf(56)) diff --git a/multisrc/overrides/hentaihand/hentaisphere/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/hentaihand/hentaisphere/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000..ae2c6302a Binary files /dev/null and b/multisrc/overrides/hentaihand/hentaisphere/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/hentaihand/hentaisphere/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/hentaihand/hentaisphere/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000..93d81e247 Binary files /dev/null and b/multisrc/overrides/hentaihand/hentaisphere/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/hentaihand/hentaisphere/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/hentaihand/hentaisphere/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000..c640aff14 Binary files /dev/null and b/multisrc/overrides/hentaihand/hentaisphere/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/hentaihand/hentaisphere/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/hentaihand/hentaisphere/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..4f8486963 Binary files /dev/null and b/multisrc/overrides/hentaihand/hentaisphere/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/hentaihand/hentaisphere/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/hentaihand/hentaisphere/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000..4620e2559 Binary files /dev/null and b/multisrc/overrides/hentaihand/hentaisphere/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/hentaihand/hentaisphere/res/web_hi_res_512.png b/multisrc/overrides/hentaihand/hentaisphere/res/web_hi_res_512.png new file mode 100644 index 000000000..0d9f6f3b7 Binary files /dev/null and b/multisrc/overrides/hentaihand/hentaisphere/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/hentaihand/hentaisphere/src/HentaiSphere.kt b/multisrc/overrides/hentaihand/hentaisphere/src/HentaiSphere.kt new file mode 100644 index 000000000..4a9a7109d --- /dev/null +++ b/multisrc/overrides/hentaihand/hentaisphere/src/HentaiSphere.kt @@ -0,0 +1,10 @@ +package eu.kanade.tachiyomi.extension.en.hentaisphere + +import eu.kanade.tachiyomi.multisrc.hentaihand.HentaiHand +import okhttp3.OkHttpClient + +class HentaiSphere : HentaiHand("HentaiSphere", "https://hentaisphere.com", "en", false) { + override val client: OkHttpClient = network.cloudflareClient.newBuilder() + .addInterceptor { authIntercept(it) } + .build() +} diff --git a/multisrc/overrides/hentaihand/manhwaclub/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/hentaihand/manhwaclub/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000..3a58383ce Binary files /dev/null and b/multisrc/overrides/hentaihand/manhwaclub/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/hentaihand/manhwaclub/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/hentaihand/manhwaclub/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000..1aa197386 Binary files /dev/null and b/multisrc/overrides/hentaihand/manhwaclub/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/hentaihand/manhwaclub/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/hentaihand/manhwaclub/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000..3eb9873ec Binary files /dev/null and b/multisrc/overrides/hentaihand/manhwaclub/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/hentaihand/manhwaclub/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/hentaihand/manhwaclub/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..09f5fe5fb Binary files /dev/null and b/multisrc/overrides/hentaihand/manhwaclub/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/hentaihand/manhwaclub/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/hentaihand/manhwaclub/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000..319382902 Binary files /dev/null and b/multisrc/overrides/hentaihand/manhwaclub/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/hentaihand/manhwaclub/res/web_hi_res_512.png b/multisrc/overrides/hentaihand/manhwaclub/res/web_hi_res_512.png new file mode 100644 index 000000000..2fa146c86 Binary files /dev/null and b/multisrc/overrides/hentaihand/manhwaclub/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/hentaihand/manhwaclub/src/ManhwaClub.kt b/multisrc/overrides/hentaihand/manhwaclub/src/ManhwaClub.kt new file mode 100644 index 000000000..d85893455 --- /dev/null +++ b/multisrc/overrides/hentaihand/manhwaclub/src/ManhwaClub.kt @@ -0,0 +1,10 @@ +package eu.kanade.tachiyomi.extension.en.manhwaclub + +import eu.kanade.tachiyomi.multisrc.hentaihand.HentaiHand +import okhttp3.OkHttpClient + +class ManhwaClub : HentaiHand("ManhwaClub", "https://manhwa.club", "en", true) { + override val client: OkHttpClient = network.cloudflareClient.newBuilder() + .addInterceptor { authIntercept(it) } + .build() +} diff --git a/src/all/nhentaicom/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/hentaihand/nhentaicom/res/mipmap-hdpi/ic_launcher.png similarity index 100% rename from src/all/nhentaicom/res/mipmap-hdpi/ic_launcher.png rename to multisrc/overrides/hentaihand/nhentaicom/res/mipmap-hdpi/ic_launcher.png diff --git a/src/all/nhentaicom/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/hentaihand/nhentaicom/res/mipmap-mdpi/ic_launcher.png similarity index 100% rename from src/all/nhentaicom/res/mipmap-mdpi/ic_launcher.png rename to multisrc/overrides/hentaihand/nhentaicom/res/mipmap-mdpi/ic_launcher.png diff --git a/src/all/nhentaicom/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/hentaihand/nhentaicom/res/mipmap-xhdpi/ic_launcher.png similarity index 100% rename from src/all/nhentaicom/res/mipmap-xhdpi/ic_launcher.png rename to multisrc/overrides/hentaihand/nhentaicom/res/mipmap-xhdpi/ic_launcher.png diff --git a/src/all/nhentaicom/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/hentaihand/nhentaicom/res/mipmap-xxhdpi/ic_launcher.png similarity index 100% rename from src/all/nhentaicom/res/mipmap-xxhdpi/ic_launcher.png rename to multisrc/overrides/hentaihand/nhentaicom/res/mipmap-xxhdpi/ic_launcher.png diff --git a/src/all/nhentaicom/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/hentaihand/nhentaicom/res/mipmap-xxxhdpi/ic_launcher.png similarity index 100% rename from src/all/nhentaicom/res/mipmap-xxxhdpi/ic_launcher.png rename to multisrc/overrides/hentaihand/nhentaicom/res/mipmap-xxxhdpi/ic_launcher.png diff --git a/src/all/nhentaicom/res/web_hi_res_512.png b/multisrc/overrides/hentaihand/nhentaicom/res/web_hi_res_512.png similarity index 100% rename from src/all/nhentaicom/res/web_hi_res_512.png rename to multisrc/overrides/hentaihand/nhentaicom/res/web_hi_res_512.png diff --git a/multisrc/overrides/hentaihand/nhentaicom/src/NHentaiComFactory.kt b/multisrc/overrides/hentaihand/nhentaicom/src/NHentaiComFactory.kt new file mode 100644 index 000000000..4d49e4189 --- /dev/null +++ b/multisrc/overrides/hentaihand/nhentaicom/src/NHentaiComFactory.kt @@ -0,0 +1,99 @@ +package eu.kanade.tachiyomi.extension.all.nhentaicom + +import eu.kanade.tachiyomi.multisrc.hentaihand.HentaiHand +import eu.kanade.tachiyomi.source.Source +import eu.kanade.tachiyomi.source.SourceFactory +import okhttp3.OkHttpClient + +class NHentaiComFactory : SourceFactory { + override fun createSources(): List = listOf( + // https://nhentai.com/api/languages?per_page=50 + NHentaiComAll(), + NHentaiComEn(), + NHentaiComZh(), + NHentaiComJa(), + NHentaiComNoText(), + NHentaiComEo(), + NHentaiComCeb(), + NHentaiComCs(), + NHentaiComAr(), + NHentaiComSk(), + NHentaiComMn(), + NHentaiComUk(), + NHentaiComLa(), + NHentaiComTl(), + NHentaiComEs(), + NHentaiComIt(), + NHentaiComKo(), + NHentaiComTh(), + NHentaiComPl(), + NHentaiComFr(), + NHentaiComPtBr(), + NHentaiComDe(), + NHentaiComFi(), + NHentaiComRu(), + NHentaiComHu(), + NHentaiComId(), + NHentaiComVi(), + NHentaiComNl(), + NHentaiComTr(), + NHentaiComEl(), + NHentaiComBg(), + NHentaiComSr(), + NHentaiComJv(), + NHentaiComHi(), + ) +} +abstract class NHentaiComCommon( + override val lang: String, + hhLangId: List = emptyList(), + //altLangId: Int? = null +) : HentaiHand("nHentai.com (unoriginal)", "https://nhentai.com", lang, false, hhLangId) { + override val client: OkHttpClient = network.cloudflareClient.newBuilder() + .addInterceptor { authIntercept(it) } + .build() +} + +class NHentaiComAll : NHentaiComCommon("all") { + override val id: Long = 9165839893600661480 +} + +class NHentaiComJa : NHentaiComCommon("ja", listOf(1, 29)) +class NHentaiComEn : NHentaiComCommon("en", listOf(2, 27)) { + override val id: Long = 5591830863732393712 +} +class NHentaiComZh : NHentaiComCommon("zh", listOf(3, 50)) +class NHentaiComBg : NHentaiComCommon("bg", listOf(4)) +class NHentaiComCeb : NHentaiComCommon("ceb", listOf(5, 44)) +class NHentaiComNoText : NHentaiComCommon("other", listOf(6)) { + override val id: Long = 5817327335315373850 +} +class NHentaiComTl : NHentaiComCommon("tl", listOf(7, 55)) +class NHentaiComAr : NHentaiComCommon("ar", listOf(8, 49)) +class NHentaiComEl : NHentaiComCommon("el", listOf(9)) +class NHentaiComSr : NHentaiComCommon("sr", listOf(10)) +class NHentaiComJv : NHentaiComCommon("jv", listOf(11, 51)) +class NHentaiComUk : NHentaiComCommon("uk", listOf(12, 46)) +class NHentaiComTr : NHentaiComCommon("tr", listOf(13, 41)) +class NHentaiComFi : NHentaiComCommon("fi", listOf(14, 54)) +class NHentaiComLa : NHentaiComCommon("la", listOf(15)) +class NHentaiComMn : NHentaiComCommon("mn", listOf(16)) +class NHentaiComEo : NHentaiComCommon("eo", listOf(17, 47)) +class NHentaiComSk : NHentaiComCommon("sk", listOf(18)) +class NHentaiComCs : NHentaiComCommon("cs", listOf(19, 52)) { + override val id: Long = 1144495813995437124 +} +class NHentaiComKo : NHentaiComCommon("ko", listOf(30, 39)) +class NHentaiComRu : NHentaiComCommon("ru", listOf(31)) +class NHentaiComIt : NHentaiComCommon("it", listOf(32)) +class NHentaiComEs : NHentaiComCommon("es", listOf(33, 37)) +class NHentaiComPtBr : NHentaiComCommon("pt-BR", listOf(34)) +class NHentaiComTh : NHentaiComCommon("th", listOf(35, 40)) +class NHentaiComFr : NHentaiComCommon("fr", listOf(36)) +class NHentaiComId : NHentaiComCommon("id", listOf(38)) +class NHentaiComVi : NHentaiComCommon("vi", listOf(42)) +class NHentaiComDe : NHentaiComCommon("de", listOf(43)) +class NHentaiComPl : NHentaiComCommon("pl", listOf(45)) +class NHentaiComHu : NHentaiComCommon("hu", listOf(48)) +class NHentaiComNl : NHentaiComCommon("nl", listOf(53)) +class NHentaiComHi : NHentaiComCommon("hi", listOf(56)) diff --git a/src/en/readmanhwa/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/hentaihand/readmanhwa/res/mipmap-hdpi/ic_launcher.png similarity index 100% rename from src/en/readmanhwa/res/mipmap-hdpi/ic_launcher.png rename to multisrc/overrides/hentaihand/readmanhwa/res/mipmap-hdpi/ic_launcher.png diff --git a/src/en/readmanhwa/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/hentaihand/readmanhwa/res/mipmap-mdpi/ic_launcher.png similarity index 100% rename from src/en/readmanhwa/res/mipmap-mdpi/ic_launcher.png rename to multisrc/overrides/hentaihand/readmanhwa/res/mipmap-mdpi/ic_launcher.png diff --git a/src/en/readmanhwa/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/hentaihand/readmanhwa/res/mipmap-xhdpi/ic_launcher.png similarity index 100% rename from src/en/readmanhwa/res/mipmap-xhdpi/ic_launcher.png rename to multisrc/overrides/hentaihand/readmanhwa/res/mipmap-xhdpi/ic_launcher.png diff --git a/src/en/readmanhwa/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/hentaihand/readmanhwa/res/mipmap-xxhdpi/ic_launcher.png similarity index 100% rename from src/en/readmanhwa/res/mipmap-xxhdpi/ic_launcher.png rename to multisrc/overrides/hentaihand/readmanhwa/res/mipmap-xxhdpi/ic_launcher.png diff --git a/src/en/readmanhwa/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/hentaihand/readmanhwa/res/mipmap-xxxhdpi/ic_launcher.png similarity index 100% rename from src/en/readmanhwa/res/mipmap-xxxhdpi/ic_launcher.png rename to multisrc/overrides/hentaihand/readmanhwa/res/mipmap-xxxhdpi/ic_launcher.png diff --git a/src/en/readmanhwa/res/web_hi_res_512.png b/multisrc/overrides/hentaihand/readmanhwa/res/web_hi_res_512.png similarity index 100% rename from src/en/readmanhwa/res/web_hi_res_512.png rename to multisrc/overrides/hentaihand/readmanhwa/res/web_hi_res_512.png diff --git a/multisrc/overrides/hentaihand/readmanhwa/src/ReadManhwa.kt b/multisrc/overrides/hentaihand/readmanhwa/src/ReadManhwa.kt new file mode 100644 index 000000000..031172fd8 --- /dev/null +++ b/multisrc/overrides/hentaihand/readmanhwa/src/ReadManhwa.kt @@ -0,0 +1,10 @@ +package eu.kanade.tachiyomi.extension.en.readmanhwa + +import eu.kanade.tachiyomi.multisrc.hentaihand.HentaiHand +import okhttp3.OkHttpClient + +class ReadManhwa : HentaiHand("ReadManhwa", "https://readmanhwa.com", "en", true) { + override val client: OkHttpClient = network.cloudflareClient.newBuilder() + .addInterceptor { authIntercept(it) } + .build() +} diff --git a/src/all/hentaihand/src/eu/kanade/tachiyomi/extension/all/hentaihand/HentaiHand.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/hentaihand/HentaiHand.kt similarity index 70% rename from src/all/hentaihand/src/eu/kanade/tachiyomi/extension/all/hentaihand/HentaiHand.kt rename to multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/hentaihand/HentaiHand.kt index 14142ed31..5eb538f99 100644 --- a/src/all/hentaihand/src/eu/kanade/tachiyomi/extension/all/hentaihand/HentaiHand.kt +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/hentaihand/HentaiHand.kt @@ -1,9 +1,10 @@ -package eu.kanade.tachiyomi.extension.all.hentaihand +package eu.kanade.tachiyomi.multisrc.hentaihand import android.app.Application import android.content.SharedPreferences import android.text.InputType import android.widget.Toast +import androidx.preference.PreferenceScreen import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.POST import eu.kanade.tachiyomi.network.asObservableSuccess @@ -20,13 +21,13 @@ import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.buildJsonObject import kotlinx.serialization.json.int import kotlinx.serialization.json.jsonArray +import kotlinx.serialization.json.jsonNull import kotlinx.serialization.json.jsonObject import kotlinx.serialization.json.jsonPrimitive import kotlinx.serialization.json.put import okhttp3.HttpUrl.Companion.toHttpUrlOrNull import okhttp3.Interceptor import okhttp3.MediaType.Companion.toMediaTypeOrNull -import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.Response @@ -36,22 +37,19 @@ import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import uy.kohesive.injekt.injectLazy import java.text.SimpleDateFormat +import java.util.Calendar import java.util.Locale abstract class HentaiHand( + override val name: String, + override val baseUrl: String, override val lang: String, - private val hhLangId: Int? = null, - extraName: String = "" + private val chapters: Boolean, + private val hhLangId: List = emptyList(), ) : ConfigurableSource, HttpSource() { - override val baseUrl: String = "https://hentaihand.com" - override val name: String = "HentaiHand$extraName" override val supportsLatest = true - override val client: OkHttpClient = network.cloudflareClient.newBuilder() - .addInterceptor { authIntercept(it) } - .build() - private val json: Json by injectLazy() private fun slugToUrl(json: JsonObject) = json["slug"]!!.jsonPrimitive.content.prependIndent("/en/comic/") @@ -73,7 +71,7 @@ abstract class HentaiHand( SManga.create().apply { url = slugToUrl(obj) title = obj["title"]!!.jsonPrimitive.content - thumbnail_url = obj["thumb_url"]!!.jsonPrimitive.content + thumbnail_url = obj["image_url"]!!.jsonPrimitive.content } } val hasNextPage = jsonResponse.jsonObject["next_page_url"]!!.jsonPrimitive.content.isNotEmpty() @@ -81,8 +79,16 @@ abstract class HentaiHand( } override fun popularMangaRequest(page: Int): Request { - val url = "$baseUrl/api/comics?page=$page&sort=popularity&order=desc&duration=all" - return GET(if (hhLangId == null) url else ("$url&languages=$hhLangId")) + val url = "$baseUrl/api/comics".toHttpUrlOrNull()!!.newBuilder() + .addQueryParameter("page", page.toString()) + .addQueryParameter("sort", "popularity") + .addQueryParameter("order", "desc") + .addQueryParameter("duration", "all") + hhLangId.forEachIndexed() {index, it -> + url.addQueryParameter("languages[${-index - 1}]", it.toString()) + } + // if (altLangId != null) url.addQueryParameter("languages", altLangId.toString()) + return GET(url.toString()) } // Latest @@ -90,8 +96,15 @@ abstract class HentaiHand( override fun latestUpdatesParse(response: Response): MangasPage = popularMangaParse(response) override fun latestUpdatesRequest(page: Int): Request { - val url = "$baseUrl/api/comics?page=$page&sort=uploaded_at&order=desc&duration=week" - return GET(if (hhLangId == null) url else ("$url&languages=$hhLangId")) + val url = "$baseUrl/api/comics".toHttpUrlOrNull()!!.newBuilder() + .addQueryParameter("page", page.toString()) + .addQueryParameter("sort", "uploaded_at") + .addQueryParameter("order", "desc") + .addQueryParameter("duration", "all") + hhLangId.forEachIndexed() {index, it -> + url.addQueryParameter("languages[${-index - 1}]", it.toString()) + } + return GET(url.toString()) } // Search @@ -119,8 +132,9 @@ abstract class HentaiHand( .addQueryParameter("page", page.toString()) .addQueryParameter("q", query) - if (hhLangId != null) - url.addQueryParameter("languages", hhLangId.toString()) + hhLangId.forEachIndexed() {index, it -> + url.addQueryParameter("languages[${-index - 1}]", it.toString()) + } (if (filters.isEmpty()) getFilterList() else filters).forEach { filter -> when (filter) { @@ -130,12 +144,15 @@ abstract class HentaiHand( is AttributesGroupFilter -> filter.state.forEach { if (it.state) url.addQueryParameter("attributes", it.value) } + is StatusGroupFilter -> filter.state.forEach { + if (it.state) url.addQueryParameter("statuses", it.value) + } is LookupFilter -> { filter.state.split(",").map { it.trim() }.filter { it.isNotBlank() }.map { lookupFilterId(it, filter.uri) ?: throw Exception("No ${filter.singularName} \"$it\" was found") - }.forEach { - if (!(filter.uri == "languages" && it == hhLangId)) - url.addQueryParameter(filter.uri, it.toString()) + }.forEachIndexed() {index, it -> + if (!(filter.uri == "languages" && hhLangId.contains(it))) + url.addQueryParameter(filter.uri + "[$index]", it.toString()) } } else -> {} @@ -163,19 +180,27 @@ abstract class HentaiHand( return SManga.create().apply { url = slugToUrl(obj) title = obj["title"]!!.jsonPrimitive.content - thumbnail_url = obj["thumb_url"]!!.jsonPrimitive.content + thumbnail_url = obj["image_url"]!!.jsonPrimitive.content artist = jsonArrayToString("artists", obj) author = jsonArrayToString("authors", obj) ?: artist genre = listOfNotNull(jsonArrayToString("tags", obj), jsonArrayToString("relationships", obj)).joinToString(", ") - status = SManga.COMPLETED + status = when (obj["status"]!!.jsonPrimitive.content) { + "complete" -> SManga.COMPLETED + "ongoing" -> SManga.ONGOING + "onhold" -> SManga.ONGOING + "canceled" -> SManga.COMPLETED + else -> SManga.COMPLETED + } + + description = listOf( Pair("Alternative Title", obj["alternative_title"]!!.jsonPrimitive.content), Pair("Groups", jsonArrayToString("groups", obj)), Pair("Description", obj["description"]!!.jsonPrimitive.content), Pair("Pages", obj["pages"]!!.jsonPrimitive.content), - Pair("Category", obj["category"]!!.jsonObject["name"]!!.jsonPrimitive.content), - Pair("Language", obj["language"]!!.jsonObject["name"]!!.jsonPrimitive.content), + Pair("Category", try { obj["category"]!!.jsonObject["name"]!!.jsonPrimitive.content } catch (_: Exception) { null }), + Pair("Language", try { obj["language"]!!.jsonObject["name"]!!.jsonPrimitive.content } catch (_: Exception) { null }), Pair("Parodies", jsonArrayToString("parodies", obj)), Pair("Characters", jsonArrayToString("characters", obj)) ).filter { !it.second.isNullOrEmpty() }.joinToString("\n\n") { "${it.first}: ${it.second}" } @@ -184,24 +209,59 @@ abstract class HentaiHand( // Chapters - override fun chapterListRequest(manga: SManga): Request = mangaDetailsApiRequest(manga) + private fun chapterListApiRequest(manga: SManga): Request { + val slug = manga.url.removePrefix("/en/comic/") + return if (chapters) { + GET("$baseUrl/api/comics/$slug/chapters") + } else { + GET("$baseUrl/api/comics/$slug") + } + } + + override fun chapterListRequest(manga: SManga): Request = chapterListApiRequest(manga) override fun chapterListParse(response: Response): List { - val obj = json.parseToJsonElement(response.body!!.string()).jsonObject - return listOf( - SChapter.create().apply { - url = "/en/comic/${obj["slug"]!!.jsonPrimitive.content}/reader/1" - name = "Chapter" - date_upload = DATE_FORMAT.parse(obj["uploaded_at"]!!.jsonPrimitive.content)?.time ?: 0 - chapter_number = 1f + val slug = response.request.url.toString().substringAfter("/api/comics/").removeSuffix("/chapters") + return if (chapters) { + val array = json.parseToJsonElement(response.body!!.string()).jsonArray + array.map { + SChapter.create().apply { + url = "$slug/${it.jsonObject["slug"]!!.jsonPrimitive.content}" + name = it.jsonObject["name"]!!.jsonPrimitive.content + val date = it.jsonObject["added_at"]!!.jsonPrimitive.content + date_upload = if (date.contains("day")) { + Calendar.getInstance().apply { + add(Calendar.DATE, date.filter { it.isDigit() }.toInt() * -1) + }.timeInMillis + } else { + DATE_FORMAT.parse(it.jsonObject["added_at"]!!.jsonPrimitive.content)?.time ?: 0 + } + } } - ) + } else { + val obj = json.parseToJsonElement(response.body!!.string()).jsonObject + listOf( + SChapter.create().apply { + url = obj["slug"]!!.jsonPrimitive.content + name = "Chapter" + val date = obj.jsonObject["uploaded_at"]!!.jsonPrimitive.content + date_upload = if (date.contains("day")) { + Calendar.getInstance().apply { + add(Calendar.DATE, date.filter { it.isDigit() }.toInt() * -1) + }.timeInMillis + } else { + DATE_FORMAT.parse(obj.jsonObject["uploaded_at"]!!.jsonPrimitive.content)?.time ?: 0 + } + chapter_number = 1f + } + ) + } } // Pages override fun pageListRequest(chapter: SChapter): Request { - val slug = chapter.url.removePrefix("/en/comic/").removeSuffix("/reader/1") + val slug = chapter.url return GET("$baseUrl/api/comics/$slug/images") } @@ -217,7 +277,7 @@ abstract class HentaiHand( // Authorization - private fun authIntercept(chain: Interceptor.Chain): Response { + protected fun authIntercept(chain: Interceptor.Chain): Response { val request = chain.request() if (username.isEmpty() or password.isEmpty()) { return chain.proceed(request) @@ -264,12 +324,12 @@ abstract class HentaiHand( Injekt.get().getSharedPreferences("source_$id", 0x0000) } - override fun setupPreferenceScreen(screen: androidx.preference.PreferenceScreen) { + override fun setupPreferenceScreen(screen: PreferenceScreen) { screen.addPreference(screen.editTextPreference(USERNAME_TITLE, USERNAME_DEFAULT, username)) screen.addPreference(screen.editTextPreference(PASSWORD_TITLE, PASSWORD_DEFAULT, password, true)) } - private fun androidx.preference.PreferenceScreen.editTextPreference(title: String, default: String, value: String, isPassword: Boolean = false): androidx.preference.EditTextPreference { + private fun PreferenceScreen.editTextPreference(title: String, default: String, value: String, isPassword: Boolean = false): androidx.preference.EditTextPreference { return androidx.preference.EditTextPreference(context).apply { key = title this.title = title @@ -305,6 +365,8 @@ abstract class HentaiHand( private class DurationFilter(durationPairs: List>) : Filter.Select("Duration", durationPairs.map { it.first }.toTypedArray()) private class AttributeFilter(name: String, val value: String) : Filter.CheckBox(name) private class AttributesGroupFilter(attributePairs: List>) : Filter.Group("Attributes", attributePairs.map { AttributeFilter(it.first, it.second) }) + private class StatusFilter(name: String, val value: String) : Filter.CheckBox(name) + private class StatusGroupFilter(attributePairs: List>) : Filter.Group("Status", attributePairs.map { StatusFilter(it.first, it.second) }) private class CategoriesFilter : LookupFilter("Categories", "categories", "category") private class TagsFilter : LookupFilter("Tags", "tags", "tag") @@ -327,7 +389,8 @@ abstract class HentaiHand( CharactersFilter(), ParodiesFilter(), LanguagesFilter(), - AttributesGroupFilter(getAttributePairs()) + AttributesGroupFilter(getAttributePairs()), + StatusGroupFilter(getStatusPairs()) ) private fun getSortPairs() = listOf( @@ -357,8 +420,15 @@ abstract class HentaiHand( Pair("Rewritten", "rewritten") ) + private fun getStatusPairs() = listOf( + Pair("Ongoing", "ongoing"), + Pair("Complete", "complete"), + Pair("On Hold", "onhold"), + Pair("Canceled", "canceled") + ) + companion object { - private val DATE_FORMAT = SimpleDateFormat("yyyy-dd-MM", Locale.US) + private val DATE_FORMAT = SimpleDateFormat("yyyy-MM-dd", Locale.US) private val MEDIA_TYPE = "application/json; charset=utf-8".toMediaTypeOrNull() private const val USERNAME_TITLE = "Username" private const val USERNAME_DEFAULT = "" diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/hentaihand/HentaiHandGenerator.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/hentaihand/HentaiHandGenerator.kt new file mode 100644 index 000000000..87ddf1cc0 --- /dev/null +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/hentaihand/HentaiHandGenerator.kt @@ -0,0 +1,29 @@ +package eu.kanade.tachiyomi.multisrc.hentaihand + +import generator.ThemeSourceData.MultiLang +import generator.ThemeSourceData.SingleLang +import generator.ThemeSourceGenerator + +class HentaiHandGenerator : ThemeSourceGenerator { + + override val themePkg = "hentaihand" + + override val themeClass = "HentaiHand" + + override val baseVersionCode: Int = 1 + + override val sources = listOf( + MultiLang("HentaiHand", "https://hentaihand.com", listOf("all", "ja", "en", "zh", "bg", "ceb", "other", "tl", "ar", "el", "sr", "jv", "uk", "tr", "fi", "la", "mn", "eo", "sk", "cs", "ko", "ru", "it", "es", "pt-BR", "th", "fr", "id", "vi", "de", "pl", "hu", "nl", "hi"), isNsfw = true, overrideVersionCode = 5), + MultiLang("nHentai.com (unoriginal)", "https://nhentai.com", listOf("all", "ja", "en", "zh", "bg", "ceb", "other", "tl", "ar", "el", "sr", "jv", "uk", "tr", "fi", "la", "mn", "eo", "sk", "cs", "ko", "ru", "it", "es", "pt-BR", "th", "fr", "id", "vi", "de", "pl", "hu", "nl", "hi"), isNsfw = true, className = "NHentaiComFactory", overrideVersionCode = 4), + SingleLang("HentaiSphere", "https://hentaisphere.com", "en", isNsfw = true), + SingleLang("ManhwaClub", "https://manhwa.club", "en", isNsfw = true, overrideVersionCode = 3), + SingleLang("ReadManhwa", "https://readmanhwa.com", "en", isNsfw = true, overrideVersionCode = 10), + ) + + companion object { + @JvmStatic + fun main(args: Array) { + HentaiHandGenerator().createAll() + } + } +} diff --git a/src/all/hentaihand/AndroidManifest.xml b/src/all/hentaihand/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/all/hentaihand/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/src/all/hentaihand/build.gradle b/src/all/hentaihand/build.gradle deleted file mode 100644 index 68a1c123e..000000000 --- a/src/all/hentaihand/build.gradle +++ /dev/null @@ -1,13 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' -apply plugin: 'kotlinx-serialization' - -ext { - extName = 'HentaiHand' - pkgNameSuffix = 'all.hentaihand' - extClass = '.HentaiHandFactory' - extVersionCode = 4 - isNsfw = true -} - -apply from: "$rootDir/common.gradle" diff --git a/src/all/hentaihand/src/eu/kanade/tachiyomi/extension/all/hentaihand/HentaiHandFactory.kt b/src/all/hentaihand/src/eu/kanade/tachiyomi/extension/all/hentaihand/HentaiHandFactory.kt deleted file mode 100644 index b0303388f..000000000 --- a/src/all/hentaihand/src/eu/kanade/tachiyomi/extension/all/hentaihand/HentaiHandFactory.kt +++ /dev/null @@ -1,91 +0,0 @@ -package eu.kanade.tachiyomi.extension.all.hentaihand - -import eu.kanade.tachiyomi.source.Source -import eu.kanade.tachiyomi.source.SourceFactory - -class HentaiHandFactory : SourceFactory { - - override fun createSources(): List = listOf( - // https://hentaihand.com/api/languages?per_page=50 - HentaiHandOther(), - HentaiHandEn(), - HentaiHandZh(), - HentaiHandJa(), - HentaiHandNoText(), - HentaiHandEo(), - HentaiHandCeb(), - HentaiHandCs(), - HentaiHandAr(), - HentaiHandSk(), - HentaiHandMn(), - HentaiHandUk(), - HentaiHandLa(), - HentaiHandTl(), - HentaiHandEs(), - HentaiHandIt(), - HentaiHandKo(), - HentaiHandTh(), - HentaiHandPl(), - HentaiHandFr(), - HentaiHandPtBr(), - HentaiHandDe(), - HentaiHandFi(), - HentaiHandRu(), - HentaiHandSv(), - HentaiHandHu(), - HentaiHandId(), - HentaiHandVi(), - HentaiHandDa(), - HentaiHandRo(), - HentaiHandEt(), - HentaiHandNl(), - HentaiHandCa(), - HentaiHandTr(), - HentaiHandEl(), - HentaiHandNo(), - HentaiHandSq(), - HentaiHandBg(), - ) -} - -class HentaiHandOther : HentaiHand("all", extraName = " (Unfiltered)") -class HentaiHandEn : HentaiHand("en", 1) -class HentaiHandZh : HentaiHand("zh", 2) -class HentaiHandJa : HentaiHand("ja", 3) -class HentaiHandNoText : HentaiHand("other", 4, extraName = " (Text Cleaned)") -class HentaiHandEo : HentaiHand("eo", 5) -class HentaiHandCeb : HentaiHand("ceb", 6) -class HentaiHandCs : HentaiHand("cs", 7) -class HentaiHandAr : HentaiHand("ar", 8) -class HentaiHandSk : HentaiHand("sk", 9) -class HentaiHandMn : HentaiHand("mn", 10) -class HentaiHandUk : HentaiHand("uk", 11) -class HentaiHandLa : HentaiHand("la", 12) -class HentaiHandTl : HentaiHand("tl", 13) -class HentaiHandEs : HentaiHand("es", 14) -class HentaiHandIt : HentaiHand("it", 15) -class HentaiHandKo : HentaiHand("ko", 16) -class HentaiHandTh : HentaiHand("th", 17) -class HentaiHandPl : HentaiHand("pl", 18) -class HentaiHandFr : HentaiHand("fr", 19) -class HentaiHandPtBr : HentaiHand("pt-BR", 20) { - // Hardcode the id because the language wasn't specific. - override val id: Long = 2516244587139644000 -} -class HentaiHandDe : HentaiHand("de", 21) -class HentaiHandFi : HentaiHand("fi", 22) -class HentaiHandRu : HentaiHand("ru", 23) -class HentaiHandSv : HentaiHand("sv", 24) -class HentaiHandHu : HentaiHand("hu", 25) -class HentaiHandId : HentaiHand("id", 26) -class HentaiHandVi : HentaiHand("vi", 27) -class HentaiHandDa : HentaiHand("da", 28) -class HentaiHandRo : HentaiHand("ro", 29) -class HentaiHandEt : HentaiHand("et", 30) -class HentaiHandNl : HentaiHand("nl", 31) -class HentaiHandCa : HentaiHand("ca", 32) -class HentaiHandTr : HentaiHand("tr", 33) -class HentaiHandEl : HentaiHand("el", 34) -class HentaiHandNo : HentaiHand("no", 35) -class HentaiHandSq : HentaiHand("sq", 1501) -class HentaiHandBg : HentaiHand("bg", 1502) diff --git a/src/all/nhentaicom/AndroidManifest.xml b/src/all/nhentaicom/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/all/nhentaicom/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/src/all/nhentaicom/build.gradle b/src/all/nhentaicom/build.gradle deleted file mode 100644 index 78c754128..000000000 --- a/src/all/nhentaicom/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'nHentai.com (unoriginal)' - pkgNameSuffix = 'all.nhentaicom' - extClass = '.NHentaiComFactory' - extVersionCode = 3 - isNsfw = true -} - -apply from: "$rootDir/common.gradle" diff --git a/src/all/nhentaicom/src/eu/kanade/tachiyomi/extension/all/nhentaicom/NHentaiCom.kt b/src/all/nhentaicom/src/eu/kanade/tachiyomi/extension/all/nhentaicom/NHentaiCom.kt deleted file mode 100644 index 7cbafb03a..000000000 --- a/src/all/nhentaicom/src/eu/kanade/tachiyomi/extension/all/nhentaicom/NHentaiCom.kt +++ /dev/null @@ -1,231 +0,0 @@ -package eu.kanade.tachiyomi.extension.all.nhentaicom - -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.asObservableSuccess -import eu.kanade.tachiyomi.source.model.Filter -import eu.kanade.tachiyomi.source.model.FilterList -import eu.kanade.tachiyomi.source.model.MangasPage -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.HttpSource -import kotlinx.serialization.json.Json -import kotlinx.serialization.json.jsonArray -import kotlinx.serialization.json.jsonObject -import kotlinx.serialization.json.jsonPrimitive -import okhttp3.Headers -import okhttp3.HttpUrl.Companion.toHttpUrlOrNull -import okhttp3.Request -import okhttp3.Response -import rx.Observable -import uy.kohesive.injekt.injectLazy - -class NHentaiCom(override val lang: String) : HttpSource() { - - override val name = when (lang) { - "other" -> "nHentai.com (unoriginal)(Text Cleaned)" - "all" -> "nHentai.com (unoriginal)(Unfiltered)" - else -> "nHentai.com (unoriginal)" - } - - override val id = when (lang) { - "en" -> 5591830863732393712 - "cs" -> 1144495813995437124 - else -> super.id - } - - override val baseUrl = "https://nhentai.com" - - private val langId = toLangId(lang) - - override val supportsLatest = true - - private val json: Json by injectLazy() - - private fun toLangId(langCode: String): String { - return when (langCode) { - "en" -> "1" - "zh" -> "2" - "ja" -> "3" - "other" -> "4" - "cs" -> "5" - "ar" -> "6" - "sk" -> "7" - "eo" -> "8" - else -> "" - } - } - - override fun headersBuilder(): Headers.Builder = Headers.Builder() - .add("User-Agent", "Mozilla/5.0 (Windows NT 6.3; WOW64)") - - private fun parseMangaFromJson(response: Response): MangasPage { - val jsonRaw = response.body!!.string() - val jsonResult = json.parseToJsonElement(jsonRaw).jsonObject - - val mangas = jsonResult["data"]!!.jsonArray.map { jsonEl -> - SManga.create().apply { - val jsonObj = jsonEl.jsonObject - title = jsonObj["title"]!!.jsonPrimitive.content - thumbnail_url = jsonObj["image_url"]!!.jsonPrimitive.content - url = jsonObj["slug"]!!.jsonPrimitive.content - } - } - - return MangasPage(mangas, jsonResult["current_page"]!!.jsonPrimitive.content.toInt() < jsonResult["last_page"]!!.jsonPrimitive.content.toInt()) - } - - private fun getMangaUrl(page: Int, sort: String): String { - val url = "$baseUrl/api/comics".toHttpUrlOrNull()!!.newBuilder() - if (langId.isNotBlank()) { - url.setQueryParameter("languages[]", langId) - } - url.setQueryParameter("page", "$page") - url.setQueryParameter("sort", sort) - url.setQueryParameter("nsfw", "false") - return url.toString() - } - - // Popular - - override fun popularMangaRequest(page: Int): Request { - return GET(getMangaUrl(page, "popularity"), headers) - } - - override fun popularMangaParse(response: Response): MangasPage = parseMangaFromJson(response) - - // Latest - - override fun latestUpdatesRequest(page: Int): Request { - return GET(getMangaUrl(page, "uploaded_at"), headers) - } - - override fun latestUpdatesParse(response: Response): MangasPage = parseMangaFromJson(response) - - // Search - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val url = "$baseUrl/api/comics".toHttpUrlOrNull()!!.newBuilder() - .addQueryParameter("per_page", "18") - .addQueryParameter("page", page.toString()) - .addQueryParameter("q", query) - .addQueryParameter("nsfw", "false") - - if (langId.isNotBlank()) { - url.setQueryParameter("languages[]", langId) - } - url.setQueryParameter("page", "$page") - url.setQueryParameter("nsfw", "false") - - filters.forEach { filter -> - when (filter) { - is SortFilter -> url.addQueryParameter("sort", filter.toUriPart()) - is DurationFilter -> url.addQueryParameter("duration", filter.toUriPart()) - is SortOrderFilter -> url.addQueryParameter("order", filter.toUriPart()) - } - } - return GET(url.toString(), headers) - } - - override fun searchMangaParse(response: Response): MangasPage = parseMangaFromJson(response) - - // Details - - // Workaround to allow "Open in browser" to use the real URL - override fun fetchMangaDetails(manga: SManga): Observable = - client.newCall(apiMangaDetailsRequest(manga)).asObservableSuccess() - .map { mangaDetailsParse(it).apply { initialized = true } } - - // Return the real URL for "Open in browser" - override fun mangaDetailsRequest(manga: SManga) = GET("$baseUrl/en/comic/${manga.url}", headers) - - private fun apiMangaDetailsRequest(manga: SManga): Request { - return GET("$baseUrl/api/comics/${manga.url}", headers) - } - - override fun mangaDetailsParse(response: Response): SManga { - val jsonRaw = response.body!!.string() - val jsonObject = json.parseToJsonElement(jsonRaw).jsonObject - - return SManga.create().apply { - description = jsonObject["description"]!!.jsonPrimitive.content - status = SManga.COMPLETED - thumbnail_url = jsonObject["image_url"]!!.jsonPrimitive.content - genre = runCatching { jsonObject["tags"]!!.jsonArray.joinToString { it.jsonObject["name"]!!.jsonPrimitive.content } }.getOrNull() - artist = runCatching { jsonObject["artists"]!!.jsonArray.joinToString { it.jsonObject["name"]!!.jsonPrimitive.content } }.getOrNull() - author = runCatching { jsonObject["authors"]!!.jsonArray.joinToString { it.jsonObject["name"]!!.jsonPrimitive.content } }.getOrNull() - } - } - - // Chapters - - override fun fetchChapterList(manga: SManga): Observable> { - return Observable.just( - listOf( - SChapter.create().apply { - name = "chapter" - url = manga.url - } - ) - ) - } - - override fun chapterListRequest(manga: SManga): Request = throw Exception("not used") - - override fun chapterListParse(response: Response): List = throw UnsupportedOperationException("Not used") - - // Pages - - override fun pageListRequest(chapter: SChapter): Request { - return GET("$baseUrl/api/comics/${chapter.url}/images", headers) - } - - override fun pageListParse(response: Response): List { - return json.parseToJsonElement(response.body!!.string()).jsonObject["images"]!!.jsonArray.mapIndexed { i, jsonEl -> - val jsonObj = jsonEl.jsonObject - Page(i, "", jsonObj["source_url"]!!.jsonPrimitive.content) - } - } - - override fun imageUrlParse(response: Response): String = throw UnsupportedOperationException("Not used") - - // Filters - - override fun getFilterList() = FilterList( - DurationFilter(getDurationList()), - SortFilter(getSortList()), - SortOrderFilter(getSortOrder()) - ) - - private class DurationFilter(pairs: Array>) : UriPartFilter("Duration", pairs) - - private class SortFilter(pairs: Array>) : UriPartFilter("Sorted by", pairs) - - private class SortOrderFilter(pairs: Array>) : UriPartFilter("Order", pairs) - - open class UriPartFilter(displayName: String, private val vals: Array>) : - Filter.Select(displayName, vals.map { it.first }.toTypedArray()) { - fun toUriPart() = vals[state].second - } - - private fun getSortOrder() = arrayOf( - Pair("Descending", "desc"), - Pair("Ascending", "asc"), - ) - - private fun getDurationList() = arrayOf( - Pair("All time", "all"), - Pair("Year", "year"), - Pair("Month", "month"), - Pair("Week", "week"), - Pair("Day", "day") - ) - - private fun getSortList() = arrayOf( - Pair("Upload date", "uploaded_at"), - Pair("Title", "title"), - Pair("Pages", "pages"), - Pair("Favorites", "favorites"), - Pair("Popularity", "popularity"), - ) -} diff --git a/src/all/nhentaicom/src/eu/kanade/tachiyomi/extension/all/nhentaicom/NHentaiComFactory.kt b/src/all/nhentaicom/src/eu/kanade/tachiyomi/extension/all/nhentaicom/NHentaiComFactory.kt deleted file mode 100644 index 718b7d4bf..000000000 --- a/src/all/nhentaicom/src/eu/kanade/tachiyomi/extension/all/nhentaicom/NHentaiComFactory.kt +++ /dev/null @@ -1,12 +0,0 @@ -package eu.kanade.tachiyomi.extension.all.nhentaicom - -import eu.kanade.tachiyomi.source.Source -import eu.kanade.tachiyomi.source.SourceFactory - -class NHentaiComFactory : SourceFactory { - override fun createSources(): List = languages.map { NHentaiCom(it) } -} - -private val languages = listOf( - "all", "en", "zh", "ja", "other", "eo", "cs", "ar", "sk" -) diff --git a/src/en/readmanhwa/AndroidManifest.xml b/src/en/readmanhwa/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/en/readmanhwa/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/src/en/readmanhwa/build.gradle b/src/en/readmanhwa/build.gradle deleted file mode 100644 index 1000ccc8c..000000000 --- a/src/en/readmanhwa/build.gradle +++ /dev/null @@ -1,13 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' -apply plugin: 'kotlinx-serialization' - -ext { - extName = 'ReadManhwa' - pkgNameSuffix = 'en.readmanhwa' - extClass = '.ReadManhwa' - extVersionCode = 9 - isNsfw = true -} - -apply from: "$rootDir/common.gradle" diff --git a/src/en/readmanhwa/src/eu/kanade/tachiyomi/extension/en/readmanhwa/ReadManhwa.kt b/src/en/readmanhwa/src/eu/kanade/tachiyomi/extension/en/readmanhwa/ReadManhwa.kt deleted file mode 100644 index e775756ce..000000000 --- a/src/en/readmanhwa/src/eu/kanade/tachiyomi/extension/en/readmanhwa/ReadManhwa.kt +++ /dev/null @@ -1,390 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.readmanhwa - -import android.app.Application -import android.content.SharedPreferences -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.asObservableSuccess -import eu.kanade.tachiyomi.source.ConfigurableSource -import eu.kanade.tachiyomi.source.model.Filter -import eu.kanade.tachiyomi.source.model.FilterList -import eu.kanade.tachiyomi.source.model.MangasPage -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.HttpSource -import kotlinx.serialization.decodeFromString -import kotlinx.serialization.json.Json -import kotlinx.serialization.json.JsonArray -import kotlinx.serialization.json.JsonObject -import kotlinx.serialization.json.contentOrNull -import kotlinx.serialization.json.int -import kotlinx.serialization.json.jsonArray -import kotlinx.serialization.json.jsonObject -import kotlinx.serialization.json.jsonPrimitive -import okhttp3.Headers -import okhttp3.HttpUrl.Companion.toHttpUrlOrNull -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import rx.Observable -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get -import uy.kohesive.injekt.injectLazy -import java.text.SimpleDateFormat -import java.util.Calendar -import java.util.Locale - -class ReadManhwa : ConfigurableSource, HttpSource() { - - override val name = "ReadManhwa" - - override val baseUrl = "https://www.readmanhwa.com" - - override val lang = "en" - - override val supportsLatest = true - - override fun headersBuilder(): Headers.Builder = headersBuilder(true) - - private fun headersBuilder(enableNsfw: Boolean) = Headers.Builder() - .add("User-Agent", "Mozilla/5.0 (Windows NT 6.3; WOW64)") - .add("X-NSFW", enableNsfw.toString()) - - private val preferences: SharedPreferences by lazy { - Injekt.get().getSharedPreferences("source_$id", 0x0000) - } - - override val client: OkHttpClient = network.cloudflareClient - - private val json: Json by injectLazy() - - private fun parseMangaFromJson(response: Response): MangasPage { - val jsonObject = json.decodeFromString(response.body!!.string()) - - val mangas = jsonObject["data"]!!.jsonArray.map { json -> - SManga.create().apply { - title = json.jsonObject["title"]!!.jsonPrimitive.content - thumbnail_url = json.jsonObject["image_url"]!!.jsonPrimitive.content - url = json.jsonObject["slug"]!!.jsonPrimitive.content - } - } - - return MangasPage(mangas, jsonObject["current_page"]!!.jsonPrimitive.int < jsonObject["last_page"]!!.jsonPrimitive.int) - } - private fun getMangaUrl(url: String): String { - return url.toHttpUrlOrNull()!!.newBuilder() - .setQueryParameter("nsfw", isNSFWEnabledInPref().toString()).toString() - } - - // Popular - - override fun popularMangaRequest(page: Int): Request { - return GET(getMangaUrl("$baseUrl/api/comics?per_page=36&page=$page&q=&sort=popularity&order=desc&duration=all"), headers) - } - - override fun popularMangaParse(response: Response): MangasPage = parseMangaFromJson(response) - - // Latest - - override fun latestUpdatesRequest(page: Int): Request { - return GET(getMangaUrl("$baseUrl/api/comics?per_page=36&page=$page&q=&sort=uploaded_at&order=desc&duration=day"), headers) - } - - override fun latestUpdatesParse(response: Response): MangasPage = parseMangaFromJson(response) - - // Search - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val enableNsfw = (filters.find { it is NSFWFilter } as? Filter.CheckBox)?.state ?: true - - val url = "$baseUrl/api/comics".toHttpUrlOrNull()!!.newBuilder() - .addQueryParameter("per_page", "36") - .addQueryParameter("page", page.toString()) - .addQueryParameter("q", query) - .addQueryParameter("nsfw", enableNsfw.toString()) - - filters.forEach { filter -> - when (filter) { - is GenreFilter -> { - - val genreInclude = mutableListOf() - filter.state.forEach { - if (it.state == 1) { - genreInclude.add(it.id) - } - } - if (genreInclude.isNotEmpty()) { - genreInclude.forEach { genre -> - url.addQueryParameter("tags[]", genre) - } - } - } - is StatusFilter -> { - val statusInclude = mutableListOf() - filter.state.forEach { - if (it.state == 1) { - statusInclude.add(it.id) - } - } - if (statusInclude.isNotEmpty()) { - statusInclude.forEach { status -> - url.addQueryParameter("statuses[]", status) - } - } - } - is OrderBy -> { - val orderby = if (filter.state!!.ascending) "asc" else "desc" - val sort = arrayOf("uploaded_at", "title", "pages", "favorites", "popularity")[filter.state!!.index] - url.addQueryParameter("sort", sort) - url.addQueryParameter("order", orderby) - } - is DurationFilter -> url.addQueryParameter("duration", filter.toUriPart()) - } - } - return GET(url.toString(), headersBuilder(enableNsfw).build()) - } - - override fun searchMangaParse(response: Response): MangasPage = parseMangaFromJson(response) - - // Details - - // Workaround to allow "Open in browser" to use the real URL - override fun fetchMangaDetails(manga: SManga): Observable = - client.newCall(apiMangaDetailsRequest(manga)).asObservableSuccess() - .map { mangaDetailsParse(it).apply { initialized = true } } - - // Return the real URL for "Open in browser" - override fun mangaDetailsRequest(manga: SManga) = GET(getMangaUrl("$baseUrl/en/webtoon/${manga.url}"), headers) - - private fun apiMangaDetailsRequest(manga: SManga): Request { - return GET(getMangaUrl("$baseUrl/api/comics/${manga.url}"), headers) - } - - override fun mangaDetailsParse(response: Response): SManga { - val jsonObject = json.decodeFromString(response.body!!.string()) - - return SManga.create().apply { - description = jsonObject["description"]!!.jsonPrimitive.contentOrNull - status = jsonObject["status"]!!.jsonPrimitive.contentOrNull.toStatus() - thumbnail_url = jsonObject["image_url"]!!.jsonPrimitive.contentOrNull - genre = try { jsonObject["tags"]!!.jsonArray.joinToString { it.jsonObject["name"]!!.jsonPrimitive.content } } catch (_: Exception) { null } - artist = try { jsonObject["artists"]!!.jsonArray.joinToString { it.jsonObject["name"]!!.jsonPrimitive.content } } catch (_: Exception) { null } - author = try { jsonObject["authors"]!!.jsonArray.joinToString { it.jsonObject["name"]!!.jsonPrimitive.content } } catch (_: Exception) { null } - } - } - - private fun String?.toStatus() = when { - this == null -> SManga.UNKNOWN - this.contains("ongoing", ignoreCase = true) -> SManga.ONGOING - this.contains("complete", ignoreCase = true) -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - - // Chapters - - override fun fetchChapterList(manga: SManga): Observable> { - return client.newCall(chapterListRequest(manga)) - .asObservableSuccess() - .map { response -> - chapterListParse(response, manga.url) - } - } - - override fun chapterListRequest(manga: SManga): Request { - return GET(getMangaUrl("$baseUrl/api/comics/${manga.url}/chapters"), headers) - } - - private fun chapterListParse(response: Response, titleSlug: String): List { - return json.decodeFromString(response.body!!.string()).map { json -> - SChapter.create().apply { - name = json.jsonObject["name"]!!.jsonPrimitive.content - url = "$titleSlug/${json.jsonObject["slug"]!!.jsonPrimitive.content}" - date_upload = json.jsonObject["added_at"]!!.jsonPrimitive.content.let { dateString -> - if (dateString.contains("ago")) { - val trimmedDate = dateString.substringBefore(" ago").removeSuffix("s").split(" ") - val calendar = Calendar.getInstance() - when (trimmedDate[1]) { - "day" -> calendar.apply { add(Calendar.DAY_OF_MONTH, -trimmedDate[0].toInt()) }.timeInMillis - "hour" -> calendar.apply { add(Calendar.HOUR_OF_DAY, -trimmedDate[0].toInt()) }.timeInMillis - "minute" -> calendar.apply { add(Calendar.MINUTE, -trimmedDate[0].toInt()) }.timeInMillis - "second" -> calendar.apply { add(Calendar.SECOND, -trimmedDate[0].toInt()) }.timeInMillis - else -> 0L - } - } else { - SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).parse(dateString)?.time ?: 0 - } - } - } - } - } - - override fun chapterListParse(response: Response): List = throw UnsupportedOperationException("Not used") - - // Pages - - override fun pageListRequest(chapter: SChapter): Request { - return GET(getMangaUrl("$baseUrl/api/comics/${chapter.url}/images"), headers) - } - - override fun pageListParse(response: Response): List { - return json.decodeFromString(response.body!!.string())["images"]!!.jsonArray.mapIndexed { i, json -> - Page(i, "", json.jsonObject["source_url"]!!.jsonPrimitive.content) - } - } - - override fun imageUrlParse(response: Response): String = throw UnsupportedOperationException("Not used") - - // Filters - - override fun getFilterList() = FilterList( - NSFWFilter().apply { state = isNSFWEnabledInPref() }, - GenreFilter(getGenreList()), - StatusFilter(getStatusList()), - DurationFilter(getDurationList()), - OrderBy() - ) - - private class NSFWFilter : Filter.CheckBox("Show NSFW", true) - private class Genre(name: String, val id: String = name) : Filter.TriState(name) - private class GenreFilter(genres: List) : Filter.Group("GENRES", genres) - private class Status(name: String, val id: String = name) : Filter.TriState(name) - private class StatusFilter(status: List) : Filter.Group("STATUS", status) - private class DurationFilter(pairs: Array>) : UriPartFilter("DURATION", pairs) - - open class UriPartFilter(displayName: String, private val vals: Array>) : - Filter.Select(displayName, vals.map { it.first }.toTypedArray()) { - fun toUriPart() = vals[state].second - } - - private fun getGenreList() = listOf( - Genre("Action", "14"), - Genre("Adventure", "6"), - Genre("All Ages", "73"), - Genre("Angst", "50"), - Genre("BL", "20"), - Genre("Boxing", "58"), - Genre("College", "82"), - Genre("Comedy", "1"), - Genre("Comic", "70"), - Genre("Completed", "53"), - Genre("Cooking", "67"), - Genre("Crime", "18"), - Genre("Cultivation", "37"), - Genre("Demons", "65"), - Genre("Drama", "2"), - Genre("Ecchi", "46"), - Genre("Fantasy", "8"), - Genre("Gender Bender", "35"), - Genre("GL", "42"), - Genre("Goshiwon", "80"), - Genre("Gossip", "12"), - Genre("Harem", "7"), - Genre("Historical", "33"), - Genre("Horror", "19"), - Genre("Incest", "10"), - Genre("Isekai", "28"), - Genre("Josei", "48"), - Genre("Long Strip", "78"), - Genre("M", "43"), - Genre("Magic", "59"), - Genre("Magical", "69"), - Genre("Magical Girls", "77"), - Genre("Manga", "56"), - Genre("Manhua", "38"), - Genre("Manhwa", "40"), - Genre("Manhwa18", "81"), - Genre("Martial arts", "26"), - Genre("Mature", "30"), - Genre("Mecha", "54"), - Genre("Medical", "24"), - Genre("Moder", "64"), - Genre("Modern", "51"), - Genre("Monster/Tentacle", "57"), - Genre("Music", "75"), - Genre("Mystery", "15"), - Genre("NTR", "32"), - Genre("Office", "84"), - Genre("Office Life", "79"), - Genre("One shot", "61"), - Genre("Philosophical", "44"), - Genre("Post Apocalyptic", "49"), - Genre("Psychological", "16"), - Genre("Revenge", "74"), - Genre("Reverse harem", "72"), - Genre("Romance", "3"), - Genre("Rpg", "41"), - Genre("School LIfe", "11"), - Genre("Sci Fi", "9"), - Genre("Seinen", "31"), - Genre("Shoujo", "36"), - Genre("Shoujo Ai", "62"), - Genre("Shounen", "29"), - Genre("Shounen Ai", "63"), - Genre("Slice of Life", "4"), - Genre("Smut", "13"), - Genre("Sports", "5"), - Genre("Super power", "71"), - Genre("Superhero", "45"), - Genre("Supernatural", "22"), - Genre("Suspense", "47"), - Genre("Thriller", "17"), - Genre("Time Travel", "55"), - Genre("TimeTravel", "52"), - Genre("ToonPoint", "83"), - Genre("Tragedy", "23"), - Genre("Uncensored", "85"), - Genre("Vampire", "68"), - Genre("Vanilla", "34"), - Genre("Web Comic", "76"), - Genre("Webtoon", "39"), - Genre("Webtoons", "60"), - Genre("Yaoi", "21"), - Genre("Youkai", "66"), - Genre("Yuri", "25") - ) - - private fun getStatusList() = listOf( - Status("Ongoing", "ongoing"), - Status("Complete", "complete"), - Status("On Hold", "onhold"), - Status("Canceled", "canceled") - ) - - private fun getDurationList() = arrayOf( - Pair("All time", "all"), - Pair("Year", "year"), - Pair("Month", "month"), - Pair("Week", "week"), - Pair("Day", "day") - ) - - private class OrderBy : Filter.Sort( - "Order by", - arrayOf("Date", "Title", "Pages", "Favorites", "Popularity"), - Selection(0, false) - ) - - override fun setupPreferenceScreen(screen: androidx.preference.PreferenceScreen) { - val nsfw = androidx.preference.CheckBoxPreference(screen.context).apply { - key = NSFW - title = NSFW_TITLE - setDefaultValue(NSFW_DEFAULT) - - setOnPreferenceChangeListener { _, newValue -> - val selected = newValue as Boolean - preferences.edit().putBoolean(NSFW, selected).commit() - } - } - screen.addPreference(nsfw) - } - - private fun isNSFWEnabledInPref(): Boolean { - return preferences.getBoolean(NSFW, NSFW_DEFAULT) - } - - companion object { - private const val NSFW = "NSFW" - private const val NSFW_TITLE = "Show NSFW" - private const val NSFW_DEFAULT = true - } -}