Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

update dependencies and lint #7213

Closed
wants to merge 13 commits into from
  •  
  •  
  •  
3 changes: 3 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ ij_kotlin_allow_trailing_comma = true
ij_kotlin_allow_trailing_comma_on_call_site = true
ij_kotlin_name_count_to_use_star_import = 2147483647
ij_kotlin_name_count_to_use_star_import_for_members = 2147483647
ktlint_function_signature_body_expression_wrapping = default
ktlint_standard_no-empty-first-line-in-class-body = disabled
ktlint_standard_chain-method-continuation = disabled

[*.properties]
charset = utf-8
10 changes: 10 additions & 0 deletions buildSrc/src/main/kotlin/lib-android.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,16 @@ 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 {
Expand Down
14 changes: 6 additions & 8 deletions buildSrc/src/main/kotlin/lib-multisrc.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -23,19 +23,17 @@ 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())
}
Expand Down
43 changes: 17 additions & 26 deletions common.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,19 @@ 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']
Expand Down Expand Up @@ -81,22 +94,14 @@ android {
}

compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}

kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8.toString()
jvmTarget = JavaVersion.VERSION_17.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 {
Expand All @@ -105,22 +110,8 @@ dependencies {
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(lintKotlin)

preBuild.dependsOn(writeManifestFile, lintKotlin)
if (System.getenv("CI") != "true") {
lintKotlin.dependsOn(formatKotlin)
}
14 changes: 7 additions & 7 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
[versions]
kotlin_version = "1.7.21"
coroutines_version = "1.6.4"
serialization_version = "1.4.0"
kotlin_version = "2.1.0"
coroutines_version = "1.10.1"
serialization_version = "1.8.0"

[libraries]
gradle-agp = { module = "com.android.tools.build:gradle", version = "8.6.1" }
gradle-agp = { module = "com.android.tools.build:gradle", version = "8.8.0" }
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" }
gradle-kotlinter = { module = "org.jmailen.gradle:kotlinter-gradle", version = "3.13.0" }
gradle-kotlinter = { module = "org.jmailen.gradle:kotlinter-gradle", version = "5.0.1" }

tachiyomi-lib = { module = "com.github.tachiyomiorg:extensions-lib", version = "1.4.2" }

Expand All @@ -20,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.15.1" }
okhttp = { module = "com.squareup.okhttp3:okhttp", version = "5.0.0-alpha.11" }
jsoup = { module = "org.jsoup:jsoup", version = "1.18.3" }
okhttp = { module = "com.squareup.okhttp3:okhttp", version = "5.0.0-alpha.14" }
quickjs = { module = "app.cash.quickjs:quickjs-android", version = "0.9.2" }

[bundles]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ open class A3Manga(
override val baseUrl: String,
override val lang: String,
) : ParsedHttpSource() {

override val supportsLatest: Boolean = false

override val client: OkHttpClient = network.cloudflareClient
Expand Down Expand Up @@ -64,33 +63,37 @@ open class A3Manga(

override fun latestUpdatesNextPageSelector() = throw UnsupportedOperationException()

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()
fetchMangaDetails(
SManga.create().apply {
url = "/truyen-tranh/$id/"
},
)
.map {
it.url = "/truyen-tranh/$id/"
MangasPage(listOf(it), false)
}
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()
fetchMangaDetails(
SManga.create().apply {
url = "/truyen-tranh/$id/"
},
).map {
it.url = "/truyen-tranh/$id/"
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 =
POST(
"$baseUrl/wp-admin/admin-ajax.php",
headers,
FormBody.Builder()
.add("action", "searchtax")
.add("keyword", query)
.build(),
)
override fun searchMangaRequest(
page: Int,
query: String,
filters: FilterList,
): Request = POST(
"$baseUrl/wp-admin/admin-ajax.php",
headers,
FormBody.Builder()
.add("action", "searchtax")
.add("keyword", query)
.build(),
)

override fun searchMangaSelector(): String = throw UnsupportedOperationException()

Expand All @@ -105,15 +108,16 @@ open class A3Manga(
return MangasPage(emptyList(), false)
}

val manga = dto.data
.filter { it.cstatus != "Nhóm dịch" }
.map {
SManga.create().apply {
setUrlWithoutDomain(it.link)
title = it.title
thumbnail_url = it.img
val manga =
dto.data
.filter { it.cstatus != "Nhóm dịch" }
.map {
SManga.create().apply {
setUrlWithoutDomain(it.link)
title = it.title
thumbnail_url = it.img
}
}
}

return MangasPage(manga, false)
}
Expand All @@ -122,19 +126,21 @@ open class A3Manga(
title = document.select(".info-title").text()
author = document.select(".comic-info strong:contains(Tác giả) + span").text().trim()
description = document.select(".intro-container .text-justify").text().substringBefore("— Xem Thêm —")
genre = document.select(".comic-info .tags a").joinToString { tag ->
tag.text().split(' ').joinToString(separator = " ") { word ->
word.replaceFirstChar { it.titlecase() }
genre =
document.select(".comic-info .tags a").joinToString { tag ->
tag.text().split(' ').joinToString(separator = " ") { word ->
word.replaceFirstChar { it.titlecase() }
}
}
}
thumbnail_url = document.select(".img-thumbnail").attr("abs:src")

val statusString = document.select(".comic-info strong:contains(Tình trạng) + span").text()
status = when (statusString) {
"Đang tiến hành" -> SManga.ONGOING
"Trọn bộ " -> SManga.COMPLETED
else -> SManga.UNKNOWN
}
status =
when (statusString) {
"Đang tiến hành" -> SManga.ONGOING
"Trọn bộ " -> SManga.COMPLETED
else -> SManga.UNKNOWN
}
}

override fun chapterListSelector(): String = ".chapter-table table tbody tr"
Expand All @@ -148,23 +154,28 @@ open class A3Manga(
}

protected fun decodeImgList(document: Document): String {
val htmlContentScript = document.selectFirst("script:containsData(htmlContent)")?.html()
?.substringAfter("var htmlContent=\"")
?.substringBefore("\";")
?.replace("\\\"", "\"")
?.replace("\\\\", "\\")
?.replace("\\/", "/")
?: throw Exception("Couldn't find script with image data.")
val htmlContentScript =
document.selectFirst("script:containsData(htmlContent)")
?.html()
?.substringAfter("var htmlContent=\"")
?.substringBefore("\";")
?.replace("\\\"", "\"")
?.replace("\\\\", "\\")
?.replace("\\/", "/")
?: throw Exception("Couldn't find script with image data.")
val htmlContent = json.decodeFromString<CipherDto>(htmlContentScript)
val ciphertext = Base64.decode(htmlContent.ciphertext, Base64.DEFAULT)
val iv = htmlContent.iv.decodeHex()
val salt = htmlContent.salt.decodeHex()

val passwordScript = document.selectFirst("script:containsData(chapterHTML)")?.html()
?: throw Exception("Couldn't find password to decrypt image data.")
val passphrase = passwordScript.substringAfter("var chapterHTML=CryptoJSAesDecrypt('")
.substringBefore("',htmlContent")
.replace("'+'", "")
val passwordScript =
document.selectFirst("script:containsData(chapterHTML)")?.html()
?: throw Exception("Couldn't find password to decrypt image data.")
val passphrase =
passwordScript
.substringAfter("var chapterHTML=CryptoJSAesDecrypt('")
.substringBefore("',htmlContent")
.replace("'+'", "")

val keyFactory = SecretKeyFactory.getInstance(KEY_ALGORITHM)
val spec = PBEKeySpec(passphrase.toCharArray(), salt, 999, 256)
Expand Down Expand Up @@ -192,12 +203,13 @@ open class A3Manga(
// We expect the URL to start with `https://`, where the last 3 characters are encoded.
// The length of the encoded character is not known, but it is the same across all.
// Essentially we are looking for the two encoded slashes, which tells us the length.
val patternIdx = patternsLengthCheck.indexOfFirst { pattern ->
val matchResult = pattern.find(this)
val g1 = matchResult?.groupValues?.get(1)
val g2 = matchResult?.groupValues?.get(2)
g1 == g2 && g1 != null
}
val patternIdx =
patternsLengthCheck.indexOfFirst { pattern ->
val matchResult = pattern.find(this)
val g1 = matchResult?.groupValues?.get(1)
val g2 = matchResult?.groupValues?.get(2)
g1 == g2 && g1 != null
}
if (patternIdx == -1) {
return null
}
Expand All @@ -215,9 +227,7 @@ open class A3Manga(

override fun imageUrlParse(document: Document) = throw UnsupportedOperationException()

private inline fun <reified T> Response.parseAs(): T {
return json.decodeFromString(body.string())
}
private inline fun <reified T> Response.parseAs(): T = json.decodeFromString(body.string())

// https://stackoverflow.com/a/66614516
private fun String.decodeHex(): ByteArray {
Expand All @@ -233,15 +243,18 @@ open class A3Manga(
const val CIPHER_TRANSFORMATION = "AES/CBC/PKCS7PADDING"

const val PREFIX_ID_SEARCH = "id:"
val dateFormat = SimpleDateFormat("dd/MM/yyyy", Locale.US).apply {
timeZone = TimeZone.getTimeZone("Asia/Ho_Chi_Minh")
}
val dateFormat =
SimpleDateFormat("dd/MM/yyyy", Locale.US).apply {
timeZone = TimeZone.getTimeZone("Asia/Ho_Chi_Minh")
}

private val patternsLengthCheck: List<Regex> = (20 downTo 1).map { i ->
"""^https.{$i}(.{$i})(.{$i})""".toRegex()
}
private val patternsSubstitution: List<Regex> = (20 downTo 1).map { i ->
"""^https(.{$i})(.{$i}).*(.{$i})(?:webp|jpeg|tiff|.{3})$""".toRegex()
}
private val patternsLengthCheck: List<Regex> =
(20 downTo 1).map { i ->
"""^https.{$i}(.{$i})(.{$i})""".toRegex()
}
private val patternsSubstitution: List<Regex> =
(20 downTo 1).map { i ->
"""^https(.{$i})(.{$i}).*(.{$i})(?:webp|jpeg|tiff|.{3})$""".toRegex()
}
}
}
Loading