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<MDcovers> = emptyList(), - @SerialName("cover_url") val cover: String? = null, - @SerialName("md_comic_md_genres") val mdGenres: List<MdGenres>, - @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<MuCategories?> = emptyList(), -) - -@Serializable -class MuCategories( - @SerialName("mu_categories") val category: Title? = null, -) - -@Serializable -class Covers( - @SerialName("md_covers") val mdCovers: List<MDcovers> = 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<Chapter>, -) - -@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<String> = 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<Page>, -) - -@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<Pair<String, String>>) : - Filter.Group<TriFilter>(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<Pair<String, String>>) : - Filter.Group<CheckBoxFilter>(name, demographicList.map { CheckBoxFilter(it.first, it.second) }) - -internal class TypeFilter(name: String, typeList: List<Pair<String, String>>) : - Filter.Group<CheckBoxFilter>(name, typeList.map { CheckBoxFilter(it.first, it.second) }) - -internal class CompletedFilter(name: String) : CheckBoxFilter(name) - -internal class CreatedAtFilter(name: String, createdAtList: List<Pair<String, String>>) : - 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<Pair<String, String>>, state: Int = 0) : - SelectFilter(name, statusList, state) - -internal class ContentRatingFilter(name: String, statusList: List<Pair<String, String>>, 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<Pair<String, String>>, state: Int = 0, defaultValue: String? = null) : - Filter.Select<String>(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<Pair<String, String>> = 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<Pair<String, String>> = listOf( - Pair("Shounen", "1"), - Pair("Shoujo", "2"), - Pair("Seinen", "3"), - Pair("Josei", "4"), - Pair("None", "5"), -) - -private val getTypeList: List<Pair<String, String>> = listOf( - Pair("Manga", "jp"), - Pair("Manhwa", "kr"), - Pair("Manhua", "cn"), - Pair("Others", "others"), -) - -private val getCreatedAtList: List<Pair<String, String>> = 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<Pair<String, String>> = 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<Pair<String, String>> = listOf( - Pair("All", "0"), - Pair("Ongoing", "1"), - Pair("Completed", "2"), - Pair("Cancelled", "3"), - Pair("Hiatus", "4"), -) - -private val getContentRatingList: List<Pair<String, String>> = 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<MDcovers>, 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 -}