Implement ReaderFront multisrc (#8545)

This commit is contained in:
ObserverOfTime 2021-08-14 12:46:02 +03:00 committed by GitHub
parent 7ea52804f1
commit d061b6597f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 346 additions and 0 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

View File

@ -0,0 +1,20 @@
package eu.kanade.tachiyomi.extension.all.ravensscans
import eu.kanade.tachiyomi.annotations.Nsfw
import eu.kanade.tachiyomi.multisrc.readerfront.ReaderFront
import eu.kanade.tachiyomi.source.SourceFactory
class RavensScansFactory : SourceFactory {
override fun createSources() = listOf(
RavensScans("es", 1),
RavensScans("en", 2)
)
@Nsfw
class RavensScans(override val lang: String, override val langId: Int) :
ReaderFront("Ravens Scans", "https://ravens-scans.com/", lang, langId) {
override fun getImageCDN(path: String, width: Int) =
"https://i${(0..2).random()}.wp.com/img-cdn1.ravens-scans.com" +
"$path?strip=all&quality=100&w=$width"
}
}

View File

@ -0,0 +1,145 @@
package eu.kanade.tachiyomi.multisrc.readerfront
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.asObservableSuccess
import eu.kanade.tachiyomi.source.model.FilterList
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 eu.kanade.tachiyomi.source.online.HttpSource
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.decodeFromJsonElement
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import okhttp3.Response
import uy.kohesive.injekt.injectLazy
import java.util.Locale
abstract class ReaderFront(
override val name: String,
override val baseUrl: String,
override val lang: String,
open val langId: Int
) : HttpSource() {
override val supportsLatest = true
private val json by injectLazy<Json>()
open val apiUrl by lazy { baseUrl.replaceFirst("://", "://api.") }
abstract fun getImageCDN(path: String, width: Int = 350): String
override fun latestUpdatesRequest(page: Int) =
GET("$apiUrl?query=${works(langId, "updatedAt", "DESC", page, 12)}", headers)
override fun popularMangaRequest(page: Int) =
GET("$apiUrl?query=${works(langId, "stub", "ASC", page, 120)}", headers)
override fun mangaDetailsRequest(manga: SManga) =
GET("$baseUrl/work/$lang/${manga.url}", headers)
override fun chapterListRequest(manga: SManga) =
GET("$apiUrl?query=${chaptersByWork(langId, manga.url)}", headers)
override fun pageListRequest(chapter: SChapter) =
GET("$apiUrl?query=${chapterById(chapter.url.toInt())}", headers)
override fun latestUpdatesParse(response: Response) =
response.parse<List<Work>>("works").map {
SManga.create().apply {
url = it.stub
title = it.toString()
thumbnail_url = getImageCDN(it.thumbnail_path)
}
}.let { MangasPage(it, false) }
override fun popularMangaParse(response: Response) =
latestUpdatesParse(response)
override fun mangaDetailsParse(response: Response) =
response.parse<Work>("work").let {
SManga.create().apply {
url = it.stub
title = it.toString()
thumbnail_url = getImageCDN(it.thumbnail_path)
description = it.description
author = it.authors!!.joinToString()
artist = it.artists!!.joinToString()
genre = buildString {
if (it.adult!!) append("18+, ")
append(it.demographic_name!!)
if (it.genres!!.isNotEmpty()) {
append(", ")
it.genres.joinTo(this, transform = ::capitalize)
}
append(", ")
append(it.type!!)
}
status = when {
it.licensed!! -> SManga.LICENSED
it.status_name == "on_going" -> SManga.ONGOING
it.status_name == "completed" -> SManga.COMPLETED
else -> SManga.UNKNOWN
}
initialized = true
}
}
override fun chapterListParse(response: Response) =
response.parse<List<Chapter>>("chaptersByWork").map {
SChapter.create().apply {
url = it.id.toString()
name = it.toString()
chapter_number = it.number
date_upload = it.timestamp
}
}
override fun pageListParse(response: Response) =
response.parse<PageList>("chapterById").let {
it.mapIndexed { idx, page ->
Page(idx, "", getImageCDN(it.path(page), page.width))
}
}
override fun fetchMangaDetails(manga: SManga) =
GET("$apiUrl?query=${work(langId, manga.url)}", headers).let {
client.newCall(it).asObservableSuccess().map(::mangaDetailsParse)
}!!
override fun fetchSearchManga(page: Int, query: String, filters: FilterList) =
client.newCall(popularMangaRequest(page)).asObservableSuccess().map { res ->
popularMangaParse(res).let { mp ->
mp.copy(mp.mangas.filter { it.title.contains(query, true) })
}
}!!
private fun capitalize(name: Name) =
name.split('_').joinToString(" ") { it.capitalize(Locale(lang)) }
private inline fun <reified T> Response.parse(name: String) =
json.parseToJsonElement(body!!.string()).jsonObject.run {
if (containsKey("errors")) {
throw Error(get("errors")!![0]["message"].content)
}
json.decodeFromJsonElement<T>(get("data")!![name])
}
private operator fun JsonElement.get(key: String) = jsonObject[key]!!
private operator fun JsonElement.get(index: Int) = jsonArray[index]
private inline val JsonElement.content get() = jsonPrimitive.content
override fun searchMangaRequest(page: Int, query: String, filters: FilterList) =
throw UnsupportedOperationException("Not used!")
override fun searchMangaParse(response: Response) =
throw UnsupportedOperationException("Not used!")
override fun imageUrlParse(response: Response) =
throw UnsupportedOperationException("Not used!")
}

View File

@ -0,0 +1,89 @@
package eu.kanade.tachiyomi.multisrc.readerfront
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
import java.text.DecimalFormat
import java.text.SimpleDateFormat
import java.util.Locale
@Serializable
data class Work(
private val name: String,
val stub: String,
val thumbnail_path: String,
val adult: Boolean? = null,
val type: String? = null,
val licensed: Boolean? = null,
val status_name: String? = null,
val description: String? = null,
val demographic_name: String? = null,
val genres: List<Name>? = null,
private val people_works: List<People>? = null
) {
@Transient
val authors = people_works?.filter { it.role == 1 }
@Transient
val artists = people_works?.filter { it.role == 2 }
override fun toString() = name
}
@Serializable
data class Chapter(
val id: Int,
private val chapter: Int,
private val subchapter: Int,
private val volume: Int,
private val name: String,
private val releaseDate: String
) {
@Transient
val number = "$chapter.$subchapter".toFloat()
@Transient
val timestamp = dateFormat.parse(releaseDate)?.time ?: 0L
override fun toString() = buildString {
if (volume > 0) append("Volume $volume ")
if (number > 0) append("Chapter ${decimalFormat.format(number)}: ")
append(name)
}
companion object {
private val decimalFormat = DecimalFormat("#.##")
private val dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.ROOT)
}
}
@Serializable
data class PageList(
private val uniqid: String,
private val work: Uniqid,
private val pages: List<Page>
) : Iterable<Page> by pages {
/** Get the path of a page in the list. */
fun path(page: Page) = "/works/$work/$this/$page"
override fun toString() = uniqid
}
@Serializable
data class Page(private val filename: String, val width: Int) {
override fun toString() = filename
}
@Serializable
data class Uniqid(private val uniqid: String) {
override fun toString() = uniqid
}
@Serializable
data class People(val role: Int, private val people: Name) {
override fun toString() = people.toString()
}
@Serializable
data class Name(private val name: String) : CharSequence by name {
override fun toString() = name
}

View File

@ -0,0 +1,21 @@
package eu.kanade.tachiyomi.multisrc.readerfront
import generator.ThemeSourceData.MultiLang
import generator.ThemeSourceGenerator
class ReaderFrontGenerator : ThemeSourceGenerator {
override val themePkg = "readerfront"
override val themeClass = "ReaderFront"
override val baseVersionCode = 1
override val sources = listOf(
MultiLang("Ravens Scans", "https://ravens-scans.com/", listOf("es", "en"), true),
)
companion object {
@JvmStatic
fun main(args: Array<String>) = ReaderFrontGenerator().createAll()
}
}

View File

@ -0,0 +1,71 @@
package eu.kanade.tachiyomi.multisrc.readerfront
private fun String.encodeUri() =
android.net.Uri.encode(trimMargin())!!
fun works(lang: Int, sort: String, order: String, page: Int, limit: Int) = """{
|works(
| orderBy: "$order"
| sortBy: "$sort"
| first: $limit
| offset: ${(page - 1) * limit}
| languages: [$lang]
| showHidden: false
|) {
| name
| stub
| thumbnail_path
|}
}""".encodeUri()
fun work(lang: Int, stub: String) = """{
|work(
| stub: "$stub"
| language: $lang
| showHidden: false
|) {
| name
| stub
| thumbnail_path
| status_name
| adult
| type
| licensed
| description
| demographic_name
| genres { name }
| people_works {
| role: rol
| people { name }
| }
|}
}""".encodeUri()
fun chaptersByWork(lang: Int, stub: String) = """{
|chaptersByWork(
| workStub: "$stub"
| languages: [$lang]
| showHidden: false
|) {
| id
| chapter
| subchapter
| volume
| name
| releaseDate
|}
}""".encodeUri()
fun chapterById(id: Int) = """{
|chapterById(
| id: $id
| showHidden: false
|) {
| uniqid
| work { uniqid }
| pages {
| filename
| width
| }
|}
}""".encodeUri()