smbc: add extension for smbc-comics.com (#10192)
* smbc: add extension for smbc-comics.com Adds an extension for Saturday Morning Breakfast Comics * hiveworks: remove references to Saturday Morning Breakfast Comics Removes code that was made to handle reading SMBC specifically. If a user still has the comic in the Hiveworks extension, they'll get a warning to migrate to the SMBC extension.
This commit is contained in:
parent
804fd752e8
commit
b6bce67308
@ -1,7 +1,7 @@
|
|||||||
ext {
|
ext {
|
||||||
extName = 'Hiveworks Comics'
|
extName = 'Hiveworks Comics'
|
||||||
extClass = '.Hiveworks'
|
extClass = '.Hiveworks'
|
||||||
extVersionCode = 10
|
extVersionCode = 11
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: "$rootDir/common.gradle"
|
apply from: "$rootDir/common.gradle"
|
||||||
|
@ -23,6 +23,10 @@ import java.util.Date
|
|||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Code that used to handle Saturday Morning Breakfast Comics has been split to its
|
||||||
|
* own separate extension at eu.kanade.tachiyomi.extension.en.saturdaymorningbreakfastcomics
|
||||||
|
*/
|
||||||
class Hiveworks : ParsedHttpSource() {
|
class Hiveworks : ParsedHttpSource() {
|
||||||
|
|
||||||
// Info
|
// Info
|
||||||
@ -39,28 +43,6 @@ class Hiveworks : ParsedHttpSource() {
|
|||||||
.readTimeout(1, TimeUnit.MINUTES)
|
.readTimeout(1, TimeUnit.MINUTES)
|
||||||
.retryOnConnectionFailure(true)
|
.retryOnConnectionFailure(true)
|
||||||
.followRedirects(true)
|
.followRedirects(true)
|
||||||
.addNetworkInterceptor { chain ->
|
|
||||||
val request = chain.request()
|
|
||||||
if (!request.url.toString().contains("smbc-comics")) {
|
|
||||||
return@addNetworkInterceptor chain.proceed(request)
|
|
||||||
}
|
|
||||||
|
|
||||||
val response = chain.proceed(request)
|
|
||||||
// As of March 2025, SMBC chapter list page returns status code 500 even
|
|
||||||
// though it still has correct data. Do not throw an error in this case.
|
|
||||||
//
|
|
||||||
// I reported this error to SMBC on 2025-05-28 and it was not fixed by
|
|
||||||
// 2025-06-11, but even if it is fixed eventually, the same problem might
|
|
||||||
// occur again in the future.
|
|
||||||
if (response.code == 500) {
|
|
||||||
val newResponse = response.newBuilder()
|
|
||||||
.code(200)
|
|
||||||
.build()
|
|
||||||
newResponse
|
|
||||||
} else {
|
|
||||||
response
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
// Popular
|
// Popular
|
||||||
@ -216,6 +198,7 @@ class Hiveworks : ParsedHttpSource() {
|
|||||||
when {
|
when {
|
||||||
"sssscomic" in uri.toString() -> uri.appendQueryParameter("id", "archive") // sssscomic uses query string in url
|
"sssscomic" in uri.toString() -> uri.appendQueryParameter("id", "archive") // sssscomic uses query string in url
|
||||||
"awkwardzombie" in uri.toString() -> uri.appendPath("awkward-zombie").appendPath("archive")
|
"awkwardzombie" in uri.toString() -> uri.appendPath("awkward-zombie").appendPath("archive")
|
||||||
|
"smbc-comics" in uri.toString() -> throw Exception("Migrate to the Saturday Morning Breakfast Comics extension to read this comic")
|
||||||
else -> {
|
else -> {
|
||||||
uri.appendPath("comic")
|
uri.appendPath("comic")
|
||||||
uri.appendPath("archive")
|
uri.appendPath("archive")
|
||||||
@ -268,10 +251,6 @@ class Hiveworks : ParsedHttpSource() {
|
|||||||
|
|
||||||
// Site specific pages can be added here
|
// Site specific pages can be added here
|
||||||
when {
|
when {
|
||||||
"smbc-comics" in url -> {
|
|
||||||
pages.add(Page(pages.size, "", document.select("div#aftercomic img").attr("src")))
|
|
||||||
pages.add(Page(pages.size, "", smbcTextHandler(document)))
|
|
||||||
}
|
|
||||||
"sssscomic" in url -> {
|
"sssscomic" in url -> {
|
||||||
val urlPath = document.select("img.comicnormal").attr("src")
|
val urlPath = document.select("img.comicnormal").attr("src")
|
||||||
val urlimg = response.request.url.resolve("../../$urlPath").toString()
|
val urlimg = response.request.url.resolve("../../$urlPath").toString()
|
||||||
@ -497,40 +476,6 @@ class Hiveworks : ParsedHttpSource() {
|
|||||||
return chapters
|
return chapters
|
||||||
}
|
}
|
||||||
|
|
||||||
// Builds Image from mouse tooltip text
|
|
||||||
private fun smbcTextHandler(document: Document): String {
|
|
||||||
val title = document.select("title").text().trim()
|
|
||||||
val altText = document.select("div#cc-comicbody img").attr("title")
|
|
||||||
|
|
||||||
val titleWords: Sequence<String> = title.splitToSequence(" ")
|
|
||||||
val altTextWords: Sequence<String> = altText.splitToSequence(" ")
|
|
||||||
|
|
||||||
val builder = StringBuilder()
|
|
||||||
var count = 0
|
|
||||||
|
|
||||||
for (i in titleWords) {
|
|
||||||
if (count != 0 && count.rem(7) == 0) {
|
|
||||||
builder.append("%0A")
|
|
||||||
}
|
|
||||||
builder.append(i).append("+")
|
|
||||||
count++
|
|
||||||
}
|
|
||||||
builder.append("%0A%0A")
|
|
||||||
|
|
||||||
var charCount = 0
|
|
||||||
|
|
||||||
for (i in altTextWords) {
|
|
||||||
if (charCount > 25) {
|
|
||||||
builder.append("%0A")
|
|
||||||
charCount = 0
|
|
||||||
}
|
|
||||||
builder.append(i).append("+")
|
|
||||||
charCount += i.length + 1
|
|
||||||
}
|
|
||||||
|
|
||||||
return "https://fakeimg.ryd.tools/1500x2126/ffffff/000000/?text=$builder&font_size=42&font=museo"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Used to throw custom error codes for http codes
|
// Used to throw custom error codes for http codes
|
||||||
private fun Call.asObservableSuccess(): Observable<Response> {
|
private fun Call.asObservableSuccess(): Observable<Response> {
|
||||||
return asObservable().doOnNext { response ->
|
return asObservable().doOnNext { response ->
|
||||||
|
8
src/en/saturdaymorningbreakfastcomics/build.gradle
Normal file
8
src/en/saturdaymorningbreakfastcomics/build.gradle
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
ext {
|
||||||
|
extName = 'Saturday Morning Breakfast Comics'
|
||||||
|
extClass = '.SaturdayMorningBreakfastComics'
|
||||||
|
extVersionCode = 1
|
||||||
|
isNsfw = false
|
||||||
|
}
|
||||||
|
|
||||||
|
apply from: "$rootDir/common.gradle"
|
Binary file not shown.
After Width: | Height: | Size: 2.3 KiB |
Binary file not shown.
After Width: | Height: | Size: 1.4 KiB |
Binary file not shown.
After Width: | Height: | Size: 3.4 KiB |
Binary file not shown.
After Width: | Height: | Size: 5.7 KiB |
Binary file not shown.
After Width: | Height: | Size: 8.7 KiB |
@ -0,0 +1,134 @@
|
|||||||
|
package eu.kanade.tachiyomi.extension.en.saturdaymorningbreakfastcomics
|
||||||
|
|
||||||
|
import android.net.Uri.encode
|
||||||
|
import eu.kanade.tachiyomi.network.asObservable
|
||||||
|
import eu.kanade.tachiyomi.source.model.FilterList
|
||||||
|
import eu.kanade.tachiyomi.source.model.MangasPage
|
||||||
|
import eu.kanade.tachiyomi.source.model.Page
|
||||||
|
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 keiyoushi.utils.tryParse
|
||||||
|
import okhttp3.Request
|
||||||
|
import okhttp3.Response
|
||||||
|
import rx.Observable
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Split from Hiveworks extension
|
||||||
|
*/
|
||||||
|
class SaturdayMorningBreakfastComics : HttpSource() {
|
||||||
|
|
||||||
|
override val name = "Saturday Morning Breakfast Comics"
|
||||||
|
|
||||||
|
override val baseUrl = "https://smbc-comics.com"
|
||||||
|
|
||||||
|
override val lang = "en"
|
||||||
|
|
||||||
|
override val supportsLatest = false
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun fetchPopularManga(page: Int): Observable<MangasPage> {
|
||||||
|
val manga = SManga.create().apply {
|
||||||
|
title = "Saturday Morning Breakfast Comics"
|
||||||
|
artist = "Zach Weinersmith"
|
||||||
|
author = "Zach Weinersmith"
|
||||||
|
status = SManga.ONGOING
|
||||||
|
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"
|
||||||
|
}
|
||||||
|
|
||||||
|
return Observable.just(MangasPage(listOf(manga), false))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun fetchSearchManga(
|
||||||
|
page: Int,
|
||||||
|
query: String,
|
||||||
|
filters: FilterList,
|
||||||
|
): Observable<MangasPage> = Observable.just(MangasPage(emptyList(), false))
|
||||||
|
|
||||||
|
override fun fetchMangaDetails(manga: SManga): Observable<SManga> = Observable.just(manga)
|
||||||
|
|
||||||
|
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
|
||||||
|
return client.newCall(chapterListRequest(manga))
|
||||||
|
.asObservable()
|
||||||
|
.map { response ->
|
||||||
|
if (!response.isSuccessful && response.code != 500) {
|
||||||
|
response.close()
|
||||||
|
throw Exception("HTTP ${response.code}")
|
||||||
|
}
|
||||||
|
response.asJsoup().select("option[value*=\"comic/\"]")
|
||||||
|
.mapIndexed { index, element ->
|
||||||
|
val chapter = SChapter.create()
|
||||||
|
chapter.url = "/${element.attr("value")}"
|
||||||
|
val (date, title) = element.text().split(" - ")
|
||||||
|
chapter.name = title
|
||||||
|
chapter.date_upload = dateFormat.tryParse(date)
|
||||||
|
chapter.chapter_number = (index + 1).toFloat()
|
||||||
|
chapter
|
||||||
|
}
|
||||||
|
.reversed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun pageListParse(response: Response): List<Page> {
|
||||||
|
val document = response.asJsoup()
|
||||||
|
val pages = mutableListOf<Page>()
|
||||||
|
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(2, "", document.select("#aftercomic > img").attr("abs:src")))
|
||||||
|
return pages
|
||||||
|
}
|
||||||
|
|
||||||
|
private val dateFormat by lazy {
|
||||||
|
SimpleDateFormat("MMMM d, yyyy", Locale.ENGLISH)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun chapterListParse(response: Response): List<SChapter> =
|
||||||
|
throw UnsupportedOperationException()
|
||||||
|
|
||||||
|
override fun popularMangaRequest(page: Int): Request = throw UnsupportedOperationException()
|
||||||
|
|
||||||
|
override fun searchMangaParse(response: Response): MangasPage =
|
||||||
|
throw UnsupportedOperationException()
|
||||||
|
|
||||||
|
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request =
|
||||||
|
throw UnsupportedOperationException()
|
||||||
|
|
||||||
|
override fun imageUrlParse(response: Response): String = throw UnsupportedOperationException()
|
||||||
|
|
||||||
|
override fun latestUpdatesParse(response: Response): MangasPage =
|
||||||
|
throw UnsupportedOperationException()
|
||||||
|
|
||||||
|
override fun latestUpdatesRequest(page: Int): Request = throw UnsupportedOperationException()
|
||||||
|
|
||||||
|
override fun mangaDetailsParse(response: Response): SManga =
|
||||||
|
throw UnsupportedOperationException()
|
||||||
|
|
||||||
|
override fun popularMangaParse(response: Response): MangasPage =
|
||||||
|
throw UnsupportedOperationException()
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user