Remove unused OtakuSanctuary multisrc (#10862)

This commit is contained in:
Vetle Ledaal 2025-10-05 11:03:25 +02:00 committed by Draff
parent 107e060ddc
commit ab4b1e2276
Signed by: Draff
GPG Key ID: E8A89F3211677653
3 changed files with 0 additions and 438 deletions

View File

@ -1,5 +0,0 @@
plugins {
id("lib-multisrc")
}
baseVersionCode = 4

View File

@ -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<SManga> {
val page = emptyList<SManga>().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<SChapter> {
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<Page> {
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>): 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")
}
}

View File

@ -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()
}
}
}