Implement latest updates in Guya (#8355)

This commit is contained in:
ObserverOfTime 2021-08-02 18:01:05 +03:00 committed by GitHub
parent 28234597b3
commit 0a2a26e48c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 109 additions and 119 deletions

View File

@ -1,13 +1,12 @@
package eu.kanade.tachiyomi.extension.all.magicaltranslators package eu.kanade.tachiyomi.extension.all.magicaltranslators
import eu.kanade.tachiyomi.multisrc.guya.Guya import eu.kanade.tachiyomi.multisrc.guya.Guya
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.SourceFactory import eu.kanade.tachiyomi.source.SourceFactory
import eu.kanade.tachiyomi.source.model.MangasPage import eu.kanade.tachiyomi.source.model.MangasPage
import okhttp3.Response import okhttp3.Response
class MagicalTranslatorsFactory : SourceFactory { class MagicalTranslatorsFactory : SourceFactory {
override fun createSources(): List<Source> = listOf( override fun createSources() = listOf(
MagicalTranslatorsEN(), MagicalTranslatorsEN(),
MagicalTranslatorsPL(), MagicalTranslatorsPL(),
) )
@ -15,18 +14,23 @@ class MagicalTranslatorsFactory : SourceFactory {
abstract class MagicalTranslatorsCommon(lang: String) : abstract class MagicalTranslatorsCommon(lang: String) :
Guya("Magical Translators", "https://mahoushoujobu.com", lang) { Guya("Magical Translators", "https://mahoushoujobu.com", lang) {
protected abstract fun filterMangasPage(mangasPage: MangasPage): MangasPage protected abstract fun filterMangasPage(mangasPage: MangasPage): MangasPage
override fun popularMangaParse(response: Response): MangasPage = override fun popularMangaParse(response: Response): MangasPage =
filterMangasPage(super.popularMangaParse(response)) filterMangasPage(super.popularMangaParse(response))
override fun proxySearchMangaParse(response: Response, slug: String): MangasPage = override fun latestUpdatesParse(response: Response): MangasPage =
filterMangasPage(super.proxySearchMangaParse(response, slug)) 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 = override fun searchMangaParseWithSlug(response: Response, slug: String): MangasPage =
filterMangasPage(super.searchMangaParseWithSlug(response, slug)) filterMangasPage(super.searchMangaParseWithSlug(response, slug))
override fun searchMangaParse(response: Response, slug: String): MangasPage = override fun searchMangaParse(response: Response, query: String): MangasPage =
filterMangasPage(super.searchMangaParse(response, slug)) filterMangasPage(super.searchMangaParse(response, query))
} }
class MagicalTranslatorsEN : MagicalTranslatorsCommon("en") { class MagicalTranslatorsEN : MagicalTranslatorsCommon("en") {

View File

@ -3,6 +3,8 @@ package eu.kanade.tachiyomi.multisrc.guya
import android.app.Application import android.app.Application
import android.content.SharedPreferences import android.content.SharedPreferences
import android.os.Build import android.os.Build
import androidx.preference.ListPreference
import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.BuildConfig import eu.kanade.tachiyomi.BuildConfig
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.asObservable import eu.kanade.tachiyomi.network.asObservable
@ -23,26 +25,23 @@ import rx.Observable
import rx.schedulers.Schedulers import rx.schedulers.Schedulers
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.util.HashMap
abstract class Guya( abstract class Guya(
override val name: String, override val name: String,
override val baseUrl: 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 { override fun headersBuilder() = Headers.Builder().add(
add( "User-Agent",
"User-Agent", "(Android ${Build.VERSION.RELEASE}; " +
"(Android ${Build.VERSION.RELEASE}; " + "${Build.MANUFACTURER} ${Build.MODEL}) " +
"${Build.MANUFACTURER} ${Build.MODEL}) " + "Tachiyomi/${BuildConfig.VERSION_NAME} ${Build.ID}"
"Tachiyomi/${BuildConfig.VERSION_NAME} " + )
Build.ID
)
}
private val scanlators: ScanlatorStore = ScanlatorStore() private val scanlators: ScanlatorStore = ScanlatorStore()
@ -50,6 +49,7 @@ abstract class Guya(
private val preferences: SharedPreferences by lazy { private val preferences: SharedPreferences by lazy {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000) Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
} }
private val scanlatorPreference = "SCANLATOR_PREFERENCE" private val scanlatorPreference = "SCANLATOR_PREFERENCE"
// Request builder for the "browse" page of the manga // Request builder for the "browse" page of the manga
@ -63,6 +63,23 @@ abstract class Guya(
return parseManga(JSONObject(res)) 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<Long, SManga>()
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 // Overridden to use our overload
override fun fetchMangaDetails(manga: SManga): Observable<SManga> { override fun fetchMangaDetails(manga: SManga): Observable<SManga> {
return when { 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 { private fun mangaDetailsParse(response: Response, manga: SManga): SManga {
val res = response.body!!.string() val res = response.body!!.string()
return parseMangaFromJson(JSONObject(res), "", manga.title) return parseMangaFromJson(JSONObject(res), "", manga.title)
@ -125,14 +137,9 @@ abstract class Guya(
return GET("$baseUrl/api/series/${manga.url}/", headers) return GET("$baseUrl/api/series/${manga.url}/", headers)
} }
override fun chapterListParse(response: Response): List<SChapter> {
throw Exception("Unused")
}
// Called after the request // Called after the request
private fun chapterListParse(response: Response, manga: SManga): List<SChapter> { private fun chapterListParse(response: Response, manga: SManga): List<SChapter> {
val res = response.body!!.string() return parseChapterList(response.body!!.string(), manga)
return parseChapterList(res, manga)
} }
// Overridden fetch so that we use our overloaded method instead // 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) return GET("$baseUrl/api/series/${chapter.url.split("/")[0]}/", headers)
} }
// Stub
override fun pageListParse(response: Response): List<Page> {
throw Exception("Unused")
}
private fun pageListParse(response: Response, chapter: SChapter): List<Page> { private fun pageListParse(response: Response, chapter: SChapter): List<Page> {
val res = response.body!!.string() val res = response.body!!.string()
@ -175,7 +177,7 @@ abstract class Guya(
val metadata = JSONObject() val metadata = JSONObject()
metadata.put("chapter", chapterNum) 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("slug", json.getString("slug"))
metadata.put( metadata.put(
"folder", "folder",
@ -198,7 +200,7 @@ abstract class Guya(
} }
} }
query.startsWith(PROXY_PREFIX) && query.contains("/") -> { query.startsWith(PROXY_PREFIX) && query.contains("/") -> {
client.newCall(proxySearchMangaRequest(page, query, filters)) client.newCall(proxySearchMangaRequest(query))
.asObservableSuccess() .asObservableSuccess()
.map { response -> .map { response ->
proxySearchMangaParse(response, query) proxySearchMangaParse(response, query)
@ -218,17 +220,11 @@ abstract class Guya(
return GET("$baseUrl/api/get_all_series/", headers) 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 { protected open fun searchMangaParseWithSlug(response: Response, slug: String): MangasPage {
val results = JSONObject(response.body!!.string()) val results = JSONObject(response.body!!.string())
val mangaIter = results.keys()
val truncatedJSON = JSONObject() val truncatedJSON = JSONObject()
while (mangaIter.hasNext()) { for (mangaTitle in results.keys()) {
val mangaTitle = mangaIter.next()
val mangaDetails = results.getJSONObject(mangaTitle) val mangaDetails = results.getJSONObject(mangaTitle)
if (mangaDetails.get("slug") == slug) { if (mangaDetails.get("slug") == slug) {
@ -244,11 +240,8 @@ abstract class Guya(
val json = JSONObject(res) val json = JSONObject(res)
val truncatedJSON = JSONObject() val truncatedJSON = JSONObject()
val iter = json.keys() for (candidate in json.keys()) {
if (candidate.contains(query, ignoreCase = true)) {
while (iter.hasNext()) {
val candidate = iter.next()
if (candidate.contains(query.toRegex(RegexOption.IGNORE_CASE))) {
truncatedJSON.put(candidate, json.get(candidate)) truncatedJSON.put(candidate, json.get(candidate))
} }
} }
@ -256,8 +249,8 @@ abstract class Guya(
return parseManga(truncatedJSON) return parseManga(truncatedJSON)
} }
override fun setupPreferenceScreen(screen: androidx.preference.PreferenceScreen) { override fun setupPreferenceScreen(screen: PreferenceScreen) {
val preference = androidx.preference.ListPreference(screen.context).apply { val preference = ListPreference(screen.context).apply {
key = "preferred_scanlator" key = "preferred_scanlator"
title = "Preferred scanlator" title = "Preferred scanlator"
entries = arrayOf<String>() entries = arrayOf<String>()
@ -271,7 +264,7 @@ abstract class Guya(
"on chapter refresh/update. It will get the next available if " + "on chapter refresh/update. It will get the next available if " +
"your preferred scanlator isn't an option (yet)." "your preferred scanlator isn't an option (yet)."
this.setDefaultValue("1") setDefaultValue("1")
setOnPreferenceChangeListener { _, newValue -> setOnPreferenceChangeListener { _, newValue ->
val selected = newValue.toString() val selected = newValue.toString()
@ -332,27 +325,23 @@ abstract class Guya(
.getJSONObject("groups") .getJSONObject("groups")
.getJSONArray(groupNum) .getJSONArray(groupNum)
} }
val pageArray = ArrayList<Page>() return List(pages.length()) {
for (i in 0 until pages.length()) { Page(
val page = if (pages.optJSONObject(i) != null) { it + 1,
pages.getJSONObject(i).getString("src") "",
} else { pages.optJSONObject(it)?.getString("src")
pages[i] ?: pages[it].toString()
} )
pageArray.add(Page(i + 1, "", page.toString()))
} }
return pageArray
} }
private fun proxySearchMangaRequest(page: Int, query: String, filters: FilterList): Request { private fun proxySearchMangaRequest(query: String): Request {
return proxySeriesRequest(query) return proxySeriesRequest(query)
} }
protected open fun proxySearchMangaParse(response: Response, query: String): MangasPage { protected open fun proxySearchMangaParse(response: Response, query: String): MangasPage {
return MangasPage( val json = JSONObject(response.body!!.string())
arrayListOf(parseMangaFromJson(JSONObject(response.body!!.string()), query)), return MangasPage(listOf(parseMangaFromJson(json, query)), false)
false
)
} }
// ------------- Helpers and whatnot --------------- // ------------- Helpers and whatnot ---------------
@ -363,7 +352,7 @@ abstract class Guya(
val chapters = response.getJSONObject("chapters") val chapters = response.getJSONObject("chapters")
val mapping = response.getJSONObject("groups") val mapping = response.getJSONObject("groups")
val chapterList = ArrayList<SChapter>() val chapterList = mutableListOf<SChapter>()
val iter = chapters.keys() val iter = chapters.keys()
@ -408,11 +397,9 @@ abstract class Guya(
chapter.chapter_number = chapterNum.toFloat() chapter.chapter_number = chapterNum.toFloat()
chapter.url = chapter.url =
if (groups.optJSONArray(groupNum) != null) { if (groups.optJSONArray(groupNum) != null) {
val mangaUrl = manga.url "${manga.url}/$chapterNum/$groupNum"
"$mangaUrl/$chapterNum/$groupNum"
} else { } else {
val url = groups.getString(groupNum) "$PROXY_PREFIX${groups.getString(groupNum)}"
"$PROXY_PREFIX$url"
} }
chapterList.add(chapter) chapterList.add(chapter)
} }
@ -425,15 +412,11 @@ abstract class Guya(
// Helper function to get all the listings // Helper function to get all the listings
private fun parseManga(payload: JSONObject): MangasPage { private fun parseManga(payload: JSONObject): MangasPage {
val mangas = ArrayList<SManga>() val mangas = mutableListOf<SManga>()
val iter = payload.keys() for (series in payload.keys()) {
while (iter.hasNext()) {
val series = iter.next()
val json = payload.getJSONObject(series) val json = payload.getJSONObject(series)
val manga = parseMangaFromJson(json, "", series) mangas += parseMangaFromJson(json, "", series)
mangas.add(manga)
} }
return MangasPage(mangas, false) return MangasPage(mangas, false)
@ -442,14 +425,19 @@ abstract class Guya(
// Takes a json of the manga to parse // Takes a json of the manga to parse
private fun parseMangaFromJson(json: JSONObject, slug: String, title: String = ""): SManga { private fun parseMangaFromJson(json: JSONObject, slug: String, title: String = ""): SManga {
val manga = SManga.create() val manga = SManga.create()
manga.title = if (title.isNotEmpty()) title else json.getString("title") manga.title = title.ifEmpty { json.getString("title") }
manga.artist = json.optString("artist", "Unknown") manga.artist = json.optString("artist")
manga.author = json.optString("author", "Unknown") manga.author = json.optString("author")
manga.description = json.optString("description", "None") manga.description = json.optString("description")
manga.url = if (slug.startsWith(PROXY_PREFIX)) slug else json.getString("slug") manga.url = if (slug.startsWith(PROXY_PREFIX)) slug else json.getString("slug")
val cover = json.optString("cover", "") val cover = json.optString("cover")
manga.thumbnail_url = if (cover.startsWith("http")) cover else "$baseUrl/$cover" manga.thumbnail_url = when {
cover.startsWith("http") -> cover
cover.isNotEmpty() -> "$baseUrl/$cover"
else -> null
}
return manga return manga
} }
@ -469,23 +457,18 @@ abstract class Guya(
private fun parsePageFromJson(json: JSONObject, metadata: JSONObject): List<Page> { private fun parsePageFromJson(json: JSONObject, metadata: JSONObject): List<Page> {
val pages = json.getJSONArray(metadata.getString("scanlator")) val pages = json.getJSONArray(metadata.getString("scanlator"))
val pageArray = ArrayList<Page>() return List(pages.length()) {
Page(
for (i in 0 until pages.length()) { it + 1,
val page = Page(
i + 1,
"", "",
pageBuilder( pageBuilder(
metadata.getString("slug"), metadata.getString("slug"),
metadata.getString("folder"), metadata.getString("folder"),
pages[i].toString(), pages[it].toString(),
metadata.getString("scanlator") metadata.getString("scanlator")
) )
) )
pageArray.add(page)
} }
return pageArray
} }
private fun getBestScanlator(json: JSONObject, sort: JSONArray): String { private fun getBestScanlator(json: JSONObject, sort: JSONArray): String {
@ -509,8 +492,8 @@ abstract class Guya(
} }
inner class ScanlatorStore { inner class ScanlatorStore {
private val scanlatorMap = HashMap<String, String>() private val scanlatorMap = mutableMapOf<String, String>()
private val TOTAL_RETRIES = 10 private val totalRetries = 10
private var retryCount = 0 private var retryCount = 0
init { init {
@ -519,13 +502,10 @@ abstract class Guya(
fun getKeyFromValue(value: String): String { fun getKeyFromValue(value: String): String {
update() update()
for (key in scanlatorMap.keys) {
if (scanlatorMap[key].equals(value)) {
return key
}
}
// Fall back to value as key if endpoint fails // 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 { fun getValueFromKey(key: String): String {
@ -545,30 +525,28 @@ abstract class Guya(
retryCount++ retryCount++
} else { } else {
val json = JSONObject(response.body!!.string()) val json = JSONObject(response.body!!.string())
val iter = json.keys() for (scanId in json.keys()) {
while (iter.hasNext()) {
val scanId = iter.next()
scanlatorMap[scanId] = json.getString(scanId) scanlatorMap[scanId] = json.getString(scanId)
} }
} }
} }
private fun onError(error: Throwable) { private fun onError(error: Throwable) {
// Do nothing for now error.printStackTrace()
} }
private fun update(blocking: Boolean = true) { private fun update(blocking: Boolean = true) {
if (scanlatorMap.isEmpty() && retryCount < TOTAL_RETRIES) { if (scanlatorMap.isEmpty() && retryCount < totalRetries) {
try { try {
val call = client.newCall(GET(scanlatorCacheUrl, headers)) val call = client.newCall(GET(scanlatorCacheUrl, headers))
.asObservable() .asObservable()
if (blocking) { if (blocking) {
call.toBlocking() call.toBlocking()
.subscribe({ res -> onResponse(res) }, { err -> onError(err) }) .subscribe(::onResponse, ::onError)
} else { } else {
call.subscribeOn(Schedulers.io()) call.subscribeOn(Schedulers.io())
.subscribe({ res -> onResponse(res) }, { err -> onError(err) }) .subscribe(::onResponse, ::onError)
} }
} catch (e: Exception) { } catch (e: Exception) {
// Prevent the extension from failing to load // Prevent the extension from failing to load
@ -579,16 +557,24 @@ abstract class Guya(
// ----------------- Things we aren't supporting ----------------- // ----------------- Things we aren't supporting -----------------
override fun mangaDetailsParse(response: Response): SManga {
throw UnsupportedOperationException("Unused")
}
override fun chapterListParse(response: Response): List<SChapter> {
throw UnsupportedOperationException("Unused")
}
override fun pageListParse(response: Response): List<Page> {
throw UnsupportedOperationException("Unused")
}
override fun searchMangaParse(response: Response): MangasPage {
throw UnsupportedOperationException("Unused.")
}
override fun imageUrlParse(response: Response): String { override fun imageUrlParse(response: Response): String {
throw Exception("imageUrlParse not supported.") throw UnsupportedOperationException("Unused.")
}
override fun latestUpdatesRequest(page: Int): Request {
throw Exception("Latest updates not supported.")
}
override fun latestUpdatesParse(response: Response): MangasPage {
throw Exception("Latest updates not supported.")
} }
companion object { companion object {

View File

@ -10,14 +10,14 @@ class GuyaGenerator : ThemeSourceGenerator {
override val themeClass = "Guya" override val themeClass = "Guya"
override val baseVersionCode: Int = 2 override val baseVersionCode = 3
override val sources = listOf( override val sources = listOf(
SingleLang("Guya", "https://guya.moe", "en", overrideVersionCode = 18), SingleLang("Guya", "https://guya.moe", "en", overrideVersionCode = 18),
SingleLang("Danke fürs Lesen", "https://danke.moe", "en", className = "DankeFursLesen"), SingleLang("Danke fürs Lesen", "https://danke.moe", "en", className = "DankeFursLesen"),
SingleLang("Colored Council", "https://coloredcouncil.moe", "en"), SingleLang("Colored Council", "https://coloredcouncil.moe", "en"),
SingleLang("Hachirumi", "https://hachirumi.com", "en", isNsfw = true), 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 { companion object {
@JvmStatic @JvmStatic