diff --git a/src/all/comickfun/AndroidManifest.xml b/src/all/comickfun/AndroidManifest.xml
deleted file mode 100644
index 8d9f69c07..000000000
--- a/src/all/comickfun/AndroidManifest.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/all/comickfun/assets/i18n/messages_en.properties b/src/all/comickfun/assets/i18n/messages_en.properties
deleted file mode 100644
index a76283014..000000000
--- a/src/all/comickfun/assets/i18n/messages_en.properties
+++ /dev/null
@@ -1,33 +0,0 @@
-ignored_groups_title=Ignored Groups
-ignored_groups_summary=Chapters from these groups won't be shown.\nOne group name per line (case-insensitive)
-preferred_groups_title=Preferred Groups
-preferred_groups_summary=Chapters from these groups will have priority over others (Even if lower score).\nOne group name per line (case-insensitive)
-ignored_tags_title=Ignored Tags
-ignored_tags_summary=Manga with these tags won't show up when browsing.\nOne tag per line (case-insensitive)
-show_alternative_titles_title=Show Alternative Titles
-show_alternative_titles_on=Adds alternative titles to the description
-show_alternative_titles_off=Does not show alternative titles to the description
-include_tags_title=Include Tags
-include_tags_on=More specific, but might contain spoilers!
-include_tags_off=Only the broader genres
-group_tags_title=Group Tags (fork must support grouping)
-group_tags_on=Will prefix tags with their type
-group_tags_off=List all tags together
-update_cover_title=Update Covers
-update_cover_on=Keep cover updated
-update_cover_off=Prefer first cover
-local_title_title=Translated Title
-local_title_on=if available
-local_title_off=Use the default title from the site
-score_position_title=Score Position in the Description
-score_position_top=Top
-score_position_middle=Middle
-score_position_bottom=Bottom
-score_position_none=Hide Score
-cover_quality_title=Cover Quality
-cover_quality_original=Original
-cover_quality_compressed=Compressed
-cover_quality_web_default=Small (Web Default)
-chapter_score_filtering_title=Automatically de-duplicate chapters
-chapter_score_filtering_on=For each chapter, only displays the scanlator with the highest score
-chapter_score_filtering_off=Does not filterout any chapters based on score (any other scanlator filtering will still apply)
diff --git a/src/all/comickfun/assets/i18n/messages_pt_br.properties b/src/all/comickfun/assets/i18n/messages_pt_br.properties
deleted file mode 100644
index d34a64f43..000000000
--- a/src/all/comickfun/assets/i18n/messages_pt_br.properties
+++ /dev/null
@@ -1,33 +0,0 @@
-ignored_groups_title=Grupos Ignorados
-ignored_groups_summary=Capítulos desses grupos não serão mostrados.\nUm nome de grupo por linha (não diferencia maiúsculas de minúsculas)
-preferred_groups_title=Grupos Preferidos
-preferred_groups_summary=Capítulos desses grupos terão prioridade sobre os outros (Mesmo que com nota mais baixa).\nUm nome de grupo por linha (não diferencia maiúsculas de minúsculas)
-ignored_tags_title=Tags Ignoradas
-ignored_tags_summary=Mangás com essas tags não aparecerão ao navegar.\nUma tag por linha (não diferencia maiúsculas de minúsculas)
-show_alternative_titles_title=Mostrar Títulos Alternativos
-show_alternative_titles_on=Adiciona títulos alternativos à descrição
-show_alternative_titles_off=Não mostra títulos alternativos na descrição
-include_tags_title=Incluir Tags
-include_tags_on=Mais específicas, mas podem conter spoilers!
-include_tags_off=Apenas os gêneros básicos
-group_tags_title=Agrupar Tags (necessário fork compatível)
-group_tags_on=Irá prefixar tags com o respectivo tipo
-group_tags_off=Listar todas as tags juntas
-update_cover_title=Atualizar Capas
-update_cover_on=Manter capa atualizada
-update_cover_off=Preferir a primeira capa
-local_title_title=Título Traduzido
-local_title_on=se disponível
-local_title_off=Usar o título padrão do site
-score_position_title=Posição da Nota na Descrição
-score_position_top=Topo
-score_position_middle=Meio
-score_position_bottom=Final
-score_position_none=Esconder Nota
-cover_quality_title=Qualidade da Capa
-cover_quality_original=Original
-cover_quality_compressed=Comprimida
-cover_quality_web_default=Pequena (Padrão Web)
-chapter_score_filtering_title=Desduplicar capítulos automaticamente
-chapter_score_filtering_on=Para cada capítulo, exibe apenas o scanlator com a maior nota
-chapter_score_filtering_off=Não filtra nenhum capítulo com base na nota (outros filtros de scanlator ainda se aplicarão)
diff --git a/src/all/comickfun/build.gradle b/src/all/comickfun/build.gradle
deleted file mode 100644
index 456ed8196..000000000
--- a/src/all/comickfun/build.gradle
+++ /dev/null
@@ -1,12 +0,0 @@
-ext {
- extName = 'Comick'
- extClass = '.ComickFactory'
- extVersionCode = 62
- isNsfw = true
-}
-
-apply from: "$rootDir/common.gradle"
-
-dependencies {
- implementation(project(":lib:i18n"))
-}
diff --git a/src/all/comickfun/res/mipmap-hdpi/ic_launcher.png b/src/all/comickfun/res/mipmap-hdpi/ic_launcher.png
deleted file mode 100644
index f8a3106a5..000000000
Binary files a/src/all/comickfun/res/mipmap-hdpi/ic_launcher.png and /dev/null differ
diff --git a/src/all/comickfun/res/mipmap-mdpi/ic_launcher.png b/src/all/comickfun/res/mipmap-mdpi/ic_launcher.png
deleted file mode 100644
index 81c0f670a..000000000
Binary files a/src/all/comickfun/res/mipmap-mdpi/ic_launcher.png and /dev/null differ
diff --git a/src/all/comickfun/res/mipmap-xhdpi/ic_launcher.png b/src/all/comickfun/res/mipmap-xhdpi/ic_launcher.png
deleted file mode 100644
index 5b0c0098e..000000000
Binary files a/src/all/comickfun/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ
diff --git a/src/all/comickfun/res/mipmap-xxhdpi/ic_launcher.png b/src/all/comickfun/res/mipmap-xxhdpi/ic_launcher.png
deleted file mode 100644
index 9057665f9..000000000
Binary files a/src/all/comickfun/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ
diff --git a/src/all/comickfun/res/mipmap-xxxhdpi/ic_launcher.png b/src/all/comickfun/res/mipmap-xxxhdpi/ic_launcher.png
deleted file mode 100644
index 64aad4954..000000000
Binary files a/src/all/comickfun/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ
diff --git a/src/all/comickfun/src/eu/kanade/tachiyomi/extension/all/comickfun/Comick.kt b/src/all/comickfun/src/eu/kanade/tachiyomi/extension/all/comickfun/Comick.kt
deleted file mode 100644
index 2811c70db..000000000
--- a/src/all/comickfun/src/eu/kanade/tachiyomi/extension/all/comickfun/Comick.kt
+++ /dev/null
@@ -1,773 +0,0 @@
-package eu.kanade.tachiyomi.extension.all.comickfun
-
-import android.content.SharedPreferences
-import androidx.preference.EditTextPreference
-import androidx.preference.ListPreference
-import androidx.preference.PreferenceScreen
-import androidx.preference.SwitchPreferenceCompat
-import eu.kanade.tachiyomi.lib.i18n.Intl
-import eu.kanade.tachiyomi.network.GET
-import eu.kanade.tachiyomi.network.asObservableSuccess
-import eu.kanade.tachiyomi.network.interceptor.rateLimit
-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 keiyoushi.utils.getPreferencesLazy
-import kotlinx.serialization.decodeFromString
-import kotlinx.serialization.json.Json
-import okhttp3.Headers
-import okhttp3.HttpUrl.Builder
-import okhttp3.HttpUrl.Companion.toHttpUrl
-import okhttp3.Interceptor
-import okhttp3.Request
-import okhttp3.Response
-import rx.Observable
-import java.text.ParseException
-import java.text.SimpleDateFormat
-import java.util.Locale
-import java.util.TimeZone
-import java.util.concurrent.TimeUnit
-import kotlin.math.min
-
-abstract class Comick(
- override val lang: String,
- private val comickLang: String,
-) : ConfigurableSource, HttpSource() {
-
- override val name = "Comick"
-
- override val baseUrl = "https://comick.io"
-
- private val apiUrl = "https://api.comick.fun"
-
- override val supportsLatest = true
-
- private val json = Json {
- ignoreUnknownKeys = true
- isLenient = true
- coerceInputValues = true
- explicitNulls = true
- }
-
- private lateinit var searchResponse: List
-
- private val intl by lazy {
- Intl(
- language = lang,
- baseLanguage = "en",
- availableLanguages = setOf("en", "pt-BR"),
- classLoader = this::class.java.classLoader!!,
- )
- }
-
- private val preferences by getPreferencesLazy { newLineIgnoredGroups() }
-
- override fun setupPreferenceScreen(screen: PreferenceScreen) {
- EditTextPreference(screen.context).apply {
- key = IGNORED_GROUPS_PREF
- title = intl["ignored_groups_title"]
- summary = intl["ignored_groups_summary"]
-
- setOnPreferenceChangeListener { _, newValue ->
- preferences.edit()
- .putString(IGNORED_GROUPS_PREF, newValue.toString())
- .commit()
- }
- }.also(screen::addPreference)
-
- EditTextPreference(screen.context).apply {
- key = PREFERRED_GROUPS_PREF
- title = intl["preferred_groups_title"]
- summary = intl["preferred_groups_summary"]
-
- setOnPreferenceChangeListener { _, newValue ->
- preferences.edit()
- .putString(PREFERRED_GROUPS_PREF, newValue.toString())
- .commit()
- }
- }.also(screen::addPreference)
-
- EditTextPreference(screen.context).apply {
- key = IGNORED_TAGS_PREF
- title = intl["ignored_tags_title"]
- summary = intl["ignored_tags_summary"]
- }.also(screen::addPreference)
-
- SwitchPreferenceCompat(screen.context).apply {
- key = SHOW_ALTERNATIVE_TITLES_PREF
- title = intl["show_alternative_titles_title"]
- summaryOn = intl["show_alternative_titles_on"]
- summaryOff = intl["show_alternative_titles_off"]
- setDefaultValue(SHOW_ALTERNATIVE_TITLES_DEFAULT)
-
- setOnPreferenceChangeListener { _, newValue ->
- preferences.edit()
- .putBoolean(SHOW_ALTERNATIVE_TITLES_PREF, newValue as Boolean)
- .commit()
- }
- }.also(screen::addPreference)
-
- SwitchPreferenceCompat(screen.context).apply {
- key = INCLUDE_MU_TAGS_PREF
- title = intl["include_tags_title"]
- summaryOn = intl["include_tags_on"]
- summaryOff = intl["include_tags_off"]
- setDefaultValue(INCLUDE_MU_TAGS_DEFAULT)
-
- setOnPreferenceChangeListener { _, newValue ->
- preferences.edit()
- .putBoolean(INCLUDE_MU_TAGS_PREF, newValue as Boolean)
- .commit()
- }
- }.also(screen::addPreference)
-
- SwitchPreferenceCompat(screen.context).apply {
- key = GROUP_TAGS_PREF
- title = intl["group_tags_title"]
- summaryOn = intl["group_tags_on"]
- summaryOff = intl["group_tags_off"]
- setDefaultValue(GROUP_TAGS_DEFAULT)
-
- setOnPreferenceChangeListener { _, newValue ->
- preferences.edit()
- .putBoolean(GROUP_TAGS_PREF, newValue as Boolean)
- .commit()
- }
- }.also(screen::addPreference)
-
- SwitchPreferenceCompat(screen.context).apply {
- key = FIRST_COVER_PREF
- title = intl["update_cover_title"]
- summaryOff = intl["update_cover_off"]
- summaryOn = intl["update_cover_on"]
- setDefaultValue(FIRST_COVER_DEFAULT)
-
- setOnPreferenceChangeListener { _, newValue ->
- preferences.edit()
- .putBoolean(FIRST_COVER_PREF, newValue as Boolean)
- .commit()
- }
- }.also(screen::addPreference)
-
- ListPreference(screen.context).apply {
- key = COVER_QUALITY_PREF
- title = intl["cover_quality_title"]
- entries = arrayOf(
- intl["cover_quality_original"],
- intl["cover_quality_compressed"],
- intl["cover_quality_web_default"],
- )
- entryValues = arrayOf(
- "Original",
- "Compressed",
- COVER_QUALITY_DEFAULT,
- )
- setDefaultValue(COVER_QUALITY_DEFAULT)
- summary = "%s"
-
- setOnPreferenceChangeListener { _, newValue ->
- preferences.edit()
- .putString(COVER_QUALITY_PREF, newValue as String)
- .commit()
- }
- }.also(screen::addPreference)
-
- SwitchPreferenceCompat(screen.context).apply {
- key = LOCAL_TITLE_PREF
- title = intl["local_title_title"]
- summaryOff = intl["local_title_off"]
- summaryOn = intl["local_title_on"]
- setDefaultValue(LOCAL_TITLE_DEFAULT)
-
- setOnPreferenceChangeListener { _, newValue ->
- preferences.edit()
- .putBoolean(LOCAL_TITLE_PREF, newValue as Boolean)
- .commit()
- }
- }.also(screen::addPreference)
-
- ListPreference(screen.context).apply {
- key = SCORE_POSITION_PREF
- title = intl["score_position_title"]
- summary = "%s"
- entries = arrayOf(
- intl["score_position_top"],
- intl["score_position_middle"],
- intl["score_position_bottom"],
- intl["score_position_none"],
- )
- entryValues = arrayOf(SCORE_POSITION_DEFAULT, "middle", "bottom", "none")
- setDefaultValue(SCORE_POSITION_DEFAULT)
-
- setOnPreferenceChangeListener { _, newValue ->
- val selected = newValue as String
- val index = findIndexOfValue(selected)
- val entry = entryValues[index] as String
-
- preferences.edit()
- .putString(SCORE_POSITION_PREF, entry)
- .commit()
- }
- }.also(screen::addPreference)
-
- SwitchPreferenceCompat(screen.context).apply {
- key = CHAPTER_SCORE_FILTERING_PREF
- title = intl["chapter_score_filtering_title"]
- summaryOff = intl["chapter_score_filtering_off"]
- summaryOn = intl["chapter_score_filtering_on"]
- setDefaultValue(CHAPTER_SCORE_FILTERING_DEFAULT)
-
- setOnPreferenceChangeListener { _, newValue ->
- preferences.edit()
- .putBoolean(CHAPTER_SCORE_FILTERING_PREF, newValue as Boolean)
- .commit()
- }
- }.also(screen::addPreference)
- }
-
- private val SharedPreferences.ignoredGroups: Set
- get() = getString(IGNORED_GROUPS_PREF, "")
- ?.lowercase()
- ?.split("\n")
- ?.map(String::trim)
- ?.filter(String::isNotEmpty)
- ?.sorted()
- .orEmpty()
- .toSet()
-
- private val SharedPreferences.preferredGroups: Set
- get() = getString(PREFERRED_GROUPS_PREF, "")
- ?.lowercase()
- ?.split("\n")
- ?.map(String::trim)
- ?.filter(String::isNotEmpty)
- .orEmpty()
- .toSet()
-
- private val SharedPreferences.ignoredTags: String
- get() = getString(IGNORED_TAGS_PREF, "")
- ?.split("\n")
- ?.map(String::trim)
- ?.filter(String::isNotEmpty)
- .orEmpty()
- .joinToString(",")
-
- private val SharedPreferences.showAlternativeTitles: Boolean
- get() = getBoolean(SHOW_ALTERNATIVE_TITLES_PREF, SHOW_ALTERNATIVE_TITLES_DEFAULT)
-
- private val SharedPreferences.includeMuTags: Boolean
- get() = getBoolean(INCLUDE_MU_TAGS_PREF, INCLUDE_MU_TAGS_DEFAULT)
-
- private val SharedPreferences.groupTags: Boolean
- get() = getBoolean(GROUP_TAGS_PREF, GROUP_TAGS_DEFAULT)
-
- private val SharedPreferences.updateCover: Boolean
- get() = getBoolean(FIRST_COVER_PREF, FIRST_COVER_DEFAULT)
-
- private val coverQuality: CoverQuality
- get() = CoverQuality.valueOf(
- preferences.getString(COVER_QUALITY_PREF, COVER_QUALITY_DEFAULT) ?: COVER_QUALITY_DEFAULT,
- )
-
- private val SharedPreferences.localTitle: String
- get() = if (getBoolean(
- LOCAL_TITLE_PREF,
- LOCAL_TITLE_DEFAULT,
- )
- ) {
- comickLang.lowercase()
- } else {
- "all"
- }
-
- private val SharedPreferences.scorePosition: String
- get() = getString(SCORE_POSITION_PREF, SCORE_POSITION_DEFAULT) ?: SCORE_POSITION_DEFAULT
-
- private val SharedPreferences.chapterScoreFiltering: Boolean
- get() = getBoolean(CHAPTER_SCORE_FILTERING_PREF, CHAPTER_SCORE_FILTERING_DEFAULT)
-
- override fun headersBuilder() = Headers.Builder().apply {
- add("Referer", "$baseUrl/")
- add("User-Agent", "Tachiyomi ${System.getProperty("http.agent")}")
- }
-
- override val client = network.cloudflareClient.newBuilder()
- .addNetworkInterceptor(::errorInterceptor)
- .addInterceptor(::imageInterceptor)
- .rateLimit(5, 6, TimeUnit.SECONDS) // == 50req each (60sec / 1min)
- .build()
-
- private val imageClient = network.cloudflareClient.newBuilder()
- .rateLimit(7, 4, TimeUnit.SECONDS) // == 1.75req/1sec == 14req/8sec == 105req/60sec
- .build()
-
- private val smallThumbnailClient = network.cloudflareClient.newBuilder()
- .rateLimit(14, 1, TimeUnit.SECONDS)
- .build()
-
- private fun imageInterceptor(chain: Interceptor.Chain): Response {
- val request = chain.request()
- val url = request.url.toString()
-
- return if ("comick.pictures" in url && "-s." in url) {
- smallThumbnailClient.newCall(request).execute()
- } else if ("comick.pictures" in url) {
- imageClient.newCall(request).execute()
- } else {
- chain.proceed(request)
- }
- }
-
- private fun errorInterceptor(chain: Interceptor.Chain): Response {
- val response = chain.proceed(chain.request())
-
- if (
- response.isSuccessful ||
- "application/json" !in response.header("Content-Type").orEmpty()
- ) {
- return response
- }
-
- val error = try {
- response.parseAs()
- } catch (_: Exception) {
- null
- }
-
- error?.run {
- throw Exception("$name error $statusCode: $message")
- } ?: throw Exception("HTTP error ${response.code}")
- }
-
- /** Popular Manga **/
- override fun popularMangaRequest(page: Int): Request {
- return searchMangaRequest(
- page = page,
- query = "",
- filters = FilterList(
- SortFilter("follow"),
- ),
- )
- }
-
- override fun popularMangaParse(response: Response): MangasPage {
- val result = response.parseAs>()
- return MangasPage(
- result.map(SearchManga::toSManga),
- hasNextPage = result.size >= LIMIT,
- )
- }
-
- /** Latest Manga **/
- override fun latestUpdatesRequest(page: Int): Request {
- return searchMangaRequest(
- page = page,
- query = "",
- filters = FilterList(
- SortFilter("uploaded"),
- ),
- )
- }
-
- override fun latestUpdatesParse(response: Response) = popularMangaParse(response)
-
- /** Manga Search **/
- override fun fetchSearchManga(
- page: Int,
- query: String,
- filters: FilterList,
- ): Observable {
- return if (query.startsWith(SLUG_SEARCH_PREFIX)) {
- // url deep link
- val slugOrHid = query.substringAfter(SLUG_SEARCH_PREFIX)
- val manga = SManga.create().apply { this.url = "/comic/$slugOrHid#" }
- fetchMangaDetails(manga).map {
- MangasPage(listOf(it), false)
- }
- } else if (query.isEmpty()) {
- // regular filtering without text search
- client.newCall(searchMangaRequest(page, query, filters))
- .asObservableSuccess()
- .map(::searchMangaParse)
- } else {
- // text search, no pagination in api
- if (page == 1) {
- client.newCall(querySearchRequest(query))
- .asObservableSuccess()
- .map(::querySearchParse)
- } else {
- Observable.just(paginatedSearchPage(page))
- }
- }
- }
-
- private fun querySearchRequest(query: String): Request {
- val url = "$apiUrl/v1.0/search?limit=300&page=1&tachiyomi=true"
- .toHttpUrl().newBuilder()
- .addQueryParameter("q", query.trim())
- .build()
-
- return GET(url, headers)
- }
-
- private fun querySearchParse(response: Response): MangasPage {
- searchResponse = response.parseAs()
-
- return paginatedSearchPage(1)
- }
-
- private fun paginatedSearchPage(page: Int): MangasPage {
- val end = min(page * LIMIT, searchResponse.size)
- val entries = searchResponse.subList((page - 1) * LIMIT, end)
- .map(SearchManga::toSManga)
- return MangasPage(entries, end < searchResponse.size)
- }
-
- private fun addTagQueryParameters(builder: Builder, tags: String, parameterName: String) {
- tags.split(",").filter(String::isNotEmpty).forEach {
- builder.addQueryParameter(
- parameterName,
- it.trim().lowercase().replace(SPACE_AND_SLASH_REGEX, "-")
- .replace("'-", "-and-039-").replace("'", "-and-039-"),
- )
- }
- }
-
- override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
- val url = "$apiUrl/v1.0/search".toHttpUrl().newBuilder().apply {
- filters.forEach { it ->
- when (it) {
- is CompletedFilter -> {
- if (it.state) {
- addQueryParameter("completed", "true")
- }
- }
-
- is GenreFilter -> {
- it.state.filter { it.isIncluded() }.forEach {
- addQueryParameter("genres", it.value)
- }
-
- it.state.filter { it.isExcluded() }.forEach {
- addQueryParameter("excludes", it.value)
- }
- }
-
- is DemographicFilter -> {
- it.state.filter { it.state }.forEach {
- addQueryParameter("demographic", it.value)
- }
- }
-
- is TypeFilter -> {
- it.state.filter { it.state }.forEach {
- addQueryParameter("country", it.value)
- }
- }
-
- is SortFilter -> {
- addQueryParameter("sort", it.getValue())
- }
-
- is StatusFilter -> {
- if (it.state > 0) {
- addQueryParameter("status", it.getValue())
- }
- }
-
- is ContentRatingFilter -> {
- if (it.state > 0) {
- addQueryParameter("content_rating", it.getValue())
- }
- }
-
- is CreatedAtFilter -> {
- if (it.state > 0) {
- addQueryParameter("time", it.getValue())
- }
- }
-
- is MinimumFilter -> {
- if (it.state.isNotEmpty()) {
- addQueryParameter("minimum", it.state)
- }
- }
-
- is FromYearFilter -> {
- if (it.state.isNotEmpty()) {
- addQueryParameter("from", it.state)
- }
- }
-
- is ToYearFilter -> {
- if (it.state.isNotEmpty()) {
- addQueryParameter("to", it.state)
- }
- }
-
- is TagFilter -> {
- if (it.state.isNotEmpty()) {
- addTagQueryParameters(this, it.state, "tags")
- }
- }
-
- is ExcludedTagFilter -> {
- if (it.state.isNotEmpty()) {
- addTagQueryParameters(this, it.state, "excluded-tags")
- }
- }
-
- else -> {}
- }
- }
- addTagQueryParameters(this, preferences.ignoredTags, "excluded-tags")
- addQueryParameter("tachiyomi", "true")
- addQueryParameter("limit", "$LIMIT")
- addQueryParameter("page", "$page")
- }.build()
-
- return GET(url, headers)
- }
-
- override fun searchMangaParse(response: Response) = popularMangaParse(response)
-
- /** Manga Details **/
- override fun mangaDetailsRequest(manga: SManga): Request {
- // Migration from slug based urls to hid based ones
- if (!manga.url.endsWith("#")) {
- throw Exception("Migrate from Comick to Comick")
- }
-
- val mangaUrl = manga.url.removeSuffix("#")
- return GET("$apiUrl$mangaUrl?tachiyomi=true", headers)
- }
-
- override fun fetchMangaDetails(manga: SManga): Observable {
- return client.newCall(mangaDetailsRequest(manga))
- .asObservableSuccess()
- .map { response ->
- mangaDetailsParse(response, manga).apply { initialized = true }
- }
- }
-
- override fun mangaDetailsParse(response: Response): SManga =
- mangaDetailsParse(response, SManga.create())
-
- private fun mangaDetailsParse(response: Response, manga: SManga): SManga {
- val mangaData = response.parseAs()
- if (!preferences.updateCover && manga.thumbnail_url != mangaData.comic.cover) {
- val coversUrl =
- "$apiUrl/comic/${mangaData.comic.slug ?: mangaData.comic.hid}/covers?tachiyomi=true"
- val covers = client.newCall(GET(coversUrl)).execute()
- .parseAs().mdCovers.reversed()
- val firstVol = covers.filter { it.vol == "1" }.ifEmpty { covers }
- val originalCovers = firstVol
- .filter { mangaData.comic.isoLang.orEmpty().startsWith(it.locale.orEmpty()) }
- val localCovers = firstVol
- .filter { comickLang.startsWith(it.locale.orEmpty()) }
- return mangaData.toSManga(
- includeMuTags = preferences.includeMuTags,
- scorePosition = preferences.scorePosition,
- showAlternativeTitles = preferences.showAlternativeTitles,
- covers = localCovers.ifEmpty { originalCovers }.ifEmpty { firstVol },
- groupTags = preferences.groupTags,
- titleLang = preferences.localTitle,
- coverQuality = coverQuality,
- )
- }
- return mangaData.toSManga(
- includeMuTags = preferences.includeMuTags,
- scorePosition = preferences.scorePosition,
- showAlternativeTitles = preferences.showAlternativeTitles,
- groupTags = preferences.groupTags,
- titleLang = preferences.localTitle,
- coverQuality = coverQuality,
- )
- }
-
- override fun getMangaUrl(manga: SManga): String {
- return "$baseUrl${manga.url.removeSuffix("#")}"
- }
-
- /** Manga Chapter List **/
- override fun chapterListRequest(manga: SManga): Request {
- // Migration from slug based urls to hid based ones
- if (!manga.url.endsWith("#")) {
- throw Exception("Migrate from Comick to Comick")
- }
-
- val mangaUrl = manga.url.removeSuffix("#")
- val url = "$apiUrl$mangaUrl".toHttpUrl().newBuilder().apply {
- addPathSegment("chapters")
- if (comickLang != "all") addQueryParameter("lang", comickLang)
- addQueryParameter("tachiyomi", "true")
- addQueryParameter("limit", "$CHAPTERS_LIMIT")
- }.build()
-
- return GET(url, headers)
- }
-
- override fun chapterListParse(response: Response): List {
- val chapterListResponse = response.parseAs()
-
- val preferredGroups = preferences.preferredGroups
- val ignoredGroupsLowercase = preferences.ignoredGroups.map { it.lowercase() }
-
- val mangaUrl = response.request.url.toString()
- .substringBefore("/chapters")
- .substringAfter(apiUrl)
-
- val currentTimestamp = System.currentTimeMillis()
-
- // First, apply the ignored groups filter to remove chapters from blocked groups
- val filteredChapters = chapterListResponse.chapters
- .filter {
- val publishTime = try {
- publishedDateFormat.parse(it.publishedAt)!!.time
- } catch (_: ParseException) {
- 0L
- }
-
- val publishedChapter = publishTime <= currentTimestamp
-
- val noGroupBlock = it.groups.map { g -> g.lowercase() }
- .intersect(ignoredGroupsLowercase)
- .isEmpty()
-
- publishedChapter && noGroupBlock
- }
-
- // Now apply the primary filtering logic based on user preferences
- val finalChapters = if (preferredGroups.isEmpty()) {
- // If preferredGroups is empty, fall back to the existing score filter
- filteredChapters.filterOnScore(preferences.chapterScoreFiltering)
- } else {
- // If preferredGroups is not empty, use the list to grab chapters from those groups in order of preference
- val chaptersByNumber = filteredChapters.groupBy { it.chap }
- val preferredFilteredChapters = mutableListOf()
-
- // Iterate through each chapter number's group of chapters
- chaptersByNumber.forEach { (_, chaptersForNumber) ->
- // Find the chapter from the most preferred group
- val preferredChapter = preferredGroups.firstNotNullOfOrNull { preferredGroup ->
- chaptersForNumber.find { chapter ->
- chapter.groups.any { group ->
- group.lowercase() == preferredGroup.lowercase()
- }
- }
- }
-
- if (preferredChapter != null) {
- preferredFilteredChapters.add(preferredChapter)
- } else {
- // If no preferred group chapter was found, fall back to the score filter
- val fallbackChapter = chaptersForNumber.filterOnScore(preferences.chapterScoreFiltering)
- preferredFilteredChapters.addAll(fallbackChapter)
- }
- }
- preferredFilteredChapters
- }
-
- // Finally, map the filtered chapters to the SChapter model
- return finalChapters.map { it.toSChapter(mangaUrl) }
- }
-
- private fun List.filterOnScore(shouldFilter: Boolean): Collection {
- if (shouldFilter) {
- return groupBy { it.chap }
- .map { (_, chapters) -> chapters.maxBy { it.score } }
- } else {
- return this
- }
- }
-
- private val publishedDateFormat =
- SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.ENGLISH).apply {
- timeZone = TimeZone.getTimeZone("UTC")
- }
-
- override fun getChapterUrl(chapter: SChapter): String {
- return "$baseUrl${chapter.url}"
- }
-
- /** Chapter Pages **/
- override fun pageListRequest(chapter: SChapter): Request {
- val chapterHid = chapter.url.substringAfterLast("/").substringBefore("-")
- return GET("$apiUrl/chapter/$chapterHid?tachiyomi=true", headers)
- }
-
- override fun pageListParse(response: Response): List {
- val result = response.parseAs()
- val images = result.chapter.images.ifEmpty {
- // cache busting
- val url = response.request.url.newBuilder()
- .addQueryParameter("_", System.currentTimeMillis().toString())
- .build()
-
- client.newCall(GET(url, headers)).execute()
- .parseAs().chapter.images
- }
- return images.mapIndexedNotNull { index, data ->
- if (data.url == null) null else Page(index = index, imageUrl = data.url)
- }
- }
-
- private inline fun Response.parseAs(): T {
- return json.decodeFromString(body.string())
- }
-
- override fun imageUrlParse(response: Response): String {
- throw UnsupportedOperationException()
- }
-
- override fun getFilterList() = getFilters()
-
- private fun SharedPreferences.newLineIgnoredGroups() {
- if (getBoolean(MIGRATED_IGNORED_GROUPS, false)) return
-
- val ignoredGroups = getString(IGNORED_GROUPS_PREF, "").orEmpty()
-
- edit()
- .putString(
- IGNORED_GROUPS_PREF,
- ignoredGroups
- .split(",")
- .map(String::trim)
- .filter(String::isNotEmpty)
- .joinToString("\n"),
- )
- .putBoolean(MIGRATED_IGNORED_GROUPS, true)
- .apply()
- }
-
- companion object {
- const val SLUG_SEARCH_PREFIX = "id:"
- private val SPACE_AND_SLASH_REGEX = Regex("[ /]")
- private const val IGNORED_GROUPS_PREF = "IgnoredGroups"
- private const val PREFERRED_GROUPS_PREF = "PreferredGroups"
- private const val IGNORED_TAGS_PREF = "IgnoredTags"
- private const val SHOW_ALTERNATIVE_TITLES_PREF = "ShowAlternativeTitles"
- const val SHOW_ALTERNATIVE_TITLES_DEFAULT = false
- private const val INCLUDE_MU_TAGS_PREF = "IncludeMangaUpdatesTags"
- const val INCLUDE_MU_TAGS_DEFAULT = false
- private const val GROUP_TAGS_PREF = "GroupTags"
- const val GROUP_TAGS_DEFAULT = false
- private const val MIGRATED_IGNORED_GROUPS = "MigratedIgnoredGroups"
- private const val FIRST_COVER_PREF = "DefaultCover"
- private const val FIRST_COVER_DEFAULT = true
- private const val COVER_QUALITY_PREF = "CoverQuality"
- const val COVER_QUALITY_DEFAULT = "WebDefault"
- private const val SCORE_POSITION_PREF = "ScorePosition"
- const val SCORE_POSITION_DEFAULT = "top"
- private const val LOCAL_TITLE_PREF = "LocalTitle"
- private const val LOCAL_TITLE_DEFAULT = false
- private const val CHAPTER_SCORE_FILTERING_PREF = "ScoreAutoFiltering"
- private const val CHAPTER_SCORE_FILTERING_DEFAULT = false
- private const val LIMIT = 20
- private const val CHAPTERS_LIMIT = 99999
- }
-}
diff --git a/src/all/comickfun/src/eu/kanade/tachiyomi/extension/all/comickfun/ComickFactory.kt b/src/all/comickfun/src/eu/kanade/tachiyomi/extension/all/comickfun/ComickFactory.kt
deleted file mode 100644
index 79aae75d6..000000000
--- a/src/all/comickfun/src/eu/kanade/tachiyomi/extension/all/comickfun/ComickFactory.kt
+++ /dev/null
@@ -1,62 +0,0 @@
-package eu.kanade.tachiyomi.extension.all.comickfun
-
-import eu.kanade.tachiyomi.source.Source
-import eu.kanade.tachiyomi.source.SourceFactory
-
-// A legacy mapping of language codes to ensure that source IDs don't change
-val legacyLanguageMappings = mapOf(
- "pt-br" to "pt-BR", // Brazilian Portuguese
- "zh-hk" to "zh-Hant", // Traditional Chinese,
- "zh" to "zh-Hans", // Simplified Chinese
-).withDefault { it } // country code matches language code
-
-class ComickFactory : SourceFactory {
- private val idMap = listOf(
- "all" to 982606170401027267,
- "en" to 2971557565147974499,
- "pt-br" to 8729626158695297897,
- "ru" to 5846182885417171581,
- "fr" to 9126078936214680667,
- "es-419" to 3182432228546767958,
- "pl" to 7005108854993254607,
- "tr" to 7186425300860782365,
- "it" to 8807318985460553537,
- "es" to 9052019484488287695,
- "id" to 5506707690027487154,
- "hu" to 7838940669485160901,
- "vi" to 9191587139933034493,
- "zh-hk" to 3140511316190656180,
- "ar" to 8266599095155001097,
- "de" to 7552236568334706863,
- "zh" to 1071494508319622063,
- "ca" to 2159382907508433047,
- "bg" to 8981320463367739957,
- "th" to 4246541831082737053,
- "fa" to 3146252372540608964,
- "uk" to 3505068018066717349,
- "mn" to 2147260678391898600,
- "ro" to 6676949771764486043,
- "he" to 5354540502202034685,
- "ms" to 4731643595200952045,
- "tl" to 8549617092958820123,
- "ja" to 8288710818308434509,
- "hi" to 5176570178081213805,
- "my" to 9199495862098963317,
- "ko" to 3493720175703105662,
- "cs" to 2651978322082769022,
- "pt" to 4153491877797434408,
- "nl" to 6104206360977276112,
- "sv" to 979314012722687145,
- "bn" to 3598159956413889411,
- "no" to 5932005504194733317,
- "lt" to 1792260331167396074,
- "el" to 6190162673651111756,
- "sr" to 571668187470919545,
- "da" to 7137437402245830147,
- ).toMap()
- override fun createSources(): List = idMap.keys.map {
- object : Comick(legacyLanguageMappings.getValue(it), it) {
- override val id: Long = idMap[it]!!
- }
- }
-}
diff --git a/src/all/comickfun/src/eu/kanade/tachiyomi/extension/all/comickfun/ComickUrlActivity.kt b/src/all/comickfun/src/eu/kanade/tachiyomi/extension/all/comickfun/ComickUrlActivity.kt
deleted file mode 100644
index fc7d912c0..000000000
--- a/src/all/comickfun/src/eu/kanade/tachiyomi/extension/all/comickfun/ComickUrlActivity.kt
+++ /dev/null
@@ -1,34 +0,0 @@
-package eu.kanade.tachiyomi.extension.all.comickfun
-
-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 ComickUrlActivity : Activity() {
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- val pathSegments = intent?.data?.pathSegments
- if (pathSegments != null && pathSegments.size > 1) {
- val slug = pathSegments[1]
- val mainIntent = Intent().apply {
- action = "eu.kanade.tachiyomi.SEARCH"
- putExtra("query", "${Comick.SLUG_SEARCH_PREFIX}$slug")
- putExtra("filter", packageName)
- }
-
- try {
- startActivity(mainIntent)
- } catch (e: ActivityNotFoundException) {
- Log.e("ComickFunUrlActivity", e.toString())
- }
- } else {
- Log.e("ComickFunUrlActivity", "could not parse uri from intent $intent")
- }
-
- finish()
- exitProcess(0)
- }
-}
diff --git a/src/all/comickfun/src/eu/kanade/tachiyomi/extension/all/comickfun/Dto.kt b/src/all/comickfun/src/eu/kanade/tachiyomi/extension/all/comickfun/Dto.kt
deleted file mode 100644
index 01ab8946e..000000000
--- a/src/all/comickfun/src/eu/kanade/tachiyomi/extension/all/comickfun/Dto.kt
+++ /dev/null
@@ -1,239 +0,0 @@
-package eu.kanade.tachiyomi.extension.all.comickfun
-
-import eu.kanade.tachiyomi.extension.all.comickfun.Comick.Companion.GROUP_TAGS_DEFAULT
-import eu.kanade.tachiyomi.extension.all.comickfun.Comick.Companion.INCLUDE_MU_TAGS_DEFAULT
-import eu.kanade.tachiyomi.extension.all.comickfun.Comick.Companion.SCORE_POSITION_DEFAULT
-import eu.kanade.tachiyomi.extension.all.comickfun.Comick.Companion.SHOW_ALTERNATIVE_TITLES_DEFAULT
-import eu.kanade.tachiyomi.source.model.SChapter
-import eu.kanade.tachiyomi.source.model.SManga
-import kotlinx.serialization.SerialName
-import kotlinx.serialization.Serializable
-import java.math.BigDecimal
-import java.math.RoundingMode
-
-@Serializable
-class SearchManga(
- private val hid: String,
- private val title: String,
- @SerialName("md_covers") val mdCovers: List = emptyList(),
- @SerialName("cover_url") val cover: String? = null,
-) {
- fun toSManga() = SManga.create().apply {
- // appending # at end as part of migration from slug to hid
- url = "/comic/$hid#"
- title = this@SearchManga.title
- thumbnail_url = parseCover(cover, mdCovers)
- }
-}
-
-@Serializable
-class Manga(
- val comic: Comic,
- private val artists: List = emptyList(),
- private val authors: List = emptyList(),
- private val genres: List = emptyList(),
- private val demographic: String? = null,
-) {
- fun toSManga(
- includeMuTags: Boolean = INCLUDE_MU_TAGS_DEFAULT,
- scorePosition: String = SCORE_POSITION_DEFAULT,
- showAlternativeTitles: Boolean = SHOW_ALTERNATIVE_TITLES_DEFAULT,
- covers: List? = null,
- groupTags: Boolean = GROUP_TAGS_DEFAULT,
- titleLang: String,
- coverQuality: CoverQuality = CoverQuality.Compressed,
- ): SManga {
- val entryTitle = comic.altTitles.firstOrNull {
- titleLang != "all" && !it.lang.isNullOrBlank() && titleLang.startsWith(it.lang)
- }?.title ?: comic.title
- val titles = listOf(Title(title = comic.title)) + comic.altTitles
-
- return SManga.create().apply {
- // appennding # at end as part of migration from slug to hid
- url = "/comic/${comic.hid}#"
- title = entryTitle
- description = buildString {
- if (scorePosition == "top") append(comic.fancyScore)
- val desc = comic.desc?.beautifyDescription()
- if (!desc.isNullOrEmpty()) {
- if (this.isNotEmpty()) append("\n\n")
- append(desc)
- }
- if (scorePosition == "middle") {
- if (this.isNotEmpty()) append("\n\n")
- append(comic.fancyScore)
- }
- if (showAlternativeTitles && comic.altTitles.isNotEmpty()) {
- if (this.isNotEmpty()) append("\n\n")
- append("Alternative Titles:\n")
- append(
- titles.distinctBy { it.title }.filter { it.title != entryTitle }
- .mapNotNull { title ->
- title.title?.let { "• $it" }
- }.joinToString("\n"),
- )
- }
- if (scorePosition == "bottom") {
- if (this.isNotEmpty()) append("\n\n")
- append(comic.fancyScore)
- }
- }
-
- status = comic.status.parseStatus(comic.translationComplete)
- thumbnail_url = parseCover(
- comic.cover,
- covers ?: comic.mdCovers,
- coverQuality,
- )
- artist = artists.joinToString { it.name.trim() }
- author = authors.joinToString { it.name.trim() }
- genre = buildList {
- comic.origination?.let { add(Genre("Origination", it.name)) }
- demographic?.let { add(Genre("Demographic", it)) }
- addAll(
- comic.mdGenres.mapNotNull { it.genre }.sortedBy { it.group }
- .sortedBy { it.name },
- )
- addAll(genres.sortedBy { it.group }.sortedBy { it.name })
- if (includeMuTags) {
- addAll(
- comic.muGenres.categories.mapNotNull { it?.category?.title }.sorted()
- .map { Genre("Category", it) },
- )
- }
- }
- .distinctBy { it.name }
- .filterNot { it.name.isNullOrBlank() || it.group.isNullOrBlank() }
- .joinToString { if (groupTags) "${it.group}:${it.name?.trim()}" else "${it.name?.trim()}" }
- }
- }
-}
-
-@Serializable
-class Comic(
- val hid: String,
- val title: String,
- private val country: String? = null,
- val slug: String? = null,
- @SerialName("md_titles") val altTitles: List = emptyList(),
- val desc: String? = null,
- val status: Int? = 0,
- @SerialName("translation_completed") val translationComplete: Boolean? = true,
- @SerialName("md_covers") val mdCovers: List = emptyList(),
- @SerialName("cover_url") val cover: String? = null,
- @SerialName("md_comic_md_genres") val mdGenres: List,
- @SerialName("mu_comics") val muGenres: MuComicCategories = MuComicCategories(emptyList()),
- @SerialName("bayesian_rating") val score: String? = null,
- @SerialName("iso639_1") val isoLang: String? = null,
-) {
- val origination = when (country) {
- "jp" -> Name("Manga")
- "kr" -> Name("Manhwa")
- "cn" -> Name("Manhua")
- else -> null
- }
- val fancyScore: String = if (score.isNullOrEmpty()) {
- ""
- } else {
- val stars = score.toBigDecimal().div(BigDecimal(2))
- .setScale(0, RoundingMode.HALF_UP).toInt()
- buildString {
- append("★".repeat(stars))
- if (stars < 5) append("☆".repeat(5 - stars))
- append(" $score")
- }
- }
-}
-
-@Serializable
-class MdGenres(
- @SerialName("md_genres") val genre: Genre? = null,
-)
-
-@Serializable
-class Genre(
- val group: String? = null,
- val name: String? = null,
-)
-
-@Serializable
-class MuComicCategories(
- @SerialName("mu_comic_categories") val categories: List = emptyList(),
-)
-
-@Serializable
-class MuCategories(
- @SerialName("mu_categories") val category: Title? = null,
-)
-
-@Serializable
-class Covers(
- @SerialName("md_covers") val mdCovers: List = emptyList(),
-)
-
-@Serializable
-class MDcovers(
- val b2key: String?,
- val vol: String? = null,
- val locale: String? = null,
-)
-
-@Serializable
-class Title(
- val title: String?,
- val lang: String? = null,
-)
-
-@Serializable
-class Name(
- val name: String,
-)
-
-@Serializable
-class ChapterList(
- val chapters: List,
-)
-
-@Serializable
-class Chapter(
- private val hid: String,
- private val lang: String = "",
- private val title: String = "",
- @SerialName("created_at") private val createdAt: String = "",
- @SerialName("publish_at") val publishedAt: String = "",
- val chap: String = "",
- private val vol: String = "",
- @SerialName("group_name") val groups: List = emptyList(),
- @SerialName("up_count") private val upCount: Int,
- @SerialName("down_count") private val downCount: Int,
-) {
- val score get() = upCount - downCount
-
- fun toSChapter(mangaUrl: String) = SChapter.create().apply {
- url = "$mangaUrl/$hid-chapter-$chap-$lang"
- name = beautifyChapterName(vol, chap, title)
- date_upload = createdAt.parseDate()
- scanlator = groups.joinToString().takeUnless { it.isBlank() } ?: "Unknown"
- }
-}
-
-@Serializable
-class PageList(
- val chapter: ChapterPageData,
-)
-
-@Serializable
-class ChapterPageData(
- val images: List,
-)
-
-@Serializable
-class Page(
- val url: String? = null,
-)
-
-@Serializable
-class Error(
- val statusCode: Int,
- val message: String,
-)
diff --git a/src/all/comickfun/src/eu/kanade/tachiyomi/extension/all/comickfun/Filters.kt b/src/all/comickfun/src/eu/kanade/tachiyomi/extension/all/comickfun/Filters.kt
deleted file mode 100644
index 5fdbfb537..000000000
--- a/src/all/comickfun/src/eu/kanade/tachiyomi/extension/all/comickfun/Filters.kt
+++ /dev/null
@@ -1,208 +0,0 @@
-package eu.kanade.tachiyomi.extension.all.comickfun
-
-import eu.kanade.tachiyomi.source.model.Filter
-import eu.kanade.tachiyomi.source.model.FilterList
-
-fun getFilters(): FilterList {
- return FilterList(
- Filter.Header(name = "The filter is ignored when using text search."),
- GenreFilter("Genre", getGenresList),
- DemographicFilter("Demographic", getDemographicList),
- TypeFilter("Type", getTypeList),
- SortFilter(),
- StatusFilter("Status", getStatusList),
- ContentRatingFilter("Content Rating", getContentRatingList),
- CompletedFilter("Completely Scanlated?"),
- CreatedAtFilter("Created at", getCreatedAtList),
- MinimumFilter("Minimum Chapters"),
- Filter.Header("From Year, ex: 2010"),
- FromYearFilter("From"),
- Filter.Header("To Year, ex: 2021"),
- ToYearFilter("To"),
- Filter.Header("Separate tags with commas"),
- TagFilter("Tags"),
- ExcludedTagFilter("Excluded Tags"),
- )
-}
-
-/** Filters **/
-internal class GenreFilter(name: String, genreList: List>) :
- Filter.Group(name, genreList.map { TriFilter(it.first, it.second) })
-
-internal class TagFilter(name: String) : TextFilter(name)
-
-internal class ExcludedTagFilter(name: String) : TextFilter(name)
-
-internal class DemographicFilter(name: String, demographicList: List>) :
- Filter.Group(name, demographicList.map { CheckBoxFilter(it.first, it.second) })
-
-internal class TypeFilter(name: String, typeList: List>) :
- Filter.Group(name, typeList.map { CheckBoxFilter(it.first, it.second) })
-
-internal class CompletedFilter(name: String) : CheckBoxFilter(name)
-
-internal class CreatedAtFilter(name: String, createdAtList: List>) :
- SelectFilter(name, createdAtList)
-
-internal class MinimumFilter(name: String) : TextFilter(name)
-
-internal class FromYearFilter(name: String) : TextFilter(name)
-
-internal class ToYearFilter(name: String) : TextFilter(name)
-
-internal class SortFilter(defaultValue: String? = null, state: Int = 0) :
- SelectFilter("Sort", getSortsList, state, defaultValue)
-
-internal class StatusFilter(name: String, statusList: List>, state: Int = 0) :
- SelectFilter(name, statusList, state)
-
-internal class ContentRatingFilter(name: String, statusList: List>, state: Int = 0) :
- SelectFilter(name, statusList, state)
-
-/** Generics **/
-internal open class TriFilter(name: String, val value: String) : Filter.TriState(name)
-
-internal open class TextFilter(name: String) : Filter.Text(name)
-
-internal open class CheckBoxFilter(name: String, val value: String = "") : Filter.CheckBox(name)
-
-internal open class SelectFilter(name: String, private val vals: List>, state: Int = 0, defaultValue: String? = null) :
- Filter.Select(name, vals.map { it.first }.toTypedArray(), vals.indexOfFirst { it.second == defaultValue }.takeIf { it != -1 } ?: state) {
- fun getValue() = vals[state].second
-}
-
-/** Filters Data **/
-private val getGenresList: List> = listOf(
- Pair("4-Koma", "4-koma"),
- Pair("Action", "action"),
- Pair("Adaptation", "adaptation"),
- Pair("Adult", "adult"),
- Pair("Adventure", "adventure"),
- Pair("Aliens", "aliens"),
- Pair("Animals", "animals"),
- Pair("Anthology", "anthology"),
- Pair("Award Winning", "award-winning"),
- Pair("Comedy", "comedy"),
- Pair("Cooking", "cooking"),
- Pair("Crime", "crime"),
- Pair("Crossdressing", "crossdressing"),
- Pair("Delinquents", "delinquents"),
- Pair("Demons", "demons"),
- Pair("Doujinshi", "doujinshi"),
- Pair("Drama", "drama"),
- Pair("Ecchi", "ecchi"),
- Pair("Fan Colored", "fan-colored"),
- Pair("Fantasy", "fantasy"),
- Pair("Full Color", "full-color"),
- Pair("Gender Bender", "gender-bender"),
- Pair("Genderswap", "genderswap"),
- Pair("Ghosts", "ghosts"),
- Pair("Gore", "gore"),
- Pair("Gyaru", "gyaru"),
- Pair("Harem", "harem"),
- Pair("Historical", "historical"),
- Pair("Horror", "horror"),
- Pair("Incest", "incest"),
- Pair("Isekai", "isekai"),
- Pair("Loli", "loli"),
- Pair("Long Strip", "long-strip"),
- Pair("Mafia", "mafia"),
- Pair("Magic", "magic"),
- Pair("Magical Girls", "magical-girls"),
- Pair("Martial Arts", "martial-arts"),
- Pair("Mature", "mature"),
- Pair("Mecha", "mecha"),
- Pair("Medical", "medical"),
- Pair("Military", "military"),
- Pair("Monster Girls", "monster-girls"),
- Pair("Monsters", "monsters"),
- Pair("Music", "music"),
- Pair("Mystery", "mystery"),
- Pair("Ninja", "ninja"),
- Pair("Office Workers", "office-workers"),
- Pair("Official Colored", "official-colored"),
- Pair("Oneshot", "oneshot"),
- Pair("Philosophical", "philosophical"),
- Pair("Police", "police"),
- Pair("Post-Apocalyptic", "post-apocalyptic"),
- Pair("Psychological", "psychological"),
- Pair("Reincarnation", "reincarnation"),
- Pair("Reverse Harem", "reverse-harem"),
- Pair("Romance", "romance"),
- Pair("Samurai", "samurai"),
- Pair("School Life", "school-life"),
- Pair("Sci-Fi", "sci-fi"),
- Pair("Sexual Violence", "sexual-violence"),
- Pair("Shota", "shota"),
- Pair("Shoujo Ai", "shoujo-ai"),
- Pair("Shounen Ai", "shounen-ai"),
- Pair("Slice of Life", "slice-of-life"),
- Pair("Smut", "smut"),
- Pair("Sports", "sports"),
- Pair("Superhero", "superhero"),
- Pair("Supernatural", "supernatural"),
- Pair("Survival", "survival"),
- Pair("Thriller", "thriller"),
- Pair("Time Travel", "time-travel"),
- Pair("Traditional Games", "traditional-games"),
- Pair("Tragedy", "tragedy"),
- Pair("User Created", "user-created"),
- Pair("Vampires", "vampires"),
- Pair("Video Games", "video-games"),
- Pair("Villainess", "villainess"),
- Pair("Virtual Reality", "virtual-reality"),
- Pair("Web Comic", "web-comic"),
- Pair("Wuxia", "wuxia"),
- Pair("Yaoi", "yaoi"),
- Pair("Yuri", "yuri"),
- Pair("Zombies", "zombies"),
-)
-
-private val getDemographicList: List> = listOf(
- Pair("Shounen", "1"),
- Pair("Shoujo", "2"),
- Pair("Seinen", "3"),
- Pair("Josei", "4"),
- Pair("None", "5"),
-)
-
-private val getTypeList: List> = listOf(
- Pair("Manga", "jp"),
- Pair("Manhwa", "kr"),
- Pair("Manhua", "cn"),
- Pair("Others", "others"),
-)
-
-private val getCreatedAtList: List> = listOf(
- Pair("", ""),
- Pair("3 days", "3"),
- Pair("7 days", "7"),
- Pair("30 days", "30"),
- Pair("3 months", "90"),
- Pair("6 months", "180"),
- Pair("1 year", "365"),
-)
-
-private val getSortsList: List> = listOf(
- Pair("Most popular", "follow"),
- Pair("Most follows", "user_follow_count"),
- Pair("Most views", "view"),
- Pair("High rating", "rating"),
- Pair("Last updated", "uploaded"),
- Pair("Newest", "created_at"),
-)
-
-private val getStatusList: List> = listOf(
- Pair("All", "0"),
- Pair("Ongoing", "1"),
- Pair("Completed", "2"),
- Pair("Cancelled", "3"),
- Pair("Hiatus", "4"),
-)
-
-private val getContentRatingList: List> = listOf(
- Pair("All", ""),
- Pair("Safe", "safe"),
- Pair("Suggestive", "suggestive"),
- Pair("Erotica", "erotica"),
-)
diff --git a/src/all/comickfun/src/eu/kanade/tachiyomi/extension/all/comickfun/Helpers.kt b/src/all/comickfun/src/eu/kanade/tachiyomi/extension/all/comickfun/Helpers.kt
deleted file mode 100644
index 1d55b2f94..000000000
--- a/src/all/comickfun/src/eu/kanade/tachiyomi/extension/all/comickfun/Helpers.kt
+++ /dev/null
@@ -1,85 +0,0 @@
-package eu.kanade.tachiyomi.extension.all.comickfun
-
-import eu.kanade.tachiyomi.source.model.SManga
-import org.jsoup.parser.Parser
-import java.text.SimpleDateFormat
-import java.util.Locale
-import java.util.TimeZone
-
-private val dateFormat by lazy {
- SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX", Locale.ENGLISH).apply {
- timeZone = TimeZone.getTimeZone("UTC")
- }
-}
-private val markdownLinksRegex = "\\[([^]]+)]\\(([^)]+)\\)".toRegex()
-private val markdownItalicBoldRegex = "\\*+\\s*([^*]*)\\s*\\*+".toRegex()
-private val markdownItalicRegex = "_+\\s*([^_]*)\\s*_+".toRegex()
-
-internal fun String.beautifyDescription(): String {
- return Parser.unescapeEntities(this, false)
- .substringBefore("---")
- .replace(markdownLinksRegex, "")
- .replace(markdownItalicBoldRegex, "")
- .replace(markdownItalicRegex, "")
- .trim()
-}
-
-internal fun Int?.parseStatus(translationComplete: Boolean?): Int {
- return when (this) {
- 1 -> SManga.ONGOING
- 2 -> {
- if (translationComplete == true) {
- SManga.COMPLETED
- } else {
- SManga.PUBLISHING_FINISHED
- }
- }
- 3 -> SManga.CANCELLED
- 4 -> SManga.ON_HIATUS
- else -> SManga.UNKNOWN
- }
-}
-enum class CoverQuality {
- Original, // HQ original
- Compressed, // HQ but compressed
- WebDefault, // what comick serves in browser, usually compressed + downscaled
-}
-
-internal fun parseCover(thumbnailUrl: String?, mdCovers: List, coverQuality: CoverQuality = CoverQuality.WebDefault): String? {
- fun addOrReplaceCoverQualitySuffix(url: String, qualitySuffix: String): String {
- return url.substringBeforeLast('#').substringBeforeLast('.').replace(Regex("-(m|s)$"), "") +
- "$qualitySuffix.jpg#${url.substringAfter('#', "")}"
- }
-
- val mdCover = mdCovers.firstOrNull()
- val coverUrl = if (mdCover != null) {
- thumbnailUrl?.replaceAfterLast("/", "${mdCover.b2key}#${mdCover.vol.orEmpty()}")
- } else {
- thumbnailUrl
- } ?: return null
-
- return when (coverQuality) {
- CoverQuality.Original -> coverUrl
- CoverQuality.Compressed -> addOrReplaceCoverQualitySuffix(coverUrl, "-m")
- CoverQuality.WebDefault -> addOrReplaceCoverQualitySuffix(coverUrl, "-s")
- }
-}
-
-internal fun beautifyChapterName(vol: String, chap: String, title: String): String {
- return buildString {
- if (vol.isNotEmpty()) {
- if (chap.isEmpty()) append("Volume $vol") else append("Vol. $vol")
- }
- if (chap.isNotEmpty()) {
- if (vol.isEmpty()) append("Chapter $chap") else append(", Ch. $chap")
- }
- if (title.isNotEmpty()) {
- if (chap.isEmpty()) append(title) else append(": $title")
- }
- }
-}
-
-internal fun String.parseDate(): Long {
- return runCatching { dateFormat.parse(this)?.time }
- .getOrNull() ?: 0L
-}