Compare commits
No commits in common. "e0bb39f99adb84553c28dce5f190fbeb60fb80b9" and "5ecf338be0dd21937706e3284fc9d0edc5950e62" have entirely different histories.
e0bb39f99a
...
5ecf338be0
@ -1,14 +1,12 @@
|
|||||||
# 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
|
||||||
@ -17,3 +15,5 @@ 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.10-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
|
||||||
networkTimeout=10000
|
networkTimeout=10000
|
||||||
validateDistributionUrl=true
|
validateDistributionUrl=true
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
@ -9,7 +9,6 @@ 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,5 +25,3 @@ 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.null2264.injekt:injekt-core", version = "4135455a2a" }
|
injekt-core = { module = "com.github.inorichi.injekt:injekt-core", version = "65b0440" }
|
||||||
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" }
|
||||||
|
2
gradle/wrapper/gradle-wrapper.properties
vendored
@ -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.10-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-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 = 4
|
baseVersionCode = 3
|
||||||
|
|
||||||
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("""if\s*\(\s*([a-zA-Z0-9_]+)\s*==\s*(?<keyType>\d+)\s*\)\s*\{\s*return\s*'(?<key>[a-zA-Z0-9_]+)'\s*;""")
|
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 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 = 24
|
baseVersionCode = 23
|
||||||
|
@ -2,4 +2,4 @@ plugins {
|
|||||||
id("lib-multisrc")
|
id("lib-multisrc")
|
||||||
}
|
}
|
||||||
|
|
||||||
baseVersionCode = 4
|
baseVersionCode = 3
|
||||||
|
@ -83,8 +83,8 @@ class Post<T>(val post: T)
|
|||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
class ChapterListResponse(
|
class ChapterListResponse(
|
||||||
val isNovel: Boolean = false,
|
val isNovel: Boolean,
|
||||||
val slug: String? = null,
|
val slug: String,
|
||||||
val chapters: List<Chapter>,
|
val chapters: List<Chapter>,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -96,13 +96,11 @@ 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 {
|
||||||
val seriesSlug = mangaSlug ?: mangaPost.slug
|
url = "/series/$mangaSlug/$slug#$id"
|
||||||
url = "/series/$seriesSlug/$slug#$id"
|
|
||||||
name = "Chapter $number"
|
name = "Chapter $number"
|
||||||
scanlator = createdBy.name
|
scanlator = createdBy.name
|
||||||
date_upload = try {
|
date_upload = try {
|
||||||
@ -113,9 +111,4 @@ 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 = 7
|
baseVersionCode = 4
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
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
|
||||||
@ -30,7 +31,9 @@ abstract class Keyoapp(
|
|||||||
) : ParsedHttpSource() {
|
) : ParsedHttpSource() {
|
||||||
override val supportsLatest = true
|
override val supportsLatest = true
|
||||||
|
|
||||||
override val client = network.cloudflareClient
|
override val client = network.cloudflareClient.newBuilder()
|
||||||
|
.rateLimit(2)
|
||||||
|
.build()
|
||||||
|
|
||||||
override fun headersBuilder() = super.headersBuilder()
|
override fun headersBuilder() = super.headersBuilder()
|
||||||
.add("Referer", "$baseUrl/")
|
.add("Referer", "$baseUrl/")
|
||||||
@ -194,18 +197,7 @@ 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 = buildList {
|
genre = document.select("div.grid:has(>h1) > div > a").joinToString { it.text() }
|
||||||
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()) {
|
||||||
@ -231,27 +223,15 @@ 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(oldImgCdnRegex) }
|
.filter { it.contains(imgCdnRegex) }
|
||||||
.mapIndexed { index, img ->
|
.mapIndexed { index, img ->
|
||||||
Page(index, document.location(), img)
|
Page(index, document.location(), img)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected val cdnUrl = "https://cdn.igniscans.com"
|
private val imgCdnRegex = Regex("""^(https?:)?//cdn\d*\.keyoapp\.com""")
|
||||||
|
|
||||||
private val oldImgCdnRegex = Regex("""^(https?:)?//cdn\d*\.keyoapp\.com""")
|
|
||||||
|
|
||||||
override fun imageUrlParse(document: Document) = ""
|
override fun imageUrlParse(document: Document) = ""
|
||||||
|
|
||||||
@ -267,7 +247,7 @@ abstract class Keyoapp(
|
|||||||
return url
|
return url
|
||||||
}
|
}
|
||||||
|
|
||||||
protected open fun Element.getImageUrl(selector: String): String? {
|
private 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 = 31
|
baseVersionCode = 30
|
||||||
|
@ -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", "Publicándose", "En emision", "连载中", "Em Lançamento", "Devam Ediyo",
|
"Curso", "En marcha", "Publicandose", "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 open val popularFilter by lazy { FilterList(OrderByFilter("", orderByFilterOptions, "popular")) }
|
protected val popularFilter by lazy { FilterList(OrderByFilter("", orderByFilterOptions, "popular")) }
|
||||||
protected open val latestFilter by lazy { FilterList(OrderByFilter("", orderByFilterOptions, "update")) }
|
protected 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())
|
||||||
}
|
}
|
||||||
|
|
||||||
protected open fun parseGenres(document: Document): List<GenreData>? {
|
private 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(),
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
<application>
|
<application>
|
||||||
<activity
|
<activity
|
||||||
android:name="eu.kanade.tachiyomi.extension.tr.sadscans.SadscansUrlActivity"
|
android:name="eu.kanade.tachiyomi.multisrc.po2scans.PO2ScansUrlActivity"
|
||||||
android:excludeFromRecents="true"
|
android:excludeFromRecents="true"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:theme="@android:style/Theme.NoDisplay">
|
android:theme="@android:style/Theme.NoDisplay">
|
||||||
@ -14,9 +14,9 @@
|
|||||||
<category android:name="android.intent.category.BROWSABLE" />
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
|
||||||
<data
|
<data
|
||||||
android:host="sadscans.com"
|
android:host="${SOURCEHOST}"
|
||||||
android:pathPattern="/series/..*"
|
android:pathPattern="/series/..*"
|
||||||
android:scheme="https" />
|
android:scheme="${SOURCESCHEME}" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
</application>
|
</application>
|
9
lib-multisrc/po2scans/build.gradle.kts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
plugins {
|
||||||
|
id("lib-multisrc")
|
||||||
|
}
|
||||||
|
|
||||||
|
baseVersionCode = 1
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
api(project(":lib:dataimage"))
|
||||||
|
}
|
@ -0,0 +1,141 @@
|
|||||||
|
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,4 +1,4 @@
|
|||||||
package eu.kanade.tachiyomi.extension.tr.sadscans
|
package eu.kanade.tachiyomi.multisrc.po2scans
|
||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.ActivityNotFoundException
|
import android.content.ActivityNotFoundException
|
||||||
@ -7,7 +7,7 @@ import android.os.Bundle
|
|||||||
import android.util.Log
|
import android.util.Log
|
||||||
import kotlin.system.exitProcess
|
import kotlin.system.exitProcess
|
||||||
|
|
||||||
class SadscansUrlActivity : Activity() {
|
class PO2ScansUrlActivity : Activity() {
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
val pathSegments = intent?.data?.pathSegments
|
val pathSegments = intent?.data?.pathSegments
|
||||||
@ -15,17 +15,17 @@ class SadscansUrlActivity : Activity() {
|
|||||||
val slug = pathSegments[1]
|
val slug = pathSegments[1]
|
||||||
val mainIntent = Intent().apply {
|
val mainIntent = Intent().apply {
|
||||||
action = "eu.kanade.tachiyomi.SEARCH"
|
action = "eu.kanade.tachiyomi.SEARCH"
|
||||||
putExtra("query", "${Sadscans.SLUG_SEARCH_PREFIX}$slug")
|
putExtra("query", "${PO2Scans.SLUG_SEARCH_PREFIX}$slug")
|
||||||
putExtra("filter", packageName)
|
putExtra("filter", packageName)
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
startActivity(mainIntent)
|
startActivity(mainIntent)
|
||||||
} catch (e: ActivityNotFoundException) {
|
} catch (e: ActivityNotFoundException) {
|
||||||
Log.e("SadscansUrlActivity", "Could not start activity", e)
|
Log.e("PO2ScansUrlActivity", "Could not start activity", e)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Log.e("SadscansUrlActivity", "could not parse URI from intent $intent")
|
Log.e("PO2ScansUrlActivity", "could not parse URI from intent $intent")
|
||||||
}
|
}
|
||||||
|
|
||||||
finish()
|
finish()
|
@ -1,7 +1,7 @@
|
|||||||
ext {
|
ext {
|
||||||
extName = 'Akuma'
|
extName = 'Akuma'
|
||||||
extClass = '.AkumaFactory'
|
extClass = '.AkumaFactory'
|
||||||
extVersionCode = 5
|
extVersionCode = 4
|
||||||
isNsfw = true
|
isNsfw = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,13 +1,8 @@
|
|||||||
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
|
||||||
@ -25,8 +20,6 @@ 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
|
||||||
@ -36,7 +29,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,
|
||||||
) : ConfigurableSource, ParsedHttpSource() {
|
) : ParsedHttpSource() {
|
||||||
|
|
||||||
override val name = "Akuma"
|
override val name = "Akuma"
|
||||||
|
|
||||||
@ -112,23 +105,6 @@ 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")
|
||||||
@ -173,9 +149,7 @@ 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().replace("\"", "").let {
|
title = element.select(".overlay-title").text()
|
||||||
if (displayFullTitle) it.trim() else it.shortenTitle()
|
|
||||||
}
|
|
||||||
thumbnail_url = element.select("img").attr("abs:src")
|
thumbnail_url = element.select("img").attr("abs:src")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -202,7 +176,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) {
|
||||||
@ -246,9 +220,7 @@ 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().replace("\"", "").let {
|
title = select(".entry-title").text()
|
||||||
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()
|
||||||
@ -330,7 +302,6 @@ 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()
|
||||||
|
@ -1,7 +0,0 @@
|
|||||||
ext {
|
|
||||||
extName = 'ComicsKingdom'
|
|
||||||
extClass = '.ComicsKingdomFactory'
|
|
||||||
extVersionCode = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
apply from: "$rootDir/common.gradle"
|
|
Before Width: | Height: | Size: 7.1 KiB |
Before Width: | Height: | Size: 3.5 KiB |
Before Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 29 KiB |
@ -1,44 +0,0 @@
|
|||||||
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 }
|
|
@ -1,311 +0,0 @@
|
|||||||
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()
|
|
||||||
}
|
|
@ -1,8 +0,0 @@
|
|||||||
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"))
|
|
||||||
}
|
|
@ -1,14 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
Before Width: | Height: | Size: 4.1 KiB |
Before Width: | Height: | Size: 2.4 KiB |
Before Width: | Height: | Size: 5.8 KiB |
Before Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 16 KiB |
@ -1,12 +0,0 @@
|
|||||||
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 = 33
|
extVersionCode = 32
|
||||||
isNsfw = true
|
isNsfw = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -517,9 +517,6 @@ 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,7 +7,6 @@ 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?,
|
||||||
|
Before Width: | Height: | Size: 3.3 KiB |
Before Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 4.8 KiB |
Before Width: | Height: | Size: 7.7 KiB |
Before Width: | Height: | Size: 12 KiB |
@ -1,41 +0,0 @@
|
|||||||
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)
|
|
||||||
}
|
|
@ -1,7 +1,7 @@
|
|||||||
ext {
|
ext {
|
||||||
extName = 'LANraragi'
|
extName = 'LANraragi'
|
||||||
extClass = '.LANraragiFactory'
|
extClass = '.LANraragiFactory'
|
||||||
extVersionCode = 18
|
extVersionCode = 17
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: "$rootDir/common.gradle"
|
apply from: "$rootDir/common.gradle"
|
||||||
|
@ -7,7 +7,6 @@ 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,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -26,6 +25,7 @@ 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 = if (archive.summary.isNullOrBlank()) archive.title else archive.summary
|
description = archive.title
|
||||||
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,10 +406,12 @@ 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.
|
// 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?
|
||||||
getCategories()
|
getCategories()
|
||||||
|
|
||||||
return listOf(Pair("", ""))
|
return listOf(Pair("", ""))
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
ext {
|
ext {
|
||||||
extName = 'MangaDex'
|
extName = 'MangaDex'
|
||||||
extClass = '.MangaDexFactory'
|
extClass = '.MangaDexFactory'
|
||||||
extVersionCode = 194
|
extVersionCode = 193
|
||||||
isNsfw = true
|
isNsfw = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -138,6 +138,13 @@ 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,6 +3,7 @@ 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
|
||||||
@ -55,7 +56,6 @@ 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,7 +67,6 @@ 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)
|
||||||
@ -79,8 +78,13 @@ 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 {
|
||||||
@ -391,7 +395,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.replace("/manga/", "/title/") + "/" + helper.titleToSlug(manga.title)
|
return baseUrl + manga.url + "/" + helper.titleToSlug(manga.title)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -757,6 +761,30 @@ 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)
|
||||||
@ -766,6 +794,7 @@ 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 =
|
||||||
@ -840,14 +869,20 @@ 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(): SharedPreferences {
|
private fun SharedPreferences.sanitizeExistingUuidPrefs() {
|
||||||
if (getBoolean(MDConstants.getHasSanitizedUuidsPrefKey(dexLang), false)) {
|
if (getBoolean(MDConstants.getHasSanitizedUuidsPrefKey(dexLang), false)) {
|
||||||
return this
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val blockedGroups = getString(MDConstants.getBlockedGroupsPrefKey(dexLang), "")!!
|
val blockedGroups = getString(MDConstants.getBlockedGroupsPrefKey(dexLang), "")!!
|
||||||
@ -867,7 +902,5 @@ 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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,39 @@
|
|||||||
|
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,8 +17,6 @@ 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 = 53
|
extVersionCode = 52
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: "$rootDir/common.gradle"
|
apply from: "$rootDir/common.gradle"
|
||||||
|
@ -289,10 +289,9 @@ class MangaPlus(
|
|||||||
}
|
}
|
||||||
|
|
||||||
val titleDetailView = result.success.titleDetailView!!
|
val titleDetailView = result.success.titleDetailView!!
|
||||||
val subtitleOnly = preferences.subtitleOnly()
|
|
||||||
|
|
||||||
return titleDetailView.chapterList
|
return titleDetailView.chapterList
|
||||||
.map { it.toSChapter(subtitleOnly) }
|
.map(Chapter::toSChapter)
|
||||||
.reversed()
|
.reversed()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -308,8 +307,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")
|
||||||
@ -379,16 +378,8 @@ 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(
|
||||||
@ -503,11 +494,11 @@ class MangaPlus(
|
|||||||
json.decodeFromString(body.string())
|
json.decodeFromString(body.string())
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun SharedPreferences.imageQuality(): String = getString("${QUALITY_PREF_KEY}_$lang", QUALITY_PREF_DEFAULT_VALUE)!!
|
private val SharedPreferences.imageQuality: String
|
||||||
|
get() = getString("${QUALITY_PREF_KEY}_$lang", QUALITY_PREF_DEFAULT_VALUE)!!
|
||||||
|
|
||||||
private fun SharedPreferences.splitImages(): Boolean = getBoolean("${SPLIT_PREF_KEY}_$lang", SPLIT_PREF_DEFAULT_VALUE)
|
private val SharedPreferences.splitImages: Boolean
|
||||||
|
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)
|
||||||
@ -535,9 +526,6 @@ 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,16 +322,11 @@ class Chapter(
|
|||||||
val isExpired: Boolean
|
val isExpired: Boolean
|
||||||
get() = subTitle == null
|
get() = subTitle == null
|
||||||
|
|
||||||
fun toSChapter(subtitlePref: Boolean): SChapter = SChapter.create().apply {
|
fun toSChapter(): SChapter = SChapter.create().apply {
|
||||||
name = if (subtitlePref && subTitle != null) {
|
name = "${this@Chapter.name} - $subTitle"
|
||||||
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"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
8
src/all/meituatop/build.gradle
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
ext {
|
||||||
|
extName = 'Meitua.top'
|
||||||
|
extClass = '.MeituaTop'
|
||||||
|
extVersionCode = 6
|
||||||
|
isNsfw = true
|
||||||
|
}
|
||||||
|
|
||||||
|
apply from: "$rootDir/common.gradle"
|
BIN
src/all/meituatop/res/mipmap-hdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 2.2 KiB |
BIN
src/all/meituatop/res/mipmap-mdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
src/all/meituatop/res/mipmap-xhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 2.6 KiB |
BIN
src/all/meituatop/res/mipmap-xxhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 4.3 KiB |
BIN
src/all/meituatop/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 5.5 KiB |
@ -0,0 +1,111 @@
|
|||||||
|
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) }
|
||||||
|
}
|
@ -1,8 +0,0 @@
|
|||||||
ext {
|
|
||||||
extName = 'Mitaku'
|
|
||||||
extClass = '.Mitaku'
|
|
||||||
extVersionCode = 1
|
|
||||||
isNsfw = true
|
|
||||||
}
|
|
||||||
|
|
||||||
apply from: "$rootDir/common.gradle"
|
|
Before Width: | Height: | Size: 7.2 KiB |
Before Width: | Height: | Size: 3.6 KiB |
Before Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 23 KiB |
Before Width: | Height: | Size: 37 KiB |
@ -1,171 +0,0 @@
|
|||||||
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 = 46
|
extVersionCode = 41
|
||||||
isNsfw = true
|
isNsfw = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -136,51 +136,57 @@ 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 "language:$nhLang "
|
val nhLangSearch = if (nhLang.isBlank()) "" else "+$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", "$query $advQuery")
|
.addQueryParameter("q", "$fixedQuery $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()
|
||||||
// Blank query (Multi + sort by popular month/week/day) shows a 404 page
|
.addQueryParameter("q", "$fixedQuery $nhLangSearch$advQuery")
|
||||||
// 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())
|
||||||
|
|
||||||
filterList.findInstance<SortFilter>()?.let { f ->
|
if (isOkayToSort) {
|
||||||
url.addQueryParameter("sort", f.toUriPart())
|
filterList.findInstance<SortFilter>()?.let { f ->
|
||||||
|
url.addQueryParameter("sort", f.toUriPart())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return GET(url.build(), headers)
|
return GET(url.build(), headers)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun combineQuery(filters: FilterList): String = buildString {
|
private fun combineQuery(filters: FilterList): String {
|
||||||
filters.filterIsInstance<AdvSearchEntryFilter>().forEach { filter ->
|
val stringBuilder = StringBuilder()
|
||||||
filter.state.split(",")
|
val advSearch = filters.filterIsInstance<AdvSearchEntryFilter>().flatMap { filter ->
|
||||||
.map(String::trim)
|
val splitState = filter.state.split(",").map(String::trim).filterNot(String::isBlank)
|
||||||
.filterNot(String::isBlank)
|
splitState.map {
|
||||||
.forEach { tag ->
|
AdvSearchEntry(filter.name, it.removePrefix("-"), it.startsWith("-"))
|
||||||
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 {
|
||||||
@ -220,7 +226,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 = 5
|
overrideVersionCode = 4
|
||||||
}
|
}
|
||||||
|
|
||||||
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://ar-thunderepic.com",
|
"https://thunderscans.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://webtoonempire-ron.com'
|
baseUrl = 'https://webtoonsempireron.com'
|
||||||
overrideVersionCode = 4
|
overrideVersionCode = 3
|
||||||
isNsfw = true
|
isNsfw = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@ 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
|
||||||
@ -14,56 +15,46 @@ import java.util.Locale
|
|||||||
class EmpireWebtoon :
|
class EmpireWebtoon :
|
||||||
Madara(
|
Madara(
|
||||||
"Empire Webtoon",
|
"Empire Webtoon",
|
||||||
"https://webtoonempire-ron.com",
|
"https://webtoonsempireron.com",
|
||||||
"ar",
|
"ar",
|
||||||
SimpleDateFormat("d MMMM، yyyy", Locale("ar")),
|
SimpleDateFormat("d MMMM، yyyy", Locale("ar")),
|
||||||
),
|
),
|
||||||
ConfigurableSource {
|
ConfigurableSource {
|
||||||
|
|
||||||
private val preferences: SharedPreferences =
|
private val defaultBaseUrl = "https://webtoonsempireron.com"
|
||||||
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
|
||||||
setDefaultValue(super.baseUrl)
|
this.setDefaultValue(defaultBaseUrl)
|
||||||
dialogTitle = BASE_URL_PREF_TITLE
|
dialogTitle = BASE_URL_PREF_TITLE
|
||||||
dialogMessage = "Default: ${super.baseUrl}"
|
|
||||||
|
|
||||||
setOnPreferenceChangeListener { _, _ ->
|
setOnPreferenceChangeListener { _, _ ->
|
||||||
Toast.makeText(screen.context, RESTART_APP, Toast.LENGTH_LONG).show()
|
Toast.makeText(screen.context, RESTART_TACHIYOMI, Toast.LENGTH_LONG).show()
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
screen.addPreference(baseUrlPref)
|
screen.addPreference(baseUrlPref)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getPrefBaseUrl(): String = preferences.getString(BASE_URL_PREF, super.baseUrl)!!
|
private fun getPrefBaseUrl(): String = preferences.getString(BASE_URL_PREF, defaultBaseUrl)!!
|
||||||
|
|
||||||
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://manjanoon.xyz'
|
baseUrl = 'https://noonscan.net'
|
||||||
overrideVersionCode = 5
|
overrideVersionCode = 4
|
||||||
isNsfw = false
|
isNsfw = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ import java.util.Calendar
|
|||||||
|
|
||||||
class MangaNoon : MangaThemesia(
|
class MangaNoon : MangaThemesia(
|
||||||
"مانجا نون",
|
"مانجا نون",
|
||||||
"https://manjanoon.xyz",
|
"https://noonscan.net",
|
||||||
"ar",
|
"ar",
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
@ -2,8 +2,8 @@ ext {
|
|||||||
extName = 'MangaSwat'
|
extName = 'MangaSwat'
|
||||||
extClass = '.MangaSwat'
|
extClass = '.MangaSwat'
|
||||||
themePkg = 'mangathemesia'
|
themePkg = 'mangathemesia'
|
||||||
baseUrl = 'https://healteer.com'
|
baseUrl = 'https://tatwt.com'
|
||||||
overrideVersionCode = 23
|
overrideVersionCode = 21
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: "$rootDir/common.gradle"
|
apply from: "$rootDir/common.gradle"
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
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
|
||||||
@ -11,6 +12,7 @@ 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
|
||||||
@ -22,7 +24,7 @@ import java.util.Locale
|
|||||||
class MangaSwat :
|
class MangaSwat :
|
||||||
MangaThemesia(
|
MangaThemesia(
|
||||||
"MangaSwat",
|
"MangaSwat",
|
||||||
"https://healteer.com",
|
"https://tatwt.com",
|
||||||
"ar",
|
"ar",
|
||||||
dateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale("ar")),
|
dateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale("ar")),
|
||||||
),
|
),
|
||||||
@ -30,53 +32,29 @@ class MangaSwat :
|
|||||||
|
|
||||||
override val baseUrl by lazy { getPrefBaseUrl() }
|
override val baseUrl by lazy { getPrefBaseUrl() }
|
||||||
|
|
||||||
private val preferences by lazy {
|
private val preferences: SharedPreferences by lazy {
|
||||||
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
||||||
}
|
}
|
||||||
|
|
||||||
override val client = super.client.newBuilder()
|
override val client: OkHttpClient = 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)
|
||||||
val urlBuilder = request.url.newBuilder()
|
if (query.isBlank()) return request
|
||||||
|
|
||||||
// remove trailing slash
|
val url = request.url.newBuilder()
|
||||||
if (request.url.pathSegments.last().isBlank()) {
|
.removePathSegment(0)
|
||||||
urlBuilder.removePathSegment(
|
.removeAllQueryParameters("title")
|
||||||
request.url.pathSegments.lastIndex,
|
.addQueryParameter("s", query)
|
||||||
)
|
.build()
|
||||||
}
|
|
||||||
|
|
||||||
if (query.isNotBlank()) {
|
|
||||||
urlBuilder
|
|
||||||
.removePathSegment(0)
|
|
||||||
.removeAllQueryParameters("title")
|
|
||||||
.addQueryParameter("s", query)
|
|
||||||
.build()
|
|
||||||
}
|
|
||||||
|
|
||||||
return request.newBuilder()
|
return request.newBuilder()
|
||||||
.url(urlBuilder.build())
|
.url(url)
|
||||||
.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]"
|
||||||
@ -104,12 +82,13 @@ class MangaSwat :
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
class TSReader(
|
data class TSReader(
|
||||||
val sources: List<ReaderImageSource>,
|
val sources: List<ReaderImageSource>,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
class ReaderImageSource(
|
data 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 = 2
|
overrideVersionCode = 1
|
||||||
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() = "li.chapter-item"
|
override fun chapterListSelector() = ".chapters-list 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"
|
||||||
|
|
||||||
|
@ -1,10 +0,0 @@
|
|||||||
ext {
|
|
||||||
extName = 'Scans 4u'
|
|
||||||
extClass = '.Scans4u'
|
|
||||||
themePkg = 'keyoapp'
|
|
||||||
baseUrl = 'https://4uscans.com'
|
|
||||||
overrideVersionCode = 0
|
|
||||||
isNsfw = false
|
|
||||||
}
|
|
||||||
|
|
||||||
apply from: "$rootDir/common.gradle"
|
|
Before Width: | Height: | Size: 5.2 KiB |
Before Width: | Height: | Size: 2.9 KiB |
Before Width: | Height: | Size: 7.4 KiB |
Before Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 23 KiB |
@ -1,5 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.extension.ar.scans4u
|
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.multisrc.keyoapp.Keyoapp
|
|
||||||
|
|
||||||
class Scans4u : Keyoapp("Scans 4u", "https://4uscans.com", "ar")
|
|
@ -2,8 +2,8 @@ ext {
|
|||||||
extName = 'YonaBar'
|
extName = 'YonaBar'
|
||||||
extClass = '.YonaBar'
|
extClass = '.YonaBar'
|
||||||
themePkg = 'madara'
|
themePkg = 'madara'
|
||||||
baseUrl = 'https://yonabar.xyz'
|
baseUrl = 'https://yonabar.com'
|
||||||
overrideVersionCode = 3
|
overrideVersionCode = 2
|
||||||
isNsfw = true
|
isNsfw = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,19 +1,7 @@
|
|||||||
package eu.kanade.tachiyomi.extension.ar.yonabar
|
package eu.kanade.tachiyomi.extension.ar.yonabar
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.multisrc.madara.Madara
|
import eu.kanade.tachiyomi.multisrc.madara.Madara
|
||||||
import eu.kanade.tachiyomi.network.interceptor.rateLimit
|
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
class YonaBar : Madara(
|
class YonaBar : Madara("YonaBar", "https://yonabar.com", "ar", SimpleDateFormat("yyyy-MM-dd", Locale("ar")))
|
||||||
"YonaBar",
|
|
||||||
"https://yonabar.xyz",
|
|
||||||
"ar",
|
|
||||||
SimpleDateFormat("MMM dd, yyyy", Locale("ar")),
|
|
||||||
) {
|
|
||||||
override val client = super.client.newBuilder()
|
|
||||||
.rateLimit(3)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
override val mangaSubString = "yaoi"
|
|
||||||
}
|
|
||||||
|
@ -3,7 +3,7 @@ ext {
|
|||||||
extClass = '.YuriMoonSub'
|
extClass = '.YuriMoonSub'
|
||||||
themePkg = 'zeistmanga'
|
themePkg = 'zeistmanga'
|
||||||
baseUrl = 'https://yurimoonsub.blogspot.com'
|
baseUrl = 'https://yurimoonsub.blogspot.com'
|
||||||
overrideVersionCode = 1
|
overrideVersionCode = 0
|
||||||
isNsfw = true
|
isNsfw = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,30 +2,9 @@ package eu.kanade.tachiyomi.extension.ar.yurimoonsub
|
|||||||
|
|
||||||
import eu.kanade.tachiyomi.multisrc.zeistmanga.ZeistManga
|
import eu.kanade.tachiyomi.multisrc.zeistmanga.ZeistManga
|
||||||
import eu.kanade.tachiyomi.network.interceptor.rateLimit
|
import eu.kanade.tachiyomi.network.interceptor.rateLimit
|
||||||
import org.jsoup.nodes.Document
|
|
||||||
import java.net.URLDecoder
|
|
||||||
import java.nio.charset.StandardCharsets
|
|
||||||
|
|
||||||
class YuriMoonSub : ZeistManga(
|
class YuriMoonSub : ZeistManga("Yuri Moon Sub", "https://yurimoonsub.blogspot.com", "ar") {
|
||||||
"Yuri Moon Sub",
|
|
||||||
"https://yurimoonsub.blogspot.com",
|
|
||||||
"ar",
|
|
||||||
) {
|
|
||||||
override val client = super.client.newBuilder()
|
override val client = super.client.newBuilder()
|
||||||
.rateLimit(2)
|
.rateLimit(2)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
override fun getChapterFeedUrl(doc: Document): String {
|
|
||||||
return URLDecoder.decode(super.getChapterFeedUrl(doc), StandardCharsets.UTF_8.toString())
|
|
||||||
.removeArabicChars()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun String.removeArabicChars() =
|
|
||||||
this.replace(ARABIC_CHARS_REGEX, "")
|
|
||||||
.replace(EXTRA_SPACES_REGEX, "")
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
val ARABIC_CHARS_REGEX = "[\\u0600-\\u06FF]".toRegex()
|
|
||||||
val EXTRA_SPACES_REGEX = "\\s{2,}".toRegex()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
7
src/de/wiemanga/build.gradle
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
ext {
|
||||||
|
extName = 'WieManga'
|
||||||
|
extClass = '.WieManga'
|
||||||
|
extVersionCode = 5
|
||||||
|
}
|
||||||
|
|
||||||
|
apply from: "$rootDir/common.gradle"
|
BIN
src/de/wiemanga/res/mipmap-hdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 3.5 KiB |
BIN
src/de/wiemanga/res/mipmap-mdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 2.0 KiB |
BIN
src/de/wiemanga/res/mipmap-xhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 4.3 KiB |
BIN
src/de/wiemanga/res/mipmap-xxhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 7.9 KiB |