diff --git a/multisrc/overrides/comicgamma/webcomicgamma/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/comicgamma/webcomicgamma/res/mipmap-hdpi/ic_launcher.png index 64e3de940..66b158ae8 100644 Binary files a/multisrc/overrides/comicgamma/webcomicgamma/res/mipmap-hdpi/ic_launcher.png and b/multisrc/overrides/comicgamma/webcomicgamma/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/comicgamma/webcomicgamma/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/comicgamma/webcomicgamma/res/mipmap-mdpi/ic_launcher.png index a09c04f57..ebdb3ba56 100644 Binary files a/multisrc/overrides/comicgamma/webcomicgamma/res/mipmap-mdpi/ic_launcher.png and b/multisrc/overrides/comicgamma/webcomicgamma/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/comicgamma/webcomicgamma/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/comicgamma/webcomicgamma/res/mipmap-xhdpi/ic_launcher.png index 8e2e2aa13..10e8972dd 100644 Binary files a/multisrc/overrides/comicgamma/webcomicgamma/res/mipmap-xhdpi/ic_launcher.png and b/multisrc/overrides/comicgamma/webcomicgamma/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/comicgamma/webcomicgamma/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/comicgamma/webcomicgamma/res/mipmap-xxhdpi/ic_launcher.png index 88ccd2622..96d8d433e 100644 Binary files a/multisrc/overrides/comicgamma/webcomicgamma/res/mipmap-xxhdpi/ic_launcher.png and b/multisrc/overrides/comicgamma/webcomicgamma/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/comicgamma/webcomicgamma/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/comicgamma/webcomicgamma/res/mipmap-xxxhdpi/ic_launcher.png index bf3a29b65..1790fcb89 100644 Binary files a/multisrc/overrides/comicgamma/webcomicgamma/res/mipmap-xxxhdpi/ic_launcher.png and b/multisrc/overrides/comicgamma/webcomicgamma/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/comicgamma/webcomicgamma/res/web_hi_res_512.png b/multisrc/overrides/comicgamma/webcomicgamma/res/web_hi_res_512.png index 620440395..d2a131003 100644 Binary files a/multisrc/overrides/comicgamma/webcomicgamma/res/web_hi_res_512.png and b/multisrc/overrides/comicgamma/webcomicgamma/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/comicgamma/webcomicgamma/src/WebComicGamma.kt b/multisrc/overrides/comicgamma/webcomicgamma/src/WebComicGamma.kt new file mode 100644 index 000000000..f6335e394 --- /dev/null +++ b/multisrc/overrides/comicgamma/webcomicgamma/src/WebComicGamma.kt @@ -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]}/" + } + } +} diff --git a/multisrc/overrides/comicgamma/webcomicgammaplus/src/WebComicGammaPlus.kt b/multisrc/overrides/comicgamma/webcomicgammaplus/src/WebComicGammaPlus.kt new file mode 100644 index 000000000..f8d1d55a8 --- /dev/null +++ b/multisrc/overrides/comicgamma/webcomicgammaplus/src/WebComicGammaPlus.kt @@ -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() } + } +} diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/comicgamma/ComicGamma.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/comicgamma/ComicGamma.kt index 16ff2dee8..abd3a641d 100644 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/comicgamma/ComicGamma.kt +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/comicgamma/ComicGamma.kt @@ -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") + } + } } diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/comicgamma/ComicGammaGenerator.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/comicgamma/ComicGammaGenerator.kt index 79242b8ed..cef2f9461 100644 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/comicgamma/ComicGammaGenerator.kt +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/comicgamma/ComicGammaGenerator.kt @@ -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", diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/comicgamma/PtImg.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/comicgamma/PtImg.kt index d3f2c1969..055b73e0b 100644 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/comicgamma/PtImg.kt +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/comicgamma/PtImg.kt @@ -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) diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/comicgamma/PtImgInterceptor.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/comicgamma/PtImgInterceptor.kt index 230993869..a006809b0 100644 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/comicgamma/PtImgInterceptor.kt +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/comicgamma/PtImgInterceptor.kt @@ -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()