revert kotlin 2.1.0 and deps for now (#7468)
generated serializers seem to be missing in final apk, need further investigation
This commit is contained in:
parent
2e45708568
commit
0658c1926c
|
@ -12,5 +12,5 @@ dependencies {
|
|||
implementation(libs.gradle.agp)
|
||||
implementation(libs.gradle.kotlin)
|
||||
implementation(libs.gradle.serialization)
|
||||
implementation(libs.spotless.gradle)
|
||||
implementation(libs.gradle.kotlinter)
|
||||
}
|
||||
|
|
|
@ -1,50 +0,0 @@
|
|||
plugins {
|
||||
id("com.diffplug.spotless")
|
||||
}
|
||||
|
||||
spotless {
|
||||
ratchetFrom = "105f615b339e681a630f21dc0d363b8ca1cb17d5"
|
||||
|
||||
kotlin {
|
||||
target("**/*.kt", "**/*.kts")
|
||||
targetExclude("**/build/**/*.kt")
|
||||
ktlint()
|
||||
.editorConfigOverride(mapOf(
|
||||
"ktlint_standard_discouraged-comment-location" to "disabled",
|
||||
"ktlint_function_signature_body_expression_wrapping" to "default",
|
||||
"ktlint_standard_no-empty-first-line-in-class-body" to "disable",
|
||||
"ktlint_standard_chain-method-continuation" to "disable"
|
||||
))
|
||||
trimTrailingWhitespace()
|
||||
endWithNewline()
|
||||
}
|
||||
|
||||
format("gradle") {
|
||||
target("**/*.gradle")
|
||||
trimTrailingWhitespace()
|
||||
endWithNewline()
|
||||
}
|
||||
|
||||
format("xml") {
|
||||
target("**/*.xml")
|
||||
targetExclude("**/build/**/*.xml")
|
||||
trimTrailingWhitespace()
|
||||
endWithNewline()
|
||||
}
|
||||
}
|
||||
|
||||
tasks {
|
||||
named("preBuild") {
|
||||
dependsOn(
|
||||
tasks.getByName("spotlessCheck")
|
||||
)
|
||||
}
|
||||
|
||||
if (System.getenv("CI") != "true") {
|
||||
named("spotlessCheck") {
|
||||
dependsOn(
|
||||
tasks.getByName("spotlessApply")
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,7 +2,6 @@ plugins {
|
|||
id("com.android.library")
|
||||
kotlin("android")
|
||||
id("kotlinx-serialization")
|
||||
id("keiyoushi.lint")
|
||||
}
|
||||
|
||||
android {
|
||||
|
@ -17,16 +16,6 @@ android {
|
|||
buildFeatures {
|
||||
androidResources = false
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = JavaVersion.VERSION_17.toString()
|
||||
freeCompilerArgs += "-opt-in=kotlinx.serialization.ExperimentalSerializationApi"
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
|
|
@ -2,7 +2,7 @@ plugins {
|
|||
id("com.android.library")
|
||||
kotlin("android")
|
||||
id("kotlinx-serialization")
|
||||
id("keiyoushi.lint")
|
||||
id("org.jmailen.kotlinter")
|
||||
}
|
||||
|
||||
android {
|
||||
|
@ -23,21 +23,35 @@ android {
|
|||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = JavaVersion.VERSION_17.toString()
|
||||
freeCompilerArgs += "-opt-in=kotlinx.serialization.ExperimentalSerializationApi"
|
||||
}
|
||||
}
|
||||
|
||||
kotlinter {
|
||||
experimentalRules = true
|
||||
disabledRules = arrayOf(
|
||||
"experimental:argument-list-wrapping", // Doesn't play well with Android Studio
|
||||
"experimental:comment-wrapping",
|
||||
)
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly(versionCatalogs.named("libs").findBundle("common").get())
|
||||
}
|
||||
|
||||
tasks {
|
||||
preBuild {
|
||||
dependsOn(lintKotlin)
|
||||
}
|
||||
|
||||
if (System.getenv("CI") != "true") {
|
||||
lintKotlin {
|
||||
dependsOn(formatKotlin)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tasks.register("printDependentExtensions") {
|
||||
doLast {
|
||||
project.printDependentExtensions()
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: 'kotlinx-serialization'
|
||||
apply plugin: 'keiyoushi.lint'
|
||||
apply plugin: 'org.jmailen.kotlinter'
|
||||
|
||||
assert !ext.has("pkgNameSuffix")
|
||||
assert !ext.has("libVersion")
|
||||
|
@ -18,17 +18,6 @@ android {
|
|||
sourceSets {
|
||||
main {
|
||||
manifest.srcFile "AndroidManifest.xml"
|
||||
if (!manifest.srcFile.exists()) {
|
||||
def buildDir = layout.buildDirectory.get().getAsFile().path
|
||||
mkdir(buildDir)
|
||||
File tempFile = new File(buildDir, "tempAndroidManifest.xml")
|
||||
if (!tempFile.exists()) {
|
||||
tempFile.withWriter {
|
||||
it.write('<?xml version="1.0" encoding="utf-8"?>\n<manifest />\n')
|
||||
}
|
||||
}
|
||||
manifest.srcFile(tempFile.path)
|
||||
}
|
||||
java.srcDirs = ['src']
|
||||
res.srcDirs = ['res']
|
||||
assets.srcDirs = ['assets']
|
||||
|
@ -92,14 +81,22 @@ android {
|
|||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = JavaVersion.VERSION_17.toString()
|
||||
jvmTarget = JavaVersion.VERSION_1_8.toString()
|
||||
freeCompilerArgs += "-opt-in=kotlinx.serialization.ExperimentalSerializationApi"
|
||||
}
|
||||
|
||||
kotlinter {
|
||||
experimentalRules = true
|
||||
disabledRules = [
|
||||
"experimental:argument-list-wrapping", // Doesn't play well with Android Studio
|
||||
"experimental:comment-wrapping",
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
@ -107,3 +104,23 @@ dependencies {
|
|||
implementation(project(":core"))
|
||||
compileOnly(libs.bundles.common)
|
||||
}
|
||||
|
||||
tasks.register("writeManifestFile") {
|
||||
doLast {
|
||||
def manifest = android.sourceSets.getByName("main").manifest
|
||||
if (!manifest.srcFile.exists()) {
|
||||
File tempFile = layout.buildDirectory.get().file("tempAndroidManifest.xml").getAsFile()
|
||||
if (!tempFile.exists()) {
|
||||
tempFile.withWriter {
|
||||
it.write('<?xml version="1.0" encoding="utf-8"?>\n<manifest />\n')
|
||||
}
|
||||
}
|
||||
manifest.srcFile(tempFile.path)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
preBuild.dependsOn(writeManifestFile, lintKotlin)
|
||||
if (System.getenv("CI") != "true") {
|
||||
lintKotlin.dependsOn(formatKotlin)
|
||||
}
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
[versions]
|
||||
kotlin_version = "2.1.0"
|
||||
coroutines_version = "1.10.1"
|
||||
serialization_version = "1.8.0"
|
||||
kotlin_version = "1.7.21"
|
||||
coroutines_version = "1.6.4"
|
||||
serialization_version = "1.4.0"
|
||||
|
||||
[libraries]
|
||||
gradle-agp = { module = "com.android.tools.build:gradle", version = "8.8.0" }
|
||||
gradle-agp = { module = "com.android.tools.build:gradle", version = "8.6.1" }
|
||||
gradle-kotlin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin_version" }
|
||||
gradle-serialization = { module = "org.jetbrains.kotlin:kotlin-serialization", version.ref = "kotlin_version" }
|
||||
|
||||
spotless-gradle = { group = "com.diffplug.spotless", name = "spotless-plugin-gradle", version = "7.0.2" }
|
||||
gradle-kotlinter = { module = "org.jmailen.gradle:kotlinter-gradle", version = "3.13.0" }
|
||||
|
||||
tachiyomi-lib = { module = "com.github.tachiyomiorg:extensions-lib", version = "1.4.2" }
|
||||
|
||||
|
@ -21,8 +20,8 @@ coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-androi
|
|||
|
||||
injekt-core = { module = "com.github.null2264.injekt:injekt-core", version = "4135455a2a" }
|
||||
rxjava = { module = "io.reactivex:rxjava", version = "1.3.8" }
|
||||
jsoup = { module = "org.jsoup:jsoup", version = "1.18.3" }
|
||||
okhttp = { module = "com.squareup.okhttp3:okhttp", version = "5.0.0-alpha.14" }
|
||||
jsoup = { module = "org.jsoup:jsoup", version = "1.15.1" }
|
||||
okhttp = { module = "com.squareup.okhttp3:okhttp", version = "5.0.0-alpha.11" }
|
||||
quickjs = { module = "app.cash.quickjs:quickjs-android", version = "0.9.2" }
|
||||
|
||||
[bundles]
|
||||
|
|
|
@ -2,7 +2,7 @@ plugins {
|
|||
id("lib-multisrc")
|
||||
}
|
||||
|
||||
baseVersionCode = 27
|
||||
baseVersionCode = 28
|
||||
|
||||
dependencies {
|
||||
api(project(":lib:i18n"))
|
||||
|
|
|
@ -2,4 +2,4 @@ plugins {
|
|||
id("lib-multisrc")
|
||||
}
|
||||
|
||||
baseVersionCode = 17
|
||||
baseVersionCode = 18
|
||||
|
|
|
@ -109,9 +109,7 @@ abstract class Madara(
|
|||
protected open val useLoadMoreRequest = LoadMoreStrategy.AutoDetect
|
||||
|
||||
enum class LoadMoreStrategy {
|
||||
AutoDetect,
|
||||
Always,
|
||||
Never,
|
||||
AutoDetect, Always, Never
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -120,9 +118,7 @@ abstract class Madara(
|
|||
private var loadMoreRequestDetected = LoadMoreDetection.Pending
|
||||
|
||||
private enum class LoadMoreDetection {
|
||||
Pending,
|
||||
True,
|
||||
False,
|
||||
Pending, True, False
|
||||
}
|
||||
|
||||
protected fun detectLoadMore(document: Document) {
|
||||
|
@ -136,10 +132,12 @@ abstract class Madara(
|
|||
}
|
||||
}
|
||||
|
||||
protected fun useLoadMoreRequest(): Boolean = when (useLoadMoreRequest) {
|
||||
LoadMoreStrategy.Always -> true
|
||||
LoadMoreStrategy.Never -> false
|
||||
else -> loadMoreRequestDetected == LoadMoreDetection.True
|
||||
protected fun useLoadMoreRequest(): Boolean {
|
||||
return when (useLoadMoreRequest) {
|
||||
LoadMoreStrategy.Always -> true
|
||||
LoadMoreStrategy.Never -> false
|
||||
else -> loadMoreRequestDetected == LoadMoreDetection.True
|
||||
}
|
||||
}
|
||||
|
||||
// Popular Manga
|
||||
|
@ -178,17 +176,19 @@ abstract class Madara(
|
|||
return manga
|
||||
}
|
||||
|
||||
override fun popularMangaRequest(page: Int): Request = if (useLoadMoreRequest()) {
|
||||
loadMoreRequest(page, popular = true)
|
||||
} else {
|
||||
GET("$baseUrl/$mangaSubString/${searchPage(page)}?m_orderby=views", headers)
|
||||
}
|
||||
override fun popularMangaRequest(page: Int): Request =
|
||||
if (useLoadMoreRequest()) {
|
||||
loadMoreRequest(page, popular = true)
|
||||
} else {
|
||||
GET("$baseUrl/$mangaSubString/${searchPage(page)}?m_orderby=views", headers)
|
||||
}
|
||||
|
||||
override fun popularMangaNextPageSelector(): String? = if (useLoadMoreRequest()) {
|
||||
"body:not(:has(.no-posts))"
|
||||
} else {
|
||||
"div.nav-previous, nav.navigation-ajax, a.nextpostslink"
|
||||
}
|
||||
override fun popularMangaNextPageSelector(): String? =
|
||||
if (useLoadMoreRequest()) {
|
||||
"body:not(:has(.no-posts))"
|
||||
} else {
|
||||
"div.nav-previous, nav.navigation-ajax, a.nextpostslink"
|
||||
}
|
||||
|
||||
// Latest Updates
|
||||
|
||||
|
@ -199,11 +199,12 @@ abstract class Madara(
|
|||
return popularMangaFromElement(element)
|
||||
}
|
||||
|
||||
override fun latestUpdatesRequest(page: Int): Request = if (useLoadMoreRequest()) {
|
||||
loadMoreRequest(page, popular = false)
|
||||
} else {
|
||||
GET("$baseUrl/$mangaSubString/${searchPage(page)}?m_orderby=latest", headers)
|
||||
}
|
||||
override fun latestUpdatesRequest(page: Int): Request =
|
||||
if (useLoadMoreRequest()) {
|
||||
loadMoreRequest(page, popular = false)
|
||||
} else {
|
||||
GET("$baseUrl/$mangaSubString/${searchPage(page)}?m_orderby=latest", headers)
|
||||
}
|
||||
|
||||
override fun latestUpdatesNextPageSelector(): String? = popularMangaNextPageSelector()
|
||||
|
||||
|
@ -250,16 +251,20 @@ abstract class Madara(
|
|||
return super.fetchSearchManga(page, query, filters)
|
||||
}
|
||||
|
||||
protected open fun searchPage(page: Int): String = if (page == 1) {
|
||||
""
|
||||
} else {
|
||||
"page/$page/"
|
||||
protected open fun searchPage(page: Int): String {
|
||||
return if (page == 1) {
|
||||
""
|
||||
} else {
|
||||
"page/$page/"
|
||||
}
|
||||
}
|
||||
|
||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = if (useLoadMoreRequest()) {
|
||||
searchLoadMoreRequest(page, query, filters)
|
||||
} else {
|
||||
searchRequest(page, query, filters)
|
||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||
return if (useLoadMoreRequest()) {
|
||||
searchLoadMoreRequest(page, query, filters)
|
||||
} else {
|
||||
searchRequest(page, query, filters)
|
||||
}
|
||||
}
|
||||
|
||||
protected open fun searchRequest(page: Int, query: String, filters: FilterList): Request {
|
||||
|
@ -305,9 +310,7 @@ abstract class Madara(
|
|||
filter.state
|
||||
.filter { it.state }
|
||||
.let { list ->
|
||||
if (list.isNotEmpty()) {
|
||||
list.forEach { genre -> url.addQueryParameter("genre[]", genre.id) }
|
||||
}
|
||||
if (list.isNotEmpty()) { list.forEach { genre -> url.addQueryParameter("genre[]", genre.id) } }
|
||||
}
|
||||
}
|
||||
else -> {}
|
||||
|
@ -486,7 +489,8 @@ abstract class Madara(
|
|||
intl["adult_content_filter_only"] to "1",
|
||||
)
|
||||
|
||||
open class UriPartFilter(displayName: String, private val vals: Array<Pair<String, String>>, state: Int = 0) : Filter.Select<String>(displayName, vals.map { it.first }.toTypedArray(), state) {
|
||||
open class UriPartFilter(displayName: String, private val vals: Array<Pair<String, String>>, state: Int = 0) :
|
||||
Filter.Select<String>(displayName, vals.map { it.first }.toTypedArray(), state) {
|
||||
fun toUriPart() = vals[state].second
|
||||
}
|
||||
|
||||
|
@ -495,21 +499,21 @@ abstract class Madara(
|
|||
protected class AuthorFilter(title: String) : Filter.Text(title)
|
||||
protected class ArtistFilter(title: String) : Filter.Text(title)
|
||||
protected class YearFilter(title: String) : Filter.Text(title)
|
||||
protected class StatusFilter(title: String, status: List<Tag>) : Filter.Group<Tag>(title, status)
|
||||
protected class StatusFilter(title: String, status: List<Tag>) :
|
||||
Filter.Group<Tag>(title, status)
|
||||
|
||||
protected class OrderByFilter(title: String, options: List<Pair<String, String>>, state: Int = 0) : UriPartFilter(title, options.toTypedArray(), state)
|
||||
protected class OrderByFilter(title: String, options: List<Pair<String, String>>, state: Int = 0) :
|
||||
UriPartFilter(title, options.toTypedArray(), state)
|
||||
|
||||
protected class GenreConditionFilter(title: String, options: List<Pair<String, String>>) :
|
||||
UriPartFilter(
|
||||
title,
|
||||
options.toTypedArray(),
|
||||
)
|
||||
protected class GenreConditionFilter(title: String, options: List<Pair<String, String>>) : UriPartFilter(
|
||||
title,
|
||||
options.toTypedArray(),
|
||||
)
|
||||
|
||||
protected class AdultContentFilter(title: String, options: List<Pair<String, String>>) :
|
||||
UriPartFilter(
|
||||
title,
|
||||
options.toTypedArray(),
|
||||
)
|
||||
protected class AdultContentFilter(title: String, options: List<Pair<String, String>>) : UriPartFilter(
|
||||
title,
|
||||
options.toTypedArray(),
|
||||
)
|
||||
|
||||
protected class GenreList(title: String, genres: List<Genre>) : Filter.Group<GenreCheckBox>(title, genres.map { GenreCheckBox(it.name, it.id) })
|
||||
class GenreCheckBox(name: String, val id: String = name) : Filter.CheckBox(name)
|
||||
|
@ -753,24 +757,32 @@ abstract class Madara(
|
|||
open val altName = intl["alt_names_heading"]
|
||||
open val updatingRegex = "Updating|Atualizando".toRegex(RegexOption.IGNORE_CASE)
|
||||
|
||||
fun String.notUpdating(): Boolean = this.contains(updatingRegex).not()
|
||||
fun String.notUpdating(): Boolean {
|
||||
return this.contains(updatingRegex).not()
|
||||
}
|
||||
|
||||
private fun String.containsIn(array: Array<String>): Boolean = this.lowercase() in array.map { it.lowercase() }
|
||||
private fun String.containsIn(array: Array<String>): Boolean {
|
||||
return this.lowercase() in array.map { it.lowercase() }
|
||||
}
|
||||
|
||||
protected open fun imageFromElement(element: Element): String? = when {
|
||||
element.hasAttr("data-src") -> element.attr("abs:data-src")
|
||||
element.hasAttr("data-lazy-src") -> element.attr("abs:data-lazy-src")
|
||||
element.hasAttr("srcset") -> element.attr("abs:srcset").getSrcSetImage()
|
||||
element.hasAttr("data-cfsrc") -> element.attr("abs:data-cfsrc")
|
||||
else -> element.attr("abs:src")
|
||||
protected open fun imageFromElement(element: Element): String? {
|
||||
return when {
|
||||
element.hasAttr("data-src") -> element.attr("abs:data-src")
|
||||
element.hasAttr("data-lazy-src") -> element.attr("abs:data-lazy-src")
|
||||
element.hasAttr("srcset") -> element.attr("abs:srcset").getSrcSetImage()
|
||||
element.hasAttr("data-cfsrc") -> element.attr("abs:data-cfsrc")
|
||||
else -> element.attr("abs:src")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the best image quality available from srcset
|
||||
*/
|
||||
protected fun String.getSrcSetImage(): String? = this.split(" ")
|
||||
.filter(URL_REGEX::matches)
|
||||
.maxOfOrNull(String::toString)
|
||||
protected fun String.getSrcSetImage(): String? {
|
||||
return this.split(" ")
|
||||
.filter(URL_REGEX::matches)
|
||||
.maxOfOrNull(String::toString)
|
||||
}
|
||||
|
||||
/**
|
||||
* Set it to true if the source uses the new AJAX endpoint to
|
||||
|
@ -795,7 +807,9 @@ abstract class Madara(
|
|||
return POST("$baseUrl/wp-admin/admin-ajax.php", xhrHeaders, form)
|
||||
}
|
||||
|
||||
protected open fun xhrChaptersRequest(mangaUrl: String): Request = POST("$mangaUrl/ajax/chapters", xhrHeaders)
|
||||
protected open fun xhrChaptersRequest(mangaUrl: String): Request {
|
||||
return POST("$mangaUrl/ajax/chapters", xhrHeaders)
|
||||
}
|
||||
|
||||
override fun chapterListParse(response: Response): List<SChapter> {
|
||||
val document = response.asJsoup()
|
||||
|
@ -866,10 +880,12 @@ abstract class Madara(
|
|||
open fun parseChapterDate(date: String?): Long {
|
||||
date ?: return 0
|
||||
|
||||
fun SimpleDateFormat.tryParse(string: String): Long = try {
|
||||
parse(string)?.time ?: 0
|
||||
} catch (_: ParseException) {
|
||||
0
|
||||
fun SimpleDateFormat.tryParse(string: String): Long {
|
||||
return try {
|
||||
parse(string)?.time ?: 0
|
||||
} catch (_: ParseException) {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
return when {
|
||||
|
@ -990,7 +1006,9 @@ abstract class Madara(
|
|||
}
|
||||
}
|
||||
|
||||
override fun imageRequest(page: Page): Request = GET(page.imageUrl!!, headers.newBuilder().set("Referer", page.url).build())
|
||||
override fun imageRequest(page: Page): Request {
|
||||
return GET(page.imageUrl!!, headers.newBuilder().set("Referer", page.url).build())
|
||||
}
|
||||
|
||||
override fun imageUrlParse(document: Document) = ""
|
||||
|
||||
|
@ -1065,22 +1083,26 @@ abstract class Madara(
|
|||
/**
|
||||
* The request to the search page (or another one) that have the genres list.
|
||||
*/
|
||||
protected open fun genresRequest(): Request = GET("$baseUrl/?s=genre&post_type=wp-manga", headers)
|
||||
protected open fun genresRequest(): Request {
|
||||
return GET("$baseUrl/?s=genre&post_type=wp-manga", headers)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the genres from the search page document.
|
||||
*
|
||||
* @param document The search page document
|
||||
*/
|
||||
protected open fun parseGenres(document: Document): List<Genre> = document.selectFirst("div.checkbox-group")
|
||||
?.select("div.checkbox")
|
||||
.orEmpty()
|
||||
.map { li ->
|
||||
Genre(
|
||||
li.selectFirst("label")!!.text(),
|
||||
li.selectFirst("input[type=checkbox]")!!.`val`(),
|
||||
)
|
||||
}
|
||||
protected open fun parseGenres(document: Document): List<Genre> {
|
||||
return document.selectFirst("div.checkbox-group")
|
||||
?.select("div.checkbox")
|
||||
.orEmpty()
|
||||
.map { li ->
|
||||
Genre(
|
||||
li.selectFirst("label")!!.text(),
|
||||
li.selectFirst("input[type=checkbox]")!!.`val`(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/a/66614516
|
||||
protected fun String.decodeHex(): ByteArray {
|
||||
|
|
|
@ -2,4 +2,4 @@ plugins {
|
|||
id("lib-multisrc")
|
||||
}
|
||||
|
||||
baseVersionCode = 13
|
||||
baseVersionCode = 14
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
ext {
|
||||
extName = 'Comick'
|
||||
extClass = '.ComickFactory'
|
||||
extVersionCode = 51
|
||||
extVersionCode = 52
|
||||
isNsfw = true
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
ext {
|
||||
extName = 'MangaDex'
|
||||
extClass = '.MangaDexFactory'
|
||||
extVersionCode = 197
|
||||
extVersionCode = 198
|
||||
isNsfw = true
|
||||
}
|
||||
|
||||
|
|
|
@ -6,11 +6,13 @@ import org.jsoup.nodes.Element
|
|||
class CoffeeManga : Madara("Coffee Manga", "https://coffeemanga.io", "en") {
|
||||
override val useNewChapterEndpoint = false
|
||||
|
||||
override fun imageFromElement(element: Element): String? = when {
|
||||
element.attr("data-src").isNotBlank() -> element.attr("abs:data-src")
|
||||
element.attr("data-lazy-src").isNotBlank() -> element.attr("abs:data-lazy-src")
|
||||
element.attr("srcset").isNotBlank() -> element.attr("abs:srcset").getSrcSetImage()
|
||||
element.attr("data-cfsrc").isNotBlank() -> element.attr("abs:data-cfsrc")
|
||||
else -> element.attr("abs:src")
|
||||
override fun imageFromElement(element: Element): String? {
|
||||
return when {
|
||||
element.attr("data-src").isNotBlank() -> element.attr("abs:data-src")
|
||||
element.attr("data-lazy-src").isNotBlank() -> element.attr("abs:data-lazy-src")
|
||||
element.attr("srcset").isNotBlank() -> element.attr("abs:srcset").getSrcSetImage()
|
||||
element.attr("data-cfsrc").isNotBlank() -> element.attr("abs:data-cfsrc")
|
||||
else -> element.attr("abs:src")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,12 +2,11 @@ package eu.kanade.tachiyomi.extension.en.luascans
|
|||
|
||||
import eu.kanade.tachiyomi.multisrc.heancms.HeanCms
|
||||
|
||||
class LuaScans :
|
||||
HeanCms(
|
||||
"Lua Scans",
|
||||
"https://luacomic.org",
|
||||
"en",
|
||||
) {
|
||||
class LuaScans : HeanCms(
|
||||
"Lua Scans",
|
||||
"https://luacomic.org",
|
||||
"en",
|
||||
) {
|
||||
// Moved from Keyoapp to HeanCms
|
||||
override val versionId = 3
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
ext {
|
||||
extName = 'Mangamo'
|
||||
extClass = '.Mangamo'
|
||||
extVersionCode = 2
|
||||
extVersionCode = 1
|
||||
isNsfw = false
|
||||
}
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ import eu.kanade.tachiyomi.network.POST
|
|||
import okhttp3.Headers
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
import okhttp3.internal.EMPTY_HEADERS
|
||||
|
||||
class MangamoAuth(
|
||||
private val helper: MangamoHelper,
|
||||
|
@ -52,7 +53,8 @@ class MangamoAuth(
|
|||
val googleIdentityResponse = client.newCall(
|
||||
POST(
|
||||
"https://identitytoolkit.googleapis.com/v1/accounts:signInWithCustomToken?key=${MangamoConstants.FIREBASE_API_KEY}",
|
||||
body = "{\"token\":\"$customToken\",\"returnSecureToken\":true}".toRequestBody(),
|
||||
EMPTY_HEADERS,
|
||||
"{\"token\":\"$customToken\",\"returnSecureToken\":true}".toRequestBody(),
|
||||
),
|
||||
).execute()
|
||||
|
||||
|
@ -95,7 +97,8 @@ class MangamoAuth(
|
|||
val googleIdentityResponse = client.newCall(
|
||||
POST(
|
||||
"https://identitytoolkit.googleapis.com/v1/accounts:signUp?key=${MangamoConstants.FIREBASE_API_KEY}",
|
||||
body = "{\"returnSecureToken\":true}".toRequestBody(),
|
||||
EMPTY_HEADERS,
|
||||
"{\"returnSecureToken\":true}".toRequestBody(),
|
||||
),
|
||||
).execute()
|
||||
|
||||
|
|
|
@ -2,12 +2,11 @@ package eu.kanade.tachiyomi.extension.en.retsu
|
|||
|
||||
import eu.kanade.tachiyomi.multisrc.madara.Madara
|
||||
|
||||
class Retsu :
|
||||
Madara(
|
||||
"Retsu",
|
||||
"https://retsu.org",
|
||||
"en",
|
||||
) {
|
||||
class Retsu : Madara(
|
||||
"Retsu",
|
||||
"https://retsu.org",
|
||||
"en",
|
||||
) {
|
||||
override fun popularMangaSelector() = "div.manga__item"
|
||||
override val popularMangaUrlSelector = "h4 a"
|
||||
|
||||
|
|
|
@ -12,13 +12,12 @@ import java.io.IOException
|
|||
import java.text.SimpleDateFormat
|
||||
import java.util.Locale
|
||||
|
||||
class SirenKomik :
|
||||
MangaThemesia(
|
||||
"Siren Komik",
|
||||
"https://sirenkomik.my.id",
|
||||
"id",
|
||||
dateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale("id")),
|
||||
) {
|
||||
class SirenKomik : MangaThemesia(
|
||||
"Siren Komik",
|
||||
"https://sirenkomik.my.id",
|
||||
"id",
|
||||
dateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale("id")),
|
||||
) {
|
||||
override val id = 8457447675410081142
|
||||
|
||||
override val hasProjectPage = true
|
||||
|
|
|
@ -32,9 +32,7 @@ import uy.kohesive.injekt.injectLazy
|
|||
import java.text.SimpleDateFormat
|
||||
import java.util.Locale
|
||||
|
||||
class SussyToons :
|
||||
HttpSource(),
|
||||
ConfigurableSource {
|
||||
class SussyToons : HttpSource(), ConfigurableSource {
|
||||
|
||||
override val name = "Sussy Toons"
|
||||
|
||||
|
@ -104,7 +102,9 @@ class SussyToons :
|
|||
|
||||
// ============================= Popular ==================================
|
||||
|
||||
override fun popularMangaRequest(page: Int): Request = GET("$apiUrl/obras/top5", headers)
|
||||
override fun popularMangaRequest(page: Int): Request {
|
||||
return GET("$apiUrl/obras/top5", headers)
|
||||
}
|
||||
|
||||
override fun popularMangaParse(response: Response): MangasPage {
|
||||
val dto = response.parseAs<WrapperDto<List<MangaDto>>>()
|
||||
|
@ -152,7 +152,8 @@ class SussyToons :
|
|||
return GET(url, headers)
|
||||
}
|
||||
|
||||
override fun mangaDetailsParse(response: Response) = response.parseAs<WrapperDto<MangaDto>>().results.toSManga()
|
||||
override fun mangaDetailsParse(response: Response) =
|
||||
response.parseAs<WrapperDto<MangaDto>>().results.toSManga()
|
||||
|
||||
private val SManga.id: String get() {
|
||||
val mangaUrl = apiUrl.toHttpUrl().newBuilder()
|
||||
|
@ -165,16 +166,18 @@ class SussyToons :
|
|||
|
||||
override fun chapterListRequest(manga: SManga) = mangaDetailsRequest(manga)
|
||||
|
||||
override fun chapterListParse(response: Response): List<SChapter> = response.parseAs<WrapperDto<WrapperChapterDto>>().results.chapters.map {
|
||||
SChapter.create().apply {
|
||||
name = it.name
|
||||
it.chapterNumber?.let {
|
||||
chapter_number = it
|
||||
override fun chapterListParse(response: Response): List<SChapter> {
|
||||
return response.parseAs<WrapperDto<WrapperChapterDto>>().results.chapters.map {
|
||||
SChapter.create().apply {
|
||||
name = it.name
|
||||
it.chapterNumber?.let {
|
||||
chapter_number = it
|
||||
}
|
||||
setUrlWithoutDomain("$baseUrl/capitulo/${it.id}")
|
||||
date_upload = it.updateAt.toDate()
|
||||
}
|
||||
setUrlWithoutDomain("$baseUrl/capitulo/${it.id}")
|
||||
date_upload = it.updateAt.toDate()
|
||||
}
|
||||
}.sortedBy(SChapter::chapter_number).reversed()
|
||||
}.sortedBy(SChapter::chapter_number).reversed()
|
||||
}
|
||||
|
||||
// ============================= Pages ====================================
|
||||
|
||||
|
@ -205,22 +208,30 @@ class SussyToons :
|
|||
Page(index, imageUrl = imageUrl.toString())
|
||||
}
|
||||
}
|
||||
private fun pageListParse(document: Document): List<Page> = document.select(pageUrlSelector).mapIndexed { index, element ->
|
||||
Page(index, document.location(), element.absUrl("src"))
|
||||
private fun pageListParse(document: Document): List<Page> {
|
||||
return document.select(pageUrlSelector).mapIndexed { index, element ->
|
||||
Page(index, document.location(), element.absUrl("src"))
|
||||
}
|
||||
}
|
||||
private fun extractScriptData(document: Document): String {
|
||||
return document.select("script").map(Element::data)
|
||||
.firstOrNull(pageRegex::containsMatchIn)
|
||||
?: throw Exception("Failed to load pages: Script data not found")
|
||||
}
|
||||
private fun extractScriptData(document: Document): String = document.select("script").map(Element::data)
|
||||
.firstOrNull(pageRegex::containsMatchIn)
|
||||
?: throw Exception("Failed to load pages: Script data not found")
|
||||
|
||||
private fun extractJsonContent(scriptData: String): String = pageRegex.find(scriptData)
|
||||
?.groups?.get(1)?.value
|
||||
?.let { json.decodeFromString<String>("\"$it\"") }
|
||||
?: throw Exception("Failed to extract JSON from script")
|
||||
private fun extractJsonContent(scriptData: String): String {
|
||||
return pageRegex.find(scriptData)
|
||||
?.groups?.get(1)?.value
|
||||
?.let { json.decodeFromString<String>("\"$it\"") }
|
||||
?: throw Exception("Failed to extract JSON from script")
|
||||
}
|
||||
|
||||
private fun parseJsonToChapterPageDto(jsonContent: String): ChapterPageDto = try {
|
||||
jsonContent.parseAs<WrapperDto<ChapterPageDto>>().results
|
||||
} catch (e: Exception) {
|
||||
throw Exception("Failed to load pages: ${e.message}")
|
||||
private fun parseJsonToChapterPageDto(jsonContent: String): ChapterPageDto {
|
||||
return try {
|
||||
jsonContent.parseAs<WrapperDto<ChapterPageDto>>().results
|
||||
} catch (e: Exception) {
|
||||
throw Exception("Failed to load pages: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
override fun imageUrlParse(response: Response): String = ""
|
||||
|
@ -334,14 +345,13 @@ class SussyToons :
|
|||
return json.decodeFromStream(body.byteStream())
|
||||
}
|
||||
|
||||
private inline fun <reified T> String.parseAs(): T = json.decodeFromString(this)
|
||||
|
||||
private fun String.toDate() = try {
|
||||
dateFormat.parse(this)!!.time
|
||||
} catch (_: Exception) {
|
||||
0L
|
||||
private inline fun <reified T> String.parseAs(): T {
|
||||
return json.decodeFromString(this)
|
||||
}
|
||||
|
||||
private fun String.toDate() =
|
||||
try { dateFormat.parse(this)!!.time } catch (_: Exception) { 0L }
|
||||
|
||||
/**
|
||||
* Normalizes path segments:
|
||||
* Ex: [ "/a/b/", "/a/b", "a/b/", "a/b" ]
|
||||
|
|
|
@ -41,11 +41,13 @@ class MangaDto(
|
|||
@SerialName("stt_nome")
|
||||
val value: String?,
|
||||
) {
|
||||
fun toStatus(): Int = when (value?.lowercase()) {
|
||||
"em andamento" -> SManga.ONGOING
|
||||
"completo" -> SManga.COMPLETED
|
||||
"hiato" -> SManga.ON_HIATUS
|
||||
else -> SManga.UNKNOWN
|
||||
fun toStatus(): Int {
|
||||
return when (value?.lowercase()) {
|
||||
"em andamento" -> SManga.ONGOING
|
||||
"completo" -> SManga.COMPLETED
|
||||
"hiato" -> SManga.ON_HIATUS
|
||||
else -> SManga.UNKNOWN
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,6 +32,8 @@ import okhttp3.MediaType.Companion.toMediaType
|
|||
import okhttp3.Request
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
import okhttp3.Response
|
||||
import okhttp3.internal.http.HTTP_FORBIDDEN
|
||||
import okhttp3.internal.http.HTTP_UNAUTHORIZED
|
||||
import org.jsoup.nodes.Document
|
||||
import org.jsoup.nodes.Element
|
||||
import org.jsoup.select.Elements
|
||||
|
@ -39,8 +41,6 @@ import rx.Observable
|
|||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.net.HttpURLConnection.HTTP_FORBIDDEN
|
||||
import java.net.HttpURLConnection.HTTP_UNAUTHORIZED
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Locale
|
||||
|
||||
|
@ -85,13 +85,15 @@ class Taiyo : ParsedHttpSource() {
|
|||
|
||||
// =============================== Search ===============================
|
||||
|
||||
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> = if (query.startsWith(PREFIX_SEARCH)) { // URL intent handler
|
||||
val id = query.removePrefix(PREFIX_SEARCH)
|
||||
client.newCall(GET("$baseUrl/media/$id"))
|
||||
.asObservableSuccess()
|
||||
.map(::searchMangaByIdParse)
|
||||
} else {
|
||||
super.fetchSearchManga(page, query, filters)
|
||||
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
|
||||
return if (query.startsWith(PREFIX_SEARCH)) { // URL intent handler
|
||||
val id = query.removePrefix(PREFIX_SEARCH)
|
||||
client.newCall(GET("$baseUrl/media/$id"))
|
||||
.asObservableSuccess()
|
||||
.map(::searchMangaByIdParse)
|
||||
} else {
|
||||
super.fetchSearchManga(page, query, filters)
|
||||
}
|
||||
}
|
||||
|
||||
private fun searchMangaByIdParse(response: Response): MangasPage {
|
||||
|
@ -144,11 +146,17 @@ class Taiyo : ParsedHttpSource() {
|
|||
return MangasPage(mangas, mangas.isNotEmpty())
|
||||
}
|
||||
|
||||
override fun searchMangaSelector(): String = throw UnsupportedOperationException()
|
||||
override fun searchMangaSelector(): String {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
override fun searchMangaFromElement(element: Element): SManga = throw UnsupportedOperationException()
|
||||
override fun searchMangaFromElement(element: Element): SManga {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
override fun searchMangaNextPageSelector(): String? = throw UnsupportedOperationException()
|
||||
override fun searchMangaNextPageSelector(): String? {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
// =========================== Manga Details ============================
|
||||
override fun mangaDetailsParse(document: Document) = SManga.create().apply {
|
||||
|
@ -227,9 +235,13 @@ class Taiyo : ParsedHttpSource() {
|
|||
return Observable.just(chapters.sortedByDescending { it.chapter_number })
|
||||
}
|
||||
|
||||
override fun chapterListSelector(): String = throw UnsupportedOperationException()
|
||||
override fun chapterListSelector(): String {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
override fun chapterFromElement(element: Element): SChapter = throw UnsupportedOperationException()
|
||||
override fun chapterFromElement(element: Element): SChapter {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
// =============================== Pages ================================
|
||||
override fun pageListParse(document: Document): List<Page> {
|
||||
|
@ -244,7 +256,9 @@ class Taiyo : ParsedHttpSource() {
|
|||
}
|
||||
}
|
||||
|
||||
override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException()
|
||||
override fun imageUrlParse(document: Document): String {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
// ============================= Utilities ==============================
|
||||
private fun Element.getImageUrl() = absUrl("srcset").substringBefore(" ")
|
||||
|
@ -253,19 +267,23 @@ class Taiyo : ParsedHttpSource() {
|
|||
json.decodeFromStream(it.body.byteStream())
|
||||
}
|
||||
|
||||
private fun String.toDate(): Long = runCatching { DATE_FORMATTER.parse(this)?.time }
|
||||
.getOrNull() ?: 0L
|
||||
private fun String.toDate(): Long {
|
||||
return runCatching { DATE_FORMATTER.parse(this)?.time }
|
||||
.getOrNull() ?: 0L
|
||||
}
|
||||
|
||||
private inline fun <reified T> Document.parseJsonFromDocument(
|
||||
itemName: String = "media",
|
||||
crossinline transformer: String.() -> String,
|
||||
): T? = runCatching {
|
||||
val script = selectFirst("script:containsData($itemName\\\\\":):containsData(\\\"6:\\[)")!!.data()
|
||||
val obj = script.substringAfter(",{\\\"$itemName\\\":")
|
||||
.run(transformer)
|
||||
.replace("\\", "")
|
||||
json.decodeFromString<T>(obj)
|
||||
}.onFailure { it.printStackTrace() }.getOrNull()
|
||||
): T? {
|
||||
return runCatching {
|
||||
val script = selectFirst("script:containsData($itemName\\\\\":):containsData(\\\"6:\\[)")!!.data()
|
||||
val obj = script.substringAfter(",{\\\"$itemName\\\":")
|
||||
.run(transformer)
|
||||
.replace("\\", "")
|
||||
json.decodeFromString<T>(obj)
|
||||
}.onFailure { it.printStackTrace() }.getOrNull()
|
||||
}
|
||||
|
||||
// ============================= Authorization ========================
|
||||
|
||||
|
@ -286,10 +304,12 @@ class Taiyo : ParsedHttpSource() {
|
|||
return chain.proceed(req)
|
||||
}
|
||||
|
||||
private fun getToken(): String = fetchBearerToken().also {
|
||||
preferences.edit()
|
||||
.putString(BEARER_TOKEN_PREF, it)
|
||||
.apply()
|
||||
private fun getToken(): String {
|
||||
return fetchBearerToken().also {
|
||||
preferences.edit()
|
||||
.putString(BEARER_TOKEN_PREF, it)
|
||||
.apply()
|
||||
}
|
||||
}
|
||||
|
||||
private fun fetchBearerToken(): String {
|
||||
|
|
|
@ -93,7 +93,8 @@ class HattoriManga : HttpSource() {
|
|||
csrfToken = document.selectFirst("meta[name=csrf-token]")!!.attr("content")
|
||||
}
|
||||
|
||||
override fun chapterListParse(response: Response) = throw UnsupportedOperationException()
|
||||
override fun chapterListParse(response: Response) =
|
||||
throw UnsupportedOperationException()
|
||||
|
||||
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
|
||||
val slug = manga.url.substringAfterLast('/')
|
||||
|
@ -115,9 +116,10 @@ class HattoriManga : HttpSource() {
|
|||
return Observable.just(chapters)
|
||||
}
|
||||
|
||||
private fun fetchChapterPageableList(slug: String, page: Int, manga: SManga): HMChapterDto = client.newCall(GET("$baseUrl/load-more-chapters/$slug?page=$page", headers))
|
||||
.execute()
|
||||
.parseAs<HMChapterDto>()
|
||||
private fun fetchChapterPageableList(slug: String, page: Int, manga: SManga): HMChapterDto =
|
||||
client.newCall(GET("$baseUrl/load-more-chapters/$slug?page=$page", headers))
|
||||
.execute()
|
||||
.parseAs<HMChapterDto>()
|
||||
|
||||
override fun latestUpdatesRequest(page: Int): Request = GET("$baseUrl/latest-chapters")
|
||||
|
||||
|
@ -147,20 +149,24 @@ class HattoriManga : HttpSource() {
|
|||
}
|
||||
}
|
||||
|
||||
override fun pageListParse(response: Response): List<Page> = response.asJsoup().select(".image-wrapper img").mapIndexed { index, element ->
|
||||
Page(index, imageUrl = "$baseUrl${element.attr("data-src")}")
|
||||
}.takeIf { it.isNotEmpty() } ?: throw Exception("Oturum açmanız, WebView'ı açmanız ve oturum açmanız gerekir")
|
||||
override fun pageListParse(response: Response): List<Page> {
|
||||
return response.asJsoup().select(".image-wrapper img").mapIndexed { index, element ->
|
||||
Page(index, imageUrl = "$baseUrl${element.attr("data-src")}")
|
||||
}.takeIf { it.isNotEmpty() } ?: throw Exception("Oturum açmanız, WebView'ı açmanız ve oturum açmanız gerekir")
|
||||
}
|
||||
|
||||
override fun latestUpdatesParse(response: Response): MangasPage = response.use {
|
||||
val mangas = it.parseAs<HMLatestUpdateDto>().chapters.map {
|
||||
SManga.create().apply {
|
||||
val manga = it.manga
|
||||
title = manga.title
|
||||
thumbnail_url = "$baseUrl/storage/${manga.thumbnail}"
|
||||
url = "/manga/${manga.slug}"
|
||||
}
|
||||
}.distinctBy { manga -> manga.title }
|
||||
MangasPage(mangas, false)
|
||||
override fun latestUpdatesParse(response: Response): MangasPage {
|
||||
return response.use {
|
||||
val mangas = it.parseAs<HMLatestUpdateDto>().chapters.map {
|
||||
SManga.create().apply {
|
||||
val manga = it.manga
|
||||
title = manga.title
|
||||
thumbnail_url = "$baseUrl/storage/${manga.thumbnail}"
|
||||
url = "/manga/${manga.slug}"
|
||||
}
|
||||
}.distinctBy { manga -> manga.title }
|
||||
MangasPage(mangas, false)
|
||||
}
|
||||
}
|
||||
|
||||
override fun popularMangaParse(response: Response): MangasPage {
|
||||
|
@ -257,18 +263,19 @@ class HattoriManga : HttpSource() {
|
|||
setUrlWithoutDomain(REGEX_MANGA_URL.find(script)!!.groups[1]!!.value)
|
||||
}
|
||||
|
||||
private fun parseGenres(document: Document): List<Genre> = document.select(".tags-blog a")
|
||||
.map { element -> Genre(element.text()) }
|
||||
private fun parseGenres(document: Document): List<Genre> {
|
||||
return document.select(".tags-blog a")
|
||||
.map { element -> Genre(element.text()) }
|
||||
}
|
||||
|
||||
private inline fun <reified T> Response.parseAs(): T = json.decodeFromString(body.string())
|
||||
private inline fun <reified T> Response.parseAs(): T {
|
||||
return json.decodeFromString(body.string())
|
||||
}
|
||||
|
||||
private fun Response.isPageExpired() = code == 419
|
||||
|
||||
private fun String.toDate(): Long = try {
|
||||
dateFormat.parse(trim())!!.time
|
||||
} catch (_: Exception) {
|
||||
0L
|
||||
}
|
||||
private fun String.toDate(): Long =
|
||||
try { dateFormat.parse(trim())!!.time } catch (_: Exception) { 0L }
|
||||
|
||||
class GenreList(title: String, genres: List<Genre>) : Filter.Group<GenreCheckBox>(title, genres.map { GenreCheckBox(it.name, it.id) })
|
||||
|
||||
|
|
|
@ -25,9 +25,7 @@ import uy.kohesive.injekt.api.get
|
|||
import java.text.SimpleDateFormat
|
||||
import java.util.Locale
|
||||
|
||||
class LxHentai :
|
||||
ParsedHttpSource(),
|
||||
ConfigurableSource {
|
||||
class LxHentai : ParsedHttpSource(), ConfigurableSource {
|
||||
|
||||
override val name = "LXHentai"
|
||||
|
||||
|
@ -43,30 +41,38 @@ class LxHentai :
|
|||
|
||||
override fun headersBuilder(): Headers.Builder = super.headersBuilder().add("Referer", baseUrl)
|
||||
|
||||
override fun popularMangaRequest(page: Int) = searchMangaRequest(page, "", FilterList(SortBy(3)))
|
||||
override fun popularMangaRequest(page: Int) =
|
||||
searchMangaRequest(page, "", FilterList(SortBy(3)))
|
||||
|
||||
override fun popularMangaSelector() = searchMangaSelector()
|
||||
|
||||
override fun popularMangaFromElement(element: Element) = searchMangaFromElement(element)
|
||||
override fun popularMangaFromElement(element: Element) =
|
||||
searchMangaFromElement(element)
|
||||
|
||||
override fun popularMangaNextPageSelector() = searchMangaNextPageSelector()
|
||||
override fun popularMangaNextPageSelector() =
|
||||
searchMangaNextPageSelector()
|
||||
|
||||
override fun latestUpdatesRequest(page: Int) = searchMangaRequest(page, "", FilterList(SortBy(0)))
|
||||
override fun latestUpdatesRequest(page: Int) =
|
||||
searchMangaRequest(page, "", FilterList(SortBy(0)))
|
||||
|
||||
override fun latestUpdatesSelector() = searchMangaSelector()
|
||||
|
||||
override fun latestUpdatesFromElement(element: Element) = searchMangaFromElement(element)
|
||||
override fun latestUpdatesFromElement(element: Element) =
|
||||
searchMangaFromElement(element)
|
||||
|
||||
override fun latestUpdatesNextPageSelector() = searchMangaNextPageSelector()
|
||||
override fun latestUpdatesNextPageSelector() =
|
||||
searchMangaNextPageSelector()
|
||||
|
||||
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> = when {
|
||||
query.startsWith(PREFIX_ID_SEARCH) -> {
|
||||
val slug = query.substringAfter(PREFIX_ID_SEARCH)
|
||||
val mangaUrl = "/truyen/$slug"
|
||||
fetchMangaDetails(SManga.create().apply { url = mangaUrl })
|
||||
.map { MangasPage(listOf(it), false) }
|
||||
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
|
||||
return when {
|
||||
query.startsWith(PREFIX_ID_SEARCH) -> {
|
||||
val slug = query.substringAfter(PREFIX_ID_SEARCH)
|
||||
val mangaUrl = "/truyen/$slug"
|
||||
fetchMangaDetails(SManga.create().apply { url = mangaUrl })
|
||||
.map { MangasPage(listOf(it), false) }
|
||||
}
|
||||
else -> super.fetchSearchManga(page, query, filters)
|
||||
}
|
||||
else -> super.fetchSearchManga(page, query, filters)
|
||||
}
|
||||
|
||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||
|
@ -162,33 +168,32 @@ class LxHentai :
|
|||
|
||||
private val dateFormat = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US)
|
||||
|
||||
private open class UriPartFilter(displayName: String, val vals: Array<Pair<String, String>>, state: Int = 0) : Filter.Select<String>(displayName, vals.map { it.first }.toTypedArray(), state) {
|
||||
private open class UriPartFilter(displayName: String, val vals: Array<Pair<String, String>>, state: Int = 0) :
|
||||
Filter.Select<String>(displayName, vals.map { it.first }.toTypedArray(), state) {
|
||||
fun toUriPart() = vals[state].second
|
||||
}
|
||||
|
||||
private class SortBy(state: Int = 0) :
|
||||
UriPartFilter(
|
||||
"Sắp xếp theo",
|
||||
arrayOf(
|
||||
Pair("Mới cập nhật", "-updated_at"),
|
||||
Pair("Mới nhất", "-created_at"),
|
||||
Pair("Cũ nhất", "created_at"),
|
||||
Pair("Xem nhiều", "-views"),
|
||||
Pair("A-Z", "name"),
|
||||
Pair("Z-A", "-name"),
|
||||
),
|
||||
state,
|
||||
)
|
||||
private class SortBy(state: Int = 0) : UriPartFilter(
|
||||
"Sắp xếp theo",
|
||||
arrayOf(
|
||||
Pair("Mới cập nhật", "-updated_at"),
|
||||
Pair("Mới nhất", "-created_at"),
|
||||
Pair("Cũ nhất", "created_at"),
|
||||
Pair("Xem nhiều", "-views"),
|
||||
Pair("A-Z", "name"),
|
||||
Pair("Z-A", "-name"),
|
||||
),
|
||||
state,
|
||||
)
|
||||
|
||||
private class Status :
|
||||
UriPartFilter(
|
||||
"Trạng thái",
|
||||
arrayOf(
|
||||
Pair("Tất cả", "1,2"),
|
||||
Pair("Đang tiến hành", "2"),
|
||||
Pair("Đã hoàn thành", "1"),
|
||||
),
|
||||
)
|
||||
private class Status : UriPartFilter(
|
||||
"Trạng thái",
|
||||
arrayOf(
|
||||
Pair("Tất cả", "1,2"),
|
||||
Pair("Đang tiến hành", "2"),
|
||||
Pair("Đã hoàn thành", "1"),
|
||||
),
|
||||
)
|
||||
|
||||
private class Genre(name: String, val id: Int) : Filter.TriState(name)
|
||||
private class GenreList(genres: List<Genre>) : Filter.Group<Genre>("Thể loại", genres)
|
||||
|
|
|
@ -34,31 +34,39 @@ class TruyenTranh3Q : ParsedHttpSource() {
|
|||
.rateLimit(3)
|
||||
.build()
|
||||
|
||||
override fun headersBuilder(): Headers.Builder = super.headersBuilder().add("Referer", "$baseUrl/")
|
||||
override fun headersBuilder(): Headers.Builder {
|
||||
return super.headersBuilder().add("Referer", "$baseUrl/")
|
||||
}
|
||||
|
||||
private val dateFormat = SimpleDateFormat("dd/MM/yyyy", Locale.US)
|
||||
|
||||
override fun popularMangaRequest(page: Int): Request = GET("$baseUrl/danh-sach/truyen-yeu-thich?page=$page", headers)
|
||||
override fun popularMangaRequest(page: Int): Request {
|
||||
return GET("$baseUrl/danh-sach/truyen-yeu-thich?page=$page", headers)
|
||||
}
|
||||
|
||||
override fun popularMangaSelector(): String = "ul.list_grid.grid > li"
|
||||
|
||||
override fun popularMangaFromElement(element: Element): SManga = SManga.create().apply {
|
||||
element.select("h3 a").let {
|
||||
title = it.text()
|
||||
setUrlWithoutDomain(it.attr("abs:href"))
|
||||
}
|
||||
thumbnail_url = element.selectFirst(".book_avatar a img")
|
||||
?.absUrl("src")
|
||||
?.let { url ->
|
||||
url.toHttpUrlOrNull()
|
||||
?.queryParameter("url")
|
||||
?: url
|
||||
override fun popularMangaFromElement(element: Element): SManga {
|
||||
return SManga.create().apply {
|
||||
element.select("h3 a").let {
|
||||
title = it.text()
|
||||
setUrlWithoutDomain(it.attr("abs:href"))
|
||||
}
|
||||
thumbnail_url = element.selectFirst(".book_avatar a img")
|
||||
?.absUrl("src")
|
||||
?.let { url ->
|
||||
url.toHttpUrlOrNull()
|
||||
?.queryParameter("url")
|
||||
?: url
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun popularMangaNextPageSelector(): String? = ".page_redirect > a:last-child > p:not(.active)"
|
||||
|
||||
override fun latestUpdatesRequest(page: Int): Request = GET("$baseUrl/danh-sach/truyen-moi-cap-nhat?page=$page", headers)
|
||||
override fun latestUpdatesRequest(page: Int): Request {
|
||||
return GET("$baseUrl/danh-sach/truyen-moi-cap-nhat?page=$page", headers)
|
||||
}
|
||||
|
||||
// same as popularManga
|
||||
override fun latestUpdatesSelector(): String = popularMangaSelector()
|
||||
|
@ -113,19 +121,21 @@ class TruyenTranh3Q : ParsedHttpSource() {
|
|||
override fun searchMangaFromElement(element: Element): SManga = popularMangaFromElement(element)
|
||||
override fun searchMangaNextPageSelector(): String? = popularMangaNextPageSelector()
|
||||
|
||||
override fun mangaDetailsParse(document: Document): SManga = SManga.create().apply {
|
||||
document.selectFirst(".book_info > .book_other")?.let { info ->
|
||||
title = info.selectFirst("h1[itemprop=name]")!!.text()
|
||||
author = info.selectFirst("ul.list-info li.author p.col-xs-9")?.text()
|
||||
status = when (info.selectFirst("ul.list-info li.status p.col-xs-9")?.text()) {
|
||||
"Đang Cập Nhật" -> SManga.ONGOING
|
||||
"Hoàn Thành" -> SManga.COMPLETED
|
||||
else -> SManga.UNKNOWN
|
||||
override fun mangaDetailsParse(document: Document): SManga {
|
||||
return SManga.create().apply {
|
||||
document.selectFirst(".book_info > .book_other")?.let { info ->
|
||||
title = info.selectFirst("h1[itemprop=name]")!!.text()
|
||||
author = info.selectFirst("ul.list-info li.author p.col-xs-9")?.text()
|
||||
status = when (info.selectFirst("ul.list-info li.status p.col-xs-9")?.text()) {
|
||||
"Đang Cập Nhật" -> SManga.ONGOING
|
||||
"Hoàn Thành" -> SManga.COMPLETED
|
||||
else -> SManga.UNKNOWN
|
||||
}
|
||||
genre = info.select(".list01 li a").joinToString { it.text() }
|
||||
}
|
||||
genre = info.select(".list01 li a").joinToString { it.text() }
|
||||
description = document.selectFirst(".book_detail > .story-detail-info")?.text()
|
||||
thumbnail_url = document.selectFirst(".book_detail > .book_info > .book_avatar > img")?.attr("abs:src")
|
||||
}
|
||||
description = document.selectFirst(".book_detail > .story-detail-info")?.text()
|
||||
thumbnail_url = document.selectFirst(".book_detail > .book_info > .book_avatar > img")?.attr("abs:src")
|
||||
}
|
||||
|
||||
// chapters
|
||||
|
@ -173,19 +183,23 @@ class TruyenTranh3Q : ParsedHttpSource() {
|
|||
}
|
||||
}
|
||||
|
||||
override fun chapterFromElement(element: Element): SChapter = SChapter.create().apply {
|
||||
element.selectFirst(".name-chap > a")?.let {
|
||||
name = it.text()
|
||||
setUrlWithoutDomain(it.attr("abs:href"))
|
||||
override fun chapterFromElement(element: Element): SChapter {
|
||||
return SChapter.create().apply {
|
||||
element.selectFirst(".name-chap > a")?.let {
|
||||
name = it.text()
|
||||
setUrlWithoutDomain(it.attr("abs:href"))
|
||||
}
|
||||
date_upload = parseChapterDate(element.selectFirst(".time-chap")?.text() ?: "")
|
||||
}
|
||||
date_upload = parseChapterDate(element.selectFirst(".time-chap")?.text() ?: "")
|
||||
}
|
||||
|
||||
// parse pages
|
||||
private val pageListSelector = ".chapter_content .page-chapter img"
|
||||
|
||||
override fun pageListParse(document: Document): List<Page> = document.select(pageListSelector).mapIndexed { idx, it ->
|
||||
Page(idx, imageUrl = it.absUrl("data-src"))
|
||||
override fun pageListParse(document: Document): List<Page> {
|
||||
return document.select(pageListSelector).mapIndexed { idx, it ->
|
||||
Page(idx, imageUrl = it.absUrl("data-src"))
|
||||
}
|
||||
}
|
||||
|
||||
override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException()
|
||||
|
@ -234,8 +248,10 @@ class TruyenTranh3Q : ParsedHttpSource() {
|
|||
|
||||
private fun genresRequest() = GET("$baseUrl/$searchPath", headers)
|
||||
|
||||
private fun parseGenres(document: Document): List<Genre> = document.select(".genre-item").mapIndexed { index, element ->
|
||||
Genre(element.text(), index + 1)
|
||||
private fun parseGenres(document: Document): List<Genre> {
|
||||
return document.select(".genre-item").mapIndexed { index, element ->
|
||||
Genre(element.text(), index + 1)
|
||||
}
|
||||
}
|
||||
|
||||
private fun fetchGenres() {
|
||||
|
|
|
@ -36,9 +36,7 @@ import java.io.IOException
|
|||
import java.net.URLDecoder
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class YuriNeko :
|
||||
HttpSource(),
|
||||
ConfigurableSource {
|
||||
class YuriNeko : HttpSource(), ConfigurableSource {
|
||||
|
||||
override val name = "YuriNeko"
|
||||
|
||||
|
@ -109,20 +107,22 @@ class YuriNeko :
|
|||
|
||||
override fun latestUpdatesParse(response: Response): MangasPage = throw UnsupportedOperationException()
|
||||
|
||||
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> = when {
|
||||
query.startsWith(PREFIX_ID_SEARCH) -> {
|
||||
val id = query.removePrefix(PREFIX_ID_SEARCH).trim()
|
||||
if (id.toIntOrNull() == null) {
|
||||
throw Exception("ID tìm kiếm không hợp lệ (phải là một số).")
|
||||
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
|
||||
return when {
|
||||
query.startsWith(PREFIX_ID_SEARCH) -> {
|
||||
val id = query.removePrefix(PREFIX_ID_SEARCH).trim()
|
||||
if (id.toIntOrNull() == null) {
|
||||
throw Exception("ID tìm kiếm không hợp lệ (phải là một số).")
|
||||
}
|
||||
fetchMangaDetails(
|
||||
SManga.create().apply {
|
||||
url = "/manga/$id"
|
||||
},
|
||||
)
|
||||
.map { MangasPage(listOf(it), false) }
|
||||
}
|
||||
fetchMangaDetails(
|
||||
SManga.create().apply {
|
||||
url = "/manga/$id"
|
||||
},
|
||||
)
|
||||
.map { MangasPage(listOf(it), false) }
|
||||
else -> super.fetchSearchManga(page, query, filters)
|
||||
}
|
||||
else -> super.fetchSearchManga(page, query, filters)
|
||||
}
|
||||
|
||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||
|
@ -182,13 +182,15 @@ class YuriNeko :
|
|||
|
||||
override fun searchMangaParse(response: Response): MangasPage = popularMangaParse(response)
|
||||
|
||||
override fun fetchMangaDetails(manga: SManga): Observable<SManga> = client.newCall(GET("$apiUrl${manga.url}"))
|
||||
.asObservableSuccess()
|
||||
.map { mangaDetailsParse(it) }
|
||||
override fun fetchMangaDetails(manga: SManga): Observable<SManga> =
|
||||
client.newCall(GET("$apiUrl${manga.url}"))
|
||||
.asObservableSuccess()
|
||||
.map { mangaDetailsParse(it) }
|
||||
|
||||
override fun mangaDetailsRequest(manga: SManga): Request = GET("$baseUrl${manga.url}")
|
||||
|
||||
override fun mangaDetailsParse(response: Response): SManga = response.parseAs<MangaDto>().toSManga(storageUrl)
|
||||
override fun mangaDetailsParse(response: Response): SManga =
|
||||
response.parseAs<MangaDto>().toSManga(storageUrl)
|
||||
|
||||
override fun chapterListRequest(manga: SManga): Request = GET("$apiUrl${manga.url}")
|
||||
|
||||
|
@ -200,11 +202,13 @@ class YuriNeko :
|
|||
|
||||
override fun pageListRequest(chapter: SChapter): Request = GET("$apiUrl${chapter.url}")
|
||||
|
||||
override fun pageListParse(response: Response): List<Page> = response.parseAs<ReadResponseDto>().toPageList(storageUrl)
|
||||
override fun pageListParse(response: Response): List<Page> =
|
||||
response.parseAs<ReadResponseDto>().toPageList(storageUrl)
|
||||
|
||||
override fun imageUrlParse(response: Response): String = throw UnsupportedOperationException()
|
||||
|
||||
open class UriPartFilter(displayName: String, private val vals: Array<Pair<String, String>>) : Filter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) {
|
||||
open class UriPartFilter(displayName: String, private val vals: Array<Pair<String, String>>) :
|
||||
Filter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) {
|
||||
fun toUriPart() = vals[state].second
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue