From 4c3c2212e1c33b2b57046438779ab5ebf17f5463 Mon Sep 17 00:00:00 2001 From: Altometer Date: Thu, 23 Jan 2025 19:58:21 +0200 Subject: [PATCH] Add Zenko (#7185) * Add Zenko * Apply requested changes * Apply requested changes * Added right url for opening manga on webview * Apply kotlin lint * Use HttpUrl for parsing ids & Added right url for chapter * Apply requested changes * Fixed generate id if chapter contains dot * Apply requested changes --- src/uk/zenko/build.gradle | 8 + src/uk/zenko/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 3210 bytes src/uk/zenko/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 1770 bytes src/uk/zenko/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 4218 bytes .../zenko/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 6782 bytes .../zenko/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 9946 bytes .../extension/uk/zenko/StringProcessor.kt | 95 +++++++++ .../tachiyomi/extension/uk/zenko/Zenko.kt | 201 ++++++++++++++++++ .../extension/uk/zenko/dtos/ZenkoMangaDto.kt | 57 +++++ 9 files changed, 361 insertions(+) create mode 100644 src/uk/zenko/build.gradle create mode 100644 src/uk/zenko/res/mipmap-hdpi/ic_launcher.png create mode 100644 src/uk/zenko/res/mipmap-mdpi/ic_launcher.png create mode 100644 src/uk/zenko/res/mipmap-xhdpi/ic_launcher.png create mode 100644 src/uk/zenko/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 src/uk/zenko/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 src/uk/zenko/src/eu/kanade/tachiyomi/extension/uk/zenko/StringProcessor.kt create mode 100644 src/uk/zenko/src/eu/kanade/tachiyomi/extension/uk/zenko/Zenko.kt create mode 100644 src/uk/zenko/src/eu/kanade/tachiyomi/extension/uk/zenko/dtos/ZenkoMangaDto.kt diff --git a/src/uk/zenko/build.gradle b/src/uk/zenko/build.gradle new file mode 100644 index 000000000..d7011d73f --- /dev/null +++ b/src/uk/zenko/build.gradle @@ -0,0 +1,8 @@ +ext { + extName = 'Zenko' + extClass = '.Zenko' + extVersionCode = 1 + isNsfw = true +} + +apply from: "$rootDir/common.gradle" diff --git a/src/uk/zenko/res/mipmap-hdpi/ic_launcher.png b/src/uk/zenko/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..151f7cfab1800ce516edd304e8884f36d620374d GIT binary patch literal 3210 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 literal 0 HcmV?d00001 diff --git a/src/uk/zenko/res/mipmap-mdpi/ic_launcher.png b/src/uk/zenko/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..86e717122cb746217bf84e1fea6f51c7ad4d9543 GIT binary patch literal 1770 zcmV@MsLPtTd%f1Ukj7goDJ%Y!HR^3OkW&dm8{ z&fNZg#frDGj0FH<5Gx~$7`b1I3K~Td zapC35m)iOHc|#=O_2B~f#QPXfq(ZmbeGv@-4VMk3r>B2L*P?Y99daeK<4-F-xtb)VPIf@*`%kZ z3uSWf1gfj6H}byviHtzVBao1gz?>oA=n3U=JbCg2qN1Xh{@S%`sSQDg2T!1)qT(~& zS3h|Z0xw>?@HtOdvu4eL4pCWLT%4cHvuDo)J(-Pi1afk6{G3TiNrAn4_X>IvsBFuY zExr*T96o$l(Bq*ff#~RH=0r?P45(Bp*2De%{a`Yg1YMiW#wI)I^!E1p2v8kzT^@=N z&}y~JfpzQF!T9*NU)jNf2L=7@+qe6@Q~1Tl$Fn*pE!UTz7=f~~GKh_hrHX|z>S2B^ zP&&{mWwlya9mU1PLK$bP41u(?G#DBh67nT9H#hszw{PDT$|X?9rKY9|`4SW(uzB-l zA&*B*O^q*o>Cz>kJk+`rBY>3&c|7>tS*=zJ?|Fwqy~g?DSvQq z5b*r6pbkBCIvrRn7NOj$6Mh18hOyi2EL~Jo#FkKvM#J9c=H{TXvJx&|z6@v1oME8R zA`o%Xp;f25yBkiNIKkc>4hKw4O$qHNMu1iiYBV=D7s|`aaY@y~=;$~$;u3V_uvlC$ zJgh;7jd1?_c{qCXD64D9Mu2(`%h?ft4stXCf84ZS-aHf(2JS^3`2~|;n{~tW8)i6u z{5WK0X2Rpgk6E3Qlaqpe*a_t2<*{c*Mh5o!C`-4tT5+d!L3)NKjARb@y_1f~she~2eLHq<4*>RdpEN^_q`rYpFRzGy&fJue5gEuYuB#%c2;%2 z9-Y=={JLT6A1CB}s}&rOgY=(2w=%2+_7T^>#+%~ z6MM}Lzg3%{yT^)i!j4O2@Rf*Gpos|w>e`^9(uAwg96bNm!S;b&yLQ2yJ9h+|uoIw3 zNozdynAK{>9oNe8C>#t1Cz5ao6W3!mkE7(k4bbHaA9epMSg?-z`g*oc1hxw^0Rk;~ zwEdgSPU!5k!QD2`gd7;~Y*Y(@bjbPk4l9O_4GB4+v9XcWDc2<^LV$4U)G5|ul~pD< zlB@FN|9_q8wI-#xI z3Jp!-9ij2M8Jb%xOow!Pdne$=%^Aqf&JGwlTsz7TAkf8yZet~-df2pS6C@`m;~B{f z83#0MLQ8sokJ#a>Z#1kdF)+54A=QHB_}|jZ=ZCrk=`jiJUlGug@QLF5c0>}U^sT{82buGD^7lXzEB=2cmg4P z+{MfIC;xW@By8BQfkB@CI^YRZRaJe?`|2m){b^8FSN8#KHhMC0FOwe}=o=V~M#I^& zXOqas*49>$010@0-@J?QdX;ue5+V{Qk=mv^vs$gLN8eUKuwP6h-olvF)YOf*nl$1u z*Jv`C%*cM4o=XX-P2BK|DwV1!B_)NzhyGg!CqUv=ScOR3wr$%Ncya%7|Ni|E0kxfm zLcWksi#jg}DdNug4klM4-it6+lP@7V&kIc!jJ?=MBItyDE6YUw1JC2zEgzn*h5!Hn M07*qoM6N<$f~m${Q2+n{ literal 0 HcmV?d00001 diff --git a/src/uk/zenko/res/mipmap-xhdpi/ic_launcher.png b/src/uk/zenko/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..aefb80cd4aa52e9b41024ca488941c1b79784669 GIT binary patch literal 4218 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 literal 0 HcmV?d00001 diff --git a/src/uk/zenko/res/mipmap-xxxhdpi/ic_launcher.png b/src/uk/zenko/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..80032a52ded6be541e0098c51d86515fff111018 GIT binary patch literal 9946 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 { + val (part, chapter, name) = parts + ParsedResult(part, chapter, name) + } + + 2 -> { + val (part, chapter) = parts + ParsedResult(part, chapter) + } + + 1 -> { + val (name) = parts + ParsedResult(name = name) + } + + else -> ParsedResult() + } + } + + // gen ID by rule: part + chapter + // example + // 1 + 0 = 100 + // 1 + 1 = 101 + // 0 + 10 = 010 + // 1 + 99 = 199 + // 1 + 100.5 = 1100.5 + fun generateId(input: String?): Double { + if (input.isNullOrEmpty()) { + return -1.0 + } + val (part, chapter) = parse(input) + + val partNumber = part.toIntOrNull() ?: 0 + val chapterNumber = chapter.toDoubleOrNull() ?: 0 + + val formattedChapter = if (chapter.contains('.')) { + chapter.split('.').joinToString(".") { part -> + if (part == chapter.split('.').first() && part.length == 1) { + part.padStart(2, '0') + } else { + part + } + } + } else { + if (chapter.length == 1) chapter.padStart(2, '0') else chapter + } + + val idString = if (partNumber > 0) { + "$partNumber$formattedChapter" + } else { + "$chapterNumber" + } + + return try { + idString.toDouble() + } catch (e: NumberFormatException) { + Log.d("ZENKO", "Invalid ID format: $idString") + -1.0 + } + } + + fun format(input: String?): String { + val (part, chapter, name) = parse(input) + val chapterLabel = if (chapter.isNotEmpty()) { + "Розділ $chapter${if (name.isNotEmpty()) ":" else ""}" + } else { + "" + } + return listOf( + if (part.isNotEmpty()) "Том $part" else "", + chapterLabel, + name, + ).filter { it.isNotEmpty() }.joinToString(" ") + } +} diff --git a/src/uk/zenko/src/eu/kanade/tachiyomi/extension/uk/zenko/Zenko.kt b/src/uk/zenko/src/eu/kanade/tachiyomi/extension/uk/zenko/Zenko.kt new file mode 100644 index 000000000..5dd337063 --- /dev/null +++ b/src/uk/zenko/src/eu/kanade/tachiyomi/extension/uk/zenko/Zenko.kt @@ -0,0 +1,201 @@ +package eu.kanade.tachiyomi.extension.uk.zenko + +import eu.kanade.tachiyomi.extension.uk.zenko.dtos.ChapterResponseItem +import eu.kanade.tachiyomi.extension.uk.zenko.dtos.MangaDetailsResponse +import eu.kanade.tachiyomi.extension.uk.zenko.dtos.ZenkoMangaListResponse +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.network.interceptor.rateLimitHost +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 kotlinx.serialization.json.Json +import kotlinx.serialization.json.decodeFromStream +import okhttp3.HttpUrl.Companion.toHttpUrl +import okhttp3.Request +import okhttp3.Response +import uy.kohesive.injekt.injectLazy + +class Zenko : HttpSource() { + override val name = "Zenko" + override val baseUrl = "https://zenko.online" + override val lang = "uk" + override val supportsLatest = true + + override fun headersBuilder() = super.headersBuilder() + .add("Origin", "$baseUrl") + .add("Referer", "$baseUrl/") + + override val client = network.cloudflareClient.newBuilder() + .rateLimitHost(API_URL.toHttpUrl(), 10) + .build() + + override fun getMangaUrl(manga: SManga): String { + return "$baseUrl${manga.url}" + } + + override fun getChapterUrl(chapter: SChapter): String { + return "$baseUrl${chapter.url}" + } + + // ============================== Popular =============================== + override fun popularMangaRequest(page: Int): Request { + val offset = offsetCounter(page) + return makeZenkoMangaRequest(offset, "viewsCount") + } + + override fun popularMangaParse(response: Response) = parseAsMangaResponseDto(response) + + // =============================== Latest =============================== + override fun latestUpdatesRequest(page: Int): Request { + val offset = offsetCounter(page) + return makeZenkoMangaRequest(offset, "lastChapterCreatedAt") + } + + override fun latestUpdatesParse(response: Response) = parseAsMangaResponseDto(response) + + // =============================== Search =============================== + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { + if (query.length >= 2) { + val offset = offsetCounter(page) + val url = "$API_URL/titles".toHttpUrl().newBuilder() + .addQueryParameter("limit", "15") + .addQueryParameter("offset", offset.toString()) + .addQueryParameter("name", query) + .build() + return GET(url, headers) + } else { + throw UnsupportedOperationException("Запит має містити щонайменше 2 символи / The query must contain at least 2 characters") + } + } + + override fun searchMangaParse(response: Response) = parseAsMangaResponseDto(response) + + // =========================== Manga Details ============================ + override fun mangaDetailsRequest(manga: SManga): Request { + val mangaId = "$baseUrl${manga.url}".toHttpUrl().pathSegments.last() + val url = "$API_URL/titles/$mangaId" + return GET(url, headers) + } + + override fun mangaDetailsParse(response: Response) = SManga.create().apply { + val mangaDto = response.parseAs() + setUrlWithoutDomain("/titles/${mangaDto.id}") + title = mangaDto.engName ?: mangaDto.name + thumbnail_url = buildImageUrl(mangaDto.coverImg) + description = "${mangaDto.name}\n${mangaDto.description}" + genre = mangaDto.genres!!.joinToString { it.name } + author = mangaDto.author!!.username + status = mangaDto.status.toStatus() + } + + // ============================== Chapters ============================== + override fun chapterListRequest(manga: SManga): Request { + val mangaId = "$baseUrl${manga.url}".toHttpUrl().pathSegments.last() + val url = "$API_URL/titles/$mangaId/chapters" + return GET(url, headers) + } + + override fun chapterListParse(response: Response): List { + val result = response.parseAs>() + return result.sortedByDescending { item -> + val id = StringProcessor.generateId(item.name) + if (id > 0) id else item.id.toDouble() + }.map { chapterResponseItem -> + SChapter.create().apply { + setUrlWithoutDomain("/titles/${chapterResponseItem.titleId}/${chapterResponseItem.id}") + name = StringProcessor.format(chapterResponseItem.name) + date_upload = chapterResponseItem.createdAt!!.secToMs() + scanlator = chapterResponseItem.publisher!!.name + } + } + } + + // =============================== Pages ================================ + override fun pageListRequest(chapter: SChapter): Request { + val chapterId = "$baseUrl${chapter.url}".toHttpUrl().pathSegments.last() + val url = "$API_URL/chapters/$chapterId" + return GET(url, headers) + } + + override fun pageListParse(response: Response): List { + val data = response.parseAs() + return data.pages!!.map { page -> + Page(page.id, imageUrl = "$IMAGE_STORAGE_URL/${page.imgUrl}") + } + } + + override fun imageUrlParse(response: Response): String = "" + + // ============================= Utilities ============================== + private fun parseAsMangaResponseDto(response: Response): MangasPage { + val zenkoMangaListResponse = response.parseAs() + return makeMangasPage(zenkoMangaListResponse.data, zenkoMangaListResponse.meta.hasNextPage) + } + + private fun offsetCounter(page: Int) = (page - 1) * 15 + + private fun makeZenkoMangaRequest(offset: Int, sortBy: String): Request { + val url = "$API_URL/titles".toHttpUrl().newBuilder() + .addQueryParameter("limit", "15") + .addQueryParameter("offset", offset.toString()) + .addQueryParameter("sortBy", sortBy) + .addQueryParameter("order", "DESC") + .build() + return GET(url, headers) + } + + private fun makeMangasPage( + mangaList: List, + hasNextPage: Boolean = false, + ): MangasPage { + return MangasPage( + mangaList.map(::makeSManga), + hasNextPage, + ) + } + + private fun makeSManga(mangaDto: MangaDetailsResponse) = SManga.create().apply { + setUrlWithoutDomain("/titles/${mangaDto.id}") + title = mangaDto.engName ?: mangaDto.name + thumbnail_url = buildImageUrl(mangaDto.coverImg) + status = mangaDto.status.toStatus() + } + + private fun String.toStatus(): Int { + val status = this.lowercase() + return when (status) { + "ongoing" -> SManga.ONGOING + "finished" -> SManga.COMPLETED + "paused" -> SManga.ON_HIATUS + else -> SManga.UNKNOWN + } + } + + private fun Long.secToMs(): Long { + return this * 1000 + } + + private fun buildImageUrl(imageId: String): String { + val url = "$IMAGE_STORAGE_URL/$imageId".toHttpUrl().newBuilder() + .addQueryParameter("optimizer", "image") + .addQueryParameter("width", "560") + .addQueryParameter("quality", "70") + .addQueryParameter("height", "auto") + .build() + return url.toString() + } + + private inline fun Response.parseAs(): T = use { + json.decodeFromStream(it.body.byteStream()) + } + + companion object { + private const val API_URL = "https://zenko-api.onrender.com" + private const val IMAGE_STORAGE_URL = "https://zenko.b-cdn.net" + + private val json: Json by injectLazy() + } +} diff --git a/src/uk/zenko/src/eu/kanade/tachiyomi/extension/uk/zenko/dtos/ZenkoMangaDto.kt b/src/uk/zenko/src/eu/kanade/tachiyomi/extension/uk/zenko/dtos/ZenkoMangaDto.kt new file mode 100644 index 000000000..564127e31 --- /dev/null +++ b/src/uk/zenko/src/eu/kanade/tachiyomi/extension/uk/zenko/dtos/ZenkoMangaDto.kt @@ -0,0 +1,57 @@ +package eu.kanade.tachiyomi.extension.uk.zenko.dtos + +import kotlinx.serialization.Serializable + +@Serializable +class ZenkoMangaListResponse( + val `data`: List, + val meta: Meta, +) + +@Serializable +class Meta( + val hasNextPage: Boolean, +) + +@Serializable +class MangaDetailsResponse( + val author: Author? = null, + val coverImg: String, + val description: String, + val engName: String? = null, + val genres: List? = null, + val id: Int, + val name: String, + val status: String, +) + +@Serializable +class Genre( + val name: String, +) + +@Serializable +class Author( + val username: String? = null, +) + +@Serializable +class ChapterResponseItem( + val createdAt: Long? = null, + val id: Int, + val name: String?, + val pages: List? = null, + val titleId: Int?, + val publisher: Publisher? = null, +) + +@Serializable +class Page( + val id: Int, + val imgUrl: String, +) + +@Serializable +class Publisher( + val name: String? = null, +)