From 7bf99f4bd0801613b3feb18030beccd00da1edb2 Mon Sep 17 00:00:00 2001 From: EgoMaw Date: Sun, 16 Nov 2025 10:04:29 +0200 Subject: [PATCH] Add Comix (#11658) * Comix init * Add genre, author and artist parsing * add search and filters * hardcode genres and themes * Add the ability to retrieve chapter pages (#1) Co-authored-by: EgoMaw * Add a pref for deduplicating chapters, cleanup code * fix api path * apparently sometimes the synopsis can be null, or an empty string * changes according to feedback * fix pagination, and dont call func inside map when not needed * Fix chapter list parsing ignoring first page and remove unused fields from DTOs * dont use custom Json instance --------- Co-authored-by: Hiirbaf <42479509+Hiirbaf@users.noreply.github.com> --- src/en/comix/build.gradle | 8 + src/en/comix/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 6176 bytes src/en/comix/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 3334 bytes src/en/comix/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 8140 bytes .../comix/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 15952 bytes .../comix/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 24611 bytes .../tachiyomi/extension/en/comix/Comix.kt | 288 ++++++++++++++++++ .../tachiyomi/extension/en/comix/ComixDto.kt | 177 +++++++++++ .../extension/en/comix/ComixFilters.kt | 265 ++++++++++++++++ 9 files changed, 738 insertions(+) create mode 100644 src/en/comix/build.gradle create mode 100644 src/en/comix/res/mipmap-hdpi/ic_launcher.png create mode 100644 src/en/comix/res/mipmap-mdpi/ic_launcher.png create mode 100644 src/en/comix/res/mipmap-xhdpi/ic_launcher.png create mode 100644 src/en/comix/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 src/en/comix/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 src/en/comix/src/eu/kanade/tachiyomi/extension/en/comix/Comix.kt create mode 100644 src/en/comix/src/eu/kanade/tachiyomi/extension/en/comix/ComixDto.kt create mode 100644 src/en/comix/src/eu/kanade/tachiyomi/extension/en/comix/ComixFilters.kt diff --git a/src/en/comix/build.gradle b/src/en/comix/build.gradle new file mode 100644 index 000000000..73ba4a18c --- /dev/null +++ b/src/en/comix/build.gradle @@ -0,0 +1,8 @@ +ext { + extName = 'Comix' + extClass = '.Comix' + extVersionCode = 1 + isNsfw = true +} + +apply from: "$rootDir/common.gradle" diff --git a/src/en/comix/res/mipmap-hdpi/ic_launcher.png b/src/en/comix/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..da3ddb74675a38a223ed6b09a91751c0611a7e58 GIT binary patch literal 6176 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 literal 0 HcmV?d00001 diff --git a/src/en/comix/res/mipmap-mdpi/ic_launcher.png b/src/en/comix/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..b0073354f874a69ef95f1778c3e8ad9a06ca7142 GIT binary patch literal 3334 zcmV+h4f*nkP)lAwQv{Ygs;gKq&!@nVH$*w6wHQ85tSYnVFf$%F2QgW!Y~5`4RTNm6rBN zHnz#h$zuu%3KmiVyc&p<6ypR`)Mb{Nn>(PmxR_X7k^CQ}Ixl~$md~U?97rC_2pl63 zT`^98rIoZ=tqn5_=|EDJ{U;#(8&ml#ngMI#D-p^Rr34Hz=}cOvR4P=lw1l0#1FG8F z3u;%6I_TvJ)Il%PQ+oq-guR0!tgWq~Qk4v_+KdNWK3z~lY@$U~IRVMQfiswAsMTup z9WW4E_I;0I5m7jFI)ZQ>ht4oA;QOBgpF%fWB!aOC@d_5UA>0TRH=gej2FTS#~~PnN--KXV99E@3$Ct&1| z4>9nA9_ZeoJ$zaD2zOsqD zygZyaaU5GWZotaW5G0Ld#RPaH89mIvv$L}iILIGU#*fCbrHim}?P~1Zu??5bM~Fb~--|=~^XKBs z&}#EVKy1it0s5GVqJ7Y#=qQ;v8hQY$I z8nl@iqTAZ77JOEHhl|rcgO(0W(C@>cAHmGr9F3YZgNaGSVJi0UsA)7Bu~in6Ge&Gw zBym8}D8VwTz(o&NJUyz%N)1!yr`Mo=$ zo$)XM`?|NsE}v#N_vr`}6%?S}(2w!fkdH;b48C@^-so{C0(WVBC!9GZ`l{5ZiO%00 zK-~TvNH}>!B*Yyml?u+Z#F%IBU@}#LVgme7hBPS9YLS+jYIs2I(=?n2=!cxt6z~W( z9y19qe!h>meLD~}Z;CMUnmQNme%(;X${KA$*Pvb4I>auSgZS_FihgrDd-!eIi*y?G zDNB1{-H0zco6m!b-(Vgkn6bPvIc^mHMXiDmrqGfs@~M*+Ruu0DS7nzlgn z{}jF!*Oq?)6SW3ymaat4!xvDe`v-`cGYPl1ek*h;y=H@s-|mK**7(_R!%+8xjZ)|Dj;{ zm5>Fvy<;=&GL~Y7qJyX7pBq+*KAsW3jeB5I*8`_V4npqJl9?s9WlDB-7M9MRgVYq= z@-5S;Zzv|9?*k}!<`aKO0KgoH=$6Vs>RP~qqX!YaV47&Qn!N;0t=pl9PScB1$0G6k z8QfgI5;wm2N(66O#S*^X?t)|E=CtFCN6L>^gq^IU--@WzD@_#>UG30jh#$m|0?EhFGD$dfl$MPZI?~H&Zs&l)yj<{XXv{6(yKWn*dwC=2 zN(>@rPn726sPtz{%PHywiV3hWL$8BJj~%9MF-7CcuUa)-IrfDK#ZUJW$G5&OC#8 z7urS}jR`{9Pd9LE@COt;UxQU>7&wlW&UW!`q{rW-v*RBq%+Evpp`W0^N25{tCw&>y z(mG1KVgjWVb8@ojwwPuR@dFrAVC7Z|Z9`UzgN%dEeMY(}6`D?%4zDS5QL}9acrW-; zET2ag&(iMmso-18SP1vdJ%pRm{)FInREi0(LjiL1q}2Ln8Ho%fl7{TYtX z63Kd;2)@4ZMX&MbAmq?BIX^Q49{q=k5oh~B6cZ4N`ZM4modKmCGB3d+Tj@0$THoElcOtg)o)G6cb=4 z#bTm?1p33lhC)V%81sy`1`S7zR&8)(00mF?AeOaW6oxvz`{UB=AbJ;iBRlCa;t%b? z<&gQ{9g3G)^%mZ&inHT~BPli#7JMfP-wX4q_J$-_pdDo#KjY|@j* zWgK%Nj!nJLaKtzYz6a9d<3y@$K3@sD`i-#vy|%cyZn?pR^{<945w}ncFS%8l>9lSy z)_)cy$dTF9`5QW{-6rlv+;(dosg(P+?Y!VKKrz8So(3vzRWD%%T&D#;g8A78;!f4qvsh*P+> z;tQM^7l6H;TVqo_XMET6b#YrYrE_C&KpAkJC*$&NCtP0@ipP-=!i0lmhz3>0e0K5^?D1=bLw!3Vdfrsr*&dE$x>^)v zXTpv?ay9j8i4_|*qhaGFWUVVFj~>=B71W9e@PirKHg7~SCBU|BzFm=c;VhoSToMVH zn3?0jkpswk{!Fx4pY<5bGni*EuU4yJV_OyV8Z<69k8ZVJm&nI~DZ<&cs~BwgX47c-e9c|6~k&J9WVuZ@vW^8(TU) zRMLf@xj8wQJZ_Zu(jf#DREi03P!c@SDvc?g#9k6ESZ;1%0ay20Xx+9w`VSg{>2nug z!?xY@i7*zYqpx81k&_5rwH9N8rl4z&Ua0Tc2zK;6_1FH}3g`Oj-rYMG=HCaA7j#Qg zFIFUq2^eegZo`3_npT2umu?s^Bmmud_D17o%|&48^mp@zm?-SuwL^R&T(dk3jBkh< z@bxl$^Ad#7_ol&pd!eVF53c=i**FZvwqgPttV99bg`{OZUBr$ZIv@i1>Wd`^`+Ttg zf9R4tQ*JXjAjUHKY{>1GVKpVlpKl830x6&QGOCn7F&T-CS0+)KQlFtnxfHytE%p8& zO(#2!jYvsRN}zxO*-t3Z(sL{;4ZXPBF2r)yWBMoJ0Qo8)lqtpuaE5tw`pu&Nwh%H2 ziUG10*~h$ukkQF4+kXPhBp=~~IfOg{$4Eq1j1ypKK2?eI7cq$7MQBLyR00L-X$-RU z{1efTe0dS35FQgaHX{&*m677LJKyqfknUQUct|ioD*~ zD2oKI5*!1^!ZA@OWlKc~l-2q#ZvEvG_%{Fm0RR7}r2^*w000I_L_t&o0Lr3g$|Pet Q%m4rY07*qoM6N<$g6Wk?XaE2J literal 0 HcmV?d00001 diff --git a/src/en/comix/res/mipmap-xhdpi/ic_launcher.png b/src/en/comix/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..307d053f2a00b4b6835f9c0be4b01a93b9bc08d8 GIT binary patch literal 8140 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 Np)e@$m{||w-9)JJ< literal 0 HcmV?d00001 diff --git a/src/en/comix/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/comix/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..cb50749de93bcce919079dcdf9469de947674358 GIT binary patch literal 24611 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 F{{dA-qQ?LL literal 0 HcmV?d00001 diff --git a/src/en/comix/src/eu/kanade/tachiyomi/extension/en/comix/Comix.kt b/src/en/comix/src/eu/kanade/tachiyomi/extension/en/comix/Comix.kt new file mode 100644 index 000000000..993a3b513 --- /dev/null +++ b/src/en/comix/src/eu/kanade/tachiyomi/extension/en/comix/Comix.kt @@ -0,0 +1,288 @@ +package eu.kanade.tachiyomi.extension.en.comix + +import android.content.SharedPreferences +import androidx.preference.ListPreference +import androidx.preference.PreferenceScreen +import androidx.preference.SwitchPreferenceCompat +import eu.kanade.tachiyomi.network.GET +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.getPreferences +import keiyoushi.utils.parseAs +import okhttp3.HttpUrl.Companion.toHttpUrl +import okhttp3.Request +import okhttp3.Response + +class Comix : HttpSource(), ConfigurableSource { + + override val name = "Comix" + override val baseUrl = "https://comix.to" + private val apiUrl = "https://comix.to/api/v2/" + override val lang = "en" + override val supportsLatest = true + + private val preferences: SharedPreferences = getPreferences() + override val client = network.cloudflareClient.newBuilder() + .rateLimit(5) + .build() + + override fun headersBuilder() = super.headersBuilder().add("Referer", "$baseUrl/") + + override fun imageUrlParse(response: Response) = + throw UnsupportedOperationException() + + /******************************* POPULAR MANGA ************************************/ + override fun popularMangaRequest(page: Int): Request { + val url = apiUrl.toHttpUrl().newBuilder() + .addPathSegment("manga") + .addQueryParameter("order[views_30d]", "desc") + .addQueryParameter("limit", "50") + .addQueryParameter("page", page.toString()) + .build() + + return GET(url, headers) + } + + override fun popularMangaParse(response: Response) = + searchMangaParse(response) + + /******************************* LATEST MANGA ************************************/ + override fun latestUpdatesRequest(page: Int): Request { + val url = apiUrl.toHttpUrl().newBuilder() + .addPathSegment("manga") + .addQueryParameter("order[chapter_updated_at]", "desc") + .addQueryParameter("limit", "50") + .addQueryParameter("page", page.toString()) + .build() + + return GET(url, headers) + } + + override fun latestUpdatesParse(response: Response) = + searchMangaParse(response) + + /******************************* SEARCHING ***************************************/ + override fun getFilterList() = ComixFilters().getFilterList() + + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { + val url = apiUrl.toHttpUrl().newBuilder() + .addPathSegment("manga") + + filters.filterIsInstance() + .forEach { it.addToUri(url) } + + // Make searches accurate + if (query.isNotBlank()) { + url.addQueryParameter("keyword", query) + url.removeAllQueryParameters("order[views_30d]") + url.setQueryParameter("order[relevance]", "desc") + } + + url.addQueryParameter("limit", "50") + .addQueryParameter("page", page.toString()) + + return GET(url.build(), headers) + } + + override fun searchMangaParse(response: Response): MangasPage { + val res: SearchResponse = response.parseAs() + val posterQuality = preferences.posterQuality() + + val manga = + res.result.items.map { manga -> manga.toBasicSManga(posterQuality) } + return MangasPage(manga, res.result.pagination.page < res.result.pagination.lastPage) + } + + /******************************* MANGA DETAILS ***************************************/ + override fun mangaDetailsRequest(manga: SManga): Request { + val url = apiUrl.toHttpUrl().newBuilder() + .addPathSegment("manga") + .addPathSegment(manga.url) + .addQueryParameter("includes[]", "demographic") + .addQueryParameter("includes[]", "genre") + .addQueryParameter("includes[]", "theme") + .addQueryParameter("includes[]", "author") + .addQueryParameter("includes[]", "artist") + .addQueryParameter("includes[]", "publisher") + .build() + + return GET(url, headers) + } + + override fun mangaDetailsParse(response: Response): SManga { + val mangaResponse: SingleMangaResponse = response.parseAs() + + return mangaResponse.result.toSManga( + preferences.posterQuality(), + preferences.alternativeNamesInDescription(), + ) + } + + override fun getMangaUrl(manga: SManga): String = + "$baseUrl/title${manga.url}" + + /******************************* Chapters List *******************************/ + override fun getChapterUrl(chapter: SChapter) = + "$baseUrl/${chapter.url}" + + override fun chapterListRequest(manga: SManga): Request { + return chapterListRequest(manga.url.removePrefix("/"), 1) + } + + private fun chapterListRequest(mangaHash: String, page: Int): Request { + val url = apiUrl.toHttpUrl().newBuilder() + .addPathSegment("manga") + .addPathSegment(mangaHash) + .addPathSegment("chapters") + .addQueryParameter("order[number]", "desc") + .addQueryParameter("limit", "100") + .addQueryParameter("page", page.toString()) + .build() + + return GET(url, headers) + } + + override fun chapterListParse(response: Response): List { + val deduplicate = preferences.deduplicateChapters() + val mangaHash = response.request.url.pathSegments[3] + var resp: ChapterDetailsResponse = response.parseAs() + + // When deduplication is enabled store only the best chapter per number. + var chapterMap: LinkedHashMap? = null + // When disabled just accumulate all. + var chapterList: ArrayList? = null + + if (deduplicate) { + chapterMap = LinkedHashMap() + deduplicateChapters(chapterMap, resp.result.items) + } else { + chapterList = ArrayList(resp.result.items) + } + + var page = 2 + var hasNext: Boolean + + do { + resp = client + .newCall(chapterListRequest(mangaHash, page++)) + .execute() + .parseAs() + + val items = resp.result.items + hasNext = resp.result.pagination.lastPage > resp.result.pagination.page + + if (deduplicate) { + deduplicateChapters(chapterMap!!, items) + } else { + chapterList!!.addAll(items) + } + } while (hasNext) + + val finalChapters: List = + if (deduplicate) { + chapterMap!!.values.toList() + } else { + chapterList!! + } + + return finalChapters.map { it.toSChapter(mangaHash) } + } + + private fun deduplicateChapters( + chapterMap: LinkedHashMap, + items: List, + ) { + for (ch in items) { + val key = ch.number + val current = chapterMap[key] + if (current == null) { + chapterMap[key] = ch + } else { + // Prefer official scan group + val officialNew = ch.scanlationGroupId == 9275 + val officialCurrent = current.scanlationGroupId == 9275 + val better = when { + officialNew && !officialCurrent -> true + !officialNew && officialCurrent -> false + // compare votes then updatedAt + else -> when { + ch.votes > current.votes -> true + ch.votes < current.votes -> false + else -> ch.updatedAt > current.updatedAt + } + } + if (better) chapterMap[key] = ch + } + } + } + + /******************************* Page List (Reader) ************************************/ + override fun pageListRequest(chapter: SChapter): Request { + val chapterId = chapter.url.substringAfterLast("/") + val url = "${apiUrl}chapters/$chapterId" + return GET(url, headers) + } + + override fun pageListParse(response: Response): List { + val res: ChapterResponse = response.parseAs() + val result = res.result ?: throw Exception("Chapter not found") + + if (result.images.isEmpty()) { + throw Exception("No images found for chapter ${result.chapterId}") + } + + return result.images.mapIndexed { index, url -> + Page(index, imageUrl = url) + } + } + + /******************************* PREFERENCES ************************************/ + override fun setupPreferenceScreen(screen: PreferenceScreen) { + ListPreference(screen.context).apply { + key = PREF_POSTER_QUALITY + title = "Thumbnail Quality" + summary = "Change the quality of the thumbnail. Current: %s." + entryValues = arrayOf("small", "medium", "large") + entries = arrayOf("Small", "Medium", "Large") + setDefaultValue("large") + }.let(screen::addPreference) + + SwitchPreferenceCompat(screen.context).apply { + key = DEDUPLICATE_CHAPTERS + title = "Deduplicate Chapters" + summary = "Remove duplicate chapters from the chapter list.\n" + + "Official chapters (Comix-marked) are preferred, followed by the highest-voted or most recent.\n" + + "Warning: It can be slow on large lists." + + setDefaultValue(false) + }.let(screen::addPreference) + + SwitchPreferenceCompat(screen.context).apply { + key = ALTERNATIVE_NAMES_IN_DESCRIPTION + title = "Show Alternative Names in Description" + + setDefaultValue(false) + }.let(screen::addPreference) + } + + private fun SharedPreferences.posterQuality() = + getString(PREF_POSTER_QUALITY, "large") + + private fun SharedPreferences.deduplicateChapters() = + getBoolean(DEDUPLICATE_CHAPTERS, false) + + private fun SharedPreferences.alternativeNamesInDescription() = + getBoolean(ALTERNATIVE_NAMES_IN_DESCRIPTION, false) + + companion object { + private const val PREF_POSTER_QUALITY = "pref_poster_quality" + private const val DEDUPLICATE_CHAPTERS = "pref_deduplicate_chapters" + private const val ALTERNATIVE_NAMES_IN_DESCRIPTION = "pref_alt_names_in_description" + } +} diff --git a/src/en/comix/src/eu/kanade/tachiyomi/extension/en/comix/ComixDto.kt b/src/en/comix/src/eu/kanade/tachiyomi/extension/en/comix/ComixDto.kt new file mode 100644 index 000000000..f16008170 --- /dev/null +++ b/src/en/comix/src/eu/kanade/tachiyomi/extension/en/comix/ComixDto.kt @@ -0,0 +1,177 @@ +package eu.kanade.tachiyomi.extension.en.comix + +import eu.kanade.tachiyomi.source.model.SChapter +import eu.kanade.tachiyomi.source.model.SManga +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class Term( + @SerialName("term_id") + private val termId: Int, + private val type: String, + val title: String, + private val slug: String, + private val count: Int?, +) + +@Serializable +class Manga( + @SerialName("hash_id") + private val hashId: String, + private val title: String, + @SerialName("alt_titles") + private val altTitles: List, + private val synopsis: String?, + private val type: String, + private val poster: Poster, + private val status: String, + @SerialName("is_nsfw") + private val isNsfw: Boolean, + private val author: List?, + private val artist: List?, + private val genre: List?, + private val theme: List?, + private val demographic: List?, +) { + @Serializable + class Poster( + private val small: String, + private val medium: String, + private val large: String, + ) { + fun from(quality: String?) = when (quality) { + "large" -> large + "small" -> small + else -> medium + } + } + + fun toSManga( + posterQuality: String?, + altTitlesInDesc: Boolean = false, + ) = SManga.create().apply { + url = "/$hashId" + title = this@Manga.title + author = this@Manga.author.takeUnless { it.isNullOrEmpty() }?.joinToString { it.title } + artist = this@Manga.artist.takeUnless { it.isNullOrEmpty() }?.joinToString { it.title } + description = buildString { + synopsis.takeUnless { it.isNullOrEmpty() } + ?.let { append(it) } + altTitles.takeIf { altTitlesInDesc && it.isNotEmpty() } + ?.let { altName -> + append("\n\n") + append("Alternative Names:\n") + append(altName.joinToString("\n")) + } + } + initialized = true + status = when (this@Manga.status) { + "releasing" -> SManga.ONGOING + "on_hiatus" -> SManga.ON_HIATUS + "finished" -> SManga.COMPLETED + "discontinued" -> SManga.CANCELLED + else -> SManga.UNKNOWN + } + thumbnail_url = this@Manga.poster.from(posterQuality) + genre = getGenres() + } + + fun toBasicSManga(posterQuality: String?) = SManga.create().apply { + url = "/$hashId" + title = this@Manga.title + thumbnail_url = this@Manga.poster.from(posterQuality) + } + + fun getGenres() = buildList { + when (type) { + "manhwa" -> add("Manhwa") + "manhua" -> add("Manhua") + "manga" -> add("Manga") + else -> add("Other") + } + genre.takeUnless { it.isNullOrEmpty() }?.map { it.title } + .let { addAll(it ?: emptyList()) } + theme.takeUnless { it.isNullOrEmpty() }?.map { it.title } + .let { addAll(it ?: emptyList()) } + demographic.takeUnless { it.isNullOrEmpty() }?.map { it.title } + .let { addAll(it ?: emptyList()) } + if (isNsfw) add("NSFW") + }.distinct().joinToString() +} + +@Serializable +class SingleMangaResponse( + val result: Manga, +) + +@Serializable +class Pagination( + @SerialName("current_page") val page: Int, + @SerialName("last_page") val lastPage: Int, +) + +@Serializable +class SearchResponse( + val result: Items, +) { + @Serializable + class Items( + val items: List, + val pagination: Pagination, + ) +} + +@Serializable +class ChapterDetailsResponse( + val result: Items, +) { + @Serializable + class Items( + val items: List, + val pagination: Pagination, + ) +} + +@Serializable +class Chapter( + @SerialName("chapter_id") + private val chapterId: Int, + @SerialName("scanlation_group_id") val scanlationGroupId: Int, + val number: Double, + private val name: String, + val votes: Int, + @SerialName("updated_at") + val updatedAt: Long, + @SerialName("scanlation_group") + private val scanlationGroup: ScanlationGroup?, +) { + @Serializable + class ScanlationGroup( + val name: String, + ) + + fun toSChapter(mangaId: String) = SChapter.create().apply { + url = "title/$mangaId/$chapterId" + name = buildString { + append("Chapter ") + append(this@Chapter.number.toString().removeSuffix(".0")) + this@Chapter.name.takeUnless { it.isEmpty() }?.let { append(": $it") } + } + date_upload = this@Chapter.updatedAt * 1000 + chapter_number = this@Chapter.number.toFloat() + scanlator = this@Chapter.scanlationGroup?.name ?: "Unknown" + } +} + +@Serializable +class ChapterResponse( + val result: Items?, +) { + @Serializable + class Items( + @SerialName("chapter_id") + val chapterId: Int, + val images: List, + ) +} diff --git a/src/en/comix/src/eu/kanade/tachiyomi/extension/en/comix/ComixFilters.kt b/src/en/comix/src/eu/kanade/tachiyomi/extension/en/comix/ComixFilters.kt new file mode 100644 index 000000000..96117a0dc --- /dev/null +++ b/src/en/comix/src/eu/kanade/tachiyomi/extension/en/comix/ComixFilters.kt @@ -0,0 +1,265 @@ +package eu.kanade.tachiyomi.extension.en.comix + +import eu.kanade.tachiyomi.source.model.Filter +import eu.kanade.tachiyomi.source.model.FilterList +import okhttp3.HttpUrl +import java.util.Calendar + +class ComixFilters { + interface UriFilter { + fun addToUri(builder: HttpUrl.Builder) + } + + companion object { + private val currentYear by lazy { + Calendar.getInstance()[Calendar.YEAR] + } + + private fun getYearsArray(includeOlder: Boolean): Array> { + val years = (currentYear downTo 1990).map { it.toString() to it.toString() } + return if (includeOlder) { + (years + ("Older" to "older")).toTypedArray() + } else { + years.toTypedArray() + } + } + + fun getGenres() = arrayOf( + Pair("Action", "6"), + Pair("Adult", "87264"), + Pair("Adventure", "7"), + Pair("Boys Love", "8"), + Pair("Comedy", "9"), + Pair("Crime", "10"), + Pair("Drama", "11"), + Pair("Ecchi", "87265"), + Pair("Fantasy", "12"), + Pair("Girls Love", "13"), + Pair("Hentai", "87266"), + Pair("Historical", "14"), + Pair("Horror", "15"), + Pair("Isekai", "16"), + Pair("Magical Girls", "17"), + Pair("Mature", "87267"), + Pair("Mecha", "18"), + Pair("Medical", "19"), + Pair("Mystery", "20"), + Pair("Philosophical", "21"), + Pair("Psychological", "22"), + Pair("Romance", "23"), + Pair("Sci-Fi", "24"), + Pair("Slice of Life", "25"), + Pair("Smut", "87268"), + Pair("Sports", "26"), + Pair("Superhero", "27"), + Pair("Thriller", "28"), + Pair("Tragedy", "29"), + Pair("Wuxia", "30"), + Pair("Aliens", "31"), + Pair("Animals", "32"), + Pair("Cooking", "33"), + Pair("Cross Dressing", "34"), + Pair("Delinquents", "35"), + Pair("Demons", "36"), + Pair("Genderswap", "37"), + Pair("Ghosts", "38"), + Pair("Gyaru", "39"), + Pair("Harem", "40"), + Pair("Incest", "41"), + Pair("Loli", "42"), + Pair("Mafia", "43"), + Pair("Magic", "44"), + Pair("Martial Arts", "45"), + Pair("Military", "46"), + Pair("Monster Girls", "47"), + Pair("Monsters", "48"), + Pair("Music", "49"), + Pair("Ninja", "50"), + Pair("Office Workers", "51"), + Pair("Police", "52"), + Pair("Post-Apocalyptic", "53"), + Pair("Reincarnation", "54"), + Pair("Reverse Harem", "55"), + Pair("Samurai", "56"), + Pair("School Life", "57"), + Pair("Shota", "58"), + Pair("Supernatural", "59"), + Pair("Survival", "60"), + Pair("Time Travel", "61"), + Pair("Traditional Games", "62"), + Pair("Vampires", "63"), + Pair("Video Games", "64"), + Pair("Villainess", "65"), + Pair("Virtual Reality", "66"), + Pair("Zombies", "67"), + ) + + fun getDemographics() = arrayOf( + Pair("Shoujo", "1"), + Pair("Shounen", "2"), + Pair("Josei", "3"), + Pair("Seinen", "4"), + ) + } + + fun getFilterList() = FilterList( + SortFilter(getSortables()), + StatusFilter(), + MinChapterFilter(), + GenreFilter(getGenres()), + TypeFilter(), + DemographicFilter(getDemographics()), + Filter.Separator(), + Filter.Header("Release Year"), + YearFromFilter(), + YearToFilter(), + ) + + private open class UriPartFilter( + name: String, + private val param: String, + private val vals: Array>, + defaultValue: String? = null, + ) : Filter.Select( + name, + vals.map { it.first }.toTypedArray(), + vals.indexOfFirst { it.second == defaultValue }.takeIf { it != -1 } ?: 0, + ), + UriFilter { + override fun addToUri(builder: HttpUrl.Builder) { + builder.addQueryParameter(param, vals[state].second) + } + } + + private open class UriMultiSelectOption(name: String, val value: String) : Filter.CheckBox(name) + + private open class UriMultiSelectFilter( + name: String, + private val param: String, + private val vals: Array>, + ) : Filter.Group( + name, + vals.map { UriMultiSelectOption(it.first, it.second) }, + ), + UriFilter { + override fun addToUri(builder: HttpUrl.Builder) { + val checked = state.filter { it.state } + checked.forEach { + builder.addQueryParameter(param, it.value) + } + } + } + + private open class UriTriSelectOption(name: String, val value: String) : Filter.TriState(name) + + private open class UriTriSelectFilter( + name: String, + private val param: String, + private val vals: Array>, + ) : Filter.Group( + name, + vals.map { UriTriSelectOption(it.first, it.second) }, + ), + UriFilter { + override fun addToUri(builder: HttpUrl.Builder) { + state.forEach { s -> + when (s.state) { + TriState.STATE_INCLUDE -> builder.addQueryParameter(param, s.value) + TriState.STATE_EXCLUDE -> builder.addQueryParameter(param, "-${s.value}") + } + } + } + } + + private class DemographicFilter(val demographics: Array>) : + UriTriSelectFilter( + "Demographic", + "demographics[]", + demographics, + ) + + private class TypeFilter : UriMultiSelectFilter( + "Type", + "type", + arrayOf( + Pair("Manga", "manga"), + Pair("Manhwa", "manhwa"), + Pair("Manhua", "manhua"), + Pair("Other", "other"), + ), + ) + + private class GenreFilter(genres: Array>) : UriTriSelectFilter( + "Genres", + "genres[]", + genres, + ) + + private class StatusFilter : UriMultiSelectFilter( + "Status", + "statuses[]", + arrayOf( + Pair("Finished", "finished"), + Pair("Releasing", "releasing"), + Pair("On Hiatus", "on_hiatus"), + Pair("Discontinued", "discontinued"), + Pair("Not Yet Released", "not_yet_released"), + ), + ) + + private class YearFromFilter : UriPartFilter( + "From", + "release_year[from]", + getYearsArray(includeOlder = true), + "older", + ) + + private class YearToFilter : UriPartFilter( + "To", + "release_year[to]", + getYearsArray(includeOlder = false), + ) + + private class MinChapterFilter : Filter.Text("Minimum Chapter Length"), UriFilter { + override fun addToUri(builder: HttpUrl.Builder) { + if (state.isNotEmpty()) { + val value = state.toIntOrNull()?.takeIf { it > 0 } + ?: throw IllegalArgumentException( + "Minimum chapter length must be a positive integer greater than 0", + ) + builder.addQueryParameter("min_chap", value.toString()) + } + } + } + + private data class Sortable(val title: String, val value: String) { + override fun toString(): String = title + } + + private fun getSortables() = arrayOf( + Sortable("Best Match", "relevance"), + Sortable("Popular", "views_30d"), + Sortable("Updated Date", "chapter_updated_at"), + Sortable("Created Date", "created_at"), + Sortable("Title", "title"), + Sortable("Year", "year"), + Sortable("Total Views", "total_views"), + Sortable("Most Follows", "followed_count"), + ) + + private class SortFilter(private val sortables: Array) : + Filter.Sort( + "Sort By", + sortables.map(Sortable::title).toTypedArray(), + Selection(1, false), + ), + UriFilter { + override fun addToUri(builder: HttpUrl.Builder) { + if (state != null) { + val query = sortables[state!!.index].value + val value = if (state!!.ascending) "asc" else "desc" + builder.addQueryParameter("order[$query]", value) + } + } + } +}