From 65f0403ccb05cb31572851d0685618533f432e43 Mon Sep 17 00:00:00 2001 From: Henry Date: Sat, 5 Jan 2019 15:54:09 -0800 Subject: [PATCH] Add ComiCake support (#706) Add ComiCake support --- src/all/comicake/build.gradle | 12 ++ .../extension/en/comicake/ComiCake.kt | 156 ++++++++++++++++++ .../extension/en/comicake/ComiCakeFactory.kt | 19 +++ src/all/foolslide/build.gradle | 2 +- .../en/foolslide/FoolSlideFactory.kt | 3 - 5 files changed, 188 insertions(+), 4 deletions(-) create mode 100644 src/all/comicake/build.gradle create mode 100644 src/all/comicake/src/eu/kanade/tachiyomi/extension/en/comicake/ComiCake.kt create mode 100644 src/all/comicake/src/eu/kanade/tachiyomi/extension/en/comicake/ComiCakeFactory.kt diff --git a/src/all/comicake/build.gradle b/src/all/comicake/build.gradle new file mode 100644 index 000000000..37b91733a --- /dev/null +++ b/src/all/comicake/build.gradle @@ -0,0 +1,12 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' + +ext { + appName = 'Tachiyomi: ComiCake' + pkgNameSuffix = "all.comicake" + extClass = '.ComiCakeFactory' + extVersionCode = 1 + libVersion = '1.2' +} + +apply from: "$rootDir/common.gradle" diff --git a/src/all/comicake/src/eu/kanade/tachiyomi/extension/en/comicake/ComiCake.kt b/src/all/comicake/src/eu/kanade/tachiyomi/extension/en/comicake/ComiCake.kt new file mode 100644 index 000000000..a09c2ca62 --- /dev/null +++ b/src/all/comicake/src/eu/kanade/tachiyomi/extension/en/comicake/ComiCake.kt @@ -0,0 +1,156 @@ +package eu.kanade.tachiyomi.extension.all.comicake + +import android.os.Build +import eu.kanade.tachiyomi.extension.BuildConfig +import eu.kanade.tachiyomi.source.model.* +import eu.kanade.tachiyomi.source.online.HttpSource +import okhttp3.Headers +import okhttp3.Request +import okhttp3.Response +import eu.kanade.tachiyomi.network.GET +import org.json.JSONArray +import org.json.JSONObject +import java.text.SimpleDateFormat +import kotlin.collections.ArrayList + +const val COMICAKE_DEFAULT_API_ENDPOINT = "/api" // Highly unlikely to change +const val COMICAKE_DEFAULT_READER_ENDPOINT = "/r" // Can change based on CC config + +open class ComiCake(override val name: String, override val baseUrl: String, override val lang: String, val readerEndpoint: String = COMICAKE_DEFAULT_READER_ENDPOINT, val apiEndpoint: String = COMICAKE_DEFAULT_API_ENDPOINT) : HttpSource() { + override val versionId = 1 + override val supportsLatest = true + private val readerBase = baseUrl + readerEndpoint + private var apiBase = baseUrl + apiEndpoint + + + private val userAgent = "Mozilla/5.0 (" + + "Android ${Build.VERSION.RELEASE}; Mobile) " + + "Tachiyomi/${BuildConfig.VERSION_NAME}" + + override fun headersBuilder() = Headers.Builder().apply { + add("User-Agent", userAgent) + } + + override fun popularMangaRequest(page: Int): Request { + return GET("$apiBase/comics.json?ordering=-created_at&page=$page") // Not actually popular, just latest added to system + } + + override fun popularMangaParse(response: Response): MangasPage { + val res = response.body()!!.string() + return getMangasPageFromComicsResponse(res) + } + + private fun getMangasPageFromComicsResponse(json: String, nested: Boolean = false) : MangasPage { + var response = JSONObject(json) + var results = response.getJSONArray("results") + val mangas = ArrayList() + val ids = mutableListOf(); + + for (i in 0 until results.length()) { + val obj = results.getJSONObject(i) + if(nested) { + val nestedComic = obj.getJSONObject("comic"); + val id = nestedComic.getInt("id") + if(ids.contains(id)) + continue + ids.add(id) + val manga = SManga.create() + manga.url = id.toString() + manga.title = nestedComic.getString("name") + mangas.add(manga) + } else { + val id = obj.getInt("id") + if(ids.contains(id)) + continue + ids.add(id) + mangas.add(parseComicJson(obj)) + } + } + return MangasPage(mangas, if (response.getString("next").isNullOrEmpty() || response.getString("next") == "null") false else true) + } + + override fun mangaDetailsRequest(manga: SManga): Request { + return GET("$apiBase/comics/${manga.url}.json") + } + + override fun mangaDetailsParse(response: Response): SManga { + val comicJson = JSONObject(response.body()!!.string()) + return parseComicJson(comicJson, true) + } + + private fun parseComicJson(obj: JSONObject, human: Boolean = false) = SManga.create().apply { + if(human) { + url = "$readerBase/series/${obj.getString("slug")}/" + } else { + url = obj.getInt("id").toString() // Yeah, I know... Feel free to improve on this + } + title = obj.getString("name") + thumbnail_url = obj.getString("cover") + author = parseListNames(obj.getJSONArray("author")) + artist = parseListNames(obj.getJSONArray("artist")) + description = obj.getString("description") + genre = parseListNames(obj.getJSONArray("tags")) + status = SManga.UNKNOWN + } + + private fun parseListNames(arr: JSONArray) : String { + var hold = ArrayList(arr.length()) + for(i in 0 until arr.length()) + hold.add(arr.getJSONObject(i).getString("name")) + return hold.sorted().joinToString(", ") + } + + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { + // TODO filters + return GET("$apiBase/comics.json?page=$page&search=$query") + } + + override fun searchMangaParse(response: Response): MangasPage { + val res = response.body()!!.string() + return getMangasPageFromComicsResponse(res) + } + + override fun latestUpdatesRequest(page: Int): Request { + return GET("$apiBase/chapters.json?page=$page&expand=comic") + } + + override fun latestUpdatesParse(response: Response): MangasPage { + val res = response.body()!!.string() + return getMangasPageFromComicsResponse(res, true) + } + + private fun parseChapterJson(obj: JSONObject) = SChapter.create().apply { + name = obj.getString("title") // title will always have content, vs. name that's an optional field + chapter_number = (obj.getInt("chapter") + (obj.getInt("subchapter") / 10.0)).toFloat() + date_upload = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX").parse(obj.getString("published_at")).time + // TODO scanlator field by adding team to expandable in CC (low priority given the use case of CC) + url = obj.getString("manifest") + } + + override fun chapterListRequest(manga: SManga): Request { + return GET("$apiBase/chapters.json?comic=${manga.url}&ordering=-volume%2C-chapter%2C-subchapter&n=1000", headers) // There's no pagination in Tachiyomi for chapters so we get 1k chapters + } + + override fun chapterListParse(response: Response): List { + val chapterJson = JSONObject(response.body()!!.string()) + var results = chapterJson.getJSONArray("results") + val ret = ArrayList() + for (i in 0 until results.length()) { + ret.add(parseChapterJson(results.getJSONObject(i))) + } + return ret + } + + override fun pageListParse(response: Response): List { + val webPub = JSONObject(response.body()!!.string()) + val readingOrder = webPub.getJSONArray("readingOrder") + val ret = ArrayList(); + for (i in 0 until readingOrder.length()) { + var pageUrl = readingOrder.getJSONObject(i).getString("href") + ret.add(Page(i, "", pageUrl)) + } + return ret + } + + override fun imageUrlParse(response: Response) = throw UnsupportedOperationException("This method should not be called!") +} \ No newline at end of file diff --git a/src/all/comicake/src/eu/kanade/tachiyomi/extension/en/comicake/ComiCakeFactory.kt b/src/all/comicake/src/eu/kanade/tachiyomi/extension/en/comicake/ComiCakeFactory.kt new file mode 100644 index 000000000..799ab1ed6 --- /dev/null +++ b/src/all/comicake/src/eu/kanade/tachiyomi/extension/en/comicake/ComiCakeFactory.kt @@ -0,0 +1,19 @@ +package eu.kanade.tachiyomi.extension.all.comicake + +import eu.kanade.tachiyomi.source.Source +import eu.kanade.tachiyomi.source.SourceFactory + +class ComiCakeFactory : SourceFactory { + override fun createSources(): List = getAllComiCake() +} + +fun getAllComiCake(): List { + return listOf( + WhimSubs(), + ChampionScans() + ) +} + +class WhimSubs : ComiCake("WhimSubs", "https://whimsubs.xyz", "en") + +class ChampionScans : ComiCake("Champion Scans", "https://reader.championscans.com", "en", "/") \ No newline at end of file diff --git a/src/all/foolslide/build.gradle b/src/all/foolslide/build.gradle index ae29aba73..ac804d2f7 100644 --- a/src/all/foolslide/build.gradle +++ b/src/all/foolslide/build.gradle @@ -5,7 +5,7 @@ ext { appName = 'Tachiyomi: FoolSlide' pkgNameSuffix = "all.foolslide" extClass = '.FoolSlideFactory' - extVersionCode = 10 + extVersionCode = 11 libVersion = '1.2' } diff --git a/src/all/foolslide/src/eu/kanade/tachiyomi/extension/en/foolslide/FoolSlideFactory.kt b/src/all/foolslide/src/eu/kanade/tachiyomi/extension/en/foolslide/FoolSlideFactory.kt index f9f8490c9..593fb46d7 100644 --- a/src/all/foolslide/src/eu/kanade/tachiyomi/extension/en/foolslide/FoolSlideFactory.kt +++ b/src/all/foolslide/src/eu/kanade/tachiyomi/extension/en/foolslide/FoolSlideFactory.kt @@ -18,7 +18,6 @@ class FoolSlideFactory : SourceFactory { fun getAllFoolSlide(): List { return listOf( JaminisBox(), - ChampionScans(), HelveticaScans(), SenseScans(), SeaOtterScans(), @@ -70,8 +69,6 @@ class JaminisBox : FoolSlide("Jaimini's Box", "https://jaiminisbox.com", "en", " } } -class ChampionScans : FoolSlide("Champion Scans", "http://reader.championscans.com", "en") - class HelveticaScans : FoolSlide("Helvetica Scans", "https://helveticascans.com", "en", "/r") class SenseScans : FoolSlide("Sense-Scans", "https://sensescans.com", "en", "/reader")