Add HentaiHand multisrc (#10664)
* Delete src/all/hentaihand directory * Delete src/all/nhentaicom directory * Delete src/en/readmanhwa directory * Add HH multisrc * Update version numbers * Update HentaiHandGenerator.kt * Add overrides and icons
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.4 KiB |
Before Width: | Height: | Size: 5.7 KiB After Width: | Height: | Size: 5.7 KiB |
Before Width: | Height: | Size: 7.4 KiB After Width: | Height: | Size: 7.4 KiB |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
@ -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<Source> = 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<Int> = 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))
|
After Width: | Height: | Size: 4.3 KiB |
After Width: | Height: | Size: 2.4 KiB |
After Width: | Height: | Size: 5.7 KiB |
After Width: | Height: | Size: 10 KiB |
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 83 KiB |
|
@ -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()
|
||||||
|
}
|
After Width: | Height: | Size: 3.9 KiB |
After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 5.8 KiB |
After Width: | Height: | Size: 10 KiB |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 65 KiB |
|
@ -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()
|
||||||
|
}
|
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 5.2 KiB |
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 7.2 KiB After Width: | Height: | Size: 7.2 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 101 KiB After Width: | Height: | Size: 101 KiB |
|
@ -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<Source> = 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<Int> = 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))
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.3 KiB |
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 4.4 KiB |
Before Width: | Height: | Size: 5.8 KiB After Width: | Height: | Size: 5.8 KiB |
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
|
@ -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()
|
||||||
|
}
|
|
@ -1,9 +1,10 @@
|
||||||
package eu.kanade.tachiyomi.extension.all.hentaihand
|
package eu.kanade.tachiyomi.multisrc.hentaihand
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.text.InputType
|
import android.text.InputType
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
import androidx.preference.PreferenceScreen
|
||||||
import eu.kanade.tachiyomi.network.GET
|
import eu.kanade.tachiyomi.network.GET
|
||||||
import eu.kanade.tachiyomi.network.POST
|
import eu.kanade.tachiyomi.network.POST
|
||||||
import eu.kanade.tachiyomi.network.asObservableSuccess
|
import eu.kanade.tachiyomi.network.asObservableSuccess
|
||||||
|
@ -20,13 +21,13 @@ import kotlinx.serialization.json.JsonObject
|
||||||
import kotlinx.serialization.json.buildJsonObject
|
import kotlinx.serialization.json.buildJsonObject
|
||||||
import kotlinx.serialization.json.int
|
import kotlinx.serialization.json.int
|
||||||
import kotlinx.serialization.json.jsonArray
|
import kotlinx.serialization.json.jsonArray
|
||||||
|
import kotlinx.serialization.json.jsonNull
|
||||||
import kotlinx.serialization.json.jsonObject
|
import kotlinx.serialization.json.jsonObject
|
||||||
import kotlinx.serialization.json.jsonPrimitive
|
import kotlinx.serialization.json.jsonPrimitive
|
||||||
import kotlinx.serialization.json.put
|
import kotlinx.serialization.json.put
|
||||||
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||||
import okhttp3.Interceptor
|
import okhttp3.Interceptor
|
||||||
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
||||||
import okhttp3.OkHttpClient
|
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import okhttp3.RequestBody.Companion.toRequestBody
|
import okhttp3.RequestBody.Companion.toRequestBody
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
|
@ -36,22 +37,19 @@ import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.Calendar
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
abstract class HentaiHand(
|
abstract class HentaiHand(
|
||||||
|
override val name: String,
|
||||||
|
override val baseUrl: String,
|
||||||
override val lang: String,
|
override val lang: String,
|
||||||
private val hhLangId: Int? = null,
|
private val chapters: Boolean,
|
||||||
extraName: String = ""
|
private val hhLangId: List<Int> = emptyList(),
|
||||||
) : ConfigurableSource, HttpSource() {
|
) : ConfigurableSource, HttpSource() {
|
||||||
|
|
||||||
override val baseUrl: String = "https://hentaihand.com"
|
|
||||||
override val name: String = "HentaiHand$extraName"
|
|
||||||
override val supportsLatest = true
|
override val supportsLatest = true
|
||||||
|
|
||||||
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
|
|
||||||
.addInterceptor { authIntercept(it) }
|
|
||||||
.build()
|
|
||||||
|
|
||||||
private val json: Json by injectLazy()
|
private val json: Json by injectLazy()
|
||||||
|
|
||||||
private fun slugToUrl(json: JsonObject) = json["slug"]!!.jsonPrimitive.content.prependIndent("/en/comic/")
|
private fun slugToUrl(json: JsonObject) = json["slug"]!!.jsonPrimitive.content.prependIndent("/en/comic/")
|
||||||
|
@ -73,7 +71,7 @@ abstract class HentaiHand(
|
||||||
SManga.create().apply {
|
SManga.create().apply {
|
||||||
url = slugToUrl(obj)
|
url = slugToUrl(obj)
|
||||||
title = obj["title"]!!.jsonPrimitive.content
|
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()
|
val hasNextPage = jsonResponse.jsonObject["next_page_url"]!!.jsonPrimitive.content.isNotEmpty()
|
||||||
|
@ -81,8 +79,16 @@ abstract class HentaiHand(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun popularMangaRequest(page: Int): Request {
|
override fun popularMangaRequest(page: Int): Request {
|
||||||
val url = "$baseUrl/api/comics?page=$page&sort=popularity&order=desc&duration=all"
|
val url = "$baseUrl/api/comics".toHttpUrlOrNull()!!.newBuilder()
|
||||||
return GET(if (hhLangId == null) url else ("$url&languages=$hhLangId"))
|
.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
|
// Latest
|
||||||
|
@ -90,8 +96,15 @@ abstract class HentaiHand(
|
||||||
override fun latestUpdatesParse(response: Response): MangasPage = popularMangaParse(response)
|
override fun latestUpdatesParse(response: Response): MangasPage = popularMangaParse(response)
|
||||||
|
|
||||||
override fun latestUpdatesRequest(page: Int): Request {
|
override fun latestUpdatesRequest(page: Int): Request {
|
||||||
val url = "$baseUrl/api/comics?page=$page&sort=uploaded_at&order=desc&duration=week"
|
val url = "$baseUrl/api/comics".toHttpUrlOrNull()!!.newBuilder()
|
||||||
return GET(if (hhLangId == null) url else ("$url&languages=$hhLangId"))
|
.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
|
// Search
|
||||||
|
@ -119,8 +132,9 @@ abstract class HentaiHand(
|
||||||
.addQueryParameter("page", page.toString())
|
.addQueryParameter("page", page.toString())
|
||||||
.addQueryParameter("q", query)
|
.addQueryParameter("q", query)
|
||||||
|
|
||||||
if (hhLangId != null)
|
hhLangId.forEachIndexed() {index, it ->
|
||||||
url.addQueryParameter("languages", hhLangId.toString())
|
url.addQueryParameter("languages[${-index - 1}]", it.toString())
|
||||||
|
}
|
||||||
|
|
||||||
(if (filters.isEmpty()) getFilterList() else filters).forEach { filter ->
|
(if (filters.isEmpty()) getFilterList() else filters).forEach { filter ->
|
||||||
when (filter) {
|
when (filter) {
|
||||||
|
@ -130,12 +144,15 @@ abstract class HentaiHand(
|
||||||
is AttributesGroupFilter -> filter.state.forEach {
|
is AttributesGroupFilter -> filter.state.forEach {
|
||||||
if (it.state) url.addQueryParameter("attributes", it.value)
|
if (it.state) url.addQueryParameter("attributes", it.value)
|
||||||
}
|
}
|
||||||
|
is StatusGroupFilter -> filter.state.forEach {
|
||||||
|
if (it.state) url.addQueryParameter("statuses", it.value)
|
||||||
|
}
|
||||||
is LookupFilter -> {
|
is LookupFilter -> {
|
||||||
filter.state.split(",").map { it.trim() }.filter { it.isNotBlank() }.map {
|
filter.state.split(",").map { it.trim() }.filter { it.isNotBlank() }.map {
|
||||||
lookupFilterId(it, filter.uri) ?: throw Exception("No ${filter.singularName} \"$it\" was found")
|
lookupFilterId(it, filter.uri) ?: throw Exception("No ${filter.singularName} \"$it\" was found")
|
||||||
}.forEach {
|
}.forEachIndexed() {index, it ->
|
||||||
if (!(filter.uri == "languages" && it == hhLangId))
|
if (!(filter.uri == "languages" && hhLangId.contains(it)))
|
||||||
url.addQueryParameter(filter.uri, it.toString())
|
url.addQueryParameter(filter.uri + "[$index]", it.toString())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else -> {}
|
else -> {}
|
||||||
|
@ -163,19 +180,27 @@ abstract class HentaiHand(
|
||||||
return SManga.create().apply {
|
return SManga.create().apply {
|
||||||
url = slugToUrl(obj)
|
url = slugToUrl(obj)
|
||||||
title = obj["title"]!!.jsonPrimitive.content
|
title = obj["title"]!!.jsonPrimitive.content
|
||||||
thumbnail_url = obj["thumb_url"]!!.jsonPrimitive.content
|
thumbnail_url = obj["image_url"]!!.jsonPrimitive.content
|
||||||
artist = jsonArrayToString("artists", obj)
|
artist = jsonArrayToString("artists", obj)
|
||||||
author = jsonArrayToString("authors", obj) ?: artist
|
author = jsonArrayToString("authors", obj) ?: artist
|
||||||
genre = listOfNotNull(jsonArrayToString("tags", obj), jsonArrayToString("relationships", obj)).joinToString(", ")
|
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(
|
description = listOf(
|
||||||
Pair("Alternative Title", obj["alternative_title"]!!.jsonPrimitive.content),
|
Pair("Alternative Title", obj["alternative_title"]!!.jsonPrimitive.content),
|
||||||
Pair("Groups", jsonArrayToString("groups", obj)),
|
Pair("Groups", jsonArrayToString("groups", obj)),
|
||||||
Pair("Description", obj["description"]!!.jsonPrimitive.content),
|
Pair("Description", obj["description"]!!.jsonPrimitive.content),
|
||||||
Pair("Pages", obj["pages"]!!.jsonPrimitive.content),
|
Pair("Pages", obj["pages"]!!.jsonPrimitive.content),
|
||||||
Pair("Category", obj["category"]!!.jsonObject["name"]!!.jsonPrimitive.content),
|
Pair("Category", try { obj["category"]!!.jsonObject["name"]!!.jsonPrimitive.content } catch (_: Exception) { null }),
|
||||||
Pair("Language", obj["language"]!!.jsonObject["name"]!!.jsonPrimitive.content),
|
Pair("Language", try { obj["language"]!!.jsonObject["name"]!!.jsonPrimitive.content } catch (_: Exception) { null }),
|
||||||
Pair("Parodies", jsonArrayToString("parodies", obj)),
|
Pair("Parodies", jsonArrayToString("parodies", obj)),
|
||||||
Pair("Characters", jsonArrayToString("characters", obj))
|
Pair("Characters", jsonArrayToString("characters", obj))
|
||||||
).filter { !it.second.isNullOrEmpty() }.joinToString("\n\n") { "${it.first}: ${it.second}" }
|
).filter { !it.second.isNullOrEmpty() }.joinToString("\n\n") { "${it.first}: ${it.second}" }
|
||||||
|
@ -184,24 +209,59 @@ abstract class HentaiHand(
|
||||||
|
|
||||||
// Chapters
|
// 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<SChapter> {
|
override fun chapterListParse(response: Response): List<SChapter> {
|
||||||
val obj = json.parseToJsonElement(response.body!!.string()).jsonObject
|
val slug = response.request.url.toString().substringAfter("/api/comics/").removeSuffix("/chapters")
|
||||||
return listOf(
|
return if (chapters) {
|
||||||
|
val array = json.parseToJsonElement(response.body!!.string()).jsonArray
|
||||||
|
array.map {
|
||||||
SChapter.create().apply {
|
SChapter.create().apply {
|
||||||
url = "/en/comic/${obj["slug"]!!.jsonPrimitive.content}/reader/1"
|
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"
|
name = "Chapter"
|
||||||
date_upload = DATE_FORMAT.parse(obj["uploaded_at"]!!.jsonPrimitive.content)?.time ?: 0
|
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
|
chapter_number = 1f
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Pages
|
// Pages
|
||||||
|
|
||||||
override fun pageListRequest(chapter: SChapter): Request {
|
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")
|
return GET("$baseUrl/api/comics/$slug/images")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -217,7 +277,7 @@ abstract class HentaiHand(
|
||||||
|
|
||||||
// Authorization
|
// Authorization
|
||||||
|
|
||||||
private fun authIntercept(chain: Interceptor.Chain): Response {
|
protected fun authIntercept(chain: Interceptor.Chain): Response {
|
||||||
val request = chain.request()
|
val request = chain.request()
|
||||||
if (username.isEmpty() or password.isEmpty()) {
|
if (username.isEmpty() or password.isEmpty()) {
|
||||||
return chain.proceed(request)
|
return chain.proceed(request)
|
||||||
|
@ -264,12 +324,12 @@ abstract class HentaiHand(
|
||||||
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
Injekt.get<Application>().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(USERNAME_TITLE, USERNAME_DEFAULT, username))
|
||||||
screen.addPreference(screen.editTextPreference(PASSWORD_TITLE, PASSWORD_DEFAULT, password, true))
|
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 {
|
return androidx.preference.EditTextPreference(context).apply {
|
||||||
key = title
|
key = title
|
||||||
this.title = title
|
this.title = title
|
||||||
|
@ -305,6 +365,8 @@ abstract class HentaiHand(
|
||||||
private class DurationFilter(durationPairs: List<Pair<String, String>>) : Filter.Select<String>("Duration", durationPairs.map { it.first }.toTypedArray())
|
private class DurationFilter(durationPairs: List<Pair<String, String>>) : Filter.Select<String>("Duration", durationPairs.map { it.first }.toTypedArray())
|
||||||
private class AttributeFilter(name: String, val value: String) : Filter.CheckBox(name)
|
private class AttributeFilter(name: String, val value: String) : Filter.CheckBox(name)
|
||||||
private class AttributesGroupFilter(attributePairs: List<Pair<String, String>>) : Filter.Group<AttributeFilter>("Attributes", attributePairs.map { AttributeFilter(it.first, it.second) })
|
private class AttributesGroupFilter(attributePairs: List<Pair<String, String>>) : Filter.Group<AttributeFilter>("Attributes", attributePairs.map { AttributeFilter(it.first, it.second) })
|
||||||
|
private class StatusFilter(name: String, val value: String) : Filter.CheckBox(name)
|
||||||
|
private class StatusGroupFilter(attributePairs: List<Pair<String, String>>) : Filter.Group<StatusFilter>("Status", attributePairs.map { StatusFilter(it.first, it.second) })
|
||||||
|
|
||||||
private class CategoriesFilter : LookupFilter("Categories", "categories", "category")
|
private class CategoriesFilter : LookupFilter("Categories", "categories", "category")
|
||||||
private class TagsFilter : LookupFilter("Tags", "tags", "tag")
|
private class TagsFilter : LookupFilter("Tags", "tags", "tag")
|
||||||
|
@ -327,7 +389,8 @@ abstract class HentaiHand(
|
||||||
CharactersFilter(),
|
CharactersFilter(),
|
||||||
ParodiesFilter(),
|
ParodiesFilter(),
|
||||||
LanguagesFilter(),
|
LanguagesFilter(),
|
||||||
AttributesGroupFilter(getAttributePairs())
|
AttributesGroupFilter(getAttributePairs()),
|
||||||
|
StatusGroupFilter(getStatusPairs())
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun getSortPairs() = listOf(
|
private fun getSortPairs() = listOf(
|
||||||
|
@ -357,8 +420,15 @@ abstract class HentaiHand(
|
||||||
Pair("Rewritten", "rewritten")
|
Pair("Rewritten", "rewritten")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
private fun getStatusPairs() = listOf(
|
||||||
|
Pair("Ongoing", "ongoing"),
|
||||||
|
Pair("Complete", "complete"),
|
||||||
|
Pair("On Hold", "onhold"),
|
||||||
|
Pair("Canceled", "canceled")
|
||||||
|
)
|
||||||
|
|
||||||
companion object {
|
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 val MEDIA_TYPE = "application/json; charset=utf-8".toMediaTypeOrNull()
|
||||||
private const val USERNAME_TITLE = "Username"
|
private const val USERNAME_TITLE = "Username"
|
||||||
private const val USERNAME_DEFAULT = ""
|
private const val USERNAME_DEFAULT = ""
|
|
@ -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<String>) {
|
||||||
|
HentaiHandGenerator().createAll()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,2 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<manifest package="eu.kanade.tachiyomi.extension" />
|
|
|
@ -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"
|
|
|
@ -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<Source> = 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)
|
|
|
@ -1,2 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<manifest package="eu.kanade.tachiyomi.extension" />
|
|
|
@ -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"
|
|
|
@ -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<SManga> =
|
|
||||||
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<List<SChapter>> {
|
|
||||||
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<SChapter> = 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<Page> {
|
|
||||||
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<Pair<String, String>>) : UriPartFilter("Duration", pairs)
|
|
||||||
|
|
||||||
private class SortFilter(pairs: Array<Pair<String, String>>) : UriPartFilter("Sorted by", pairs)
|
|
||||||
|
|
||||||
private class SortOrderFilter(pairs: Array<Pair<String, String>>) : UriPartFilter("Order", pairs)
|
|
||||||
|
|
||||||
open class UriPartFilter(displayName: String, private val vals: Array<Pair<String, String>>) :
|
|
||||||
Filter.Select<String>(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"),
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -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<Source> = languages.map { NHentaiCom(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
private val languages = listOf(
|
|
||||||
"all", "en", "zh", "ja", "other", "eo", "cs", "ar", "sk"
|
|
||||||
)
|
|
|
@ -1,2 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<manifest package="eu.kanade.tachiyomi.extension" />
|
|
|
@ -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"
|
|
|
@ -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<Application>().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<JsonObject>(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<String>()
|
|
||||||
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<String>()
|
|
||||||
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<SManga> =
|
|
||||||
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<JsonObject>(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<List<SChapter>> {
|
|
||||||
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<SChapter> {
|
|
||||||
return json.decodeFromString<JsonArray>(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<SChapter> = 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<Page> {
|
|
||||||
return json.decodeFromString<JsonObject>(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<Genre>) : Filter.Group<Genre>("GENRES", genres)
|
|
||||||
private class Status(name: String, val id: String = name) : Filter.TriState(name)
|
|
||||||
private class StatusFilter(status: List<Status>) : Filter.Group<Status>("STATUS", status)
|
|
||||||
private class DurationFilter(pairs: Array<Pair<String, String>>) : UriPartFilter("DURATION", pairs)
|
|
||||||
|
|
||||||
open class UriPartFilter(displayName: String, private val vals: Array<Pair<String, String>>) :
|
|
||||||
Filter.Select<String>(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
|
|
||||||
}
|
|
||||||
}
|
|