Compare commits
157 Commits
5ecf338be0
...
e0bb39f99a
Author | SHA1 | Date |
---|---|---|
AwkwardPeak7 | e0bb39f99a | |
AwkwardPeak7 | 6032b0853f | |
Smol Ame | 2802de7b62 | |
Chopper | 8c426ce4fb | |
Chopper | e74443b530 | |
Chopper | fdcadcd5a1 | |
Chopper | c1435f4c2f | |
Chopper | 0b1efd70bd | |
Dexroneum | 708f2cc55f | |
Vetle Ledaal | c3e3a3d5c7 | |
Vetle Ledaal | 0f7c99bc52 | |
Vetle Ledaal | ea19c76636 | |
Smol Ame | abf3dca6fd | |
AwkwardPeak7 | 33e3638351 | |
Smol Ame | d1c75db514 | |
Hasan | b7a770367b | |
Luqman | 50dd55646c | |
bapeey | 78fb4d0fcf | |
mr-brune | e13faaeff9 | |
bapeey | 12fb2353ad | |
KirinRaikage | 5cee63f5bb | |
bapeey | af8a367cfa | |
Smol Ame | 96d97d7275 | |
TheKingTermux | 4559cdf121 | |
AlphaBoom | 1883e7a889 | |
Chopper | c0e41cb54c | |
bapeey | 606e70fc75 | |
Chopper | 66244d5c2c | |
Vetle Ledaal | 9a08636bda | |
Vetle Ledaal | a92b59fb3f | |
Vetle Ledaal | 90eb12294c | |
bapeey | f8ab0d3f2f | |
bapeey | 20b3c8ac86 | |
Smol Ame | 68f096e7c8 | |
Smol Ame | a4aea82374 | |
Smol Ame | 72585f0b78 | |
AlphaBoom | da86cb3ac1 | |
Smol Ame | a8d36f9eca | |
AwkwardPeak7 | 0af3536837 | |
bapeey | 3b826e0365 | |
dngonz | 076ee3f5de | |
mr-brune | 71e7c34dd7 | |
AwkwardPeak7 | 7dd03b565b | |
mr-brune | ba388ba801 | |
TheKingTermux | 4addb7dad3 | |
dngonz | 47380ab91d | |
TheKingTermux | 66c2007490 | |
dngonz | dc2fede86f | |
Cuong-Tran | dddecfbe05 | |
extmisc | 7792ab3013 | |
Deivid Gabriel Pereira de Oliveira | 3ecfc66689 | |
dngonz | 3f964e8b2c | |
David Alysson | cad87f7e78 | |
David Alysson | 7663887261 | |
ringosham | a65e48cb1b | |
dngonz | c600831a70 | |
dngonz | 7d7a127ae2 | |
dngonz | 07a8f8fe9e | |
bapeey | 5e5e60e8bd | |
bapeey | 6b4dbb1d3d | |
Hasan | e11342f5df | |
Smol Ame | 6ca8f886ed | |
bapeey | 4ce4b79054 | |
bapeey | f6bf98b764 | |
bapeey | 4e3f2d235f | |
bapeey | 57bbef431b | |
bapeey | 925e50d120 | |
bapeey | a88503f4f4 | |
bapeey | 87a1c4b5fe | |
bapeey | 6b9115a327 | |
dngonz | ff8b61ce59 | |
dngonz | 57d69df4d9 | |
Vetle Ledaal | 71ee8dc587 | |
Paco Chrispijn | 27bf92cc04 | |
long21wt | 81aa3a5d21 | |
Vetle Ledaal | 77aafd4076 | |
Creepler13 | f75ed7f762 | |
Creepler13 | 30c9647669 | |
Wackery | 3fe52c264d | |
Wackery | b00c569c12 | |
roolyz | c162877800 | |
KirinRaikage | 195b3771d3 | |
AwkwardPeak7 | 5a6c302a6d | |
mohamedotaku | 2d16d1896b | |
roolyz | a973660aa0 | |
Tran M. Cuong | 08c07a08dd | |
Chopper | bbd983389a | |
AwkwardPeak7 | d655d6b23f | |
AwkwardPeak7 | 96113ce68e | |
AwkwardPeak7 | bc6ebbb394 | |
Orion | 2dcaabffb4 | |
Pedro Azevedo | a6659a97dc | |
KirinRaikage | edff39f57e | |
Chopper | 589ebcd677 | |
Chopper | 4c947f56b9 | |
mohamedotaku | 8a8eaf675e | |
mohamedotaku | d571f6b6d8 | |
mohamedotaku | ee9df7d875 | |
Creepler13 | bd7aa80936 | |
Creepler13 | 1671af188e | |
Creepler13 | e11ebc5fd0 | |
Creepler13 | 6457e349f5 | |
RePod | f297323f42 | |
Vetle Ledaal | 89a64d0b80 | |
TheKingTermux | cfd6629d98 | |
Vetle Ledaal | 67fcfb842f | |
Chopper | 601f45baec | |
KenjieDec | 1302bf5e80 | |
Chopper | 042abd4e93 | |
renovate[bot] | c3fe4230fe | |
Chopper | b0b1e0d5cd | |
Vetle Ledaal | f8de9ce1ea | |
Creepler13 | cb17be204b | |
Vetle Ledaal | 7f456c6dc0 | |
Chopper | 971007cde7 | |
Chopper | ac53bd65ff | |
Chopper | 41ef482be7 | |
iloverabbit | 4f58440542 | |
KenjieDec | 384146984b | |
子斗子 | abf5f64873 | |
bapeey | dc6189f4c2 | |
AwkwardPeak7 | 0dc7a9817b | |
AwkwardPeak7 | 0f09d40fde | |
AwkwardPeak7 | e90f887d47 | |
子斗子 | 9758bd897f | |
AwkwardPeak7 | cb8cac580f | |
子斗子 | 231197b43c | |
子斗子 | 779155707a | |
bapeey | 0321569d77 | |
Creepler13 | 08cb43435a | |
AwkwardPeak7 | 99d1e125ff | |
AwkwardPeak7 | c34db1d83e | |
OncePunchedMan | 4801226983 | |
bapeey | 94829063c1 | |
AwkwardPeak7 | 4fe3fbd9c3 | |
AwkwardPeak7 | 16e852a398 | |
AwkwardPeak7 | 8e09de0654 | |
KenjieDec | 5d82449b0c | |
子斗子 | 4060738822 | |
AwkwardPeak7 | b5df6c5788 | |
AwkwardPeak7 | 7c8f692488 | |
AwkwardPeak7 | 9fd4ff010d | |
KenjieDec | 0735c39dad | |
AwkwardPeak7 | 81811577a6 | |
AwkwardPeak7 | 9b17442c34 | |
AwkwardPeak7 | 42037d400c | |
heagan01 | 4a1bdae3fa | |
Chopper | 5376efd5a7 | |
Chopper | afe6556c25 | |
Vetle Ledaal | 84f3f8d2ff | |
Vetle Ledaal | 9938fb1750 | |
AwkwardPeak7 | 50e1900029 | |
子斗子 | 2bbdd0a351 | |
AwkwardPeak7 | f7a92a1573 | |
iloverabbit | a15350649f | |
AwkwardPeak7 | a7b59aecd4 | |
Chopper | 2f4249e842 |
|
@ -1,12 +1,14 @@
|
||||||
# Editor configuration, see https://editorconfig.org
|
# Editor configuration, see https://editorconfig.org
|
||||||
root = true
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
insert_final_newline = true
|
||||||
|
end_of_line = lf
|
||||||
|
|
||||||
[*.kt]
|
[*.kt]
|
||||||
charset = utf-8
|
charset = utf-8
|
||||||
end_of_line = lf
|
|
||||||
indent_size = 4
|
indent_size = 4
|
||||||
indent_style = space
|
indent_style = space
|
||||||
insert_final_newline = true
|
|
||||||
trim_trailing_whitespace = true
|
trim_trailing_whitespace = true
|
||||||
ij_kotlin_allow_trailing_comma = true
|
ij_kotlin_allow_trailing_comma = true
|
||||||
ij_kotlin_allow_trailing_comma_on_call_site = true
|
ij_kotlin_allow_trailing_comma_on_call_site = true
|
||||||
|
@ -15,5 +17,3 @@ ij_kotlin_name_count_to_use_star_import_for_members = 2147483647
|
||||||
|
|
||||||
[*.properties]
|
[*.properties]
|
||||||
charset = utf-8
|
charset = utf-8
|
||||||
end_of_line = lf
|
|
||||||
insert_final_newline = true
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10-bin.zip
|
||||||
networkTimeout=10000
|
networkTimeout=10000
|
||||||
validateDistributionUrl=true
|
validateDistributionUrl=true
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
|
|
@ -9,6 +9,7 @@ assert !ext.has("libVersion")
|
||||||
assert extName.chars().max().asInt < 0x180 : "Extension name should be romanized"
|
assert extName.chars().max().asInt < 0x180 : "Extension name should be romanized"
|
||||||
|
|
||||||
Project theme = ext.has("themePkg") ? project(":lib-multisrc:$themePkg") : null
|
Project theme = ext.has("themePkg") ? project(":lib-multisrc:$themePkg") : null
|
||||||
|
if (theme != null) evaluationDependsOn(theme.path)
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdk AndroidConfig.compileSdk
|
compileSdk AndroidConfig.compileSdk
|
||||||
|
|
|
@ -25,3 +25,5 @@ android.useAndroidX=true
|
||||||
android.enableBuildConfigAsBytecode=true
|
android.enableBuildConfigAsBytecode=true
|
||||||
android.defaults.buildfeatures.resvalues=false
|
android.defaults.buildfeatures.resvalues=false
|
||||||
android.defaults.buildfeatures.shaders=false
|
android.defaults.buildfeatures.shaders=false
|
||||||
|
|
||||||
|
org.gradle.configureondemand=true
|
||||||
|
|
|
@ -18,7 +18,7 @@ kotlin-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", ver
|
||||||
coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines_version" }
|
coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines_version" }
|
||||||
coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "coroutines_version" }
|
coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "coroutines_version" }
|
||||||
|
|
||||||
injekt-core = { module = "com.github.inorichi.injekt:injekt-core", version = "65b0440" }
|
injekt-core = { module = "com.github.null2264.injekt:injekt-core", version = "4135455a2a" }
|
||||||
rxjava = { module = "io.reactivex:rxjava", version = "1.3.8" }
|
rxjava = { module = "io.reactivex:rxjava", version = "1.3.8" }
|
||||||
jsoup = { module = "org.jsoup:jsoup", version = "1.15.1" }
|
jsoup = { module = "org.jsoup:jsoup", version = "1.15.1" }
|
||||||
okhttp = { module = "com.squareup.okhttp3:okhttp", version = "5.0.0-alpha.11" }
|
okhttp = { module = "com.squareup.okhttp3:okhttp", version = "5.0.0-alpha.11" }
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10-bin.zip
|
||||||
networkTimeout=10000
|
networkTimeout=10000
|
||||||
validateDistributionUrl=true
|
validateDistributionUrl=true
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
|
|
@ -2,7 +2,7 @@ plugins {
|
||||||
id("lib-multisrc")
|
id("lib-multisrc")
|
||||||
}
|
}
|
||||||
|
|
||||||
baseVersionCode = 3
|
baseVersionCode = 4
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
api(project(":lib:synchrony"))
|
api(project(":lib:synchrony"))
|
||||||
|
|
|
@ -276,7 +276,7 @@ abstract class ColaManga(
|
||||||
}.also(screen::addPreference)
|
}.also(screen::addPreference)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val keyMappingRegex = Regex("""[0-9A-Za-z_]+\s*==\s*['"](?<keyType>\d+)['"]\s*&&\s*\([0-9A-Za-z_]+\s*=\s*['"](?<key>[a-zA-Z0-9]+)['"]\)""")
|
private val keyMappingRegex = Regex("""if\s*\(\s*([a-zA-Z0-9_]+)\s*==\s*(?<keyType>\d+)\s*\)\s*\{\s*return\s*'(?<key>[a-zA-Z0-9_]+)'\s*;""")
|
||||||
|
|
||||||
private val keyMapping by lazy {
|
private val keyMapping by lazy {
|
||||||
val obfuscatedReadJs = client.newCall(GET("$baseUrl/js/manga.read.js")).execute().body.string()
|
val obfuscatedReadJs = client.newCall(GET("$baseUrl/js/manga.read.js")).execute().body.string()
|
||||||
|
|
|
@ -2,4 +2,4 @@ plugins {
|
||||||
id("lib-multisrc")
|
id("lib-multisrc")
|
||||||
}
|
}
|
||||||
|
|
||||||
baseVersionCode = 23
|
baseVersionCode = 24
|
||||||
|
|
|
@ -2,4 +2,4 @@ plugins {
|
||||||
id("lib-multisrc")
|
id("lib-multisrc")
|
||||||
}
|
}
|
||||||
|
|
||||||
baseVersionCode = 3
|
baseVersionCode = 4
|
||||||
|
|
|
@ -83,8 +83,8 @@ class Post<T>(val post: T)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
class ChapterListResponse(
|
class ChapterListResponse(
|
||||||
val isNovel: Boolean,
|
val isNovel: Boolean = false,
|
||||||
val slug: String,
|
val slug: String? = null,
|
||||||
val chapters: List<Chapter>,
|
val chapters: List<Chapter>,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -96,11 +96,13 @@ class Chapter(
|
||||||
private val createdBy: Name,
|
private val createdBy: Name,
|
||||||
private val createdAt: String,
|
private val createdAt: String,
|
||||||
private val chapterStatus: String,
|
private val chapterStatus: String,
|
||||||
|
private val mangaPost: ChapterPostDetails,
|
||||||
) {
|
) {
|
||||||
fun isPublic() = chapterStatus == "PUBLIC"
|
fun isPublic() = chapterStatus == "PUBLIC"
|
||||||
|
|
||||||
fun toSChapter(mangaSlug: String) = SChapter.create().apply {
|
fun toSChapter(mangaSlug: String?) = SChapter.create().apply {
|
||||||
url = "/series/$mangaSlug/$slug#$id"
|
val seriesSlug = mangaSlug ?: mangaPost.slug
|
||||||
|
url = "/series/$seriesSlug/$slug#$id"
|
||||||
name = "Chapter $number"
|
name = "Chapter $number"
|
||||||
scanlator = createdBy.name
|
scanlator = createdBy.name
|
||||||
date_upload = try {
|
date_upload = try {
|
||||||
|
@ -111,4 +113,9 @@ class Chapter(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class ChapterPostDetails(
|
||||||
|
val slug: String,
|
||||||
|
)
|
||||||
|
|
||||||
private val dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.ENGLISH)
|
private val dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.ENGLISH)
|
||||||
|
|
|
@ -2,4 +2,4 @@ plugins {
|
||||||
id("lib-multisrc")
|
id("lib-multisrc")
|
||||||
}
|
}
|
||||||
|
|
||||||
baseVersionCode = 4
|
baseVersionCode = 7
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package eu.kanade.tachiyomi.multisrc.keyoapp
|
package eu.kanade.tachiyomi.multisrc.keyoapp
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.network.GET
|
import eu.kanade.tachiyomi.network.GET
|
||||||
import eu.kanade.tachiyomi.network.interceptor.rateLimit
|
|
||||||
import eu.kanade.tachiyomi.source.model.Filter
|
import eu.kanade.tachiyomi.source.model.Filter
|
||||||
import eu.kanade.tachiyomi.source.model.FilterList
|
import eu.kanade.tachiyomi.source.model.FilterList
|
||||||
import eu.kanade.tachiyomi.source.model.MangasPage
|
import eu.kanade.tachiyomi.source.model.MangasPage
|
||||||
|
@ -31,9 +30,7 @@ abstract class Keyoapp(
|
||||||
) : ParsedHttpSource() {
|
) : ParsedHttpSource() {
|
||||||
override val supportsLatest = true
|
override val supportsLatest = true
|
||||||
|
|
||||||
override val client = network.cloudflareClient.newBuilder()
|
override val client = network.cloudflareClient
|
||||||
.rateLimit(2)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
override fun headersBuilder() = super.headersBuilder()
|
override fun headersBuilder() = super.headersBuilder()
|
||||||
.add("Referer", "$baseUrl/")
|
.add("Referer", "$baseUrl/")
|
||||||
|
@ -197,7 +194,18 @@ abstract class Keyoapp(
|
||||||
status = document.selectFirst("div[alt=Status]").parseStatus()
|
status = document.selectFirst("div[alt=Status]").parseStatus()
|
||||||
author = document.selectFirst("div[alt=Author]")?.text()
|
author = document.selectFirst("div[alt=Author]")?.text()
|
||||||
artist = document.selectFirst("div[alt=Artist]")?.text()
|
artist = document.selectFirst("div[alt=Artist]")?.text()
|
||||||
genre = document.select("div.grid:has(>h1) > div > a").joinToString { it.text() }
|
genre = buildList {
|
||||||
|
document.selectFirst("div[alt='Series Type']")?.text()?.replaceFirstChar {
|
||||||
|
if (it.isLowerCase()) {
|
||||||
|
it.titlecase(
|
||||||
|
Locale.getDefault(),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
it.toString()
|
||||||
|
}
|
||||||
|
}.let(::add)
|
||||||
|
document.select("div.grid:has(>h1) > div > a").forEach { add(it.text()) }
|
||||||
|
}.joinToString()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Element?.parseStatus(): Int = when (this?.text()?.lowercase()) {
|
private fun Element?.parseStatus(): Int = when (this?.text()?.lowercase()) {
|
||||||
|
@ -223,15 +231,27 @@ abstract class Keyoapp(
|
||||||
// Image list
|
// Image list
|
||||||
|
|
||||||
override fun pageListParse(document: Document): List<Page> {
|
override fun pageListParse(document: Document): List<Page> {
|
||||||
|
document.select("#pages > img")
|
||||||
|
.map { it.attr("uid") }
|
||||||
|
.filter { it.isNotEmpty() }
|
||||||
|
.mapIndexed { index, img ->
|
||||||
|
Page(index, document.location(), "$cdnUrl/uploads/$img")
|
||||||
|
}
|
||||||
|
.takeIf { it.isNotEmpty() }
|
||||||
|
?.also { return it }
|
||||||
|
|
||||||
|
// Fallback, old method
|
||||||
return document.select("#pages > img")
|
return document.select("#pages > img")
|
||||||
.map { it.imgAttr() }
|
.map { it.imgAttr() }
|
||||||
.filter { it.contains(imgCdnRegex) }
|
.filter { it.contains(oldImgCdnRegex) }
|
||||||
.mapIndexed { index, img ->
|
.mapIndexed { index, img ->
|
||||||
Page(index, document.location(), img)
|
Page(index, document.location(), img)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val imgCdnRegex = Regex("""^(https?:)?//cdn\d*\.keyoapp\.com""")
|
protected val cdnUrl = "https://cdn.igniscans.com"
|
||||||
|
|
||||||
|
private val oldImgCdnRegex = Regex("""^(https?:)?//cdn\d*\.keyoapp\.com""")
|
||||||
|
|
||||||
override fun imageUrlParse(document: Document) = ""
|
override fun imageUrlParse(document: Document) = ""
|
||||||
|
|
||||||
|
@ -247,7 +267,7 @@ abstract class Keyoapp(
|
||||||
return url
|
return url
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Element.getImageUrl(selector: String): String? {
|
protected open fun Element.getImageUrl(selector: String): String? {
|
||||||
return this.selectFirst(selector)?.let { element ->
|
return this.selectFirst(selector)?.let { element ->
|
||||||
element.attr("style")
|
element.attr("style")
|
||||||
.substringAfter(":url(", "")
|
.substringAfter(":url(", "")
|
||||||
|
|
|
@ -2,4 +2,4 @@ plugins {
|
||||||
id("lib-multisrc")
|
id("lib-multisrc")
|
||||||
}
|
}
|
||||||
|
|
||||||
baseVersionCode = 30
|
baseVersionCode = 31
|
||||||
|
|
|
@ -622,7 +622,7 @@ abstract class Madara(
|
||||||
"OnGoing", "Продолжается", "Updating", "Em Lançamento", "Em lançamento", "Em andamento",
|
"OnGoing", "Продолжается", "Updating", "Em Lançamento", "Em lançamento", "Em andamento",
|
||||||
"Em Andamento", "En cours", "En Cours", "En cours de publication", "Ativo", "Lançando", "Đang Tiến Hành", "Devam Ediyor",
|
"Em Andamento", "En cours", "En Cours", "En cours de publication", "Ativo", "Lançando", "Đang Tiến Hành", "Devam Ediyor",
|
||||||
"Devam ediyor", "In Corso", "In Arrivo", "مستمرة", "مستمر", "En Curso", "En curso", "Emision",
|
"Devam ediyor", "In Corso", "In Arrivo", "مستمرة", "مستمر", "En Curso", "En curso", "Emision",
|
||||||
"Curso", "En marcha", "Publicandose", "En emision", "连载中", "Em Lançamento", "Devam Ediyo",
|
"Curso", "En marcha", "Publicandose", "Publicándose", "En emision", "连载中", "Em Lançamento", "Devam Ediyo",
|
||||||
"Đang làm", "Em postagem", "Devam Eden", "Em progresso", "Em curso",
|
"Đang làm", "Em postagem", "Devam Eden", "Em progresso", "Em curso",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -490,8 +490,8 @@ abstract class MangaThemesia(
|
||||||
Pair(intl["order_by_filter_popular"], "popular"),
|
Pair(intl["order_by_filter_popular"], "popular"),
|
||||||
)
|
)
|
||||||
|
|
||||||
protected val popularFilter by lazy { FilterList(OrderByFilter("", orderByFilterOptions, "popular")) }
|
protected open val popularFilter by lazy { FilterList(OrderByFilter("", orderByFilterOptions, "popular")) }
|
||||||
protected val latestFilter by lazy { FilterList(OrderByFilter("", orderByFilterOptions, "update")) }
|
protected open val latestFilter by lazy { FilterList(OrderByFilter("", orderByFilterOptions, "update")) }
|
||||||
|
|
||||||
protected class ProjectFilter(
|
protected class ProjectFilter(
|
||||||
name: String,
|
name: String,
|
||||||
|
@ -603,7 +603,7 @@ abstract class MangaThemesia(
|
||||||
(!strict && url.pathSegments.size == n + 1 && url.pathSegments[n].isEmpty())
|
(!strict && url.pathSegments.size == n + 1 && url.pathSegments[n].isEmpty())
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun parseGenres(document: Document): List<GenreData>? {
|
protected open fun parseGenres(document: Document): List<GenreData>? {
|
||||||
return document.selectFirst("ul.genrez")?.select("li")?.map { li ->
|
return document.selectFirst("ul.genrez")?.select("li")?.map { li ->
|
||||||
GenreData(
|
GenreData(
|
||||||
li.selectFirst("label")!!.text(),
|
li.selectFirst("label")!!.text(),
|
||||||
|
|
|
@ -1,9 +0,0 @@
|
||||||
plugins {
|
|
||||||
id("lib-multisrc")
|
|
||||||
}
|
|
||||||
|
|
||||||
baseVersionCode = 1
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
api(project(":lib:dataimage"))
|
|
||||||
}
|
|
|
@ -1,141 +0,0 @@
|
||||||
package eu.kanade.tachiyomi.multisrc.po2scans
|
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.lib.dataimage.DataImageInterceptor
|
|
||||||
import eu.kanade.tachiyomi.lib.dataimage.dataImageAsUrl
|
|
||||||
import eu.kanade.tachiyomi.network.GET
|
|
||||||
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.ParsedHttpSource
|
|
||||||
import org.jsoup.nodes.Document
|
|
||||||
import org.jsoup.nodes.Element
|
|
||||||
import rx.Observable
|
|
||||||
import java.text.SimpleDateFormat
|
|
||||||
import java.util.Locale
|
|
||||||
|
|
||||||
abstract class PO2Scans(
|
|
||||||
override val name: String,
|
|
||||||
override val baseUrl: String,
|
|
||||||
override val lang: String,
|
|
||||||
private val dateFormat: SimpleDateFormat = SimpleDateFormat("dd MMMM, yy", Locale.ENGLISH),
|
|
||||||
) : ParsedHttpSource() {
|
|
||||||
|
|
||||||
override val supportsLatest = true
|
|
||||||
|
|
||||||
override val client = network.cloudflareClient.newBuilder()
|
|
||||||
.addInterceptor(DataImageInterceptor())
|
|
||||||
.build()
|
|
||||||
|
|
||||||
override fun headersBuilder() = super.headersBuilder()
|
|
||||||
.add("Referer", "$baseUrl/")
|
|
||||||
|
|
||||||
// popular
|
|
||||||
override fun popularMangaRequest(page: Int) = GET("$baseUrl/series", headers)
|
|
||||||
|
|
||||||
override fun popularMangaSelector() = "div.series-list"
|
|
||||||
|
|
||||||
override fun popularMangaFromElement(element: Element) = SManga.create().apply {
|
|
||||||
setUrlWithoutDomain(element.selectFirst("div > a")!!.absUrl("href"))
|
|
||||||
title = element.selectFirst("div > h2")!!.text()
|
|
||||||
thumbnail_url = element.selectFirst("img")?.absUrl("data-src")
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: add page selectors & url parameters when site have enough series for pagination
|
|
||||||
override fun popularMangaNextPageSelector() = null
|
|
||||||
|
|
||||||
// latest
|
|
||||||
override fun latestUpdatesRequest(page: Int) = GET(baseUrl, headers)
|
|
||||||
|
|
||||||
override fun latestUpdatesSelector() = "div.chap"
|
|
||||||
|
|
||||||
override fun latestUpdatesFromElement(element: Element) = SManga.create().apply {
|
|
||||||
element.selectFirst("div.chap-title a")!!.let {
|
|
||||||
setUrlWithoutDomain(it.absUrl("href"))
|
|
||||||
title = it.text()
|
|
||||||
}
|
|
||||||
thumbnail_url = element.selectFirst("img")?.absUrl("data-src")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector()
|
|
||||||
|
|
||||||
// search
|
|
||||||
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
|
|
||||||
if (!query.startsWith(SLUG_SEARCH_PREFIX)) {
|
|
||||||
return super.fetchSearchManga(page, query, filters)
|
|
||||||
}
|
|
||||||
|
|
||||||
val url = "/series/${query.substringAfter(SLUG_SEARCH_PREFIX)}"
|
|
||||||
return fetchMangaDetails(SManga.create().apply { this.url = url })
|
|
||||||
.map {
|
|
||||||
it.url = url
|
|
||||||
MangasPage(listOf(it), false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList) =
|
|
||||||
GET("$baseUrl/series?search=$query", headers)
|
|
||||||
|
|
||||||
override fun searchMangaSelector() = popularMangaSelector()
|
|
||||||
|
|
||||||
override fun searchMangaFromElement(element: Element) = popularMangaFromElement(element)
|
|
||||||
|
|
||||||
override fun searchMangaNextPageSelector() = popularMangaNextPageSelector()
|
|
||||||
|
|
||||||
// manga details
|
|
||||||
override fun mangaDetailsParse(document: Document): SManga {
|
|
||||||
return SManga.create().apply {
|
|
||||||
title = document.selectFirst(".title")!!.text()
|
|
||||||
author = document.select(".author > span:nth-child(2)").text()
|
|
||||||
artist = author
|
|
||||||
status = document.select(".status > span:nth-child(2)").text().parseStatus()
|
|
||||||
description = document.select(".summary p").text()
|
|
||||||
thumbnail_url = document.select("div.series-image img").attr("abs:src")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// chapter list
|
|
||||||
override fun chapterListSelector() = "div.chap"
|
|
||||||
|
|
||||||
override fun chapterFromElement(element: Element) = SChapter.create().apply {
|
|
||||||
element.selectFirst("a")!!.let {
|
|
||||||
setUrlWithoutDomain(it.absUrl("href"))
|
|
||||||
name = it.text()
|
|
||||||
}
|
|
||||||
date_upload = parseDate(element.select("div > div > span:nth-child(2)").text())
|
|
||||||
}
|
|
||||||
|
|
||||||
// page list
|
|
||||||
override fun pageListParse(document: Document) =
|
|
||||||
document.select(".swiper-slide img").mapIndexed { index, img ->
|
|
||||||
Page(index, imageUrl = img.imgAttr())
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun imageUrlParse(document: Document) = throw UnsupportedOperationException()
|
|
||||||
|
|
||||||
private val statusOngoing = listOf("ongoing", "devam ediyor")
|
|
||||||
private val statusCompleted = listOf("complete", "tamamlandı", "bitti")
|
|
||||||
|
|
||||||
private fun String.parseStatus(): Int {
|
|
||||||
return when (this.lowercase()) {
|
|
||||||
in statusOngoing -> SManga.ONGOING
|
|
||||||
in statusCompleted -> SManga.COMPLETED
|
|
||||||
else -> SManga.UNKNOWN
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun Element.imgAttr(): String = when {
|
|
||||||
hasAttr("data-pagespeed-high-res-src") -> dataImageAsUrl("data-pagespeed-high-res-src")
|
|
||||||
hasAttr("data-pagespeed-lazy-src") -> dataImageAsUrl("data-pagespeed-lazy-src")
|
|
||||||
else -> dataImageAsUrl("src")
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun parseDate(dateStr: String) =
|
|
||||||
runCatching { dateFormat.parse(dateStr)!!.time }
|
|
||||||
.getOrDefault(0L)
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val SLUG_SEARCH_PREFIX = "slug:"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,7 +1,7 @@
|
||||||
ext {
|
ext {
|
||||||
extName = 'Akuma'
|
extName = 'Akuma'
|
||||||
extClass = '.AkumaFactory'
|
extClass = '.AkumaFactory'
|
||||||
extVersionCode = 4
|
extVersionCode = 5
|
||||||
isNsfw = true
|
isNsfw = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,13 @@
|
||||||
package eu.kanade.tachiyomi.extension.all.akuma
|
package eu.kanade.tachiyomi.extension.all.akuma
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import android.content.SharedPreferences
|
||||||
|
import androidx.preference.PreferenceScreen
|
||||||
|
import androidx.preference.SwitchPreferenceCompat
|
||||||
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.interceptor.rateLimit
|
import eu.kanade.tachiyomi.network.interceptor.rateLimit
|
||||||
|
import eu.kanade.tachiyomi.source.ConfigurableSource
|
||||||
import eu.kanade.tachiyomi.source.model.FilterList
|
import eu.kanade.tachiyomi.source.model.FilterList
|
||||||
import eu.kanade.tachiyomi.source.model.MangasPage
|
import eu.kanade.tachiyomi.source.model.MangasPage
|
||||||
import eu.kanade.tachiyomi.source.model.Page
|
import eu.kanade.tachiyomi.source.model.Page
|
||||||
|
@ -20,6 +25,8 @@ import okhttp3.Response
|
||||||
import org.jsoup.nodes.Document
|
import org.jsoup.nodes.Document
|
||||||
import org.jsoup.nodes.Element
|
import org.jsoup.nodes.Element
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.text.ParseException
|
import java.text.ParseException
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
|
@ -29,7 +36,7 @@ import java.util.TimeZone
|
||||||
class Akuma(
|
class Akuma(
|
||||||
override val lang: String,
|
override val lang: String,
|
||||||
private val akumaLang: String,
|
private val akumaLang: String,
|
||||||
) : ParsedHttpSource() {
|
) : ConfigurableSource, ParsedHttpSource() {
|
||||||
|
|
||||||
override val name = "Akuma"
|
override val name = "Akuma"
|
||||||
|
|
||||||
|
@ -105,6 +112,23 @@ class Akuma(
|
||||||
return storedToken!!
|
return storedToken!!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val preferences: SharedPreferences by lazy {
|
||||||
|
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val displayFullTitle: Boolean get() = preferences.getBoolean(PREF_TITLE, false)
|
||||||
|
|
||||||
|
private val shortenTitleRegex = Regex("""(\[[^]]*]|[({][^)}]*[)}])""")
|
||||||
|
private fun String.shortenTitle() = this.replace(shortenTitleRegex, "").trim()
|
||||||
|
|
||||||
|
override fun setupPreferenceScreen(screen: PreferenceScreen) {
|
||||||
|
SwitchPreferenceCompat(screen.context).apply {
|
||||||
|
key = PREF_TITLE
|
||||||
|
title = "Display manga title as full title"
|
||||||
|
setDefaultValue(false)
|
||||||
|
}.also(screen::addPreference)
|
||||||
|
}
|
||||||
|
|
||||||
override fun popularMangaRequest(page: Int): Request {
|
override fun popularMangaRequest(page: Int): Request {
|
||||||
val payload = FormBody.Builder()
|
val payload = FormBody.Builder()
|
||||||
.add("view", "3")
|
.add("view", "3")
|
||||||
|
@ -149,7 +173,9 @@ class Akuma(
|
||||||
override fun popularMangaFromElement(element: Element): SManga {
|
override fun popularMangaFromElement(element: Element): SManga {
|
||||||
return SManga.create().apply {
|
return SManga.create().apply {
|
||||||
setUrlWithoutDomain(element.select("a").attr("href"))
|
setUrlWithoutDomain(element.select("a").attr("href"))
|
||||||
title = element.select(".overlay-title").text()
|
title = element.select(".overlay-title").text().replace("\"", "").let {
|
||||||
|
if (displayFullTitle) it.trim() else it.shortenTitle()
|
||||||
|
}
|
||||||
thumbnail_url = element.select("img").attr("abs:src")
|
thumbnail_url = element.select("img").attr("abs:src")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -176,7 +202,7 @@ class Akuma(
|
||||||
val finalQuery: MutableList<String> = mutableListOf(query)
|
val finalQuery: MutableList<String> = mutableListOf(query)
|
||||||
|
|
||||||
if (lang != "all") {
|
if (lang != "all") {
|
||||||
finalQuery.add("language: $akumaLang$")
|
finalQuery.add("language:$akumaLang$")
|
||||||
}
|
}
|
||||||
filters.forEach { filter ->
|
filters.forEach { filter ->
|
||||||
when (filter) {
|
when (filter) {
|
||||||
|
@ -220,7 +246,9 @@ class Akuma(
|
||||||
|
|
||||||
override fun mangaDetailsParse(document: Document) = with(document) {
|
override fun mangaDetailsParse(document: Document) = with(document) {
|
||||||
SManga.create().apply {
|
SManga.create().apply {
|
||||||
title = select(".entry-title").text()
|
title = select(".entry-title").text().replace("\"", "").let {
|
||||||
|
if (displayFullTitle) it.trim() else it.shortenTitle()
|
||||||
|
}
|
||||||
thumbnail_url = select(".img-thumbnail").attr("abs:src")
|
thumbnail_url = select(".img-thumbnail").attr("abs:src")
|
||||||
|
|
||||||
author = select(".group~.value").eachText().joinToString()
|
author = select(".group~.value").eachText().joinToString()
|
||||||
|
@ -302,6 +330,7 @@ class Akuma(
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val PREFIX_ID = "id:"
|
const val PREFIX_ID = "id:"
|
||||||
|
private const val PREF_TITLE = "pref_title"
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun latestUpdatesRequest(page: Int) = throw UnsupportedOperationException()
|
override fun latestUpdatesRequest(page: Int) = throw UnsupportedOperationException()
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
ext {
|
||||||
|
extName = 'ComicsKingdom'
|
||||||
|
extClass = '.ComicsKingdomFactory'
|
||||||
|
extVersionCode = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
apply from: "$rootDir/common.gradle"
|
After Width: | Height: | Size: 7.1 KiB |
After Width: | Height: | Size: 3.5 KiB |
After Width: | Height: | Size: 10 KiB |
After Width: | Height: | Size: 20 KiB |
After Width: | Height: | Size: 29 KiB |
|
@ -0,0 +1,44 @@
|
||||||
|
package eu.kanade.tachiyomi.extension.all.comicskingdom
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class Chapter(
|
||||||
|
val id: Int,
|
||||||
|
val date: String,
|
||||||
|
val assets: Assets?,
|
||||||
|
val link: String,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class Assets(
|
||||||
|
val single: AssetData,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class AssetData(
|
||||||
|
val url: String,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class Manga(
|
||||||
|
val id: Int,
|
||||||
|
val link: String,
|
||||||
|
val title: Rendered,
|
||||||
|
val content: Rendered,
|
||||||
|
val meta: MangaMeta,
|
||||||
|
val yoast_head: String,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class MangaMeta(
|
||||||
|
val ck_byline_on_app: String,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class Rendered(
|
||||||
|
val rendered: String,
|
||||||
|
)
|
||||||
|
|
||||||
|
val ChapterFields = Chapter.javaClass.fields.joinToString(",") { it.name }
|
||||||
|
val MangaFields = Manga.javaClass.fields.joinToString(",") { it.name }
|
|
@ -0,0 +1,311 @@
|
||||||
|
package eu.kanade.tachiyomi.extension.all.comicskingdom
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import android.content.SharedPreferences
|
||||||
|
import androidx.preference.PreferenceScreen
|
||||||
|
import eu.kanade.tachiyomi.network.GET
|
||||||
|
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 okhttp3.HttpUrl
|
||||||
|
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||||
|
import okhttp3.Request
|
||||||
|
import okhttp3.Response
|
||||||
|
import org.jsoup.Jsoup
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.Locale
|
||||||
|
import kotlin.math.ceil
|
||||||
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
|
class ComicsKingdom(override val lang: String) : ConfigurableSource, HttpSource() {
|
||||||
|
|
||||||
|
override val name = "Comics Kingdom"
|
||||||
|
override val baseUrl = "https://wp.comicskingdom.com"
|
||||||
|
override val supportsLatest = true
|
||||||
|
|
||||||
|
private val json: Json by injectLazy()
|
||||||
|
private val dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.getDefault())
|
||||||
|
private val compactChapterCountRegex = Regex("\"totalItems\":(\\d+)")
|
||||||
|
private val thumbnailUrlRegex = Regex("thumbnailUrl\":\"(\\S+)\",\"dateP")
|
||||||
|
|
||||||
|
private val mangaPerPage = 20
|
||||||
|
private val chapterPerPage = 100
|
||||||
|
|
||||||
|
private fun mangaApiUrl(): HttpUrl.Builder =
|
||||||
|
baseUrl.toHttpUrl().newBuilder().apply {
|
||||||
|
addPathSegments("wp-json/wp/v2")
|
||||||
|
addPathSegment("ck_feature")
|
||||||
|
addQueryParameter("per_page", mangaPerPage.toString())
|
||||||
|
addQueryParameter("_fields", MangaFields)
|
||||||
|
addQueryParameter("ck_language", if (lang == "es") "spanish" else "english")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun chapterApiUrl(): HttpUrl.Builder = baseUrl.toHttpUrl().newBuilder().apply {
|
||||||
|
addPathSegments("wp-json/wp/v2")
|
||||||
|
addPathSegment("ck_comic")
|
||||||
|
addQueryParameter("per_page", chapterPerPage.toString())
|
||||||
|
addQueryParameter("_fields", ChapterFields)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getReq(orderBy: String, page: Int): Request = GET(
|
||||||
|
mangaApiUrl().apply {
|
||||||
|
addQueryParameter("orderBy", orderBy)
|
||||||
|
addQueryParameter("page", page.toString())
|
||||||
|
}.build(),
|
||||||
|
headers,
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun popularMangaRequest(page: Int): Request = getReq("relevance", page)
|
||||||
|
override fun latestUpdatesRequest(page: Int): Request = getReq("modified", page)
|
||||||
|
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request =
|
||||||
|
GET(
|
||||||
|
mangaApiUrl().apply {
|
||||||
|
addQueryParameter("search", query)
|
||||||
|
addQueryParameter("page", page.toString())
|
||||||
|
|
||||||
|
if (!filters.isEmpty()) {
|
||||||
|
for (filter in filters) {
|
||||||
|
when (filter) {
|
||||||
|
is OrderFilter -> {
|
||||||
|
addQueryParameter("orderby", filter.getValue())
|
||||||
|
}
|
||||||
|
|
||||||
|
is GenreList -> {
|
||||||
|
if (filter.included.isNotEmpty()) {
|
||||||
|
addQueryParameter(
|
||||||
|
"ck_genre",
|
||||||
|
filter.included.joinToString(","),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (filter.excluded.isNotEmpty()) {
|
||||||
|
addQueryParameter(
|
||||||
|
"ck_genre_exclude",
|
||||||
|
filter.excluded.joinToString(","),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.build(),
|
||||||
|
headers,
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun searchMangaParse(response: Response): MangasPage {
|
||||||
|
val list = json.decodeFromString<List<Manga>>(response.body.string())
|
||||||
|
return MangasPage(
|
||||||
|
list.map {
|
||||||
|
SManga.create().apply {
|
||||||
|
thumbnail_url = thumbnailUrlRegex.find(it.yoast_head)?.groupValues?.get(1)
|
||||||
|
setUrlWithoutDomain(
|
||||||
|
mangaApiUrl().apply {
|
||||||
|
addPathSegment(it.id.toString())
|
||||||
|
addQueryParameter("slug", it.link.toHttpUrl().pathSegments.last())
|
||||||
|
}
|
||||||
|
.build().toString(),
|
||||||
|
)
|
||||||
|
title = it.title.rendered
|
||||||
|
}
|
||||||
|
},
|
||||||
|
list.count() == mangaPerPage,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun popularMangaParse(response: Response): MangasPage = searchMangaParse(response)
|
||||||
|
override fun latestUpdatesParse(response: Response): MangasPage = searchMangaParse(response)
|
||||||
|
|
||||||
|
override fun mangaDetailsParse(response: Response): SManga = SManga.create().apply {
|
||||||
|
val mangaData = json.decodeFromString<Manga>(response.body.string())
|
||||||
|
title = mangaData.title.rendered
|
||||||
|
author = mangaData.meta.ck_byline_on_app.substringAfter("By").trim()
|
||||||
|
description = Jsoup.parse(mangaData.content.rendered).text()
|
||||||
|
status = SManga.UNKNOWN
|
||||||
|
thumbnail_url = thumbnailUrlRegex.find(mangaData.yoast_head)?.groupValues?.get(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getMangaUrl(manga: SManga): String =
|
||||||
|
"$baseUrl/${(baseUrl + manga.url).toHttpUrl().queryParameter("slug")}"
|
||||||
|
|
||||||
|
override fun chapterListParse(response: Response): List<SChapter> {
|
||||||
|
val mangaData = json.decodeFromString<Manga>(response.body.string())
|
||||||
|
val mangaName = mangaData.link.toHttpUrl().pathSegments.last()
|
||||||
|
|
||||||
|
if (shouldCompact()) {
|
||||||
|
val res = client.newCall(GET(mangaData.link)).execute()
|
||||||
|
val postCount = compactChapterCountRegex.findAll(res.body.string())
|
||||||
|
.find { result -> result.groupValues[1].toDouble() > 0 }!!.groupValues[1].toDouble()
|
||||||
|
res.close()
|
||||||
|
val maxPage = ceil(postCount / chapterPerPage)
|
||||||
|
return List(maxPage.roundToInt()) { idx ->
|
||||||
|
SChapter.create().apply {
|
||||||
|
chapter_number = idx * 0.01F
|
||||||
|
name =
|
||||||
|
"${idx * chapterPerPage + 1}-${if (postCount - (idx + 1) * chapterPerPage < 0) postCount.toInt() else (idx + 1) * chapterPerPage}"
|
||||||
|
setUrlWithoutDomain(
|
||||||
|
chapterApiUrl().apply {
|
||||||
|
addQueryParameter("orderBy", "date")
|
||||||
|
addQueryParameter("order", "asc")
|
||||||
|
addQueryParameter("ck_feature", mangaName)
|
||||||
|
addQueryParameter("page", (idx + 1).toString())
|
||||||
|
}.build().toString(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}.reversed()
|
||||||
|
}
|
||||||
|
|
||||||
|
val chapters = mutableListOf<SChapter>()
|
||||||
|
var pageNum = 1
|
||||||
|
|
||||||
|
var chapterData = getChapterList(mangaName, pageNum)
|
||||||
|
var chapterNum = 0.0F
|
||||||
|
|
||||||
|
while (chapterData != null) {
|
||||||
|
val list = chapterData.map {
|
||||||
|
chapterNum += 0.01F
|
||||||
|
SChapter.create().apply {
|
||||||
|
chapter_number = chapterNum
|
||||||
|
setUrlWithoutDomain(
|
||||||
|
chapterApiUrl().apply {
|
||||||
|
addPathSegment(it.id.toString())
|
||||||
|
addQueryParameter("slug", it.link.substringAfter(baseUrl))
|
||||||
|
}
|
||||||
|
.toString(),
|
||||||
|
)
|
||||||
|
date_upload = dateFormat.parse(it.date).time
|
||||||
|
name = it.date.substringBefore("T")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
chapters.addAll(list)
|
||||||
|
|
||||||
|
if (list.count() < 100) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
pageNum++
|
||||||
|
try {
|
||||||
|
chapterData = getChapterList(mangaName, pageNum)
|
||||||
|
} catch (exception: Exception) {
|
||||||
|
if (chapters.isNotEmpty()) {
|
||||||
|
return chapters
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return chapters
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getChapterList(mangaName: String, page: Int): List<Chapter> {
|
||||||
|
val url = chapterApiUrl().apply {
|
||||||
|
addQueryParameter("order", "desc")
|
||||||
|
addQueryParameter("ck_feature", mangaName)
|
||||||
|
addQueryParameter("page", page.toString())
|
||||||
|
}.build()
|
||||||
|
|
||||||
|
val call = client.newCall(GET(url, headers)).execute()
|
||||||
|
val body = call.body.string()
|
||||||
|
call.close()
|
||||||
|
return json.decodeFromString<List<Chapter>>(body)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getChapterUrl(chapter: SChapter): String {
|
||||||
|
if (shouldCompact()) {
|
||||||
|
return "$baseUrl/${(baseUrl + chapter.url).toHttpUrl().queryParameter("ck_feature")}"
|
||||||
|
}
|
||||||
|
return "$baseUrl/${(baseUrl + chapter.url).toHttpUrl().queryParameter("slug")}"
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun pageListParse(response: Response): List<Page> {
|
||||||
|
if (shouldCompact()) {
|
||||||
|
return json.decodeFromString<List<Chapter>>(response.body.string())
|
||||||
|
.mapIndexed { idx, chapter ->
|
||||||
|
Page(idx, imageUrl = chapter.assets!!.single.url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val chapter = json.decodeFromString<Chapter>(response.body.string())
|
||||||
|
return listOf(Page(0, imageUrl = chapter.assets!!.single.url))
|
||||||
|
}
|
||||||
|
|
||||||
|
private class OrderFilter :
|
||||||
|
Filter.Select<String>(
|
||||||
|
"Order by",
|
||||||
|
arrayOf(
|
||||||
|
"author",
|
||||||
|
"date",
|
||||||
|
"id",
|
||||||
|
"include",
|
||||||
|
"modified",
|
||||||
|
"parent",
|
||||||
|
"relevance",
|
||||||
|
"title",
|
||||||
|
"rand",
|
||||||
|
),
|
||||||
|
) {
|
||||||
|
fun getValue(): String = values[state]
|
||||||
|
}
|
||||||
|
|
||||||
|
private class Genre(name: String, val gid: String) : Filter.TriState(name)
|
||||||
|
private class GenreList(genres: List<Genre>) : Filter.Group<Genre>("Genres", genres) {
|
||||||
|
val included: List<String>
|
||||||
|
get() = state.filter { it.isIncluded() }.map { it.gid }
|
||||||
|
|
||||||
|
val excluded: List<String>
|
||||||
|
get() = state.filter { it.isExcluded() }.map { it.gid }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getFilterList() = FilterList(
|
||||||
|
OrderFilter(),
|
||||||
|
GenreList(getGenreList()),
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun getGenreList() = listOf(
|
||||||
|
Genre("Action", "action"),
|
||||||
|
Genre("Adventure", "adventure"),
|
||||||
|
Genre("Classic", "classic"),
|
||||||
|
Genre("Comedy", "comedy"),
|
||||||
|
Genre("Crime", "crime"),
|
||||||
|
Genre("Fantasy", "fantasy"),
|
||||||
|
Genre("Gag Cartoons", "gag-cartoons"),
|
||||||
|
Genre("Mystery", "mystery"),
|
||||||
|
Genre("New Arrivals", "new-arrivals"),
|
||||||
|
Genre("Non-Fiction", "non-fiction"),
|
||||||
|
Genre("OffBeat", "offbeat"),
|
||||||
|
Genre("Political Cartoons", "political-cartoons"),
|
||||||
|
Genre("Romance", "romance"),
|
||||||
|
Genre("Sci-Fi", "sci-fi"),
|
||||||
|
Genre("Slice Of Life", "slice-of-life"),
|
||||||
|
Genre("Superhero", "superhero"),
|
||||||
|
Genre("Vintage", "vintage"),
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun setupPreferenceScreen(screen: PreferenceScreen) {
|
||||||
|
val compactpref = androidx.preference.CheckBoxPreference(screen.context).apply {
|
||||||
|
key = "compactPref"
|
||||||
|
title = "Compact chapters"
|
||||||
|
summary =
|
||||||
|
"Unchecking this will make each daily/weekly upload into a chapter which can be very slow because some comics have 8000+ uploads"
|
||||||
|
isChecked = true
|
||||||
|
}
|
||||||
|
|
||||||
|
screen.addPreference(compactpref)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val preferences: SharedPreferences by lazy {
|
||||||
|
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun shouldCompact() = preferences.getBoolean("compactPref", true)
|
||||||
|
|
||||||
|
override fun imageUrlParse(response: Response): String = throw UnsupportedOperationException()
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
package eu.kanade.tachiyomi.extension.all.comicskingdom
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.source.Source
|
||||||
|
import eu.kanade.tachiyomi.source.SourceFactory
|
||||||
|
|
||||||
|
class ComicsKingdomFactory : SourceFactory {
|
||||||
|
override fun createSources(): List<Source> = listOf(ComicsKingdom("en"), ComicsKingdom("es"))
|
||||||
|
}
|
|
@ -3,7 +3,7 @@ ext {
|
||||||
extClass = '.ComicsValley'
|
extClass = '.ComicsValley'
|
||||||
themePkg = 'madara'
|
themePkg = 'madara'
|
||||||
baseUrl = 'https://comicsvalley.com'
|
baseUrl = 'https://comicsvalley.com'
|
||||||
overrideVersionCode = 1
|
overrideVersionCode = 2
|
||||||
isNsfw = true
|
isNsfw = true
|
||||||
}
|
}
|
||||||
|
|
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 4.7 KiB |
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.4 KiB |
Before Width: | Height: | Size: 7.7 KiB After Width: | Height: | Size: 7.7 KiB |
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 27 KiB |
|
@ -0,0 +1,14 @@
|
||||||
|
package eu.kanade.tachiyomi.extension.all.comicsvalley
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.multisrc.madara.Madara
|
||||||
|
|
||||||
|
class ComicsValley : Madara(
|
||||||
|
"Comics Valley",
|
||||||
|
"https://comicsvalley.com",
|
||||||
|
"all",
|
||||||
|
) {
|
||||||
|
override val mangaSubString = "comics-new"
|
||||||
|
override val useNewChapterEndpoint = true
|
||||||
|
override val useLoadMoreRequest = LoadMoreStrategy.Always
|
||||||
|
override val id = 1103204227230640533
|
||||||
|
}
|
|
@ -1,8 +1,8 @@
|
||||||
ext {
|
ext {
|
||||||
extName = 'Hentai Keyfi'
|
extName = 'Eromanhwa'
|
||||||
extClass = '.HentaiKeyfi'
|
extClass = '.Eromanhwa'
|
||||||
themePkg = 'madara'
|
themePkg = 'madara'
|
||||||
baseUrl = 'https://hentaikeyfi.com'
|
baseUrl = 'https://eromanhwa.org'
|
||||||
overrideVersionCode = 1
|
overrideVersionCode = 1
|
||||||
isNsfw = true
|
isNsfw = true
|
||||||
}
|
}
|
After Width: | Height: | Size: 4.1 KiB |
After Width: | Height: | Size: 2.4 KiB |
After Width: | Height: | Size: 5.8 KiB |
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 16 KiB |
|
@ -0,0 +1,12 @@
|
||||||
|
package eu.kanade.tachiyomi.extension.all.eromanhwa
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.multisrc.madara.Madara
|
||||||
|
|
||||||
|
class Eromanhwa : Madara(
|
||||||
|
"Eromanhwa",
|
||||||
|
"https://eromanhwa.org",
|
||||||
|
"all",
|
||||||
|
) {
|
||||||
|
override val id = 3597355706480775153 // accidently set lang to en...
|
||||||
|
override val useNewChapterEndpoint = true
|
||||||
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
ext {
|
ext {
|
||||||
extName = 'Hitomi'
|
extName = 'Hitomi'
|
||||||
extClass = '.HitomiFactory'
|
extClass = '.HitomiFactory'
|
||||||
extVersionCode = 32
|
extVersionCode = 33
|
||||||
isNsfw = true
|
isNsfw = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -517,6 +517,9 @@ class Hitomi(
|
||||||
"https://${subDomain}tn.$domain/webpbigtn/${thumbPathFromHash(hash)}/$hash.webp"
|
"https://${subDomain}tn.$domain/webpbigtn/${thumbPathFromHash(hash)}/$hash.webp"
|
||||||
}
|
}
|
||||||
description = buildString {
|
description = buildString {
|
||||||
|
japaneseTitle?.let {
|
||||||
|
append("Japanese title: ", it, "\n")
|
||||||
|
}
|
||||||
parodys?.joinToString { it.formatted }?.let {
|
parodys?.joinToString { it.formatted }?.let {
|
||||||
append("Series: ", it, "\n")
|
append("Series: ", it, "\n")
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import kotlinx.serialization.json.JsonPrimitive
|
||||||
class Gallery(
|
class Gallery(
|
||||||
val galleryurl: String,
|
val galleryurl: String,
|
||||||
val title: String,
|
val title: String,
|
||||||
|
val japaneseTitle: String?,
|
||||||
val date: String,
|
val date: String,
|
||||||
val type: String?,
|
val type: String?,
|
||||||
val language: String?,
|
val language: String?,
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
ext {
|
ext {
|
||||||
extName = 'Unitoon'
|
extName = 'KDT Scans'
|
||||||
extClass = '.Unitoon'
|
extClass = '.KdtScans'
|
||||||
themePkg = 'madara'
|
themePkg = 'madara'
|
||||||
baseUrl = 'https://lectorunitoon.com'
|
baseUrl = 'https://kdtscans.com'
|
||||||
overrideVersionCode = 0
|
overrideVersionCode = 0
|
||||||
}
|
}
|
||||||
|
|
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 4.8 KiB |
After Width: | Height: | Size: 7.7 KiB |
After Width: | Height: | Size: 12 KiB |
|
@ -0,0 +1,41 @@
|
||||||
|
package eu.kanade.tachiyomi.extension.all.kdtscans
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.multisrc.madara.Madara
|
||||||
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
|
import org.jsoup.nodes.Document
|
||||||
|
import org.jsoup.nodes.Element
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
|
class KdtScans : Madara(
|
||||||
|
"KDT Scans",
|
||||||
|
"https://kdtscans.com",
|
||||||
|
"all",
|
||||||
|
dateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale("es")),
|
||||||
|
) {
|
||||||
|
override val useNewChapterEndpoint = true
|
||||||
|
override val fetchGenres = false
|
||||||
|
|
||||||
|
override fun popularMangaFromElement(element: Element): SManga {
|
||||||
|
return super.popularMangaFromElement(element).apply {
|
||||||
|
title = title.cleanupTitle()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun searchMangaFromElement(element: Element): SManga {
|
||||||
|
return super.searchMangaFromElement(element).apply {
|
||||||
|
title = title.cleanupTitle()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun mangaDetailsParse(document: Document): SManga {
|
||||||
|
return super.mangaDetailsParse(document).apply {
|
||||||
|
title = title.cleanupTitle()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun String.cleanupTitle() = replace(titleCleanupRegex, "").trim()
|
||||||
|
|
||||||
|
private val titleCleanupRegex =
|
||||||
|
Regex("""^\[(ESPAÑOL|English)\]\s+(–\s+)?""", RegexOption.IGNORE_CASE)
|
||||||
|
}
|
|
@ -2,7 +2,7 @@
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<application>
|
<application>
|
||||||
<activity
|
<activity
|
||||||
android:name=".en.kiutaku.KiutakuUrlActivity"
|
android:name=".all.kiutaku.KiutakuUrlActivity"
|
||||||
android:excludeFromRecents="true"
|
android:excludeFromRecents="true"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:theme="@android:style/Theme.NoDisplay">
|
android:theme="@android:style/Theme.NoDisplay">
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.7 KiB |
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 4.4 KiB |
Before Width: | Height: | Size: 7.9 KiB After Width: | Height: | Size: 7.9 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
@ -1,4 +1,4 @@
|
||||||
package eu.kanade.tachiyomi.extension.en.kiutaku
|
package eu.kanade.tachiyomi.extension.all.kiutaku
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.network.GET
|
import eu.kanade.tachiyomi.network.GET
|
||||||
import eu.kanade.tachiyomi.network.asObservableSuccess
|
import eu.kanade.tachiyomi.network.asObservableSuccess
|
||||||
|
@ -24,7 +24,9 @@ class Kiutaku : ParsedHttpSource() {
|
||||||
|
|
||||||
override val baseUrl = "https://kiutaku.com"
|
override val baseUrl = "https://kiutaku.com"
|
||||||
|
|
||||||
override val lang = "en"
|
override val lang = "all"
|
||||||
|
|
||||||
|
override val id = 3040035304874076216
|
||||||
|
|
||||||
override val supportsLatest = true
|
override val supportsLatest = true
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package eu.kanade.tachiyomi.extension.en.kiutaku
|
package eu.kanade.tachiyomi.extension.all.kiutaku
|
||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.ActivityNotFoundException
|
import android.content.ActivityNotFoundException
|
|
@ -1,7 +1,7 @@
|
||||||
ext {
|
ext {
|
||||||
extName = 'LANraragi'
|
extName = 'LANraragi'
|
||||||
extClass = '.LANraragiFactory'
|
extClass = '.LANraragiFactory'
|
||||||
extVersionCode = 17
|
extVersionCode = 18
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: "$rootDir/common.gradle"
|
apply from: "$rootDir/common.gradle"
|
||||||
|
|
|
@ -7,6 +7,7 @@ data class Archive(
|
||||||
val arcid: String,
|
val arcid: String,
|
||||||
val isnew: String,
|
val isnew: String,
|
||||||
val tags: String?,
|
val tags: String?,
|
||||||
|
val summary: String?,
|
||||||
val title: String,
|
val title: String,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -25,7 +26,6 @@ data class ArchiveSearchResult(
|
||||||
@Serializable
|
@Serializable
|
||||||
data class Category(
|
data class Category(
|
||||||
val id: String,
|
val id: String,
|
||||||
val last_used: String,
|
|
||||||
val name: String,
|
val name: String,
|
||||||
val pinned: String,
|
val pinned: String,
|
||||||
)
|
)
|
||||||
|
|
|
@ -242,7 +242,7 @@ open class LANraragi(private val suffix: String = "") : ConfigurableSource, Unme
|
||||||
private fun archiveToSManga(archive: Archive) = SManga.create().apply {
|
private fun archiveToSManga(archive: Archive) = SManga.create().apply {
|
||||||
url = "/reader?id=${archive.arcid}"
|
url = "/reader?id=${archive.arcid}"
|
||||||
title = archive.title
|
title = archive.title
|
||||||
description = archive.title
|
description = if (archive.summary.isNullOrBlank()) archive.title else archive.summary
|
||||||
thumbnail_url = getThumbnailUri(archive.arcid)
|
thumbnail_url = getThumbnailUri(archive.arcid)
|
||||||
genre = archive.tags?.replace(",", ", ")
|
genre = archive.tags?.replace(",", ", ")
|
||||||
artist = getArtist(archive.tags)
|
artist = getArtist(archive.tags)
|
||||||
|
@ -406,12 +406,10 @@ open class LANraragi(private val suffix: String = "") : ConfigurableSource, Unme
|
||||||
|
|
||||||
private fun getCategoryPairs(categories: List<Category>): Array<Pair<String?, String>> {
|
private fun getCategoryPairs(categories: List<Category>): Array<Pair<String?, String>> {
|
||||||
// Empty pair to disable. Sort by pinned status then name for convenience.
|
// Empty pair to disable. Sort by pinned status then name for convenience.
|
||||||
// Web client sort is pinned > last_used but reflects between page changes.
|
|
||||||
|
|
||||||
val pin = "\uD83D\uDCCC "
|
val pin = "\uD83D\uDCCC "
|
||||||
|
|
||||||
// Maintain categories sync for next FilterList reset. If there's demand for it, it's now
|
// Maintain categories sync for next FilterList reset.
|
||||||
// possible to sort by last_used similar to the web client. Maybe an option toggle?
|
|
||||||
getCategories()
|
getCategories()
|
||||||
|
|
||||||
return listOf(Pair("", ""))
|
return listOf(Pair("", ""))
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
ext {
|
ext {
|
||||||
extName = 'MangaDex'
|
extName = 'MangaDex'
|
||||||
extClass = '.MangaDexFactory'
|
extClass = '.MangaDexFactory'
|
||||||
extVersionCode = 193
|
extVersionCode = 194
|
||||||
isNsfw = true
|
isNsfw = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -138,13 +138,6 @@ object MDConstants {
|
||||||
return "${altTitlesInDescPref}_$dexLang"
|
return "${altTitlesInDescPref}_$dexLang"
|
||||||
}
|
}
|
||||||
|
|
||||||
private const val customUserAgentPref = "customUserAgent"
|
|
||||||
fun getCustomUserAgentPrefKey(dexLang: String): String {
|
|
||||||
return "${customUserAgentPref}_$dexLang"
|
|
||||||
}
|
|
||||||
|
|
||||||
val defaultUserAgent = "Tachiyomi " + System.getProperty("http.agent")
|
|
||||||
|
|
||||||
private const val tagGroupContent = "content"
|
private const val tagGroupContent = "content"
|
||||||
private const val tagGroupFormat = "format"
|
private const val tagGroupFormat = "format"
|
||||||
private const val tagGroupGenre = "genre"
|
private const val tagGroupGenre = "genre"
|
||||||
|
|
|
@ -3,7 +3,6 @@ package eu.kanade.tachiyomi.extension.all.mangadex
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.preference.EditTextPreference
|
import androidx.preference.EditTextPreference
|
||||||
import androidx.preference.ListPreference
|
import androidx.preference.ListPreference
|
||||||
import androidx.preference.MultiSelectListPreference
|
import androidx.preference.MultiSelectListPreference
|
||||||
|
@ -56,6 +55,7 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St
|
||||||
|
|
||||||
private val preferences: SharedPreferences by lazy {
|
private val preferences: SharedPreferences by lazy {
|
||||||
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
||||||
|
.sanitizeExistingUuidPrefs()
|
||||||
}
|
}
|
||||||
|
|
||||||
private val helper = MangaDexHelper(lang)
|
private val helper = MangaDexHelper(lang)
|
||||||
|
@ -67,6 +67,7 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St
|
||||||
"Keiyoushi"
|
"Keiyoushi"
|
||||||
|
|
||||||
val builder = super.headersBuilder().apply {
|
val builder = super.headersBuilder().apply {
|
||||||
|
set("User-Agent", "Tachiyomi " + System.getProperty("http.agent"))
|
||||||
set("Referer", "$baseUrl/")
|
set("Referer", "$baseUrl/")
|
||||||
set("Origin", baseUrl)
|
set("Origin", baseUrl)
|
||||||
set("Extra", extraHeader)
|
set("Extra", extraHeader)
|
||||||
|
@ -78,13 +79,8 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St
|
||||||
override val client = network.client.newBuilder()
|
override val client = network.client.newBuilder()
|
||||||
.rateLimit(3)
|
.rateLimit(3)
|
||||||
.addInterceptor(MdAtHomeReportInterceptor(network.client, headers))
|
.addInterceptor(MdAtHomeReportInterceptor(network.client, headers))
|
||||||
.addInterceptor(MdUserAgentInterceptor(preferences, dexLang))
|
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
init {
|
|
||||||
preferences.sanitizeExistingUuidPrefs()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Popular manga section
|
// Popular manga section
|
||||||
|
|
||||||
override fun popularMangaRequest(page: Int): Request {
|
override fun popularMangaRequest(page: Int): Request {
|
||||||
|
@ -395,7 +391,7 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St
|
||||||
// Manga Details section
|
// Manga Details section
|
||||||
|
|
||||||
override fun getMangaUrl(manga: SManga): String {
|
override fun getMangaUrl(manga: SManga): String {
|
||||||
return baseUrl + manga.url + "/" + helper.titleToSlug(manga.title)
|
return baseUrl + manga.url.replace("/manga/", "/title/") + "/" + helper.titleToSlug(manga.title)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -761,30 +757,6 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val userAgentPref = EditTextPreference(screen.context).apply {
|
|
||||||
key = MDConstants.getCustomUserAgentPrefKey(dexLang)
|
|
||||||
title = helper.intl["set_custom_useragent"]
|
|
||||||
summary = helper.intl["set_custom_useragent_summary"]
|
|
||||||
dialogMessage = helper.intl.format(
|
|
||||||
"set_custom_useragent_dialog",
|
|
||||||
MDConstants.defaultUserAgent,
|
|
||||||
)
|
|
||||||
|
|
||||||
setDefaultValue(MDConstants.defaultUserAgent)
|
|
||||||
|
|
||||||
setOnPreferenceChangeListener { _, newValue ->
|
|
||||||
try {
|
|
||||||
Headers.Builder().add("User-Agent", newValue as String)
|
|
||||||
summary = newValue
|
|
||||||
true
|
|
||||||
} catch (e: Throwable) {
|
|
||||||
val errorMessage = helper.intl.format("set_custom_useragent_error_invalid", e.message)
|
|
||||||
Toast.makeText(screen.context, errorMessage, Toast.LENGTH_LONG).show()
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
screen.addPreference(coverQualityPref)
|
screen.addPreference(coverQualityPref)
|
||||||
screen.addPreference(tryUsingFirstVolumeCoverPref)
|
screen.addPreference(tryUsingFirstVolumeCoverPref)
|
||||||
screen.addPreference(dataSaverPref)
|
screen.addPreference(dataSaverPref)
|
||||||
|
@ -794,7 +766,6 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St
|
||||||
screen.addPreference(originalLanguagePref)
|
screen.addPreference(originalLanguagePref)
|
||||||
screen.addPreference(blockedGroupsPref)
|
screen.addPreference(blockedGroupsPref)
|
||||||
screen.addPreference(blockedUploaderPref)
|
screen.addPreference(blockedUploaderPref)
|
||||||
screen.addPreference(userAgentPref)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getFilterList(): FilterList =
|
override fun getFilterList(): FilterList =
|
||||||
|
@ -869,20 +840,14 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St
|
||||||
private val SharedPreferences.altTitlesInDesc
|
private val SharedPreferences.altTitlesInDesc
|
||||||
get() = getBoolean(MDConstants.getAltTitlesInDescPrefKey(dexLang), false)
|
get() = getBoolean(MDConstants.getAltTitlesInDescPrefKey(dexLang), false)
|
||||||
|
|
||||||
private val SharedPreferences.customUserAgent
|
|
||||||
get() = getString(
|
|
||||||
MDConstants.getCustomUserAgentPrefKey(dexLang),
|
|
||||||
MDConstants.defaultUserAgent,
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Previous versions of the extension allowed invalid UUID values to be stored in the
|
* Previous versions of the extension allowed invalid UUID values to be stored in the
|
||||||
* preferences. This method clear invalid UUIDs in case the user have updated from
|
* preferences. This method clear invalid UUIDs in case the user have updated from
|
||||||
* a previous version with that behaviour.
|
* a previous version with that behaviour.
|
||||||
*/
|
*/
|
||||||
private fun SharedPreferences.sanitizeExistingUuidPrefs() {
|
private fun SharedPreferences.sanitizeExistingUuidPrefs(): SharedPreferences {
|
||||||
if (getBoolean(MDConstants.getHasSanitizedUuidsPrefKey(dexLang), false)) {
|
if (getBoolean(MDConstants.getHasSanitizedUuidsPrefKey(dexLang), false)) {
|
||||||
return
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
val blockedGroups = getString(MDConstants.getBlockedGroupsPrefKey(dexLang), "")!!
|
val blockedGroups = getString(MDConstants.getBlockedGroupsPrefKey(dexLang), "")!!
|
||||||
|
@ -902,5 +867,7 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St
|
||||||
.putString(MDConstants.getBlockedUploaderPrefKey(dexLang), blockedUploaders)
|
.putString(MDConstants.getBlockedUploaderPrefKey(dexLang), blockedUploaders)
|
||||||
.putBoolean(MDConstants.getHasSanitizedUuidsPrefKey(dexLang), true)
|
.putBoolean(MDConstants.getHasSanitizedUuidsPrefKey(dexLang), true)
|
||||||
.apply()
|
.apply()
|
||||||
|
|
||||||
|
return this
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,39 +0,0 @@
|
||||||
package eu.kanade.tachiyomi.extension.all.mangadex
|
|
||||||
|
|
||||||
import android.content.SharedPreferences
|
|
||||||
import okhttp3.Interceptor
|
|
||||||
import okhttp3.Response
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Interceptor to set custom useragent for MangaDex
|
|
||||||
*/
|
|
||||||
class MdUserAgentInterceptor(
|
|
||||||
private val preferences: SharedPreferences,
|
|
||||||
private val dexLang: String,
|
|
||||||
) : Interceptor {
|
|
||||||
|
|
||||||
private val SharedPreferences.customUserAgent
|
|
||||||
get() = getString(
|
|
||||||
MDConstants.getCustomUserAgentPrefKey(dexLang),
|
|
||||||
MDConstants.defaultUserAgent,
|
|
||||||
)
|
|
||||||
|
|
||||||
override fun intercept(chain: Interceptor.Chain): Response {
|
|
||||||
val originalRequest = chain.request()
|
|
||||||
|
|
||||||
val newUserAgent = preferences.customUserAgent
|
|
||||||
?: return chain.proceed(originalRequest)
|
|
||||||
|
|
||||||
val originalHeaders = originalRequest.headers
|
|
||||||
|
|
||||||
val modifiedHeaders = originalHeaders.newBuilder()
|
|
||||||
.set("User-Agent", newUserAgent)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
val modifiedRequest = originalRequest.newBuilder()
|
|
||||||
.headers(modifiedHeaders)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
return chain.proceed(modifiedRequest)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -17,6 +17,8 @@ schedule_monthly=Monthly
|
||||||
schedule_other=Other
|
schedule_other=Other
|
||||||
schedule_trimonthly=Trimonthly
|
schedule_trimonthly=Trimonthly
|
||||||
schedule_weekly=Weekly
|
schedule_weekly=Weekly
|
||||||
|
subtitle_only=Show subtitle only
|
||||||
|
subtitle_only_summary=Removes the redundant chapter number from the chapter name.
|
||||||
serialization=Serialization: %s
|
serialization=Serialization: %s
|
||||||
split_double_pages=Split double pages
|
split_double_pages=Split double pages
|
||||||
split_double_pages_summary=Only a few titles supports disabling this setting.
|
split_double_pages_summary=Only a few titles supports disabling this setting.
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
ext {
|
ext {
|
||||||
extName = 'MANGA Plus by SHUEISHA'
|
extName = 'MANGA Plus by SHUEISHA'
|
||||||
extClass = '.MangaPlusFactory'
|
extClass = '.MangaPlusFactory'
|
||||||
extVersionCode = 52
|
extVersionCode = 53
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: "$rootDir/common.gradle"
|
apply from: "$rootDir/common.gradle"
|
||||||
|
|
|
@ -289,9 +289,10 @@ class MangaPlus(
|
||||||
}
|
}
|
||||||
|
|
||||||
val titleDetailView = result.success.titleDetailView!!
|
val titleDetailView = result.success.titleDetailView!!
|
||||||
|
val subtitleOnly = preferences.subtitleOnly()
|
||||||
|
|
||||||
return titleDetailView.chapterList
|
return titleDetailView.chapterList
|
||||||
.map(Chapter::toSChapter)
|
.map { it.toSChapter(subtitleOnly) }
|
||||||
.reversed()
|
.reversed()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -307,8 +308,8 @@ class MangaPlus(
|
||||||
private fun pageListRequest(chapterId: String): Request {
|
private fun pageListRequest(chapterId: String): Request {
|
||||||
val url = "$APP_API_URL/manga_viewer".toHttpUrl().newBuilder()
|
val url = "$APP_API_URL/manga_viewer".toHttpUrl().newBuilder()
|
||||||
.addQueryParameter("chapter_id", chapterId)
|
.addQueryParameter("chapter_id", chapterId)
|
||||||
.addQueryParameter("split", if (preferences.splitImages) "yes" else "no")
|
.addQueryParameter("split", if (preferences.splitImages()) "yes" else "no")
|
||||||
.addQueryParameter("img_quality", preferences.imageQuality)
|
.addQueryParameter("img_quality", preferences.imageQuality())
|
||||||
.addQueryParameter("ticket_reading", "no")
|
.addQueryParameter("ticket_reading", "no")
|
||||||
.addQueryParameter("free_reading", "yes")
|
.addQueryParameter("free_reading", "yes")
|
||||||
.addQueryParameter("subscription_reading", "no")
|
.addQueryParameter("subscription_reading", "no")
|
||||||
|
@ -378,8 +379,16 @@ class MangaPlus(
|
||||||
key = "${VER_PREF_KEY}_$lang",
|
key = "${VER_PREF_KEY}_$lang",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val titlePref = SwitchPreferenceCompat(screen.context).apply {
|
||||||
|
key = "${SUBTITLE_ONLY_KEY}_$lang"
|
||||||
|
title = intl["subtitle_only"]
|
||||||
|
summary = intl["subtitle_only_summary"]
|
||||||
|
setDefaultValue(SUBTITLE_ONLY_DEFAULT_VALUE)
|
||||||
|
}
|
||||||
|
|
||||||
screen.addPreference(qualityPref)
|
screen.addPreference(qualityPref)
|
||||||
screen.addPreference(splitPref)
|
screen.addPreference(splitPref)
|
||||||
|
screen.addPreference(titlePref)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun PreferenceScreen.addEditTextPreference(
|
private fun PreferenceScreen.addEditTextPreference(
|
||||||
|
@ -494,11 +503,11 @@ class MangaPlus(
|
||||||
json.decodeFromString(body.string())
|
json.decodeFromString(body.string())
|
||||||
}
|
}
|
||||||
|
|
||||||
private val SharedPreferences.imageQuality: String
|
private fun SharedPreferences.imageQuality(): String = getString("${QUALITY_PREF_KEY}_$lang", QUALITY_PREF_DEFAULT_VALUE)!!
|
||||||
get() = getString("${QUALITY_PREF_KEY}_$lang", QUALITY_PREF_DEFAULT_VALUE)!!
|
|
||||||
|
|
||||||
private val SharedPreferences.splitImages: Boolean
|
private fun SharedPreferences.splitImages(): Boolean = getBoolean("${SPLIT_PREF_KEY}_$lang", SPLIT_PREF_DEFAULT_VALUE)
|
||||||
get() = getBoolean("${SPLIT_PREF_KEY}_$lang", SPLIT_PREF_DEFAULT_VALUE)
|
|
||||||
|
private fun SharedPreferences.subtitleOnly(): Boolean = getBoolean("${SUBTITLE_ONLY_KEY}_$lang", SUBTITLE_ONLY_DEFAULT_VALUE)
|
||||||
|
|
||||||
private val SharedPreferences.appVersion: String?
|
private val SharedPreferences.appVersion: String?
|
||||||
get() = getString("${VER_PREF_KEY}_$lang", VER_PREF_DEFAULT_VALUE)
|
get() = getString("${VER_PREF_KEY}_$lang", VER_PREF_DEFAULT_VALUE)
|
||||||
|
@ -526,6 +535,9 @@ private val QUALITY_PREF_DEFAULT_VALUE = QUALITY_PREF_ENTRY_VALUES[2]
|
||||||
private const val SPLIT_PREF_KEY = "splitImage"
|
private const val SPLIT_PREF_KEY = "splitImage"
|
||||||
private const val SPLIT_PREF_DEFAULT_VALUE = true
|
private const val SPLIT_PREF_DEFAULT_VALUE = true
|
||||||
|
|
||||||
|
private const val SUBTITLE_ONLY_KEY = "subtitleOnly"
|
||||||
|
private const val SUBTITLE_ONLY_DEFAULT_VALUE = false
|
||||||
|
|
||||||
private const val VER_PREF_KEY = "appVer"
|
private const val VER_PREF_KEY = "appVer"
|
||||||
private const val VER_PREF_DEFAULT_VALUE = ""
|
private const val VER_PREF_DEFAULT_VALUE = ""
|
||||||
private const val SECRET_PREF_KEY = "accountSecret"
|
private const val SECRET_PREF_KEY = "accountSecret"
|
||||||
|
|
|
@ -322,11 +322,16 @@ class Chapter(
|
||||||
val isExpired: Boolean
|
val isExpired: Boolean
|
||||||
get() = subTitle == null
|
get() = subTitle == null
|
||||||
|
|
||||||
fun toSChapter(): SChapter = SChapter.create().apply {
|
fun toSChapter(subtitlePref: Boolean): SChapter = SChapter.create().apply {
|
||||||
name = "${this@Chapter.name} - $subTitle"
|
name = if (subtitlePref && subTitle != null) {
|
||||||
|
subTitle
|
||||||
|
} else {
|
||||||
|
"${this@Chapter.name} - $subTitle"
|
||||||
|
}
|
||||||
date_upload = 1000L * startTimeStamp
|
date_upload = 1000L * startTimeStamp
|
||||||
url = "#/viewer/$chapterId"
|
url = "#/viewer/$chapterId"
|
||||||
chapter_number = this@Chapter.name.substringAfter("#").toFloatOrNull() ?: -1f
|
chapter_number = this@Chapter.name.substringAfter("#").toFloatOrNull() ?: -1f
|
||||||
|
scanlator = "MANGA Plus"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,8 +0,0 @@
|
||||||
ext {
|
|
||||||
extName = 'Meitua.top'
|
|
||||||
extClass = '.MeituaTop'
|
|
||||||
extVersionCode = 6
|
|
||||||
isNsfw = true
|
|
||||||
}
|
|
||||||
|
|
||||||
apply from: "$rootDir/common.gradle"
|
|
Before Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 2.6 KiB |
Before Width: | Height: | Size: 4.3 KiB |
Before Width: | Height: | Size: 5.5 KiB |
|
@ -1,111 +0,0 @@
|
||||||
package eu.kanade.tachiyomi.extension.all.meituatop
|
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.network.GET
|
|
||||||
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 eu.kanade.tachiyomi.util.asJsoup
|
|
||||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
|
||||||
import okhttp3.Request
|
|
||||||
import okhttp3.Response
|
|
||||||
import org.jsoup.select.Evaluator
|
|
||||||
import rx.Observable
|
|
||||||
import java.text.SimpleDateFormat
|
|
||||||
import java.util.Locale
|
|
||||||
|
|
||||||
// Uses MACCMS http://www.maccms.la/
|
|
||||||
class MeituaTop : HttpSource() {
|
|
||||||
override val name = "Meitua.top"
|
|
||||||
override val lang = "all"
|
|
||||||
override val supportsLatest = false
|
|
||||||
|
|
||||||
override val baseUrl = "https://mt1.meitu1.sbs"
|
|
||||||
|
|
||||||
override fun popularMangaRequest(page: Int) = GET("$baseUrl/arttype/0b-$page.html", headers)
|
|
||||||
|
|
||||||
override fun popularMangaParse(response: Response): MangasPage {
|
|
||||||
val document = response.asJsoup()
|
|
||||||
val mangas = document.selectFirst(Evaluator.Class("thumbnail-group"))!!.children().map {
|
|
||||||
SManga.create().apply {
|
|
||||||
url = it.selectFirst(Evaluator.Tag("a"))!!.attr("href")
|
|
||||||
val image = it.selectFirst(Evaluator.Tag("img"))!!
|
|
||||||
title = image.attr("alt")
|
|
||||||
thumbnail_url = image.attr("src")
|
|
||||||
val info = it.selectFirst(Evaluator.Tag("p"))!!.ownText().split(" - ")
|
|
||||||
genre = info[0]
|
|
||||||
description = info[1]
|
|
||||||
status = SManga.COMPLETED
|
|
||||||
initialized = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val pageLinks = document.select(Evaluator.Class("page_link"))
|
|
||||||
if (pageLinks.isEmpty()) return MangasPage(mangas, false)
|
|
||||||
val lastPage = pageLinks[3].attr("href")
|
|
||||||
val hasNextPage = document.location().pageNumber() != lastPage.pageNumber()
|
|
||||||
return MangasPage(mangas, hasNextPage)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun latestUpdatesRequest(page: Int) = throw UnsupportedOperationException()
|
|
||||||
|
|
||||||
override fun latestUpdatesParse(response: Response) = throw UnsupportedOperationException()
|
|
||||||
|
|
||||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
|
||||||
if (query.isNotEmpty()) {
|
|
||||||
val url = "$baseUrl/artsearch/-------.html".toHttpUrl().newBuilder()
|
|
||||||
.addQueryParameter("wd", query)
|
|
||||||
.addQueryParameter("page", page.toString())
|
|
||||||
.toString()
|
|
||||||
return GET(url, headers)
|
|
||||||
}
|
|
||||||
|
|
||||||
val filter = filters.filterIsInstance<RegionFilter>().firstOrNull() ?: return popularMangaRequest(page)
|
|
||||||
return GET("$baseUrl/arttype/${21 + filter.state}b-$page.html", headers)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun searchMangaParse(response: Response) = popularMangaParse(response)
|
|
||||||
|
|
||||||
override fun fetchMangaDetails(manga: SManga): Observable<SManga> = Observable.just(manga)
|
|
||||||
|
|
||||||
override fun mangaDetailsParse(response: Response) = throw UnsupportedOperationException()
|
|
||||||
|
|
||||||
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
|
|
||||||
val chapter = SChapter.create().apply {
|
|
||||||
url = manga.url
|
|
||||||
name = "Gallery"
|
|
||||||
date_upload = dateFormat.parse(manga.description!!)!!.time
|
|
||||||
chapter_number = -2f
|
|
||||||
}
|
|
||||||
return Observable.just(listOf(chapter))
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun chapterListParse(response: Response) = throw UnsupportedOperationException()
|
|
||||||
|
|
||||||
override fun pageListParse(response: Response): List<Page> {
|
|
||||||
val document = response.asJsoup()
|
|
||||||
val images = document.selectFirst(Evaluator.Class("ttnr"))!!.select(Evaluator.Tag("img"))
|
|
||||||
.map { it.attr("src") }.distinct()
|
|
||||||
return images.mapIndexed { index, imageUrl -> Page(index, imageUrl = imageUrl) }
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun imageUrlParse(response: Response) = throw UnsupportedOperationException()
|
|
||||||
|
|
||||||
override fun getFilterList() = FilterList(
|
|
||||||
Filter.Header("Category (ignored for text search)"),
|
|
||||||
RegionFilter(),
|
|
||||||
)
|
|
||||||
|
|
||||||
private class RegionFilter : Filter.Select<String>(
|
|
||||||
"Region",
|
|
||||||
arrayOf("All", "国产美女", "韩国美女", "台湾美女", "日本美女", "欧美美女", "泰国美女"),
|
|
||||||
)
|
|
||||||
|
|
||||||
private fun String.pageNumber() = numberRegex.findAll(this).last().value.toInt()
|
|
||||||
|
|
||||||
private val numberRegex by lazy { Regex("""\d+""") }
|
|
||||||
|
|
||||||
private val dateFormat by lazy { SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH) }
|
|
||||||
}
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
ext {
|
||||||
|
extName = 'Mitaku'
|
||||||
|
extClass = '.Mitaku'
|
||||||
|
extVersionCode = 1
|
||||||
|
isNsfw = true
|
||||||
|
}
|
||||||
|
|
||||||
|
apply from: "$rootDir/common.gradle"
|
After Width: | Height: | Size: 7.2 KiB |
After Width: | Height: | Size: 3.6 KiB |
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 23 KiB |
After Width: | Height: | Size: 37 KiB |
|
@ -0,0 +1,171 @@
|
||||||
|
package eu.kanade.tachiyomi.extension.all.mitaku
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.network.GET
|
||||||
|
import eu.kanade.tachiyomi.source.model.Filter
|
||||||
|
import eu.kanade.tachiyomi.source.model.FilterList
|
||||||
|
import eu.kanade.tachiyomi.source.model.Page
|
||||||
|
import eu.kanade.tachiyomi.source.model.SChapter
|
||||||
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
|
import eu.kanade.tachiyomi.source.model.UpdateStrategy
|
||||||
|
import eu.kanade.tachiyomi.source.online.ParsedHttpSource
|
||||||
|
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||||
|
import okhttp3.Request
|
||||||
|
import org.jsoup.nodes.Document
|
||||||
|
import org.jsoup.nodes.Element
|
||||||
|
import rx.Observable
|
||||||
|
|
||||||
|
class Mitaku : ParsedHttpSource() {
|
||||||
|
override val name = "Mitaku"
|
||||||
|
|
||||||
|
override val baseUrl = "https://mitaku.net"
|
||||||
|
|
||||||
|
override val lang = "all"
|
||||||
|
|
||||||
|
override val supportsLatest = false
|
||||||
|
|
||||||
|
// ============================== Popular ===============================
|
||||||
|
override fun popularMangaRequest(page: Int) = GET("$baseUrl/category/ero-cosplay/page/$page", headers)
|
||||||
|
|
||||||
|
override fun popularMangaSelector() = "div.article-container article"
|
||||||
|
|
||||||
|
override fun popularMangaFromElement(element: Element) = SManga.create().apply {
|
||||||
|
setUrlWithoutDomain(element.selectFirst("a")!!.absUrl("href"))
|
||||||
|
|
||||||
|
title = element.selectFirst("a")!!.attr("title")
|
||||||
|
|
||||||
|
thumbnail_url = element.selectFirst("img")?.absUrl("src")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun popularMangaNextPageSelector() = "div.wp-pagenavi a.page.larger"
|
||||||
|
|
||||||
|
// =============================== Latest ===============================
|
||||||
|
override fun latestUpdatesRequest(page: Int): Request {
|
||||||
|
throw UnsupportedOperationException()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun latestUpdatesSelector(): String {
|
||||||
|
throw UnsupportedOperationException()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun latestUpdatesFromElement(element: Element): SManga {
|
||||||
|
throw UnsupportedOperationException()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun latestUpdatesNextPageSelector(): String? {
|
||||||
|
throw UnsupportedOperationException()
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================== Search ===============================
|
||||||
|
|
||||||
|
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||||
|
val filterList = if (filters.isEmpty()) getFilterList() else filters
|
||||||
|
val tagFilter = filterList.findInstance<TagFilter>()!!
|
||||||
|
val categoryFilter = filterList.findInstance<CategoryFilter>()!!
|
||||||
|
|
||||||
|
return when {
|
||||||
|
query.isEmpty() && categoryFilter.state != 0 -> {
|
||||||
|
val url = "$baseUrl/category/${categoryFilter.toUriPart()}/page/$page/"
|
||||||
|
GET(url, headers)
|
||||||
|
}
|
||||||
|
query.isEmpty() && tagFilter.state.isNotEmpty() -> {
|
||||||
|
val url = baseUrl.toHttpUrl().newBuilder()
|
||||||
|
.addPathSegment("tag")
|
||||||
|
.addPathSegment(tagFilter.toUriPart())
|
||||||
|
.addPathSegment("page")
|
||||||
|
.addPathSegment(page.toString())
|
||||||
|
.build()
|
||||||
|
GET(url, headers)
|
||||||
|
}
|
||||||
|
query.isNotEmpty() -> {
|
||||||
|
val url = "$baseUrl/page/$page/".toHttpUrl().newBuilder()
|
||||||
|
.addQueryParameter("s", query)
|
||||||
|
.build()
|
||||||
|
GET(url, headers)
|
||||||
|
}
|
||||||
|
else -> latestUpdatesRequest(page)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun searchMangaSelector() = popularMangaSelector()
|
||||||
|
|
||||||
|
override fun searchMangaFromElement(element: Element) = popularMangaFromElement(element)
|
||||||
|
|
||||||
|
override fun searchMangaNextPageSelector() = popularMangaNextPageSelector()
|
||||||
|
|
||||||
|
override fun mangaDetailsParse(document: Document) = SManga.create().apply {
|
||||||
|
status = SManga.COMPLETED
|
||||||
|
update_strategy = UpdateStrategy.ONLY_FETCH_ONCE
|
||||||
|
with(document.selectFirst("article")!!) {
|
||||||
|
title = selectFirst("h1")!!.text()
|
||||||
|
val catGenres = select("span.cat-links a").joinToString { it.text() }
|
||||||
|
val tagGenres = select("span.tag-links a").joinToString { it.text() }
|
||||||
|
genre = listOf(catGenres, tagGenres).filter { it.isNotEmpty() }.joinToString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================== Chapters ==============================
|
||||||
|
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
|
||||||
|
val chapter = SChapter.create().apply {
|
||||||
|
url = manga.url
|
||||||
|
chapter_number = 1F
|
||||||
|
name = "Chapter"
|
||||||
|
}
|
||||||
|
|
||||||
|
return Observable.just(listOf(chapter))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun chapterListSelector(): String {
|
||||||
|
throw UnsupportedOperationException()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun chapterFromElement(element: Element): SChapter {
|
||||||
|
throw UnsupportedOperationException()
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================== Pages ================================
|
||||||
|
|
||||||
|
override fun pageListParse(document: Document): List<Page> {
|
||||||
|
val imageElements = document.select("a.msacwl-img-link")
|
||||||
|
|
||||||
|
return imageElements.mapIndexed { index, element ->
|
||||||
|
val imageUrl = element.absUrl("data-mfp-src")
|
||||||
|
Page(index, imageUrl = imageUrl)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun imageUrlParse(document: Document): String {
|
||||||
|
throw UnsupportedOperationException()
|
||||||
|
}
|
||||||
|
|
||||||
|
open class UriPartFilter(
|
||||||
|
displayName: String,
|
||||||
|
private val valuePair: Array<Pair<String, String>>,
|
||||||
|
) : Filter.Select<String>(displayName, valuePair.map { it.first }.toTypedArray()) {
|
||||||
|
fun toUriPart() = valuePair[state].second
|
||||||
|
}
|
||||||
|
|
||||||
|
class CategoryFilter : UriPartFilter(
|
||||||
|
"Category",
|
||||||
|
arrayOf(
|
||||||
|
Pair("Any", ""),
|
||||||
|
Pair("Ero Cosplay", "/ero-cosplay"),
|
||||||
|
Pair("Nude", "/nude"),
|
||||||
|
Pair("Sexy Set", "/sexy-set"),
|
||||||
|
Pair("Online Video", "/online-video"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
override fun getFilterList(): FilterList = FilterList(
|
||||||
|
Filter.Header("NOTE: Only one tag search"),
|
||||||
|
Filter.Separator(),
|
||||||
|
CategoryFilter(),
|
||||||
|
TagFilter(),
|
||||||
|
)
|
||||||
|
|
||||||
|
class TagFilter : Filter.Text("Tag") {
|
||||||
|
fun toUriPart(): String {
|
||||||
|
return state.trim().lowercase().replace(" ", "-")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private inline fun <reified T> Iterable<*>.findInstance() = find { it is T } as? T
|
||||||
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
ext {
|
ext {
|
||||||
extName = 'NHentai'
|
extName = 'NHentai'
|
||||||
extClass = '.NHFactory'
|
extClass = '.NHFactory'
|
||||||
extVersionCode = 41
|
extVersionCode = 46
|
||||||
isNsfw = true
|
isNsfw = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -136,57 +136,51 @@ open class NHentai(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||||
val fixedQuery = query.ifEmpty { "\"\"" }
|
|
||||||
val filterList = if (filters.isEmpty()) getFilterList() else filters
|
val filterList = if (filters.isEmpty()) getFilterList() else filters
|
||||||
val nhLangSearch = if (nhLang.isBlank()) "" else "+$nhLang "
|
val nhLangSearch = if (nhLang.isBlank()) "" else "language:$nhLang "
|
||||||
val advQuery = combineQuery(filterList)
|
val advQuery = combineQuery(filterList)
|
||||||
val favoriteFilter = filterList.findInstance<FavoriteFilter>()
|
val favoriteFilter = filterList.findInstance<FavoriteFilter>()
|
||||||
val isOkayToSort = filterList.findInstance<UploadedFilter>()?.state?.isBlank() ?: true
|
|
||||||
val offsetPage =
|
val offsetPage =
|
||||||
filterList.findInstance<OffsetPageFilter>()?.state?.toIntOrNull()?.plus(page) ?: page
|
filterList.findInstance<OffsetPageFilter>()?.state?.toIntOrNull()?.plus(page) ?: page
|
||||||
|
|
||||||
if (favoriteFilter?.state == true) {
|
if (favoriteFilter?.state == true) {
|
||||||
val url = "$baseUrl/favorites".toHttpUrl().newBuilder()
|
val url = "$baseUrl/favorites/".toHttpUrl().newBuilder()
|
||||||
.addQueryParameter("q", "$fixedQuery $advQuery")
|
.addQueryParameter("q", "$query $advQuery")
|
||||||
.addQueryParameter("page", offsetPage.toString())
|
.addQueryParameter("page", offsetPage.toString())
|
||||||
|
|
||||||
return GET(url.build(), headers)
|
return GET(url.build(), headers)
|
||||||
} else {
|
} else {
|
||||||
val url = "$baseUrl/search".toHttpUrl().newBuilder()
|
val url = "$baseUrl/search/".toHttpUrl().newBuilder()
|
||||||
.addQueryParameter("q", "$fixedQuery $nhLangSearch$advQuery")
|
// Blank query (Multi + sort by popular month/week/day) shows a 404 page
|
||||||
|
// Searching for `""` is a hacky way to return everything without any filtering
|
||||||
|
.addQueryParameter("q", "$query $nhLangSearch$advQuery".ifBlank { "\"\"" })
|
||||||
.addQueryParameter("page", offsetPage.toString())
|
.addQueryParameter("page", offsetPage.toString())
|
||||||
|
|
||||||
if (isOkayToSort) {
|
filterList.findInstance<SortFilter>()?.let { f ->
|
||||||
filterList.findInstance<SortFilter>()?.let { f ->
|
url.addQueryParameter("sort", f.toUriPart())
|
||||||
url.addQueryParameter("sort", f.toUriPart())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return GET(url.build(), headers)
|
return GET(url.build(), headers)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun combineQuery(filters: FilterList): String {
|
private fun combineQuery(filters: FilterList): String = buildString {
|
||||||
val stringBuilder = StringBuilder()
|
filters.filterIsInstance<AdvSearchEntryFilter>().forEach { filter ->
|
||||||
val advSearch = filters.filterIsInstance<AdvSearchEntryFilter>().flatMap { filter ->
|
filter.state.split(",")
|
||||||
val splitState = filter.state.split(",").map(String::trim).filterNot(String::isBlank)
|
.map(String::trim)
|
||||||
splitState.map {
|
.filterNot(String::isBlank)
|
||||||
AdvSearchEntry(filter.name, it.removePrefix("-"), it.startsWith("-"))
|
.forEach { tag ->
|
||||||
}
|
val y = !(filter.name == "Pages" || filter.name == "Uploaded")
|
||||||
|
if (tag.startsWith("-")) append("-")
|
||||||
|
append(filter.name, ':')
|
||||||
|
if (y) append('"')
|
||||||
|
append(tag.removePrefix("-"))
|
||||||
|
if (y) append('"')
|
||||||
|
append(" ")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
advSearch.forEach { entry ->
|
|
||||||
if (entry.exclude) stringBuilder.append("-")
|
|
||||||
stringBuilder.append("${entry.name}:")
|
|
||||||
stringBuilder.append(entry.text)
|
|
||||||
stringBuilder.append(" ")
|
|
||||||
}
|
|
||||||
|
|
||||||
return stringBuilder.toString()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
data class AdvSearchEntry(val name: String, val text: String, val exclude: Boolean)
|
|
||||||
|
|
||||||
private fun searchMangaByIdRequest(id: String) = GET("$baseUrl/g/$id", headers)
|
private fun searchMangaByIdRequest(id: String) = GET("$baseUrl/g/$id", headers)
|
||||||
|
|
||||||
private fun searchMangaByIdParse(response: Response, id: String): MangasPage {
|
private fun searchMangaByIdParse(response: Response, id: String): MangasPage {
|
||||||
|
@ -226,7 +220,7 @@ open class NHentai(
|
||||||
.plus("$fullTitle\n")
|
.plus("$fullTitle\n")
|
||||||
.plus("${document.select("div#info h2").text()}\n\n")
|
.plus("${document.select("div#info h2").text()}\n\n")
|
||||||
.plus("Pages: ${getNumPages(document)}\n")
|
.plus("Pages: ${getNumPages(document)}\n")
|
||||||
.plus("Favorited by: ${document.select("div#info i.fa-heart + span span").text().removeSurrounding("(", ")")}\n")
|
.plus("Favorited by: ${document.select("div#info i.fa-heart ~ span span").text().removeSurrounding("(", ")")}\n")
|
||||||
.plus(getTagDescription(document))
|
.plus(getTagDescription(document))
|
||||||
genre = getTags(document)
|
genre = getTags(document)
|
||||||
update_strategy = UpdateStrategy.ONLY_FETCH_ONCE
|
update_strategy = UpdateStrategy.ONLY_FETCH_ONCE
|
||||||
|
|
|
@ -3,7 +3,7 @@ ext {
|
||||||
extClass = '.ThunderScansFactory'
|
extClass = '.ThunderScansFactory'
|
||||||
themePkg = 'mangathemesia'
|
themePkg = 'mangathemesia'
|
||||||
baseUrl = 'https://en-thunderscans.com'
|
baseUrl = 'https://en-thunderscans.com'
|
||||||
overrideVersionCode = 4
|
overrideVersionCode = 5
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: "$rootDir/common.gradle"
|
apply from: "$rootDir/common.gradle"
|
||||||
|
|
|
@ -14,7 +14,7 @@ class ThunderScansFactory : SourceFactory {
|
||||||
|
|
||||||
class ThunderScansAR : MangaThemesiaAlt(
|
class ThunderScansAR : MangaThemesiaAlt(
|
||||||
"Thunder Scans",
|
"Thunder Scans",
|
||||||
"https://thunderscans.com",
|
"https://ar-thunderepic.com",
|
||||||
"ar",
|
"ar",
|
||||||
dateFormat = SimpleDateFormat("MMM d, yyy", Locale("ar")),
|
dateFormat = SimpleDateFormat("MMM d, yyy", Locale("ar")),
|
||||||
)
|
)
|
||||||
|
|
|
@ -2,8 +2,8 @@ ext {
|
||||||
extName = 'Empire Webtoon'
|
extName = 'Empire Webtoon'
|
||||||
extClass = '.EmpireWebtoon'
|
extClass = '.EmpireWebtoon'
|
||||||
themePkg = 'madara'
|
themePkg = 'madara'
|
||||||
baseUrl = 'https://webtoonsempireron.com'
|
baseUrl = 'https://webtoonempire-ron.com'
|
||||||
overrideVersionCode = 3
|
overrideVersionCode = 4
|
||||||
isNsfw = true
|
isNsfw = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,6 @@ import android.app.Application
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.preference.PreferenceScreen
|
import androidx.preference.PreferenceScreen
|
||||||
import eu.kanade.tachiyomi.extension.BuildConfig
|
|
||||||
import eu.kanade.tachiyomi.multisrc.madara.Madara
|
import eu.kanade.tachiyomi.multisrc.madara.Madara
|
||||||
import eu.kanade.tachiyomi.source.ConfigurableSource
|
import eu.kanade.tachiyomi.source.ConfigurableSource
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
|
@ -15,46 +14,56 @@ import java.util.Locale
|
||||||
class EmpireWebtoon :
|
class EmpireWebtoon :
|
||||||
Madara(
|
Madara(
|
||||||
"Empire Webtoon",
|
"Empire Webtoon",
|
||||||
"https://webtoonsempireron.com",
|
"https://webtoonempire-ron.com",
|
||||||
"ar",
|
"ar",
|
||||||
SimpleDateFormat("d MMMM، yyyy", Locale("ar")),
|
SimpleDateFormat("d MMMM، yyyy", Locale("ar")),
|
||||||
),
|
),
|
||||||
ConfigurableSource {
|
ConfigurableSource {
|
||||||
|
|
||||||
private val defaultBaseUrl = "https://webtoonsempireron.com"
|
private val preferences: SharedPreferences =
|
||||||
|
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
||||||
|
|
||||||
|
init {
|
||||||
|
preferences.getString(DEFAULT_BASE_URL_PREF, null).let { prefDefaultBaseUrl ->
|
||||||
|
if (prefDefaultBaseUrl != super.baseUrl) {
|
||||||
|
preferences.edit()
|
||||||
|
.putString(BASE_URL_PREF, super.baseUrl)
|
||||||
|
.putString(DEFAULT_BASE_URL_PREF, super.baseUrl)
|
||||||
|
.apply()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override val baseUrl by lazy { getPrefBaseUrl() }
|
override val baseUrl by lazy { getPrefBaseUrl() }
|
||||||
|
|
||||||
private val preferences: SharedPreferences by lazy {
|
|
||||||
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
|
||||||
}
|
|
||||||
|
|
||||||
override val mangaSubString = "webtoon"
|
override val mangaSubString = "webtoon"
|
||||||
|
|
||||||
override val useNewChapterEndpoint = false
|
override val useNewChapterEndpoint = false
|
||||||
|
|
||||||
companion object {
|
|
||||||
private const val RESTART_TACHIYOMI = "Restart Tachiyomi to apply new setting."
|
|
||||||
private const val BASE_URL_PREF_TITLE = "Override BaseUrl"
|
|
||||||
private const val BASE_URL_PREF = "overrideBaseUrl_v${BuildConfig.VERSION_CODE}"
|
|
||||||
private const val BASE_URL_PREF_SUMMARY = "For temporary uses. Updating the extension will erase this setting."
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setupPreferenceScreen(screen: PreferenceScreen) {
|
override fun setupPreferenceScreen(screen: PreferenceScreen) {
|
||||||
val baseUrlPref = androidx.preference.EditTextPreference(screen.context).apply {
|
val baseUrlPref = androidx.preference.EditTextPreference(screen.context).apply {
|
||||||
key = BASE_URL_PREF
|
key = BASE_URL_PREF
|
||||||
title = BASE_URL_PREF_TITLE
|
title = BASE_URL_PREF_TITLE
|
||||||
summary = BASE_URL_PREF_SUMMARY
|
summary = BASE_URL_PREF_SUMMARY
|
||||||
this.setDefaultValue(defaultBaseUrl)
|
setDefaultValue(super.baseUrl)
|
||||||
dialogTitle = BASE_URL_PREF_TITLE
|
dialogTitle = BASE_URL_PREF_TITLE
|
||||||
|
dialogMessage = "Default: ${super.baseUrl}"
|
||||||
|
|
||||||
setOnPreferenceChangeListener { _, _ ->
|
setOnPreferenceChangeListener { _, _ ->
|
||||||
Toast.makeText(screen.context, RESTART_TACHIYOMI, Toast.LENGTH_LONG).show()
|
Toast.makeText(screen.context, RESTART_APP, Toast.LENGTH_LONG).show()
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
screen.addPreference(baseUrlPref)
|
screen.addPreference(baseUrlPref)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getPrefBaseUrl(): String = preferences.getString(BASE_URL_PREF, defaultBaseUrl)!!
|
private fun getPrefBaseUrl(): String = preferences.getString(BASE_URL_PREF, super.baseUrl)!!
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val DEFAULT_BASE_URL_PREF = "defaultBaseUrl"
|
||||||
|
private const val RESTART_APP = "Restart app to apply new setting."
|
||||||
|
private const val BASE_URL_PREF_TITLE = "Override BaseUrl"
|
||||||
|
private const val BASE_URL_PREF = "overrideBaseUrl"
|
||||||
|
private const val BASE_URL_PREF_SUMMARY = "For temporary uses. Updating the extension will erase this setting."
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,8 +2,8 @@ ext {
|
||||||
extName = 'MangaNoon'
|
extName = 'MangaNoon'
|
||||||
extClass = '.MangaNoon'
|
extClass = '.MangaNoon'
|
||||||
themePkg = 'mangathemesia'
|
themePkg = 'mangathemesia'
|
||||||
baseUrl = 'https://noonscan.net'
|
baseUrl = 'https://manjanoon.xyz'
|
||||||
overrideVersionCode = 4
|
overrideVersionCode = 5
|
||||||
isNsfw = false
|
isNsfw = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ import java.util.Calendar
|
||||||
|
|
||||||
class MangaNoon : MangaThemesia(
|
class MangaNoon : MangaThemesia(
|
||||||
"مانجا نون",
|
"مانجا نون",
|
||||||
"https://noonscan.net",
|
"https://manjanoon.xyz",
|
||||||
"ar",
|
"ar",
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
|
|
@ -2,8 +2,8 @@ ext {
|
||||||
extName = 'MangaSwat'
|
extName = 'MangaSwat'
|
||||||
extClass = '.MangaSwat'
|
extClass = '.MangaSwat'
|
||||||
themePkg = 'mangathemesia'
|
themePkg = 'mangathemesia'
|
||||||
baseUrl = 'https://tatwt.com'
|
baseUrl = 'https://healteer.com'
|
||||||
overrideVersionCode = 21
|
overrideVersionCode = 23
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: "$rootDir/common.gradle"
|
apply from: "$rootDir/common.gradle"
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package eu.kanade.tachiyomi.extension.ar.mangaswat
|
package eu.kanade.tachiyomi.extension.ar.mangaswat
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.content.SharedPreferences
|
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.preference.PreferenceScreen
|
import androidx.preference.PreferenceScreen
|
||||||
import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia
|
import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia
|
||||||
|
@ -12,7 +11,6 @@ import eu.kanade.tachiyomi.source.model.Page
|
||||||
import eu.kanade.tachiyomi.source.model.SChapter
|
import eu.kanade.tachiyomi.source.model.SChapter
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.decodeFromString
|
import kotlinx.serialization.decodeFromString
|
||||||
import okhttp3.OkHttpClient
|
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import org.jsoup.nodes.Document
|
import org.jsoup.nodes.Document
|
||||||
import org.jsoup.nodes.Element
|
import org.jsoup.nodes.Element
|
||||||
|
@ -24,7 +22,7 @@ import java.util.Locale
|
||||||
class MangaSwat :
|
class MangaSwat :
|
||||||
MangaThemesia(
|
MangaThemesia(
|
||||||
"MangaSwat",
|
"MangaSwat",
|
||||||
"https://tatwt.com",
|
"https://healteer.com",
|
||||||
"ar",
|
"ar",
|
||||||
dateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale("ar")),
|
dateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale("ar")),
|
||||||
),
|
),
|
||||||
|
@ -32,29 +30,53 @@ class MangaSwat :
|
||||||
|
|
||||||
override val baseUrl by lazy { getPrefBaseUrl() }
|
override val baseUrl by lazy { getPrefBaseUrl() }
|
||||||
|
|
||||||
private val preferences: SharedPreferences by lazy {
|
private val preferences by lazy {
|
||||||
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
||||||
}
|
}
|
||||||
|
|
||||||
override val client: OkHttpClient = super.client.newBuilder()
|
override val client = super.client.newBuilder()
|
||||||
.rateLimit(1)
|
.rateLimit(1)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
|
override fun latestUpdatesRequest(page: Int): Request {
|
||||||
|
val filter = FilterList(OrderByFilter("", orderByFilterOptions, "added"))
|
||||||
|
|
||||||
|
return searchMangaRequest(page, "", filter)
|
||||||
|
}
|
||||||
|
|
||||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||||
val request = super.searchMangaRequest(page, query, filters)
|
val request = super.searchMangaRequest(page, query, filters)
|
||||||
if (query.isBlank()) return request
|
val urlBuilder = request.url.newBuilder()
|
||||||
|
|
||||||
val url = request.url.newBuilder()
|
// remove trailing slash
|
||||||
.removePathSegment(0)
|
if (request.url.pathSegments.last().isBlank()) {
|
||||||
.removeAllQueryParameters("title")
|
urlBuilder.removePathSegment(
|
||||||
.addQueryParameter("s", query)
|
request.url.pathSegments.lastIndex,
|
||||||
.build()
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (query.isNotBlank()) {
|
||||||
|
urlBuilder
|
||||||
|
.removePathSegment(0)
|
||||||
|
.removeAllQueryParameters("title")
|
||||||
|
.addQueryParameter("s", query)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
return request.newBuilder()
|
return request.newBuilder()
|
||||||
.url(url)
|
.url(urlBuilder.build())
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override val orderByFilterOptions = arrayOf(
|
||||||
|
Pair(intl["order_by_filter_default"], ""),
|
||||||
|
Pair(intl["order_by_filter_az"], "a-z"),
|
||||||
|
Pair(intl["order_by_filter_za"], "z-a"),
|
||||||
|
Pair(intl["order_by_filter_latest_update"], "update"),
|
||||||
|
Pair(intl["order_by_filter_latest_added"], "added"),
|
||||||
|
Pair(intl["order_by_filter_popular"], "popular"),
|
||||||
|
)
|
||||||
|
|
||||||
override fun searchMangaNextPageSelector() = "a[rel=next]"
|
override fun searchMangaNextPageSelector() = "a[rel=next]"
|
||||||
|
|
||||||
override val seriesTitleSelector = "h1[itemprop=headline]"
|
override val seriesTitleSelector = "h1[itemprop=headline]"
|
||||||
|
@ -82,13 +104,12 @@ class MangaSwat :
|
||||||
}
|
}
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class TSReader(
|
class TSReader(
|
||||||
val sources: List<ReaderImageSource>,
|
val sources: List<ReaderImageSource>,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class ReaderImageSource(
|
class ReaderImageSource(
|
||||||
val source: String,
|
|
||||||
val images: List<String>,
|
val images: List<String>,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ ext {
|
||||||
extClass = '.RocksManga'
|
extClass = '.RocksManga'
|
||||||
themePkg = 'madara'
|
themePkg = 'madara'
|
||||||
baseUrl = 'https://rocksmanga.com'
|
baseUrl = 'https://rocksmanga.com'
|
||||||
overrideVersionCode = 1
|
overrideVersionCode = 2
|
||||||
isNsfw = false
|
isNsfw = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -49,7 +49,7 @@ class RocksManga : Madara(
|
||||||
override val mangaDetailsSelectorThumbnail = ".manga-poster img"
|
override val mangaDetailsSelectorThumbnail = ".manga-poster img"
|
||||||
override val mangaDetailsSelectorGenre = "div.meta span:contains(التصنيف:) + span a"
|
override val mangaDetailsSelectorGenre = "div.meta span:contains(التصنيف:) + span a"
|
||||||
override val altNameSelector = "div.alternative"
|
override val altNameSelector = "div.alternative"
|
||||||
override fun chapterListSelector() = ".chapters-list li.chapter-item"
|
override fun chapterListSelector() = "li.chapter-item"
|
||||||
override fun chapterDateSelector() = ".chapter-release-date"
|
override fun chapterDateSelector() = ".chapter-release-date"
|
||||||
override val pageListParseSelector = ".chapter-reading-page img"
|
override val pageListParseSelector = ".chapter-reading-page img"
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
ext {
|
||||||
|
extName = 'Scans 4u'
|
||||||
|
extClass = '.Scans4u'
|
||||||
|
themePkg = 'keyoapp'
|
||||||
|
baseUrl = 'https://4uscans.com'
|
||||||
|
overrideVersionCode = 0
|
||||||
|
isNsfw = false
|
||||||
|
}
|
||||||
|
|
||||||
|
apply from: "$rootDir/common.gradle"
|