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:
parent
d31dd59dd1
commit
f15cb17e2b
|
@ -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 |
|
@ -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.")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue