mirror of
https://github.com/keiyoushi/extensions-source.git
synced 2024-11-22 02:12:42 +01:00
Remove ReadManga.at (#4327)
This commit is contained in:
parent
70773b7037
commit
2948fa9233
@ -1,8 +0,0 @@
|
||||
ext {
|
||||
extName = 'ReadManga.at'
|
||||
extClass = '.ReadMangaAt'
|
||||
extVersionCode = 1
|
||||
isNsfw = true
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
Binary file not shown.
Before Width: | Height: | Size: 4.6 KiB |
Binary file not shown.
Before Width: | Height: | Size: 2.3 KiB |
Binary file not shown.
Before Width: | Height: | Size: 6.3 KiB |
Binary file not shown.
Before Width: | Height: | Size: 12 KiB |
Binary file not shown.
Before Width: | Height: | Size: 19 KiB |
@ -1,22 +0,0 @@
|
||||
package eu.kanade.tachiyomi.extension.ja.readmangaat
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
class LatestDto(
|
||||
val mes: String,
|
||||
val going: String,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
class InfoDto(
|
||||
val mes: String,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
class ImageResponseDto(
|
||||
val mes: String,
|
||||
val going: Int,
|
||||
@SerialName("img_index") val imgIndex: Int,
|
||||
)
|
@ -1,323 +0,0 @@
|
||||
package eu.kanade.tachiyomi.extension.ja.readmangaat
|
||||
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.network.POST
|
||||
import eu.kanade.tachiyomi.network.interceptor.rateLimit
|
||||
import eu.kanade.tachiyomi.source.model.Filter
|
||||
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.ParsedHttpSource
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.decodeFromStream
|
||||
import okhttp3.FormBody
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import org.jsoup.Jsoup
|
||||
import org.jsoup.nodes.Document
|
||||
import org.jsoup.nodes.Element
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.text.ParseException
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Locale
|
||||
|
||||
class ReadMangaAt : ParsedHttpSource() {
|
||||
|
||||
override val name = "ReadManga.at"
|
||||
|
||||
override val baseUrl = "https://readmanga.at"
|
||||
|
||||
override val lang = "ja"
|
||||
|
||||
override val supportsLatest = true
|
||||
|
||||
override val client = network.cloudflareClient.newBuilder()
|
||||
.rateLimit(2)
|
||||
.build()
|
||||
|
||||
override fun headersBuilder() = super.headersBuilder()
|
||||
.add("Referer", "$baseUrl/")
|
||||
|
||||
private val ajaxUrl = "$baseUrl/wp-admin/admin-ajax.php"
|
||||
|
||||
private val apiHeaders by lazy { apiHeadersBuilder().build() }
|
||||
|
||||
private fun apiHeadersBuilder() = headersBuilder().apply {
|
||||
add("Accept", "application/json, text/javascript, */*; q=0.01")
|
||||
add("Host", baseUrl.toHttpUrl().host)
|
||||
add("Origin", baseUrl)
|
||||
add("X-Requested-With", "XMLHttpRequest")
|
||||
}
|
||||
|
||||
private val json: Json by injectLazy()
|
||||
|
||||
private val dateFormat = SimpleDateFormat("M月 d, yyyy", Locale.JAPANESE)
|
||||
|
||||
private val chapterRegex = Regex("""\bp:\s*(\d+)""")
|
||||
|
||||
// ============================== Popular ===============================
|
||||
|
||||
override fun popularMangaRequest(page: Int): Request = GET(baseUrl, headers)
|
||||
|
||||
override fun popularMangaSelector(): String = "div:has(>div:contains(Popular)) > div > div[class~=entry]"
|
||||
|
||||
override fun popularMangaFromElement(element: Element): SManga = SManga.create().apply {
|
||||
with(element.selectFirst("h4 > a")!!) {
|
||||
setUrlWithoutDomain(attr("abs:href"))
|
||||
title = text()
|
||||
}
|
||||
}
|
||||
|
||||
override fun popularMangaNextPageSelector(): String? = null
|
||||
|
||||
// =============================== Latest ===============================
|
||||
|
||||
override fun latestUpdatesRequest(page: Int): Request {
|
||||
return if (page > 1) {
|
||||
val apiBody = FormBody.Builder().apply {
|
||||
add("action", "z_do_ajax")
|
||||
add("_action", "loadmore")
|
||||
add("p", page.toString())
|
||||
}.build()
|
||||
|
||||
POST(ajaxUrl, apiHeaders, apiBody)
|
||||
} else {
|
||||
GET(baseUrl, headers)
|
||||
}
|
||||
}
|
||||
|
||||
override fun latestUpdatesSelector(): String = "div.row > div.col-sm-6"
|
||||
|
||||
override fun latestUpdatesFromElement(element: Element): SManga = SManga.create().apply {
|
||||
with(element.selectFirst("a:has(img)")!!) {
|
||||
thumbnail_url = selectFirst("img")!!.attr("abs:src")
|
||||
setUrlWithoutDomain(attr("abs:href"))
|
||||
title = attr("title")
|
||||
}
|
||||
}
|
||||
|
||||
override fun latestUpdatesNextPageSelector(): String = ".text-center > a:contains(Load More)"
|
||||
|
||||
override fun latestUpdatesParse(response: Response): MangasPage {
|
||||
if (response.request.method == "GET") {
|
||||
return super.latestUpdatesParse(response)
|
||||
}
|
||||
|
||||
val data = response.parseAs<LatestDto>()
|
||||
val mangaList = Jsoup.parseBodyFragment(data.mes)
|
||||
.select("div.col-sm-6")
|
||||
.map(::latestUpdatesFromElement)
|
||||
val hasNextPage = data.going == "yes"
|
||||
|
||||
return MangasPage(mangaList, hasNextPage)
|
||||
}
|
||||
|
||||
// =============================== Search ===============================
|
||||
|
||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||
val url = baseUrl.toHttpUrl().newBuilder().apply {
|
||||
val genreFilter = filters.filterIsInstance<GenreFilter>().first()
|
||||
|
||||
if (query.isNotBlank()) {
|
||||
addQueryParameter("s", query)
|
||||
} else {
|
||||
addPathSegment("genres")
|
||||
addEncodedPathSegment(genreFilter.toUriPart())
|
||||
}
|
||||
|
||||
addPathSegment("")
|
||||
if (page > 1) {
|
||||
addPathSegment("page")
|
||||
addPathSegment(page.toString())
|
||||
addPathSegment("")
|
||||
}
|
||||
}.build()
|
||||
|
||||
return GET(url, headers)
|
||||
}
|
||||
|
||||
override fun searchMangaSelector(): String = ".row > .col-6 > .entry-ma"
|
||||
|
||||
override fun searchMangaFromElement(element: Element): SManga =
|
||||
latestUpdatesFromElement(element)
|
||||
|
||||
override fun searchMangaNextPageSelector(): String = ".pagination > span.current + a"
|
||||
|
||||
// =============================== Filters ==============================
|
||||
|
||||
override fun getFilterList(): FilterList = FilterList(
|
||||
Filter.Header("Note: ignored when using text search"),
|
||||
Filter.Separator(),
|
||||
GenreFilter(),
|
||||
)
|
||||
|
||||
open class UriPartFilter(displayName: String, val vals: Array<Pair<String, String>>) :
|
||||
Filter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) {
|
||||
fun toUriPart() = vals[state].second
|
||||
}
|
||||
|
||||
class GenreFilter : UriPartFilter(
|
||||
"Genre",
|
||||
arrayOf(
|
||||
Pair("Ecchi", "ecchi"),
|
||||
Pair("SF.ファンタジー", "sf-%e3%83%95%e3%82%a1%e3%83%b3%e3%82%bf%e3%82%b8%e3%83%bc"),
|
||||
Pair("お嬢様・令嬢", "%e3%81%8a%e5%ac%a2%e6%a7%98%e3%83%bb%e4%bb%a4%e5%ac%a2"),
|
||||
Pair("アドベンチャ", "%e3%82%a2%e3%83%89%e3%83%99%e3%83%b3%e3%83%81%e3%83%a3"),
|
||||
Pair("ゲーム", "%e3%82%b2%e3%83%bc%e3%83%a0"),
|
||||
Pair("サブカル・個性派", "%e3%82%b5%e3%83%96%e3%82%ab%e3%83%ab%e3%83%bb%e5%80%8b%e6%80%a7%e6%b4%be"),
|
||||
Pair("ショタコン", "%e3%82%b7%e3%83%a7%e3%82%bf%e3%82%b3%e3%83%b3"),
|
||||
Pair("バスケットボール", "%e3%83%90%e3%82%b9%e3%82%b1%e3%83%83%e3%83%88%e3%83%9c%e3%83%bc%e3%83%ab"),
|
||||
Pair("バトル", "%e3%83%90%e3%83%88%e3%83%ab"),
|
||||
Pair("バレーボール", "%e3%83%90%e3%83%ac%e3%83%bc%e3%83%9c%e3%83%bc%e3%83%ab"),
|
||||
Pair("ピューター", "%e3%83%94%e3%83%a5%e3%83%bc%e3%82%bf%e3%83%bc"),
|
||||
Pair("ミステリー・サスペンス", "%e3%83%9f%e3%82%b9%e3%83%86%e3%83%aa%e3%83%bc%e3%83%bb%e3%82%b5%e3%82%b9%e3%83%9a%e3%83%b3%e3%82%b9"),
|
||||
Pair("ラブラブ・あまあま", "%e3%83%a9%e3%83%96%e3%83%a9%e3%83%96%e3%83%bb%e3%81%82%e3%81%be%e3%81%82%e3%81%be"),
|
||||
Pair("三角関係", "%e4%b8%89%e8%a7%92%e9%96%a2%e4%bf%82"),
|
||||
Pair("会社", "%e4%bc%9a%e7%a4%be"),
|
||||
Pair("俺様・S彼", "%e4%bf%ba%e6%a7%98%e3%83%bbs%e5%bd%bc"),
|
||||
Pair("兄弟", "%e5%85%84%e5%bc%9f"),
|
||||
Pair("制服", "%e5%88%b6%e6%9c%8d"),
|
||||
Pair("前世", "%e5%89%8d%e4%b8%96"),
|
||||
Pair("天使・悪魔", "%e5%a4%a9%e4%bd%bf%e3%83%bb%e6%82%aa%e9%ad%94"),
|
||||
Pair("妊婦", "%e5%a6%8a%e5%a9%a6"),
|
||||
Pair("宗教", "%e5%ae%97%e6%95%99"),
|
||||
Pair("悪役令嬢", "%e6%82%aa%e5%bd%b9%e4%bb%a4%e5%ac%a2"),
|
||||
Pair("擬人化", "%e6%93%ac%e4%ba%ba%e5%8c%96"),
|
||||
Pair("短編", "%e7%9f%ad%e7%b7%a8"),
|
||||
Pair("職業・ビジネス", "%e8%81%b7%e6%a5%ad%e3%83%bb%e3%83%93%e3%82%b8%e3%83%8d%e3%82%b9"),
|
||||
Pair("致命的な", "%e8%87%b4%e5%91%bd%e7%9a%84%e3%81%aa"),
|
||||
Pair("萌え", "%e8%90%8c%e3%81%88"),
|
||||
Pair("超能力日常", "%e8%b6%85%e8%83%bd%e5%8a%9b%e6%97%a5%e5%b8%b8"),
|
||||
Pair("電子特典付き", "%e9%9b%bb%e5%ad%90%e7%89%b9%e5%85%b8%e4%bb%98%e3%81%8d"),
|
||||
),
|
||||
)
|
||||
|
||||
// =========================== Manga Details ============================
|
||||
|
||||
override fun mangaDetailsParse(document: Document): SManga = SManga.create().apply {
|
||||
thumbnail_url = document.selectFirst(".main-thumb > img[src]")!!.attr("abs:src")
|
||||
|
||||
with(document.selectFirst("div.row[class~=mb] > .col")!!) {
|
||||
title = selectFirst(".name")!!.text()
|
||||
status = selectFirst("span").parseStatus()
|
||||
}
|
||||
|
||||
val id = document.selectFirst("a[data-t]")!!.attr("data-t")
|
||||
val apiHeaders = apiHeadersBuilder().apply {
|
||||
set("Referer", document.location())
|
||||
}.build()
|
||||
|
||||
// Genres
|
||||
val genresBody = FormBody.Builder().apply {
|
||||
add("action", "z_do_ajax")
|
||||
add("_action", "load_all_genres")
|
||||
add("t", id)
|
||||
}.build()
|
||||
|
||||
val genreDto = client.newCall(
|
||||
POST(ajaxUrl, apiHeaders, genresBody),
|
||||
).execute().parseAs<InfoDto>()
|
||||
genre = Jsoup.parseBodyFragment(genreDto.mes)
|
||||
.select("a")
|
||||
.joinToString { it.text() }
|
||||
|
||||
// Desc
|
||||
val descBody = FormBody.Builder().apply {
|
||||
add("action", "z_do_ajax")
|
||||
add("_action", "load_tag_desc")
|
||||
add("t", id)
|
||||
}.build()
|
||||
description = client.newCall(
|
||||
POST(ajaxUrl, apiHeaders, descBody),
|
||||
).execute().parseAs<InfoDto>().mes
|
||||
}
|
||||
|
||||
private fun Element?.parseStatus(): Int = when (this?.text()?.lowercase()) {
|
||||
"ongoing" -> SManga.ONGOING
|
||||
"dropped" -> SManga.CANCELLED
|
||||
"paused" -> SManga.ON_HIATUS
|
||||
"completed" -> SManga.COMPLETED
|
||||
else -> SManga.UNKNOWN
|
||||
}
|
||||
|
||||
// ============================== Chapters ==============================
|
||||
|
||||
override fun chapterListSelector(): String = ".row > .col-sm-6 > .entry-chapter"
|
||||
|
||||
override fun chapterFromElement(element: Element): SChapter = SChapter.create().apply {
|
||||
date_upload = try {
|
||||
element.selectFirst(".date")?.text()?.let {
|
||||
dateFormat.parse(it)!!.time
|
||||
} ?: 0L
|
||||
} catch (_: ParseException) {
|
||||
0L
|
||||
}
|
||||
with(element.selectFirst("a")!!) {
|
||||
name = text()
|
||||
setUrlWithoutDomain(attr("abs:href"))
|
||||
}
|
||||
}
|
||||
|
||||
// =============================== Pages ================================
|
||||
|
||||
override fun pageListParse(document: Document): List<Page> {
|
||||
val script = document.selectFirst("script:containsData(decode_images)")?.data()
|
||||
?: throw Exception("Unable to find script")
|
||||
val id = chapterRegex.find(script)?.groupValues?.get(1)
|
||||
?: throw Exception("Unable to get chapter id")
|
||||
|
||||
var going = 1
|
||||
var imgIndex = 0
|
||||
val pageList = mutableListOf<Page>()
|
||||
val apiHeaders = apiHeadersBuilder().apply {
|
||||
set("Referer", document.location())
|
||||
}.build()
|
||||
|
||||
while (going == 1) {
|
||||
val pagesBody = FormBody.Builder().apply {
|
||||
add("action", "z_do_ajax")
|
||||
add("_action", "decode_images")
|
||||
add("p", id)
|
||||
add("img_index", imgIndex.toString())
|
||||
}.build()
|
||||
|
||||
val data = client.newCall(
|
||||
POST(ajaxUrl, apiHeaders, pagesBody),
|
||||
).execute().parseAs<ImageResponseDto>()
|
||||
|
||||
imgIndex = data.imgIndex
|
||||
going = if (data.mes.isNotBlank()) data.going else 0
|
||||
|
||||
Jsoup.parseBodyFragment(data.mes).select("img[src]").forEachIndexed { index, element ->
|
||||
pageList.add(Page(imgIndex + index, imageUrl = element.attr("abs:src")))
|
||||
}
|
||||
}
|
||||
|
||||
return pageList
|
||||
}
|
||||
|
||||
override fun imageUrlParse(document: Document) = ""
|
||||
|
||||
override fun imageRequest(page: Page): Request {
|
||||
val imgHeaders = headersBuilder().apply {
|
||||
add("Accept", "image/avif,image/webp,*/*")
|
||||
add("Accept-Language", "en-US,en;q=0.5")
|
||||
add("DNT", "1")
|
||||
add("Host", page.imageUrl!!.substringAfter("://").substringBefore("/"))
|
||||
add("Sec-Fetch-Dest", "image")
|
||||
add("Sec-Fetch-Mode", "no-cors")
|
||||
add("Sec-Fetch-Site", "cross-site")
|
||||
}.build()
|
||||
|
||||
return GET(page.imageUrl!!, imgHeaders)
|
||||
}
|
||||
|
||||
// ============================= Utilities ==============================
|
||||
|
||||
private inline fun <reified T> Response.parseAs(): T = use {
|
||||
json.decodeFromStream(it.body.byteStream())
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user