diff --git a/.run/FansubsCatGenerator.run.xml b/.run/FansubsCatGenerator.run.xml new file mode 100644 index 000000000..3609da5d0 --- /dev/null +++ b/.run/FansubsCatGenerator.run.xml @@ -0,0 +1,11 @@ + + + + + diff --git a/multisrc/overrides/fansubscat/fansubscat/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/fansubscat/fansubscat/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000..62a89997b Binary files /dev/null and b/multisrc/overrides/fansubscat/fansubscat/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/fansubscat/fansubscat/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/fansubscat/fansubscat/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000..3ffb60b76 Binary files /dev/null and b/multisrc/overrides/fansubscat/fansubscat/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/fansubscat/fansubscat/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/fansubscat/fansubscat/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000..515447115 Binary files /dev/null and b/multisrc/overrides/fansubscat/fansubscat/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/fansubscat/fansubscat/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/fansubscat/fansubscat/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..c550e843d Binary files /dev/null and b/multisrc/overrides/fansubscat/fansubscat/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/fansubscat/fansubscat/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/fansubscat/fansubscat/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000..bb7d942e0 Binary files /dev/null and b/multisrc/overrides/fansubscat/fansubscat/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/fansubscat/fansubscat/res/web_hi_res_512.png b/multisrc/overrides/fansubscat/fansubscat/res/web_hi_res_512.png new file mode 100644 index 000000000..9e00dbe6e Binary files /dev/null and b/multisrc/overrides/fansubscat/fansubscat/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/fansubscat/fansubscat/src/FansubsCatMain.kt b/multisrc/overrides/fansubscat/fansubscat/src/FansubsCatMain.kt new file mode 100644 index 000000000..abecc8907 --- /dev/null +++ b/multisrc/overrides/fansubscat/fansubscat/src/FansubsCatMain.kt @@ -0,0 +1,10 @@ +package eu.kanade.tachiyomi.extension.ca.fansubscat + +import eu.kanade.tachiyomi.multisrc.fansubscat.FansubsCat + +class FansubsCatMain : FansubsCat( + "Fansubs.cat", + "https://manga.fansubs.cat", + "ca", + isHentaiSite = false, +) diff --git a/multisrc/overrides/fansubscat/fansubscathentai/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/fansubscat/fansubscathentai/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000..85c7cda14 Binary files /dev/null and b/multisrc/overrides/fansubscat/fansubscathentai/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/fansubscat/fansubscathentai/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/fansubscat/fansubscathentai/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000..3f52697c4 Binary files /dev/null and b/multisrc/overrides/fansubscat/fansubscathentai/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/fansubscat/fansubscathentai/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/fansubscat/fansubscathentai/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000..015a708e2 Binary files /dev/null and b/multisrc/overrides/fansubscat/fansubscathentai/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/fansubscat/fansubscathentai/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/fansubscat/fansubscathentai/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..9d3a29b18 Binary files /dev/null and b/multisrc/overrides/fansubscat/fansubscathentai/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/fansubscat/fansubscathentai/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/fansubscat/fansubscathentai/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000..e3fb8c336 Binary files /dev/null and b/multisrc/overrides/fansubscat/fansubscathentai/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/fansubscat/fansubscathentai/res/web_hi_res_512.png b/multisrc/overrides/fansubscat/fansubscathentai/res/web_hi_res_512.png new file mode 100644 index 000000000..c2546bc59 Binary files /dev/null and b/multisrc/overrides/fansubscat/fansubscathentai/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/fansubscat/fansubscathentai/src/FansubsCatHentai.kt b/multisrc/overrides/fansubscat/fansubscathentai/src/FansubsCatHentai.kt new file mode 100644 index 000000000..704e68942 --- /dev/null +++ b/multisrc/overrides/fansubscat/fansubscathentai/src/FansubsCatHentai.kt @@ -0,0 +1,10 @@ +package eu.kanade.tachiyomi.extension.ca.fansubscathentai + +import eu.kanade.tachiyomi.multisrc.fansubscat.FansubsCat + +class FansubsCatHentai : FansubsCat( + "Fansubs.cat - Hentai", + "https://hentai.fansubs.cat/manga", + "ca", + isHentaiSite = true, +) diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/fansubscat/FansubsCat.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/fansubscat/FansubsCat.kt new file mode 100644 index 000000000..bae45d35d --- /dev/null +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/fansubscat/FansubsCat.kt @@ -0,0 +1,403 @@ +package eu.kanade.tachiyomi.multisrc.fansubscat + +import eu.kanade.tachiyomi.AppInfo +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.source.model.Filter +import eu.kanade.tachiyomi.source.model.FilterList +import eu.kanade.tachiyomi.source.model.MangasPage +import eu.kanade.tachiyomi.source.model.Page +import eu.kanade.tachiyomi.source.model.SChapter +import eu.kanade.tachiyomi.source.model.SManga +import eu.kanade.tachiyomi.source.online.HttpSource +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.contentOrNull +import kotlinx.serialization.json.float +import kotlinx.serialization.json.jsonArray +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive +import kotlinx.serialization.json.long +import okhttp3.Headers +import okhttp3.HttpUrl +import okhttp3.HttpUrl.Companion.toHttpUrl +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.Response +import uy.kohesive.injekt.injectLazy + +abstract class FansubsCat( + override val name: String, + override val baseUrl: String, + override val lang: String, + val isHentaiSite: Boolean, +) : HttpSource() { + + private val apiBaseUrl = "https://api.fansubs.cat" + + override val supportsLatest = true + + override fun headersBuilder(): Headers.Builder = Headers.Builder() + .add("User-Agent", "Tachiyomi/${AppInfo.getVersionName()}") + + override val client: OkHttpClient = network.client + + private val json: Json by injectLazy() + + private fun parseMangaFromJson(response: Response): MangasPage { + val jsonObject = json.decodeFromString(response.body.string()) + + val mangas = jsonObject["result"]!!.jsonArray.map { json -> + SManga.create().apply { + url = json.jsonObject["slug"]!!.jsonPrimitive.content + title = json.jsonObject["name"]!!.jsonPrimitive.content + thumbnail_url = json.jsonObject["thumbnail_url"]!!.jsonPrimitive.content + author = json.jsonObject["author"]!!.jsonPrimitive.contentOrNull + description = json.jsonObject["synopsis"]!!.jsonPrimitive.contentOrNull + status = json.jsonObject["status"]!!.jsonPrimitive.content.toStatus() + genre = json.jsonObject["genres"]!!.jsonPrimitive.contentOrNull + } + } + + return MangasPage(mangas, mangas.size >= 20) + } + + private fun parseChapterListFromJson(response: Response): List { + val jsonObject = json.decodeFromString(response.body.string()) + + return jsonObject["result"]!!.jsonArray.map { json -> + SChapter.create().apply { + url = json.jsonObject["id"]!!.jsonPrimitive.content + name = json.jsonObject["title"]!!.jsonPrimitive.content + chapter_number = json.jsonObject["number"]!!.jsonPrimitive.float + scanlator = json.jsonObject["fansub"]!!.jsonPrimitive.content + date_upload = json.jsonObject["created"]!!.jsonPrimitive.long + } + } + } + + private fun parsePageListFromJson(response: Response): List { + val jsonObject = json.decodeFromString(response.body.string()) + + return jsonObject["result"]!!.jsonArray.mapIndexed { i, it -> + Page( + i, + it.jsonObject["url"]!!.jsonPrimitive.content, + it.jsonObject["url"]!!.jsonPrimitive.content, + ) + } + } + + // Popular + + override fun popularMangaRequest(page: Int): Request { + return GET("$apiBaseUrl/manga/popular/$page?hentai=$isHentaiSite", headers) + } + + override fun popularMangaParse(response: Response): MangasPage = parseMangaFromJson(response) + + // Latest + + override fun latestUpdatesRequest(page: Int): Request { + return GET("$apiBaseUrl/manga/recent/$page?hentai=$isHentaiSite", headers) + } + + override fun latestUpdatesParse(response: Response): MangasPage = parseMangaFromJson(response) + + // Search + + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { + val filterList = if (filters.isEmpty()) getFilterList() else filters + val mangaTypeFilter = filterList.find { it is MangaTypeFilter } as MangaTypeFilter + val stateFilter = filterList.find { it is StateFilter } as StateFilter + val demographyFilter = filterList.find { it is DemographyFilter } as DemographyFilter + val genreFilter = filterList.find { it is GenreTagFilter } as GenreTagFilter + val themeFilter = filterList.find { it is ThemeTagFilter } as ThemeTagFilter + val builder = "$apiBaseUrl/manga/search/$page?hentai=$isHentaiSite".toHttpUrl().newBuilder() + mangaTypeFilter.addQueryParameter(builder) + stateFilter.addQueryParameter(builder) + demographyFilter.addQueryParameter(builder) + genreFilter.addQueryParameter(builder) + themeFilter.addQueryParameter(builder) + if (query.isNotBlank()) { + builder.addQueryParameter("query", query) + } + return GET(builder.toString(), headers) + } + + override fun searchMangaParse(response: Response): MangasPage = parseMangaFromJson(response) + + // Details + + override fun mangaDetailsRequest(manga: SManga): Request { + return GET( + "$apiBaseUrl/manga/details/${manga.url.substringAfterLast('/')}?hentai=$isHentaiSite", + headers, + ) + } + + override fun getMangaUrl(manga: SManga): String { + return "$baseUrl/${manga.url}" + } + + override fun mangaDetailsParse(response: Response): SManga { + val jsonObject = json.decodeFromString(response.body.string()) + val resultObject = jsonObject.jsonObject["result"]!!.jsonObject + + return SManga.create().apply { + url = resultObject["slug"]!!.jsonPrimitive.content + title = resultObject["name"]!!.jsonPrimitive.content + thumbnail_url = resultObject["thumbnail_url"]!!.jsonPrimitive.content + author = resultObject["author"]!!.jsonPrimitive.contentOrNull + description = resultObject["synopsis"]!!.jsonPrimitive.contentOrNull + status = resultObject["status"]!!.jsonPrimitive.content.toStatus() + genre = resultObject["genres"]!!.jsonPrimitive.contentOrNull + } + } + + private fun String?.toStatus() = when { + this == null -> SManga.UNKNOWN + this.contains("ongoing", ignoreCase = true) -> SManga.ONGOING + this.contains("finished", ignoreCase = true) -> SManga.COMPLETED + else -> SManga.UNKNOWN + } + + // Chapters + + override fun chapterListRequest(manga: SManga): Request { + return GET( + "$apiBaseUrl/manga/chapters/${manga.url.substringAfterLast('/')}?hentai=$isHentaiSite", + headers, + ) + } + + override fun chapterListParse(response: Response): List = + parseChapterListFromJson(response) + + // Pages + + override fun pageListRequest(chapter: SChapter): Request { + return GET( + "$apiBaseUrl/manga/pages/${chapter.url.substringAfterLast('/')}?hentai=$isHentaiSite", + headers, + ) + } + + override fun getChapterUrl(chapter: SChapter): String { + return "$baseUrl/${chapter.url.replace("/", "?f=")}" + } + + override fun pageListParse(response: Response): List = parsePageListFromJson(response) + + override fun imageUrlParse(response: Response): String = + throw UnsupportedOperationException("Not used") + + // Filter + override fun getFilterList() = FilterList( + listOfNotNull( + MangaTypeFilter("Tipus", getMangaTypeList()), + StateFilter("Estat", getStateList()), + if (!isHentaiSite) { + DemographyFilter("Demografies", getDemographyList()) + } else { + null + }, + GenreTagFilter("Gèneres (inclou/exclou)", getGenreList()), + ThemeTagFilter("Temàtiques (inclou/exclou)", getThemeList()), + ), + ) + + private fun getMangaTypeList() = listOf( + MangaType("oneshot", "One-shots"), + MangaType("serialized", "Serialitzats"), + ) + + private fun getStateList() = listOf( + State(1, "Completat"), + State(2, "En procés"), + State(3, "Parcialment completat"), + State(4, "Abandonat"), + State(5, "Cancel·lat"), + ) + + private fun getDemographyList() = listOf( + Demography(35, "Infantil"), + Demography(27, "Josei"), + Demography(12, "Seinen"), + Demography(16, "Shōjo"), + Demography(1, "Shōnen"), + Demography(-1, "No definida"), + ) + + private fun getGenreList() = listOfNotNull( + Tag(4, "Acció"), + Tag(7, "Amor"), + Tag(38, "Amor entre noies"), + Tag(23, "Amor entre nois"), + Tag(31, "Avantguardisme"), + Tag(6, "Aventura"), + Tag(10, "Ciència-ficció"), + Tag(2, "Comèdia"), + Tag(47, "De prestigi"), + Tag(3, "Drama"), + Tag(19, "Ecchi"), + Tag(46, "Erotisme"), + Tag(20, "Esports"), + Tag(5, "Fantasia"), + Tag(48, "Gastronomia"), + if (isHentaiSite) { + Tag(34, "Hentai") + } else { + null + }, + Tag(11, "Misteri"), + Tag(8, "Sobrenatural"), + Tag(17, "Suspens"), + Tag(21, "Terror"), + Tag(42, "Vida quotidiana"), + ) + + private fun getThemeList() = listOf( + Tag(71, "Animals de companyia"), + Tag(50, "Antropomorfisme"), + Tag(70, "Arts escèniques"), + Tag(18, "Arts marcials"), + Tag(81, "Arts visuals"), + Tag(64, "Canvi de gènere màgic"), + Tag(56, "Comèdia de gags"), + Tag(68, "Crim organitzat"), + Tag(69, "Cultura otaku"), + Tag(30, "Curses"), + Tag(54, "Delinqüència"), + Tag(43, "Detectivesc"), + Tag(55, "Educatiu"), + Tag(9, "Escolar"), + Tag(39, "Espai"), + Tag(77, "Esports d’equip"), + Tag(53, "Esports de combat"), + Tag(25, "Harem"), + Tag(73, "Harem invers"), + Tag(15, "Històric"), + Tag(59, "Idols femenines"), + Tag(60, "Idols masculins"), + Tag(75, "Indústria de l’entreteniment"), + Tag(61, "Isekai"), + Tag(58, "Joc d’alt risc"), + Tag(33, "Joc d’estratègia"), + Tag(82, "Laboral"), + Tag(29, "Mecha"), + Tag(66, "Medicina"), + Tag(67, "Memòries"), + Tag(22, "Militar"), + Tag(32, "Mitologia"), + Tag(26, "Música"), + Tag(65, "Noies màgiques"), + Tag(36, "Paròdia"), + Tag(49, "Personatges adults"), + Tag(51, "Personatges bufons"), + Tag(63, "Polígon amorós"), + Tag(13, "Psicològic"), + Tag(52, "Puericultura"), + Tag(72, "Reencarnació"), + Tag(62, "Relaxant"), + Tag(74, "Rerefons romàntic"), + Tag(37, "Samurais"), + Tag(57, "Sang i fetge"), + Tag(40, "Superpoders"), + Tag(76, "Supervivència"), + Tag(80, "Tirana"), + Tag(45, "Transformisme"), + Tag(41, "Vampirs"), + Tag(78, "Viatges en el temps"), + Tag(79, "Videojocs"), + ) + + private interface UrlQueryFilter { + fun addQueryParameter(url: HttpUrl.Builder) + } + + internal class MangaType(val id: String, name: String) : Filter.CheckBox(name) + internal class State(val id: Int, name: String) : Filter.CheckBox(name) + internal class Tag(val id: Int, name: String) : Filter.TriState(name) + internal class Demography(val id: Int, name: String) : Filter.CheckBox(name) + + private class MangaTypeFilter(collection: String, mangaTypes: List) : + Filter.Group(collection, mangaTypes), + UrlQueryFilter { + + override fun addQueryParameter(url: HttpUrl.Builder) { + var oneShotSelected = false + var serializedSelected = false + state.forEach { mangaType -> + if (mangaType.id.equals("oneshot") && mangaType.state) { + oneShotSelected = true + } else if (mangaType.id.equals("serialized") && mangaType.state) { + serializedSelected = true + } + } + if (oneShotSelected && !serializedSelected) { + url.addQueryParameter("type", "oneshot") + } else if (!oneShotSelected && serializedSelected) { + url.addQueryParameter("type", "serialized") + } else { + url.addQueryParameter("type", "all") + } + } + } + + private class StateFilter(collection: String, states: List) : + Filter.Group(collection, states), + UrlQueryFilter { + + override fun addQueryParameter(url: HttpUrl.Builder) { + state.forEach { state -> + if (state.state) { + url.addQueryParameter("status[]", state.id.toString()) + } + } + } + } + + private class DemographyFilter(collection: String, demographies: List) : + Filter.Group(collection, demographies), + UrlQueryFilter { + + override fun addQueryParameter(url: HttpUrl.Builder) { + state.forEach { demography -> + if (demography.state) { + url.addQueryParameter("demographies[]", demography.id.toString()) + } + } + } + } + + private class GenreTagFilter(collection: String, tags: List) : + Filter.Group(collection, tags), + UrlQueryFilter { + + override fun addQueryParameter(url: HttpUrl.Builder) { + state.forEach { tag -> + if (tag.isIncluded()) { + url.addQueryParameter("genres_include[]", tag.id.toString()) + } else if (tag.isExcluded()) { + url.addQueryParameter("genres_exclude[]", tag.id.toString()) + } + } + } + } + + private class ThemeTagFilter(collection: String, tags: List) : + Filter.Group(collection, tags), + UrlQueryFilter { + + override fun addQueryParameter(url: HttpUrl.Builder) { + state.forEach { tag -> + if (tag.isIncluded()) { + url.addQueryParameter("themes_include[]", tag.id.toString()) + } else if (tag.isExcluded()) { + url.addQueryParameter("themes_exclude[]", tag.id.toString()) + } + } + } + } +} diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/fansubscat/FansubsCatGenerator.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/fansubscat/FansubsCatGenerator.kt new file mode 100644 index 000000000..9271d2c6e --- /dev/null +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/fansubscat/FansubsCatGenerator.kt @@ -0,0 +1,36 @@ +package eu.kanade.tachiyomi.multisrc.fansubscat + +import generator.ThemeSourceData.SingleLang +import generator.ThemeSourceGenerator + +class FansubsCatGenerator : ThemeSourceGenerator { + + override val themePkg = "fansubscat" + + override val themeClass = "FansubsCat" + + override val baseVersionCode = 4 + + override val sources = listOf( + SingleLang( + name = "Fansubs.cat", + baseUrl = "https://manga.fansubs.cat", + lang = "ca", + className = "FansubsCatMain", + isNsfw = false, + pkgName = "fansubscat", + ), + SingleLang( + name = "Fansubs.cat - Hentai", + baseUrl = "https://hentai.fansubs.cat/manga", + lang = "ca", + className = "FansubsCatHentai", + isNsfw = true, + ), + ) + + companion object { + @JvmStatic + fun main(args: Array) = FansubsCatGenerator().createAll() + } +} diff --git a/src/ca/fansubscat/AndroidManifest.xml b/src/ca/fansubscat/AndroidManifest.xml deleted file mode 100644 index 8072ee00d..000000000 --- a/src/ca/fansubscat/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/src/ca/fansubscat/build.gradle b/src/ca/fansubscat/build.gradle deleted file mode 100644 index 5712d25d8..000000000 --- a/src/ca/fansubscat/build.gradle +++ /dev/null @@ -1,13 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' -apply plugin: 'kotlinx-serialization' - -ext { - extName = 'Fansubs.cat' - pkgNameSuffix = 'ca.fansubscat' - extClass = '.FansubsCat' - extVersionCode = 3 - isNsfw = true -} - -apply from: "$rootDir/common.gradle" diff --git a/src/ca/fansubscat/res/mipmap-hdpi/ic_launcher.png b/src/ca/fansubscat/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 8e0468783..000000000 Binary files a/src/ca/fansubscat/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/ca/fansubscat/res/mipmap-mdpi/ic_launcher.png b/src/ca/fansubscat/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index e0702ecea..000000000 Binary files a/src/ca/fansubscat/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/ca/fansubscat/res/mipmap-xhdpi/ic_launcher.png b/src/ca/fansubscat/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index b0027ad57..000000000 Binary files a/src/ca/fansubscat/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ca/fansubscat/res/mipmap-xxhdpi/ic_launcher.png b/src/ca/fansubscat/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 2424e711f..000000000 Binary files a/src/ca/fansubscat/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ca/fansubscat/res/mipmap-xxxhdpi/ic_launcher.png b/src/ca/fansubscat/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index ccc47ea5f..000000000 Binary files a/src/ca/fansubscat/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/ca/fansubscat/res/web_hi_res_512.png b/src/ca/fansubscat/res/web_hi_res_512.png deleted file mode 100644 index 8e8555a10..000000000 Binary files a/src/ca/fansubscat/res/web_hi_res_512.png and /dev/null differ diff --git a/src/ca/fansubscat/src/eu/kanade/tachiyomi/extension/ca/fansubscat/FansubsCat.kt b/src/ca/fansubscat/src/eu/kanade/tachiyomi/extension/ca/fansubscat/FansubsCat.kt deleted file mode 100644 index c65c57bba..000000000 --- a/src/ca/fansubscat/src/eu/kanade/tachiyomi/extension/ca/fansubscat/FansubsCat.kt +++ /dev/null @@ -1,163 +0,0 @@ -package eu.kanade.tachiyomi.extension.ca.fansubscat - -import eu.kanade.tachiyomi.AppInfo -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.asObservableSuccess -import eu.kanade.tachiyomi.source.model.FilterList -import eu.kanade.tachiyomi.source.model.MangasPage -import eu.kanade.tachiyomi.source.model.Page -import eu.kanade.tachiyomi.source.model.SChapter -import eu.kanade.tachiyomi.source.model.SManga -import eu.kanade.tachiyomi.source.online.HttpSource -import kotlinx.serialization.decodeFromString -import kotlinx.serialization.json.Json -import kotlinx.serialization.json.JsonObject -import kotlinx.serialization.json.contentOrNull -import kotlinx.serialization.json.float -import kotlinx.serialization.json.jsonArray -import kotlinx.serialization.json.jsonObject -import kotlinx.serialization.json.jsonPrimitive -import kotlinx.serialization.json.long -import okhttp3.Headers -import okhttp3.HttpUrl.Companion.toHttpUrlOrNull -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import rx.Observable -import uy.kohesive.injekt.injectLazy - -class FansubsCat : HttpSource() { - - override val name = "Fansubs.cat" - - override val baseUrl = "https://manga.fansubs.cat" - - override val lang = "ca" - - override val supportsLatest = true - - override fun headersBuilder(): Headers.Builder = Headers.Builder() - .add("User-Agent", "Tachiyomi/FansubsCat/${AppInfo.getVersionName()}") - - override val client: OkHttpClient = network.client - - private val json: Json by injectLazy() - - private val apiBaseUrl = "https://api.fansubs.cat" - - private fun parseMangaFromJson(response: Response): MangasPage { - val jsonObject = json.decodeFromString(response.body.string()) - - val mangas = jsonObject["result"]!!.jsonArray.map { json -> - SManga.create().apply { - url = json.jsonObject["slug"]!!.jsonPrimitive.content - title = json.jsonObject["name"]!!.jsonPrimitive.content - thumbnail_url = json.jsonObject["thumbnail_url"]!!.jsonPrimitive.content - author = json.jsonObject["author"]!!.jsonPrimitive.contentOrNull - description = json.jsonObject["synopsis"]!!.jsonPrimitive.contentOrNull - status = json.jsonObject["status"]!!.jsonPrimitive.content.toStatus() - genre = json.jsonObject["genres"]!!.jsonPrimitive.contentOrNull - } - } - - return MangasPage(mangas, mangas.size >= 20) - } - - private fun parseChapterListFromJson(response: Response): List { - val jsonObject = json.decodeFromString(response.body.string()) - - return jsonObject["result"]!!.jsonArray.map { json -> - SChapter.create().apply { - url = json.jsonObject["id"]!!.jsonPrimitive.content - name = json.jsonObject["title"]!!.jsonPrimitive.content - chapter_number = json.jsonObject["number"]!!.jsonPrimitive.float - scanlator = json.jsonObject["fansub"]!!.jsonPrimitive.content - date_upload = json.jsonObject["created"]!!.jsonPrimitive.long - } - } - } - - private fun parsePageListFromJson(response: Response): List { - val jsonObject = json.decodeFromString(response.body.string()) - - return jsonObject["result"]!!.jsonArray.mapIndexed { i, it -> - Page(i, it.jsonObject["url"]!!.jsonPrimitive.content, it.jsonObject["url"]!!.jsonPrimitive.content) - } - } - - // Popular - - override fun popularMangaRequest(page: Int): Request { - return GET("$apiBaseUrl/manga/popular/$page", headers) - } - - override fun popularMangaParse(response: Response): MangasPage = parseMangaFromJson(response) - - // Latest - - override fun latestUpdatesRequest(page: Int): Request { - return GET("$apiBaseUrl/manga/recent/$page", headers) - } - - override fun latestUpdatesParse(response: Response): MangasPage = parseMangaFromJson(response) - - // Search - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val url = "$apiBaseUrl/manga/search/$page".toHttpUrlOrNull()!!.newBuilder() - .addQueryParameter("query", query) - return GET(url.toString(), headers) - } - - override fun searchMangaParse(response: Response): MangasPage = parseMangaFromJson(response) - - // Details - - // Workaround to allow "Open in browser" to use the real URL - override fun fetchMangaDetails(manga: SManga): Observable = - client.newCall(apiMangaDetailsRequest(manga)).asObservableSuccess() - .map { mangaDetailsParse(it).apply { initialized = true } } - - // Return the real URL for "Open in browser" - override fun mangaDetailsRequest(manga: SManga) = GET("$baseUrl/${manga.url}", headers) - - private fun apiMangaDetailsRequest(manga: SManga): Request { - return GET("$apiBaseUrl/manga/details/${manga.url.substringAfterLast('/')}", headers) - } - - override fun mangaDetailsParse(response: Response): SManga { - val jsonObject = json.decodeFromString(response.body.string()) - val resultObject = jsonObject.jsonObject["result"]!!.jsonObject - - return SManga.create().apply { - url = resultObject["slug"]!!.jsonPrimitive.content - title = resultObject["name"]!!.jsonPrimitive.content - thumbnail_url = resultObject["thumbnail_url"]!!.jsonPrimitive.content - author = resultObject["author"]!!.jsonPrimitive.contentOrNull - description = resultObject["synopsis"]!!.jsonPrimitive.contentOrNull - status = resultObject["status"]!!.jsonPrimitive.content.toStatus() - genre = resultObject["genres"]!!.jsonPrimitive.contentOrNull - } - } - - private fun String?.toStatus() = when { - this == null -> SManga.UNKNOWN - this.contains("ongoing", ignoreCase = true) -> SManga.ONGOING - this.contains("finished", ignoreCase = true) -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - - // Chapters - - override fun chapterListRequest(manga: SManga): Request = GET("$apiBaseUrl/manga/chapters/${manga.url.substringAfterLast('/')}", headers) - - override fun chapterListParse(response: Response): List = parseChapterListFromJson(response) - - // Pages - - override fun pageListRequest(chapter: SChapter): Request = GET("$apiBaseUrl/manga/pages/${chapter.url}", headers) - - override fun pageListParse(response: Response): List = parsePageListFromJson(response) - - override fun imageUrlParse(response: Response): String = throw UnsupportedOperationException("Not used") -}