Web Comic Gamma: update for new theme (#12393)

* Web Comic Gamma: update for new theme

* preserve old chapter URL

* preserve chapter number
This commit is contained in:
stevenyomi 2022-07-02 07:06:57 +08:00 committed by GitHub
parent e399317bf9
commit a72e10d6a2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 184 additions and 89 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.1 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 216 KiB

After

Width:  |  Height:  |  Size: 57 KiB

View File

@ -0,0 +1,83 @@
package eu.kanade.tachiyomi.extension.ja.webcomicgamma
import eu.kanade.tachiyomi.multisrc.comicgamma.ComicGamma
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import org.jsoup.select.Evaluator
class WebComicGamma : ComicGamma("Web Comic Gamma", "https://webcomicgamma.takeshobo.co.jp") {
override val supportsLatest = false
override fun popularMangaSelector() = ".tab_panel.active .manga_item"
override fun popularMangaFromElement(element: Element) = SManga.create().apply {
url = element.selectFirst(Evaluator.Tag("a")).attr("href")
title = element.selectFirst(Evaluator.Class("manga_title")).text()
author = element.selectFirst(Evaluator.Class("manga_author")).text()
val genreList = element.select(Evaluator.Tag("li")).map { it.text() }
genre = genreList.joinToString()
status = when {
genreList.contains("完結") && !genreList.contains("リピート配信") -> SManga.COMPLETED
else -> SManga.ONGOING
}
thumbnail_url = element.selectFirst(Evaluator.Tag("img")).absUrl("src")
}
override fun latestUpdatesRequest(page: Int) = throw UnsupportedOperationException("Not used.")
override fun latestUpdatesNextPageSelector() = throw UnsupportedOperationException("Not used.")
override fun latestUpdatesSelector() = throw UnsupportedOperationException("Not used.")
override fun latestUpdatesFromElement(element: Element) = throw UnsupportedOperationException("Not used.")
override fun mangaDetailsParse(document: Document): SManga {
val titleElement = document.selectFirst(Evaluator.Class("manga__title"))
val titleName = titleElement.child(0).text()
val desc = document.selectFirst(".detail__item > p")?.run {
select(Evaluator.Tag("br")).prepend("\\n")
this.text().replace("\\n", "\n").replace("\n ", "\n")
}
val listResponse = client.newCall(popularMangaRequest(0)).execute()
val manga = popularMangaParse(listResponse).mangas.find { it.title == titleName }
return manga?.apply { description = desc } ?: SManga.create().apply {
author = titleElement.child(1).text()
description = desc
status = SManga.UNKNOWN
val slug = document.location().removeSuffix("/").substringAfterLast("/")
thumbnail_url = "$baseUrl/img/manga_thumb/${slug}_list.jpg"
}
}
override fun chapterListSelector() = ".read__area > .read__outer > a"
override fun chapterFromElement(element: Element) = SChapter.create().apply {
url = element.attr("href").toOldChapterUrl()
val number = url.removeSuffix("/").substringAfterLast('/').replace('_', '.')
val list = element.selectFirst(Evaluator.Class("read__contents")).children()
name = "[$number] ${list[0].text()}"
if (list.size >= 3) {
date_upload = dateFormat.parse(list[2].text())?.time ?: 0L
}
if (date_upload <= 0L) date_upload = -1L // hide unknown ones
}
override fun pageListRequest(chapter: SChapter) =
GET(baseUrl + chapter.url.toNewChapterUrl(), headers)
companion object {
private val dateFormat by lazy { getJSTFormat("yyyy年M月dd日") }
private fun String.toOldChapterUrl(): String {
// ../../../_files/madeinabyss/063_2/
val segments = split('/')
val size = segments.size
val slug = segments[size - 3]
val number = segments[size - 2]
return "/manga/$slug/_files/$number/"
}
private fun String.toNewChapterUrl(): String {
val segments = split('/')
return "/_files/${segments[2]}/${segments[4]}/"
}
}
}

View File

@ -0,0 +1,77 @@
package eu.kanade.tachiyomi.extension.ja.webcomicgammaplus
import eu.kanade.tachiyomi.multisrc.comicgamma.ComicGamma
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import java.text.DateFormat
class WebComicGammaPlus : ComicGamma("Web Comic Gamma Plus", "https://gammaplus.takeshobo.co.jp") {
override val supportsLatest = true
override fun popularMangaSelector() = ".work_list li"
override fun popularMangaFromElement(element: Element) = SManga.create().apply {
setUrlWithoutDomain(element.select("a").attr("abs:href"))
val image = element.select("img")
title = image.attr("alt").removePrefix("").removeSuffix("』作品ページへ")
thumbnail_url = image.attr("abs:src")
}
override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/", headers)
override fun latestUpdatesNextPageSelector(): String? = null
override fun latestUpdatesSelector() = ".whatsnew li:contains(読む)"
override fun latestUpdatesFromElement(element: Element) = SManga.create().apply {
val url = element.select(".show_detail").attr("abs:href")
setUrlWithoutDomain(url)
title = element.select("h3").textNodes()[0].text()
thumbnail_url = url + "main.jpg"
}
override fun mangaDetailsParse(document: Document) = SManga.create().apply {
title = document.select("h1").text()
author = document.select(".author").text()
description = document.select(".work_sammary").text()
val nextDate = document.select(".episode_caption:contains(【次回更新】)")
if (nextDate.isNotEmpty()) {
val dateStr = nextDate.textNodes()
.filter { it.text().contains("【次回更新】") }[0]
.text().trim().removePrefix("【次回更新】")
val localDate = JST_FORMAT_DESC.parseJST(dateStr)?.let { LOCAL_FORMAT_DESC.format(it) }
if (localDate != null) description = "【Next/Repeat: $localDate\n$description"
}
}
override fun chapterListSelector() =
".box_episode > .box_episode_L:contains(読む), .box_episode > .box_episode_M:contains(読む)" // filter out purchase links
override fun chapterFromElement(element: Element) = SChapter.create().apply {
val url = element.select("a[id^=read]").attr("abs:href")
setUrlWithoutDomain(url)
val chapterNumber = url.removeSuffix("/").substringAfterLast('/').replace('_', '.')
val title = element.select(".episode_title").textNodes().filterNot {
it.text().contains("集中連載") || it.text().contains("配信中!!")
}.joinToString("") { it.text() }
name = "$chapterNumber $title"
element.select(".episode_caption").textNodes().forEach {
if (it.text().contains("【公開日】")) {
val date = it.text().trim().removePrefix("【公開日】")
date_upload = JST_FORMAT_LIST.parseJST(date)!!.time
} else if (it.text().contains("【次回更新】")) {
val date = it.text().trim().removePrefix("【次回更新】")
val localDate = JST_FORMAT_LIST.parseJST(date)?.let { LOCAL_FORMAT_LIST.format(it) }
if (localDate != null) scanlator = "~$localDate"
}
}
if (date_upload <= 0L) date_upload = -1L // hide unknown ones
}
companion object {
private const val datePattern = "yyyy年M月dd日(E)"
private val JST_FORMAT_DESC by lazy { getJSTFormat(datePattern) }
private val JST_FORMAT_LIST by lazy { getJSTFormat(datePattern) }
private val LOCAL_FORMAT_DESC by lazy { DateFormat.getDateTimeInstance() }
private val LOCAL_FORMAT_LIST by lazy { DateFormat.getDateTimeInstance() }
}
}

View File

@ -4,46 +4,24 @@ import eu.kanade.tachiyomi.network.GET
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 org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import rx.Observable
import java.text.DateFormat.getDateTimeInstance
import java.text.SimpleDateFormat
import java.util.Locale
import java.util.TimeZone
open class ComicGamma(
abstract class ComicGamma(
override val name: String,
override val baseUrl: String,
override val lang: String = "ja",
) : ParsedHttpSource() {
override val supportsLatest = true
override val client = network.client.newBuilder().addInterceptor(PtImgInterceptor).build()
override fun popularMangaRequest(page: Int) = GET("$baseUrl/manga/", headers)
override fun popularMangaNextPageSelector(): String? = null
override fun popularMangaSelector() = ".work_list li"
override fun popularMangaFromElement(element: Element) = SManga.create().apply {
setUrlWithoutDomain(element.select("a").attr("abs:href"))
val image = element.select("img")
title = image.attr("alt").removePrefix("").removeSuffix("』作品ページへ")
thumbnail_url = image.attr("abs:src")
}
override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/", headers)
override fun latestUpdatesNextPageSelector(): String? = null
override fun latestUpdatesSelector() = ".whatsnew li:contains(読む)"
override fun latestUpdatesFromElement(element: Element) = SManga.create().apply {
val url = element.select(".show_detail").attr("abs:href")
setUrlWithoutDomain(url)
title = element.select("h3").textNodes()[0].text()
thumbnail_url = url + "main.jpg"
}
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> =
fetchPopularManga(page).map { p -> MangasPage(p.mangas.filter { it.title.contains(query) }, false) }
@ -54,44 +32,6 @@ open class ComicGamma(
override fun searchMangaRequest(page: Int, query: String, filters: FilterList) =
throw UnsupportedOperationException("Not used.")
override fun mangaDetailsParse(document: Document) = SManga.create().apply {
title = document.select("h1").text()
author = document.select(".author").text()
description = document.select(".work_sammary").text()
val nextDate = document.select(".episode_caption:contains(【次回更新】)")
if (nextDate.isNotEmpty()) {
val dateStr = nextDate.textNodes()
.filter { it.text().contains("【次回更新】") }[0]
.text().trim().removePrefix("【次回更新】")
val localDate = JST_FORMAT_DESC.parseJST(dateStr)?.let { LOCAL_FORMAT_DESC.format(it) }
if (localDate != null) description = "【Next/Repeat: $localDate\n$description"
}
}
override fun chapterListSelector() =
".box_episode > .box_episode_L:contains(読む), .box_episode > .box_episode_M:contains(読む)" // filter out purchase links
override fun chapterFromElement(element: Element) = SChapter.create().apply {
val url = element.select("a[id^=read]").attr("abs:href")
setUrlWithoutDomain(url)
val chapterNumber = url.removeSuffix("/").substringAfterLast('/').replace('_', '.')
val title = element.select(".episode_title").textNodes().filterNot {
it.text().contains("集中連載") || it.text().contains("配信中!!")
}.joinToString("") { it.text() }
name = "$chapterNumber $title"
element.select(".episode_caption").textNodes().forEach {
if (it.text().contains("【公開日】")) {
val date = it.text().trim().removePrefix("【公開日】")
date_upload = JST_FORMAT_LIST.parseJST(date)!!.time
} else if (it.text().contains("【次回更新】")) {
val date = it.text().trim().removePrefix("【次回更新】")
val localDate = JST_FORMAT_LIST.parseJST(date)?.let { LOCAL_FORMAT_LIST.format(it) }
if (localDate != null) scanlator = "~$localDate"
}
}
if (date_upload <= 0L) date_upload = -1L // hide unknown ones
}
override fun pageListParse(document: Document) =
document.select("#content > div[data-ptimg]").mapIndexed { i, e ->
Page(i, imageUrl = e.attr("abs:data-ptimg"))
@ -99,18 +39,14 @@ open class ComicGamma(
override fun imageUrlParse(document: Document) = throw UnsupportedOperationException("Not used.")
// for thread-safety
private val JST_FORMAT_DESC = getJSTFormat()
private val JST_FORMAT_LIST = getJSTFormat()
private val LOCAL_FORMAT_DESC = getDateTimeInstance()
private val LOCAL_FORMAT_LIST = getDateTimeInstance()
private fun SimpleDateFormat.parseJST(date: String) = parse(date)?.apply {
time += 12 * 3600 * 1000 // updates at 12 noon
}
private fun getJSTFormat() =
SimpleDateFormat("yyyy年M月dd日(E)", Locale.JAPANESE).apply {
timeZone = TimeZone.getTimeZone("GMT+09:00")
companion object {
internal fun SimpleDateFormat.parseJST(date: String) = parse(date)?.apply {
time += 12 * 3600 * 1000 // updates at 12 noon
}
internal fun getJSTFormat(datePattern: String) =
SimpleDateFormat(datePattern, Locale.JAPANESE).apply {
timeZone = TimeZone.getTimeZone("GMT+09:00")
}
}
}

View File

@ -16,7 +16,7 @@ class ComicGammaGenerator : ThemeSourceGenerator {
className = "WebComicGamma",
pkgName = "webcomicgamma",
sourceName = "Web Comic Gamma",
overrideVersionCode = 0,
overrideVersionCode = 1,
),
SingleLang(
name = "Web Comic Gamma Plus",

View File

@ -5,24 +5,22 @@ import kotlinx.serialization.Serializable
val COORD_REGEX = Regex("""^i:(\d+),(\d+)\+(\d+),(\d+)>(\d+),(\d+)$""")
@Serializable
data class PtImg(val resources: Resource, val views: List<View>) {
class PtImg(val resources: Resource, val views: List<View>) {
fun getFilename() = resources.i.src
fun getViewSize() = Pair(views[0].width, views[0].height)
fun getTranslations() = views[0].coords.map(::toTranslation)
private fun toTranslation(coord: String): Translation {
val v = COORD_REGEX.matchEntire(coord)!!.destructured.toList().map(String::toInt)
return Translation(v[0], v[1], v[2], v[3], v[4], v[5])
fun getTranslations() = views[0].coords.map { coord ->
val v = COORD_REGEX.matchEntire(coord)!!.groupValues.drop(1).map { it.toInt() }
Translation(v[0], v[1], v[2], v[3], v[4], v[5])
}
}
@Serializable
data class Resource(val i: Image)
class Resource(val i: Image)
@Serializable
data class Image(val src: String, val width: Int, val height: Int)
class Image(val src: String, val width: Int, val height: Int)
@Serializable
data class View(val width: Int, val height: Int, val coords: List<String>)
class View(val width: Int, val height: Int, val coords: List<String>)
data class Translation(val ix: Int, val iy: Int, val w: Int, val h: Int, val vx: Int, val vy: Int)
class Translation(val ix: Int, val iy: Int, val w: Int, val h: Int, val vx: Int, val vy: Int)

View File

@ -20,12 +20,13 @@ object PtImgInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
val response = chain.proceed(request)
val url = request.url.toString()
if (!url.endsWith(".ptimg.json")) return response
val url = request.url
val path = url.pathSegments
if (!path.last().endsWith(".ptimg.json")) return response
val metadata = json.decodeFromString<PtImg>(response.body!!.string())
val imgRequest = request.newBuilder()
.url(url.replaceAfterLast('/', metadata.getFilename())).build()
val imageUrl = url.newBuilder().setEncodedPathSegment(path.size - 1, metadata.getFilename()).build()
val imgRequest = request.newBuilder().url(imageUrl).build()
val imgResponse = chain.proceed(imgRequest)
val image = BitmapFactory.decodeStream(imgResponse.body!!.byteStream())
val (width, height) = metadata.getViewSize()