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
|
||||
root = true
|
||||
|
||||
[*]
|
||||
insert_final_newline = true
|
||||
end_of_line = lf
|
||||
|
||||
[*.kt]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
indent_size = 4
|
||||
indent_style = space
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
ij_kotlin_allow_trailing_comma = 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]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
distributionBase=GRADLE_USER_HOME
|
||||
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
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
|
|
@ -9,6 +9,7 @@ assert !ext.has("libVersion")
|
|||
assert extName.chars().max().asInt < 0x180 : "Extension name should be romanized"
|
||||
|
||||
Project theme = ext.has("themePkg") ? project(":lib-multisrc:$themePkg") : null
|
||||
if (theme != null) evaluationDependsOn(theme.path)
|
||||
|
||||
android {
|
||||
compileSdk AndroidConfig.compileSdk
|
||||
|
|
|
@ -25,3 +25,5 @@ android.useAndroidX=true
|
|||
android.enableBuildConfigAsBytecode=true
|
||||
android.defaults.buildfeatures.resvalues=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-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" }
|
||||
jsoup = { module = "org.jsoup:jsoup", version = "1.15.1" }
|
||||
okhttp = { module = "com.squareup.okhttp3:okhttp", version = "5.0.0-alpha.11" }
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
distributionBase=GRADLE_USER_HOME
|
||||
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
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
|
|
@ -2,7 +2,7 @@ plugins {
|
|||
id("lib-multisrc")
|
||||
}
|
||||
|
||||
baseVersionCode = 3
|
||||
baseVersionCode = 4
|
||||
|
||||
dependencies {
|
||||
api(project(":lib:synchrony"))
|
||||
|
|
|
@ -276,7 +276,7 @@ abstract class ColaManga(
|
|||
}.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 {
|
||||
val obfuscatedReadJs = client.newCall(GET("$baseUrl/js/manga.read.js")).execute().body.string()
|
||||
|
|
|
@ -2,4 +2,4 @@ plugins {
|
|||
id("lib-multisrc")
|
||||
}
|
||||
|
||||
baseVersionCode = 23
|
||||
baseVersionCode = 24
|
||||
|
|
|
@ -2,4 +2,4 @@ plugins {
|
|||
id("lib-multisrc")
|
||||
}
|
||||
|
||||
baseVersionCode = 3
|
||||
baseVersionCode = 4
|
||||
|
|
|
@ -83,8 +83,8 @@ class Post<T>(val post: T)
|
|||
|
||||
@Serializable
|
||||
class ChapterListResponse(
|
||||
val isNovel: Boolean,
|
||||
val slug: String,
|
||||
val isNovel: Boolean = false,
|
||||
val slug: String? = null,
|
||||
val chapters: List<Chapter>,
|
||||
)
|
||||
|
||||
|
@ -96,11 +96,13 @@ class Chapter(
|
|||
private val createdBy: Name,
|
||||
private val createdAt: String,
|
||||
private val chapterStatus: String,
|
||||
private val mangaPost: ChapterPostDetails,
|
||||
) {
|
||||
fun isPublic() = chapterStatus == "PUBLIC"
|
||||
|
||||
fun toSChapter(mangaSlug: String) = SChapter.create().apply {
|
||||
url = "/series/$mangaSlug/$slug#$id"
|
||||
fun toSChapter(mangaSlug: String?) = SChapter.create().apply {
|
||||
val seriesSlug = mangaSlug ?: mangaPost.slug
|
||||
url = "/series/$seriesSlug/$slug#$id"
|
||||
name = "Chapter $number"
|
||||
scanlator = createdBy.name
|
||||
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)
|
||||
|
|
|
@ -2,4 +2,4 @@ plugins {
|
|||
id("lib-multisrc")
|
||||
}
|
||||
|
||||
baseVersionCode = 4
|
||||
baseVersionCode = 7
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package eu.kanade.tachiyomi.multisrc.keyoapp
|
||||
|
||||
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.FilterList
|
||||
import eu.kanade.tachiyomi.source.model.MangasPage
|
||||
|
@ -31,9 +30,7 @@ abstract class Keyoapp(
|
|||
) : ParsedHttpSource() {
|
||||
override val supportsLatest = true
|
||||
|
||||
override val client = network.cloudflareClient.newBuilder()
|
||||
.rateLimit(2)
|
||||
.build()
|
||||
override val client = network.cloudflareClient
|
||||
|
||||
override fun headersBuilder() = super.headersBuilder()
|
||||
.add("Referer", "$baseUrl/")
|
||||
|
@ -197,7 +194,18 @@ abstract class Keyoapp(
|
|||
status = document.selectFirst("div[alt=Status]").parseStatus()
|
||||
author = document.selectFirst("div[alt=Author]")?.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()) {
|
||||
|
@ -223,15 +231,27 @@ abstract class Keyoapp(
|
|||
// Image list
|
||||
|
||||
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")
|
||||
.map { it.imgAttr() }
|
||||
.filter { it.contains(imgCdnRegex) }
|
||||
.filter { it.contains(oldImgCdnRegex) }
|
||||
.mapIndexed { index, 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) = ""
|
||||
|
||||
|
@ -247,7 +267,7 @@ abstract class Keyoapp(
|
|||
return url
|
||||
}
|
||||
|
||||
private fun Element.getImageUrl(selector: String): String? {
|
||||
protected open fun Element.getImageUrl(selector: String): String? {
|
||||
return this.selectFirst(selector)?.let { element ->
|
||||
element.attr("style")
|
||||
.substringAfter(":url(", "")
|
||||
|
|
|
@ -2,4 +2,4 @@ plugins {
|
|||
id("lib-multisrc")
|
||||
}
|
||||
|
||||
baseVersionCode = 30
|
||||
baseVersionCode = 31
|
||||
|
|
|
@ -622,7 +622,7 @@ abstract class Madara(
|
|||
"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",
|
||||
"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",
|
||||
)
|
||||
|
||||
|
|
|
@ -490,8 +490,8 @@ abstract class MangaThemesia(
|
|||
Pair(intl["order_by_filter_popular"], "popular"),
|
||||
)
|
||||
|
||||
protected val popularFilter by lazy { FilterList(OrderByFilter("", orderByFilterOptions, "popular")) }
|
||||
protected val latestFilter by lazy { FilterList(OrderByFilter("", orderByFilterOptions, "update")) }
|
||||
protected open val popularFilter by lazy { FilterList(OrderByFilter("", orderByFilterOptions, "popular")) }
|
||||
protected open val latestFilter by lazy { FilterList(OrderByFilter("", orderByFilterOptions, "update")) }
|
||||
|
||||
protected class ProjectFilter(
|
||||
name: String,
|
||||
|
@ -603,7 +603,7 @@ abstract class MangaThemesia(
|
|||
(!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 ->
|
||||
GenreData(
|
||||
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 {
|
||||
extName = 'Akuma'
|
||||
extClass = '.AkumaFactory'
|
||||
extVersionCode = 4
|
||||
extVersionCode = 5
|
||||
isNsfw = true
|
||||
}
|
||||
|
||||
|
|
|
@ -1,8 +1,13 @@
|
|||
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.POST
|
||||
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.MangasPage
|
||||
import eu.kanade.tachiyomi.source.model.Page
|
||||
|
@ -20,6 +25,8 @@ import okhttp3.Response
|
|||
import org.jsoup.nodes.Document
|
||||
import org.jsoup.nodes.Element
|
||||
import rx.Observable
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.io.IOException
|
||||
import java.text.ParseException
|
||||
import java.text.SimpleDateFormat
|
||||
|
@ -29,7 +36,7 @@ import java.util.TimeZone
|
|||
class Akuma(
|
||||
override val lang: String,
|
||||
private val akumaLang: String,
|
||||
) : ParsedHttpSource() {
|
||||
) : ConfigurableSource, ParsedHttpSource() {
|
||||
|
||||
override val name = "Akuma"
|
||||
|
||||
|
@ -105,6 +112,23 @@ class Akuma(
|
|||
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 {
|
||||
val payload = FormBody.Builder()
|
||||
.add("view", "3")
|
||||
|
@ -149,7 +173,9 @@ class Akuma(
|
|||
override fun popularMangaFromElement(element: Element): SManga {
|
||||
return SManga.create().apply {
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
@ -176,7 +202,7 @@ class Akuma(
|
|||
val finalQuery: MutableList<String> = mutableListOf(query)
|
||||
|
||||
if (lang != "all") {
|
||||
finalQuery.add("language: $akumaLang$")
|
||||
finalQuery.add("language:$akumaLang$")
|
||||
}
|
||||
filters.forEach { filter ->
|
||||
when (filter) {
|
||||
|
@ -220,7 +246,9 @@ class Akuma(
|
|||
|
||||
override fun mangaDetailsParse(document: Document) = with(document) {
|
||||
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")
|
||||
|
||||
author = select(".group~.value").eachText().joinToString()
|
||||
|
@ -302,6 +330,7 @@ class Akuma(
|
|||
|
||||
companion object {
|
||||
const val PREFIX_ID = "id:"
|
||||
private const val PREF_TITLE = "pref_title"
|
||||
}
|
||||
|
||||
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'
|
||||
themePkg = 'madara'
|
||||
baseUrl = 'https://comicsvalley.com'
|
||||
overrideVersionCode = 1
|
||||
overrideVersionCode = 2
|
||||
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 {
|
||||
extName = 'Hentai Keyfi'
|
||||
extClass = '.HentaiKeyfi'
|
||||
extName = 'Eromanhwa'
|
||||
extClass = '.Eromanhwa'
|
||||
themePkg = 'madara'
|
||||
baseUrl = 'https://hentaikeyfi.com'
|
||||
baseUrl = 'https://eromanhwa.org'
|
||||
overrideVersionCode = 1
|
||||
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 {
|
||||
extName = 'Hitomi'
|
||||
extClass = '.HitomiFactory'
|
||||
extVersionCode = 32
|
||||
extVersionCode = 33
|
||||
isNsfw = true
|
||||
}
|
||||
|
||||
|
|
|
@ -517,6 +517,9 @@ class Hitomi(
|
|||
"https://${subDomain}tn.$domain/webpbigtn/${thumbPathFromHash(hash)}/$hash.webp"
|
||||
}
|
||||
description = buildString {
|
||||
japaneseTitle?.let {
|
||||
append("Japanese title: ", it, "\n")
|
||||
}
|
||||
parodys?.joinToString { it.formatted }?.let {
|
||||
append("Series: ", it, "\n")
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import kotlinx.serialization.json.JsonPrimitive
|
|||
class Gallery(
|
||||
val galleryurl: String,
|
||||
val title: String,
|
||||
val japaneseTitle: String?,
|
||||
val date: String,
|
||||
val type: String?,
|
||||
val language: String?,
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
ext {
|
||||
extName = 'Unitoon'
|
||||
extClass = '.Unitoon'
|
||||
extName = 'KDT Scans'
|
||||
extClass = '.KdtScans'
|
||||
themePkg = 'madara'
|
||||
baseUrl = 'https://lectorunitoon.com'
|
||||
baseUrl = 'https://kdtscans.com'
|
||||
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">
|
||||
<application>
|
||||
<activity
|
||||
android:name=".en.kiutaku.KiutakuUrlActivity"
|
||||
android:name=".all.kiutaku.KiutakuUrlActivity"
|
||||
android:excludeFromRecents="true"
|
||||
android:exported="true"
|
||||
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.asObservableSuccess
|
||||
|
@ -24,7 +24,9 @@ class Kiutaku : ParsedHttpSource() {
|
|||
|
||||
override val baseUrl = "https://kiutaku.com"
|
||||
|
||||
override val lang = "en"
|
||||
override val lang = "all"
|
||||
|
||||
override val id = 3040035304874076216
|
||||
|
||||
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.content.ActivityNotFoundException
|
|
@ -1,7 +1,7 @@
|
|||
ext {
|
||||
extName = 'LANraragi'
|
||||
extClass = '.LANraragiFactory'
|
||||
extVersionCode = 17
|
||||
extVersionCode = 18
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
||||
|
|
|
@ -7,6 +7,7 @@ data class Archive(
|
|||
val arcid: String,
|
||||
val isnew: String,
|
||||
val tags: String?,
|
||||
val summary: String?,
|
||||
val title: String,
|
||||
)
|
||||
|
||||
|
@ -25,7 +26,6 @@ data class ArchiveSearchResult(
|
|||
@Serializable
|
||||
data class Category(
|
||||
val id: String,
|
||||
val last_used: String,
|
||||
val name: 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 {
|
||||
url = "/reader?id=${archive.arcid}"
|
||||
title = archive.title
|
||||
description = archive.title
|
||||
description = if (archive.summary.isNullOrBlank()) archive.title else archive.summary
|
||||
thumbnail_url = getThumbnailUri(archive.arcid)
|
||||
genre = archive.tags?.replace(",", ", ")
|
||||
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>> {
|
||||
// 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 "
|
||||
|
||||
// Maintain categories sync for next FilterList reset. If there's demand for it, it's now
|
||||
// possible to sort by last_used similar to the web client. Maybe an option toggle?
|
||||
// Maintain categories sync for next FilterList reset.
|
||||
getCategories()
|
||||
|
||||
return listOf(Pair("", ""))
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
ext {
|
||||
extName = 'MangaDex'
|
||||
extClass = '.MangaDexFactory'
|
||||
extVersionCode = 193
|
||||
extVersionCode = 194
|
||||
isNsfw = true
|
||||
}
|
||||
|
||||
|
|
|
@ -138,13 +138,6 @@ object MDConstants {
|
|||
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 tagGroupFormat = "format"
|
||||
private const val tagGroupGenre = "genre"
|
||||
|
|
|
@ -3,7 +3,6 @@ package eu.kanade.tachiyomi.extension.all.mangadex
|
|||
import android.app.Application
|
||||
import android.content.SharedPreferences
|
||||
import android.os.Build
|
||||
import android.widget.Toast
|
||||
import androidx.preference.EditTextPreference
|
||||
import androidx.preference.ListPreference
|
||||
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 {
|
||||
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
||||
.sanitizeExistingUuidPrefs()
|
||||
}
|
||||
|
||||
private val helper = MangaDexHelper(lang)
|
||||
|
@ -67,6 +67,7 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St
|
|||
"Keiyoushi"
|
||||
|
||||
val builder = super.headersBuilder().apply {
|
||||
set("User-Agent", "Tachiyomi " + System.getProperty("http.agent"))
|
||||
set("Referer", "$baseUrl/")
|
||||
set("Origin", baseUrl)
|
||||
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()
|
||||
.rateLimit(3)
|
||||
.addInterceptor(MdAtHomeReportInterceptor(network.client, headers))
|
||||
.addInterceptor(MdUserAgentInterceptor(preferences, dexLang))
|
||||
.build()
|
||||
|
||||
init {
|
||||
preferences.sanitizeExistingUuidPrefs()
|
||||
}
|
||||
|
||||
// Popular manga section
|
||||
|
||||
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
|
||||
|
||||
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(tryUsingFirstVolumeCoverPref)
|
||||
screen.addPreference(dataSaverPref)
|
||||
|
@ -794,7 +766,6 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St
|
|||
screen.addPreference(originalLanguagePref)
|
||||
screen.addPreference(blockedGroupsPref)
|
||||
screen.addPreference(blockedUploaderPref)
|
||||
screen.addPreference(userAgentPref)
|
||||
}
|
||||
|
||||
override fun getFilterList(): FilterList =
|
||||
|
@ -869,20 +840,14 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St
|
|||
private val SharedPreferences.altTitlesInDesc
|
||||
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
|
||||
* preferences. This method clear invalid UUIDs in case the user have updated from
|
||||
* a previous version with that behaviour.
|
||||
*/
|
||||
private fun SharedPreferences.sanitizeExistingUuidPrefs() {
|
||||
private fun SharedPreferences.sanitizeExistingUuidPrefs(): SharedPreferences {
|
||||
if (getBoolean(MDConstants.getHasSanitizedUuidsPrefKey(dexLang), false)) {
|
||||
return
|
||||
return this
|
||||
}
|
||||
|
||||
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)
|
||||
.putBoolean(MDConstants.getHasSanitizedUuidsPrefKey(dexLang), true)
|
||||
.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_trimonthly=Trimonthly
|
||||
schedule_weekly=Weekly
|
||||
subtitle_only=Show subtitle only
|
||||
subtitle_only_summary=Removes the redundant chapter number from the chapter name.
|
||||
serialization=Serialization: %s
|
||||
split_double_pages=Split double pages
|
||||
split_double_pages_summary=Only a few titles supports disabling this setting.
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
ext {
|
||||
extName = 'MANGA Plus by SHUEISHA'
|
||||
extClass = '.MangaPlusFactory'
|
||||
extVersionCode = 52
|
||||
extVersionCode = 53
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
||||
|
|
|
@ -289,9 +289,10 @@ class MangaPlus(
|
|||
}
|
||||
|
||||
val titleDetailView = result.success.titleDetailView!!
|
||||
val subtitleOnly = preferences.subtitleOnly()
|
||||
|
||||
return titleDetailView.chapterList
|
||||
.map(Chapter::toSChapter)
|
||||
.map { it.toSChapter(subtitleOnly) }
|
||||
.reversed()
|
||||
}
|
||||
|
||||
|
@ -307,8 +308,8 @@ class MangaPlus(
|
|||
private fun pageListRequest(chapterId: String): Request {
|
||||
val url = "$APP_API_URL/manga_viewer".toHttpUrl().newBuilder()
|
||||
.addQueryParameter("chapter_id", chapterId)
|
||||
.addQueryParameter("split", if (preferences.splitImages) "yes" else "no")
|
||||
.addQueryParameter("img_quality", preferences.imageQuality)
|
||||
.addQueryParameter("split", if (preferences.splitImages()) "yes" else "no")
|
||||
.addQueryParameter("img_quality", preferences.imageQuality())
|
||||
.addQueryParameter("ticket_reading", "no")
|
||||
.addQueryParameter("free_reading", "yes")
|
||||
.addQueryParameter("subscription_reading", "no")
|
||||
|
@ -378,8 +379,16 @@ class MangaPlus(
|
|||
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(splitPref)
|
||||
screen.addPreference(titlePref)
|
||||
}
|
||||
|
||||
private fun PreferenceScreen.addEditTextPreference(
|
||||
|
@ -494,11 +503,11 @@ class MangaPlus(
|
|||
json.decodeFromString(body.string())
|
||||
}
|
||||
|
||||
private val SharedPreferences.imageQuality: String
|
||||
get() = getString("${QUALITY_PREF_KEY}_$lang", QUALITY_PREF_DEFAULT_VALUE)!!
|
||||
private fun SharedPreferences.imageQuality(): String = getString("${QUALITY_PREF_KEY}_$lang", QUALITY_PREF_DEFAULT_VALUE)!!
|
||||
|
||||
private val SharedPreferences.splitImages: Boolean
|
||||
get() = getBoolean("${SPLIT_PREF_KEY}_$lang", SPLIT_PREF_DEFAULT_VALUE)
|
||||
private fun SharedPreferences.splitImages(): Boolean = 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?
|
||||
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_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_DEFAULT_VALUE = ""
|
||||
private const val SECRET_PREF_KEY = "accountSecret"
|
||||
|
|
|
@ -322,11 +322,16 @@ class Chapter(
|
|||
val isExpired: Boolean
|
||||
get() = subTitle == null
|
||||
|
||||
fun toSChapter(): SChapter = SChapter.create().apply {
|
||||
name = "${this@Chapter.name} - $subTitle"
|
||||
fun toSChapter(subtitlePref: Boolean): SChapter = SChapter.create().apply {
|
||||
name = if (subtitlePref && subTitle != null) {
|
||||
subTitle
|
||||
} else {
|
||||
"${this@Chapter.name} - $subTitle"
|
||||
}
|
||||
date_upload = 1000L * startTimeStamp
|
||||
url = "#/viewer/$chapterId"
|
||||
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 {
|
||||
extName = 'NHentai'
|
||||
extClass = '.NHFactory'
|
||||
extVersionCode = 41
|
||||
extVersionCode = 46
|
||||
isNsfw = true
|
||||
}
|
||||
|
||||
|
|
|
@ -136,57 +136,51 @@ open class NHentai(
|
|||
}
|
||||
|
||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||
val fixedQuery = query.ifEmpty { "\"\"" }
|
||||
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 favoriteFilter = filterList.findInstance<FavoriteFilter>()
|
||||
val isOkayToSort = filterList.findInstance<UploadedFilter>()?.state?.isBlank() ?: true
|
||||
val offsetPage =
|
||||
filterList.findInstance<OffsetPageFilter>()?.state?.toIntOrNull()?.plus(page) ?: page
|
||||
|
||||
if (favoriteFilter?.state == true) {
|
||||
val url = "$baseUrl/favorites".toHttpUrl().newBuilder()
|
||||
.addQueryParameter("q", "$fixedQuery $advQuery")
|
||||
val url = "$baseUrl/favorites/".toHttpUrl().newBuilder()
|
||||
.addQueryParameter("q", "$query $advQuery")
|
||||
.addQueryParameter("page", offsetPage.toString())
|
||||
|
||||
return GET(url.build(), headers)
|
||||
} else {
|
||||
val url = "$baseUrl/search".toHttpUrl().newBuilder()
|
||||
.addQueryParameter("q", "$fixedQuery $nhLangSearch$advQuery")
|
||||
val url = "$baseUrl/search/".toHttpUrl().newBuilder()
|
||||
// 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())
|
||||
|
||||
if (isOkayToSort) {
|
||||
filterList.findInstance<SortFilter>()?.let { f ->
|
||||
url.addQueryParameter("sort", f.toUriPart())
|
||||
}
|
||||
filterList.findInstance<SortFilter>()?.let { f ->
|
||||
url.addQueryParameter("sort", f.toUriPart())
|
||||
}
|
||||
|
||||
return GET(url.build(), headers)
|
||||
}
|
||||
}
|
||||
|
||||
private fun combineQuery(filters: FilterList): String {
|
||||
val stringBuilder = StringBuilder()
|
||||
val advSearch = filters.filterIsInstance<AdvSearchEntryFilter>().flatMap { filter ->
|
||||
val splitState = filter.state.split(",").map(String::trim).filterNot(String::isBlank)
|
||||
splitState.map {
|
||||
AdvSearchEntry(filter.name, it.removePrefix("-"), it.startsWith("-"))
|
||||
}
|
||||
private fun combineQuery(filters: FilterList): String = buildString {
|
||||
filters.filterIsInstance<AdvSearchEntryFilter>().forEach { filter ->
|
||||
filter.state.split(",")
|
||||
.map(String::trim)
|
||||
.filterNot(String::isBlank)
|
||||
.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 searchMangaByIdParse(response: Response, id: String): MangasPage {
|
||||
|
@ -226,7 +220,7 @@ open class NHentai(
|
|||
.plus("$fullTitle\n")
|
||||
.plus("${document.select("div#info h2").text()}\n\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))
|
||||
genre = getTags(document)
|
||||
update_strategy = UpdateStrategy.ONLY_FETCH_ONCE
|
||||
|
|
|
@ -3,7 +3,7 @@ ext {
|
|||
extClass = '.ThunderScansFactory'
|
||||
themePkg = 'mangathemesia'
|
||||
baseUrl = 'https://en-thunderscans.com'
|
||||
overrideVersionCode = 4
|
||||
overrideVersionCode = 5
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
||||
|
|
|
@ -14,7 +14,7 @@ class ThunderScansFactory : SourceFactory {
|
|||
|
||||
class ThunderScansAR : MangaThemesiaAlt(
|
||||
"Thunder Scans",
|
||||
"https://thunderscans.com",
|
||||
"https://ar-thunderepic.com",
|
||||
"ar",
|
||||
dateFormat = SimpleDateFormat("MMM d, yyy", Locale("ar")),
|
||||
)
|
||||
|
|
|
@ -2,8 +2,8 @@ ext {
|
|||
extName = 'Empire Webtoon'
|
||||
extClass = '.EmpireWebtoon'
|
||||
themePkg = 'madara'
|
||||
baseUrl = 'https://webtoonsempireron.com'
|
||||
overrideVersionCode = 3
|
||||
baseUrl = 'https://webtoonempire-ron.com'
|
||||
overrideVersionCode = 4
|
||||
isNsfw = true
|
||||
}
|
||||
|
||||
|
|
|
@ -4,7 +4,6 @@ import android.app.Application
|
|||
import android.content.SharedPreferences
|
||||
import android.widget.Toast
|
||||
import androidx.preference.PreferenceScreen
|
||||
import eu.kanade.tachiyomi.extension.BuildConfig
|
||||
import eu.kanade.tachiyomi.multisrc.madara.Madara
|
||||
import eu.kanade.tachiyomi.source.ConfigurableSource
|
||||
import uy.kohesive.injekt.Injekt
|
||||
|
@ -15,46 +14,56 @@ import java.util.Locale
|
|||
class EmpireWebtoon :
|
||||
Madara(
|
||||
"Empire Webtoon",
|
||||
"https://webtoonsempireron.com",
|
||||
"https://webtoonempire-ron.com",
|
||||
"ar",
|
||||
SimpleDateFormat("d MMMM، yyyy", Locale("ar")),
|
||||
),
|
||||
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() }
|
||||
|
||||
private val preferences: SharedPreferences by lazy {
|
||||
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
||||
}
|
||||
|
||||
override val mangaSubString = "webtoon"
|
||||
|
||||
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) {
|
||||
val baseUrlPref = androidx.preference.EditTextPreference(screen.context).apply {
|
||||
key = BASE_URL_PREF
|
||||
title = BASE_URL_PREF_TITLE
|
||||
summary = BASE_URL_PREF_SUMMARY
|
||||
this.setDefaultValue(defaultBaseUrl)
|
||||
setDefaultValue(super.baseUrl)
|
||||
dialogTitle = BASE_URL_PREF_TITLE
|
||||
dialogMessage = "Default: ${super.baseUrl}"
|
||||
|
||||
setOnPreferenceChangeListener { _, _ ->
|
||||
Toast.makeText(screen.context, RESTART_TACHIYOMI, Toast.LENGTH_LONG).show()
|
||||
Toast.makeText(screen.context, RESTART_APP, Toast.LENGTH_LONG).show()
|
||||
true
|
||||
}
|
||||
}
|
||||
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'
|
||||
extClass = '.MangaNoon'
|
||||
themePkg = 'mangathemesia'
|
||||
baseUrl = 'https://noonscan.net'
|
||||
overrideVersionCode = 4
|
||||
baseUrl = 'https://manjanoon.xyz'
|
||||
overrideVersionCode = 5
|
||||
isNsfw = false
|
||||
}
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ import java.util.Calendar
|
|||
|
||||
class MangaNoon : MangaThemesia(
|
||||
"مانجا نون",
|
||||
"https://noonscan.net",
|
||||
"https://manjanoon.xyz",
|
||||
"ar",
|
||||
) {
|
||||
|
||||
|
|
|
@ -2,8 +2,8 @@ ext {
|
|||
extName = 'MangaSwat'
|
||||
extClass = '.MangaSwat'
|
||||
themePkg = 'mangathemesia'
|
||||
baseUrl = 'https://tatwt.com'
|
||||
overrideVersionCode = 21
|
||||
baseUrl = 'https://healteer.com'
|
||||
overrideVersionCode = 23
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package eu.kanade.tachiyomi.extension.ar.mangaswat
|
||||
|
||||
import android.app.Application
|
||||
import android.content.SharedPreferences
|
||||
import android.widget.Toast
|
||||
import androidx.preference.PreferenceScreen
|
||||
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 kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.decodeFromString
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import org.jsoup.nodes.Document
|
||||
import org.jsoup.nodes.Element
|
||||
|
@ -24,7 +22,7 @@ import java.util.Locale
|
|||
class MangaSwat :
|
||||
MangaThemesia(
|
||||
"MangaSwat",
|
||||
"https://tatwt.com",
|
||||
"https://healteer.com",
|
||||
"ar",
|
||||
dateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale("ar")),
|
||||
),
|
||||
|
@ -32,29 +30,53 @@ class MangaSwat :
|
|||
|
||||
override val baseUrl by lazy { getPrefBaseUrl() }
|
||||
|
||||
private val preferences: SharedPreferences by lazy {
|
||||
private val preferences by lazy {
|
||||
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
||||
}
|
||||
|
||||
override val client: OkHttpClient = super.client.newBuilder()
|
||||
override val client = super.client.newBuilder()
|
||||
.rateLimit(1)
|
||||
.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 {
|
||||
val request = super.searchMangaRequest(page, query, filters)
|
||||
if (query.isBlank()) return request
|
||||
val urlBuilder = request.url.newBuilder()
|
||||
|
||||
val url = request.url.newBuilder()
|
||||
.removePathSegment(0)
|
||||
.removeAllQueryParameters("title")
|
||||
.addQueryParameter("s", query)
|
||||
.build()
|
||||
// remove trailing slash
|
||||
if (request.url.pathSegments.last().isBlank()) {
|
||||
urlBuilder.removePathSegment(
|
||||
request.url.pathSegments.lastIndex,
|
||||
)
|
||||
}
|
||||
|
||||
if (query.isNotBlank()) {
|
||||
urlBuilder
|
||||
.removePathSegment(0)
|
||||
.removeAllQueryParameters("title")
|
||||
.addQueryParameter("s", query)
|
||||
.build()
|
||||
}
|
||||
|
||||
return request.newBuilder()
|
||||
.url(url)
|
||||
.url(urlBuilder.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 val seriesTitleSelector = "h1[itemprop=headline]"
|
||||
|
@ -82,13 +104,12 @@ class MangaSwat :
|
|||
}
|
||||
|
||||
@Serializable
|
||||
data class TSReader(
|
||||
class TSReader(
|
||||
val sources: List<ReaderImageSource>,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class ReaderImageSource(
|
||||
val source: String,
|
||||
class ReaderImageSource(
|
||||
val images: List<String>,
|
||||
)
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ ext {
|
|||
extClass = '.RocksManga'
|
||||
themePkg = 'madara'
|
||||
baseUrl = 'https://rocksmanga.com'
|
||||
overrideVersionCode = 1
|
||||
overrideVersionCode = 2
|
||||
isNsfw = false
|
||||
}
|
||||
|
||||
|
|
|
@ -49,7 +49,7 @@ class RocksManga : Madara(
|
|||
override val mangaDetailsSelectorThumbnail = ".manga-poster img"
|
||||
override val mangaDetailsSelectorGenre = "div.meta span:contains(التصنيف:) + span a"
|
||||
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 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"
|