Jinmantiantang: update URL, cleanup and refactor (#12184)

* Jinmantiantang: extract SimpleDateFormat instance

* Update URL and bump version

* Cleanup for preferences

* Refactor manga list parse

* some additional cleanups

* fix manga list parsing

Co-authored-by: kasperskier <95685115+kasperskier@users.noreply.github.com>
Co-authored-by: steven-ya <95685115+steven-ya@users.noreply.github.com>
This commit is contained in:
stevenyomi 2022-06-18 21:40:11 +08:00 committed by GitHub
parent 55b52dc0ea
commit 5cf8a7cb9b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 66 additions and 71 deletions

View File

@ -27,7 +27,7 @@
android:pathPattern="/album/..*" android:pathPattern="/album/..*"
android:scheme="https" /> android:scheme="https" />
<data <data
android:host="jmcomic1.asia" android:host="jmcomic.city"
android:pathPattern="/album/..*" android:pathPattern="/album/..*"
android:scheme="https" /> android:scheme="https" />
</intent-filter> </intent-filter>

View File

@ -1,4 +1,3 @@
apply plugin: 'com.android.application' apply plugin: 'com.android.application'
apply plugin: 'kotlin-android' apply plugin: 'kotlin-android'
@ -6,7 +5,7 @@ ext {
extName = 'Jinmantiantang' extName = 'Jinmantiantang'
pkgNameSuffix = 'zh.jinmantiantang' pkgNameSuffix = 'zh.jinmantiantang'
extClass = '.Jinmantiantang' extClass = '.Jinmantiantang'
extVersionCode = 27 extVersionCode = 28
isNsfw = true isNsfw = true
} }

View File

@ -3,6 +3,7 @@ package eu.kanade.tachiyomi.extension.zh.jinmantiantang
import android.app.Application import android.app.Application
import android.content.SharedPreferences import android.content.SharedPreferences
import androidx.preference.EditTextPreference import androidx.preference.EditTextPreference
import androidx.preference.ListPreference
import androidx.preference.PreferenceScreen import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.asObservableSuccess import eu.kanade.tachiyomi.network.asObservableSuccess
@ -16,7 +17,6 @@ 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 eu.kanade.tachiyomi.util.asJsoup
import okhttp3.HttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
@ -30,28 +30,26 @@ import uy.kohesive.injekt.api.get
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Locale import java.util.Locale
class Jinmantiantang : ConfigurableSource, ParsedHttpSource() { class Jinmantiantang : ParsedHttpSource(), ConfigurableSource {
override val lang: String = "zh" override val lang: String = "zh"
override val name: String = "禁漫天堂" override val name: String = "禁漫天堂"
override val supportsLatest: Boolean = true override val supportsLatest: Boolean = true
private val preferences: SharedPreferences by lazy { private val preferences: SharedPreferences =
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000) Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
}
override val baseUrl: String = "https://" + override val baseUrl: String = "https://" +
SITE_ENTRIES_ARRAY[preferences.getString(USE_MIRROR_URL_PREF, "0")!!.toInt()] SITE_ENTRIES_ARRAY[preferences.getString(USE_MIRROR_URL_PREF, "0")!!.toInt()]
private val baseHttpUrl: HttpUrl = baseUrl.toHttpUrlOrNull()!!
// 处理URL请求 // 处理URL请求
override val client: OkHttpClient = network.cloudflareClient override val client: OkHttpClient = network.cloudflareClient
.newBuilder() .newBuilder()
// Add rate limit to fix manga thumbnail load failure // Add rate limit to fix manga thumbnail load failure
.rateLimitHost( .rateLimitHost(
baseHttpUrl, baseUrl.toHttpUrlOrNull()!!,
preferences.getString(MAINSITE_RATELIMIT_PREF, "1")!!.toInt(), preferences.getString(MAINSITE_RATELIMIT_PREF, MAINSITE_RATELIMIT_PREF_DEFAULT)!!.toInt(),
preferences.getString(MAINSITE_RATELIMIT_PERIOD, "3")!!.toLong(), preferences.getString(MAINSITE_RATELIMIT_PERIOD, MAINSITE_RATELIMIT_PERIOD_DEFAULT)!!.toLong(),
) )
.addInterceptor(ScrambledImageInterceptor).build() .addInterceptor(ScrambledImageInterceptor).build()
@ -62,21 +60,32 @@ class Jinmantiantang : ConfigurableSource, ParsedHttpSource() {
override fun popularMangaNextPageSelector(): String = "a.prevnext" override fun popularMangaNextPageSelector(): String = "a.prevnext"
override fun popularMangaSelector(): String { override fun popularMangaSelector(): String {
val baseSelector = "div.list-col:not([style])" return "div.list-col:not([style])"
}
private fun List<SManga>.filterGenre(): List<SManga> {
val removedGenres = preferences.getString("BLOCK_GENRES_LIST", "")!!.substringBefore("//").trim() val removedGenres = preferences.getString("BLOCK_GENRES_LIST", "")!!.substringBefore("//").trim()
// Extra selector is jquery-like selector, it uses regex to match element.text(). if (removedGenres.isEmpty()) return this
// If string after 標籤 contains any word of removedGenres, the element would be ignored. val removedList = removedGenres.lowercase().split(' ')
return if (removedGenres != "") return this.filterNot { manga ->
baseSelector + ":not(:matches((?i).*標籤: .*(${removedGenres.split(' ').joinToString("|")}).*))" manga.genre.orEmpty().lowercase().split(", ").any { removedList.contains(it) }
else }
baseSelector
} }
override fun popularMangaFromElement(element: Element): SManga = SManga.create().apply { override fun popularMangaFromElement(element: Element): SManga = SManga.create().apply {
title = element.select("span.video-title").text() val children = element.child(0).children()
setUrlWithoutDomain(element.select("a").first().attr("href")) if (children[0].tagName() == "a") children.removeFirst()
thumbnail_url = element.select("img").attr("data-original").split("?")[0] title = children[1].text()
author = element.select("div.title-truncate").select("a").first().text() setUrlWithoutDomain(children[0].selectFirst("a").attr("href"))
val img = children[0].selectFirst("img")
thumbnail_url = img.attr("data-original").ifEmpty { img.attr("src") }.substringBeforeLast('?')
author = children[2].select("a").joinToString(", ") { it.text() }
genre = children[3].select("a").joinToString(", ") { it.text() }
}
override fun popularMangaParse(response: Response): MangasPage {
val page = super.popularMangaParse(response)
return MangasPage(page.mangas.filterGenre(), page.hasNextPage)
} }
// 最新排序 // 最新排序
@ -88,6 +97,11 @@ class Jinmantiantang : ConfigurableSource, ParsedHttpSource() {
override fun latestUpdatesSelector(): String = popularMangaSelector() override fun latestUpdatesSelector(): String = popularMangaSelector()
override fun latestUpdatesFromElement(element: Element): SManga = popularMangaFromElement(element) override fun latestUpdatesFromElement(element: Element): SManga = popularMangaFromElement(element)
override fun latestUpdatesParse(response: Response): MangasPage {
val page = super.latestUpdatesParse(response)
return MangasPage(page.mangas.filterGenre(), page.hasNextPage)
}
// For JinmantiantangUrlActivity // For JinmantiantangUrlActivity
private fun searchMangaByIdRequest(id: String) = GET("$baseUrl/album/$id", headers) private fun searchMangaByIdRequest(id: String) = GET("$baseUrl/album/$id", headers)
@ -110,11 +124,7 @@ class Jinmantiantang : ConfigurableSource, ParsedHttpSource() {
// 查询信息 // 查询信息
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
var params = filters.map { var params = filters.filterIsInstance<UriPartFilter>().joinToString("") { it.toUriPart() }
if (it is UriPartFilter) {
it.toUriPart()
} else ""
}.filter { it != "" }.joinToString("")
val url = if (query != "" && !query.contains("-")) { val url = if (query != "" && !query.contains("-")) {
// 禁漫天堂特有搜索方式: A +B --> A and B, A B --> A or B // 禁漫天堂特有搜索方式: A +B --> A and B, A B --> A or B
@ -144,22 +154,24 @@ class Jinmantiantang : ConfigurableSource, ParsedHttpSource() {
override fun searchMangaSelector(): String = popularMangaSelector() override fun searchMangaSelector(): String = popularMangaSelector()
override fun searchMangaFromElement(element: Element): SManga = popularMangaFromElement(element) override fun searchMangaFromElement(element: Element): SManga = popularMangaFromElement(element)
override fun searchMangaParse(response: Response): MangasPage {
val page = super.searchMangaParse(response)
return MangasPage(page.mangas.filterGenre(), page.hasNextPage)
}
// 漫画详情 // 漫画详情
// url网址 , title标题 , artist艺术家 , author作者 , description描述 , genre类型 , thumbnail_url缩图网址 , initialized是否初始化
// status状态 0未知,1连载,2完结,3领取牌照
override fun mangaDetailsParse(document: Document): SManga = SManga.create().apply { override fun mangaDetailsParse(document: Document): SManga = SManga.create().apply {
title = document.select("div.panel-heading").select("div.pull-left").first().text() title = document.selectFirst("h1").text()
// keep thumbnail_url same as the one in popularMangaFromElement() // keep thumbnail_url same as the one in popularMangaFromElement()
thumbnail_url = document.select("img.lazy_img.img-responsive").attr("src").split("?")[0].replace(".jpg", "_3x4.jpg") thumbnail_url = document.selectFirst(".thumb-overlay > img").attr("src").substringBeforeLast('.') + "_3x4.jpg"
author = selectAuthor(document) author = selectAuthor(document)
artist = author
genre = selectDetailsStatusAndGenre(document, 0).trim().split(" ").joinToString(", ") genre = selectDetailsStatusAndGenre(document, 0).trim().split(" ").joinToString(", ")
// When the index passed by the "selectDetailsStatusAndGenre(document: Document, index: Int)" index is 1, // When the index passed by the "selectDetailsStatusAndGenre(document: Document, index: Int)" index is 1,
// it will definitely return a String type of 0, 1 or 2. This warning can be ignored // it will definitely return a String type of 0, 1 or 2. This warning can be ignored
status = selectDetailsStatusAndGenre(document, 1).trim().toInt() status = selectDetailsStatusAndGenre(document, 1).trim().toInt()
description = document.select("#intro-block .p-t-5.p-b-5").text().substringAfter("敘述:").trim() description = document.selectFirst("#intro-block .p-t-5.p-b-5").text().substringAfter("敘述:").trim()
} }
// 查询作者信息 // 查询作者信息
@ -207,8 +219,9 @@ class Jinmantiantang : ConfigurableSource, ParsedHttpSource() {
// 漫画章节信息 // 漫画章节信息
override fun chapterListSelector(): String = "div[id=episode-block] a[href^=/photo/]" override fun chapterListSelector(): String = "div[id=episode-block] a[href^=/photo/]"
private val sdf = SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH)
override fun chapterFromElement(element: Element): SChapter = SChapter.create().apply { override fun chapterFromElement(element: Element): SChapter = SChapter.create().apply {
val sdf = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())
url = element.select("a").attr("href") url = element.select("a").attr("href")
name = element.select("a li").first().ownText() name = element.select("a li").first().ownText()
date_upload = sdf.parse(element.select("a li span.hidden-xs").text().trim())?.time ?: 0 date_upload = sdf.parse(element.select("a li span.hidden-xs").text().trim())?.time ?: 0
@ -218,7 +231,6 @@ class Jinmantiantang : ConfigurableSource, ParsedHttpSource() {
val document = response.asJsoup() val document = response.asJsoup()
if (document.select("div[id=episode-block] a li").size == 0) { if (document.select("div[id=episode-block] a li").size == 0) {
val singleChapter = SChapter.create().apply { val singleChapter = SChapter.create().apply {
val sdf = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())
name = "单章节" name = "单章节"
url = document.select("a[class=col btn btn-primary dropdown-toggle reading]").attr("href") url = document.select("a[class=col btn btn-primary dropdown-toggle reading]").attr("href")
date_upload = sdf.parse(document.select("div[itemprop='datePublished']").attr("content"))?.time date_upload = sdf.parse(document.select("div[itemprop='datePublished']").attr("content"))?.time
@ -377,45 +389,33 @@ class Jinmantiantang : ConfigurableSource, ParsedHttpSource() {
} }
override fun setupPreferenceScreen(screen: PreferenceScreen) { override fun setupPreferenceScreen(screen: PreferenceScreen) {
val mainSiteRateLimitPreference = androidx.preference.ListPreference(screen.context).apply { val mainSiteRateLimitPreference = ListPreference(screen.context).apply {
key = MAINSITE_RATELIMIT_PREF key = MAINSITE_RATELIMIT_PREF
title = MAINSITE_RATELIMIT_PREF_TITLE title = MAINSITE_RATELIMIT_PREF_TITLE
entries = PREF_ENTRIES_ARRAY entries = PREF_ENTRIES_ARRAY
entryValues = PREF_ENTRIES_ARRAY entryValues = PREF_ENTRIES_ARRAY
summary = MAINSITE_RATELIMIT_PREF_SUMMARY summary = MAINSITE_RATELIMIT_PREF_SUMMARY
setDefaultValue("1") setDefaultValue(MAINSITE_RATELIMIT_PREF_DEFAULT)
setOnPreferenceChangeListener { _, newValue -> setOnPreferenceChangeListener { _, newValue ->
try { preferences.edit().putString(MAINSITE_RATELIMIT_PREF, newValue as String).commit()
val setting = preferences.edit().putString(MAINSITE_RATELIMIT_PREF, newValue as String).commit()
setting
} catch (e: Exception) {
e.printStackTrace()
false
}
} }
} }
val mainSiteRateLimitPeriodPreference = androidx.preference.ListPreference(screen.context).apply { val mainSiteRateLimitPeriodPreference = ListPreference(screen.context).apply {
key = MAINSITE_RATELIMIT_PERIOD key = MAINSITE_RATELIMIT_PERIOD
title = MAINSITE_RATELIMIT_PERIOD_TITLE title = MAINSITE_RATELIMIT_PERIOD_TITLE
entries = PERIOD_ENTRIES_ARRAY entries = PERIOD_ENTRIES_ARRAY
entryValues = PERIOD_ENTRIES_ARRAY entryValues = PERIOD_ENTRIES_ARRAY
summary = MAINSITE_RATELIMIT_PERIOD_SUMMARY summary = MAINSITE_RATELIMIT_PERIOD_SUMMARY
setDefaultValue("3") setDefaultValue(MAINSITE_RATELIMIT_PERIOD_DEFAULT)
setOnPreferenceChangeListener { _, newValue -> setOnPreferenceChangeListener { _, newValue ->
try { preferences.edit().putString(MAINSITE_RATELIMIT_PERIOD, newValue as String).commit()
val setting = preferences.edit().putString(MAINSITE_RATELIMIT_PERIOD, newValue as String).commit()
setting
} catch (e: Exception) {
e.printStackTrace()
false
}
} }
} }
val mirrorURLPreference = androidx.preference.ListPreference(screen.context).apply { val mirrorURLPreference = ListPreference(screen.context).apply {
key = USE_MIRROR_URL_PREF key = USE_MIRROR_URL_PREF
title = USE_MIRROR_URL_PREF_TITLE title = USE_MIRROR_URL_PREF_TITLE
entries = SITE_ENTRIES_ARRAY_DESCRIPTION entries = SITE_ENTRIES_ARRAY_DESCRIPTION
@ -424,37 +424,31 @@ class Jinmantiantang : ConfigurableSource, ParsedHttpSource() {
setDefaultValue("0") setDefaultValue("0")
setOnPreferenceChangeListener { _, newValue -> setOnPreferenceChangeListener { _, newValue ->
try { preferences.edit().putString(USE_MIRROR_URL_PREF, newValue as String).commit()
val setting = preferences.edit().putString(USE_MIRROR_URL_PREF, newValue as String).commit()
setting
} catch (e: Exception) {
e.printStackTrace()
false
}
} }
} }
EditTextPreference(screen.context).apply { val blockGenrePreference = EditTextPreference(screen.context).apply {
key = "BLOCK_GENRES_LIST" key = BLOCK_PREF
title = BLOCK_PREF_TITLE title = BLOCK_PREF_TITLE
setDefaultValue(BLOCK_PREF_DEFAULT) setDefaultValue(BLOCK_PREF_DEFAULT)
dialogTitle = BLOCK_PREF_DIALOGTITLE dialogTitle = BLOCK_PREF_DIALOGTITLE
setOnPreferenceChangeListener { _, newValue -> setOnPreferenceChangeListener { _, newValue ->
preferences.edit().putString("BLOCK_GENRES_LIST", newValue as String).commit() preferences.edit().putString(BLOCK_PREF, newValue as String).commit()
} }
}.let {
screen.addPreference(it)
} }
screen.addPreference(mainSiteRateLimitPreference) screen.addPreference(mainSiteRateLimitPreference)
screen.addPreference(mainSiteRateLimitPeriodPreference) screen.addPreference(mainSiteRateLimitPeriodPreference)
screen.addPreference(mirrorURLPreference) screen.addPreference(mirrorURLPreference)
screen.addPreference(blockGenrePreference)
} }
companion object { companion object {
private const val DEFAULT_SITE = "18comic.vip" private const val DEFAULT_SITE = "18comic.vip"
const val PREFIX_ID_SEARCH = "JM:" const val PREFIX_ID_SEARCH = "JM:"
private const val BLOCK_PREF = "BLOCK_GENRES_LIST"
private const val BLOCK_PREF_TITLE = "屏蔽词列表" private const val BLOCK_PREF_TITLE = "屏蔽词列表"
private const val BLOCK_PREF_DEFAULT = "// 例如 \"YAOI cos 扶他 毛絨絨 獵奇 韩漫 韓漫\", " + private const val BLOCK_PREF_DEFAULT = "// 例如 \"YAOI cos 扶他 毛絨絨 獵奇 韩漫 韓漫\", " +
"关键词之间用空格分离, 大小写不敏感, \"//\"后的字符会被忽略" "关键词之间用空格分离, 大小写不敏感, \"//\"后的字符会被忽略"
@ -463,17 +457,19 @@ class Jinmantiantang : ConfigurableSource, ParsedHttpSource() {
private const val MAINSITE_RATELIMIT_PREF = "mainSiteRateLimitPreference" private const val MAINSITE_RATELIMIT_PREF = "mainSiteRateLimitPreference"
private const val MAINSITE_RATELIMIT_PREF_TITLE = "在限制时间内(下个设置项)允许的请求数量。" // Number of requests allowed within a period of units. private const val MAINSITE_RATELIMIT_PREF_TITLE = "在限制时间内(下个设置项)允许的请求数量。" // Number of requests allowed within a period of units.
private const val MAINSITE_RATELIMIT_PREF_SUMMARY = "此值影响更新书架时发起连接请求的数量。调低此值可能减小IP被屏蔽的几率但加载速度也会变慢。需要重启软件以生效。\n当前值:%s" private const val MAINSITE_RATELIMIT_PREF_SUMMARY = "此值影响更新书架时发起连接请求的数量。调低此值可能减小IP被屏蔽的几率但加载速度也会变慢。需要重启软件以生效。\n当前值:%s"
private val PREF_ENTRIES_ARRAY = (1..10).map { i -> i.toString() }.toTypedArray()
private const val MAINSITE_RATELIMIT_PREF_DEFAULT = 1.toString()
private const val MAINSITE_RATELIMIT_PERIOD = "mainSiteRateLimitPeriodPreference" private const val MAINSITE_RATELIMIT_PERIOD = "mainSiteRateLimitPeriodPreference"
private const val MAINSITE_RATELIMIT_PERIOD_TITLE = "限制持续时间。单位秒" // The limiting duration. Defaults to 3. private const val MAINSITE_RATELIMIT_PERIOD_TITLE = "限制持续时间。单位秒" // The limiting duration. Defaults to 3.
private const val MAINSITE_RATELIMIT_PERIOD_SUMMARY = "此值影响更新书架时请求的间隔时间。调大此值可能减小IP被屏蔽的几率但更新时间也会变慢。需要重启软件以生效。\n当前值:%s" private const val MAINSITE_RATELIMIT_PERIOD_SUMMARY = "此值影响更新书架时请求的间隔时间。调大此值可能减小IP被屏蔽的几率但更新时间也会变慢。需要重启软件以生效。\n当前值:%s"
private val PERIOD_ENTRIES_ARRAY = (1..60).map { i -> i.toString() }.toTypedArray()
private const val MAINSITE_RATELIMIT_PERIOD_DEFAULT = 3.toString()
private const val USE_MIRROR_URL_PREF = "useMirrorWebsitePreference" private const val USE_MIRROR_URL_PREF = "useMirrorWebsitePreference"
private const val USE_MIRROR_URL_PREF_TITLE = "使用镜像网址" private const val USE_MIRROR_URL_PREF_TITLE = "使用镜像网址"
private const val USE_MIRROR_URL_PREF_SUMMARY = "使用镜像网址。需要重启软件以生效。" // "Use mirror url. Defaults to main site" private const val USE_MIRROR_URL_PREF_SUMMARY = "使用镜像网址。需要重启软件以生效。" // "Use mirror url. Defaults to main site"
private val PREF_ENTRIES_ARRAY = (1..10).map { i -> i.toString() }.toTypedArray()
private val PERIOD_ENTRIES_ARRAY = (1..60).map { i -> i.toString() }.toTypedArray()
private val SITE_ENTRIES_ARRAY_DESCRIPTION = arrayOf( private val SITE_ENTRIES_ARRAY_DESCRIPTION = arrayOf(
"主站", "海外分流", "主站", "海外分流",
"中国大陆总站", "中国大陆分流1", "中国大陆分流1" "中国大陆总站", "中国大陆分流1", "中国大陆分流1"
@ -484,7 +480,7 @@ class Jinmantiantang : ConfigurableSource, ParsedHttpSource() {
// Please also update AndroidManifest // Please also update AndroidManifest
private val SITE_ENTRIES_ARRAY = arrayOf( private val SITE_ENTRIES_ARRAY = arrayOf(
DEFAULT_SITE, "18comic.org", DEFAULT_SITE, "18comic.org",
"jmcomic.asia", "jmcomic1.asia", "jmcomic1.asia" "jmcomic.asia", "jmcomic.city", "jmcomic.city"
) )
} }
} }

View File

@ -29,10 +29,10 @@ class JinmantiantangUrlActivity : Activity() {
try { try {
startActivity(mainIntent) startActivity(mainIntent)
} catch (e: ActivityNotFoundException) { } catch (e: ActivityNotFoundException) {
Log.e("JinmantiantangUrlActivity", e.toString()) Log.e("JinmantiantangUrl", e.toString())
} }
} else { } else {
Log.e("JinmantiantangUrlActivity", "could not parse uri from intent $intent") Log.e("JinmantiantangUrl", "could not parse uri from intent $intent")
} }
finish() finish()