Drop fakeimg (#11743)

* xkcd: drop fakeimg

* smbc: drop fakeimg

* mehgazone: drop fakeimg

* swordscomic: drop fakeimg

* buttsmithy: drop fakeimg

* xkcd: better thumbnail

It's unclear what the original source for it is but it's all over the
web.

Link: https://libguides.davenportlibrary.com/comicsforallages/xkcd

* smbc, xkcd: implement fetchMangaDetails properly

* Update src/en/saturdaymorningbreakfastcomics/build.gradle

* Update src/all/xkcd/build.gradle

---------

Co-authored-by: Vetle Ledaal <vetle.ledaal@gmail.com>
This commit is contained in:
novenary 2025-11-22 00:25:48 +02:00 committed by Draff
parent 8bd7ed9a90
commit e17d796fc1
Signed by: Draff
GPG Key ID: E8A89F3211677653
15 changed files with 114 additions and 114 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

View File

@ -1,7 +1,11 @@
ext {
extName = 'xkcd'
extClass = '.XkcdFactory'
extVersionCode = 14
extVersionCode = 15
}
apply from: "$rootDir/common.gradle"
dependencies {
implementation(project(':lib:textinterceptor'))
}

View File

@ -1,6 +1,7 @@
package eu.kanade.tachiyomi.extension.all.xkcd
import android.net.Uri.encode
import eu.kanade.tachiyomi.lib.textinterceptor.TextInterceptor
import eu.kanade.tachiyomi.lib.textinterceptor.TextInterceptorHelper
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.MangasPage
import eu.kanade.tachiyomi.source.model.Page
@ -8,7 +9,11 @@ import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.Protocol
import okhttp3.Response
import okhttp3.ResponseBody.Companion.toResponseBody
import rx.Observable
import java.text.SimpleDateFormat
import java.util.Locale
@ -21,6 +26,26 @@ open class Xkcd(
final override val name = "xkcd"
final override val supportsLatest = false
override val client: OkHttpClient = network.client.newBuilder()
.addInterceptor(TextInterceptor())
.addInterceptor { chain ->
val request = chain.request()
val url = request.url
if (url.host != "thumbnail") return@addInterceptor chain.proceed(request)
val image = this::class.java
.getResourceAsStream("/assets/thumbnail.png")!!
.readBytes()
val responseBody = image.toResponseBody("image/png".toMediaType())
Response.Builder()
.request(request)
.protocol(Protocol.HTTP_1_1)
.code(200)
.message("OK")
.body(responseBody)
.build()
}
.build()
protected open val archive = "/archive"
@ -32,8 +57,6 @@ open class Xkcd(
protected open val interactiveText =
"To experience the interactive version of this comic, open it in WebView/browser."
protected open val altTextUrl = LATIN_ALT_TEXT_URL
protected open val chapterListSelector = "#middleContainer > a"
protected open val imageSelector = "#comic > img"
@ -42,45 +65,27 @@ open class Xkcd(
protected fun String.timestamp() = dateFormat.parse(this)?.time ?: 0L
protected fun String.image() = altTextUrl + "&text=" + encode(this)
protected open fun String.numbered(number: Any) = "$number - $this"
// TODO: maybe use BreakIterator
protected fun wordWrap(title: String, altText: String) = buildString {
title.split(' ').forEachIndexed { i, w ->
if (i != 0 && i % 7 == 0) append("\n")
append(w).append(' ')
}
append("\n\n")
var charCount = 0
altText.replace("\r\n", " ").split(' ').forEach { w ->
if (charCount > 25) {
append("\n")
charCount = 0
}
append(w).append(' ')
charCount += w.length + 1
}
}
final override fun fetchPopularManga(page: Int) =
private fun makeSManga(): SManga =
SManga.create().apply {
title = name
artist = creator
author = creator
description = synopsis
status = SManga.ONGOING
thumbnail_url = THUMBNAIL_URL
thumbnail_url = "https://thumbnail/xkcd.png"
setUrlWithoutDomain(archive)
}.let { Observable.just(MangasPage(listOf(it), false))!! }
}
final override fun fetchPopularManga(page: Int) =
Observable.just(MangasPage(listOf(makeSManga()), false))
final override fun fetchSearchManga(page: Int, query: String, filters: FilterList) =
Observable.just(MangasPage(emptyList(), false))!!
final override fun fetchMangaDetails(manga: SManga) =
Observable.just(manga.apply { initialized = true })!!
Observable.just(makeSManga())!!
override fun chapterListParse(response: Response) =
response.asJsoup().select(chapterListSelector).map {
@ -106,9 +111,9 @@ open class Xkcd(
}
// create a text image for the alt text
val text = wordWrap(img.attr("alt"), img.attr("title"))
val text = TextInterceptorHelper.createUrl(img.attr("alt"), img.attr("title"))
return listOf(Page(0, "", image), Page(1, "", text.image()))
return listOf(Page(0, "", image), Page(1, "", text))
}
final override fun imageUrlParse(response: Response) =
@ -134,16 +139,4 @@ open class Xkcd(
final override fun searchMangaRequest(page: Int, query: String, filters: FilterList) =
throw UnsupportedOperationException()
companion object {
private const val THUMBNAIL_URL =
"https://fakeimg.ryd.tools/550x780/ffffff/6e7b91/?font=museo&text=xkcd"
const val LATIN_ALT_TEXT_URL =
"https://fakeimg.ryd.tools/1500x2126/ffffff/000000/?font=museo&font_size=42"
const val CJK_ALT_TEXT_URL =
"https://placehold.jp/42/ffffff/000000/1500x2126.png?css=" +
"%7B%22padding%22%3A%22300px%22%2C%22text-align%22%3A%22left%22%7D"
}
}

View File

@ -1,6 +1,7 @@
package eu.kanade.tachiyomi.extension.all.xkcd.translations
import eu.kanade.tachiyomi.extension.all.xkcd.Xkcd
import eu.kanade.tachiyomi.lib.textinterceptor.TextInterceptorHelper
import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.util.asJsoup
@ -36,9 +37,9 @@ class XkcdFR : Xkcd("https://xkcd.lapin.org", "fr") {
val img = it.child(2).child(0).child(0)
// create a text image for the alt text
val text = wordWrap(it.child(0).text(), img.attr("alt"))
val text = TextInterceptorHelper.createUrl(it.child(0).text(), img.attr("alt"))
listOf(Page(0, "", img.attr("abs:src")), Page(1, "", text.image()))
listOf(Page(0, "", img.attr("abs:src")), Page(1, "", text))
}
override val interactiveText: String

View File

@ -1,6 +1,7 @@
package eu.kanade.tachiyomi.extension.all.xkcd.translations
import eu.kanade.tachiyomi.extension.all.xkcd.Xkcd
import eu.kanade.tachiyomi.lib.textinterceptor.TextInterceptorHelper
import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.util.asJsoup
@ -13,8 +14,6 @@ class XkcdRU : Xkcd("https://xkcd.ru", "ru") {
override val synopsis = "о романтике, сарказме, математике и языке"
override val altTextUrl = super.altTextUrl.replace("museo", "noto")
override val chapterListSelector = ".main > a"
override val imageSelector = ".main"
@ -38,9 +37,9 @@ class XkcdRU : Xkcd("https://xkcd.ru", "ru") {
val img = it.child(5).child(0)
// create a text image for the alt text
val text = wordWrap(img.attr("alt"), it.child(7).text())
val text = TextInterceptorHelper.createUrl(img.attr("alt"), it.child(7).text())
listOf(Page(0, "", img.attr("abs:src")), Page(1, "", text.image()))
listOf(Page(0, "", img.attr("abs:src")), Page(1, "", text))
}
override val interactiveText: String

View File

@ -1,6 +1,7 @@
package eu.kanade.tachiyomi.extension.all.xkcd.translations
import eu.kanade.tachiyomi.extension.all.xkcd.Xkcd
import eu.kanade.tachiyomi.lib.textinterceptor.TextInterceptorHelper
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.model.SChapter
@ -23,8 +24,6 @@ class XkcdZH : Xkcd("https://xkcd.tw", "zh", "yyyy-MM-dd HH:mm:ss") {
override val interactiveText =
"要體驗本漫畫的互動版請在WebView/瀏覽器中打開。"
override val altTextUrl = CJK_ALT_TEXT_URL
override val imageSelector = "#content > img:not([id])"
private val json by injectLazy<Json>()
@ -54,9 +53,9 @@ class XkcdZH : Xkcd("https://xkcd.tw", "zh", "yyyy-MM-dd HH:mm:ss") {
val image = img.attr("abs:src")
// create a text image for the alt text
val text = img.attr("alt") + "\n\n" + img.attr("title")
val text = TextInterceptorHelper.createUrl(img.attr("alt"), img.attr("title"))
return listOf(Page(0, "", image), Page(1, "", text.image()))
return listOf(Page(0, "", image), Page(1, "", text))
}
override val chapterListSelector: String

View File

@ -1,8 +1,12 @@
ext {
extName = 'buttsmithy'
extClass = '.Buttsmithy'
extVersionCode = 3
extVersionCode = 4
isNsfw = true
}
apply from: "$rootDir/common.gradle"
dependencies {
implementation(project(':lib:textinterceptor'))
}

View File

@ -1,6 +1,8 @@
package eu.kanade.tachiyomi.extension.en.buttsmithy
import android.app.Application
import eu.kanade.tachiyomi.lib.textinterceptor.TextInterceptor
import eu.kanade.tachiyomi.lib.textinterceptor.TextInterceptorHelper
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.MangasPage
@ -9,6 +11,7 @@ import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import org.jsoup.nodes.Document
@ -36,6 +39,7 @@ class Buttsmithy : HttpSource() {
private val alfieDateParser = SimpleDateFormat("HH:mm MMMM dd, yyyy", Locale.US)
override val supportsLatest: Boolean = false
override val client: OkHttpClient = network.client.newBuilder().addInterceptor(TextInterceptor()).build()
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
val chapters: List<SChapter> =
@ -255,7 +259,7 @@ class Buttsmithy : HttpSource() {
}
private fun generateImageUrlWithText(text: String): String {
return "https://fakeimg.ryd.tools/800x1236/?text=$text&font=lobster"
return TextInterceptorHelper.createUrl(text, "")
}
private fun generateMangasPage(): MangasPage {

View File

@ -1,8 +1,12 @@
ext {
extName = 'Mehgazone'
extClass = '.Mehgazone'
extVersionCode = 1
extVersionCode = 2
isNsfw = true
}
apply from: "$rootDir/common.gradle"
dependencies {
implementation(project(':lib:textinterceptor'))
}

View File

@ -14,6 +14,8 @@ import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.extension.en.mehgazone.interceptors.BasicAuthInterceptor
import eu.kanade.tachiyomi.extension.en.mehgazone.serialization.ChapterListDto
import eu.kanade.tachiyomi.extension.en.mehgazone.serialization.PageListDto
import eu.kanade.tachiyomi.lib.textinterceptor.TextInterceptor
import eu.kanade.tachiyomi.lib.textinterceptor.TextInterceptorHelper
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.source.ConfigurableSource
import eu.kanade.tachiyomi.source.model.FilterList
@ -55,6 +57,7 @@ class Mehgazone : ConfigurableSource, HttpSource() {
override val client: OkHttpClient by lazy {
network.cloudflareClient
.newBuilder()
.addInterceptor(TextInterceptor())
.addInterceptor(authInterceptor)
.build()
}
@ -63,10 +66,6 @@ class Mehgazone : ConfigurableSource, HttpSource() {
SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.US)
}
private val textToImageURL = "https://fakeimg.ryd.tools/1500x2126/ffffff/000000/?font=museo&font_size=42".toHttpUrl()
private fun String.image() = textToImageURL.newBuilder().setQueryParameter("text", this).build().toString()
private fun String.unescape() = unescapeEntities(this, false)
private fun String.linkify() = SpannableString(this).apply { Linkify.addLinks(this, Linkify.WEB_URLS) }
@ -172,19 +171,6 @@ class Mehgazone : ConfigurableSource, HttpSource() {
}.reversed()
}
// Adapted from the xkcd source's wordWrap function
private fun wordWrap(text: String) = buildString {
var charCount = 0
text.replace('\n', ' ').split(' ').forEach { w ->
if (charCount > 25) {
append("\n")
charCount = 0
}
append(w).append(' ')
charCount += w.length + 1
}
}
override fun pageListRequest(chapter: SChapter): Request {
val chapterUrl = chapter.url.toHttpUrl()
val pageListUrl = chapterUrl
@ -208,7 +194,7 @@ class Mehgazone : ConfigurableSource, HttpSource() {
Page(
images.size,
"",
wordWrap(Jsoup.parseBodyFragment(apiResponse.excerpt.rendered.unescape()).text()).image(),
TextInterceptorHelper.createUrl("", Jsoup.parseBodyFragment(apiResponse.excerpt.rendered.unescape()).text()),
),
)
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -1,8 +1,12 @@
ext {
extName = 'Saturday Morning Breakfast Comics'
extClass = '.SaturdayMorningBreakfastComics'
extVersionCode = 1
extVersionCode = 2
isNsfw = false
}
apply from: "$rootDir/common.gradle"
dependencies {
implementation(project(':lib:textinterceptor'))
}

View File

@ -1,6 +1,7 @@
package eu.kanade.tachiyomi.extension.en.saturdaymorningbreakfastcomics
import android.net.Uri.encode
import eu.kanade.tachiyomi.lib.textinterceptor.TextInterceptor
import eu.kanade.tachiyomi.lib.textinterceptor.TextInterceptorHelper
import eu.kanade.tachiyomi.network.asObservable
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.MangasPage
@ -10,8 +11,12 @@ import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.util.asJsoup
import keiyoushi.utils.tryParse
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.Protocol
import okhttp3.Request
import okhttp3.Response
import okhttp3.ResponseBody.Companion.toResponseBody
import rx.Observable
import java.text.SimpleDateFormat
import java.util.Locale
@ -28,27 +33,29 @@ class SaturdayMorningBreakfastComics : HttpSource() {
override val lang = "en"
override val supportsLatest = false
override val client: OkHttpClient = network.client.newBuilder()
.addInterceptor(TextInterceptor())
.addInterceptor { chain ->
val request = chain.request()
val url = request.url
if (url.host != "thumbnail") return@addInterceptor chain.proceed(request)
private fun String.image() =
"https://fakeimg.ryd.tools/1500x2126/ffffff/000000/?font=museo&font_size=42&text=" + encode(
this,
)
// Taken from XKCD
private fun wordWrap(text: String) = buildString {
var charCount = 0
text.replace("\r\n", " ").split(' ').forEach { w ->
if (charCount > 25) {
append("\n")
charCount = 0
}
append(w).append(' ')
charCount += w.length + 1
val image = this::class.java
.getResourceAsStream("/assets/thumbnail.png")!!
.readBytes()
val responseBody = image.toResponseBody("image/png".toMediaType())
Response.Builder()
.request(request)
.protocol(Protocol.HTTP_1_1)
.code(200)
.message("OK")
.body(responseBody)
.build()
}
}
.build()
override fun fetchPopularManga(page: Int): Observable<MangasPage> {
val manga = SManga.create().apply {
private fun makeSManga(): SManga =
SManga.create().apply {
title = "Saturday Morning Breakfast Comics"
artist = "Zach Weinersmith"
author = "Zach Weinersmith"
@ -56,9 +63,11 @@ class SaturdayMorningBreakfastComics : HttpSource() {
url = "/comic/archive"
description =
"SMBC is a daily comic strip about life, philosophy, science, mathematics, and dirty jokes."
thumbnail_url = "https://fakeimg.ryd.tools/550x780/ffffff/6e7b91/?font=museo&text=SMBC"
thumbnail_url = "https://thumbnail/smbc.png"
}
override fun fetchPopularManga(page: Int): Observable<MangasPage> {
val manga = makeSManga()
return Observable.just(MangasPage(listOf(manga), false))
}
@ -68,7 +77,7 @@ class SaturdayMorningBreakfastComics : HttpSource() {
filters: FilterList,
): Observable<MangasPage> = Observable.just(MangasPage(emptyList(), false))
override fun fetchMangaDetails(manga: SManga): Observable<SManga> = Observable.just(manga)
override fun fetchMangaDetails(manga: SManga): Observable<SManga> = Observable.just(makeSManga())
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
return client.newCall(chapterListRequest(manga))
@ -98,7 +107,7 @@ class SaturdayMorningBreakfastComics : HttpSource() {
val image = document.select("img#cc-comic")
pages.add(Page(0, "", image.attr("abs:src")))
if (image.hasAttr("title")) {
pages.add(Page(1, "", wordWrap(image.attr("title")).image()))
pages.add(Page(1, "", TextInterceptorHelper.createUrl("", image.attr("title"))))
}
pages.add(Page(2, "", document.select("#aftercomic > img").attr("abs:src")))
return pages

View File

@ -1,7 +1,11 @@
ext {
extName = 'Swords Comic'
extClass = '.SwordsComic'
extVersionCode = 4
extVersionCode = 5
}
apply from: "$rootDir/common.gradle"
dependencies {
implementation(project(':lib:textinterceptor'))
}

View File

@ -1,6 +1,7 @@
package eu.kanade.tachiyomi.extension.en.swordscomic
import android.net.Uri
import eu.kanade.tachiyomi.lib.textinterceptor.TextInterceptor
import eu.kanade.tachiyomi.lib.textinterceptor.TextInterceptorHelper
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.MangasPage
import eu.kanade.tachiyomi.source.model.Page
@ -25,7 +26,7 @@ class SwordsComic : HttpSource() {
override val supportsLatest = false
override val client: OkHttpClient = network.cloudflareClient
override val client: OkHttpClient = network.cloudflareClient.newBuilder().addInterceptor(TextInterceptor()).build()
private fun createManga(): SManga {
return SManga.create().apply {
@ -92,21 +93,9 @@ class SwordsComic : HttpSource() {
if (!imageElement.hasAttr("title")) {
return listOf(Page(0, "", imageElement.attr("abs:src")))
}
val titleText = TextInterceptorHelper.createUrl("", imageElement.attr("title"))
val builder = StringBuilder()
var charCount = 0
for (word in imageElement.attr("title").splitToSequence(" ")) {
if (charCount + word.length > 45) {
builder.append("%0A")
charCount = 0
}
charCount += word.length + 1
builder.append(Uri.encode(word.uppercase()))
builder.append("+")
}
return listOf(Page(0, "", imageElement.attr("abs:src")), Page(1, "", "https://fakeimg.ryd.tools/1800x2252/978B65/000000/?text=$builder&font_size=60&font=comic+sans"))
return listOf(Page(0, "", imageElement.attr("abs:src")), Page(1, "", titleText))
}
override fun imageUrlParse(response: Response): String = throw UnsupportedOperationException()