diff --git a/lib/textinterceptor/build.gradle.kts b/lib/textinterceptor/build.gradle.kts new file mode 100644 index 000000000..e8086827c --- /dev/null +++ b/lib/textinterceptor/build.gradle.kts @@ -0,0 +1,22 @@ +plugins { + id("com.android.library") + kotlin("android") +} + +android { + compileSdk = AndroidConfig.compileSdk + + defaultConfig { + minSdk = AndroidConfig.minSdk + targetSdk = AndroidConfig.targetSdk + } +} + +repositories { + mavenCentral() +} + +dependencies { + compileOnly(libs.kotlin.stdlib) + compileOnly(libs.okhttp) +} diff --git a/lib/textinterceptor/src/main/AndroidManifest.xml b/lib/textinterceptor/src/main/AndroidManifest.xml new file mode 100644 index 000000000..6d52001ad --- /dev/null +++ b/lib/textinterceptor/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + diff --git a/lib/textinterceptor/src/main/java/eu/kanade/tachiyomi/lib/textinterceptor/TextInterceptor.kt b/lib/textinterceptor/src/main/java/eu/kanade/tachiyomi/lib/textinterceptor/TextInterceptor.kt new file mode 100644 index 000000000..b614c03b9 --- /dev/null +++ b/lib/textinterceptor/src/main/java/eu/kanade/tachiyomi/lib/textinterceptor/TextInterceptor.kt @@ -0,0 +1,121 @@ +package eu.kanade.tachiyomi.lib.textinterceptor + +import android.graphics.Bitmap +import android.graphics.Canvas +import android.graphics.Color +import android.graphics.Typeface +import android.net.Uri +import android.os.Build +import android.text.Html +import android.text.Layout +import android.text.StaticLayout +import android.text.TextPaint +import okhttp3.Interceptor +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.Protocol +import okhttp3.Response +import okhttp3.ResponseBody.Companion.toResponseBody +import java.io.ByteArrayOutputStream + +class TextInterceptor : Interceptor { + // With help from: + // https://github.com/tachiyomiorg/tachiyomi-extensions/pull/13304#issuecomment-1234532897 + // https://medium.com/over-engineering/drawing-multiline-text-to-canvas-on-android-9b98f0bfa16a + + companion object { + // Designer values: + private const val WIDTH: Int = 1000 + private const val X_PADDING: Float = 50f + private const val Y_PADDING: Float = 25f + private const val HEADING_FONT_SIZE: Float = 36f + private const val BODY_FONT_SIZE: Float = 30f + private const val SPACING_MULT: Float = 1.1f + private const val SPACING_ADD: Float = 2f + + // No need to touch this one: + private const val HOST = TextInterceptorHelper.HOST + } + + override fun intercept(chain: Interceptor.Chain): Response { + val request = chain.request() + val url = request.url + if (url.host != HOST) return chain.proceed(request) + + val creator = textFixer("Author's Notes from ${url.pathSegments[0]}") + val story = textFixer(url.pathSegments[1]) + + // Heading + val paintHeading = TextPaint().apply { + color = Color.BLACK + textSize = HEADING_FONT_SIZE + typeface = Typeface.DEFAULT_BOLD + isAntiAlias = true + } + + @Suppress("DEPRECATION") + val heading: StaticLayout = StaticLayout( + creator, paintHeading, (WIDTH - 2 * X_PADDING).toInt(), + Layout.Alignment.ALIGN_NORMAL, SPACING_MULT, SPACING_ADD, true + ) + + // Body + val paintBody = TextPaint().apply { + color = Color.BLACK + textSize = BODY_FONT_SIZE + typeface = Typeface.DEFAULT + isAntiAlias = true + } + + @Suppress("DEPRECATION") + val body: StaticLayout = StaticLayout( + story, paintBody, (WIDTH - 2 * X_PADDING).toInt(), + Layout.Alignment.ALIGN_NORMAL, SPACING_MULT, SPACING_ADD, true + ) + + // Image building + val imgHeight: Int = (heading.height + body.height + 2 * Y_PADDING).toInt() + val bitmap: Bitmap = Bitmap.createBitmap(WIDTH, imgHeight, Bitmap.Config.ARGB_8888) + val canvas: Canvas = Canvas(bitmap) + + // Image drawing + canvas.drawColor(Color.WHITE) + heading.draw(canvas, X_PADDING, Y_PADDING) + body.draw(canvas, X_PADDING, Y_PADDING + heading.height.toFloat()) + + // Image converting & returning + val stream = ByteArrayOutputStream() + bitmap.compress(Bitmap.CompressFormat.PNG, 0, stream) + val responseBody = stream.toByteArray().toResponseBody("image/png".toMediaType()) + return Response.Builder() + .request(request) + .protocol(Protocol.HTTP_1_1) + .code(200) + .message("OK") + .body(responseBody) + .build() + } + + private fun textFixer(htmlString: String): String { + return if (Build.VERSION.SDK_INT >= 24) { + Html.fromHtml(htmlString , Html.FROM_HTML_MODE_LEGACY).toString() + } else { + Html.fromHtml(htmlString).toString() + } + } + + private fun StaticLayout.draw(canvas: Canvas, x: Float, y: Float) { + canvas.save() + canvas.translate(x, y) + this.draw(canvas) + canvas.restore() + } +} + +object TextInterceptorHelper { + + const val HOST = "tachiyomi-lib-textinterceptor" + + fun createUrl(creator: String, text: String): String { + return "http://$HOST/" + Uri.encode(creator) + "/" + Uri.encode(text) + } +} diff --git a/multisrc/overrides/webtoons/webtoons/additional.gradle b/multisrc/overrides/webtoons/webtoons/additional.gradle new file mode 100644 index 000000000..8de52e9e0 --- /dev/null +++ b/multisrc/overrides/webtoons/webtoons/additional.gradle @@ -0,0 +1,3 @@ +dependencies { + implementation(project(':lib-textinterceptor')) +} diff --git a/multisrc/overrides/webtoons/webtoons/src/WebtoonsFactory.kt b/multisrc/overrides/webtoons/webtoons/src/WebtoonsFactory.kt index 20f3a88d0..9a3664e75 100644 --- a/multisrc/overrides/webtoons/webtoons/src/WebtoonsFactory.kt +++ b/multisrc/overrides/webtoons/webtoons/src/WebtoonsFactory.kt @@ -1,6 +1,5 @@ package eu.kanade.tachiyomi.extension.all.webtoons -import eu.kanade.tachiyomi.multisrc.webtoons.Webtoons import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.SourceFactory import java.text.SimpleDateFormat @@ -18,8 +17,8 @@ class WebtoonsFactory : SourceFactory { WebtoonsDE(), ) } -class WebtoonsEN : Webtoons("Webtoons.com", "https://www.webtoons.com", "en") -class WebtoonsID : Webtoons("Webtoons.com", "https://www.webtoons.com", "id") { +class WebtoonsEN : WebtoonsSrc("Webtoons.com", "https://www.webtoons.com", "en") +class WebtoonsID : WebtoonsSrc("Webtoons.com", "https://www.webtoons.com", "id") { // Override ID as part of the name was removed to be more consiten with other enteries override val id: Long = 8749627068478740298 @@ -36,8 +35,8 @@ class WebtoonsID : Webtoons("Webtoons.com", "https://www.webtoons.com", "id") { return GregorianCalendar(year.toInt(), monthIndex, day.toInt()).time.time } } -class WebtoonsTH : Webtoons("Webtoons.com", "https://www.webtoons.com", "th", dateFormat = SimpleDateFormat("d MMM yyyy", Locale("th"))) -class WebtoonsES : Webtoons("Webtoons.com", "https://www.webtoons.com", "es") { +class WebtoonsTH : WebtoonsSrc("Webtoons.com", "https://www.webtoons.com", "th", dateFormat = SimpleDateFormat("d MMM yyyy", Locale("th"))) +class WebtoonsES : WebtoonsSrc("Webtoons.com", "https://www.webtoons.com", "es") { // Android seems to be unable to parse es dates like Indonesian; we'll use a short hard-coded table instead. private val dateMap: Array = arrayOf( "ene", "feb", "mar", "abr", "may", "jun", "jul", "ago", "sep", "oct", "nov", "dic" @@ -50,9 +49,9 @@ class WebtoonsES : Webtoons("Webtoons.com", "https://www.webtoons.com", "es") { return GregorianCalendar(year.toInt(), monthIndex, day.toInt()).time.time } } -class WebtoonsFR : Webtoons("Webtoons.com", "https://www.webtoons.com", "fr", dateFormat = SimpleDateFormat("d MMM yyyy", Locale.FRENCH)) -class WebtoonsZH : Webtoons("Webtoons.com", "https://www.webtoons.com", "zh-Hant", "zh-hant", "zh_TW", SimpleDateFormat("yyyy/MM/dd", Locale.TRADITIONAL_CHINESE)) { +class WebtoonsFR : WebtoonsSrc("Webtoons.com", "https://www.webtoons.com", "fr", dateFormat = SimpleDateFormat("d MMM yyyy", Locale.FRENCH)) +class WebtoonsZH : WebtoonsSrc("Webtoons.com", "https://www.webtoons.com", "zh-Hant", "zh-hant", "zh_TW", SimpleDateFormat("yyyy/MM/dd", Locale.TRADITIONAL_CHINESE)) { // Due to lang code getting more specific override val id: Long = 2959982438613576472 } -class WebtoonsDE : Webtoons("Webtoons.com", "https://www.webtoons.com", "de", dateFormat = SimpleDateFormat("dd.MM.yyyy", Locale.GERMAN)) +class WebtoonsDE : WebtoonsSrc("Webtoons.com", "https://www.webtoons.com", "de", dateFormat = SimpleDateFormat("dd.MM.yyyy", Locale.GERMAN)) diff --git a/multisrc/overrides/webtoons/webtoons/src/WebtoonsSrc.kt b/multisrc/overrides/webtoons/webtoons/src/WebtoonsSrc.kt new file mode 100644 index 000000000..2698a9cc0 --- /dev/null +++ b/multisrc/overrides/webtoons/webtoons/src/WebtoonsSrc.kt @@ -0,0 +1,97 @@ +package eu.kanade.tachiyomi.extension.all.webtoons + +import android.app.Application +import android.content.SharedPreferences +import androidx.preference.PreferenceScreen +import androidx.preference.SwitchPreferenceCompat +import eu.kanade.tachiyomi.lib.textinterceptor.TextInterceptor +import eu.kanade.tachiyomi.lib.textinterceptor.TextInterceptorHelper +import eu.kanade.tachiyomi.multisrc.webtoons.Webtoons +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.source.ConfigurableSource +import eu.kanade.tachiyomi.source.model.Page +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive +import okhttp3.OkHttpClient +import org.jsoup.nodes.Document +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get +import java.text.SimpleDateFormat +import java.util.Locale + +open class WebtoonsSrc( + override val name: String, + override val baseUrl: String, + override val lang: String, + langCode: String = lang, + override val localeForCookie: String = lang, + dateFormat: SimpleDateFormat = SimpleDateFormat("MMM d, yyyy", Locale.ENGLISH) +) : ConfigurableSource, Webtoons(name, baseUrl, lang, langCode, localeForCookie, dateFormat) { + + override val client: OkHttpClient = super.client.newBuilder() + .addInterceptor(TextInterceptor()) + .build() + + private val preferences: SharedPreferences by lazy { + Injekt.get().getSharedPreferences("source_$id", 0x0000) + } + + override fun setupPreferenceScreen(screen: PreferenceScreen) { + val authorsNotesPref = SwitchPreferenceCompat(screen.context).apply { + key = SHOW_AUTHORS_NOTES_KEY + title = "Show author's notes" + summary = "Enable to see the author's notes at the end of chapters (if they're there)." + setDefaultValue(false) + + setOnPreferenceChangeListener { _, newValue -> + val checkValue = newValue as Boolean + preferences.edit().putBoolean(SHOW_AUTHORS_NOTES_KEY, checkValue).commit() + } + } + screen.addPreference(authorsNotesPref) + } + + private fun showAuthorsNotesPref() = preferences.getBoolean(SHOW_AUTHORS_NOTES_KEY, false) + + override fun pageListParse(document: Document): List { + var pages = document.select("div#_imageList > img").mapIndexed { i, element -> Page(i, "", element.attr("data-url")) } + + if (showAuthorsNotesPref()) { + val note = document.select("div.comment_area div.info_area p").text() + + if (note.isNotEmpty()) { + + val creator = document.select("div.creator_note span.author a").text().trim() + + pages = pages + Page( + pages.size, "", + TextInterceptorHelper.createUrl(creator, note) + ) + } + } + + if (pages.isNotEmpty()) { return pages } + + val docString = document.toString() + + val docUrlRegex = Regex("documentURL:.*?'(.*?)'") + val motiontoonPathRegex = Regex("jpg:.*?'(.*?)\\{") + + val docUrl = docUrlRegex.find(docString)!!.destructured.toList()[0] + val motiontoonPath = motiontoonPathRegex.find(docString)!!.destructured.toList()[0] + val motiontoonResponse = client.newCall(GET(docUrl, headers)).execute() + + val motiontoonJson = json.parseToJsonElement(motiontoonResponse.body!!.string()).jsonObject + val motiontoonImages = motiontoonJson["assets"]!!.jsonObject["image"]!!.jsonObject + + return motiontoonImages.entries + .filter { it.key.contains("layer") } + .mapIndexed { i, entry -> + Page(i, "", motiontoonPath + entry.value.jsonPrimitive.content) + } + } + + companion object { + private const val SHOW_AUTHORS_NOTES_KEY = "showAuthorsNotes" + } +} diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/webtoons/Webtoons.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/webtoons/Webtoons.kt index 7cbe7a84a..07adc7162 100644 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/webtoons/Webtoons.kt +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/webtoons/Webtoons.kt @@ -1,19 +1,6 @@ package eu.kanade.tachiyomi.multisrc.webtoons -import android.app.Application -import android.content.SharedPreferences -import android.graphics.Bitmap -import android.graphics.Canvas -import android.graphics.Color -import android.graphics.Typeface -import android.net.Uri -import android.text.Layout -import android.text.StaticLayout -import android.text.TextPaint -import androidx.preference.PreferenceScreen -import androidx.preference.SwitchPreferenceCompat import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.ConfigurableSource import eu.kanade.tachiyomi.source.model.Filter.Header import eu.kanade.tachiyomi.source.model.Filter.Select import eu.kanade.tachiyomi.source.model.Filter.Separator @@ -33,19 +20,13 @@ import okhttp3.Headers import okhttp3.HttpUrl import okhttp3.HttpUrl.Companion.toHttpUrlOrNull import okhttp3.Interceptor -import okhttp3.MediaType.Companion.toMediaType import okhttp3.OkHttpClient -import okhttp3.Protocol import okhttp3.Request import okhttp3.Response -import okhttp3.ResponseBody.Companion.toResponseBody import org.jsoup.nodes.Document import org.jsoup.nodes.Element import rx.Observable -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get import uy.kohesive.injekt.injectLazy -import java.io.ByteArrayOutputStream import java.net.SocketException import java.text.ParseException import java.text.SimpleDateFormat @@ -59,7 +40,7 @@ open class Webtoons( open val langCode: String = lang, open val localeForCookie: String = lang, private val dateFormat: SimpleDateFormat = SimpleDateFormat("MMM d, yyyy", Locale.ENGLISH) -) : ConfigurableSource, ParsedHttpSource() { +) : ParsedHttpSource() { override val supportsLatest = true @@ -84,7 +65,6 @@ open class Webtoons( } ) .addInterceptor(::sslRetryInterceptor) - .addInterceptor(TextInterceptor) .build() // m.webtoons.com throws an SSL error that can be solved by a simple retry @@ -112,16 +92,12 @@ open class Webtoons( } } - private val json: Json by injectLazy() + val json: Json by injectLazy() override fun popularMangaSelector() = "not using" override fun latestUpdatesSelector() = "div#dailyList > $day li > a" - private val preferences: SharedPreferences by lazy { - Injekt.get().getSharedPreferences("source_$id", 0x0000) - } - override fun headersBuilder(): Headers.Builder = super.headersBuilder() .add("Referer", "https://www.webtoons.com/$langCode/") @@ -129,23 +105,6 @@ open class Webtoons( .add("Referer", "https://m.webtoons.com") .build() - override fun setupPreferenceScreen(screen: PreferenceScreen) { - val authorsNotesPref = SwitchPreferenceCompat(screen.context).apply { - key = SHOW_AUTHORS_NOTES_KEY - title = "Show author's notes" - summary = "Enable to see the author's notes at the end of chapters (if they're there)." - setDefaultValue(false) - - setOnPreferenceChangeListener { _, newValue -> - val checkValue = newValue as Boolean - preferences.edit().putBoolean(SHOW_AUTHORS_NOTES_KEY, checkValue).commit() - } - } - screen.addPreference(authorsNotesPref) - } - - private fun showAuthorsNotesPref() = preferences.getBoolean(SHOW_AUTHORS_NOTES_KEY, false) - override fun popularMangaRequest(page: Int) = GET("$baseUrl/$langCode/dailySchedule", headers) override fun popularMangaParse(response: Response): MangasPage { @@ -317,20 +276,6 @@ open class Webtoons( override fun pageListParse(document: Document): List { var pages = document.select("div#_imageList > img").mapIndexed { i, element -> Page(i, "", element.attr("data-url")) } - if (showAuthorsNotesPref()) { - val note = document.select("div.comment_area div.info_area p").text() - - if (note.isNotEmpty()) { - - val creator = document.select("div.creator_note span.author a").text().trim() - - pages = pages + Page( - pages.size, "", - "http://note/" + Uri.encode(creator) + "/" + Uri.encode(note) - ) - } - } - if (pages.isNotEmpty()) { return pages } val docString = document.toString() @@ -354,102 +299,5 @@ open class Webtoons( companion object { const val URL_SEARCH_PREFIX = "url:" - private const val SHOW_AUTHORS_NOTES_KEY = "showAuthorsNotes" - } - - // TODO: Split off into library file or something, because Webtoons is using the exact same TextInterceptor - // src/en/tapastic/src/eu/kanade/tachiyomi/extension/en/tapastic/Tapastic.kt - object TextInterceptor : Interceptor { - // With help from: - // https://github.com/tachiyomiorg/tachiyomi-extensions/pull/13304#issuecomment-1234532897 - // https://medium.com/over-engineering/drawing-multiline-text-to-canvas-on-android-9b98f0bfa16a - - // Designer values: - private const val WIDTH: Int = 1000 - private const val X_PADDING: Float = 50f - private const val Y_PADDING: Float = 25f - private const val HEADING_FONT_SIZE: Float = 36f - private const val BODY_FONT_SIZE: Float = 30f - private const val SPACING_MULT: Float = 1.1f - private const val SPACING_ADD: Float = 2f - - // No need to touch this one: - private const val HOST = "note" - - override fun intercept(chain: Interceptor.Chain): Response { - val request = chain.request() - val url = request.url - if (url.host != HOST) return chain.proceed(request) - - val creator = textFixer("Author's Notes from ${url.pathSegments[0]}") - val story = textFixer(url.pathSegments[1]) - - // Heading - val paintHeading = TextPaint().apply { - color = Color.BLACK - textSize = HEADING_FONT_SIZE - typeface = Typeface.DEFAULT_BOLD - isAntiAlias = true - } - - @Suppress("DEPRECATION") - val heading: StaticLayout = StaticLayout( - creator, paintHeading, (WIDTH - 2 * X_PADDING).toInt(), - Layout.Alignment.ALIGN_NORMAL, SPACING_MULT, SPACING_ADD, true - ) - - // Body - val paintBody = TextPaint().apply { - color = Color.BLACK - textSize = BODY_FONT_SIZE - typeface = Typeface.DEFAULT - isAntiAlias = true - } - - @Suppress("DEPRECATION") - val body: StaticLayout = StaticLayout( - story, paintBody, (WIDTH - 2 * X_PADDING).toInt(), - Layout.Alignment.ALIGN_NORMAL, SPACING_MULT, SPACING_ADD, true - ) - - // Image building - val imgHeight: Int = (heading.height + body.height + 2 * Y_PADDING).toInt() - val bitmap: Bitmap = Bitmap.createBitmap(WIDTH, imgHeight, Bitmap.Config.ARGB_8888) - val canvas: Canvas = Canvas(bitmap) - - // Image drawing - canvas.drawColor(Color.WHITE) - heading.draw(canvas, X_PADDING, Y_PADDING) - body.draw(canvas, X_PADDING, Y_PADDING + heading.height.toFloat()) - - // Image converting & returning - val stream: ByteArrayOutputStream = ByteArrayOutputStream() - bitmap.compress(Bitmap.CompressFormat.PNG, 0, stream) - val responseBody = stream.toByteArray().toResponseBody("image/png".toMediaType()) - return Response.Builder() - .request(request) - .protocol(Protocol.HTTP_1_1) - .code(200) - .message("OK") - .body(responseBody) - .build() - } - - private fun textFixer(t: String): String { - return t - .replace("&", "&") - .replace("'", "'") - .replace(""", "\"") - .replace("<", "<") - .replace(">", ">") - .replace("\\s*
\\s*".toRegex(), "\n") - } - - private fun StaticLayout.draw(canvas: Canvas, x: Float, y: Float) { - canvas.save() - canvas.translate(x, y) - this.draw(canvas) - canvas.restore() - } } } diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/webtoons/WebtoonsTranslate.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/webtoons/WebtoonsTranslate.kt index 642e11cdb..7c5b967d1 100644 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/webtoons/WebtoonsTranslate.kt +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/webtoons/WebtoonsTranslate.kt @@ -7,7 +7,6 @@ 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 kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.boolean import kotlinx.serialization.json.contentOrNull @@ -24,7 +23,6 @@ import okhttp3.Response import org.jsoup.nodes.Document import org.jsoup.nodes.Element import rx.Observable -import uy.kohesive.injekt.injectLazy open class WebtoonsTranslate( override val name: String, @@ -42,8 +40,6 @@ open class WebtoonsTranslate( private val pageSize = 24 - private val json: Json by injectLazy() - override fun headersBuilder(): Headers.Builder = super.headersBuilder() .removeAll("Referer") .add("Referer", mobileBaseUrl.toString()) diff --git a/settings.gradle.kts b/settings.gradle.kts index a1f75eb5b..12157fe60 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,6 +1,6 @@ include(":core") -listOf("dataimage", "unpacker", "cryptoaes").forEach { +listOf("dataimage", "unpacker", "cryptoaes", "textinterceptor").forEach { include(":lib-$it") project(":lib-$it").projectDir = File("lib/$it") } diff --git a/src/en/tapastic/build.gradle b/src/en/tapastic/build.gradle index c9597ef64..f2a387f54 100644 --- a/src/en/tapastic/build.gradle +++ b/src/en/tapastic/build.gradle @@ -11,3 +11,7 @@ ext { } apply from: "$rootDir/common.gradle" + +dependencies { + implementation(project(':lib-textinterceptor')) +} \ No newline at end of file diff --git a/src/en/tapastic/src/eu/kanade/tachiyomi/extension/en/tapastic/Tapastic.kt b/src/en/tapastic/src/eu/kanade/tachiyomi/extension/en/tapastic/Tapastic.kt index e4e38dd65..0766618e7 100644 --- a/src/en/tapastic/src/eu/kanade/tachiyomi/extension/en/tapastic/Tapastic.kt +++ b/src/en/tapastic/src/eu/kanade/tachiyomi/extension/en/tapastic/Tapastic.kt @@ -2,18 +2,12 @@ package eu.kanade.tachiyomi.extension.en.tapastic import android.app.Application import android.content.SharedPreferences -import android.graphics.Bitmap -import android.graphics.Bitmap.CompressFormat -import android.graphics.Canvas -import android.graphics.Color -import android.graphics.Typeface import android.net.Uri -import android.text.Layout -import android.text.StaticLayout -import android.text.TextPaint import android.webkit.CookieManager import androidx.preference.PreferenceScreen import androidx.preference.SwitchPreferenceCompat +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.Filter @@ -33,20 +27,15 @@ import okhttp3.CookieJar import okhttp3.Headers import okhttp3.HttpUrl import okhttp3.HttpUrl.Companion.toHttpUrlOrNull -import okhttp3.Interceptor -import okhttp3.MediaType.Companion.toMediaType import okhttp3.OkHttpClient -import okhttp3.Protocol import okhttp3.Request import okhttp3.Response -import okhttp3.ResponseBody.Companion.toResponseBody import org.jsoup.Jsoup import org.jsoup.nodes.Document import org.jsoup.nodes.Element import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import uy.kohesive.injekt.injectLazy -import java.io.ByteArrayOutputStream import java.text.SimpleDateFormat import java.util.Locale @@ -108,7 +97,7 @@ class Tapastic : ConfigurableSource, ParsedHttpSource() { } } ) - .addInterceptor(TextInterceptor) + .addInterceptor(TextInterceptor()) .build() private val preferences: SharedPreferences by lazy { @@ -372,7 +361,7 @@ class Tapastic : ConfigurableSource, ParsedHttpSource() { pages = pages + Page( pages.size, "", - "http://note/" + Uri.encode(creator) + "/" + Uri.encode(episodeStory) + TextInterceptorHelper.createUrl(creator, episodeStory) ) } } @@ -528,100 +517,4 @@ class Tapastic : ConfigurableSource, ParsedHttpSource() { private const val SHOW_AUTHORS_NOTES_KEY = "showAuthorsNotes" private const val USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:105.0) Gecko/20100101 Firefox/105.0" } - - // TODO: Split off into library file or something, because Webtoons is using the exact same TextInterceptor - // multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/webtoons/Webtoons.kt - object TextInterceptor : Interceptor { - // With help from: - // https://github.com/tachiyomiorg/tachiyomi-extensions/pull/13304#issuecomment-1234532897 - // https://medium.com/over-engineering/drawing-multiline-text-to-canvas-on-android-9b98f0bfa16a - - // Designer values: - private const val WIDTH: Int = 1000 - private const val X_PADDING: Float = 50f - private const val Y_PADDING: Float = 25f - private const val HEADING_FONT_SIZE: Float = 36f - private const val BODY_FONT_SIZE: Float = 30f - private const val SPACING_MULT: Float = 1.1f - private const val SPACING_ADD: Float = 2f - - // No need to touch this one: - private const val HOST = "note" - - override fun intercept(chain: Interceptor.Chain): Response { - val request = chain.request() - val url = request.url - if (url.host != HOST) return chain.proceed(request) - - val creator = textFixer("Author's Notes from ${url.pathSegments[0]}") - val story = textFixer(url.pathSegments[1]) - - // Heading - val paintHeading = TextPaint().apply { - color = Color.BLACK - textSize = HEADING_FONT_SIZE - typeface = Typeface.DEFAULT_BOLD - isAntiAlias = true - } - - @Suppress("DEPRECATION") - val heading: StaticLayout = StaticLayout( - creator, paintHeading, (WIDTH - 2 * X_PADDING).toInt(), - Layout.Alignment.ALIGN_NORMAL, SPACING_MULT, SPACING_ADD, true - ) - - // Body - val paintBody = TextPaint().apply { - color = Color.BLACK - textSize = BODY_FONT_SIZE - typeface = Typeface.DEFAULT - isAntiAlias = true - } - - @Suppress("DEPRECATION") - val body: StaticLayout = StaticLayout( - story, paintBody, (WIDTH - 2 * X_PADDING).toInt(), - Layout.Alignment.ALIGN_NORMAL, SPACING_MULT, SPACING_ADD, true - ) - - // Image building - val imgHeight: Int = (heading.height + body.height + 2 * Y_PADDING).toInt() - val bitmap: Bitmap = Bitmap.createBitmap(WIDTH, imgHeight, Bitmap.Config.ARGB_8888) - val canvas: Canvas = Canvas(bitmap) - - // Image drawing - canvas.drawColor(Color.WHITE) - heading.draw(canvas, X_PADDING, Y_PADDING) - body.draw(canvas, X_PADDING, Y_PADDING + heading.height.toFloat()) - - // Image converting & returning - val stream: ByteArrayOutputStream = ByteArrayOutputStream() - bitmap.compress(CompressFormat.PNG, 0, stream) - val responseBody = stream.toByteArray().toResponseBody("image/png".toMediaType()) - return Response.Builder() - .request(request) - .protocol(Protocol.HTTP_1_1) - .code(200) - .message("OK") - .body(responseBody) - .build() - } - - private fun textFixer(t: String): String { - return t - .replace("&", "&") - .replace("'", "'") - .replace(""", "\"") - .replace("<", "<") - .replace(">", ">") - .replace("\\s*
\\s*".toRegex(), "\n") - } - - private fun StaticLayout.draw(canvas: Canvas, x: Float, y: Float) { - canvas.save() - canvas.translate(x, y) - this.draw(canvas) - canvas.restore() - } - } }