From 0a2a26e48ccf4d377a73e32e76c9a718380485c0 Mon Sep 17 00:00:00 2001 From: ObserverOfTime Date: Mon, 2 Aug 2021 18:01:05 +0300 Subject: [PATCH] Implement latest updates in Guya (#8355) --- .../src/MagicalTranslators.kt | 16 +- .../eu/kanade/tachiyomi/multisrc/guya/Guya.kt | 208 ++++++++---------- .../tachiyomi/multisrc/guya/GuyaGenerator.kt | 4 +- 3 files changed, 109 insertions(+), 119 deletions(-) diff --git a/multisrc/overrides/guya/magicaltranslators/src/MagicalTranslators.kt b/multisrc/overrides/guya/magicaltranslators/src/MagicalTranslators.kt index 6f664276a..5e1956133 100644 --- a/multisrc/overrides/guya/magicaltranslators/src/MagicalTranslators.kt +++ b/multisrc/overrides/guya/magicaltranslators/src/MagicalTranslators.kt @@ -1,13 +1,12 @@ package eu.kanade.tachiyomi.extension.all.magicaltranslators import eu.kanade.tachiyomi.multisrc.guya.Guya -import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.SourceFactory import eu.kanade.tachiyomi.source.model.MangasPage import okhttp3.Response class MagicalTranslatorsFactory : SourceFactory { - override fun createSources(): List = listOf( + override fun createSources() = listOf( MagicalTranslatorsEN(), MagicalTranslatorsPL(), ) @@ -15,18 +14,23 @@ class MagicalTranslatorsFactory : SourceFactory { abstract class MagicalTranslatorsCommon(lang: String) : Guya("Magical Translators", "https://mahoushoujobu.com", lang) { + protected abstract fun filterMangasPage(mangasPage: MangasPage): MangasPage + override fun popularMangaParse(response: Response): MangasPage = filterMangasPage(super.popularMangaParse(response)) - override fun proxySearchMangaParse(response: Response, slug: String): MangasPage = - filterMangasPage(super.proxySearchMangaParse(response, slug)) + override fun latestUpdatesParse(response: Response): MangasPage = + filterMangasPage(super.latestUpdatesParse(response)) + + override fun proxySearchMangaParse(response: Response, query: String): MangasPage = + filterMangasPage(super.proxySearchMangaParse(response, query)) override fun searchMangaParseWithSlug(response: Response, slug: String): MangasPage = filterMangasPage(super.searchMangaParseWithSlug(response, slug)) - override fun searchMangaParse(response: Response, slug: String): MangasPage = - filterMangasPage(super.searchMangaParse(response, slug)) + override fun searchMangaParse(response: Response, query: String): MangasPage = + filterMangasPage(super.searchMangaParse(response, query)) } class MagicalTranslatorsEN : MagicalTranslatorsCommon("en") { diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/guya/Guya.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/guya/Guya.kt index ae472b72f..7bb1ae9cd 100644 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/guya/Guya.kt +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/guya/Guya.kt @@ -3,6 +3,8 @@ package eu.kanade.tachiyomi.multisrc.guya import android.app.Application import android.content.SharedPreferences import android.os.Build +import androidx.preference.ListPreference +import androidx.preference.PreferenceScreen import eu.kanade.tachiyomi.BuildConfig import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.asObservable @@ -23,26 +25,23 @@ import rx.Observable import rx.schedulers.Schedulers import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get -import java.util.HashMap abstract class Guya( override val name: String, override val baseUrl: String, - override val lang: String) : ConfigurableSource, HttpSource() { + override val lang: String +) : ConfigurableSource, HttpSource() { - override val supportsLatest = false + override val supportsLatest = true - private val scanlatorCacheUrl = "$baseUrl/api/get_all_groups/" + private val scanlatorCacheUrl by lazy { "$baseUrl/api/get_all_groups/" } - override fun headersBuilder() = Headers.Builder().apply { - add( - "User-Agent", - "(Android ${Build.VERSION.RELEASE}; " + - "${Build.MANUFACTURER} ${Build.MODEL}) " + - "Tachiyomi/${BuildConfig.VERSION_NAME} " + - Build.ID - ) - } + override fun headersBuilder() = Headers.Builder().add( + "User-Agent", + "(Android ${Build.VERSION.RELEASE}; " + + "${Build.MANUFACTURER} ${Build.MODEL}) " + + "Tachiyomi/${BuildConfig.VERSION_NAME} ${Build.ID}" + ) private val scanlators: ScanlatorStore = ScanlatorStore() @@ -50,6 +49,7 @@ abstract class Guya( private val preferences: SharedPreferences by lazy { Injekt.get().getSharedPreferences("source_$id", 0x0000) } + private val scanlatorPreference = "SCANLATOR_PREFERENCE" // Request builder for the "browse" page of the manga @@ -63,6 +63,23 @@ abstract class Guya( return parseManga(JSONObject(res)) } + override fun latestUpdatesRequest(page: Int): Request { + return popularMangaRequest(page) + } + + override fun latestUpdatesParse(response: Response): MangasPage { + val payload = JSONObject(response.body!!.string()) + val mangas = sortedMapOf() + + for (series in payload.keys()) { + val json = payload.getJSONObject(series) + val timestamp = json.getLong("last_updated") + mangas[timestamp] = parseMangaFromJson(json, "", series) + } + + return MangasPage(mangas.values.reversed(), false) + } + // Overridden to use our overload override fun fetchMangaDetails(manga: SManga): Observable { return when { @@ -91,11 +108,6 @@ abstract class Guya( } } - // Stub - override fun mangaDetailsParse(response: Response): SManga { - throw Exception("Unused") - } - private fun mangaDetailsParse(response: Response, manga: SManga): SManga { val res = response.body!!.string() return parseMangaFromJson(JSONObject(res), "", manga.title) @@ -125,14 +137,9 @@ abstract class Guya( return GET("$baseUrl/api/series/${manga.url}/", headers) } - override fun chapterListParse(response: Response): List { - throw Exception("Unused") - } - // Called after the request private fun chapterListParse(response: Response, manga: SManga): List { - val res = response.body!!.string() - return parseChapterList(res, manga) + return parseChapterList(response.body!!.string(), manga) } // Overridden fetch so that we use our overloaded method instead @@ -159,11 +166,6 @@ abstract class Guya( return GET("$baseUrl/api/series/${chapter.url.split("/")[0]}/", headers) } - // Stub - override fun pageListParse(response: Response): List { - throw Exception("Unused") - } - private fun pageListParse(response: Response, chapter: SChapter): List { val res = response.body!!.string() @@ -175,7 +177,7 @@ abstract class Guya( val metadata = JSONObject() metadata.put("chapter", chapterNum) - metadata.put("scanlator", scanlators.getKeyFromValue(chapter.scanlator.toString())) + metadata.put("scanlator", scanlators.getKeyFromValue(chapter.scanlator ?: "")) metadata.put("slug", json.getString("slug")) metadata.put( "folder", @@ -198,7 +200,7 @@ abstract class Guya( } } query.startsWith(PROXY_PREFIX) && query.contains("/") -> { - client.newCall(proxySearchMangaRequest(page, query, filters)) + client.newCall(proxySearchMangaRequest(query)) .asObservableSuccess() .map { response -> proxySearchMangaParse(response, query) @@ -218,17 +220,11 @@ abstract class Guya( return GET("$baseUrl/api/get_all_series/", headers) } - override fun searchMangaParse(response: Response): MangasPage { - throw Exception("Unused.") - } - protected open fun searchMangaParseWithSlug(response: Response, slug: String): MangasPage { val results = JSONObject(response.body!!.string()) - val mangaIter = results.keys() val truncatedJSON = JSONObject() - while (mangaIter.hasNext()) { - val mangaTitle = mangaIter.next() + for (mangaTitle in results.keys()) { val mangaDetails = results.getJSONObject(mangaTitle) if (mangaDetails.get("slug") == slug) { @@ -244,11 +240,8 @@ abstract class Guya( val json = JSONObject(res) val truncatedJSON = JSONObject() - val iter = json.keys() - - while (iter.hasNext()) { - val candidate = iter.next() - if (candidate.contains(query.toRegex(RegexOption.IGNORE_CASE))) { + for (candidate in json.keys()) { + if (candidate.contains(query, ignoreCase = true)) { truncatedJSON.put(candidate, json.get(candidate)) } } @@ -256,8 +249,8 @@ abstract class Guya( return parseManga(truncatedJSON) } - override fun setupPreferenceScreen(screen: androidx.preference.PreferenceScreen) { - val preference = androidx.preference.ListPreference(screen.context).apply { + override fun setupPreferenceScreen(screen: PreferenceScreen) { + val preference = ListPreference(screen.context).apply { key = "preferred_scanlator" title = "Preferred scanlator" entries = arrayOf() @@ -271,7 +264,7 @@ abstract class Guya( "on chapter refresh/update. It will get the next available if " + "your preferred scanlator isn't an option (yet)." - this.setDefaultValue("1") + setDefaultValue("1") setOnPreferenceChangeListener { _, newValue -> val selected = newValue.toString() @@ -332,27 +325,23 @@ abstract class Guya( .getJSONObject("groups") .getJSONArray(groupNum) } - val pageArray = ArrayList() - for (i in 0 until pages.length()) { - val page = if (pages.optJSONObject(i) != null) { - pages.getJSONObject(i).getString("src") - } else { - pages[i] - } - pageArray.add(Page(i + 1, "", page.toString())) + return List(pages.length()) { + Page( + it + 1, + "", + pages.optJSONObject(it)?.getString("src") + ?: pages[it].toString() + ) } - return pageArray } - private fun proxySearchMangaRequest(page: Int, query: String, filters: FilterList): Request { + private fun proxySearchMangaRequest(query: String): Request { return proxySeriesRequest(query) } protected open fun proxySearchMangaParse(response: Response, query: String): MangasPage { - return MangasPage( - arrayListOf(parseMangaFromJson(JSONObject(response.body!!.string()), query)), - false - ) + val json = JSONObject(response.body!!.string()) + return MangasPage(listOf(parseMangaFromJson(json, query)), false) } // ------------- Helpers and whatnot --------------- @@ -363,7 +352,7 @@ abstract class Guya( val chapters = response.getJSONObject("chapters") val mapping = response.getJSONObject("groups") - val chapterList = ArrayList() + val chapterList = mutableListOf() val iter = chapters.keys() @@ -408,11 +397,9 @@ abstract class Guya( chapter.chapter_number = chapterNum.toFloat() chapter.url = if (groups.optJSONArray(groupNum) != null) { - val mangaUrl = manga.url - "$mangaUrl/$chapterNum/$groupNum" + "${manga.url}/$chapterNum/$groupNum" } else { - val url = groups.getString(groupNum) - "$PROXY_PREFIX$url" + "$PROXY_PREFIX${groups.getString(groupNum)}" } chapterList.add(chapter) } @@ -425,15 +412,11 @@ abstract class Guya( // Helper function to get all the listings private fun parseManga(payload: JSONObject): MangasPage { - val mangas = ArrayList() + val mangas = mutableListOf() - val iter = payload.keys() - - while (iter.hasNext()) { - val series = iter.next() + for (series in payload.keys()) { val json = payload.getJSONObject(series) - val manga = parseMangaFromJson(json, "", series) - mangas.add(manga) + mangas += parseMangaFromJson(json, "", series) } return MangasPage(mangas, false) @@ -442,14 +425,19 @@ abstract class Guya( // Takes a json of the manga to parse private fun parseMangaFromJson(json: JSONObject, slug: String, title: String = ""): SManga { val manga = SManga.create() - manga.title = if (title.isNotEmpty()) title else json.getString("title") - manga.artist = json.optString("artist", "Unknown") - manga.author = json.optString("author", "Unknown") - manga.description = json.optString("description", "None") + manga.title = title.ifEmpty { json.getString("title") } + manga.artist = json.optString("artist") + manga.author = json.optString("author") + manga.description = json.optString("description") manga.url = if (slug.startsWith(PROXY_PREFIX)) slug else json.getString("slug") - val cover = json.optString("cover", "") - manga.thumbnail_url = if (cover.startsWith("http")) cover else "$baseUrl/$cover" + val cover = json.optString("cover") + manga.thumbnail_url = when { + cover.startsWith("http") -> cover + cover.isNotEmpty() -> "$baseUrl/$cover" + else -> null + } + return manga } @@ -469,23 +457,18 @@ abstract class Guya( private fun parsePageFromJson(json: JSONObject, metadata: JSONObject): List { val pages = json.getJSONArray(metadata.getString("scanlator")) - val pageArray = ArrayList() - - for (i in 0 until pages.length()) { - val page = Page( - i + 1, + return List(pages.length()) { + Page( + it + 1, "", pageBuilder( metadata.getString("slug"), metadata.getString("folder"), - pages[i].toString(), + pages[it].toString(), metadata.getString("scanlator") ) ) - pageArray.add(page) } - - return pageArray } private fun getBestScanlator(json: JSONObject, sort: JSONArray): String { @@ -509,8 +492,8 @@ abstract class Guya( } inner class ScanlatorStore { - private val scanlatorMap = HashMap() - private val TOTAL_RETRIES = 10 + private val scanlatorMap = mutableMapOf() + private val totalRetries = 10 private var retryCount = 0 init { @@ -519,13 +502,10 @@ abstract class Guya( fun getKeyFromValue(value: String): String { update() - for (key in scanlatorMap.keys) { - if (scanlatorMap[key].equals(value)) { - return key - } - } // Fall back to value as key if endpoint fails - return value + return scanlatorMap.keys.firstOrNull { + scanlatorMap[it].equals(value) + } ?: value } fun getValueFromKey(key: String): String { @@ -545,30 +525,28 @@ abstract class Guya( retryCount++ } else { val json = JSONObject(response.body!!.string()) - val iter = json.keys() - while (iter.hasNext()) { - val scanId = iter.next() + for (scanId in json.keys()) { scanlatorMap[scanId] = json.getString(scanId) } } } private fun onError(error: Throwable) { - // Do nothing for now + error.printStackTrace() } private fun update(blocking: Boolean = true) { - if (scanlatorMap.isEmpty() && retryCount < TOTAL_RETRIES) { + if (scanlatorMap.isEmpty() && retryCount < totalRetries) { try { val call = client.newCall(GET(scanlatorCacheUrl, headers)) .asObservable() if (blocking) { call.toBlocking() - .subscribe({ res -> onResponse(res) }, { err -> onError(err) }) + .subscribe(::onResponse, ::onError) } else { call.subscribeOn(Schedulers.io()) - .subscribe({ res -> onResponse(res) }, { err -> onError(err) }) + .subscribe(::onResponse, ::onError) } } catch (e: Exception) { // Prevent the extension from failing to load @@ -579,16 +557,24 @@ abstract class Guya( // ----------------- Things we aren't supporting ----------------- + override fun mangaDetailsParse(response: Response): SManga { + throw UnsupportedOperationException("Unused") + } + + override fun chapterListParse(response: Response): List { + throw UnsupportedOperationException("Unused") + } + + override fun pageListParse(response: Response): List { + throw UnsupportedOperationException("Unused") + } + + override fun searchMangaParse(response: Response): MangasPage { + throw UnsupportedOperationException("Unused.") + } + override fun imageUrlParse(response: Response): String { - throw Exception("imageUrlParse not supported.") - } - - override fun latestUpdatesRequest(page: Int): Request { - throw Exception("Latest updates not supported.") - } - - override fun latestUpdatesParse(response: Response): MangasPage { - throw Exception("Latest updates not supported.") + throw UnsupportedOperationException("Unused.") } companion object { diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/guya/GuyaGenerator.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/guya/GuyaGenerator.kt index 66c206641..54b2a2293 100644 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/guya/GuyaGenerator.kt +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/guya/GuyaGenerator.kt @@ -10,14 +10,14 @@ class GuyaGenerator : ThemeSourceGenerator { override val themeClass = "Guya" - override val baseVersionCode: Int = 2 + override val baseVersionCode = 3 override val sources = listOf( SingleLang("Guya", "https://guya.moe", "en", overrideVersionCode = 18), SingleLang("Danke fürs Lesen", "https://danke.moe", "en", className = "DankeFursLesen"), SingleLang("Colored Council", "https://coloredcouncil.moe", "en"), SingleLang("Hachirumi", "https://hachirumi.com", "en", isNsfw = true), - MultiLang("Magical Translators", "https://mahoushoujobu.com", listOf("en", "pl"), className = "MagicalTranslatorsFactory"), + MultiLang("Magical Translators", "https://mahoushoujobu.com", listOf("en", "pl")), ) companion object { @JvmStatic