Added guya.moe support. (#1321)

* Added guya.moe support.

* Using new icon files.

* Cleaned up use of URL appendages in favour of overloaded methods for metadata.

* Added support for TL preferences.

* Removed factory method.

* Removed some extraneous code, some naming changes, and added search.

* Moved scanlator sourcing into an inner class.
This commit is contained in:
funkyhippo 2019-07-22 15:26:31 -07:00 committed by Eugene
parent d31dd59dd1
commit f15cb17e2b
8 changed files with 353 additions and 0 deletions

17
src/en/guya/build.gradle Normal file
View File

@ -0,0 +1,17 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
ext {
appName = 'Tachiyomi: Guya'
pkgNameSuffix = "en.guya"
extClass = '.Guya'
extVersionCode = 1
libVersion = '1.2'
}
dependencies {
compileOnly project(':preference-stub')
compileOnly 'com.github.inorichi.injekt:injekt-core:65b0440'
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

View File

@ -0,0 +1,336 @@
package eu.kanade.tachiyomi.extension.en.guya
import android.app.Application
import eu.kanade.tachiyomi.source.model.*
import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.asObservableSuccess
import eu.kanade.tachiyomi.source.ConfigurableSource
import android.content.SharedPreferences
import android.support.v7.preference.ListPreference
import android.support.v7.preference.PreferenceScreen
import okhttp3.*
import org.json.JSONObject
import rx.Observable
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.io.IOException
import java.util.*
import java.util.concurrent.TimeUnit
import kotlin.collections.ArrayList
open class Guya() : ConfigurableSource, HttpSource() {
override val name = "Guya"
override val baseUrl = "https://guya.moe"
override val supportsLatest = false
override val lang = "en"
private val Scanlators: ScanlatorStore = ScanlatorStore()
// Preferences confirguration
private val preferences: SharedPreferences by lazy {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
}
private val SCANLATOR_PREFERENCE = "SCANLATOR_PREFERENCE"
// Request builder for the "browse" page of the manga
override fun popularMangaRequest(page: Int): Request {
return GET("$baseUrl/api/get_all_series")
}
// Gets the response object from the request
override fun popularMangaParse(response: Response): MangasPage {
val res = response.body()!!.string()
return parseManga(JSONObject(res))
}
// Overridden to use our overload
override fun fetchMangaDetails(manga: SManga): Observable<SManga> {
return clientBuilder().newCall(GET("$baseUrl/api/get_all_series/"))
.asObservableSuccess()
.map {response ->
mangaDetailsParse(response, manga)
}
}
// Called when the series is loaded, or when opening in browser
override fun mangaDetailsRequest(manga: SManga): Request {
return GET("$baseUrl/reader/series/" + manga.url)
}
// 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).getJSONObject(manga.title), manga.title)
}
// Gets the chapter list based on the series being viewed
override fun chapterListRequest(manga: SManga): Request {
return GET("$baseUrl/api/series/" + manga.url)
}
// Called after the request
override fun chapterListParse(response: Response): List<SChapter> {
val res = response.body()!!.string()
return parseChapterList(res)
}
// Overridden fetch so that we use our overloaded method instead
override fun fetchPageList(chapter: SChapter): Observable<List<Page>> {
return clientBuilder().newCall(pageListRequest(chapter))
.asObservableSuccess()
.map { response ->
pageListParse(response, chapter)
}
}
// Stub
override fun pageListParse(response: Response): List<Page> {
throw Exception("Unused")
}
private fun pageListParse(response: Response, chapter: SChapter): List<Page> {
val res = response.body()!!.string()
val json = JSONObject(res)
val chapterNum = chapter.name.split(" - ")[0]
val pages = json.getJSONObject("chapters")
.getJSONObject(chapterNum)
.getJSONObject("groups")
val metadata = JSONObject()
metadata.put("chapter", chapterNum)
metadata.put("scanlator", Scanlators.getKeyFromValue(chapter.scanlator.toString()))
metadata.put("slug", json.getString("slug"))
metadata.put("folder", json.getJSONObject("chapters")
.getJSONObject(chapterNum)
.getString("folder"))
return parsePageFromJson(pages, metadata)
}
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
return client.newCall(searchMangaRequest(page, query, filters))
.asObservableSuccess()
.map { response ->
searchMangaParse(response, query)
}
}
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
return GET("$baseUrl/api/get_all_series")
}
override fun searchMangaParse(response: Response): MangasPage {
throw Exception("Unused.")
}
private fun searchMangaParse(response: Response, query: String): MangasPage {
val res = response.body()!!.string()
var 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))) {
truncatedJSON.put(candidate, json.get(candidate))
}
}
return parseManga(truncatedJSON)
}
override fun setupPreferenceScreen(screen: PreferenceScreen) {
val preference = ListPreference(screen.context).apply {
key = "preferred_scanlator"
title = "Preferred scanlator"
var scanlators = arrayOf<String>()
var scanlatorsIndex = arrayOf<String>()
for (key in Scanlators.keys()) {
scanlators += Scanlators.getValueFromKey(key)
scanlatorsIndex += key
}
entries = scanlators
entryValues = scanlatorsIndex
summary = "%s"
this.setDefaultValue(1)
setOnPreferenceChangeListener{_, newValue ->
val selected = newValue.toString()
val index = (this.findIndexOfValue(selected) + 1).toString()
preferences.edit().putString(SCANLATOR_PREFERENCE, index).commit()
}
}
screen.addPreference(preference)
}
// ------------- Helpers and whatnot ---------------
private fun parseChapterList(payload: String): List<SChapter> {
val response = JSONObject(payload)
val chapters = response.getJSONObject("chapters")
val chapterList = ArrayList<SChapter>()
val iter = chapters.keys()
while (iter.hasNext()) {
val chapter = iter.next()
val chapterObj = chapters.getJSONObject(chapter)
chapterList.add(parseChapterFromJson(chapterObj, chapter, response.getString("slug")))
}
return chapterList.reversed()
}
// Helper function to get all the listings
private fun parseManga(payload: JSONObject) : MangasPage {
val mangas = ArrayList<SManga>()
val iter = payload.keys()
while (iter.hasNext()) {
val series = iter.next()
val json = payload.getJSONObject(series)
val manga = parseMangaFromJson(json, series)
mangas.add(manga)
}
return MangasPage(mangas, false)
}
// Takes a json of the manga to parse
private fun parseMangaFromJson(json: JSONObject, title: String): SManga {
val manga = SManga.create()
manga.title = title
manga.artist = json.getString("artist")
manga.author = json.getString("author")
manga.description = json.getString("description")
manga.url = json.getString("slug")
manga.thumbnail_url = "$baseUrl/" + json.getString("cover")
return manga
}
private fun parseChapterFromJson(json: JSONObject, num: String, slug: String): SChapter {
val chapter = SChapter.create()
// Get the scanlator info based on group ranking; do it first since we need it later
val firstGroupId = getBestScanlator(json.getJSONObject("groups"))
chapter.scanlator = Scanlators.getValueFromKey(firstGroupId)
chapter.name = num + " - " + json.getString("title")
chapter.chapter_number = num.toFloat()
chapter.url = "/api/series/$slug/$num/1"
return chapter
}
private fun parsePageFromJson(json: JSONObject, metadata: JSONObject): List<Page> {
val pages = json.getJSONArray(metadata.getString("scanlator"))
val pageArray = ArrayList<Page>()
for (i in 0 until pages.length()) {
val page = Page(i + 1, "", pageBuilder(metadata.getString("slug"),
metadata.getString("folder"),
pages[i].toString(),
metadata.getString("scanlator")))
pageArray.add(page)
}
return pageArray
}
private fun getBestScanlator(json: JSONObject): String {
val preferred = preferences.getString(SCANLATOR_PREFERENCE, null)
return if (preferred != null && json.has(preferred)) {
preferred
} else {
json.keys().next()
}
}
private fun pageBuilder(slug: String, folder: String, filename: String, groupId: String): String {
return "$baseUrl/media/manga/$slug/chapters/$folder/$groupId/$filename"
}
private fun clientBuilder(): OkHttpClient = network.cloudflareClient.newBuilder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.build()!!
inner class ScanlatorStore {
private val scanlatorMap = HashMap<String, String>()
private var polling = false
init {
update()
}
fun getKeyFromValue(value: String): String {
update()
for (key in scanlatorMap.keys) {
if (scanlatorMap[key].equals(value)) {
return key
}
}
throw Exception("Cannot find scanlator group!")
}
fun getValueFromKey(key: String): String {
update()
return if (!scanlatorMap[key].isNullOrEmpty())
scanlatorMap[key].toString() else "No info"
}
fun keys(): MutableSet<String> {
update()
return scanlatorMap.keys
}
private fun update() {
if (scanlatorMap.isEmpty() && !polling) {
polling = true
clientBuilder().newCall(GET("$baseUrl/api/get_all_groups")).enqueue(
object: Callback {
override fun onResponse(call: Call, response: Response) {
val json = JSONObject(response.body()!!.string())
val iter = json.keys()
while (iter.hasNext()) {
val scanId = iter.next()
scanlatorMap[scanId] = json.getString(scanId)
}
polling = false
}
override fun onFailure(call: Call, e: IOException) {
polling = false
}
}
)
}
}
}
// ----------------- Things we aren't supporting -----------------
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.")
}
}