So far just mangadex, jamini's, mangaplus, kireicakepull/3963/head
parent
b4151e6761
commit
ad57086d8c
@ -0,0 +1,45 @@
|
|||||||
|
package eu.kanade.tachiyomi.source.online
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
|
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.MangaImpl
|
||||||
|
import eu.kanade.tachiyomi.network.NetworkHelper
|
||||||
|
import eu.kanade.tachiyomi.source.fetchChapterListAsync
|
||||||
|
import eu.kanade.tachiyomi.source.model.SChapter
|
||||||
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
|
||||||
|
abstract class DelegatedHttpSource {
|
||||||
|
|
||||||
|
var delegate: HttpSource? = null
|
||||||
|
abstract val domainName: String
|
||||||
|
|
||||||
|
protected val db: DatabaseHelper 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 fun getMangaInfo(url: String): Manga? {
|
||||||
|
val id = delegate?.id ?: return null
|
||||||
|
val manga = Manga.create(url, "", id)
|
||||||
|
val networkManga = delegate?.fetchMangaDetails(manga)?.toBlocking()?.single() ?: 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?.fetchChapterListAsync(manga)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,105 @@
|
|||||||
|
package eu.kanade.tachiyomi.source.online.all
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
|
import com.github.salomonbrys.kotson.nullInt
|
||||||
|
import com.github.salomonbrys.kotson.nullString
|
||||||
|
import com.github.salomonbrys.kotson.obj
|
||||||
|
import com.google.gson.JsonParser
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
|
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.online.DelegatedHttpSource
|
||||||
|
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 uy.kohesive.injekt.injectLazy
|
||||||
|
|
||||||
|
class MangaDex : DelegatedHttpSource() {
|
||||||
|
|
||||||
|
override val domainName: String = "mangadex"
|
||||||
|
|
||||||
|
val sourceManager: SourceManager by injectLazy()
|
||||||
|
|
||||||
|
override fun canOpenUrl(uri: Uri): Boolean {
|
||||||
|
return uri.pathSegments?.lastOrNull() != "comments"
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun chapterUrl(uri: Uri): String? {
|
||||||
|
val chapterNumber = uri.pathSegments.getOrNull(1) ?: return null
|
||||||
|
return "/api/chapter/$chapterNumber"
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun pageNumber(uri: Uri): Int? {
|
||||||
|
return uri.pathSegments.getOrNull(2)?.toIntOrNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun fetchMangaFromChapterUrl(uri: Uri): Triple<Chapter, Manga, List<SChapter>>? {
|
||||||
|
val url = chapterUrl(uri) ?: return null
|
||||||
|
val request =
|
||||||
|
GET("https://mangadex.org$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()
|
||||||
|
if (body.isEmpty()) {
|
||||||
|
throw Exception("Null Response")
|
||||||
|
}
|
||||||
|
|
||||||
|
val jsonObject = JsonParser.parseString(body).obj
|
||||||
|
val mangaId = jsonObject["manga_id"]?.nullInt ?: throw Exception(
|
||||||
|
"No manga associated with chapter"
|
||||||
|
)
|
||||||
|
val langCode = getRealLangCode(jsonObject["lang_code"]?.nullString ?: "en").toUpperCase()
|
||||||
|
// 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)" }
|
||||||
|
?: return error("Source not found")
|
||||||
|
val mangaUrl = "/manga/$mangaId/"
|
||||||
|
return withContext(Dispatchers.IO) {
|
||||||
|
val deferredManga = async {
|
||||||
|
db.getManga(mangaUrl, delegate?.id!!).executeAsBlocking() ?: getMangaInfo(mangaUrl)
|
||||||
|
}
|
||||||
|
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 == url }?.toChapter() ?: error(
|
||||||
|
context.getString(R.string.chapter_not_found)
|
||||||
|
)
|
||||||
|
if (manga != null) {
|
||||||
|
Triple(trueChapter, manga, chapters.orEmpty())
|
||||||
|
} else null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getRealLangCode(langCode: String): String {
|
||||||
|
return when (langCode.toLowerCase()) {
|
||||||
|
"gb" -> "en"
|
||||||
|
"vn" -> "vi"
|
||||||
|
"mx" -> "es-419"
|
||||||
|
"br" -> "pt-BR"
|
||||||
|
"ph" -> "fil"
|
||||||
|
"sa" -> "ar"
|
||||||
|
"bd" -> "bn"
|
||||||
|
"mm" -> "my"
|
||||||
|
"cz" -> "cs"
|
||||||
|
"dk" -> "da"
|
||||||
|
"gr" -> "el"
|
||||||
|
"jp" -> "ja"
|
||||||
|
"kr" -> "ko"
|
||||||
|
"my" -> "ms"
|
||||||
|
"ir" -> "fa"
|
||||||
|
"rs" -> "sh"
|
||||||
|
"ua" -> "uk"
|
||||||
|
"cn" -> "zh-Hans" "hk" -> "zh-Hant"
|
||||||
|
else -> langCode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,102 @@
|
|||||||
|
package eu.kanade.tachiyomi.source.online.english
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.MangaImpl
|
||||||
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
|
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
|
||||||
|
|
||||||
|
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 dbManga = db.getManga(mangaUrl, sourceId).executeAsBlocking()
|
||||||
|
val deferredManga = async {
|
||||||
|
dbManga ?: 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(R.string.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 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())
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,29 @@
|
|||||||
|
package eu.kanade.tachiyomi.source.online.english
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.MangaImpl
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,70 @@
|
|||||||
|
package eu.kanade.tachiyomi.source.online.english
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
|
import eu.kanade.tachiyomi.network.GET
|
||||||
|
import eu.kanade.tachiyomi.network.await
|
||||||
|
import eu.kanade.tachiyomi.source.model.SChapter
|
||||||
|
import eu.kanade.tachiyomi.source.online.DelegatedHttpSource
|
||||||
|
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
|
||||||
|
|
||||||
|
class MangaPlus : DelegatedHttpSource() {
|
||||||
|
override val domainName: String = "jumpg-webapi.tokyo-cdn"
|
||||||
|
|
||||||
|
private val titleIdRegex =
|
||||||
|
Regex("https:\\/\\/mangaplus\\.shueisha\\.co\\.jp\\/drm\\/title\\/\\d*")
|
||||||
|
private val titleRegex = Regex("#MANGA_Plus .*\u0012")
|
||||||
|
|
||||||
|
private val chapterUrlTemplate =
|
||||||
|
"https://jumpg-webapi.tokyo-cdn.com/api/manga_viewer?chapter_id=##&split=no&img_quality=low"
|
||||||
|
|
||||||
|
override fun canOpenUrl(uri: Uri): Boolean = true
|
||||||
|
|
||||||
|
override fun chapterUrl(uri: Uri): String? = "#/viewer/${uri.pathSegments[1]}"
|
||||||
|
|
||||||
|
override fun pageNumber(uri: Uri): Int? = null
|
||||||
|
|
||||||
|
override suspend fun fetchMangaFromChapterUrl(uri: Uri): Triple<Chapter, Manga, List<SChapter>>? {
|
||||||
|
val url = chapterUrl(uri) ?: return null
|
||||||
|
val request = GET(
|
||||||
|
chapterUrlTemplate.replace("##", uri.pathSegments[1]),
|
||||||
|
delegate!!.headers,
|
||||||
|
CacheControl.FORCE_NETWORK
|
||||||
|
)
|
||||||
|
return withContext(Dispatchers.IO) {
|
||||||
|
val response = network.client.newCall(request).await()
|
||||||
|
if (response.code != 200) throw Exception("HTTP error ${response.code}")
|
||||||
|
val body = response.body!!.string()
|
||||||
|
val match = titleIdRegex.find(body)
|
||||||
|
val titleId = match?.groupValues?.firstOrNull()?.substringAfterLast("/")
|
||||||
|
?: error("Title not found")
|
||||||
|
val title = titleRegex.find(body)?.groups?.firstOrNull()?.value?.substringAfter("Plus ")
|
||||||
|
?: error("Title not found")
|
||||||
|
val trimmedTitle = title.substring(0, title.length - 1)
|
||||||
|
val mangaUrl = "#/titles/$titleId"
|
||||||
|
val deferredManga = async {
|
||||||
|
db.getManga(mangaUrl, delegate?.id!!).executeAsBlocking() ?: getMangaInfo(mangaUrl)
|
||||||
|
}
|
||||||
|
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 == url }?.toChapter() ?: error(
|
||||||
|
context.getString(R.string.chapter_not_found)
|
||||||
|
)
|
||||||
|
if (manga != null) {
|
||||||
|
Triple(trueChapter, manga.apply {
|
||||||
|
this.title = trimmedTitle
|
||||||
|
}, chapters.orEmpty())
|
||||||
|
} else null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in new issue