Webtoons.com: add rate limit & handle chapter count reset on new season (#9593)

* Webtoons: ratelimit chapters fetch api

closes https://github.com/keiyoushi/extensions-source/issues/9570

* handle season number reset

closes https://github.com/keiyoushi/extensions-source/issues/9495

* edge case bug

* bump
This commit is contained in:
AwkwardPeak7 2025-07-09 11:14:42 +05:00 committed by Draff
parent 827e91d2c6
commit d773d2692b
Signed by: Draff
GPG Key ID: E8A89F3211677653
3 changed files with 66 additions and 36 deletions

View File

@ -1,7 +1,7 @@
ext {
extName = 'Webtoons.com'
extClass = '.WebtoonsFactory'
extVersionCode = 49
extVersionCode = 50
isNsfw = false
}

View File

@ -20,7 +20,10 @@ class Episode(
val viewerLink: String,
val exposureDateMillis: Long,
val hasBgm: Boolean = false,
)
) {
var chapterNumber = -1f
var seasonNumber = 1
}
@Serializable
class MotionToonResponse(

View File

@ -7,6 +7,7 @@ import eu.kanade.tachiyomi.lib.textinterceptor.TextInterceptor
import eu.kanade.tachiyomi.lib.textinterceptor.TextInterceptorHelper
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.asObservableSuccess
import eu.kanade.tachiyomi.network.interceptor.rateLimitHost
import eu.kanade.tachiyomi.source.ConfigurableSource
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.MangasPage
@ -65,6 +66,7 @@ open class Webtoons(
}
}
.addInterceptor(TextInterceptor())
.rateLimitHost(mobileUrl.toHttpUrl(), 1)
.build()
private val preferences by getPreferencesLazy()
@ -276,10 +278,60 @@ open class Webtoons(
override fun chapterListParse(response: Response): List<SChapter> {
val result = response.parseAs<EpisodeListResponse>()
val recognized: MutableList<Int> = mutableListOf()
val unrecognized: MutableList<Int> = mutableListOf()
var recognized = 0
var unrecognized = 0
val chapters = result.result.episodeList.mapIndexed { index, episode ->
val chapters = result.result.episodeList.onEach { episode ->
val match = episodeNoRegex
.find(episode.episodeTitle)
?.groupValues
?.takeIf { it[6].isEmpty() } // skip mini/bonus episodes
episode.chapterNumber = match?.get(11)?.toFloat() ?: -1f
episode.seasonNumber = match?.get(4)?.takeIf(String::isNotBlank)?.toInt() ?: 1
if (episode.chapterNumber == -1f) {
unrecognized++
} else {
recognized++
}
}
if (unrecognized > recognized) {
chapters.onEachIndexed { index, chapter ->
chapter.chapterNumber = (index + 1).toFloat()
}
} else {
var maxChapterNumber = 0f
var currentSeason = 1
var seasonOffset = 0f
chapters.forEachIndexed { idx, chapter ->
if (chapter.chapterNumber != -1f) {
val originalNumber = chapter.chapterNumber
// Check if we've moved to a new season
if (chapter.seasonNumber > currentSeason) {
currentSeason = chapter.seasonNumber
if (originalNumber <= maxChapterNumber) {
seasonOffset = maxChapterNumber
}
}
chapter.chapterNumber = seasonOffset + originalNumber
maxChapterNumber = maxOf(maxChapterNumber, chapter.chapterNumber)
} else {
val previous = chapters.getOrNull(idx - 1)
if (previous == null) {
chapter.chapterNumber = 0f
} else {
chapter.chapterNumber = previous.chapterNumber + 0.01f
}
}
}
}
return chapters.map { episode ->
SChapter.create().apply {
url = episode.viewerLink
name = Parser.unescapeEntities(episode.episodeTitle, false)
@ -287,41 +339,16 @@ open class Webtoons(
name += ""
}
date_upload = episode.exposureDateMillis
chapter_number = episodeNoRegex
.find(episode.episodeTitle)
?.groupValues
?.get(4)
?.toFloat()
?: -1f
if (chapter_number == -1f) {
unrecognized += index
} else {
recognized += index
}
chapter_number = episode.chapterNumber
}
}
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()
}.asReversed()
}
// season number - 4 capture group
// possible bonus/mini/special episode - 6 capture group
// episode number - 11 capture group
private val episodeNoRegex = Regex(
"""(ep(isode)?|ch(apter)?)\s*\.?\s*(\d+(\.\d+)?)""",
"""(?:(s(eason)?|part|vol(ume)?)\s*\.?\s*(\d+).*?)?(.*?(mini|bonus|special).*?)?(e(p(isode)?)?|ch(apter)?)\s*\.?\s*(\d+(\.\d+)?)""",
RegexOption.IGNORE_CASE,
)