mirror of
https://github.com/null2264/yokai.git
synced 2025-06-21 10:44:42 +00:00
refactor: WIP delegated source refactor
J2K only handles deep link, which was disabled when I forked it as Yokai... Might gonna re-introduce it for some sources I used later (mainly Cubari tbh)
This commit is contained in:
parent
b4377a4609
commit
2cf2fcfc4f
10 changed files with 460 additions and 275 deletions
|
@ -1,20 +1,13 @@
|
|||
package eu.kanade.tachiyomi.source
|
||||
|
||||
import android.content.Context
|
||||
import eu.kanade.tachiyomi.R
|
||||
import yokai.i18n.MR
|
||||
import yokai.util.lang.getString
|
||||
import dev.icerock.moko.resources.compose.stringResource
|
||||
import eu.kanade.tachiyomi.extension.ExtensionManager
|
||||
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.DelegatedHttpSource
|
||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||
import eu.kanade.tachiyomi.source.online.all.Cubari
|
||||
import eu.kanade.tachiyomi.source.online.all.MangaDex
|
||||
import eu.kanade.tachiyomi.source.online.english.KireiCake
|
||||
import eu.kanade.tachiyomi.source.online.english.MangaPlus
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
|
@ -24,7 +17,8 @@ import kotlinx.coroutines.flow.collectLatest
|
|||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import yokai.i18n.MR
|
||||
import yokai.util.lang.getString
|
||||
|
||||
class SourceManager(
|
||||
private val context: Context,
|
||||
|
@ -40,28 +34,8 @@ class SourceManager(
|
|||
val catalogueSources: Flow<List<CatalogueSource>> = sourcesMapFlow.map { it.values.filterIsInstance<CatalogueSource>() }
|
||||
val onlineSources: Flow<List<HttpSource>> = catalogueSources.map { it.filterIsInstance<HttpSource>() }
|
||||
|
||||
private val delegatedSources = listOf(
|
||||
DelegatedSource(
|
||||
"reader.kireicake.com",
|
||||
5509224355268673176,
|
||||
KireiCake(),
|
||||
),
|
||||
DelegatedSource(
|
||||
"mangadex.org",
|
||||
2499283573021220255,
|
||||
MangaDex(),
|
||||
),
|
||||
DelegatedSource(
|
||||
"mangaplus.shueisha.co.jp",
|
||||
1998944621602463790,
|
||||
MangaPlus(),
|
||||
),
|
||||
DelegatedSource(
|
||||
"cubari.moe",
|
||||
6338219619148105941,
|
||||
Cubari(),
|
||||
),
|
||||
).associateBy { it.sourceId }
|
||||
// FIXME: Delegated source, unused at the moment, J2K only delegate deep links
|
||||
private val delegatedSources = emptyList<DelegatedSource>().associateBy { it.sourceId }
|
||||
|
||||
init {
|
||||
scope.launch {
|
||||
|
@ -71,8 +45,8 @@ class SourceManager(
|
|||
extensions.forEach { extension ->
|
||||
extension.sources.forEach {
|
||||
mutableMap[it.id] = it
|
||||
delegatedSources[it.id]?.delegatedHttpSource?.delegate = it as? HttpSource
|
||||
// registerStubSource(it)
|
||||
//delegatedSources[it.id]?.delegatedHttpSource?.delegate = it as? HttpSource
|
||||
//registerStubSource(it)
|
||||
}
|
||||
}
|
||||
sourcesMapFlow.value = mutableMap
|
||||
|
|
|
@ -1,47 +0,0 @@
|
|||
package eu.kanade.tachiyomi.source.online
|
||||
|
||||
import android.net.Uri
|
||||
import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||
import eu.kanade.tachiyomi.data.database.models.MangaImpl
|
||||
import eu.kanade.tachiyomi.data.database.models.create
|
||||
import eu.kanade.tachiyomi.domain.manga.models.Manga
|
||||
import eu.kanade.tachiyomi.network.NetworkHelper
|
||||
import eu.kanade.tachiyomi.source.model.SChapter
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import yokai.domain.chapter.interactor.GetChapter
|
||||
import yokai.domain.manga.interactor.GetManga
|
||||
|
||||
abstract class DelegatedHttpSource {
|
||||
|
||||
var delegate: HttpSource? = null
|
||||
abstract val domainName: String
|
||||
|
||||
protected val getChapter: GetChapter by injectLazy()
|
||||
protected val getManga: GetManga by injectLazy()
|
||||
|
||||
protected val network: NetworkHelper by injectLazy()
|
||||
|
||||
abstract fun canOpenUrl(uri: Uri): Boolean
|
||||
abstract fun chapterUrl(uri: Uri): String?
|
||||
open fun pageNumber(uri: Uri): Int? = uri.pathSegments.lastOrNull()?.toIntOrNull()
|
||||
abstract suspend fun fetchMangaFromChapterUrl(uri: Uri): Triple<Chapter, Manga, List<SChapter>>?
|
||||
|
||||
protected open suspend fun getMangaInfo(url: String): Manga? {
|
||||
val id = delegate?.id ?: return null
|
||||
val manga = Manga.create(url, "", id)
|
||||
val networkManga = delegate?.getMangaDetails(manga.copy()) ?: return null
|
||||
val newManga = MangaImpl().apply {
|
||||
this.url = url
|
||||
title = try { networkManga.title } catch (e: Exception) { "" }
|
||||
source = id
|
||||
}
|
||||
newManga.copyFrom(networkManga)
|
||||
return newManga
|
||||
}
|
||||
|
||||
suspend fun getChapters(url: String): List<SChapter>? {
|
||||
val id = delegate?.id ?: return null
|
||||
val manga = Manga.create(url, "", id)
|
||||
return delegate?.getChapterList(manga)
|
||||
}
|
||||
}
|
|
@ -1,22 +1,31 @@
|
|||
package eu.kanade.tachiyomi.source.online.all
|
||||
|
||||
import android.net.Uri
|
||||
import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||
import eu.kanade.tachiyomi.data.database.models.toChapter
|
||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.domain.manga.models.Manga
|
||||
import eu.kanade.tachiyomi.source.model.SChapter
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import eu.kanade.tachiyomi.source.online.DelegatedHttpSource
|
||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||
import java.util.Locale
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.withContext
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import yokai.domain.chapter.interactor.GetChapter
|
||||
import yokai.domain.manga.interactor.GetManga
|
||||
import yokai.i18n.MR
|
||||
import yokai.util.lang.getString
|
||||
|
||||
class Cubari : DelegatedHttpSource() {
|
||||
class Cubari(delegate: HttpSource) :
|
||||
DelegatedHttpSource(delegate) {
|
||||
|
||||
private val getManga: GetManga = Injekt.get()
|
||||
private val getChapter: GetChapter = Injekt.get()
|
||||
|
||||
override val lang = "all"
|
||||
|
||||
override val domainName: String = "cubari"
|
||||
override fun canOpenUrl(uri: Uri): Boolean = true
|
||||
|
||||
|
@ -24,24 +33,24 @@ class Cubari : DelegatedHttpSource() {
|
|||
|
||||
override fun pageNumber(uri: Uri): Int? = uri.pathSegments.getOrNull(4)?.toIntOrNull()
|
||||
|
||||
override suspend fun fetchMangaFromChapterUrl(uri: Uri): Triple<Chapter, Manga, List<SChapter>>? {
|
||||
override suspend fun fetchMangaFromChapterUrl(uri: Uri): Triple<SChapter, SManga, List<SChapter>>? {
|
||||
val cubariType = uri.pathSegments.getOrNull(1)?.lowercase(Locale.ROOT) ?: return null
|
||||
val cubariPath = uri.pathSegments.getOrNull(2) ?: return null
|
||||
val chapterNumber = uri.pathSegments.getOrNull(3)?.replace("-", ".")?.toFloatOrNull() ?: return null
|
||||
val mangaUrl = "/read/$cubariType/$cubariPath"
|
||||
return withContext(Dispatchers.IO) {
|
||||
val deferredManga = async {
|
||||
getManga.awaitByUrlAndSource(mangaUrl, delegate?.id!!) ?: getMangaInfo(mangaUrl)
|
||||
getManga.awaitByUrlAndSource(mangaUrl, delegate.id) ?: getMangaDetailsByUrl(mangaUrl)
|
||||
}
|
||||
val deferredChapters = async {
|
||||
getManga.awaitByUrlAndSource(mangaUrl, delegate?.id!!)?.let { manga ->
|
||||
getManga.awaitByUrlAndSource(mangaUrl, delegate.id)?.let { manga ->
|
||||
val chapters = getChapter.awaitAll(manga, false)
|
||||
val chapter = findChapter(chapters, cubariType, chapterNumber)
|
||||
if (chapter != null) {
|
||||
return@async chapters
|
||||
}
|
||||
}
|
||||
getChapters(mangaUrl)
|
||||
getChapterListByUrl(mangaUrl)
|
||||
}
|
||||
val manga = deferredManga.await()
|
||||
val chapters = deferredChapters.await()
|
||||
|
@ -50,11 +59,7 @@ class Cubari : DelegatedHttpSource() {
|
|||
?: error(
|
||||
context.getString(MR.strings.chapter_not_found),
|
||||
)
|
||||
if (manga != null) {
|
||||
Triple(trueChapter, manga, chapters.orEmpty())
|
||||
} else {
|
||||
null
|
||||
}
|
||||
Triple(trueChapter, manga, chapters)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
package eu.kanade.tachiyomi.source.online.all
|
||||
|
||||
import android.net.Uri
|
||||
import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||
import eu.kanade.tachiyomi.data.database.models.toChapter
|
||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.domain.manga.models.Manga
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.network.await
|
||||
import eu.kanade.tachiyomi.source.SourceManager
|
||||
import eu.kanade.tachiyomi.source.model.SChapter
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import eu.kanade.tachiyomi.source.online.DelegatedHttpSource
|
||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||
import java.util.Locale
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.async
|
||||
|
@ -20,10 +20,15 @@ import okhttp3.CacheControl
|
|||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import yokai.domain.manga.interactor.GetManga
|
||||
import yokai.i18n.MR
|
||||
import yokai.util.lang.getString
|
||||
|
||||
class MangaDex : DelegatedHttpSource() {
|
||||
class MangaDex(delegate: HttpSource) : DelegatedHttpSource(delegate) {
|
||||
|
||||
private val getManga: GetManga = Injekt.get()
|
||||
|
||||
override val lang: String = "all"
|
||||
|
||||
override val domainName: String = "mangadex"
|
||||
|
||||
|
@ -42,13 +47,13 @@ class MangaDex : DelegatedHttpSource() {
|
|||
return uri.pathSegments.getOrNull(2)?.toIntOrNull()
|
||||
}
|
||||
|
||||
override suspend fun fetchMangaFromChapterUrl(uri: Uri): Triple<Chapter, Manga, List<SChapter>>? {
|
||||
override suspend fun fetchMangaFromChapterUrl(uri: Uri): Triple<SChapter, SManga, List<SChapter>>? {
|
||||
val url = chapterUrl(uri) ?: return null
|
||||
val request =
|
||||
GET("https:///api.mangadex.org/v2$url", delegate!!.headers, CacheControl.FORCE_NETWORK)
|
||||
val response = network.client.newCall(request).await()
|
||||
if (response.code != 200) throw Exception("HTTP error ${response.code}")
|
||||
val body = response.body.string().orEmpty()
|
||||
val body = response.body.string()
|
||||
if (body.isEmpty()) {
|
||||
throw Exception("Null Response")
|
||||
}
|
||||
|
@ -56,28 +61,19 @@ class MangaDex : DelegatedHttpSource() {
|
|||
val jsonObject = Json.decodeFromString<MangaDexChapterData>(body)
|
||||
val dataObject = jsonObject.data ?: throw Exception("Chapter not found")
|
||||
val mangaId = dataObject.mangaId ?: throw Exception("No manga associated with chapter")
|
||||
val langCode = getRealLangCode(dataObject.language ?: "en").uppercase(Locale.getDefault())
|
||||
// Use the correct MangaDex source based on the language code, or the api will not return
|
||||
// the correct chapter list
|
||||
delegate = sourceManager.getOnlineSources().find { it.toString() == "MangaDex ($langCode)" }
|
||||
?: error("Source not found")
|
||||
val mangaUrl = "/manga/$mangaId/"
|
||||
return withContext(Dispatchers.IO) {
|
||||
val deferredManga = async {
|
||||
getManga.awaitByUrlAndSource(mangaUrl, delegate?.id!!) ?: getMangaInfo(mangaUrl)
|
||||
getManga.awaitByUrlAndSource(mangaUrl, delegate.id) ?: getMangaDetailsByUrl(mangaUrl)
|
||||
}
|
||||
val deferredChapters = async { getChapters(mangaUrl) }
|
||||
val deferredChapters = async { getChapterListByUrl(mangaUrl) }
|
||||
val manga = deferredManga.await()
|
||||
val chapters = deferredChapters.await()
|
||||
val context = Injekt.get<PreferencesHelper>().context
|
||||
val trueChapter = chapters?.find { it.url == "/api$url" }?.toChapter() ?: error(
|
||||
val trueChapter = chapters.find { it.url == "/api$url" }?.toChapter() ?: error(
|
||||
context.getString(MR.strings.chapter_not_found),
|
||||
)
|
||||
if (manga != null) {
|
||||
Triple(trueChapter, manga, chapters.orEmpty())
|
||||
} else {
|
||||
null
|
||||
}
|
||||
Triple(trueChapter, manga, chapters)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,110 +0,0 @@
|
|||
package eu.kanade.tachiyomi.source.online.english
|
||||
|
||||
import android.net.Uri
|
||||
import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||
import eu.kanade.tachiyomi.data.database.models.MangaImpl
|
||||
import eu.kanade.tachiyomi.data.database.models.toChapter
|
||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.domain.manga.models.Manga
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.network.POST
|
||||
import eu.kanade.tachiyomi.network.await
|
||||
import eu.kanade.tachiyomi.source.model.SChapter
|
||||
import eu.kanade.tachiyomi.source.online.DelegatedHttpSource
|
||||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.withContext
|
||||
import okhttp3.FormBody
|
||||
import okhttp3.Request
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import yokai.i18n.MR
|
||||
import yokai.util.lang.getString
|
||||
|
||||
open class FoolSlide(override val domainName: String, private val urlModifier: String = "") :
|
||||
DelegatedHttpSource
|
||||
() {
|
||||
|
||||
override fun canOpenUrl(uri: Uri): Boolean = true
|
||||
|
||||
override fun chapterUrl(uri: Uri): String? {
|
||||
val offset = if (urlModifier.isEmpty()) 0 else 1
|
||||
val mangaName = uri.pathSegments.getOrNull(1 + offset) ?: return null
|
||||
val lang = uri.pathSegments.getOrNull(2 + offset) ?: return null
|
||||
val volume = uri.pathSegments.getOrNull(3 + offset) ?: return null
|
||||
val chapterNumber = uri.pathSegments.getOrNull(4 + offset) ?: return null
|
||||
val subChapterNumber = uri.pathSegments.getOrNull(5 + offset)?.toIntOrNull()?.toString()
|
||||
return "$urlModifier/read/" + listOfNotNull(
|
||||
mangaName,
|
||||
lang,
|
||||
volume,
|
||||
chapterNumber,
|
||||
subChapterNumber,
|
||||
).joinToString("/") + "/"
|
||||
}
|
||||
|
||||
override fun pageNumber(uri: Uri): Int? {
|
||||
val count = uri.pathSegments.count()
|
||||
if (count > 2 && uri.pathSegments[count - 2] == "page") {
|
||||
return super.pageNumber(uri)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
override suspend fun fetchMangaFromChapterUrl(uri: Uri): Triple<Chapter, Manga, List<SChapter>>? {
|
||||
val offset = if (urlModifier.isEmpty()) 0 else 1
|
||||
val mangaName = uri.pathSegments.getOrNull(1 + offset) ?: return null
|
||||
var chapterNumber = uri.pathSegments.getOrNull(4 + offset) ?: return null
|
||||
val subChapterNumber = uri.pathSegments.getOrNull(5 + offset)?.toIntOrNull()
|
||||
if (subChapterNumber != null) {
|
||||
chapterNumber += ".$subChapterNumber"
|
||||
}
|
||||
return withContext(Dispatchers.IO) {
|
||||
val mangaUrl = "$urlModifier/series/$mangaName/"
|
||||
val sourceId = delegate?.id ?: return@withContext null
|
||||
val deferredManga = async {
|
||||
getManga.awaitByUrlAndSource(mangaUrl, sourceId) ?: getManga(mangaUrl)
|
||||
}
|
||||
val chapterUrl = chapterUrl(uri)
|
||||
val deferredChapters = async { getChapters(mangaUrl) }
|
||||
val manga = deferredManga.await()
|
||||
val chapters = deferredChapters.await()
|
||||
val context = Injekt.get<PreferencesHelper>().context
|
||||
val trueChapter = chapters?.find { it.url == chapterUrl }?.toChapter() ?: error(
|
||||
context.getString(MR.strings.chapter_not_found),
|
||||
)
|
||||
if (manga != null) Triple(trueChapter, manga, chapters) else null
|
||||
}
|
||||
}
|
||||
|
||||
open suspend fun getManga(url: String): Manga? {
|
||||
val request = GET("${delegate!!.baseUrl}$url")
|
||||
val document = network.client.newCall(allowAdult(request)).await().asJsoup()
|
||||
val mangaDetailsInfoSelector = "div.info"
|
||||
val infoElement = document.select(mangaDetailsInfoSelector).first()?.text() ?: return null
|
||||
return MangaImpl().apply {
|
||||
this.url = url
|
||||
source = delegate?.id ?: -1
|
||||
title = infoElement.substringAfter("Title:").substringBefore("Author:").trim()
|
||||
author = infoElement.substringAfter("Author:").substringBefore("Artist:").trim()
|
||||
artist = infoElement.substringAfter("Artist:").substringBefore("Synopsis:").trim()
|
||||
description = infoElement.substringAfter("Synopsis:").trim()
|
||||
thumbnail_url = document.select("div.thumbnail img").firstOrNull()?.attr("abs:src")?.trim()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform a GET request into a POST request that automatically authorizes all adult content
|
||||
*/
|
||||
private fun allowAdult(request: Request) = allowAdult(request.url.toString())
|
||||
|
||||
private fun allowAdult(url: String): Request {
|
||||
return POST(
|
||||
url,
|
||||
body = FormBody.Builder()
|
||||
.add("adult", "true")
|
||||
.build(),
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
package eu.kanade.tachiyomi.source.online.english
|
||||
|
||||
import eu.kanade.tachiyomi.data.database.models.MangaImpl
|
||||
import eu.kanade.tachiyomi.domain.manga.models.Manga
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.network.await
|
||||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
import eu.kanade.tachiyomi.util.lang.capitalizeWords
|
||||
|
||||
class KireiCake : FoolSlide("kireicake") {
|
||||
|
||||
override suspend fun getManga(url: String): Manga? {
|
||||
val request = GET("${delegate!!.baseUrl}$url")
|
||||
val document = network.client.newCall(request).await().asJsoup()
|
||||
val mangaDetailsInfoSelector = "div.info"
|
||||
return MangaImpl().apply {
|
||||
this.url = url
|
||||
source = delegate?.id ?: -1
|
||||
title = document.select("$mangaDetailsInfoSelector li:has(b:contains(title))").first()
|
||||
?.ownText()?.substringAfter(":")?.trim()
|
||||
?: url.split("/").last().replace("_", " " + "").capitalizeWords()
|
||||
description =
|
||||
document.select("$mangaDetailsInfoSelector li:has(b:contains(description))").first()
|
||||
?.ownText()?.substringAfter(":")
|
||||
thumbnail_url = document.select("div.thumbnail img").firstOrNull()?.attr("abs:src")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,24 +1,31 @@
|
|||
package eu.kanade.tachiyomi.source.online.english
|
||||
|
||||
import android.net.Uri
|
||||
import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||
import eu.kanade.tachiyomi.data.database.models.toChapter
|
||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.domain.manga.models.Manga
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.network.await
|
||||
import eu.kanade.tachiyomi.source.model.SChapter
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import eu.kanade.tachiyomi.source.online.DelegatedHttpSource
|
||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.withContext
|
||||
import okhttp3.CacheControl
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import yokai.domain.manga.interactor.GetManga
|
||||
import yokai.i18n.MR
|
||||
import yokai.util.lang.getString
|
||||
|
||||
class MangaPlus : DelegatedHttpSource() {
|
||||
class MangaPlus(delegate: HttpSource) :
|
||||
DelegatedHttpSource(delegate) {
|
||||
|
||||
private val getManga: GetManga = Injekt.get()
|
||||
|
||||
override val lang: String get() = delegate.lang
|
||||
|
||||
override val domainName: String = "jumpg-webapi.tokyo-cdn"
|
||||
|
||||
private val titleIdRegex =
|
||||
|
@ -34,11 +41,11 @@ class MangaPlus : DelegatedHttpSource() {
|
|||
|
||||
override fun pageNumber(uri: Uri): Int? = null
|
||||
|
||||
override suspend fun fetchMangaFromChapterUrl(uri: Uri): Triple<Chapter, Manga, List<SChapter>>? {
|
||||
override suspend fun fetchMangaFromChapterUrl(uri: Uri): Triple<SChapter, SManga, List<SChapter>>? {
|
||||
val url = chapterUrl(uri) ?: return null
|
||||
val request = GET(
|
||||
chapterUrlTemplate.replace("##", uri.pathSegments[1]),
|
||||
delegate!!.headers,
|
||||
delegate.headers,
|
||||
CacheControl.FORCE_NETWORK,
|
||||
)
|
||||
return withContext(Dispatchers.IO) {
|
||||
|
@ -53,16 +60,15 @@ class MangaPlus : DelegatedHttpSource() {
|
|||
val trimmedTitle = title.substring(0, title.length - 1)
|
||||
val mangaUrl = "#/titles/$titleId"
|
||||
val deferredManga = async {
|
||||
getManga.awaitByUrlAndSource(mangaUrl, delegate?.id!!) ?: getMangaInfo(mangaUrl)
|
||||
getManga.awaitByUrlAndSource(mangaUrl, delegate.id) ?: getMangaDetailsByUrl(mangaUrl)
|
||||
}
|
||||
val deferredChapters = async { getChapters(mangaUrl) }
|
||||
val deferredChapters = async { getChapterListByUrl(mangaUrl) }
|
||||
val manga = deferredManga.await()
|
||||
val chapters = deferredChapters.await()
|
||||
val context = Injekt.get<PreferencesHelper>().context
|
||||
val trueChapter = chapters?.find { it.url == url }?.toChapter() ?: error(
|
||||
val trueChapter = chapters.find { it.url == url }?.toChapter() ?: error(
|
||||
context.getString(MR.strings.chapter_not_found),
|
||||
)
|
||||
if (manga != null) {
|
||||
Triple(
|
||||
trueChapter,
|
||||
manga.apply {
|
||||
|
@ -70,9 +76,6 @@ class MangaPlus : DelegatedHttpSource() {
|
|||
},
|
||||
chapters,
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ import com.hippo.unifile.UniFile
|
|||
import eu.kanade.tachiyomi.data.cache.CoverCache
|
||||
import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||
import eu.kanade.tachiyomi.data.database.models.History
|
||||
import eu.kanade.tachiyomi.data.database.models.create
|
||||
import eu.kanade.tachiyomi.data.database.models.defaultReaderType
|
||||
import eu.kanade.tachiyomi.data.database.models.orientationType
|
||||
import eu.kanade.tachiyomi.data.database.models.readingModeType
|
||||
|
@ -307,6 +308,7 @@ class ReaderViewModel(
|
|||
return delegatedSource.pageNumber(url)?.minus(1)
|
||||
}
|
||||
|
||||
// FIXME: Unused at the moment, handles J2K's delegated deep link, refactor or remove later
|
||||
suspend fun loadChapterURL(url: Uri) {
|
||||
val host = url.host ?: return
|
||||
val context = Injekt.get<Application>()
|
||||
|
@ -314,9 +316,7 @@ class ReaderViewModel(
|
|||
context.getString(MR.strings.source_not_installed),
|
||||
)
|
||||
val chapterUrl = delegatedSource.chapterUrl(url)
|
||||
val sourceId = delegatedSource.delegate?.id ?: error(
|
||||
context.getString(MR.strings.source_not_installed),
|
||||
)
|
||||
val sourceId = delegatedSource.delegate.id
|
||||
if (chapterUrl != null) {
|
||||
val dbChapter = getChapter.awaitAllByUrl(chapterUrl, false).find {
|
||||
val source = getManga.awaitById(it.manga_id!!)?.source ?: return@find false
|
||||
|
@ -334,7 +334,9 @@ class ReaderViewModel(
|
|||
}
|
||||
val info = delegatedSource.fetchMangaFromChapterUrl(url)
|
||||
if (info != null) {
|
||||
val (chapter, manga, chapters) = info
|
||||
val (sChapter, sManga, chapters) = info
|
||||
val manga = Manga.create(sourceId).apply { copyFrom(sManga) }
|
||||
val chapter = Chapter.create().apply { copyFrom(sChapter) }
|
||||
val id = insertManga.await(manga)
|
||||
manga.id = id ?: manga.id
|
||||
chapter.manga_id = manga.id
|
||||
|
|
|
@ -0,0 +1,378 @@
|
|||
package eu.kanade.tachiyomi.source.online
|
||||
|
||||
import android.net.Uri
|
||||
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 okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import rx.Observable
|
||||
|
||||
abstract class DelegatedHttpSource(val delegate: HttpSource): HttpSource() {
|
||||
/**
|
||||
* Returns the request for the popular manga given the page.
|
||||
*
|
||||
* @param page the page number to retrieve.
|
||||
*/
|
||||
override fun popularMangaRequest(page: Int) =
|
||||
throw UnsupportedOperationException("Should never be called!")
|
||||
|
||||
/**
|
||||
* Parses the response from the site and returns a [MangasPage] object.
|
||||
*
|
||||
* @param response the response from the site.
|
||||
*/
|
||||
override fun popularMangaParse(response: Response) =
|
||||
throw UnsupportedOperationException("Should never be called!")
|
||||
|
||||
/**
|
||||
* Returns the request for the search manga given the page.
|
||||
*
|
||||
* @param page the page number to retrieve.
|
||||
* @param query the search query.
|
||||
* @param filters the list of filters to apply.
|
||||
*/
|
||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList) =
|
||||
throw UnsupportedOperationException("Should never be called!")
|
||||
|
||||
/**
|
||||
* Parses the response from the site and returns a [MangasPage] object.
|
||||
*
|
||||
* @param response the response from the site.
|
||||
*/
|
||||
override fun searchMangaParse(response: Response) =
|
||||
throw UnsupportedOperationException("Should never be called!")
|
||||
|
||||
/**
|
||||
* Returns the request for latest manga given the page.
|
||||
*
|
||||
* @param page the page number to retrieve.
|
||||
*/
|
||||
override fun latestUpdatesRequest(page: Int) =
|
||||
throw UnsupportedOperationException("Should never be called!")
|
||||
|
||||
/**
|
||||
* Parses the response from the site and returns a [MangasPage] object.
|
||||
*
|
||||
* @param response the response from the site.
|
||||
*/
|
||||
override fun latestUpdatesParse(response: Response) =
|
||||
throw UnsupportedOperationException("Should never be called!")
|
||||
|
||||
/**
|
||||
* Parses the response from the site and returns the details of a manga.
|
||||
*
|
||||
* @param response the response from the site.
|
||||
*/
|
||||
override fun mangaDetailsParse(response: Response) =
|
||||
throw UnsupportedOperationException("Should never be called!")
|
||||
|
||||
/**
|
||||
* Parses the response from the site and returns a list of chapters.
|
||||
*
|
||||
* @param response the response from the site.
|
||||
*/
|
||||
override fun chapterListParse(response: Response) =
|
||||
throw UnsupportedOperationException("Should never be called!")
|
||||
|
||||
/**
|
||||
* Parses the response from the site and returns a SChapter Object.
|
||||
*
|
||||
* @param response the response from the site.
|
||||
*/
|
||||
override fun chapterPageParse(response: Response) =
|
||||
throw UnsupportedOperationException("Should never be called!")
|
||||
|
||||
/**
|
||||
* Parses the response from the site and returns a list of pages.
|
||||
*
|
||||
* @param response the response from the site.
|
||||
*/
|
||||
override fun pageListParse(response: Response) =
|
||||
throw UnsupportedOperationException("Should never be called!")
|
||||
|
||||
/**
|
||||
* Parses the response from the site and returns the absolute url to the source image.
|
||||
*
|
||||
* @param response the response from the site.
|
||||
*/
|
||||
override fun imageUrlParse(response: Response) =
|
||||
throw UnsupportedOperationException("Should never be called!")
|
||||
|
||||
abstract val domainName: String
|
||||
|
||||
/**
|
||||
* Base url of the website without the trailing slash, like: http://mysite.com
|
||||
*/
|
||||
override val baseUrl get() = delegate.baseUrl
|
||||
|
||||
/**
|
||||
* Headers used for requests.
|
||||
*/
|
||||
override val headers get() = delegate.headers
|
||||
|
||||
/**
|
||||
* Whether the source has support for latest updates.
|
||||
*/
|
||||
override val supportsLatest get() = delegate.supportsLatest
|
||||
|
||||
/**
|
||||
* Name of the source.
|
||||
*/
|
||||
final override val name get() = delegate.name
|
||||
|
||||
// ===> OPTIONAL FIELDS
|
||||
|
||||
/**
|
||||
* Id of the source. By default it uses a generated id using the first 16 characters (64 bits)
|
||||
* of the MD5 of the string: sourcename/language/versionId
|
||||
* Note the generated id sets the sign bit to 0.
|
||||
*/
|
||||
override val id get() = delegate.id
|
||||
|
||||
/**
|
||||
* Default network client for doing requests.
|
||||
*/
|
||||
final override val client get() = delegate.client
|
||||
|
||||
/**
|
||||
* You must NEVER call super.client if you override this!
|
||||
*/
|
||||
open val baseHttpClient: OkHttpClient? = null
|
||||
open val networkHttpClient: OkHttpClient get() = network.client
|
||||
|
||||
/**
|
||||
* Visible name of the source.
|
||||
*/
|
||||
override fun toString() = delegate.toString()
|
||||
|
||||
/**
|
||||
* Returns an observable containing a page with a list of manga. Normally it's not needed to
|
||||
* override this method.
|
||||
*
|
||||
* @param page the page number to retrieve.
|
||||
*/
|
||||
@Deprecated("Use the non-RxJava API instead", replaceWith = ReplaceWith("getPopularManga"))
|
||||
override fun fetchPopularManga(page: Int): Observable<MangasPage> {
|
||||
ensureDelegateCompatible()
|
||||
return delegate.fetchPopularManga(page)
|
||||
}
|
||||
|
||||
override suspend fun getPopularManga(page: Int): MangasPage {
|
||||
ensureDelegateCompatible()
|
||||
return delegate.getPopularManga(page)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an observable containing a page with a list of manga. Normally it's not needed to
|
||||
* override this method.
|
||||
*
|
||||
* @param page the page number to retrieve.
|
||||
* @param query the search query.
|
||||
* @param filters the list of filters to apply.
|
||||
*/
|
||||
@Deprecated("Use the non-RxJava API instead", replaceWith = ReplaceWith("getSearchManga"))
|
||||
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
|
||||
ensureDelegateCompatible()
|
||||
return delegate.fetchSearchManga(page, query, filters)
|
||||
}
|
||||
|
||||
override suspend fun getSearchManga(page: Int, query: String, filters: FilterList): MangasPage {
|
||||
ensureDelegateCompatible()
|
||||
return delegate.getSearchManga(page, query, filters)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an observable containing a page with a list of latest manga updates.
|
||||
*
|
||||
* @param page the page number to retrieve.
|
||||
*/
|
||||
@Deprecated("Use the non-RxJava API instead", replaceWith = ReplaceWith("getLatestUpdates"))
|
||||
override fun fetchLatestUpdates(page: Int): Observable<MangasPage> {
|
||||
ensureDelegateCompatible()
|
||||
return delegate.fetchLatestUpdates(page)
|
||||
}
|
||||
|
||||
override suspend fun getLatestUpdates(page: Int): MangasPage {
|
||||
ensureDelegateCompatible()
|
||||
return delegate.getLatestUpdates(page)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an observable with the updated details for a manga. Normally it's not needed to
|
||||
* override this method.
|
||||
*
|
||||
* @param manga the manga to be updated.
|
||||
*/
|
||||
@Deprecated("Use the 1.x API instead", replaceWith = ReplaceWith("getMangaDetails"))
|
||||
override fun fetchMangaDetails(manga: SManga): Observable<SManga> {
|
||||
ensureDelegateCompatible()
|
||||
return delegate.fetchMangaDetails(manga)
|
||||
}
|
||||
|
||||
/**
|
||||
* [1.x API] Get the updated details for a manga.
|
||||
*/
|
||||
override suspend fun getMangaDetails(manga: SManga): SManga {
|
||||
ensureDelegateCompatible()
|
||||
return delegate.getMangaDetails(manga)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the request for the details of a manga. Override only if it's needed to change the
|
||||
* url, send different headers or request method like POST.
|
||||
*
|
||||
* @param manga the manga to be updated.
|
||||
*/
|
||||
override fun mangaDetailsRequest(manga: SManga): Request {
|
||||
ensureDelegateCompatible()
|
||||
return delegate.mangaDetailsRequest(manga)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an observable with the updated chapter list for a manga. Normally it's not needed to
|
||||
* override this method. If a manga is licensed an empty chapter list observable is returned
|
||||
*
|
||||
* @param manga the manga to look for chapters.
|
||||
*/
|
||||
@Deprecated("Use the 1.x API instead", replaceWith = ReplaceWith("getChapterList"))
|
||||
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
|
||||
ensureDelegateCompatible()
|
||||
return delegate.fetchChapterList(manga)
|
||||
}
|
||||
|
||||
/**
|
||||
* [1.x API] Get all the available chapters for a manga.
|
||||
*/
|
||||
override suspend fun getChapterList(manga: SManga): List<SChapter> {
|
||||
ensureDelegateCompatible()
|
||||
return delegate.getChapterList(manga)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an observable with the page list for a chapter.
|
||||
*
|
||||
* @param chapter the chapter whose page list has to be fetched.
|
||||
*/
|
||||
@Deprecated("Use the 1.x API instead", replaceWith = ReplaceWith("getPageList"))
|
||||
override fun fetchPageList(chapter: SChapter): Observable<List<Page>> {
|
||||
ensureDelegateCompatible()
|
||||
return delegate.fetchPageList(chapter)
|
||||
}
|
||||
|
||||
/**
|
||||
* [1.x API] Get the list of pages a chapter has.
|
||||
*/
|
||||
override suspend fun getPageList(chapter: SChapter): List<Page> {
|
||||
ensureDelegateCompatible()
|
||||
return delegate.getPageList(chapter)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an observable with the page containing the source url of the image. If there's any
|
||||
* error, it will return null instead of throwing an exception.
|
||||
*
|
||||
* @param page the page whose source image has to be fetched.
|
||||
*/
|
||||
@Deprecated("Use the non-RxJava API instead", replaceWith = ReplaceWith("getImageUrl"))
|
||||
override fun fetchImageUrl(page: Page): Observable<String> {
|
||||
ensureDelegateCompatible()
|
||||
return delegate.fetchImageUrl(page)
|
||||
}
|
||||
|
||||
override suspend fun getImageUrl(page: Page): String {
|
||||
ensureDelegateCompatible()
|
||||
return delegate.getImageUrl(page)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the response of the source image.
|
||||
*
|
||||
* @param page the page whose source image has to be downloaded.
|
||||
*/
|
||||
override suspend fun getImage(page: Page): Response {
|
||||
ensureDelegateCompatible()
|
||||
return delegate.getImage(page)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the url of the provided manga
|
||||
*
|
||||
* @since extensions-lib 1.4
|
||||
* @param manga the manga
|
||||
* @return url of the manga
|
||||
*/
|
||||
override fun getMangaUrl(manga: SManga): String {
|
||||
ensureDelegateCompatible()
|
||||
return delegate.getMangaUrl(manga)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the url of the provided chapter
|
||||
*
|
||||
* @since extensions-lib 1.4
|
||||
* @param chapter the chapter
|
||||
* @return url of the chapter
|
||||
*/
|
||||
override fun getChapterUrl(chapter: SChapter): String {
|
||||
ensureDelegateCompatible()
|
||||
return delegate.getChapterUrl(chapter)
|
||||
}
|
||||
|
||||
/**
|
||||
* Called before inserting a new chapter into database. Use it if you need to override chapter
|
||||
* fields, like the title or the chapter number. Do not change anything to [manga].
|
||||
*
|
||||
* @param chapter the chapter to be added.
|
||||
* @param manga the manga of the chapter.
|
||||
*/
|
||||
override fun prepareNewChapter(chapter: SChapter, manga: SManga) {
|
||||
ensureDelegateCompatible()
|
||||
return delegate.prepareNewChapter(chapter, manga)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of filters for the source.
|
||||
*/
|
||||
override fun getFilterList() = delegate.getFilterList()
|
||||
|
||||
abstract fun canOpenUrl(uri: Uri): Boolean
|
||||
abstract fun chapterUrl(uri: Uri): String?
|
||||
open fun pageNumber(uri: Uri): Int? = uri.pathSegments.lastOrNull()?.toIntOrNull()
|
||||
abstract suspend fun fetchMangaFromChapterUrl(uri: Uri): Triple<SChapter, SManga, List<SChapter>>?
|
||||
|
||||
open suspend fun getMangaDetailsByUrl(url: String): SManga {
|
||||
val manga = SManga.create().apply {
|
||||
this.url = url
|
||||
this.title = ""
|
||||
}
|
||||
return delegate.getMangaDetails(manga.copy())
|
||||
}
|
||||
|
||||
open suspend fun getChapterListByUrl(url: String): List<SChapter> {
|
||||
val manga = SManga.create().apply {
|
||||
this.url = url
|
||||
this.title = ""
|
||||
}
|
||||
return delegate.getChapterList(manga)
|
||||
}
|
||||
|
||||
protected open fun ensureDelegateCompatible() {
|
||||
if (versionId != delegate.versionId || lang != delegate.lang) {
|
||||
throw IncompatibleDelegateException(
|
||||
"Delegate source is not compatible (" +
|
||||
"versionId: $versionId <=> ${delegate.versionId}, lang: $lang <=> ${delegate.lang}" +
|
||||
")!",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class IncompatibleDelegateException(message: String) : RuntimeException(message)
|
||||
|
||||
init {
|
||||
delegate.bindDelegate(this)
|
||||
}
|
||||
}
|
|
@ -12,21 +12,22 @@ 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.util.awaitSingle
|
||||
import java.net.URI
|
||||
import java.net.URISyntaxException
|
||||
import java.security.MessageDigest
|
||||
import okhttp3.Headers
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import rx.Observable
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.net.URI
|
||||
import java.net.URISyntaxException
|
||||
import java.security.MessageDigest
|
||||
|
||||
/**
|
||||
* A simple implementation for sources from a website.
|
||||
*/
|
||||
@Suppress("unused")
|
||||
abstract class HttpSource : CatalogueSource {
|
||||
private var delegate: DelegatedHttpSource? = null
|
||||
|
||||
/**
|
||||
* Network service.
|
||||
|
@ -59,7 +60,7 @@ abstract class HttpSource : CatalogueSource {
|
|||
/**
|
||||
* Headers used for requests.
|
||||
*/
|
||||
val headers: Headers by lazy { headersBuilder().build() }
|
||||
open val headers: Headers by lazy { headersBuilder().build() }
|
||||
|
||||
/**
|
||||
* Default network client for doing requests.
|
||||
|
@ -67,6 +68,10 @@ abstract class HttpSource : CatalogueSource {
|
|||
open val client: OkHttpClient
|
||||
get() = network.client
|
||||
|
||||
fun bindDelegate(delegate: DelegatedHttpSource) {
|
||||
this.delegate = delegate
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a unique ID for the source based on the provided [name], [lang] and
|
||||
* [versionId]. It will use the first 16 characters (64 bits) of the MD5 of the string
|
||||
|
@ -283,6 +288,13 @@ abstract class HttpSource : CatalogueSource {
|
|||
*/
|
||||
protected abstract fun chapterListParse(response: Response): List<SChapter>
|
||||
|
||||
/**
|
||||
* Parses the response from the site and returns a SChapter Object.
|
||||
*
|
||||
* @param response the response from the site.
|
||||
*/
|
||||
protected abstract fun chapterPageParse(response: Response): SChapter
|
||||
|
||||
/**
|
||||
* Get the list of pages a chapter has. Pages should be returned
|
||||
* in the expected order; the index is ignored.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue