Remove CatManga (#9800)
This commit is contained in:
parent
4b45240f36
commit
ad0c8e2fec
|
@ -1,23 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
package="eu.kanade.tachiyomi.extension">
|
|
||||||
<application>
|
|
||||||
<activity
|
|
||||||
android:name=".en.catmanga.CatMangaUrlActivity"
|
|
||||||
android:excludeFromRecents="true"
|
|
||||||
android:exported="true"
|
|
||||||
android:theme="@android:style/Theme.NoDisplay">
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.intent.action.VIEW" />
|
|
||||||
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
|
||||||
<category android:name="android.intent.category.BROWSABLE" />
|
|
||||||
|
|
||||||
<data
|
|
||||||
android:host="catmanga.org"
|
|
||||||
android:pathPattern="/series/..*"
|
|
||||||
android:scheme="https" />
|
|
||||||
</intent-filter>
|
|
||||||
</activity>
|
|
||||||
</application>
|
|
||||||
</manifest>
|
|
|
@ -1,12 +0,0 @@
|
||||||
apply plugin: 'com.android.application'
|
|
||||||
apply plugin: 'kotlin-android'
|
|
||||||
apply plugin: 'kotlinx-serialization'
|
|
||||||
|
|
||||||
ext {
|
|
||||||
extName = 'CatManga'
|
|
||||||
pkgNameSuffix = "en.catmanga"
|
|
||||||
extClass = '.CatManga'
|
|
||||||
extVersionCode = 7
|
|
||||||
}
|
|
||||||
|
|
||||||
apply from: "$rootDir/common.gradle"
|
|
Binary file not shown.
Before Width: | Height: | Size: 1.7 KiB |
Binary file not shown.
Before Width: | Height: | Size: 1.2 KiB |
Binary file not shown.
Before Width: | Height: | Size: 2.4 KiB |
Binary file not shown.
Before Width: | Height: | Size: 3.7 KiB |
Binary file not shown.
Before Width: | Height: | Size: 5.2 KiB |
Binary file not shown.
Before Width: | Height: | Size: 18 KiB |
|
@ -1,57 +0,0 @@
|
||||||
package eu.kanade.tachiyomi.extension.en.catmanga
|
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class CatSeries(
|
|
||||||
val alt_titles: List<String>,
|
|
||||||
val authors: List<String>,
|
|
||||||
val genres: List<String>,
|
|
||||||
val chapters: List<CatSeriesChapter>? = null,
|
|
||||||
val title: String,
|
|
||||||
val series_id: String,
|
|
||||||
val description: String,
|
|
||||||
val status: String,
|
|
||||||
val cover_art: CatSeriesCover,
|
|
||||||
val all_covers: List<CatSeriesCover>? = null
|
|
||||||
) {
|
|
||||||
fun toSManga() = this.let { series ->
|
|
||||||
SManga.create().apply {
|
|
||||||
url = "/series/${series.series_id}"
|
|
||||||
title = series.title
|
|
||||||
thumbnail_url = series.cover_art.source
|
|
||||||
author = series.authors.joinToString(", ")
|
|
||||||
description = series.description
|
|
||||||
genre = series.genres.joinToString(", ")
|
|
||||||
status = when (series.status) {
|
|
||||||
"ongoing" -> SManga.ONGOING
|
|
||||||
"completed" -> SManga.COMPLETED
|
|
||||||
else -> SManga.UNKNOWN
|
|
||||||
}
|
|
||||||
|
|
||||||
if (alt_titles.isNotEmpty()) {
|
|
||||||
description += "\n\nAlternative titles:\n"
|
|
||||||
alt_titles.forEach {
|
|
||||||
description += "• $it\n"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class CatSeriesChapter(
|
|
||||||
val title: String? = null,
|
|
||||||
val groups: List<String>,
|
|
||||||
val number: Float,
|
|
||||||
val display_number: String? = null,
|
|
||||||
val volume: Int? = null
|
|
||||||
)
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class CatSeriesCover(
|
|
||||||
val source: String,
|
|
||||||
val width: Int,
|
|
||||||
val height: Int
|
|
||||||
)
|
|
|
@ -1,179 +0,0 @@
|
||||||
package eu.kanade.tachiyomi.extension.en.catmanga
|
|
||||||
|
|
||||||
import android.app.Application
|
|
||||||
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 eu.kanade.tachiyomi.util.asJsoup
|
|
||||||
import kotlinx.serialization.ExperimentalSerializationApi
|
|
||||||
import kotlinx.serialization.decodeFromString
|
|
||||||
import kotlinx.serialization.json.Json
|
|
||||||
import kotlinx.serialization.json.boolean
|
|
||||||
import kotlinx.serialization.json.decodeFromJsonElement
|
|
||||||
import kotlinx.serialization.json.jsonObject
|
|
||||||
import kotlinx.serialization.json.jsonPrimitive
|
|
||||||
import okhttp3.Request
|
|
||||||
import okhttp3.Response
|
|
||||||
import org.jsoup.nodes.Document
|
|
||||||
import rx.Observable
|
|
||||||
import uy.kohesive.injekt.injectLazy
|
|
||||||
|
|
||||||
@ExperimentalSerializationApi
|
|
||||||
class CatManga : HttpSource() {
|
|
||||||
|
|
||||||
private val application: Application by injectLazy()
|
|
||||||
|
|
||||||
override val name = "CatManga"
|
|
||||||
override val baseUrl = "https://catmanga.org"
|
|
||||||
override val supportsLatest = false
|
|
||||||
override val lang = "en"
|
|
||||||
private val json: Json by injectLazy()
|
|
||||||
|
|
||||||
private val allSeriesRequest = GET("$baseUrl/api/series/allSeries")
|
|
||||||
|
|
||||||
override fun popularMangaRequest(page: Int) = allSeriesRequest
|
|
||||||
|
|
||||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList) = allSeriesRequest
|
|
||||||
|
|
||||||
override fun chapterListRequest(manga: SManga): Request {
|
|
||||||
val seriesId = manga.url.substringAfter("/series/")
|
|
||||||
return GET("$baseUrl/api/series/$seriesId")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
|
|
||||||
return client.newCall(searchMangaRequest(page, query, filters))
|
|
||||||
.asObservableSuccess()
|
|
||||||
.map { response ->
|
|
||||||
val manga = json.decodeFromString<List<CatSeries>>(response.body!!.string())
|
|
||||||
.filter {
|
|
||||||
if (query.startsWith(SERIES_ID_SEARCH_PREFIX)) {
|
|
||||||
return@filter it.series_id.contains(query.removePrefix(SERIES_ID_SEARCH_PREFIX), true)
|
|
||||||
}
|
|
||||||
sequence { yieldAll(it.alt_titles); yield(it.title) }
|
|
||||||
.any { title -> title.contains(query, true) }
|
|
||||||
}
|
|
||||||
.map { it.toSManga() }
|
|
||||||
.toList()
|
|
||||||
MangasPage(manga, false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun fetchMangaDetails(manga: SManga): Observable<SManga> {
|
|
||||||
val seriesId = manga.url.substringAfter("/series/")
|
|
||||||
return client.newCall(allSeriesRequest)
|
|
||||||
.asObservableSuccess()
|
|
||||||
.map { response ->
|
|
||||||
json.decodeFromString<List<CatSeries>>(response.body!!.string())
|
|
||||||
.find { it.series_id == seriesId }
|
|
||||||
?.toSManga() ?: manga
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun chapterListParse(response: Response): List<SChapter> {
|
|
||||||
val series = json.decodeFromString<CatSeries>(response.body!!.string())
|
|
||||||
val seriesPrefs = application.getSharedPreferences("source_${id}_time_found:${series.series_id}", 0)
|
|
||||||
val seriesPrefsEditor = seriesPrefs.edit()
|
|
||||||
val chapters = series.chapters!!
|
|
||||||
.asReversed()
|
|
||||||
.map { chapter ->
|
|
||||||
val title = chapter.title ?: ""
|
|
||||||
val groups = chapter.groups.joinToString(", ")
|
|
||||||
val numberUrl = chapter.number.chapterNumberToUrlPath()
|
|
||||||
val displayNumber = chapter.display_number ?: numberUrl
|
|
||||||
SChapter.create().apply {
|
|
||||||
url = "/series/${series.series_id}/$numberUrl"
|
|
||||||
chapter_number = chapter.number
|
|
||||||
scanlator = groups
|
|
||||||
|
|
||||||
name = if (chapter.volume != null) {
|
|
||||||
"Vol.${chapter.volume} "
|
|
||||||
} else {
|
|
||||||
""
|
|
||||||
}
|
|
||||||
name += "Ch.$displayNumber"
|
|
||||||
if (title.isNotBlank()) {
|
|
||||||
name += " - $title"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save current time when a chapter is found for the first time, and reuse it on future
|
|
||||||
// checks to prevent manga entry without any new chapter bumped to the top of
|
|
||||||
// "Latest chapter" list when the library is updated.
|
|
||||||
val currentTimeMillis = System.currentTimeMillis()
|
|
||||||
if (!seriesPrefs.contains(numberUrl)) {
|
|
||||||
seriesPrefsEditor.putLong(numberUrl, currentTimeMillis)
|
|
||||||
}
|
|
||||||
date_upload = seriesPrefs.getLong(numberUrl, currentTimeMillis)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
seriesPrefsEditor.apply()
|
|
||||||
return chapters
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun popularMangaParse(response: Response): MangasPage {
|
|
||||||
val mangas = json.decodeFromString<List<CatSeries>>(response.body!!.string()).map { it.toSManga() }
|
|
||||||
return MangasPage(mangas, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun fetchPageList(chapter: SChapter): Observable<List<Page>> {
|
|
||||||
return client.newCall(pageListRequest(chapter))
|
|
||||||
.asObservableSuccess()
|
|
||||||
.map {
|
|
||||||
val doc = it.asJsoup().getDataJsonObject()
|
|
||||||
val pages = if (doc["isFallback"]!!.jsonPrimitive.boolean) {
|
|
||||||
val buildId = doc["buildId"]!!.jsonPrimitive.content
|
|
||||||
val directRequest = GET("$baseUrl/_next/data/$buildId/${chapter.url}.json")
|
|
||||||
val directResponse = client.newCall(directRequest).execute()
|
|
||||||
json.parseToJsonElement(directResponse.body!!.string())
|
|
||||||
} else {
|
|
||||||
doc["props"]!!
|
|
||||||
}.jsonObject["pageProps"]!!.jsonObject["pages"]!!
|
|
||||||
json.decodeFromJsonElement<List<String>>(pages)
|
|
||||||
.mapIndexed { index, s -> Page(index, "", s) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns json object of site data
|
|
||||||
*/
|
|
||||||
private fun Document.getDataJsonObject() = json.parseToJsonElement(getElementById("__NEXT_DATA__").html()).jsonObject
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns string without decimal when it is not relevant
|
|
||||||
*/
|
|
||||||
private fun Float.chapterNumberToUrlPath(): String {
|
|
||||||
return if (toInt().toFloat() == this) toInt().toString() else toString()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun pageListParse(response: Response): List<Page> {
|
|
||||||
throw UnsupportedOperationException("Not used.")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun latestUpdatesRequest(page: Int): Request {
|
|
||||||
throw UnsupportedOperationException("Not used.")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun latestUpdatesParse(response: Response): MangasPage {
|
|
||||||
throw UnsupportedOperationException("Not used.")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun mangaDetailsParse(response: Response): SManga {
|
|
||||||
throw UnsupportedOperationException("Not used.")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun searchMangaParse(response: Response): MangasPage {
|
|
||||||
throw UnsupportedOperationException("Not used.")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun imageUrlParse(response: Response): String {
|
|
||||||
throw UnsupportedOperationException("Not used.")
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val SERIES_ID_SEARCH_PREFIX = "series_id:"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,38 +0,0 @@
|
||||||
package eu.kanade.tachiyomi.extension.en.catmanga
|
|
||||||
|
|
||||||
import android.app.Activity
|
|
||||||
import android.content.ActivityNotFoundException
|
|
||||||
import android.content.Intent
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.util.Log
|
|
||||||
import kotlin.system.exitProcess
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Springboard that accepts https://catmanga.org/series/xxxxxx intents and redirects them to
|
|
||||||
* the main Tachiyomi process.
|
|
||||||
*/
|
|
||||||
class CatMangaUrlActivity : Activity() {
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
val pathSegments = intent?.data?.pathSegments
|
|
||||||
if (pathSegments != null && pathSegments.size > 1) {
|
|
||||||
val id = pathSegments[1]
|
|
||||||
val mainIntent = Intent().apply {
|
|
||||||
action = "eu.kanade.tachiyomi.SEARCH"
|
|
||||||
putExtra("query", "${CatManga.SERIES_ID_SEARCH_PREFIX}$id")
|
|
||||||
putExtra("filter", packageName)
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
startActivity(mainIntent)
|
|
||||||
} catch (e: ActivityNotFoundException) {
|
|
||||||
Log.e("CatMangaUrlActivity", e.toString())
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Log.e("CatMangaUrlActivity", "could not parse uri from intent $intent")
|
|
||||||
}
|
|
||||||
|
|
||||||
finish()
|
|
||||||
exitProcess(0)
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue