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
- )
-}