diff --git a/src/en/comikey/AndroidManifest.xml b/src/en/comikey/AndroidManifest.xml deleted file mode 100644 index 0dded649c..000000000 --- a/src/en/comikey/AndroidManifest.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - - - - - - - - - - - - - diff --git a/src/en/comikey/build.gradle b/src/en/comikey/build.gradle deleted file mode 100644 index 21844a23f..000000000 --- a/src/en/comikey/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' -apply plugin: 'kotlinx-serialization' - -ext { - extName = 'Comikey' - pkgNameSuffix = 'en.comikey' - extClass = '.Comikey' - extVersionCode = 2 -} - -apply from: "$rootDir/common.gradle" diff --git a/src/en/comikey/res/mipmap-hdpi/ic_launcher.png b/src/en/comikey/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 020c2a408..000000000 Binary files a/src/en/comikey/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/comikey/res/mipmap-mdpi/ic_launcher.png b/src/en/comikey/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 6463da43c..000000000 Binary files a/src/en/comikey/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/comikey/res/mipmap-xhdpi/ic_launcher.png b/src/en/comikey/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index d993c20f8..000000000 Binary files a/src/en/comikey/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/comikey/res/mipmap-xxhdpi/ic_launcher.png b/src/en/comikey/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 7d351955a..000000000 Binary files a/src/en/comikey/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/comikey/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/comikey/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index eccb3ca08..000000000 Binary files a/src/en/comikey/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/comikey/res/web_hi_res_512.png b/src/en/comikey/res/web_hi_res_512.png deleted file mode 100644 index 450d9b98c..000000000 Binary files a/src/en/comikey/res/web_hi_res_512.png and /dev/null differ diff --git a/src/en/comikey/src/eu/kanade/tachiyomi/extension/en/comikey/Comikey.kt b/src/en/comikey/src/eu/kanade/tachiyomi/extension/en/comikey/Comikey.kt deleted file mode 100644 index 210ecd772..000000000 --- a/src/en/comikey/src/eu/kanade/tachiyomi/extension/en/comikey/Comikey.kt +++ /dev/null @@ -1,295 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.comikey - -import android.app.Application -import android.content.SharedPreferences -import eu.kanade.tachiyomi.extension.en.comikey.dto.MangaDetailsDto -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.asObservableSuccess -import eu.kanade.tachiyomi.source.ConfigurableSource -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.decodeFromString -import kotlinx.serialization.json.Json -import kotlinx.serialization.json.booleanOrNull -import kotlinx.serialization.json.jsonArray -import kotlinx.serialization.json.jsonObject -import kotlinx.serialization.json.jsonPrimitive -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import org.jsoup.Jsoup -import org.jsoup.parser.Parser -import rx.Observable -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get -import java.net.URLEncoder -import java.text.SimpleDateFormat - -class Comikey : HttpSource(), ConfigurableSource { - override val name = "Comikey" - - override val baseUrl = "https://comikey.com" - - private val apiUrl = "$baseUrl/sapi" - - override val lang = "en" - - override val supportsLatest = true - - override val client: OkHttpClient = network.cloudflareClient - - private val json = Json { - isLenient = true - ignoreUnknownKeys = true - } - - companion object { - const val SLUG_SEARCH_PREFIX = "slug:" - } - - // Home page functions - - override fun popularMangaRequest(page: Int) = GET("$baseUrl/comics/?order=-views&page=$page", headers) - - override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/comics/?page=$page", headers) - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - return GET("$baseUrl/comics/?q=${URLEncoder.encode(query, "utf-8")}&page=$page", headers) - } - - override fun popularMangaParse(response: Response) = mangaParse(response) - - override fun latestUpdatesParse(response: Response) = mangaParse(response) - - override fun searchMangaParse(response: Response) = mangaParse(response) - - override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable { - return if (query.startsWith(SLUG_SEARCH_PREFIX)) { - val manga = SManga.create().apply { - url = "/comics/" + query.removePrefix(SLUG_SEARCH_PREFIX) - } - return fetchMangaDetails(manga).map { mangaWithDetails -> - MangasPage(listOf(mangaWithDetails), false) - } - } else { - super.fetchSearchManga(page, query, filters) - } - } - - private fun mangaParse(response: Response): MangasPage { - - val responseJson = response.asJsoup() - - val mangaList = responseJson.select("section#series-list div.series-listing[data-view=list] > ul > li") - .map { - SManga.create().apply { - title = it.selectFirst("span.title a").text() - url = it.selectFirst("span.title a[href]").attr("href") - - val subtitle = it.selectFirst("span.subtitle").text().removePrefix("by") - author = subtitle.substringBefore("|").trim() - artist = subtitle.substringAfter("|").trim() - - thumbnail_url = it.selectFirst("div.image[style*=url(]") - ?.attr("style") - ?.substringAfter("url(")?.substringBefore(")") - ?: "https://comikey.com/static/images/svgs/no-cover.svg" - - genre = it.select("div.categories > ul.category-listing > li > span.category-button") - .joinToString(", ") { el -> el.text() } - - description = it.selectFirst("div.description").text() - status = SManga.UNKNOWN - initialized = true // we already have all of the fields - } - } - // we have a next page if the "Next Page" button is not disabled - val hasNextPage = responseJson.selectFirst("li.page-item.active ~ li.page-item.disabled") == null && - responseJson.selectFirst("li.page-item.active ~ li.page-item:not(.disabled)") != null - - return MangasPage(mangaList, hasNextPage) - } - - // Manga page functions - - override fun fetchMangaDetails(manga: SManga): Observable { - return getMangaId(manga).flatMap { id -> - client.newCall(mangaDetailsRequest(id)) - .asObservableSuccess() - .map { response -> - mangaDetailsParse(response).apply { initialized = true } - } - } - } - - private fun mangaDetailsRequest(id: Int) = GET("$apiUrl/comics/$id?format=json", headers) - - override fun mangaDetailsParse(response: Response): SManga { - val details = json.decodeFromString(response.body!!.string()) - return SManga.create().apply { - title = details.name!! - url = details.link!! - author = details.author?.map { it?.name }?.joinToString(", ") - artist = details.artist?.map { it?.name }?.joinToString(", ") - thumbnail_url = details.cover - genre = details.tags?.map { it?.name }?.joinToString(", ") - description = details.excerpt + "\n\n" + details.description - status = SManga.UNKNOWN - initialized = true - } - } - - private fun getMangaId(manga: SManga): Observable { - val mangaId = manga.url.trimEnd('/').substringAfterLast('/').toIntOrNull() - return if (mangaId != null) { - Observable.just(mangaId) - } else { - client.newCall(GET(baseUrl + manga.url, headers)) - .asObservableSuccess() - .map { response -> - manga.url = response.asJsoup().selectFirst("meta[property=og:url]").attr("content") - manga.url.trimEnd('/').substringAfterLast('/').toInt() - } - } - } - - private fun rssFeedRequest(mangaId: Int) = GET("$apiUrl/comics/$mangaId/feed.rss", headers) - - override fun fetchChapterList(manga: SManga): Observable> { - val chapterList = getMangaId(manga).flatMap { mangaId -> - client.newCall(rssFeedRequest(mangaId)) - .asObservableSuccess() - .map { response -> - chapterListParse(response, mangaId) - } - } - return if (preferences.getBoolean("filterOwnedChapter", false)) { - chapterList.flatMap { it.filterChapterList() } - } else { - chapterList - } - } - - override fun chapterListRequest(manga: SManga) = throw UnsupportedOperationException("Not used (chapterListRequest)") - - override fun chapterListParse(response: Response) = throw UnsupportedOperationException("Not used (chapterListParse)") - - private fun chapterListParse(response: Response, mangaId: Int): List { - return Jsoup.parse(response.body!!.string(), response.request.url.toString(), Parser.xmlParser()) - .select("channel > item").map { item -> - SChapter.create().apply { - val chapterGuid = item.selectFirst("guid").text().substringAfterLast(':') - url = "$apiUrl/comics/$mangaId/read?format=json&content=$chapterGuid" - name = item.selectFirst("title").text() - date_upload = SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss Z", java.util.Locale.US) - .parse(item.selectFirst("pubDate").text()) - ?.time ?: 0L - } - }.reversed() - } - - private data class IndexedChapter(val index: Int, val chapter: SChapter) : Comparable { - override fun compareTo(other: IndexedChapter) = this.index.compareTo(other.index) - } - - // determine which chapters the user has access to, and which are locked behind a paywall - private fun List.filterChapterList(): Observable> { - return Observable.from(this.mapIndexed { index, chapter -> IndexedChapter(index, chapter) }) - .filterByObservable { (_, chapter) -> - chapter.isAvailable() - }.toSortedList() - .map { indexed -> indexed.map { it.chapter } } - } - - private fun Observable.filterByObservable(predicate: rx.functions.Func1>): Observable { - return this.flatMap { item -> - predicate.call(item) - .first() - .filter { it } - .map { item } - } - } - - private fun SChapter.isAvailable(): Observable { - return client.newCall(pageListRequest(this)) - .asObservableSuccess() - .map { response -> - response.body?.string() - ?.let { Json.parseToJsonElement(it) } - ?.jsonObject?.get("ok") - ?.jsonPrimitive?.booleanOrNull - ?: true // Default to displaying the chapter if we get an error - } - } - - // Chapter page functions - - private val urlForbidden = "https://fakeimg.pl/1800x2252/FFFFFF/000000/?font_size=120&text=This%20chapter%20is%20not%20available%20for%20free.%0A%0AIf%20you%20have%20purchased%20this%20chapter%2C%20please%20%0Aopen%20the%20website%20in%20web%20view%20and%20log%20in." - - override fun fetchPageList(chapter: SChapter): Observable> { - return client.newCall(pageListRequest(chapter)) - .asObservableSuccess() - .flatMap { response -> - val request = getActualPageList(response) - ?: return@flatMap Observable.just(listOf(Page(0, urlForbidden, urlForbidden))) - - client.newCall(request) - .asObservableSuccess() - .map { responseActual -> - pageListParse(responseActual) - } - } - } - - override fun pageListRequest(chapter: SChapter) = GET(chapter.url, headers) - - private fun getActualPageList(response: Response): Request? { - val element = Json.parseToJsonElement(response.body!!.string()).jsonObject - val ok = element["ok"]?.jsonPrimitive?.booleanOrNull - if (ok != null && !ok) { - return null - } - val url = element["href"]?.jsonPrimitive?.content - return GET(url!!, headers) - } - - override fun pageListParse(response: Response): List { - return Json.parseToJsonElement(response.body!!.string()) - .jsonObject["readingOrder"]!! - .jsonArray.mapIndexed { index, element -> - val url = element.jsonObject["href"]!!.jsonPrimitive.content - Page(index, url, url) - } - } - - // the image url is always equal to the page url - override fun fetchImageUrl(page: Page): Observable = Observable.just(page.url) - - override fun imageUrlParse(response: Response) = throw UnsupportedOperationException("Not used (imageUrlParse)") - - // Preferences - - private val preferences: SharedPreferences by lazy { - Injekt.get().getSharedPreferences("source_$id", 0x0000) - } - - override fun setupPreferenceScreen(screen: androidx.preference.PreferenceScreen) { - val filterOwnedChapterPref = androidx.preference.CheckBoxPreference(screen.context).apply { - key = "filterOwnedChapter" - title = "[Experimental] Only show free/owned chapters" - setDefaultValue(false) - - setOnPreferenceChangeListener { _, newValue -> - val checkValue = newValue as Boolean - preferences.edit().putBoolean("filterOwnedChapter", checkValue).commit() - } - } - - screen.addPreference(filterOwnedChapterPref) - } -} diff --git a/src/en/comikey/src/eu/kanade/tachiyomi/extension/en/comikey/ComikeyURLActivity.kt b/src/en/comikey/src/eu/kanade/tachiyomi/extension/en/comikey/ComikeyURLActivity.kt deleted file mode 100644 index 5551dbf34..000000000 --- a/src/en/comikey/src/eu/kanade/tachiyomi/extension/en/comikey/ComikeyURLActivity.kt +++ /dev/null @@ -1,34 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.comikey - -import android.app.Activity -import android.content.ActivityNotFoundException -import android.content.Intent -import android.os.Bundle -import android.util.Log -import kotlin.system.exitProcess - -class ComikeyURLActivity : Activity() { - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - val pathSegments = intent?.data?.pathSegments - if (pathSegments != null && pathSegments.size >= 2) { - - val mainIntent = Intent().apply { - action = "eu.kanade.tachiyomi.SEARCH" - putExtra("query", Comikey.SLUG_SEARCH_PREFIX + pathSegments[1]) - putExtra("filter", packageName) - } - - try { - startActivity(mainIntent) - } catch (e: ActivityNotFoundException) { - Log.e("ComikeyUrlActivity", e.toString()) - } - } else { - Log.e("ComikeyUrlActivity", "could not parse uri from intent $intent") - } - - finish() - exitProcess(0) - } -} diff --git a/src/en/comikey/src/eu/kanade/tachiyomi/extension/en/comikey/dto/MangaDetailsDto.kt b/src/en/comikey/src/eu/kanade/tachiyomi/extension/en/comikey/dto/MangaDetailsDto.kt deleted file mode 100644 index 5b526d849..000000000 --- a/src/en/comikey/src/eu/kanade/tachiyomi/extension/en/comikey/dto/MangaDetailsDto.kt +++ /dev/null @@ -1,146 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.comikey.dto - -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable - -@Serializable -data class MangaDetailsDto( - @SerialName("id") - val id: Int? = null, - @SerialName("link") - val link: String? = null, - @SerialName("name") - val name: String? = null, - @SerialName("e4pid") - val e4pid: String? = null, - @SerialName("uslug") - val uslug: String? = null, - @SerialName("alt") - val alt: String? = null, - @SerialName("author") - val author: List? = null, - @SerialName("artist") - val artist: List? = null, - @SerialName("adult") - val adult: Boolean? = null, - @SerialName("tags") - val tags: List? = null, - @SerialName("keywords") - val keywords: String? = null, - @SerialName("description") - val description: String? = null, - @SerialName("excerpt") - val excerpt: String? = null, - @SerialName("created_at") - val createdAt: String? = null, - @SerialName("modified_at") - val modifiedAt: String? = null, - @SerialName("publisher") - val publisher: Publisher? = null, - @SerialName("color") - val color: String? = null, - @SerialName("in_exclusive") - val inExclusive: Boolean? = null, - @SerialName("in_hype") - val inHype: Boolean? = null, - @SerialName("all_free") - val allFree: Boolean? = null, - @SerialName("availability_strategy") - val availabilityStrategy: AvailabilityStrategy? = null, - @SerialName("campaigns") - val campaigns: List? = null, // unknown list element type, was null - @SerialName("last_updated") - val lastUpdated: String? = null, - @SerialName("chapter_count") - val chapterCount: Int? = null, - @SerialName("update_status") - val updateStatus: Int? = null, - @SerialName("update_text") - val updateText: String? = null, - @SerialName("format") - val format: Int? = null, - @SerialName("cover") - val cover: String? = null, - @SerialName("logo") - val logo: String? = null, - @SerialName("banner") - val banner: String? = null, - @SerialName("showcase") - val showcase: String? = null, // unknown type, was null - @SerialName("preview") - val preview: String? = null, // unknown type, was null - @SerialName("chapter_title") - val chapterTitle: String? = null, - @SerialName("geoblocks") - val geoblocks: String? = null -) { - @Serializable - data class Author( - @SerialName("id") - val id: Int? = null, - @SerialName("name") - val name: String? = null, - @SerialName("alt") - val alt: String? = null - ) - - @Serializable - data class Artist( - @SerialName("id") - val id: Int? = null, - @SerialName("name") - val name: String? = null, - @SerialName("alt") - val alt: String? = null - ) - - @Serializable - data class Tag( - @SerialName("name") - val name: String? = null, - @SerialName("description") - val description: String? = null, - @SerialName("slug") - val slug: String? = null, - @SerialName("color") - val color: String? = null, - @SerialName("is_primary") - val isPrimary: Boolean? = null - ) - - @Serializable - data class Publisher( - @SerialName("id") - val id: Int? = null, - @SerialName("name") - val name: String? = null, - @SerialName("language") - val language: String? = null, - @SerialName("homepage") - val homepage: String? = null, - @SerialName("logo") - val logo: String? = null, - @SerialName("geoblocks") - val geoblocks: String? = null - ) - - @Serializable - data class AvailabilityStrategy( - @SerialName("starting_count") - val startingCount: Int? = null, - @SerialName("latest_only_free") - val latestOnlyFree: Boolean? = null, - @SerialName("catchup_count") - val catchupCount: Int? = null, - @SerialName("simulpub") - val simulpub: Boolean? = null, - @SerialName("fpf_becomes_paid") - val fpfBecomesPaid: String? = null, - @SerialName("fpf_becomes_free") - val fpfBecomesFree: String? = null, - @SerialName("fpf_becomes_backlog") - val fpfBecomesBacklog: String? = null, - @SerialName("backlog_becomes_backlog") - val backlogBecomesBacklog: String? = null - ) -}