Fix Baozimh.org and rename to GoDa Manhua (#3087)
* Fix Baozimh * Cleanup * Fix author info * Use parent request * Bump versionID * Update * Update code * Rename source to GoDa Manhua and update icon (to distinguish from the "legit" Baozi Manhua) * Add English source * Update * Update * Keep the Chinese source only for now * Add comments to explain why the English source is not added * Error message on old chapter keys --------- Co-authored-by: stevenyomi <95685115+stevenyomi@users.noreply.github.com>
@ -1,7 +1,7 @@
|
|||||||
ext {
|
ext {
|
||||||
extName = 'Baozimh.org'
|
extName = 'GoDa'
|
||||||
extClass = '.BaozimhOrg'
|
extClass = '.GoDaManhua'
|
||||||
extVersionCode = 28
|
extVersionCode = 29
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: "$rootDir/common.gradle"
|
apply from: "$rootDir/common.gradle"
|
||||||
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 2.3 KiB |
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 5.0 KiB After Width: | Height: | Size: 2.9 KiB |
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 5.2 KiB |
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 6.7 KiB |
@ -1,10 +1,6 @@
|
|||||||
package eu.kanade.tachiyomi.extension.zh.baozimhorg
|
package eu.kanade.tachiyomi.extension.zh.baozimhorg
|
||||||
|
|
||||||
import android.app.Application
|
|
||||||
import androidx.preference.ListPreference
|
|
||||||
import androidx.preference.PreferenceScreen
|
|
||||||
import eu.kanade.tachiyomi.network.GET
|
import eu.kanade.tachiyomi.network.GET
|
||||||
import eu.kanade.tachiyomi.source.ConfigurableSource
|
|
||||||
import eu.kanade.tachiyomi.source.model.Filter
|
import eu.kanade.tachiyomi.source.model.Filter
|
||||||
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
|
||||||
@ -13,140 +9,133 @@ 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.HttpSource
|
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||||
import eu.kanade.tachiyomi.util.asJsoup
|
import eu.kanade.tachiyomi.util.asJsoup
|
||||||
import okhttp3.HttpUrl
|
|
||||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
import org.jsoup.nodes.Document
|
import org.jsoup.nodes.Document
|
||||||
import org.jsoup.nodes.Element
|
import org.jsoup.nodes.Entities
|
||||||
import org.jsoup.select.Evaluator
|
import rx.Observable
|
||||||
import uy.kohesive.injekt.Injekt
|
|
||||||
import uy.kohesive.injekt.api.get
|
|
||||||
import java.text.SimpleDateFormat
|
|
||||||
import java.util.Locale
|
|
||||||
|
|
||||||
// Uses WPManga + GeneratePress/Blocksy Child
|
open class BaozimhOrg(
|
||||||
class BaozimhOrg : HttpSource(), ConfigurableSource {
|
override val name: String,
|
||||||
|
override val baseUrl: String,
|
||||||
|
override val lang: String,
|
||||||
|
) : HttpSource() {
|
||||||
|
|
||||||
override val name get() = "包子漫画导航"
|
|
||||||
override val lang get() = "zh"
|
|
||||||
override val supportsLatest get() = true
|
override val supportsLatest get() = true
|
||||||
|
|
||||||
override val baseUrl: String
|
private val enableGenres = true
|
||||||
private val baseHttpUrl: HttpUrl
|
|
||||||
private val enableGenres: Boolean
|
|
||||||
|
|
||||||
init {
|
override fun headersBuilder() = super.headersBuilder().add("Referer", "$baseUrl/")
|
||||||
val mirrors = MIRRORS
|
|
||||||
val mirrorIndex = Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
|
||||||
.getString(MIRROR_PREF, "0")!!.toInt().coerceAtMost(mirrors.size - 1)
|
|
||||||
baseUrl = "https://" + mirrors[mirrorIndex]
|
|
||||||
baseHttpUrl = baseUrl.toHttpUrl()
|
|
||||||
enableGenres = mirrorIndex == 0
|
|
||||||
}
|
|
||||||
|
|
||||||
override val client = network.client.newBuilder()
|
override val client = network.cloudflareClient
|
||||||
.addInterceptor(UrlInterceptor)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
private fun getKey(link: String): String {
|
private fun getKey(link: String): String {
|
||||||
val pathSegments = baseHttpUrl.resolve(link)!!.pathSegments
|
return link.substringAfter("/manga/").removeSuffix("/")
|
||||||
val fromIndex = if (pathSegments[0] == "manga") 1 else 0
|
|
||||||
val toIndex = if (pathSegments.last().isEmpty()) pathSegments.size - 1 else pathSegments.size
|
|
||||||
val list = pathSegments.subList(fromIndex, toIndex).toMutableList()
|
|
||||||
list[0] = list[0].split("-").take(2).joinToString("-")
|
|
||||||
return list.joinToString("/")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun popularMangaRequest(page: Int) = GET("$baseUrl/hots/page/$page/", headers)
|
override fun popularMangaRequest(page: Int) = GET("$baseUrl/hots/page/$page", headers)
|
||||||
|
|
||||||
override fun popularMangaParse(response: Response): MangasPage {
|
override fun popularMangaParse(response: Response): MangasPage {
|
||||||
val document = response.asJsoup().also(::parseGenres)
|
val document = response.asJsoup().also(::parseGenres)
|
||||||
val mangas = document.select("article.wp-manga").map { element ->
|
val mangas = document.select(".cardlist .pb-2 a").map { element ->
|
||||||
SManga.create().apply {
|
SManga.create().apply {
|
||||||
val link = element.selectFirst(Evaluator.Tag("h2"))!!.child(0)
|
val imgSrc = element.selectFirst("img")!!.attr("src")
|
||||||
url = getKey(link.attr("href"))
|
url = getKey(element.attr("href"))
|
||||||
title = link.ownText()
|
title = element.selectFirst("h3")!!.ownText()
|
||||||
thumbnail_url = element.selectFirst(Evaluator.Tag("img"))!!.imgSrc
|
thumbnail_url = if ("url=" in imgSrc) imgSrc.toHttpUrl().queryParameter("url")!! else imgSrc
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val hasNextPage = document.selectFirst(Evaluator.Class("next"))?.tagName() == "a" ||
|
val nextPage = if (lang == "zh") "下一頁" else "NEXT"
|
||||||
document.selectFirst(".gb-button[aria-label=Next page]") != null
|
val hasNextPage = document.selectFirst("a[aria-label=$nextPage] button") != null
|
||||||
return MangasPage(mangas, hasNextPage)
|
return MangasPage(mangas, hasNextPage)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/newss/page/$page/", headers)
|
override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/newss/page/$page", headers)
|
||||||
|
|
||||||
override fun latestUpdatesParse(response: Response) = popularMangaParse(response)
|
override fun latestUpdatesParse(response: Response) = popularMangaParse(response)
|
||||||
|
|
||||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||||
if (query.isNotEmpty()) {
|
if (query.isNotEmpty()) {
|
||||||
val url = "$baseUrl/page/$page/".toHttpUrl().newBuilder()
|
val url = "$baseUrl/s".toHttpUrl().newBuilder()
|
||||||
.addQueryParameter("s", query)
|
.addPathSegment(query)
|
||||||
return Request.Builder().url(url.build()).headers(headers).build()
|
.addEncodedQueryParameter("page", "$page")
|
||||||
|
.build()
|
||||||
|
return GET(url, headers)
|
||||||
}
|
}
|
||||||
for (filter in filters) {
|
for (filter in filters) {
|
||||||
if (filter is UriPartFilter) return GET(baseUrl + filter.toUriPart() + "page/$page/", headers)
|
if (filter is UriPartFilter) return GET(baseUrl + filter.toUriPart() + "/page/$page", headers)
|
||||||
}
|
}
|
||||||
return popularMangaRequest(page)
|
return popularMangaRequest(page)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun searchMangaParse(response: Response) = popularMangaParse(response)
|
override fun searchMangaParse(response: Response) = popularMangaParse(response)
|
||||||
|
|
||||||
|
override fun getMangaUrl(manga: SManga) = "$baseUrl/manga/${manga.url}"
|
||||||
|
|
||||||
override fun mangaDetailsRequest(manga: SManga): Request {
|
override fun mangaDetailsRequest(manga: SManga): Request {
|
||||||
val url = manga.url
|
return GET(getMangaUrl(manga), headers)
|
||||||
if (url[0] == '/') throw Exception(MIGRATE)
|
|
||||||
return GET("$baseUrl/manga/$url/", headers)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun Document.getMangaId() = selectFirst("#mangachapters")!!.attr("data-mid")
|
||||||
|
|
||||||
override fun mangaDetailsParse(response: Response) = SManga.create().apply {
|
override fun mangaDetailsParse(response: Response) = SManga.create().apply {
|
||||||
val document = response.asJsoup()
|
val document = response.asJsoup()
|
||||||
title = document.selectFirst(Evaluator.Tag("h1"))!!.ownText()
|
val titleElement = document.selectFirst("h1")!!
|
||||||
author = document.selectFirst(Evaluator.Class("author-content"))!!.children().joinToString { it.ownText() }
|
val elements = titleElement.parent()!!.parent()!!.children()
|
||||||
description = document.selectFirst(".descrip_manga_info, .wp-block-stackable-text")!!.text()
|
check(elements.size == 6)
|
||||||
thumbnail_url = document.selectFirst("img.wp-post-image")!!.imgSrc
|
|
||||||
|
|
||||||
val genreList = document.selectFirst(Evaluator.Class("genres-content"))!!
|
title = titleElement.ownText()
|
||||||
.children().eachText().toMutableSet()
|
status = SManga.UNKNOWN // Everything is marked as ongoing
|
||||||
if ("连载中" in genreList) {
|
author = Entities.unescape(elements[1].children().drop(1).joinToString { it.text().removeSuffix(" ,") })
|
||||||
genreList.remove("连载中")
|
genre = buildList {
|
||||||
status = SManga.ONGOING
|
elements[2].children().drop(1).mapTo(this) { it.text().removeSuffix(" ,") }
|
||||||
} else if ("已完结" in genreList) {
|
elements[3].children().mapTo(this) { it.text().removePrefix("#") }
|
||||||
genreList.remove("已完结")
|
}.joinToString()
|
||||||
status = SManga.COMPLETED
|
description = elements[4].text() + "\n\nID: ${document.getMangaId()}"
|
||||||
}
|
thumbnail_url = document.selectFirst("img.object-cover")!!.attr("src")
|
||||||
genre = genreList.joinToString()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun chapterListRequest(manga: SManga): Request {
|
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> = Observable.fromCallable {
|
||||||
val url = manga.url
|
val mangaId = manga.description
|
||||||
if (url[0] == '/') throw Exception(MIGRATE)
|
?.substringAfterLast("\nID: ", "")
|
||||||
return GET("$baseUrl/chapterlist/$url/", headers)
|
?.takeIf { it.isNotEmpty() && it.all(Character::isDigit) }
|
||||||
|
?: client.newCall(mangaDetailsRequest(manga)).execute().asJsoup().getMangaId()
|
||||||
|
|
||||||
|
fetchChapterList(mangaId)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun chapterListParse(response: Response): List<SChapter> {
|
override fun chapterListParse(response: Response): List<SChapter> {
|
||||||
val document = response.asJsoup()
|
throw UnsupportedOperationException()
|
||||||
return document.selectFirst(Evaluator.Class("version-chaps"))!!.children().map {
|
}
|
||||||
|
|
||||||
|
open fun fetchChapterList(mangaId: String): List<SChapter> {
|
||||||
|
val response = client.newCall(GET("$baseUrl/manga/get?mid=$mangaId&mode=all", headers)).execute()
|
||||||
|
|
||||||
|
return response.asJsoup().select(".chapteritem").asReversed().map { element ->
|
||||||
|
val anchor = element.selectFirst("a")!!
|
||||||
SChapter.create().apply {
|
SChapter.create().apply {
|
||||||
url = getKey(it.attr("href"))
|
url = getKey(anchor.attr("href")) + "#$mangaId/" + anchor.attr("data-cs")
|
||||||
name = it.ownText()
|
name = anchor.attr("data-ct")
|
||||||
date_upload = parseChapterDate(it.child(0).text())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getChapterUrl(chapter: SChapter) = "$baseUrl/manga/" + chapter.url.substringBeforeLast('#')
|
||||||
|
|
||||||
override fun pageListRequest(chapter: SChapter): Request {
|
override fun pageListRequest(chapter: SChapter): Request {
|
||||||
val url = chapter.url
|
val id = chapter.url.substringAfterLast('#', "")
|
||||||
if (url[0] == '/') throw Exception(MIGRATE)
|
val mangaId = id.substringBefore('/')
|
||||||
return GET("$baseUrl/manga/$url/", headers)
|
val chapterId = id.substringAfter('/')
|
||||||
|
return pageListRequest(mangaId, chapterId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
open fun pageListRequest(mangaId: String, chapterId: String) = GET("$baseUrl/chapter/getcontent?m=$mangaId&c=$chapterId", headers)
|
||||||
|
|
||||||
override fun pageListParse(response: Response): List<Page> {
|
override fun pageListParse(response: Response): List<Page> {
|
||||||
val document = response.asJsoup()
|
val document = response.asJsoup()
|
||||||
// Jsoup won't ignore duplicates inside <noscript> tag
|
return document.select("noscript > img").mapIndexed { index, element ->
|
||||||
document.select(Evaluator.Tag("noscript")).remove()
|
Page(index, imageUrl = element.attr("src"))
|
||||||
return document.select("img[decoding=async]").mapIndexed { index, element ->
|
|
||||||
Page(index, imageUrl = element.imgSrc)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -156,26 +145,23 @@ class BaozimhOrg : HttpSource(), ConfigurableSource {
|
|||||||
|
|
||||||
private fun parseGenres(document: Document) {
|
private fun parseGenres(document: Document) {
|
||||||
if (!enableGenres || genres.isNotEmpty()) return
|
if (!enableGenres || genres.isNotEmpty()) return
|
||||||
val box = document.selectFirst(Evaluator.Class("wp-block-navigation__container")) ?: return
|
val box = document.selectFirst("h2")?.parent()?.parent() ?: return
|
||||||
val items = box.children()
|
val items = box.select("a")
|
||||||
genres = buildList(items.size + 1) {
|
genres = Array(items.size) { i ->
|
||||||
add(Pair("全部", "/allmanga/"))
|
val item = items[i]
|
||||||
items.mapTo(this) {
|
Pair(item.text().removePrefix("#"), item.attr("href"))
|
||||||
val link = it.child(0)
|
}
|
||||||
Pair(link.text(), link.attr("href"))
|
|
||||||
}
|
|
||||||
}.toTypedArray()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getFilterList(): FilterList =
|
override fun getFilterList(): FilterList =
|
||||||
if (!enableGenres) {
|
if (!enableGenres) {
|
||||||
FilterList()
|
FilterList()
|
||||||
} else if (genres.isEmpty()) {
|
} else if (genres.isEmpty()) {
|
||||||
FilterList(listOf(Filter.Header("点击“重置”刷新分类")))
|
FilterList(listOf(Filter.Header(if (lang == "zh") "点击“重置”刷新分类" else "Tap 'Reset' to load genres")))
|
||||||
} else {
|
} else {
|
||||||
val list = listOf(
|
val list = listOf(
|
||||||
Filter.Header("分类(搜索文本时无效)"),
|
Filter.Header(if (lang == "zh") "分类(搜索文本时无效)" else "Filters are ignored when using text search."),
|
||||||
UriPartFilter("分类", genres),
|
UriPartFilter(if (lang == "zh") "分类" else "Genre", genres),
|
||||||
)
|
)
|
||||||
FilterList(list)
|
FilterList(list)
|
||||||
}
|
}
|
||||||
@ -184,33 +170,4 @@ class BaozimhOrg : HttpSource(), ConfigurableSource {
|
|||||||
Filter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) {
|
Filter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) {
|
||||||
fun toUriPart() = vals[state].second
|
fun toUriPart() = vals[state].second
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setupPreferenceScreen(screen: PreferenceScreen) {
|
|
||||||
ListPreference(screen.context).apply {
|
|
||||||
val mirrors = MIRRORS
|
|
||||||
key = MIRROR_PREF
|
|
||||||
title = "镜像网址"
|
|
||||||
summary = "%s\n重启生效,暂未适配GoDa漫画的分类筛选功能"
|
|
||||||
entries = mirrors
|
|
||||||
entryValues = Array(mirrors.size) { it.toString() }
|
|
||||||
setDefaultValue("0")
|
|
||||||
}.let(screen::addPreference)
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private const val MIRROR_PREF = "MIRROR"
|
|
||||||
private val MIRRORS get() = arrayOf("baozimh.org", "cn.godamanga.com")
|
|
||||||
|
|
||||||
const val MIGRATE = "请将此漫画重新迁移到本图源"
|
|
||||||
|
|
||||||
val Element.imgSrc: String get() = attr("data-src").ifEmpty { attr("src") }
|
|
||||||
|
|
||||||
private val dateFormat by lazy { SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH) }
|
|
||||||
|
|
||||||
fun parseChapterDate(text: String): Long = try {
|
|
||||||
dateFormat.parse(text)!!.time
|
|
||||||
} catch (_: Throwable) {
|
|
||||||
0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,61 @@
|
|||||||
|
package eu.kanade.tachiyomi.extension.zh.baozimhorg
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.source.model.Page
|
||||||
|
import eu.kanade.tachiyomi.source.model.SChapter
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.Locale
|
||||||
|
import java.util.TimeZone
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class ResponseDto<T>(val data: T)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class ChapterListDto(
|
||||||
|
private val id: Int,
|
||||||
|
private val slug: String,
|
||||||
|
private val chapters: List<ChapterDto>,
|
||||||
|
) {
|
||||||
|
fun toChapterList(): List<SChapter> {
|
||||||
|
val mangaId = id.toString()
|
||||||
|
val mangaSlug = slug
|
||||||
|
return chapters.asReversed().map { it.toSChapter(mangaSlug, mangaId) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class ChapterDto(
|
||||||
|
private val id: Int,
|
||||||
|
private val attributes: AttributesDto,
|
||||||
|
) {
|
||||||
|
fun toSChapter(mangaSlug: String, mangaId: String) = attributes.toSChapter(mangaSlug, mangaId, id.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class AttributesDto(
|
||||||
|
private val title: String,
|
||||||
|
private val slug: String,
|
||||||
|
private val updatedAt: String,
|
||||||
|
) {
|
||||||
|
fun toSChapter(mangaSlug: String, mangaId: String, chapterId: String) = SChapter.create().apply {
|
||||||
|
url = "$mangaSlug/$slug#$mangaId/$chapterId"
|
||||||
|
name = title
|
||||||
|
date_upload = dateFormat.parse(updatedAt)!!.time
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Static field, no need for lazy
|
||||||
|
private val dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US).apply {
|
||||||
|
timeZone = TimeZone.getTimeZone("UTC")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class PageListDto(val info: PageListInfoDto)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class PageListInfoDto(val images: List<ImageDto>)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class ImageDto(private val url: String, private val order: Int) {
|
||||||
|
fun toPage() = Page(order, imageUrl = url)
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
package eu.kanade.tachiyomi.extension.zh.baozimhorg
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.source.SourceFactory
|
||||||
|
|
||||||
|
// This is not used because ideally the extension language should be updated to "Multi" (all).
|
||||||
|
// Chinese users don't receive status updates from Discord, so I'll keep the package name unchanged for now.
|
||||||
|
class GoDaFactory : SourceFactory {
|
||||||
|
override fun createSources() = listOf(
|
||||||
|
GoDaManhua(),
|
||||||
|
BaozimhOrg("Goda", "https://manhuascans.org", "en"),
|
||||||
|
)
|
||||||
|
}
|
@ -0,0 +1,79 @@
|
|||||||
|
package eu.kanade.tachiyomi.extension.zh.baozimhorg
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import androidx.preference.ListPreference
|
||||||
|
import androidx.preference.PreferenceScreen
|
||||||
|
import eu.kanade.tachiyomi.network.GET
|
||||||
|
import eu.kanade.tachiyomi.source.ConfigurableSource
|
||||||
|
import eu.kanade.tachiyomi.source.model.Page
|
||||||
|
import eu.kanade.tachiyomi.source.model.SChapter
|
||||||
|
import kotlinx.serialization.decodeFromString
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import okhttp3.Interceptor
|
||||||
|
import okhttp3.Request
|
||||||
|
import okhttp3.Response
|
||||||
|
import okio.IOException
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
|
class GoDaManhua : BaozimhOrg("GoDa漫画", "", "zh"), ConfigurableSource {
|
||||||
|
|
||||||
|
override val id get() = 774030471139699415
|
||||||
|
|
||||||
|
override val baseUrl: String
|
||||||
|
|
||||||
|
init {
|
||||||
|
val mirrors = MIRRORS
|
||||||
|
if (System.getenv("CI") == "true") {
|
||||||
|
baseUrl = mirrors.joinToString("#, ") { "https://$it" }
|
||||||
|
} else {
|
||||||
|
val mirrorIndex = Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
||||||
|
.getString(MIRROR_PREF, "0")!!.toInt().coerceAtMost(mirrors.size - 1)
|
||||||
|
baseUrl = "https://" + mirrors[mirrorIndex]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override val client = super.client.newBuilder().addInterceptor(NotFoundInterceptor()).build()
|
||||||
|
|
||||||
|
private val json: Json = Injekt.get()
|
||||||
|
|
||||||
|
override fun fetchChapterList(mangaId: String): List<SChapter> {
|
||||||
|
val response = client.newCall(GET("https://api-get.mgsearcher.com/api/manga/get?mid=$mangaId&mode=all", headers)).execute()
|
||||||
|
return json.decodeFromString<ResponseDto<ChapterListDto>>(response.body.string()).data.toChapterList()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun pageListRequest(mangaId: String, chapterId: String): Request {
|
||||||
|
if (mangaId.isEmpty() || chapterId.isEmpty()) throw Exception("请刷新漫画")
|
||||||
|
return GET("https://api-get.mgsearcher.com/api/chapter/getinfo?m=$mangaId&c=$chapterId", headers)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun pageListParse(response: Response): List<Page> {
|
||||||
|
return json.decodeFromString<ResponseDto<PageListDto>>(response.body.string()).data.info.images.map { it.toPage() }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setupPreferenceScreen(screen: PreferenceScreen) {
|
||||||
|
ListPreference(screen.context).apply {
|
||||||
|
val mirrors = MIRRORS
|
||||||
|
key = MIRROR_PREF
|
||||||
|
title = "镜像网址"
|
||||||
|
summary = "%s\n重启生效"
|
||||||
|
entries = mirrors
|
||||||
|
entryValues = Array(mirrors.size, Int::toString)
|
||||||
|
setDefaultValue("0")
|
||||||
|
}.let(screen::addPreference)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private const val MIRROR_PREF = "MIRROR"
|
||||||
|
|
||||||
|
// https://nav.telltome.net/
|
||||||
|
private val MIRRORS get() = arrayOf("baozimh.org", "godamh.com", "m.baozimh.one")
|
||||||
|
|
||||||
|
private class NotFoundInterceptor : Interceptor {
|
||||||
|
override fun intercept(chain: Interceptor.Chain): Response {
|
||||||
|
val response = chain.proceed(chain.request())
|
||||||
|
if (response.code != 404) return response
|
||||||
|
response.close()
|
||||||
|
throw IOException("请将此漫画重新迁移到本图源")
|
||||||
|
}
|
||||||
|
}
|
@ -1,32 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.extension.zh.baozimhorg
|
|
||||||
|
|
||||||
import okhttp3.Interceptor
|
|
||||||
import okhttp3.Response
|
|
||||||
|
|
||||||
// Temporary interceptor to handle URL redirections
|
|
||||||
object UrlInterceptor : Interceptor {
|
|
||||||
|
|
||||||
override fun intercept(chain: Interceptor.Chain): Response {
|
|
||||||
val request = chain.request()
|
|
||||||
val url = request.url
|
|
||||||
val (type, slug) = url.pathSegments
|
|
||||||
when (type) {
|
|
||||||
"manga", "chapterlist" -> {}
|
|
||||||
else -> return chain.proceed(request)
|
|
||||||
}
|
|
||||||
|
|
||||||
val mangaUrl = "/manga/$slug/"
|
|
||||||
val headRequest = request.newBuilder()
|
|
||||||
.head()
|
|
||||||
.url(url.resolve(mangaUrl)!!)
|
|
||||||
.build()
|
|
||||||
// might redirect multiple times
|
|
||||||
val headResponse = chain.proceed(headRequest)
|
|
||||||
if (headResponse.priorResponse == null) return chain.proceed(request)
|
|
||||||
|
|
||||||
val realSlug = headResponse.request.url.pathSegments[1]
|
|
||||||
val newUrl = url.newBuilder().setEncodedPathSegment(1, realSlug).build()
|
|
||||||
val newRequest = request.newBuilder().url(newUrl).build()
|
|
||||||
return chain.proceed(newRequest)
|
|
||||||
}
|
|
||||||
}
|
|