[Pepper&Carrot] multiples fixes (#11559)

* fix: cannot retrieve mangas when framagit.org is unreachable

since it is used only for translated titles, we can omit that if it is unreachable

* fix: fails to retrieve chapter list because the website changed

* fix: page list parsing

* feat: add support for mini theather fantasy

* bump version

* refactor: use tryParse util

* refactor: remove unnecessary !!
This commit is contained in:
Gauthier 2025-11-13 03:45:59 +08:00 committed by Draff
parent bd530edb7d
commit 68af18e453
Signed by: Draff
GPG Key ID: E8A89F3211677653
3 changed files with 55 additions and 23 deletions

View File

@ -1,7 +1,7 @@
ext { ext {
extName = 'Pepper&Carrot' extName = 'Pepper&Carrot'
extClass = '.PepperCarrot' extClass = '.PepperCarrot'
extVersionCode = 3 extVersionCode = 4
} }
apply from: "$rootDir/common.gradle" apply from: "$rootDir/common.gradle"

View File

@ -10,8 +10,8 @@ import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.util.asJsoup import eu.kanade.tachiyomi.util.asJsoup
import keiyoushi.utils.getPreferences
import keiyoushi.utils.getPreferencesLazy import keiyoushi.utils.getPreferencesLazy
import keiyoushi.utils.tryParse
import okhttp3.CacheControl import okhttp3.CacheControl
import okhttp3.Request import okhttp3.Request
import okhttp3.Response import okhttp3.Response
@ -39,7 +39,8 @@ class PepperCarrot : HttpSource(), ConfigurableSource {
} }
val langMap = preferences.langData.associateBy { langData -> langData.key } val langMap = preferences.langData.associateBy { langData -> langData.key }
val mangas = lang.map { key -> langMap[key]!!.toSManga() } val mangas = lang.map { key -> langMap[key]!!.toSManga() }
val result = MangasPage(mangas + getArtworkList(), false) val miniFantasyTheaters = lang.map { key -> langMap[key]!!.getMiniFantasyTheaterEntry() }
val result = MangasPage(mangas + miniFantasyTheaters + getArtworkList(), false)
it.onSuccess(result) it.onSuccess(result)
}.toObservable() }.toObservable()
@ -68,6 +69,9 @@ class PepperCarrot : HttpSource(), ConfigurableSource {
val key = manga.url val key = manga.url
val result = if (key.startsWith('#')) { val result = if (key.startsWith('#')) {
getArtworkEntry(key.substring(1)) getArtworkEntry(key.substring(1))
} else if (key.startsWith("miniFantasyTheater")) {
val langKey = key.substringAfter("#")
preferences.langData.find { lang -> lang.key == langKey }!!.getMiniFantasyTheaterEntry()
} else { } else {
preferences.langData.find { lang -> lang.key == key }!!.toSManga() preferences.langData.find { lang -> lang.key == key }!!.toSManga()
} }
@ -78,8 +82,11 @@ class PepperCarrot : HttpSource(), ConfigurableSource {
val key = manga.url val key = manga.url
val url = if (key.startsWith('#')) { // artwork val url = if (key.startsWith('#')) { // artwork
"$BASE_URL/en/files/${key.substring(1)}.html" "$BASE_URL/en/files/${key.substring(1)}.html"
} else if (key.startsWith("miniFantasyTheater")) {
val langKey = key.substringAfter("#")
"$BASE_URL/$langKey/webcomics/miniFantasyTheater.html"
} else { } else {
"$BASE_URL/$key/webcomics/index.html" "$BASE_URL/$key/webcomics/peppercarrot.html"
} }
return GET(url, headers) return GET(url, headers)
} }
@ -94,7 +101,19 @@ class PepperCarrot : HttpSource(), ConfigurableSource {
"Language: $name\nTranslators: $translators" "Language: $name\nTranslators: $translators"
} }
status = SManga.ONGOING status = SManga.ONGOING
thumbnail_url = "$BASE_URL/0_sources/0ther/artworks/low-res/2016-02-24_vertical-cover_remake_by-David-Revoy.jpg" thumbnail_url =
"$BASE_URL/0_sources/0ther/artworks/low-res/2016-02-24_vertical-cover_remake_by-David-Revoy.jpg"
initialized = true
}
private fun LangData.getMiniFantasyTheaterEntry() = SManga.create().apply {
url = "miniFantasyTheater#$key"
title = "Mini Fantasy Theater" + if (key != "en") " (${key.uppercase()})" else ""
author = AUTHOR
description =
"A webcomic series featuring short stories set in the enchanting world of Pepper&Carrot. With its playful humor and whimsical tales, this collection of gag strips is perfect for audiences of all ages."
status = SManga.ONGOING
thumbnail_url = "$BASE_URL/0_sources/0ther/artworks/low-res/2018-11-22_vertical-cover-book-three_by-David-Revoy.jpg"
initialized = true initialized = true
} }
@ -111,18 +130,22 @@ class PepperCarrot : HttpSource(), ConfigurableSource {
initialized = true initialized = true
} }
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> = Single.create<List<SChapter>> { override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> =
updateLangData(client, headers, preferences) Single.create<List<SChapter>> {
val response = client.newCall(chapterListRequest(manga)).execute() updateLangData(client, headers, preferences)
it.onSuccess(chapterListParse(response)) val response = client.newCall(chapterListRequest(manga)).execute()
}.toObservable() it.onSuccess(chapterListParse(response))
}.toObservable()
override fun chapterListRequest(manga: SManga): Request { override fun chapterListRequest(manga: SManga): Request {
val key = manga.url val key = manga.url
val url = if (key.startsWith('#')) { // artwork val url = if (key.startsWith('#')) { // artwork
"$BASE_URL/0_sources/0ther/${key.substring(1)}/low-res/" "$BASE_URL/0_sources/0ther/${key.substring(1)}/low-res/"
} else if (key.startsWith("miniFantasyTheater")) {
val langKey = key.substringAfter("#")
"$BASE_URL/$langKey/webcomics/miniFantasyTheater.html"
} else { } else {
"$BASE_URL/$key/webcomics/index.html" "$BASE_URL/$key/webcomics/peppercarrot.html"
} }
val lastUpdated = preferences.lastUpdated val lastUpdated = preferences.lastUpdated
if (lastUpdated == 0L) return GET(url, headers) if (lastUpdated == 0L) return GET(url, headers)
@ -150,10 +173,9 @@ class PepperCarrot : HttpSource(), ConfigurableSource {
else -> substringBeforeLast('(').trimEnd() else -> substringBeforeLast('(').trimEnd()
} }
} }
date_upload = it.selectFirst(Evaluator.Tag("figcaption"))!!.ownText().let { date_upload = it.selectFirst(Evaluator.Tag("figcaption"))?.text()?.let { text ->
val date = dateRegex.find(it)!!.value dateRegex.find(text)?.value?.let { date -> dateFormat.tryParse(date) }
dateFormat.parse(date)!!.time } ?: 0L
}
chapter_number = number.toFloat() chapter_number = number.toFloat()
} }
} }
@ -162,7 +184,7 @@ class PepperCarrot : HttpSource(), ConfigurableSource {
private fun parseArtwork(response: Response): List<SChapter> { private fun parseArtwork(response: Response): List<SChapter> {
val baseDir = response.request.url.toString().removePrefix(BASE_URL) val baseDir = response.request.url.toString().removePrefix(BASE_URL)
return response.asJsoup().select(Evaluator.Tag("a")).asReversed().mapNotNull { return response.asJsoup().select(Evaluator.Tag("a")).asReversed().mapNotNull {
val filename = it.attr("href")!! val filename = it.attr("href")
if (!filename.endsWith(".jpg")) return@mapNotNull null if (!filename.endsWith(".jpg")) return@mapNotNull null
val file = filename.removeSuffix(".jpg").removeSuffix("_by-David-Revoy") val file = filename.removeSuffix(".jpg").removeSuffix("_by-David-Revoy")
@ -170,11 +192,11 @@ class PepperCarrot : HttpSource(), ConfigurableSource {
val date: Long val date: Long
if (file.length >= 10 && dateRegex.matches(file.substring(0, 10))) { if (file.length >= 10 && dateRegex.matches(file.substring(0, 10))) {
fileStripped = file.substring(10) fileStripped = file.substring(10)
date = dateFormat.parse(file.substring(0, 10))!!.time date = dateFormat.tryParse(file.substring(0, 10))
} else { } else {
fileStripped = file fileStripped = file
val lastModified = it.nextSibling() as? TextNode val lastModified = it.nextSibling() as? TextNode
date = if (lastModified == null) 0 else dateFormat.parse(lastModified.text())!!.time date = if (lastModified == null) 0 else dateFormat.tryParse(lastModified.text())
} }
val fileNormalized = fileStripped val fileNormalized = fileStripped
.replace('_', ' ') .replace('_', ' ')
@ -202,9 +224,14 @@ class PepperCarrot : HttpSource(), ConfigurableSource {
override fun pageListParse(response: Response): List<Page> { override fun pageListParse(response: Response): List<Page> {
val document = response.asJsoup() val document = response.asJsoup()
val urls = document.select(Evaluator.Class("comicpage")).map { it.attr("src")!! } val urls = document.select(".webcomic-page img").map { it.attr("src") }
val thumbnail = urls[0].replace("P00.jpg", ".jpg") val thumbnail =
return (listOf(thumbnail) + urls).mapIndexed { index, imageUrl -> if (urls[0].contains("miniFantasyTheater", true)) {
emptyList()
} else {
listOf(urls[0].replace("P00.jpg", ".jpg"))
}
return (thumbnail + urls).mapIndexed { index, imageUrl ->
Page(index, imageUrl = imageUrl) Page(index, imageUrl = imageUrl)
} }
} }

View File

@ -63,7 +63,12 @@ fun updateLangData(client: OkHttpClient, headers: Headers, preferences: SharedPr
val translatedCount = episodes.flatMap { it.translated_languages } val translatedCount = episodes.flatMap { it.translated_languages }
.groupingBy { it }.eachCount() .groupingBy { it }.eachCount()
val titles = fetchTitles(client, headers) // framagit.org is IP blocked in some countries
val titles = try {
fetchTitles(client, headers)
} catch (e: Exception) {
null
}
val langs = client.newCall(GET("$BASE_URL/0_sources/langs.json", headers)) val langs = client.newCall(GET("$BASE_URL/0_sources/langs.json", headers))
.execute().parseAs<LangsDto>().entries.map { (key, dto) -> .execute().parseAs<LangsDto>().entries.map { (key, dto) ->
@ -81,7 +86,7 @@ fun updateLangData(client: OkHttpClient, headers: Headers, preferences: SharedPr
.also { if (preferences.lang.isEmpty()) editor.chooseLang(it) } .also { if (preferences.lang.isEmpty()) editor.chooseLang(it) }
.map { .map {
val progress = "${it.translatedCount}/$total translated" val progress = "${it.translatedCount}/$total translated"
LangData(it.key, it.name, progress, it.translators, titles[it.key]) LangData(it.key, it.name, progress, it.translators, titles?.get(it.key) ?: if (it.key == "en") TITLE else "$TITLE (${it.key.uppercase()})")
} }
editor.putString(LANG_DATA_PREF, ProtoBuf.encodeToBase64(langs)).apply() editor.putString(LANG_DATA_PREF, ProtoBuf.encodeToBase64(langs)).apply()