From ab4b1e227657f3ae520cf28e815f1834ff838e67 Mon Sep 17 00:00:00 2001 From: Vetle Ledaal Date: Sun, 5 Oct 2025 11:03:25 +0200 Subject: [PATCH] Remove unused OtakuSanctuary multisrc (#10862) --- lib-multisrc/otakusanctuary/build.gradle.kts | 5 - .../multisrc/otakusanctuary/OtakuSanctuary.kt | 275 ------------------ .../otakusanctuary/OtakuSanctuaryHelper.kt | 158 ---------- 3 files changed, 438 deletions(-) delete mode 100644 lib-multisrc/otakusanctuary/build.gradle.kts delete mode 100644 lib-multisrc/otakusanctuary/src/eu/kanade/tachiyomi/multisrc/otakusanctuary/OtakuSanctuary.kt delete mode 100644 lib-multisrc/otakusanctuary/src/eu/kanade/tachiyomi/multisrc/otakusanctuary/OtakuSanctuaryHelper.kt diff --git a/lib-multisrc/otakusanctuary/build.gradle.kts b/lib-multisrc/otakusanctuary/build.gradle.kts deleted file mode 100644 index 6e70fd158..000000000 --- a/lib-multisrc/otakusanctuary/build.gradle.kts +++ /dev/null @@ -1,5 +0,0 @@ -plugins { - id("lib-multisrc") -} - -baseVersionCode = 4 diff --git a/lib-multisrc/otakusanctuary/src/eu/kanade/tachiyomi/multisrc/otakusanctuary/OtakuSanctuary.kt b/lib-multisrc/otakusanctuary/src/eu/kanade/tachiyomi/multisrc/otakusanctuary/OtakuSanctuary.kt deleted file mode 100644 index 7dbcdc9e1..000000000 --- a/lib-multisrc/otakusanctuary/src/eu/kanade/tachiyomi/multisrc/otakusanctuary/OtakuSanctuary.kt +++ /dev/null @@ -1,275 +0,0 @@ -package eu.kanade.tachiyomi.multisrc.otakusanctuary - -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.POST -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 kotlinx.serialization.json.Json -import kotlinx.serialization.json.jsonArray -import kotlinx.serialization.json.jsonObject -import kotlinx.serialization.json.jsonPrimitive -import okhttp3.FormBody -import okhttp3.Headers -import okhttp3.HttpUrl.Companion.toHttpUrl -import okhttp3.Request -import okhttp3.Response -import org.jsoup.select.Elements -import org.jsoup.select.Evaluator -import uy.kohesive.injekt.injectLazy -import java.text.SimpleDateFormat -import java.util.Calendar -import java.util.Locale -import java.util.TimeZone - -open class OtakuSanctuary( - override val name: String, - override val baseUrl: String, - override val lang: String, -) : HttpSource() { - - override val supportsLatest = false - - override val client = network.cloudflareClient - - override fun headersBuilder(): Headers.Builder = super.headersBuilder().add("Referer", "$baseUrl/") - - private val helper = OtakuSanctuaryHelper(lang) - - private val json: Json by injectLazy() - - // There's no popular list, this will have to do - override fun popularMangaRequest(page: Int) = POST( - "$baseUrl/Manga/Newest", - headers, - FormBody.Builder().apply { - add("Lang", helper.otakusanLang()) - add("Page", page.toString()) - add("Type", "Include") - add("Dir", "NewPostedDate") - }.build(), - ) - - private fun parseMangaCollection(elements: Elements): List { - val page = emptyList().toMutableList() - - for (element in elements) { - val url = element.select("div.mdl-card__title a").first()!!.attr("abs:href") - // ignore external chapters - if (url.toHttpUrl().host != baseUrl.toHttpUrl().host) { - continue - } - - // ignore web novels/light novels - val variant = element.select("div.mdl-card__supporting-text div.text-overflow-90 a").text() - if (variant.contains("Novel")) { - continue - } - - // ignore languages that dont match current ext - val language = element.select("img.flag").attr("abs:src") - .substringAfter("flags/") - .substringBefore(".png") - if (helper.otakusanLang() != "all" && language != helper.otakusanLang()) { - continue - } - - page += SManga.create().apply { - setUrlWithoutDomain(url) - title = element.select("div.mdl-card__supporting-text a[target=_blank]").text() - .replaceFirstChar { it.titlecase() } - thumbnail_url = element.select("div.container-3-4.background-contain img").first()!!.attr("abs:src") - } - } - return page - } - - override fun popularMangaParse(response: Response): MangasPage { - val document = response.asJsoup() - val collection = document.select("div.mdl-card") - val hasNextPage = !document.select("button.btn-loadmore").text().contains("Hết") - return MangasPage(parseMangaCollection(collection), 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 = - GET( - baseUrl.toHttpUrl().newBuilder().apply { - addPathSegments("Home/Search") - addQueryParameter("search", query) - }.build().toString(), - headers, - ) - - override fun searchMangaParse(response: Response): MangasPage { - val document = response.asJsoup() - val collection = document.select("div.collection:has(.group-header:contains(Manga)) div.mdl-card") - return MangasPage(parseMangaCollection(collection), false) - } - - override fun mangaDetailsParse(response: Response): SManga { - val document = response.asJsoup() - - return SManga.create().apply { - title = document.select("h1.title.text-lg-left.text-overflow-2-line") - .text() - .replaceFirstChar { it.titlecase() } - author = document.select("tr:contains(Tác Giả) a.capitalize").first()!!.text() - .replaceFirstChar { it.titlecase() } - description = document.select("div.summary p").joinToString("\n") { - it.run { - select(Evaluator.Tag("br")).prepend("\\n") - this.text().replace("\\n", "\n").replace("\n ", "\n") - } - }.trim() - genre = document.select("div.genres a").joinToString { it.text() } - thumbnail_url = document.select("div.container-3-4.background-contain img").attr("abs:src") - - val statusString = document.select("tr:contains(Tình Trạng) td").first()!!.text().trim() - status = when (statusString) { - "Ongoing" -> SManga.ONGOING - "Done" -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - } - } - - private val dateFormat = SimpleDateFormat("dd/MM/yyyy", Locale.US).apply { - timeZone = TimeZone.getTimeZone("Asia/Ho_Chi_Minh") - } - - private fun parseDate(date: String): Long { - if (date.contains("cách đây")) { - val number = Regex("""(\d+)""").find(date)?.value?.toIntOrNull() ?: return 0 - val cal = Calendar.getInstance() - - return when { - date.contains("ngày") -> cal.apply { add(Calendar.DAY_OF_MONTH, -number) }.timeInMillis - date.contains("tiếng") -> cal.apply { add(Calendar.HOUR, -number) }.timeInMillis - date.contains("phút") -> cal.apply { add(Calendar.MINUTE, -number) }.timeInMillis - date.contains("giây") -> cal.apply { add(Calendar.SECOND, -number) }.timeInMillis - else -> 0L - } - } else { - return runCatching { dateFormat.parse(date)?.time }.getOrNull() ?: 0L - } - } - - override fun chapterListParse(response: Response): List { - val document = response.asJsoup() - - return document.select("tr.chapter").map { - val cells = it.select("td") - SChapter.create().apply { - setUrlWithoutDomain(cells[1].select("a").attr("href")) - name = cells[1].text() - date_upload = parseDate(cells[3].text()) - chapter_number = cells[0].text().toFloatOrNull() ?: -1f - } - } - } - - override fun imageUrlParse(response: Response) = throw UnsupportedOperationException() - - override fun pageListParse(response: Response): List { - val document = response.asJsoup() - - val vi = document.select("#dataip").attr("value") - val numericId = document.select("#inpit-c").attr("data-chapter-id") - - val data = json.parseToJsonElement( - client.newCall( - POST( - "$baseUrl/Manga/UpdateView", - headers, - FormBody.Builder().add("chapId", numericId).build(), - ), - ).execute().body.string(), - ).jsonObject - - if (data["view"] != null) { - val usingservers = mutableListOf(0, 0, 0) - - val isSuccess = data["isSuccess"]!!.jsonArray.map { it.jsonPrimitive.content } - return json.parseToJsonElement(data["view"]!!.jsonPrimitive.content).jsonArray.mapIndexed { idx, it -> - var url = helper.processUrl(it.jsonPrimitive.content).removePrefix("image:") - val indexServer = getIndexLessServer(usingservers) - - if (url.contains("ImageSyncing") || url.contains("FetchService") || url.contains("otakusan.net_") && (url.contains("extendContent") || url.contains("/Extend")) && !url.contains("fetcher.otakusan.net") && !url.contains("image3.otakusan.net") && !url.contains("image3.otakuscan.net") && !url.contains("[GDP]") && !url.contains("[GDT]")) { - if (url.startsWith("/api/Value/")) { - val serverUrl = if (helper.otakusanLang() == "us" && indexServer == 1) { - US_SERVERS[0] - } else { - SERVERS[indexServer] - } - url = "$serverUrl$url" - } - - if (url.contains("otakusan.net_") && !url.contains("fetcher.otakuscan.net")) { - url += "#${isSuccess[idx]}" - } - - usingservers[indexServer] += 1 - } - - Page(idx, imageUrl = url) - } - } else { - val alternate = json.parseToJsonElement( - client.newCall( - POST( - "$baseUrl/Manga/CheckingAlternate", - headers, - FormBody.Builder().add("chapId", numericId).build(), - ), - ).execute().body.string(), - ).jsonObject - val content = alternate["Content"]?.jsonPrimitive?.content - ?: throw Exception("No pages found") - return json.parseToJsonElement(content).jsonArray.mapIndexed { idx, it -> - Page(idx, imageUrl = helper.processUrl(it.jsonPrimitive.content, vi)) - } - } - } - - override fun imageRequest(page: Page): Request { - val request = super.imageRequest(page) - val url = request.url.toString() - - val newRequest = request.newBuilder() - - if (url.contains("ImageSyncing") || url.contains("FetchService") || url.contains("otakusan.net_") && (url.contains("extendContent") || url.contains("/Extend")) && !url.contains("fetcher.otakusan.net") && !url.contains("image3.otakusan.net") && !url.contains("image3.otakuscan.net") && !url.contains("[GDP]") && !url.contains("[GDT]")) { - if (url.contains("otakusan.net_") && !url.contains("fetcher.otakuscan.net")) { - newRequest.header("page-sign", request.url.fragment!!) - } else { - newRequest.header("page-lang", "vn-lang") - } - } - - return newRequest.build() - } - - private fun getIndexLessServer(usingservers: List): Int { - var minIndex = usingservers[0] - var minNumber = usingservers[0] - for (i in 1 until 3) { - if (usingservers[i] <= minNumber) { - minIndex = i - minNumber = usingservers[i] - } - } - return minIndex - } - - companion object { - val SERVERS = listOf("https://image2.otakuscan.net", "https://shopotaku.net", "https://image.otakuscan.net") - val US_SERVERS = listOf("https://image3.shopotaku.net", "https://image2.otakuscan.net") - } -} diff --git a/lib-multisrc/otakusanctuary/src/eu/kanade/tachiyomi/multisrc/otakusanctuary/OtakuSanctuaryHelper.kt b/lib-multisrc/otakusanctuary/src/eu/kanade/tachiyomi/multisrc/otakusanctuary/OtakuSanctuaryHelper.kt deleted file mode 100644 index ec410cebc..000000000 --- a/lib-multisrc/otakusanctuary/src/eu/kanade/tachiyomi/multisrc/otakusanctuary/OtakuSanctuaryHelper.kt +++ /dev/null @@ -1,158 +0,0 @@ -package eu.kanade.tachiyomi.multisrc.otakusanctuary - -import okhttp3.HttpUrl.Companion.toHttpUrl - -class OtakuSanctuaryHelper(private val lang: String) { - - fun otakusanLang() = when (lang) { - "vi" -> "vn" - "en" -> "us" - else -> lang - } - - fun processUrl(url: String, vi: String = ""): String { - var url = url.replace("_h_", "http") - .replace("_e_", "/extendContent/Manga") - .replace("_r_", "/extendContent/MangaRaw") - - if (url.startsWith("//")) { - url = "https:$url" - } - if (url.contains("drive.google.com")) { - return url - } - - url = when (url.slice(0..4)) { - "[GDP]" -> url.replace("[GDP]", "https://drive.google.com/uc?export=view&id=") - "[GDT]" -> if (otakusanLang() == "us") { - url.replace("image2.otakuscan.net", "image3.shopotaku.net") - .replace("image2.otakusan.net", "image3.shopotaku.net") - } else { - url - } - "[IS1]" -> { - val url = url.replace("[IS1]", "https://imagepi.otakuscan.net/") - if (url.contains("vi") && url.contains("otakusan.net_")) { - url - } else { - url.toHttpUrl().newBuilder().apply { - addQueryParameter("vi", vi) - }.build().toString() - } - } - "[IS3]" -> url.replace("[IS3]", "https://image3.otakusan.net/") - "[IO3]" -> url.replace("[IO3]", "http://image3.shopotaku.net/") - else -> url - } - - if (url.contains("/Content/Workshop") || url.contains("otakusan") || url.contains("myrockmanga")) { - return url - } - - if (url.contains("file-bato-orig.anyacg.co")) { - url = url.replace("file-bato-orig.anyacg.co", "file-bato-orig.bato.to") - } - - if (url.contains("file-comic")) { - if (url.contains("file-comic-1")) { - url = url.replace("file-comic-1.anyacg.co", "z-img-01.mangapark.net") - } - if (url.contains("file-comic-2")) { - url = url.replace("file-comic-2.anyacg.co", "z-img-02.mangapark.net") - } - if (url.contains("file-comic-3")) { - url = url.replace("file-comic-3.anyacg.co", "z-img-03.mangapark.net") - } - if (url.contains("file-comic-4")) { - url = url.replace("file-comic-4.anyacg.co", "z-img-04.mangapark.net") - } - if (url.contains("file-comic-5")) { - url = url.replace("file-comic-5.anyacg.co", "z-img-05.mangapark.net") - } - if (url.contains("file-comic-6")) { - url = url.replace("file-comic-6.anyacg.co", "z-img-06.mangapark.net") - } - if (url.contains("file-comic-9")) { - url = url.replace("file-comic-9.anyacg.co", "z-img-09.mangapark.net") - } - if (url.contains("file-comic-10")) { - url = url.replace("file-comic-10.anyacg.co", "z-img-10.mangapark.net") - } - if (url.contains("file-comic-99")) { - url = url.replace("file-comic-99.anyacg.co/uploads", "file-bato-0001.bato.to") - } - } - - if (url.contains("cdn.nettruyen.com")) { - url = url.replace( - "cdn.nettruyen.com/Data/Images/", - "truyen.cloud/data/images/", - ) - } - if (url.contains("url=")) { - url = url.substringAfter("url=") - } - if (url.contains("blogspot") || url.contains("fshare")) { - url = url.replace("http:", "https:") - } - if (url.contains("blogspot") && !url.contains("http")) { - url = "https://$url" - } - if (url.contains("app/manga/uploads/") && !url.contains("http")) { - url = "https://lhscan.net$url" - } - url = url.replace("//cdn.adtrue.com/rtb/async.js", "") - - if (url.contains(".webp")) { - url = "https://otakusan.net/api/Value/ImageSyncing?ip=34512351".toHttpUrl().newBuilder() - .apply { - addQueryParameter("url", url) - }.build().toString() - } else if ( - ( - url.contains("merakiscans") || - url.contains("mangazuki") || - url.contains("ninjascans") || - url.contains("anyacg.co") || - url.contains("mangakatana") || - url.contains("zeroscans") || - url.contains("mangapark") || - url.contains("mangadex") || - url.contains("uptruyen") || - url.contains("hocvientruyentranh") || - url.contains("ntruyen.info") || - url.contains("chancanvas") || - url.contains("bato.to") - ) && - ( - !url.contains("googleusercontent") && - !url.contains("otakusan") && - !url.contains("otakuscan") && - !url.contains("shopotaku") - ) - ) { - url = - "https://images2-focus-opensocial.googleusercontent.com/gadgets/proxy?container=focus&gadget=a&no_expand=1&resize_h=0&rewriteMime=image%2F*".toHttpUrl() - .newBuilder().apply { - addQueryParameter("url", url) - }.build().toString() - } else if (url.contains("imageinstant.com")) { - url = "https://images.weserv.nl/".toHttpUrl().newBuilder().apply { - addQueryParameter("url", url) - }.build().toString() - } else if (!url.contains("otakusan.net")) { - url = "https://otakusan.net/api/Value/ImageSyncing?ip=34512351".toHttpUrl().newBuilder() - .apply { - addQueryParameter("url", url) - }.build().toString() - } - - return if (url.contains("vi=") && !url.contains("otakusan.net_")) { - url - } else { - url.toHttpUrl().newBuilder().apply { - addQueryParameter("vi", vi) - }.build().toString() - } - } -}