From dd47332ab92bcc1a0b24880d19f222404c940c9f Mon Sep 17 00:00:00 2001 From: AwkwardPeak7 <48650614+AwkwardPeak7@users.noreply.github.com> Date: Sat, 21 Jun 2025 15:54:58 +0500 Subject: [PATCH] Webtoons.com: fixes (#9349) * better compatibility with old urls * use index for chapter number as fallback * better chapter number logic * escape chapter names * fix deep link for canvas * Update build.gradle * bgm * deeplink * i + 1 --- src/all/webtoons/build.gradle | 2 +- .../tachiyomi/extension/all/webtoons/Dto.kt | 1 - .../extension/all/webtoons/Webtoons.kt | 95 +++++++++++++++---- .../all/webtoons/WebtoonsUrlActivity.kt | 8 +- 4 files changed, 82 insertions(+), 24 deletions(-) diff --git a/src/all/webtoons/build.gradle b/src/all/webtoons/build.gradle index b875a6816..08722cd51 100644 --- a/src/all/webtoons/build.gradle +++ b/src/all/webtoons/build.gradle @@ -1,7 +1,7 @@ ext { extName = 'Webtoons.com' extClass = '.WebtoonsFactory' - extVersionCode = 48 + extVersionCode = 49 isNsfw = false } diff --git a/src/all/webtoons/src/eu/kanade/tachiyomi/extension/all/webtoons/Dto.kt b/src/all/webtoons/src/eu/kanade/tachiyomi/extension/all/webtoons/Dto.kt index dd0713c52..a2e1ba21c 100644 --- a/src/all/webtoons/src/eu/kanade/tachiyomi/extension/all/webtoons/Dto.kt +++ b/src/all/webtoons/src/eu/kanade/tachiyomi/extension/all/webtoons/Dto.kt @@ -16,7 +16,6 @@ class EpisodeList( @Serializable class Episode( - val episodeNo: Float, val episodeTitle: String, val viewerLink: String, val exposureDateMillis: Long, diff --git a/src/all/webtoons/src/eu/kanade/tachiyomi/extension/all/webtoons/Webtoons.kt b/src/all/webtoons/src/eu/kanade/tachiyomi/extension/all/webtoons/Webtoons.kt index 0f6573ca1..9780e3cbe 100644 --- a/src/all/webtoons/src/eu/kanade/tachiyomi/extension/all/webtoons/Webtoons.kt +++ b/src/all/webtoons/src/eu/kanade/tachiyomi/extension/all/webtoons/Webtoons.kt @@ -23,6 +23,7 @@ import okhttp3.Request import okhttp3.Response import org.jsoup.nodes.Document import org.jsoup.nodes.Element +import org.jsoup.parser.Parser import rx.Observable import java.net.SocketException import java.util.Calendar @@ -122,18 +123,23 @@ open class Webtoons( override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable { if (query.startsWith(ID_SEARCH_PREFIX)) { - val (_, titleLang, titleNo) = query.split(":", limit = 3) + val (_, type, lang, titleNo) = query.split(":", limit = 4) val tmpManga = SManga.create().apply { - url = "/episodeList?titleNo=$titleNo" + url = buildString { + if (type == "canvas") { + append("/challenge") + } + append("/episodeList?titleNo=") + append(titleNo) + } } - return if (titleLang == langCode) { + + return if (lang == langCode) { fetchMangaDetails(tmpManga).map { MangasPage(listOf(it), false) } } else { - Observable.just( - MangasPage(emptyList(), false), - ) + Observable.just(MangasPage(emptyList(), false)) } } @@ -200,7 +206,7 @@ open class Webtoons( else -> SManga.UNKNOWN } } - + initialized = true thumbnail_url = run { val bannerFile = document.selectFirst(".detail_header .thmb img") ?.absUrl("src") @@ -232,18 +238,33 @@ open class Webtoons( val webtoonUrl = getMangaUrl(manga).toHttpUrl() val titleId = webtoonUrl.queryParameter("title_no") ?: webtoonUrl.queryParameter("titleNo") - ?: throw Exception("id not found, Migrate from $name to $name") + ?: throw Exception("Migrate from $name to $name") - val isCanvas = webtoonUrl.pathSegments.getOrNull(1)?.equals("canvas") - ?: throw Exception("unknown type, Migrate from $name to $name") + val type = run { + val path = webtoonUrl.pathSegments.filter(String::isNotEmpty) + + // older url pattern, people have in their library + if (webtoonUrl.encodedPath.contains("episodeList")) { + when (path[0]) { + // "/episodeList?titleNo=1049" + "episodeList" -> "webtoon" + // "/challenge/episodeList?titleNo=304446" + "challenge" -> "canvas" + else -> throw Exception("Migrate from $name to $name") + } + } else { + // "/en/canvas/meme-girls/list?title_no=304446" + if (path[1] == "canvas") { + "canvas" + } else { + "webtoon" + } + } + } val url = mobileUrl.toHttpUrl().newBuilder().apply { addPathSegments("api/v1") - if (isCanvas) { - addPathSegment("canvas") - } else { - addPathSegment("webtoon") - } + addPathSegment(type) addPathSegment(titleId) addPathSegment("episodes") addQueryParameter("pageSize", "99999") @@ -255,19 +276,55 @@ open class Webtoons( override fun chapterListParse(response: Response): List { val result = response.parseAs() - return result.result.episodeList.map { episode -> + val recognized: MutableList = mutableListOf() + val unrecognized: MutableList = mutableListOf() + + val chapters = result.result.episodeList.mapIndexed { index, episode -> SChapter.create().apply { url = episode.viewerLink - name = episode.episodeTitle + name = Parser.unescapeEntities(episode.episodeTitle, false) if (episode.hasBgm) { name += " ♫" } date_upload = episode.exposureDateMillis - chapter_number = episode.episodeNo + chapter_number = episodeNoRegex + .find(episode.episodeTitle) + ?.groupValues + ?.get(4) + ?.toFloat() + ?: -1f + if (chapter_number == -1f) { + unrecognized += index + } else { + recognized += index + } } - }.asReversed() + } + + if (unrecognized.size > recognized.size) { + chapters.onEachIndexed { index, chapter -> + chapter.chapter_number = (index + 1).toFloat() + } + } else { + unrecognized.forEach { uIdx -> + val chapter = chapters[uIdx] + val previous = chapters.getOrNull(uIdx - 1) + if (previous == null) { + chapter.chapter_number = 0f + } else { + chapter.chapter_number = previous.chapter_number + 0.01f + } + } + } + + return chapters.asReversed() } + private val episodeNoRegex = Regex( + """(ep(isode)?|ch(apter)?)\s*\.?\s*(\d+(\.\d+)?)""", + RegexOption.IGNORE_CASE, + ) + override fun pageListParse(response: Response): List { val document = response.asJsoup() val useMaxQuality = useMaxQualityPref() diff --git a/src/all/webtoons/src/eu/kanade/tachiyomi/extension/all/webtoons/WebtoonsUrlActivity.kt b/src/all/webtoons/src/eu/kanade/tachiyomi/extension/all/webtoons/WebtoonsUrlActivity.kt index 64ed9e60e..a07eb382c 100644 --- a/src/all/webtoons/src/eu/kanade/tachiyomi/extension/all/webtoons/WebtoonsUrlActivity.kt +++ b/src/all/webtoons/src/eu/kanade/tachiyomi/extension/all/webtoons/WebtoonsUrlActivity.kt @@ -23,11 +23,13 @@ class WebtoonsUrlActivity : Activity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val titleNo = intent?.data?.getQueryParameter("title_no") - val lang = intent?.data?.pathSegments?.get(0) - if (titleNo != null) { + val path = intent?.data?.pathSegments + if (titleNo != null && path != null && path.size >= 3) { + val lang = path[0] + val type = path[1] val mainIntent = Intent().apply { action = "eu.kanade.tachiyomi.SEARCH" - putExtra("query", "$ID_SEARCH_PREFIX$lang:$titleNo") + putExtra("query", "$ID_SEARCH_PREFIX$type:$lang:$titleNo") putExtra("filter", packageName) }