From 9f524ee265813abe99dc2226385071b5cd1e2428 Mon Sep 17 00:00:00 2001 From: sinkableShip <89952969+sinkableShip@users.noreply.github.com> Date: Thu, 6 Jun 2024 13:29:20 +0700 Subject: [PATCH] Add Manga Toshokan Z (#3346) * working basic function without known problem for now * add filter and some changes * add logo * Apply suggestions from code review Co-authored-by: AwkwardPeak7 <48650614+AwkwardPeak7@users.noreply.github.com> * fix bugs for manga id published by registered user and change search manga request to use api with page option instead --------- Co-authored-by: AwkwardPeak7 <48650614+AwkwardPeak7@users.noreply.github.com> --- src/ja/mangatoshokanz/build.gradle | 11 + .../res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 2638 bytes .../res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 1521 bytes .../res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 3300 bytes .../res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 5905 bytes .../res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 7725 bytes .../extension/ja/mangatoshokanz/Crypto.kt | 76 ++++ .../ja/mangatoshokanz/MangaToshokanZ.kt | 338 ++++++++++++++++++ 8 files changed, 425 insertions(+) create mode 100644 src/ja/mangatoshokanz/build.gradle create mode 100644 src/ja/mangatoshokanz/res/mipmap-hdpi/ic_launcher.png create mode 100644 src/ja/mangatoshokanz/res/mipmap-mdpi/ic_launcher.png create mode 100644 src/ja/mangatoshokanz/res/mipmap-xhdpi/ic_launcher.png create mode 100644 src/ja/mangatoshokanz/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 src/ja/mangatoshokanz/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 src/ja/mangatoshokanz/src/eu/kanade/tachiyomi/extension/ja/mangatoshokanz/Crypto.kt create mode 100644 src/ja/mangatoshokanz/src/eu/kanade/tachiyomi/extension/ja/mangatoshokanz/MangaToshokanZ.kt diff --git a/src/ja/mangatoshokanz/build.gradle b/src/ja/mangatoshokanz/build.gradle new file mode 100644 index 000000000..156cfacfc --- /dev/null +++ b/src/ja/mangatoshokanz/build.gradle @@ -0,0 +1,11 @@ +ext { + extName = 'Manga Toshokan Z' + extClass = '.MangaToshokanZ' + extVersionCode = 1 +} + +apply from: "$rootDir/common.gradle" + +dependencies { + implementation(project(':lib:cryptoaes')) +} diff --git a/src/ja/mangatoshokanz/res/mipmap-hdpi/ic_launcher.png b/src/ja/mangatoshokanz/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..02c28e63368f56e096632b9339da99f5130d9668 GIT binary patch literal 2638 zcmV-U3bFNxP)bU~Qk!gA z*J;zD{57RY8aho=mUJ!47<wOX=zhhvhgOoUk2N-@Au+sF6J8B7@UjZ zBVE97ALo4T?|sf8Mxt#|0x?lOixY?f3Lqg-Kv6*98Ih<2Et3QhqzZGjL!I$3;dk|C zznFp<)v|Pad(+aCVO~7dwZ{*o}M0aI2_H5jg4;}IB;MHy_sIDM}YvD z>+0&VDk>^oFq_T0V`5^Ga5Ky89b(u|ci|2rZnwMX(xpp>4;?zxgU@b%cU=sS>BNZ> zsnylhzp~kEUktlj2E2jO>3se4>C=x@RaN!-gX9koe{X4NX?Zv=FYgT0D$Ri73%g5D zx1sj-_9r%O+*pg7P3hgxA0QJ>wsm%PKD}<;x@VwT=CFHXz#GiW%uM(8_CAL({si4~ z=-DHGfCwa^ySw{%W@hGh4LG)C>rzio&(8tq2e{$5o|-KN$PNiQo|Tn#blG}iz#DaS zb)C%4&L*I-Pyrui!0A6dsYFf}cgyC&uLZ4TLFH_J2Ilf)bo_-#c5(BZ~@sYl1u#KsCS z+LLLOFRh=2m0~qXDh^BF{stvqE<9)uK!$Mqy?0XT+9su;!yyyy*&gugANOX+zU;Vw z_lpRiy|4AkyZugYXQ*y!O9Pm@QY~^{h85pq<(~9d$+KI;!bmHG=A51pO6oiPQ}X_x zORn}!$$ti&(mOeuN@<_T$DiAkD&H&!oHj-VPy+xB%mUJORDGNLPT{dbgi@_Bk_#31 zMt*{nWZGm?qQzS#@Z|+2(lX+b%RQ5F{*!TOn{dkjfQ&;@_&04TzA6Cu!qEbn{ds~`zBN`U&$h`!IdPIt2qpEbV-MZFBCVk)3HH!t#| zyOZUKEr|*NdY~TIl@ErT^5L*cJ{)q%zhKl`$6V46qpMf@3`cql2}o;$;MwN^^7T%% zGmK#l zS)7IiL{G&?I#fU$6xA*Fl};}|izg!A3m99HEV4ZbdIaRAb1KtK=trAL}Pku)I@5RLS=Jw`eh z72`Q7AWupqkL1S5Z%Q)eU9Q?FZPlO)5jItmPC#vjQZX$czCcBSc7zY85NdV4I9fyv&yRvq$@9hjnZ@Q%|5w$Yfi-}Wt1Sp04k-5Ee9YP=~riM5Swo@niLx&b!BU0 z*IKJrl_)(`R|gkdV^}~O6yNTTm!e`CK)z8mX?6k3Cgnmy`FVdV|IH;qDptbVGWWh9tr!4IvNHRv8Tis8~nE z!U5#gtk}s#?)9e!*T@zW)718}>mvvXU9ubA>`}x-oDlf+Cu8yyY?8wxQpN)G=Y^3p zZP1gUM$+(NIi6I5Fj&~H+L|cGwe&+7AQg~W*U_Hmuk@-7C|3m_?Ld+N zXEVwc%)+;(SY#{QH49-}+~CVg1>{z1Ymy!Qg|aI}_lU z8(axD+kEf0;IrNXaa{sz7roy@NNfYOh%8>tJ4f|R#?(+SVASVD43IxjCa||r4t*P-sD@nl#0f(N zcrITzt#cFFrUZxx7h$nbR=n0GaLK1w&C%_cQ%i;e8!`V?go#1Ug|T=~&sUIWRxa-Kw*t)8840k3IH<-C<-V%eTz!a zN=i^iN5`|+33Dgx_F2+i4SREQ^V5Zeh1J+`7}nADA3JvJd$qN-&3N!I4z9jhkW(*~*3j|0Q;b`Km%kM7?2KzW}EDFHW#_W+{=1-fo&ye){f6pub1WJo~ wEc&!yBxvQkYv3nD0R?zDQ9w~Z;py-H0Y-q6qfqCyg#Z8m07*qoM6N<$f+pDALjV8( literal 0 HcmV?d00001 diff --git a/src/ja/mangatoshokanz/res/mipmap-mdpi/ic_launcher.png b/src/ja/mangatoshokanz/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..5ad933016b00f8a929fc903b9748c87dc23091b7 GIT binary patch literal 1521 zcmV##%_ViWV)(i~3;NL_;ur(8M=lOpKTIO-G&(7tX?>par zJ2NI)?&xsr-r z6vu+WU`V( zSy0z%ECCg~L`0cNRC;`k)nHXhHrdD@5$SZVpUz>G2mPE-wa+Ahz~N?Dfw{oPUW)}e z>sgJGeBMw6jORz2_|ZP zIcJuwvLiB?zlVbK&v1aQz~&KPX8DU2yd6<&SC&An<6-LYMR1=;7oT?<=L4*OO27#M z9XbMf&tp*eKlMk=%?i)n=}M*IjaJ$R|JaFeYz2v4+_&||evn-O0v|WaVbmq0%u8Sr zVa9W!S2C#=5uLs+a*+e3r*AjCVcPz)Ow>>Xj$VTTS7icD5a^so0UrpsL4ae=(Z@3A z?S~z*8eqg>)uP1O0~bf>yH4NA5YQ^n`O0h`(66kPKz3RR9eU7C?Z~#Rcfxe)*(@m# zTp0;c(Wyb%S1>@Gkv_l*ER;Z&VnWe;flk$~OgfZrm-y~KGfaOVv8m2&IDwBeKG2nb z0!%i(Hwq=#p-HNX__lf@eSj6vn$SWC ze81O)VlPc1^G&;tvTP~zBr4i&Ki92qs z&(|M9!uxL`Y%GCKSb?UQM3yK4cH%D(dEQhaj~Z_KuqQZ!J;4uMK01jqn-`}31veEM zRZgn}R3GR`Kmn(sz%$uas(vDqIw!;QTW>%rv9Bw|pBka~*Kh(KDhlvqFQGgr$+6KM zk3a<(sWGoOIFT^{u5r{h)6-O~nv;eT_&`G-3JL(S;gCTW6sui9k;@ z3e5+eum5GF37xz(MX#Z5(I%$o(rRKkpUX`*Q=Y?2=TTm3@n0FyP&YPQ`47c+0_WJJbSCyD(~hSqSI{ z9>XT4aWsgsAV8OqRWA(%$j6yUrwISg!CrYUoVf^K+1$3Thud9Eg-i6&lDv*qRG z<<`{H9L~?rFT-aY`Bg%a8a{VTbaZsomY0{m-_+F91sL)MT50hJ@VRsxY(ndBq$UZw zI&Byoej&@IGo>{jzG1Gs!sji@k?ilpZ0}5hkJ&ZKT?iz3(G#D3DVaU-Sq!{$%@g<^ XwyR%Wg>W_m00000NkvXXu0mjfOFPUt literal 0 HcmV?d00001 diff --git a/src/ja/mangatoshokanz/res/mipmap-xhdpi/ic_launcher.png b/src/ja/mangatoshokanz/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..cd41c041cc7896e43ec50250a3ae16a631604ef2 GIT binary patch literal 3300 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 literal 0 HcmV?d00001 diff --git a/src/ja/mangatoshokanz/res/mipmap-xxxhdpi/ic_launcher.png b/src/ja/mangatoshokanz/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..2bf8d5117d3cfb06f0e8a374bfcd14f0137a886e GIT binary patch literal 7725 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 Xs~+8SD8InTz<`>PmSTm1MezRt4Bt!m literal 0 HcmV?d00001 diff --git a/src/ja/mangatoshokanz/src/eu/kanade/tachiyomi/extension/ja/mangatoshokanz/Crypto.kt b/src/ja/mangatoshokanz/src/eu/kanade/tachiyomi/extension/ja/mangatoshokanz/Crypto.kt new file mode 100644 index 000000000..8bc60f55e --- /dev/null +++ b/src/ja/mangatoshokanz/src/eu/kanade/tachiyomi/extension/ja/mangatoshokanz/Crypto.kt @@ -0,0 +1,76 @@ +package eu.kanade.tachiyomi.extension.ja.mangatoshokanz + +import android.util.Base64 +import eu.kanade.tachiyomi.lib.cryptoaes.CryptoAES +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.json.Json +import okhttp3.Response +import uy.kohesive.injekt.injectLazy +import java.security.KeyPair +import java.security.KeyPairGenerator +import java.security.PrivateKey +import java.security.PublicKey +import java.security.spec.RSAKeyGenParameterSpec +import javax.crypto.Cipher + +private val json: Json by injectLazy() + +internal fun getKeys(): KeyPair { + return KeyPairGenerator.getInstance("RSA").run { + initialize(RSAKeyGenParameterSpec(512, RSAKeyGenParameterSpec.F4)) + generateKeyPair() + } +} + +internal fun PublicKey.toPem(): String { + val base64Encoded = Base64.encodeToString(encoded, Base64.DEFAULT) + + return StringBuilder("-----BEGIN PUBLIC KEY-----") + .appendLine() + .append(base64Encoded) + .append("-----END PUBLIC KEY-----") + .toString() +} + +internal fun Response.decryptPages(privateKey: PrivateKey): Decrypted { + val encrypted = json.decodeFromString(body.string()) + + val biDecoded = Base64.decode(encrypted.bi, Base64.DEFAULT) + val ekDecoded = Base64.decode(encrypted.ek, Base64.DEFAULT) + + val ekDecrypted = Cipher.getInstance("RSA/ECB/PKCS1Padding").run { + init(Cipher.DECRYPT_MODE, privateKey) + doFinal(ekDecoded) + } + val dataDecrypted = CryptoAES.decrypt(encrypted.data, ekDecrypted, biDecoded) + + return json.decodeFromString(dataDecrypted) +} + +@Serializable +private class Encrypted( + val bi: String, + val ek: String, + val data: String, +) + +@Serializable +internal class Decrypted( + @SerialName("Images") + val images: List, + @SerialName("Location") + val location: Location, +) { + @Serializable + internal class Image( + val file: String, + ) + + @Serializable + internal class Location( + val base: String, + val st: String, + ) +} diff --git a/src/ja/mangatoshokanz/src/eu/kanade/tachiyomi/extension/ja/mangatoshokanz/MangaToshokanZ.kt b/src/ja/mangatoshokanz/src/eu/kanade/tachiyomi/extension/ja/mangatoshokanz/MangaToshokanZ.kt new file mode 100644 index 000000000..f94397550 --- /dev/null +++ b/src/ja/mangatoshokanz/src/eu/kanade/tachiyomi/extension/ja/mangatoshokanz/MangaToshokanZ.kt @@ -0,0 +1,338 @@ +package eu.kanade.tachiyomi.extension.ja.mangatoshokanz + +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.network.POST +import eu.kanade.tachiyomi.source.model.Filter +import eu.kanade.tachiyomi.source.model.FilterList +import eu.kanade.tachiyomi.source.model.MangasPage +import eu.kanade.tachiyomi.source.model.Page +import eu.kanade.tachiyomi.source.model.SChapter +import eu.kanade.tachiyomi.source.model.SManga +import eu.kanade.tachiyomi.source.online.HttpSource +import eu.kanade.tachiyomi.util.asJsoup +import okhttp3.FormBody +import okhttp3.HttpUrl +import okhttp3.HttpUrl.Companion.toHttpUrl +import okhttp3.Interceptor +import okhttp3.Request +import okhttp3.Response +import java.lang.StringBuilder +import java.security.KeyPair + +class MangaToshokanZ : HttpSource() { + override val lang = "ja" + override val supportsLatest = true + override val name = "マンガ図書館Z" + override val baseUrl = "https://www.mangaz.com" + + override val client = network.cloudflareClient.newBuilder() + .addNetworkInterceptor(::r18Interceptor) + .build() + + override fun headersBuilder() = super.headersBuilder() + // author/illustrator name might just show blank if language not set to japan + .add("cookie", "_LANG_=ja") + + private val keys: KeyPair by lazy { + getKeys() + } + + private val _serial by lazy { + getSerial() + } + + private var isR18 = false + + private fun r18Interceptor(chain: Interceptor.Chain): Response { + val request = chain.request() + + // open access to R18 section + if (request.url.host == "r18.mangaz.com" && isR18.not()) { + val url = "https://r18.mangaz.com/attention/r18/yes" + + val r18Request = Request.Builder() + .url(url) + .head() + .build() + + isR18 = true + client.newCall(r18Request).execute().close() + } + + return chain.proceed(request) + } + + override fun popularMangaRequest(page: Int) = GET("$baseUrl/ranking/views", headers) + + override fun popularMangaParse(response: Response): MangasPage { + val mangas = response.toMangas(".itemList") + return MangasPage(mangas, false) + } + + override fun latestUpdatesRequest(page: Int): Request { + val header = headers.newBuilder() + .add("X-Requested-With", "XMLHttpRequest") + .build() + + val url = baseUrl.toHttpUrl().newBuilder() + .addPathSegments("title/addpage_renewal") + .addQueryParameter("type", "official") + .addQueryParameter("sort", "new") + .addQueryParameter("page", page.toString()) + .build() + + return GET(url, header) + } + + override fun latestUpdatesParse(response: Response): MangasPage { + val mangas = response.toMangas("body") + return MangasPage(mangas, mangas.size == 50) + } + + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { + val header = headers.newBuilder() + .add("X-Requested-With", "XMLHttpRequest") + .build() + + val url = baseUrl.toHttpUrl().newBuilder() + .addPathSegments("title/addpage_renewal") + .addQueryParameter("query", query) + .addQueryParameter("page", page.toString()) + + filters.forEach { filter -> + when (filter) { + is Category -> { + if (filter.state != 0) { + url.addQueryParameter("category", categories[filter.state].lowercase()) + } + if (filter.state == 5) { + url.host("r18.mangaz.com") + } + } + is Sort -> { + url.addQueryParameter("sort", sortBy[filter.state].lowercase()) + } + else -> {} + } + } + + return GET(url.build(), header) + } + + override fun searchMangaParse(response: Response) = latestUpdatesParse(response) + + private fun Response.toMangas(selector: String): List { + return asJsoup().selectFirst(selector)!!.children().filter { child -> + child.`is`("li") + }.filterNot { li -> + // discard manga that in the middle of asking for license progress, it can't be read + li.selectFirst(".iconConsent") != null + }.map { li -> + SManga.create().apply { + val a = li.selectFirst("h4 > a")!! + url = a.attr("href").substringAfterLast("/") + title = a.text() + + thumbnail_url = li.selectFirst("a > img")!!.attr("data-src").ifBlank { + li.selectFirst("a > img")!!.attr("src") + } + } + } + } + + override fun getFilterList() = FilterList(Category(), Sort()) + + private class Category : Filter.Select("Category", categories) + + private class Sort : Filter.Select("Sort", sortBy) + + // in this manga details section we use book/detail/id since it have tags over series/detail/id + override fun mangaDetailsRequest(manga: SManga): Request { + // normally manga published by the website has the same id in it's series and book + // example: https://www.mangaz.com/series/detail/202371 (series) + // https://www.mangaz.com/book/detail/202371 (book) + // strangely manga published by registered user has different id in it's series and book + // example: https://www.mangaz.com/series/detail/224931 (series) + // https://www.mangaz.com/book/detail/224932 (book) + + // so in here we want the id from the manga thumbnail url since it contain the book id + // instead of manga url that contain series id which used for the chapter section later + // example: https://www.mangaz.com/series/detail/224931 (manga url) + // https://books.j-comi.jp/Books/224/224932/thumb160_1713230205.jpg (thumbnail url) + val bookId = manga.thumbnail_url!!.substringBeforeLast("/").substringAfterLast("/") + val url = baseUrl.toHttpUrl().newBuilder() + .addPathSegments("book/detail") + .addPathSegment(bookId) + .build() + + return GET(url, headers) + } + + override fun mangaDetailsParse(response: Response): SManga { + val document = response.asJsoup() + + return SManga.create().apply { + document.select(".detailAuthor > li").forEach { li -> + when { + li.ownText().contains("者") || li.ownText().contains("原作") -> { + if (author.isNullOrEmpty()) { + author = li.child(0).text() + } else { + author += ", ${li.child(0).text()}" + } + } + li.ownText().contains("作画") || li.ownText().contains("マンガ") -> { + if (artist.isNullOrEmpty()) { + artist = li.child(0).text() + } else { + artist += ", ${li.child(0).text()}" + } + } + } + } + description = document.selectFirst(".wordbreak")?.text() + genre = document.select(".inductionTags a").joinToString { it.text() } + status = when { + document.selectFirst("p.iconContinues") != null -> SManga.ONGOING + document.selectFirst("p.iconEnd") != null -> SManga.COMPLETED + else -> SManga.UNKNOWN + } + } + } + + // we want series/detail/id over book/detail/id in here since book/detail/id have problem + // where if the name of the chapter become too long the end become ellipsis (...) + override fun chapterListRequest(manga: SManga): Request { + val url = baseUrl.toHttpUrl().newBuilder() + .addPathSegments("series/detail") + .addPathSegment(manga.url) + .build() + + return GET(url, headers) + } + + override fun chapterListParse(response: Response): List { + val document = response.asJsoup() + + // if it's single chapter, it will be redirected back to book/detail/id + if (response.request.url.pathSegments.first() == "book") { + return listOf( + SChapter.create().apply { + name = document.selectFirst(".GA4_booktitle")!!.text() + url = document.baseUri().substringAfterLast("/") + chapter_number = 1f + date_upload = 0 + }, + ) + } + + // if it's multiple chapters + return document.select(".itemList li").reversed().mapIndexed { i, li -> + SChapter.create().apply { + name = li.selectFirst(".title")!!.text() + url = li.selectFirst("a")!!.attr("href").substringAfterLast("/") + chapter_number = i.toFloat() + date_upload = 0 + } + }.reversed() + } + + override fun pageListRequest(chapter: SChapter): Request { + val ticket = getTicket(chapter.url) + val pem = keys.public.toPem() + + val url = virgoBuilder() + .addPathSegment("docx") + .addPathSegment(chapter.url.plus(".json")) + .build() + + val header = headers.newBuilder() + .add("X-Requested-With", "XMLHttpRequest") + .add("Cookie", "virgo!__ticket=$ticket") + .build() + + val body = FormBody.Builder() + .add("__serial", _serial) + .add("__ticket", ticket) + .add("pub", pem) + .build() + + return POST(url.toString(), header, body) + } + + private fun getTicket(chapterId: String): String { + val ticketUrl = virgoBuilder() + .addPathSegments("view") + .addPathSegment(chapterId) + .build() + + val ticketRequest = Request.Builder() + .url(ticketUrl) + .headers(headers) + .head() + .build() + + try { + client.newCall(ticketRequest).execute().close() + } catch (_: Exception) { + throw Exception("Fail to retrieve ticket") + } + + return client.cookieJar.loadForRequest(ticketUrl).find { cookie -> + cookie.name == "virgo!__ticket" + }?.value ?: throw Exception("Fail to retrieve ticket from cookie") + } + + private fun getSerial(): String { + val url = virgoBuilder() + .addPathSegment("app.js") + .build() + + val response = try { + client.newCall(GET(url, headers)).execute() + } catch (_: Exception) { + throw Exception("Fail to retrieve serial") + } + + val appJsString = response.body.string() + return appJsString.substringAfter("__serial = \"").substringBefore("\";") + } + + private fun virgoBuilder(): HttpUrl.Builder { + return baseUrl.toHttpUrl().newBuilder() + .host("vw.mangaz.com") + .addPathSegment("virgo") + } + + override fun pageListParse(response: Response): List { + val decrypted = response.decryptPages(keys.private) + + return decrypted.images.mapIndexed { i, image -> + val imageUrl = StringBuilder(decrypted.location.base) + .append(decrypted.location.st) + .append(image.file.substringBefore(".")) + .append(".jpg") + + Page(i, imageUrl = imageUrl.toString()) + } + } + + override fun imageUrlParse(response: Response): String { + throw UnsupportedOperationException() + } + + companion object { + private val categories = arrayOf( + "All", + "Mens", + "Womens", + "TL", + "BL", + "R18", + ) + private val sortBy = arrayOf( + "Popular", + "New", + ) + } +}