Webtoons.com: use api to get chapters (#9332)

* Webtoons.com: use api to get chapters

* completed status

* rename
This commit is contained in:
AwkwardPeak7 2025-06-20 07:26:12 +05:00 committed by Draff
parent 68df5d3b69
commit 4d61698687
Signed by: Draff
GPG Key ID: E8A89F3211677653
4 changed files with 62 additions and 26 deletions

View File

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

View File

@ -2,6 +2,27 @@ package eu.kanade.tachiyomi.extension.all.webtoons
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
typealias EpisodeListResponse = ResultDto<EpisodeList>
@Serializable
class ResultDto<T>(
val result: T,
)
@Serializable
class EpisodeList(
val episodeList: List<Episode>,
)
@Serializable
class Episode(
val episodeNo: Float,
val episodeTitle: String,
val viewerLink: String,
val exposureDateMillis: Long,
val hasBgm: Boolean = false,
)
@Serializable @Serializable
class MotionToonResponse( class MotionToonResponse(
val assets: MotionToonAssets, val assets: MotionToonAssets,

View File

@ -18,7 +18,6 @@ import eu.kanade.tachiyomi.util.asJsoup
import keiyoushi.utils.firstInstanceOrNull import keiyoushi.utils.firstInstanceOrNull
import keiyoushi.utils.getPreferencesLazy import keiyoushi.utils.getPreferencesLazy
import keiyoushi.utils.parseAs import keiyoushi.utils.parseAs
import keiyoushi.utils.tryParse
import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Request import okhttp3.Request
import okhttp3.Response import okhttp3.Response
@ -26,14 +25,12 @@ import org.jsoup.nodes.Document
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import rx.Observable import rx.Observable
import java.net.SocketException import java.net.SocketException
import java.text.SimpleDateFormat
import java.util.Calendar import java.util.Calendar
open class Webtoons( open class Webtoons(
override val lang: String, override val lang: String,
private val langCode: String = lang, private val langCode: String = lang,
localeForCookie: String = lang, localeForCookie: String = lang,
private val dateFormat: SimpleDateFormat,
) : HttpSource(), ConfigurableSource { ) : HttpSource(), ConfigurableSource {
override val name = "Webtoons.com" override val name = "Webtoons.com"
override val baseUrl = "https://www.webtoons.com" override val baseUrl = "https://www.webtoons.com"
@ -199,7 +196,7 @@ open class Webtoons(
status = with(infoElement?.selectFirst("p.day_info")?.text().orEmpty()) { status = with(infoElement?.selectFirst("p.day_info")?.text().orEmpty()) {
when { when {
contains("UP") || contains("EVERY") || contains("NOUVEAU") -> SManga.ONGOING contains("UP") || contains("EVERY") || contains("NOUVEAU") -> SManga.ONGOING
contains("END") || contains("TERMINÉ") -> SManga.COMPLETED contains("END") || contains("COMPLETED") || contains("TERMINÉ") -> SManga.COMPLETED
else -> SManga.UNKNOWN else -> SManga.UNKNOWN
} }
} }
@ -231,24 +228,44 @@ open class Webtoons(
throw UnsupportedOperationException() throw UnsupportedOperationException()
} }
override fun chapterListRequest(manga: SManga) = GET(mobileUrl + manga.url, mobileHeaders) override fun chapterListRequest(manga: SManga): Request {
val webtoonUrl = getMangaUrl(manga).toHttpUrl()
val titleId = webtoonUrl.queryParameter("title_no")
?: webtoonUrl.queryParameter("titleNo")
?: throw Exception("id not found, Migrate from $name to $name")
val isCanvas = webtoonUrl.pathSegments.getOrNull(1)?.equals("canvas")
?: throw Exception("unknown type, Migrate from $name to $name")
val url = mobileUrl.toHttpUrl().newBuilder().apply {
addPathSegments("api/v1")
if (isCanvas) {
addPathSegment("canvas")
} else {
addPathSegment("webtoon")
}
addPathSegment(titleId)
addPathSegment("episodes")
addQueryParameter("pageSize", "99999")
}.build()
return GET(url, mobileHeaders)
}
override fun chapterListParse(response: Response): List<SChapter> { override fun chapterListParse(response: Response): List<SChapter> {
val document = response.asJsoup() val result = response.parseAs<EpisodeListResponse>()
return document.select("ul#_episodeList li[id*=episode] a").map { element -> return result.result.episodeList.map { episode ->
SChapter.create().apply { SChapter.create().apply {
setUrlWithoutDomain(element.absUrl("href")) url = episode.viewerLink
name = element.selectFirst(".sub_title > span.ellipsis")!!.text() name = episode.episodeTitle
element.selectFirst("a > div.row > div.num")?.let { if (episode.hasBgm) {
name += " Ch. " + it.text().substringAfter("#")
}
element.selectFirst(".ico_bgm")?.also {
name += "" name += ""
} }
date_upload = dateFormat.tryParse(element.selectFirst(".sub_info .date")?.text()) date_upload = episode.exposureDateMillis
chapter_number = episode.episodeNo
} }
} }.asReversed()
} }
override fun pageListParse(response: Response): List<Page> { override fun pageListParse(response: Response): List<Page> {

View File

@ -1,23 +1,21 @@
package eu.kanade.tachiyomi.extension.all.webtoons package eu.kanade.tachiyomi.extension.all.webtoons
import eu.kanade.tachiyomi.source.SourceFactory import eu.kanade.tachiyomi.source.SourceFactory
import java.text.SimpleDateFormat
import java.util.Locale
class WebtoonsFactory : SourceFactory { class WebtoonsFactory : SourceFactory {
override fun createSources() = listOf( override fun createSources() = listOf(
Webtoons("en", dateFormat = SimpleDateFormat("MMM d, yyyy", Locale.ENGLISH)), Webtoons("en"),
object : Webtoons("id", dateFormat = SimpleDateFormat("d MMMM yyyy", Locale("id"))) { object : Webtoons("id") {
// Override ID as part of the name was removed to be more consiten with other enteries // Override ID as part of the name was removed to be more consistent with other entries
override val id: Long = 8749627068478740298 override val id: Long = 8749627068478740298
}, },
Webtoons("th", dateFormat = SimpleDateFormat("d MMM yyyy", Locale("th"))), Webtoons("th"),
Webtoons("es", dateFormat = SimpleDateFormat("d MMMM. yyyy", Locale("es"))), Webtoons("es"),
Webtoons("fr", dateFormat = SimpleDateFormat("d MMM yyyy", Locale.FRENCH)), Webtoons("fr"),
object : Webtoons("zh-Hant", "zh-hant", "zh_TW", SimpleDateFormat("yyyy/MM/dd", Locale.TRADITIONAL_CHINESE)) { object : Webtoons("zh-Hant", "zh-hant", "zh_TW") {
// Due to lang code getting more specific // Due to lang code getting more specific
override val id: Long = 2959982438613576472 override val id: Long = 2959982438613576472
}, },
Webtoons("de", dateFormat = SimpleDateFormat("dd.MM.yyyy", Locale.GERMAN)), Webtoons("de"),
) )
} }