From 2072ede6d49bd806cee7f363052b8f8a4a22c9e5 Mon Sep 17 00:00:00 2001 From: Kleidukos Date: Sat, 18 Jan 2025 21:16:15 +0100 Subject: [PATCH 1/4] Rewrite MangaTube extension to utilize the backend API This commit includes a complete rewrite of the MangaTube extension, improving functionality and compatibility by leveraging the backend API and adopting models for a cleaner architecture. --- src/de/mangatube/build.gradle | 2 +- .../extension/de/mangatube/MangaTube.kt | 286 +++++++++++------- .../extension/de/mangatube/dio/Chapter.kt | 14 + .../extension/de/mangatube/dio/Manga.kt | 16 + .../extension/de/mangatube/dio/Page.kt | 11 + .../extension/de/mangatube/dio/Pagination.kt | 15 + .../extension/de/mangatube/dio/Person.kt | 8 + .../mangatube/dio/wrapper/ChapterWrapper.kt | 9 + .../mangatube/dio/wrapper/ChaptersWrapper.kt | 9 + .../de/mangatube/dio/wrapper/MangaWrapper.kt | 7 + .../de/mangatube/dio/wrapper/MangasWrapper.kt | 9 + .../de/mangatube/util/BaseResponse.kt | 11 + .../extension/de/mangatube/util/Genre.kt | 61 ++++ .../de/mangatube/util/MangaTubeHelper.kt | 34 +++ 14 files changed, 385 insertions(+), 107 deletions(-) create mode 100644 src/de/mangatube/src/eu/kanade/tachiyomi/extension/de/mangatube/dio/Chapter.kt create mode 100644 src/de/mangatube/src/eu/kanade/tachiyomi/extension/de/mangatube/dio/Manga.kt create mode 100644 src/de/mangatube/src/eu/kanade/tachiyomi/extension/de/mangatube/dio/Page.kt create mode 100644 src/de/mangatube/src/eu/kanade/tachiyomi/extension/de/mangatube/dio/Pagination.kt create mode 100644 src/de/mangatube/src/eu/kanade/tachiyomi/extension/de/mangatube/dio/Person.kt create mode 100644 src/de/mangatube/src/eu/kanade/tachiyomi/extension/de/mangatube/dio/wrapper/ChapterWrapper.kt create mode 100644 src/de/mangatube/src/eu/kanade/tachiyomi/extension/de/mangatube/dio/wrapper/ChaptersWrapper.kt create mode 100644 src/de/mangatube/src/eu/kanade/tachiyomi/extension/de/mangatube/dio/wrapper/MangaWrapper.kt create mode 100644 src/de/mangatube/src/eu/kanade/tachiyomi/extension/de/mangatube/dio/wrapper/MangasWrapper.kt create mode 100644 src/de/mangatube/src/eu/kanade/tachiyomi/extension/de/mangatube/util/BaseResponse.kt create mode 100644 src/de/mangatube/src/eu/kanade/tachiyomi/extension/de/mangatube/util/Genre.kt create mode 100644 src/de/mangatube/src/eu/kanade/tachiyomi/extension/de/mangatube/util/MangaTubeHelper.kt diff --git a/src/de/mangatube/build.gradle b/src/de/mangatube/build.gradle index 62fb3ed966..26474ad5b4 100644 --- a/src/de/mangatube/build.gradle +++ b/src/de/mangatube/build.gradle @@ -1,7 +1,7 @@ ext { extName = 'Manga Tube' extClass = '.MangaTube' - extVersionCode = 2 + extVersionCode = 3 } apply from: "$rootDir/common.gradle" diff --git a/src/de/mangatube/src/eu/kanade/tachiyomi/extension/de/mangatube/MangaTube.kt b/src/de/mangatube/src/eu/kanade/tachiyomi/extension/de/mangatube/MangaTube.kt index a79fc2f35a..d8179e0397 100644 --- a/src/de/mangatube/src/eu/kanade/tachiyomi/extension/de/mangatube/MangaTube.kt +++ b/src/de/mangatube/src/eu/kanade/tachiyomi/extension/de/mangatube/MangaTube.kt @@ -1,36 +1,31 @@ package eu.kanade.tachiyomi.extension.de.mangatube +import Manga +import android.annotation.SuppressLint +import android.util.Log +import eu.kanade.tachiyomi.extension.de.mangatube.dio.wrapper.ChapterWrapper +import eu.kanade.tachiyomi.extension.de.mangatube.dio.wrapper.ChaptersWrapper +import eu.kanade.tachiyomi.extension.de.mangatube.dio.wrapper.MangaWrapper +import eu.kanade.tachiyomi.extension.de.mangatube.dio.wrapper.MangasWrapper +import eu.kanade.tachiyomi.extension.de.mangatube.util.BaseResponse +import eu.kanade.tachiyomi.extension.de.mangatube.util.Genre +import eu.kanade.tachiyomi.extension.de.mangatube.util.MangaTubeHelper import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.POST -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.ParsedHttpSource +import eu.kanade.tachiyomi.source.online.HttpSource import kotlinx.serialization.decodeFromString import kotlinx.serialization.json.Json -import kotlinx.serialization.json.JsonArray -import kotlinx.serialization.json.JsonObject -import kotlinx.serialization.json.jsonArray -import kotlinx.serialization.json.jsonObject -import kotlinx.serialization.json.jsonPrimitive -import okhttp3.Headers import okhttp3.OkHttpClient import okhttp3.Request -import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import rx.Observable -import uy.kohesive.injekt.injectLazy -import java.text.ParseException import java.text.SimpleDateFormat -import java.util.Locale import java.util.concurrent.TimeUnit -class MangaTube : ParsedHttpSource() { +class MangaTube : HttpSource() { override val name = "Manga Tube" @@ -40,146 +35,225 @@ class MangaTube : ParsedHttpSource() { override val supportsLatest = true + private val mangas: LinkedHashMap = LinkedHashMap() + + private val json = Json { + isLenient = true + ignoreUnknownKeys = true + allowSpecialFloatingPointValues = true + prettyPrint = true + } + override val client: OkHttpClient = network.cloudflareClient.newBuilder() .connectTimeout(1, TimeUnit.MINUTES) .readTimeout(1, TimeUnit.MINUTES) .writeTimeout(1, TimeUnit.MINUTES) .build() - private val xhrHeaders: Headers = headersBuilder().add("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8").build() + override fun imageUrlParse(response: Response): String = "" + + override fun getMangaUrl(manga: SManga): String { + if (!manga.url.startsWith(baseUrl)) { + return "$baseUrl${manga.url}" + } + return manga.url + } + + override fun getChapterUrl(chapter: SChapter): String { + return chapter.url + } + + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { + val url = "$baseUrl/api/manga/search?page=$page&query=$query" + + Log.d("MangaTube", "Search for: $query") + Log.d("MangaTube", "Url -> $url") - private val json: Json by injectLazy() + return GET(url) + } + + override fun searchMangaParse(response: Response): MangasPage { + val body = MangaTubeHelper.checkResponse(response) - // Popular + val res: BaseResponse> = json.decodeFromString(body) - override fun fetchPopularManga(page: Int): Observable { - return client.newCall(popularMangaRequest(page)) - .asObservableSuccess() - .map { response -> - parseMangaFromJson(response, page < 96) + if(!res.success){ + throw Exception("Something went wrong!") + } + + val mangaList = res.data.map { manga -> + mangas[manga.title] = manga + SManga.create().apply { + title = manga.title + url = "$baseUrl${manga.url}" + thumbnail_url = manga.cover + status = MangaTubeHelper.mangaStatus(manga.status) } + } + + return MangasPage(mangaList, res.pagination!!.lastPage()) } override fun popularMangaRequest(page: Int): Request { - val rbodyContent = "action=load_series_list_entries¶meter%5Bpage%5D=$page¶meter%5Bletter%5D=¶meter%5Bsortby%5D=popularity¶meter%5Border%5D=asc" - return POST("$baseUrl/ajax", xhrHeaders, rbodyContent.toRequestBody(null)) - } + val url = "$baseUrl/api/home/top-manga" - // popular uses "success" as a key, search uses "suggestions" - // for future reference: if adding filters, advanced search might use a different key - private fun parseMangaFromJson(response: Response, hasNextPage: Boolean): MangasPage { - var titleKey = "manga_title" - val mangas = json.decodeFromString(response.body.string()) - .let { it["success"] ?: it["suggestions"].also { titleKey = "value" } }!! - .jsonArray - .map { json -> - SManga.create().apply { - title = json.jsonObject[titleKey]!!.jsonPrimitive.content - url = "/series/${json.jsonObject["manga_slug"]!!.jsonPrimitive.content}" - thumbnail_url = json.jsonObject["covers"]!!.jsonArray[0].jsonObject["img_name"]!!.jsonPrimitive.content - } - } - return MangasPage(mangas, hasNextPage) + Log.d("MangaTube", "Request popular mangas") + Log.d("MangaTube", "Url -> $url") + + return GET(url) } - override fun popularMangaSelector() = throw UnsupportedOperationException() + override fun popularMangaParse(response: Response): MangasPage { + val body = MangaTubeHelper.checkResponse(response) - override fun popularMangaFromElement(element: Element): SManga = throw UnsupportedOperationException() + val res: BaseResponse = json.decodeFromString(body) - override fun popularMangaNextPageSelector() = throw UnsupportedOperationException() + if(!res.success){ + throw Exception("Something went wrong!") + } + + + val mangaList = res.data.manga.map { manga -> + mangas[manga.title] = manga + SManga.create().apply { + title = manga.title + url = "$baseUrl${manga.url}" + thumbnail_url = manga.cover + genre = manga.genre.map { genre -> Genre.fromId(genre)!! } + .joinToString(", ") { genre -> genre.displayName } + status = MangaTubeHelper.mangaStatus(manga.status) + } + } - // Latest + return MangasPage(mangaList, false) + } override fun latestUpdatesRequest(page: Int): Request { - return GET("$baseUrl/?page=$page", headers) + val url = "$baseUrl/api/home/new-manga" + + Log.d("MangaTube", "Request new mangas") + Log.d("MangaTube", "Url -> $url") + + return GET(url) } - override fun latestUpdatesSelector() = "div#series-updates div.series-update:not([style\$=none])" + override fun latestUpdatesParse(response: Response): MangasPage { + val body = MangaTubeHelper.checkResponse(response) - override fun latestUpdatesFromElement(element: Element): SManga { - return SManga.create().apply { - element.select("a.series-name").let { - title = it.text() - setUrlWithoutDomain(it.attr("href")) + val res: BaseResponse = json.decodeFromString(body) + + if(!res.success){ + throw Exception("Something went wrong!") + } + + + val mangaList = res.data.manga.map { manga -> + mangas[manga.title] = manga + SManga.create().apply { + title = manga.title + url = "$baseUrl${manga.url}" + thumbnail_url = manga.cover + genre = manga.genre.map { genre -> Genre.fromId(genre)!! } + .joinToString(", ") { genre -> genre.displayName } + status = MangaTubeHelper.mangaStatus(manga.status) + description = manga.description } - thumbnail_url = element.select("div.cover img").attr("abs:data-original") } + + return MangasPage(mangaList, false) } - override fun latestUpdatesNextPageSelector() = "button#load-more-updates" + override fun mangaDetailsRequest(manga: SManga): Request { + val url = "$baseUrl/api/manga/${mangas[manga.title]!!.id}" - // Search + Log.d("MangaTube", "Request manga details for: ${manga.title}") + Log.d("MangaTube", "Url -> $url") - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val rbodyContent = "action=search_query¶meter%5Bquery%5D=$query" - return POST("$baseUrl/ajax", xhrHeaders, rbodyContent.toRequestBody(null)) + return GET(url) } - override fun searchMangaParse(response: Response): MangasPage { - return parseMangaFromJson(response, false) - } + override fun mangaDetailsParse(response: Response): SManga { + val body = MangaTubeHelper.checkResponse(response) - override fun searchMangaSelector() = throw UnsupportedOperationException() + val res: BaseResponse = json.decodeFromString(body) - override fun searchMangaFromElement(element: Element): SManga = throw UnsupportedOperationException() + if(!res.success){ + throw Exception("Something went wrong!") + } - override fun searchMangaNextPageSelector() = throw UnsupportedOperationException() + val manga: Manga = res.data.manga - // Details + mangas[manga.title] = manga - override fun mangaDetailsParse(document: Document): SManga { return SManga.create().apply { - document.select("div.series-detailed div.row").first()!!.let { info -> - author = info.select("li:contains(Autor:) a").joinToString { it.text() } - artist = info.select("li:contains(Artist:) a").joinToString { it.text() } - status = info.select("li:contains(Offiziel)").firstOrNull()?.ownText().toStatus() - genre = info.select(".genre-list a").joinToString { it.text() } - thumbnail_url = info.select("img").attr("abs:data-original") - } - description = document.select("div.series-footer h4 ~ p").joinToString("\n\n") { it.text() } + title = manga.title + author = manga.author.joinToString(", ") { author -> author.name } + url = "$baseUrl${manga.url}" + artist = manga.artist.joinToString(", ") { artist -> artist.name } + description = manga.description + thumbnail_url = manga.cover + genre = manga.genre.map { genre -> Genre.fromId(genre)!! } + .joinToString(", ") { genre -> genre.displayName } + status = MangaTubeHelper.mangaStatus(manga.status) } } - private fun String?.toStatus() = when { - this == null -> SManga.UNKNOWN - this.contains("laufend", ignoreCase = true) -> SManga.ONGOING - this.contains("abgeschlossen", ignoreCase = true) -> SManga.COMPLETED - else -> SManga.UNKNOWN + override fun chapterListRequest(manga: SManga): Request { + val url = "$baseUrl/api/manga/${mangas[manga.title]!!.slug}/chapters" + + Log.d("MangaTube", "Request chapters for manga: ${manga.title}") + Log.d("MangaTube", "Url -> $url") + + return GET(url) } - // Chapters + @SuppressLint("SimpleDateFormat") + override fun chapterListParse(response: Response): List { + val body = MangaTubeHelper.checkResponse(response) - override fun chapterListSelector() = "ul.chapter-list li" + val res: BaseResponse = json.decodeFromString(body) - override fun chapterFromElement(element: Element): SChapter { - return SChapter.create().apply { - element.select("a[title]").let { - name = "${it.select("b").text()} ${it.select("span:not(.btn)").joinToString(" ") { span -> span.text() }}" - setUrlWithoutDomain(it.attr("href")) - } - date_upload = element.select("p.chapter-date").text().let { - try { - SimpleDateFormat("dd.MM.yyyy", Locale.getDefault()).parse(it.substringAfter(" "))?.time ?: 0L - } catch (_: ParseException) { - 0L - } + if(!res.success){ + throw Exception("Something went wrong!") + } + + val chapterList = res.data.chapters.map { chapter -> + SChapter.create().apply { + url = "$baseUrl${chapter.readerURL}" + name = chapter.name.ifBlank { "Chapter ${chapter.number}" } + date_upload = + SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(chapter.publishedAt)!!.time + chapter_number = chapter.number.toFloat() + scanlator = chapter.volume.toString() } } + + return chapterList } - // Pages + override fun pageListRequest(chapter: SChapter): Request { + val split = chapter.url.split("/") + val slug = split[split.size - 4] + val id = split[split.size - 2] - override fun pageListParse(document: Document): List { - val script = document.select("script:containsData(current_chapter:)").first()!!.data() - val imagePath = Regex("""img_path: '(.*)'""").find(script)?.groupValues?.get(1) - ?: throw Exception("Couldn't find image path") - val jsonArray = Regex("""pages: (\[.*]),""").find(script)?.groupValues?.get(1) - ?: throw Exception("Couldn't find JSON array") + val url = "$baseUrl/api/manga/$slug/chapter/$id" - return json.decodeFromString(jsonArray).mapIndexed { i, json -> - Page(i, "", imagePath + json.jsonObject["file_name"]!!.jsonPrimitive.content) - } + return GET(url) } - override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException() + override fun pageListParse(response: Response): List { + val body = MangaTubeHelper.checkResponse(response) + + val res: BaseResponse = json.decodeFromString(body) + + if(!res.success){ + throw Exception("Something went wrong!") + } + + val mangaList = res.data.chapter.pages.map { page -> + Page(page.index, page.url, page.altSource) + } + + return mangaList + } } diff --git a/src/de/mangatube/src/eu/kanade/tachiyomi/extension/de/mangatube/dio/Chapter.kt b/src/de/mangatube/src/eu/kanade/tachiyomi/extension/de/mangatube/dio/Chapter.kt new file mode 100644 index 0000000000..08fba18466 --- /dev/null +++ b/src/de/mangatube/src/eu/kanade/tachiyomi/extension/de/mangatube/dio/Chapter.kt @@ -0,0 +1,14 @@ +package eu.kanade.tachiyomi.extension.de.mangatube.dio + +import kotlinx.serialization.Serializable + +@Serializable +data class Chapter( + val id: Int, + val number: Int, + val volume: Int, + val name: String, + val publishedAt: String, + val readerURL: String, + val pages: List = emptyList(), +) diff --git a/src/de/mangatube/src/eu/kanade/tachiyomi/extension/de/mangatube/dio/Manga.kt b/src/de/mangatube/src/eu/kanade/tachiyomi/extension/de/mangatube/dio/Manga.kt new file mode 100644 index 0000000000..bf0857c26f --- /dev/null +++ b/src/de/mangatube/src/eu/kanade/tachiyomi/extension/de/mangatube/dio/Manga.kt @@ -0,0 +1,16 @@ +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.JsonNames + +@Serializable +data class Manga( + val id: Int, + val title: String, + val url: String, + val slug: String, + val cover: String, + @JsonNames("status", "statusScanlation")val status: Int = -1, + val description: String = "", + val genre: List = emptyList(), + val artist: List = emptyList(), + val author: List = emptyList(), +) diff --git a/src/de/mangatube/src/eu/kanade/tachiyomi/extension/de/mangatube/dio/Page.kt b/src/de/mangatube/src/eu/kanade/tachiyomi/extension/de/mangatube/dio/Page.kt new file mode 100644 index 0000000000..fd7bb499b7 --- /dev/null +++ b/src/de/mangatube/src/eu/kanade/tachiyomi/extension/de/mangatube/dio/Page.kt @@ -0,0 +1,11 @@ +package eu.kanade.tachiyomi.extension.de.mangatube.dio + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class Page( + val url: String, + @SerialName("page") val index: Int, + @SerialName("alt_source") val altSource: String, +) diff --git a/src/de/mangatube/src/eu/kanade/tachiyomi/extension/de/mangatube/dio/Pagination.kt b/src/de/mangatube/src/eu/kanade/tachiyomi/extension/de/mangatube/dio/Pagination.kt new file mode 100644 index 0000000000..1a01dd9b33 --- /dev/null +++ b/src/de/mangatube/src/eu/kanade/tachiyomi/extension/de/mangatube/dio/Pagination.kt @@ -0,0 +1,15 @@ +package eu.kanade.tachiyomi.extension.de.mangatube.dio + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class Pagination( + @SerialName("current_page") val currentPage: Int, + @SerialName("last_page") val lastPage: Int, +) { + + fun lastPage(): Boolean { + return currentPage == lastPage + } +} diff --git a/src/de/mangatube/src/eu/kanade/tachiyomi/extension/de/mangatube/dio/Person.kt b/src/de/mangatube/src/eu/kanade/tachiyomi/extension/de/mangatube/dio/Person.kt new file mode 100644 index 0000000000..f9cfe5a0ca --- /dev/null +++ b/src/de/mangatube/src/eu/kanade/tachiyomi/extension/de/mangatube/dio/Person.kt @@ -0,0 +1,8 @@ +import kotlinx.serialization.Serializable + +@Serializable +data class Person( + val id: Int, + val name: String, + val link: String, +) diff --git a/src/de/mangatube/src/eu/kanade/tachiyomi/extension/de/mangatube/dio/wrapper/ChapterWrapper.kt b/src/de/mangatube/src/eu/kanade/tachiyomi/extension/de/mangatube/dio/wrapper/ChapterWrapper.kt new file mode 100644 index 0000000000..3adbb64527 --- /dev/null +++ b/src/de/mangatube/src/eu/kanade/tachiyomi/extension/de/mangatube/dio/wrapper/ChapterWrapper.kt @@ -0,0 +1,9 @@ +package eu.kanade.tachiyomi.extension.de.mangatube.dio.wrapper + +import eu.kanade.tachiyomi.extension.de.mangatube.dio.Chapter +import kotlinx.serialization.Serializable + +@Serializable +data class ChapterWrapper( + val chapter: Chapter, +) diff --git a/src/de/mangatube/src/eu/kanade/tachiyomi/extension/de/mangatube/dio/wrapper/ChaptersWrapper.kt b/src/de/mangatube/src/eu/kanade/tachiyomi/extension/de/mangatube/dio/wrapper/ChaptersWrapper.kt new file mode 100644 index 0000000000..491a0221e0 --- /dev/null +++ b/src/de/mangatube/src/eu/kanade/tachiyomi/extension/de/mangatube/dio/wrapper/ChaptersWrapper.kt @@ -0,0 +1,9 @@ +package eu.kanade.tachiyomi.extension.de.mangatube.dio.wrapper + +import eu.kanade.tachiyomi.extension.de.mangatube.dio.Chapter +import kotlinx.serialization.Serializable + +@Serializable +data class ChaptersWrapper( + val chapters: List, +) diff --git a/src/de/mangatube/src/eu/kanade/tachiyomi/extension/de/mangatube/dio/wrapper/MangaWrapper.kt b/src/de/mangatube/src/eu/kanade/tachiyomi/extension/de/mangatube/dio/wrapper/MangaWrapper.kt new file mode 100644 index 0000000000..b18f491ef3 --- /dev/null +++ b/src/de/mangatube/src/eu/kanade/tachiyomi/extension/de/mangatube/dio/wrapper/MangaWrapper.kt @@ -0,0 +1,7 @@ +package eu.kanade.tachiyomi.extension.de.mangatube.dio.wrapper + +import Manga +import kotlinx.serialization.Serializable + +@Serializable +data class MangaWrapper(val manga: Manga) diff --git a/src/de/mangatube/src/eu/kanade/tachiyomi/extension/de/mangatube/dio/wrapper/MangasWrapper.kt b/src/de/mangatube/src/eu/kanade/tachiyomi/extension/de/mangatube/dio/wrapper/MangasWrapper.kt new file mode 100644 index 0000000000..893cd66576 --- /dev/null +++ b/src/de/mangatube/src/eu/kanade/tachiyomi/extension/de/mangatube/dio/wrapper/MangasWrapper.kt @@ -0,0 +1,9 @@ +package eu.kanade.tachiyomi.extension.de.mangatube.dio.wrapper + +import Manga +import kotlinx.serialization.Serializable + +@Serializable +data class MangasWrapper( + val manga: List, +) diff --git a/src/de/mangatube/src/eu/kanade/tachiyomi/extension/de/mangatube/util/BaseResponse.kt b/src/de/mangatube/src/eu/kanade/tachiyomi/extension/de/mangatube/util/BaseResponse.kt new file mode 100644 index 0000000000..108741644f --- /dev/null +++ b/src/de/mangatube/src/eu/kanade/tachiyomi/extension/de/mangatube/util/BaseResponse.kt @@ -0,0 +1,11 @@ +package eu.kanade.tachiyomi.extension.de.mangatube.util + +import eu.kanade.tachiyomi.extension.de.mangatube.dio.Pagination +import kotlinx.serialization.Serializable + +@Serializable +open class BaseResponse( + val success: Boolean, + val data: T, + val pagination: Pagination? = null, +) diff --git a/src/de/mangatube/src/eu/kanade/tachiyomi/extension/de/mangatube/util/Genre.kt b/src/de/mangatube/src/eu/kanade/tachiyomi/extension/de/mangatube/util/Genre.kt new file mode 100644 index 0000000000..b84f329304 --- /dev/null +++ b/src/de/mangatube/src/eu/kanade/tachiyomi/extension/de/mangatube/util/Genre.kt @@ -0,0 +1,61 @@ +package eu.kanade.tachiyomi.extension.de.mangatube.util + +enum class Genre(val id: Int, val displayName: String) { + KOMA_4(50, "4-Koma"), + ABENTEUER(1, "Abenteuer"), + ACTION(2, "Action"), + ALLTAGSLEBEN(12, "Alltagsleben"), + AUTOS(28, "Autos"), + BOYS_LOVE(46, "Boys Love"), + DAEMONEN(29, "Dämonen"), + DELINQUENT(59, "Delinquent"), + DOUJINSHI(30, "Doujinshi"), + DRAMA(3, "Drama"), + ECCHI(7, "Ecchi"), + EROTIK(44, "Erotik"), + FANTASY(13, "Fantasy"), + GEISTER(55, "Geister"), + GENDER_BENDER(32, "Gender Bender"), + GIRLS_LOVE(47, "Girls Love"), + HAREM(23, "Harem"), + HENTAI(33, "Hentai"), + HISTORISCH(34, "Historisch"), + HORROR(35, "Horror"), + ISEKAI(53, "Isekai"), + JOSEI(36, "Josei"), + KAMPFSPORT(25, "Kampfsport"), + KARTENSPIEL(26, "Kartenspiel"), + KINDER(37, "Kinder"), + KOCHEN(51, "Kochen"), + KOMOEDIE(5, "Komödie"), + KRIMI(56, "Krimi"), + MAGICAL_GIRL(54, "Magical Girl"), + MAGIE(24, "Magie"), + MECHA(11, "Mecha"), + MILITAER(38, "Militär"), + MONSTER(49, "Monster"), + MUSIK(39, "Musik"), + MYSTERY(43, "Mystery"), + ONESHOTS(58, "OneShots"), + PSYCHODRAMA(52, "Psychodrama"), + ROMANZE(6, "Romanze"), + SCHULE(27, "Schule"), + SCI_FI(10, "Sci-Fi"), + SEINEN(48, "Seinen"), + SHOUJO(42, "Shoujo"), + SHOUNEN(4, "Shounen"), + SPLATTER(57, "Splatter"), + SPORT(14, "Sport"), + SUPERKRAEFTE(16, "Superkräfte"), + THRILLER(40, "Thriller"), + VAMPIRE(15, "Vampire"), + VIDEOSPIEL(9, "Videospiel"), + WEIBLICHE_PROTAGONISTIN(60, "weibliche Protagonistin"), + YAOI(41, "Yaoi"), + YURI(45, "Yuri"), + ; + + companion object { + fun fromId(id: Int): Genre? = values().find { it.id == id } + } +} diff --git a/src/de/mangatube/src/eu/kanade/tachiyomi/extension/de/mangatube/util/MangaTubeHelper.kt b/src/de/mangatube/src/eu/kanade/tachiyomi/extension/de/mangatube/util/MangaTubeHelper.kt new file mode 100644 index 0000000000..2c0dbcaa4d --- /dev/null +++ b/src/de/mangatube/src/eu/kanade/tachiyomi/extension/de/mangatube/util/MangaTubeHelper.kt @@ -0,0 +1,34 @@ +package eu.kanade.tachiyomi.extension.de.mangatube.util + +import eu.kanade.tachiyomi.source.model.SManga +import okhttp3.Response + +class MangaTubeHelper { + + companion object { + fun mangaStatus(status: Int): Int { + return when (status) { + 0 -> SManga.ONGOING + 1 -> SManga.ON_HIATUS + 2 -> SManga.LICENSED + 3 -> SManga.CANCELLED + 4 -> SManga.COMPLETED + else -> SManga.UNKNOWN + } + } + + fun checkResponse(response: Response): String { + val body = response.body.string() + + if (response.code != 200) { + throw Exception("Unexpected network issue") + } + + if (body.startsWith("")) { + throw Exception("IP isn't verified. Open webview!") + } + + return body + } + } +} From e4578ec61acba069cd40f9af1ae5ffb4e5f93f74 Mon Sep 17 00:00:00 2001 From: Kleidukos Date: Mon, 20 Jan 2025 19:14:35 +0100 Subject: [PATCH 2/4] Remove logging --- .../extension/de/mangatube/MangaTube.kt | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/src/de/mangatube/src/eu/kanade/tachiyomi/extension/de/mangatube/MangaTube.kt b/src/de/mangatube/src/eu/kanade/tachiyomi/extension/de/mangatube/MangaTube.kt index d8179e0397..426f054b0a 100644 --- a/src/de/mangatube/src/eu/kanade/tachiyomi/extension/de/mangatube/MangaTube.kt +++ b/src/de/mangatube/src/eu/kanade/tachiyomi/extension/de/mangatube/MangaTube.kt @@ -2,7 +2,6 @@ package eu.kanade.tachiyomi.extension.de.mangatube import Manga import android.annotation.SuppressLint -import android.util.Log import eu.kanade.tachiyomi.extension.de.mangatube.dio.wrapper.ChapterWrapper import eu.kanade.tachiyomi.extension.de.mangatube.dio.wrapper.ChaptersWrapper import eu.kanade.tachiyomi.extension.de.mangatube.dio.wrapper.MangaWrapper @@ -66,9 +65,6 @@ class MangaTube : HttpSource() { override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { val url = "$baseUrl/api/manga/search?page=$page&query=$query" - Log.d("MangaTube", "Search for: $query") - Log.d("MangaTube", "Url -> $url") - return GET(url) } @@ -97,9 +93,6 @@ class MangaTube : HttpSource() { override fun popularMangaRequest(page: Int): Request { val url = "$baseUrl/api/home/top-manga" - Log.d("MangaTube", "Request popular mangas") - Log.d("MangaTube", "Url -> $url") - return GET(url) } @@ -131,9 +124,6 @@ class MangaTube : HttpSource() { override fun latestUpdatesRequest(page: Int): Request { val url = "$baseUrl/api/home/new-manga" - Log.d("MangaTube", "Request new mangas") - Log.d("MangaTube", "Url -> $url") - return GET(url) } @@ -166,9 +156,6 @@ class MangaTube : HttpSource() { override fun mangaDetailsRequest(manga: SManga): Request { val url = "$baseUrl/api/manga/${mangas[manga.title]!!.id}" - Log.d("MangaTube", "Request manga details for: ${manga.title}") - Log.d("MangaTube", "Url -> $url") - return GET(url) } @@ -201,9 +188,6 @@ class MangaTube : HttpSource() { override fun chapterListRequest(manga: SManga): Request { val url = "$baseUrl/api/manga/${mangas[manga.title]!!.slug}/chapters" - Log.d("MangaTube", "Request chapters for manga: ${manga.title}") - Log.d("MangaTube", "Url -> $url") - return GET(url) } From fa66069768747c377106d6d20dbdf5c97de6c280 Mon Sep 17 00:00:00 2001 From: Kleidukos Date: Mon, 20 Jan 2025 19:18:17 +0100 Subject: [PATCH 3/4] Initialize SimpleDateFormat in class --- .../extension/de/mangatube/MangaTube.kt | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/de/mangatube/src/eu/kanade/tachiyomi/extension/de/mangatube/MangaTube.kt b/src/de/mangatube/src/eu/kanade/tachiyomi/extension/de/mangatube/MangaTube.kt index 426f054b0a..29fd6efa57 100644 --- a/src/de/mangatube/src/eu/kanade/tachiyomi/extension/de/mangatube/MangaTube.kt +++ b/src/de/mangatube/src/eu/kanade/tachiyomi/extension/de/mangatube/MangaTube.kt @@ -22,6 +22,7 @@ import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.Response import java.text.SimpleDateFormat +import java.util.Locale import java.util.concurrent.TimeUnit class MangaTube : HttpSource() { @@ -34,6 +35,9 @@ class MangaTube : HttpSource() { override val supportsLatest = true + @SuppressLint("SimpleDateFormat") + private val dateFormat: SimpleDateFormat = SimpleDateFormat("yyyy-MM-dd HH:mm:ss") + private val mangas: LinkedHashMap = LinkedHashMap() private val json = Json { @@ -73,7 +77,7 @@ class MangaTube : HttpSource() { val res: BaseResponse> = json.decodeFromString(body) - if(!res.success){ + if (!res.success) { throw Exception("Something went wrong!") } @@ -101,7 +105,7 @@ class MangaTube : HttpSource() { val res: BaseResponse = json.decodeFromString(body) - if(!res.success){ + if (!res.success) { throw Exception("Something went wrong!") } @@ -132,7 +136,7 @@ class MangaTube : HttpSource() { val res: BaseResponse = json.decodeFromString(body) - if(!res.success){ + if (!res.success) { throw Exception("Something went wrong!") } @@ -164,7 +168,7 @@ class MangaTube : HttpSource() { val res: BaseResponse = json.decodeFromString(body) - if(!res.success){ + if (!res.success) { throw Exception("Something went wrong!") } @@ -191,13 +195,12 @@ class MangaTube : HttpSource() { return GET(url) } - @SuppressLint("SimpleDateFormat") override fun chapterListParse(response: Response): List { val body = MangaTubeHelper.checkResponse(response) val res: BaseResponse = json.decodeFromString(body) - if(!res.success){ + if (!res.success) { throw Exception("Something went wrong!") } @@ -205,8 +208,7 @@ class MangaTube : HttpSource() { SChapter.create().apply { url = "$baseUrl${chapter.readerURL}" name = chapter.name.ifBlank { "Chapter ${chapter.number}" } - date_upload = - SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(chapter.publishedAt)!!.time + date_upload = dateFormat.parse(chapter.publishedAt)!!.time chapter_number = chapter.number.toFloat() scanlator = chapter.volume.toString() } @@ -230,7 +232,7 @@ class MangaTube : HttpSource() { val res: BaseResponse = json.decodeFromString(body) - if(!res.success){ + if (!res.success) { throw Exception("Something went wrong!") } From 5b19920b9c711e89951c50afae4074f5cdf43ff4 Mon Sep 17 00:00:00 2001 From: Kleidukos Date: Mon, 20 Jan 2025 19:47:47 +0100 Subject: [PATCH 4/4] Use interceptor to check if response is valid --- .../extension/de/mangatube/MangaTube.kt | 41 ++++--------------- .../de/mangatube/util/MangaTubeHelper.kt | 14 ------- .../de/mangatube/util/ResponseInterceptor.kt | 37 +++++++++++++++++ 3 files changed, 45 insertions(+), 47 deletions(-) create mode 100644 src/de/mangatube/src/eu/kanade/tachiyomi/extension/de/mangatube/util/ResponseInterceptor.kt diff --git a/src/de/mangatube/src/eu/kanade/tachiyomi/extension/de/mangatube/MangaTube.kt b/src/de/mangatube/src/eu/kanade/tachiyomi/extension/de/mangatube/MangaTube.kt index 29fd6efa57..4a55ba6a03 100644 --- a/src/de/mangatube/src/eu/kanade/tachiyomi/extension/de/mangatube/MangaTube.kt +++ b/src/de/mangatube/src/eu/kanade/tachiyomi/extension/de/mangatube/MangaTube.kt @@ -9,6 +9,7 @@ import eu.kanade.tachiyomi.extension.de.mangatube.dio.wrapper.MangasWrapper import eu.kanade.tachiyomi.extension.de.mangatube.util.BaseResponse import eu.kanade.tachiyomi.extension.de.mangatube.util.Genre import eu.kanade.tachiyomi.extension.de.mangatube.util.MangaTubeHelper +import eu.kanade.tachiyomi.extension.de.mangatube.util.ResponseInterceptor import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.source.model.FilterList import eu.kanade.tachiyomi.source.model.MangasPage @@ -22,7 +23,6 @@ import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.Response import java.text.SimpleDateFormat -import java.util.Locale import java.util.concurrent.TimeUnit class MangaTube : HttpSource() { @@ -51,6 +51,7 @@ class MangaTube : HttpSource() { .connectTimeout(1, TimeUnit.MINUTES) .readTimeout(1, TimeUnit.MINUTES) .writeTimeout(1, TimeUnit.MINUTES) + .addInterceptor(ResponseInterceptor()) .build() override fun imageUrlParse(response: Response): String = "" @@ -73,14 +74,10 @@ class MangaTube : HttpSource() { } override fun searchMangaParse(response: Response): MangasPage { - val body = MangaTubeHelper.checkResponse(response) + val body = response.body.string() val res: BaseResponse> = json.decodeFromString(body) - if (!res.success) { - throw Exception("Something went wrong!") - } - val mangaList = res.data.map { manga -> mangas[manga.title] = manga SManga.create().apply { @@ -101,15 +98,10 @@ class MangaTube : HttpSource() { } override fun popularMangaParse(response: Response): MangasPage { - val body = MangaTubeHelper.checkResponse(response) + val body = response.body.string() val res: BaseResponse = json.decodeFromString(body) - if (!res.success) { - throw Exception("Something went wrong!") - } - - val mangaList = res.data.manga.map { manga -> mangas[manga.title] = manga SManga.create().apply { @@ -132,15 +124,10 @@ class MangaTube : HttpSource() { } override fun latestUpdatesParse(response: Response): MangasPage { - val body = MangaTubeHelper.checkResponse(response) + val body = response.body.string() val res: BaseResponse = json.decodeFromString(body) - if (!res.success) { - throw Exception("Something went wrong!") - } - - val mangaList = res.data.manga.map { manga -> mangas[manga.title] = manga SManga.create().apply { @@ -164,14 +151,10 @@ class MangaTube : HttpSource() { } override fun mangaDetailsParse(response: Response): SManga { - val body = MangaTubeHelper.checkResponse(response) + val body = response.body.string() val res: BaseResponse = json.decodeFromString(body) - if (!res.success) { - throw Exception("Something went wrong!") - } - val manga: Manga = res.data.manga mangas[manga.title] = manga @@ -196,14 +179,10 @@ class MangaTube : HttpSource() { } override fun chapterListParse(response: Response): List { - val body = MangaTubeHelper.checkResponse(response) + val body = response.body.string() val res: BaseResponse = json.decodeFromString(body) - if (!res.success) { - throw Exception("Something went wrong!") - } - val chapterList = res.data.chapters.map { chapter -> SChapter.create().apply { url = "$baseUrl${chapter.readerURL}" @@ -228,14 +207,10 @@ class MangaTube : HttpSource() { } override fun pageListParse(response: Response): List { - val body = MangaTubeHelper.checkResponse(response) + val body = response.body.string() val res: BaseResponse = json.decodeFromString(body) - if (!res.success) { - throw Exception("Something went wrong!") - } - val mangaList = res.data.chapter.pages.map { page -> Page(page.index, page.url, page.altSource) } diff --git a/src/de/mangatube/src/eu/kanade/tachiyomi/extension/de/mangatube/util/MangaTubeHelper.kt b/src/de/mangatube/src/eu/kanade/tachiyomi/extension/de/mangatube/util/MangaTubeHelper.kt index 2c0dbcaa4d..92db23ed6a 100644 --- a/src/de/mangatube/src/eu/kanade/tachiyomi/extension/de/mangatube/util/MangaTubeHelper.kt +++ b/src/de/mangatube/src/eu/kanade/tachiyomi/extension/de/mangatube/util/MangaTubeHelper.kt @@ -1,7 +1,6 @@ package eu.kanade.tachiyomi.extension.de.mangatube.util import eu.kanade.tachiyomi.source.model.SManga -import okhttp3.Response class MangaTubeHelper { @@ -17,18 +16,5 @@ class MangaTubeHelper { } } - fun checkResponse(response: Response): String { - val body = response.body.string() - - if (response.code != 200) { - throw Exception("Unexpected network issue") - } - - if (body.startsWith("")) { - throw Exception("IP isn't verified. Open webview!") - } - - return body - } } } diff --git a/src/de/mangatube/src/eu/kanade/tachiyomi/extension/de/mangatube/util/ResponseInterceptor.kt b/src/de/mangatube/src/eu/kanade/tachiyomi/extension/de/mangatube/util/ResponseInterceptor.kt new file mode 100644 index 0000000000..4f0995f162 --- /dev/null +++ b/src/de/mangatube/src/eu/kanade/tachiyomi/extension/de/mangatube/util/ResponseInterceptor.kt @@ -0,0 +1,37 @@ +package eu.kanade.tachiyomi.extension.de.mangatube.util + +import okhttp3.Interceptor +import okhttp3.Response +import okhttp3.ResponseBody.Companion.toResponseBody +import okio.IOException + +class ResponseInterceptor : Interceptor { + + override fun intercept(chain: Interceptor.Chain): Response { + val request = chain.request() + + val response = try { + chain.proceed(request) + } catch (e: IOException) { + throw e + } + + val body = response.body.string() + + if (response.code != 200) { + throw Exception("Unexpected api issue") + } + + if (body.startsWith("")) { + throw Exception("IP isn't verified. Open webview!") + } + + if (body.contains(""""success":false""")) { + throw Exception("Resource not found!") + } + + return response.newBuilder() + .body(body.toResponseBody(response.body.contentType())) + .build() + } +}