TCB scans: fix page list and refactor (#3793)

* TCB scans: fix page list and refactor

* rename state pref

* fix .5 chapter number

* remove redundant + in regex
This commit is contained in:
AwkwardPeak7 2024-07-04 17:06:21 +05:00 committed by Draff
parent 99539e261e
commit 7d607486c4
Signed by: Draff
GPG Key ID: E8A89F3211677653
2 changed files with 113 additions and 131 deletions

View File

@ -1,7 +1,7 @@
ext { ext {
extName = 'TCB Scans' extName = 'TCB Scans'
extClass = '.TCBScans' extClass = '.TCBScans'
extVersionCode = 7 extVersionCode = 8
} }
apply from: "$rootDir/common.gradle" apply from: "$rootDir/common.gradle"

View File

@ -1,174 +1,156 @@
package eu.kanade.tachiyomi.extension.en.tcbscans package eu.kanade.tachiyomi.extension.en.tcbscans
import android.app.Application import android.app.Application
import android.util.Log
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.asObservable
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
import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.ParsedHttpSource import eu.kanade.tachiyomi.source.online.ParsedHttpSource
import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import okhttp3.Response import okhttp3.Response
import okio.IOException
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import rx.Observable
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.io.File
class TCBScans : ParsedHttpSource() { class TCBScans : ParsedHttpSource() {
override val name = "TCB Scans" override val name = "TCB Scans"
override val baseUrl = "https://tcbscans.com" override val baseUrl = "https://tcbscans.me"
override val lang = "en" override val lang = "en"
override val supportsLatest = false override val supportsLatest = false
override val client: OkHttpClient = network.cloudflareClient
companion object { override val client = network.cloudflareClient.newBuilder().addNetworkInterceptor { chain ->
private const val MIGRATE_MESSAGE = "Migrate from TCB Scans to TCB Scans" val request = chain.request()
private val TITLE_REGEX = "[0-9]+$".toRegex() val response = chain.proceed(request)
}
if (
request.url.toString().startsWith(baseUrl) &&
request.url.pathSegments.firstOrNull() in listOf("mangas", "chapters") &&
response.code == 404
) {
throw IOException("Migrate from TCB Scans to TCB Scans")
}
return@addNetworkInterceptor response
}.build()
// popular // popular
override fun popularMangaRequest(page: Int): Request { override fun popularMangaRequest(page: Int): Request {
return GET("$baseUrl/projects")
}
override fun popularMangaSelector() = ".bg-card.border.border-border.rounded.p-3.mb-3"
override fun popularMangaFromElement(element: Element): SManga {
val manga = SManga.create()
manga.thumbnail_url = element.select(".w-24.h-24.object-cover.rounded-lg").attr("src")
manga.setUrlWithoutDomain(element.select("a.mb-3.text-white.text-lg.font-bold").attr("href"))
manga.title = element.select("a.mb-3.text-white.text-lg.font-bold").text()
return manga
}
override fun popularMangaNextPageSelector(): String? = null
override fun fetchMangaDetails(manga: SManga): Observable<SManga> {
return client.newCall(mangaDetailsRequest(manga))
.asObservable()
.doOnNext { response ->
if (!response.isSuccessful) {
response.close()
throw Exception(if (response.code == 404) MIGRATE_MESSAGE else "HTTP error ${response.code}")
}
}
.map { response ->
mangaDetailsParse(response).apply { initialized = true }
}
}
// 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()
override fun searchMangaParse(response: Response): MangasPage {
val document = response.asJsoup()
var mangas = document.select(popularMangaSelector()).map { popularMangaFromElement(it) }
val query = response.request.headers["query"]
mangas = if (query != null) {
mangas.filter { it.title.contains(query, true) }
} else {
emptyList()
}
return MangasPage(mangas, false)
}
override fun searchMangaFromElement(element: Element): SManga = popularMangaFromElement(element)
override fun searchMangaNextPageSelector(): String = throw UnsupportedOperationException()
override fun searchMangaSelector(): String = popularMangaSelector()
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
val headers = headersBuilder()
.add("query", query)
.build()
return GET("$baseUrl/projects", headers) return GET("$baseUrl/projects", headers)
} }
override fun popularMangaSelector() = "div.bg-card"
override fun popularMangaFromElement(element: Element) = SManga.create().apply {
with(element.selectFirst("a[href].text-white")!!) {
setUrlWithoutDomain(absUrl("href"))
title = text()
}
thumbnail_url = element.selectFirst("img")?.absUrl("src")
}
override fun popularMangaNextPageSelector() = null
// latest
override fun latestUpdatesRequest(page: Int) = throw UnsupportedOperationException()
override fun latestUpdatesSelector() = throw UnsupportedOperationException()
override fun latestUpdatesFromElement(element: Element) = throw UnsupportedOperationException()
override fun latestUpdatesNextPageSelector() = throw UnsupportedOperationException()
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
return GET("$baseUrl/projects#$query", headers)
}
override fun searchMangaParse(response: Response): MangasPage {
val query = response.request.url.fragment!!
val mangas = popularMangaParse(response).mangas.filter {
it.title.contains(query, true)
}
return MangasPage(mangas, false)
}
override fun searchMangaFromElement(element: Element): SManga = popularMangaFromElement(element)
override fun searchMangaNextPageSelector(): String = throw UnsupportedOperationException()
override fun searchMangaSelector(): String = popularMangaSelector()
// manga details // manga details
override fun mangaDetailsParse(document: Document) = SManga.create().apply { override fun mangaDetailsParse(document: Document) = SManga.create().apply {
val descElement = document.select(".order-1.bg-card.border.border-border.rounded.py-3") with(document.selectFirst("div.order-1")!!) {
thumbnail_url = selectFirst("img")?.absUrl("src")
thumbnail_url = descElement.select(".flex.items-center.justify-center img").attr("src") title = selectFirst("h1")!!.text()
title = descElement.select(".my-3.font-bold.text-3xl").text() description = selectFirst("p")?.text()
description = descElement.select(".leading-6.my-3").text() }
} }
// chapters // chapters
override fun chapterListSelector() = override fun chapterListSelector() = "div.grid a"
".block.border.border-border.bg-card.mb-3.p-3.rounded"
private fun chapterWithDate(element: Element, slug: String): SChapter { override fun chapterFromElement(element: Element) = SChapter.create().apply {
val seriesPrefs = Injekt.get<Application>().getSharedPreferences("source_${id}_updateTime:$slug", 0) setUrlWithoutDomain(element.absUrl("href"))
val seriesPrefsEditor = seriesPrefs.edit()
val chapter = chapterFromElement(element) val title = element.select("div.font-bold:not(.flex)").text()
val description = element.selectFirst(".text-gray-500")
?.text()?.takeIf { it.isNotBlank() }
val chapNumber = TITLE_REGEX.find(title)?.value
val currentTimeMillis = System.currentTimeMillis() name = buildString {
if (!seriesPrefs.contains(chapter.name)) { if (chapNumber != null) {
seriesPrefsEditor.putLong(chapter.name, currentTimeMillis) append("Chapter ")
} append(chapNumber)
} else {
chapter.date_upload = seriesPrefs.getLong(chapter.name, currentTimeMillis) append(title)
seriesPrefsEditor.apply()
return chapter
}
override fun chapterFromElement(element: Element): SChapter {
val chapter = SChapter.create()
chapter.setUrlWithoutDomain(element.attr("href"))
// Chapters retro compatibility
var name = element.select(".text-lg.font-bold:not(.flex)").text()
val description = element.select(".text-gray-500").text()
val matchResult = TITLE_REGEX.find(name)
if (matchResult != null) {
name = "Chapter ${matchResult.value}"
}
chapter.name = "$name: $description"
return chapter
}
override fun chapterListParse(response: Response): List<SChapter> {
val document = response.asJsoup()
val slug = response.request.url.pathSegments[2]
return document.select(chapterListSelector()).map { chapterWithDate(it, slug) }
}
override fun fetchPageList(chapter: SChapter): Observable<List<Page>> {
return client.newCall(pageListRequest(chapter))
.asObservable()
.doOnNext { response ->
if (!response.isSuccessful) {
response.close()
throw Exception(if (response.code == 404) MIGRATE_MESSAGE else "HTTP error ${response.code}")
}
} }
.map { response -> if (description != null) {
pageListParse(response) append(": ")
append(description)
} }
}
} }
override fun pageListParse(document: Document): List<Page> { override fun pageListParse(document: Document): List<Page> {
return document.select(".flex.flex-col.items-center.justify-center picture img") return document.select("picture img, .image-container img").mapIndexed { i, img ->
.mapIndexed { i, el -> Page(i, "", el.attr("src")) } Page(i, imageUrl = img.absUrl("src"))
}
} }
override fun imageUrlParse(document: Document) = "" override fun imageUrlParse(document: Document) = throw UnsupportedOperationException()
init {
val context = Injekt.get<Application>()
val prefs = context.getSharedPreferences("source_$id", 0x0000)
if (!prefs.getBoolean("legacy_updateTime_removed", false)) {
try {
val sharedPrefDir = File(context.applicationInfo.dataDir, "shared_prefs")
if (sharedPrefDir.exists() && sharedPrefDir.isDirectory()) {
val files = sharedPrefDir.listFiles()
if (files != null) {
for (file in files) {
if (
file.isFile &&
file.name.startsWith("source_${id}_updateTime") &&
file.name.endsWith(".xml")
) {
Log.d(name, "Deleting ${file.name}")
file.delete()
}
}
}
}
} catch (_: Exception) {
Log.e(name, "Failed to delete old preference files")
}
prefs.edit()
.putBoolean("legacy_updateTime_removed", true)
.apply()
}
}
} }
private val TITLE_REGEX = Regex("""\d+.?\d+$""")