From 6af9f2d853fbed8a94f917453fa5bfcd8aaf4419 Mon Sep 17 00:00:00 2001 From: peakedshout <93729380+peakedshout@users.noreply.github.com> Date: Wed, 18 Jun 2025 21:59:43 +0800 Subject: [PATCH] SakuraManhwa: Added support for SakuraManhwa source (#9195) New: Added support for SakuraManhwa source --- src/all/sakuramanhwa/build.gradle | 8 + .../res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 4696 bytes .../res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 2446 bytes .../res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 6923 bytes .../res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 13483 bytes .../res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 20628 bytes .../extension/all/sakuramanhwa/ApiModel.kt | 93 +++++ .../all/sakuramanhwa/CryptoHelper.kt | 191 +++++++++ .../extension/all/sakuramanhwa/Filter.kt | 143 +++++++ .../extension/all/sakuramanhwa/I18nHelper.kt | 44 +++ .../all/sakuramanhwa/SakuraManhwa.kt | 374 ++++++++++++++++++ 11 files changed, 853 insertions(+) create mode 100644 src/all/sakuramanhwa/build.gradle create mode 100644 src/all/sakuramanhwa/res/mipmap-hdpi/ic_launcher.png create mode 100644 src/all/sakuramanhwa/res/mipmap-mdpi/ic_launcher.png create mode 100644 src/all/sakuramanhwa/res/mipmap-xhdpi/ic_launcher.png create mode 100644 src/all/sakuramanhwa/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 src/all/sakuramanhwa/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 src/all/sakuramanhwa/src/eu/kanade/tachiyomi/extension/all/sakuramanhwa/ApiModel.kt create mode 100644 src/all/sakuramanhwa/src/eu/kanade/tachiyomi/extension/all/sakuramanhwa/CryptoHelper.kt create mode 100644 src/all/sakuramanhwa/src/eu/kanade/tachiyomi/extension/all/sakuramanhwa/Filter.kt create mode 100644 src/all/sakuramanhwa/src/eu/kanade/tachiyomi/extension/all/sakuramanhwa/I18nHelper.kt create mode 100644 src/all/sakuramanhwa/src/eu/kanade/tachiyomi/extension/all/sakuramanhwa/SakuraManhwa.kt diff --git a/src/all/sakuramanhwa/build.gradle b/src/all/sakuramanhwa/build.gradle new file mode 100644 index 000000000..b345abece --- /dev/null +++ b/src/all/sakuramanhwa/build.gradle @@ -0,0 +1,8 @@ +ext { + extName = 'SakuraManhwa' + extClass = '.SakuraManhwa' + extVersionCode = 1 + isNsfw = true +} + +apply from: "$rootDir/common.gradle" diff --git a/src/all/sakuramanhwa/res/mipmap-hdpi/ic_launcher.png b/src/all/sakuramanhwa/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..e92d7965ae4095093f33386e6c3a4429a7559065 GIT binary patch literal 4696 zcmV-e5~uBnP)Px{5=lfsRCr$PoC$DUS9!;OcYXKmk}PYnWk<3s*-mh_327k42@P?<5S)p_kid}X z05h2a1A&w_DQ!vHVLBN~=!A9}wz5qrX#kV0AsE{sj0%AyK;m7pC2QZ8x8J=zSH3UL zs=P)PGUGRMM^Epa`|dga`~SZ0|9$72D{=94N(7{f_?#MnivXR5Ar}F<2+-?uA{QC7 zKnxNB}?OM9|(`v6~zKjIyyRPFTecq&AP6)0*R$D#&hmP&NR(^k3II-&pz~_ z59Ln>)TvBNd?)YRxpU=>H{ST~ilSU2Nm3k8&$(+$<3z{z{fy^%`}geG^ZuJ}zIgy} zPu8_l0VF^0zyqnzeC9Jxsj7PY(iq#q_QG+TT~9pm#0R!--#!9(^ZokC01}_8M~)o1 zrMbEJYqBg?FKpMA#;h5GvXzXJ;M`Jz(+ z6dxKI`fNi(!>7(q*UtV|eP2Z8`CpQBZjx?zc=(~l#zp~$=!`N0GnZPhPR`6bWO{q_i>ujz=6F={?P88)^}3IvXZkumF{r<9V2w z0{%mS`j1XaLfgn%o5mQ0QxfSvjUn=~shj zKO7``*9!EuniCiz+B*BkNPcIC(w$92uUUbjYbdG`v^yP}E+~M?z?2-^u4(9;!5PaV z9nF9uQ~X#vTGA*pM6ks3Jc`pf5?|~DL&IN{M5>ih*H$5~TZR(VPiQ~a!Cx={mBGnb zct7gJe|eM{-KH4xF^WDkMu>diGW2K!MN!J*6#mI7nONW0i$7vhjCfSdc*HCXZ*v36 zEiEYVlY?}@03?{;+79-UJ@_3%jK3w0dTk?`uHiTis-hB$Mp1PQso+rDb(F}a)kxI_ zj%AZC6mUH^V2f>9*uNTOc}EWE#(IpcYmrrDUM5`-015U8CKUT7(Z9H#>CI`h8&)H0 zulW%#Z($#uAb!~jeAj2@`6DE*X+o=tQQS9xygY$a8!u}m&vx*h?IZERB<_7zVl-3* z%$w_zmOMbAX$h80bqWbEj{WComtoHv`}7Mk|!?awVuqLcyi<{*}b8Y($n7Bv}doGN%eiU+t#ol`uYW8PRwQ zRaO6AWLpvdg_`DMxl{;H=I}7de?3a>k83gB-i&U{I%MnE1jRxLWz56u=qK4{GqC|u zogRKd!@53=etA9em`(Jm##w+0CCsOKN$sAXaQ|9jmn;tenkyuK?*NsflVC~r4DMtB z?b2E#O~E}pj$g2m)-S`4C}ajFNPhkx&YSCy-`$KBjRXVAz0gf|CX0U1M(HXNRb@tQ zucGSN36wF5>smNCL+$&n4gj(WChX{iUyfq_^#+plHA@d50gmT-IQzH>lD0OSK+LRllk!hmcL-)y{!@HntGI)7?y1Xp}aVmMSFUX zi1$|AvhwE@XrUyIXUe2tVVB0;CVjQSQg*$&}$PYhK8Lf!83g%4rIuGq#f5# ziF~;SPj^x7-axb_Nnv1$?9aQAuUw9=Ntn-ep>DneC91*R5sbBUBrk2EG?77m_yA}+ z)<;`0lCc0Bam<&M=&k?#N|Fa&HC;Q25Zy>s{5m}i{BE?Iz3&U;TREK9seDw&W z)v3VgR(*SvnK#EsysIrR4bdh$HG>+_af&9n-}GZ_sK=;D1pmj=NfcELqG9&G_EYn+ zMfTn`#OkZib$wp;h<+>@K$XR;&`G+Fyh_orP(HY!oMq3KJPI%!+oteT7pa$QvNy#@ z|2R+XmRh3MuL$nGI4=~GPRXKlY?4S*6zg2PbsP%M^^kgIn*1l4iLb6bDSH+L zpvp?0o3pS6rqL%%3XVnOhl6CUPZ8VNhN`In69V=eo0mWHn~G1q#o&G z;#!rO-^vu%t0>zpA-X&jup?xiXdg1L(rjGE#mW}&zIOn<8_Yj%BUYQ9H+n6K51Nfq zF78AL=cj%6uT10QObib^-$yA(v)bpGPSj`nkxZ4z>yjkz z*l^P5b>0CA=x)0>d&lse?xU2?VqccVYBZ1y4bOCuMhir)tS5fSvNFxZK=Q{Xz;ubV z)}m-?zy`sf;#8LGPdagH;*d~KeF3a)EFTAi8& z`H@#~(<<4vD3NO$NYz)D{m;qjLg&Q?l|%Tk8RV~bGFDqc+1iZ0EPlKw^P*ix~P!^z9(V2MY7xL(XOZum?NC?eCoNfCbZxHg+51=6s&LV z#ThMNf1s6!07sI7P#Oxv$>%$fOCGg1uSbtWK$G!>qW;Q0ls*UV{!394kw82;rp3hm z0o+$7h~3gkBpxk~BXIip67G{nh`*d+`V;NM>eE=JN%7bu^08bnxh}P&NVnDzF^qt* zm1ezY3@VopHTCHjTESR0oizo!)~VU#-+4osMnFty@~!^9u!L)wtWymb{k+D&A@ zrSQpCWFSyeL`h1Y#)LYMz=++ zNmtfxOw(lIxh|@IGJ||~J9^u)695tn3aW&nBx+d{I(kVzJ&t>M8gJAjGVYVRE{@SL zPUa0!^lfX2r4zxR=Q2_JhB=bOfA|2?%O#Av+K4CPWqlOdVrb$aD5nEw!FA9H0Og7} zJG$`?Wl%ochH=Skwk#4xQOy(1+Vwr$l8x=yL9!`kCsNPN5XnpUo{y*bOkAO${7EzM z)bV`j_>2UeAJle`>=U%vSFI{A} z))L#is_ZvP!_%nW?ZSIUGbP0#_JclA?FwdfMa@~B@EB^Bv1?RTJzv79QBYzEG7^dp z`Kzi?uUmoggKml&Q&hdBb(V#uNpAlz>2D2Fe0vqKEp4Y%$a7~ePWJzK@j=32&J2vB zKYS4BP4!4yRt2*S!tV1!lbGK-jJ~A>TeXpQ4B$Cnl@_|NBQs^}1N^ zYDBxP3F-I(g35?zmn^*9y~KBoF>sYedPpWZBQx?wmHPiF;kTu*ZfioVdMy%`9jxc$ zh)KjBc4HL^l_N(`x>;i>T>oi z=B%ua0n{f3nOyHYUIzZ*JgD@SDL*)wC&-atwHH{_iu$a&j96+H63hwVSO zAGHSsb>m`&aC>@XD9OlM%oTmp+vkY8C*s%)7n2y@+;lUe59H9hK2%lO#Dx7M2*ktr^=FTWs+*|8z=s6N6CJy zndqe#DnO=*^Z8#BSy6*`$C{w_Q@P43bflq)jqN>#Qz%h)RV!LVFBk9U*0n`7z}(Y^ z`osX{U#=(FRJ$PBXHghbsfk1~n)%6LQZHoi-ntUqRq$6vQP(Z6vY$Eh6K8 zWCKR?tR^Z0WZHN??M52TBW+)goPLdMfqbwW>>b@mKO4qVidcqE>5jFeFI#zn_IbVG z{k$ZrbECjq(U_T>CObAwR5D0>XBf7xAo<3opr|gYh9czq&yOHKJB%j^@>eI3Z&`z~ zJQmE=3*BT*7jPdrM84K1cS9ALu2WsN3_UU{4(D6rbFL9=!OI=s{z6BUmelSWKDokdDq!%4W%4mBy>li2h=NWXV8ot-@b!1O;ekx`_9iF|s#T1uMwH z>R?ztT`2vtC=99;jUqn_CIExP)FSul5bmBnswOmKU#1XuDXmYSUa^eG%2ZG{kLP-iLskP2v44x^_ZS&-2{Q&dz^azka>gPsWzkH7Ps@7!^GL*4(|w|dGg znRniK=bF!b?sH#mX=(X`+|=9K+xy~ex7~KfOE0}Ncs5%ygBu8FKx*5zZS4;}_~4hC znwna5T~A1obXMkergw9(+WEfkx~^O3>FL>b-+lMp`}pIJ9|E$XcyKnGdPRQ)5D_5V z($Z3Q&pr2STDx{_yI~kH-}ldL|KnnzaEj}grkU^W@9%l^(MLOW@7~=DWRA0E-o5ju z8)yiUSZX0w69~Y>XQw-F2|zJ#7lh^zK!_dA#p8ZQyg#Q03xrn?3k${aE&+`Adm%r= zBYa+m07UGQ7stF06bJ`@rc9`OxXGEGvy@r1U=KH0s)Jm#eqHEnIPXAR1n4vnUj*nh a0R1lws!{ID{~tgA0000Px;NJ&INRA@u(nQLquLi>wWQCg5n(@IFCt>_Pmr~pwYx8N8N{&w0;#&dewA89$`YIDY<5V4*X3x(vvGI2SrgArPEA zPl?w|;G`Wpc4Rhe*sx7i)f**As$Iw^hhaG4IL^)|pL}xjrcIj)fFM*pW+IRAT(bk(sn+9 zkFlJT^Z4X?n2A757=}aVia;2Kh;xbzBne5H-76$XS_F(P7=a)N@JB8DKOG<}x&)4o zT%(ix)(VvB)T|^Hlz@o3Ydd%kzsdL|8G@7qQiyB?RPQdL+_#pbmOL$q1tB1Si^#iq z3#WS!@AV<1P3>i%VGt5N){FebMzqd4)Jg+cmS-gLsS^<3{ShIV{S*(568awD7wXZj zYa@|JL}lQ49;JUCKzeBm84ta+3gz2fBx+8T)Tc^7fcHu+{v+LtUZRq?Vo4O$gsh;c z8czQNq3xlstivi4@d%KzmHCu>QlQziAO67YSW{LvAVM|<(^TTLRPMbgOk z_LB@1)O-L%7jJ2bQa(ra@(UsYPToTQ*}DW=F2<;@L{XHFy6(Ajq3PBr2o%Q)C=cw$ z`Tk0bmKr1^T-U{VdOztG2MHyaVtWSt&MuNkHJZBe`v%b-I|M&pjZu-AQV-`O196dh zNAt)PDw3+;AI(8ymcsBj>egO@`#(pjGJ|_?gxvc>B%c|@Yfa!)N~GV;VSRlOMs*Fw z!BUm6S6xe1S0T`hyUn1 zjI}`WE6vz@hmi{|+S;Yq-N&hVb_ny{cKm-FK@UN{{UY+aj$jsxU^>V}AHUHc?LzkR zD=2N>jgwMPzp(i-V`boYvM)JZbe033u)%Zx&H>8D-Qbxc5Sgw%VvMS#R~ja0&gZEfGL- zXaeb>ZmdcT)$uVBDy(0MblGBpm-`76P_J%|+{fMa2JRt~(s$aZy5vMaCd+4D1R{U# z8K(5-cPZS^fObJ$R9Mb|F;WSY^y(8$${8#W+CJK{$|%akL!;#1A0&BM{Rs;l%#(U* zfRr6j$oSFQYBtc2sxnxIhA95HgKRd7uIo`ircbDIl8^x6ST^26uT%JDGpUx^NShJB zqhm;ZNT#9!rQX1Q;Rw=zi+oEf{;-MVIh2m&$aFSDU_>B_BRNvP?xlEl2TE;<($NW| zyo=IYL8h)U(!KycJ^FLbK-7?qjUjLC#`)oDQuP((IYwM_@;36;caX1NhBt1b47m7L z*P{IHJ?y(yU@WgI7o)h}$`w$y^-{W_5v{Af9AyDm0RK4fb0Sc78{4L^b1z0#L%VTh znLtz*u8Vni6m9!{SY#kO;7=4N+_aRcHO-Nh<8eR`D7@SU&-Eky=ps^vQNAWJ^l?+@ zoNY8FP#m1Vf20Thdn?E;Uj&Yi|H=U2rFBUEK8o+UkyMmJ4z31@s-SdMqRI)vb&Cmy zE##&wiTVtMkqMMX--TP-FghBh5SZ;P@T_YfCSZ=|34XN;>FzF4jn#OTL;gQ~=$9_W zdj2qu@4?{$bptA4+M_7BSRGjuB|&Q468ynDT1z#0eI=z_5%(8w!#(X(EU!Ps>rY4G ztOiH4EJ33JsGSPEl36(w^CHeRP%tZ-QS4PeG`0JaHFRd&4c~ry` zC4$HIkZ+L4-rN-%8-wFWtQqc_7mRTj=rMygvGwt2Y(wNca?#DT`Q?nYQ|>uiDw{=w8%oKR3hB5 z2WL$cRoArQ59D!8n_|)@S(8S3d4R-m5BH{K{0W<+oIuZoC>=HA_Z>%h;vnHqSE1?p zDTCT<`k#{v#fwI-hQ5z&S@>Ia<8SUDU6H~5a2VGtk?vX&^#$wLc=VQ)T3#DT!|op^ z=r5AJq8Zz=2_AkM>Fy4UrPVVws@X`)sRqQNWjife1e;%jht@?A6w(ma;*KeXJ88It zdTmQYAf9)`b&);!mp!-}TS+hbs5i_8dfqiKnLx?5@gDpy+_4IMS#`MpV2!z6r(a6=pV+NEVj@(zwg z6REQLgguJGE!gr3C zLHwr=x<|ZU1P9c}L zsN-x2lA?bIz~WjAUI3p9_<2icax`LF6#zw6Vo9)Bi}zXwf@Z2VhHU_McgF_cQBF}0u zcXE%I?^=8I?0KTIv-1Z)?sPjeVwXW%vu4e*?c29M(AL(rURBk~`96F;|92c`qOY&- zxs4k)Ztm{xJ_MAeG`ATR7aw9$fRIoYNG{-3tJunMf&5A7Skt##KW<+~{4TzKi*q6O zisLV!Z literal 0 HcmV?d00001 diff --git a/src/all/sakuramanhwa/res/mipmap-xhdpi/ic_launcher.png b/src/all/sakuramanhwa/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..e3505ab614fc6de181ee9224c10c5545c85f6321 GIT binary patch literal 6923 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 R7JmQ$002ovPDHLkV1g*WO*8-i literal 0 HcmV?d00001 diff --git a/src/all/sakuramanhwa/res/mipmap-xxhdpi/ic_launcher.png b/src/all/sakuramanhwa/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..1ceeaf311a806df84c7fb6fee22f7ced9a241294 GIT binary patch literal 13483 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, +) + +@Serializable +internal class MangaInfo( + val details: MangaDetails, + val follows: Int, + val views: Int, +) + +@Serializable +internal class MangaDetails( + val id: String, + val title: String, + val img: String, + val description: String?, + val language: String, + val slug: String, + val type: String, + val status: String?, + val author: String, + val rating: Float?, + val create_at: String?, +) + +@Serializable +internal class ChapterInfo( + val id: String, + val title: String?, + val create_at: String, + val number: Float, +) + +@Serializable +internal class ApiChapterInfo( + val chapter: PageInfo, + val prev_chapter: String?, + val next_chapter: String?, +) + +@Serializable +internal class PageInfo( + val id: String, + val number: Float, + val title: String?, + val images: List, +) + +@Serializable +internal class ApiMangaList( + val mangas: List, + val next_page: Int?, + val prev_page: Int?, + val max_pages: Int?, +) + +@Serializable +internal class I18nDictionary( + val home: I18nHomeDictionary, + val library: I18nLibraryDictionary, +) + +@Serializable +internal class I18nHomeDictionary( + val updates: I18nHomeUpdatesDictionary, + val lastUpdatesNormal: String, +) + +@Serializable +internal class I18nHomeUpdatesDictionary( + val buttons: I18nHomeButtonsDictionary, +) + +@Serializable +internal class I18nHomeButtonsDictionary( + val language: Map, // all, spanish, english, chinese, raw + val genres: Map, // all, mature, normal +) + +@Serializable +internal class I18nLibraryDictionary( + val title: String, + val search: String, + val sort: Map, // title, type, rating, date + val filter: Map, // title, category, language, sortBy +) diff --git a/src/all/sakuramanhwa/src/eu/kanade/tachiyomi/extension/all/sakuramanhwa/CryptoHelper.kt b/src/all/sakuramanhwa/src/eu/kanade/tachiyomi/extension/all/sakuramanhwa/CryptoHelper.kt new file mode 100644 index 000000000..bf2045e74 --- /dev/null +++ b/src/all/sakuramanhwa/src/eu/kanade/tachiyomi/extension/all/sakuramanhwa/CryptoHelper.kt @@ -0,0 +1,191 @@ +package eu.kanade.tachiyomi.extension.all.sakuramanhwa + +import android.util.Base64 +import eu.kanade.tachiyomi.network.GET +import keiyoushi.utils.toJsonString +import okhttp3.Headers +import okhttp3.Interceptor +import okhttp3.OkHttpClient +import okhttp3.Response +import okio.IOException +import java.nio.charset.StandardCharsets +import java.security.MessageDigest +import java.security.SecureRandom +import javax.crypto.Cipher +import javax.crypto.Mac +import javax.crypto.spec.IvParameterSpec +import javax.crypto.spec.SecretKeySpec + +class CryptoHelper( + private val baseUrl: String, + private val secretKey: String, + private val encryptKey: String, +) : Interceptor { + private var client: OkHttpClient? = null + fun setClient(c: OkHttpClient) { + client = c + } + + @Volatile + private var serverTimeAtGeneration: Long? = null + private var localTimeAtGeneration: Long? = null + + private val cacheValidityMillis = 4 * 60 * 1000 // 5min expiration + + private var cachedSignedData: CacheState? = null + + private class CacheState(val cachedSignedData: String, val cachedSigneTime: Long) + + @Synchronized + fun initServerTime() { + if (serverTimeAtGeneration != null) { + return + } + + val response = + client!!.newCall(GET("$baseUrl/v1", Headers.Builder().set("NX", "").build())).execute() + val serverTime = response.headers.getDate("Date")?.time + ?: throw IOException("Uninitialized server date") + + this.localTimeAtGeneration = System.currentTimeMillis() + this.serverTimeAtGeneration = serverTime + this.cachedSignedData = null + } + + @Synchronized + fun generateSigned(): String { + if (serverTimeAtGeneration == null) { + throw Exception("Uninitialized time") + } + + val currentTime = System.currentTimeMillis() + if (cachedSignedData != null && currentTime - cachedSignedData!!.cachedSigneTime <= cacheValidityMillis) { + return cachedSignedData!!.cachedSignedData + } + + val timestamp = getCurrentServerTime(currentTime).toString() + + val dataToHash = mapOf(Pair("timesTamp", timestamp)).toJsonString() + val hash = hmacSha256(dataToHash, secretKey) + val dataToEncrypt = mapOf(Pair("hash", hash), Pair("timesTamp", timestamp)).toJsonString() + + val encrypted = AESCrypt.encrypt(dataToEncrypt, encryptKey) + + cachedSignedData = CacheState( + encrypted, + currentTime, + ) + return cachedSignedData!!.cachedSignedData + } + + fun hmacSha256(data: String, key: String): String { + val secretKeySpec = SecretKeySpec(key.toByteArray(), "HmacSHA256") + val mac = Mac.getInstance("HmacSHA256") + mac.init(secretKeySpec) + val hashBytes = mac.doFinal(data.toByteArray()) + return hashBytes.joinToString("") { "%02x".format(it) } + } + + private fun getCurrentServerTime(currentLocalTime: Long): Long { + val elapsedLocalMillis = currentLocalTime - localTimeAtGeneration!! + return serverTimeAtGeneration!! + elapsedLocalMillis + } + + override fun intercept(chain: Interceptor.Chain): Response { + var request = chain.request() + if (request.headers["NX"] == null && serverTimeAtGeneration == null) { + initServerTime() + } + if (request.headers["NX"] == null && request.url.toString().startsWith(baseUrl)) { + request = request.newBuilder().apply { + header("Referer", "$baseUrl/") + header("St-soon", generateSigned()) + }.build() + } + return chain.proceed(request) + } + + private object AESCrypt { + private const val SALTED_PREFIX = "Salted__" + private const val KEY_SIZE = 32 + private const val IV_SIZE = 16 + private const val SALT_SIZE = 8 + private const val MD5_ALGORITHM = "MD5" + private const val AES_ALGORITHM = "AES" + private const val CIPHER_TRANSFORMATION = "AES/CBC/PKCS5Padding" + + fun encrypt(value: String, key: String): String { + val salt = ByteArray(SALT_SIZE).apply { + SecureRandom().nextBytes(this) + } + + val (keyBytes, iv) = deriveKeyAndIV(key, salt) + + val secretKeySpec = SecretKeySpec(keyBytes, AES_ALGORITHM) + val ivParameterSpec = IvParameterSpec(iv) + + val cipher = Cipher.getInstance(CIPHER_TRANSFORMATION) + cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec) + + val encrypted = cipher.doFinal(value.toByteArray(StandardCharsets.UTF_8)) + + val resultBytes = ByteArray(SALTED_PREFIX.length + SALT_SIZE + encrypted.size) + + SALTED_PREFIX.toByteArray().copyInto(resultBytes) + salt.copyInto(resultBytes, SALTED_PREFIX.length) + encrypted.copyInto(resultBytes, SALTED_PREFIX.length + SALT_SIZE) + + return Base64.encodeToString(resultBytes, Base64.NO_WRAP) + } + + fun decrypt(encrypted: String, key: String): String { + val encryptedBytes = Base64.decode(encrypted, Base64.NO_WRAP) + + val salt = ByteArray(SALT_SIZE).also { + encryptedBytes.copyInto( + it, + 0, + SALTED_PREFIX.length, + SALTED_PREFIX.length + SALT_SIZE, + ) + } + + val (keyBytes, iv) = deriveKeyAndIV(key, salt) + + val secretKeySpec = SecretKeySpec(keyBytes, AES_ALGORITHM) + val ivParameterSpec = IvParameterSpec(iv) + + val cipher = Cipher.getInstance(CIPHER_TRANSFORMATION) + cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec) + + val ciphertext = ByteArray(encryptedBytes.size - (SALTED_PREFIX.length + SALT_SIZE)) + encryptedBytes.copyInto(ciphertext, 0, SALTED_PREFIX.length + SALT_SIZE) + + val decrypted = cipher.doFinal(ciphertext) + + return String(decrypted, StandardCharsets.UTF_8) + } + + private fun deriveKeyAndIV(key: String, salt: ByteArray): Pair { + val keyBytes = key.toByteArray(StandardCharsets.UTF_8) + val result = ByteArray(KEY_SIZE + IV_SIZE) + val md5 = MessageDigest.getInstance(MD5_ALGORITHM) + + var currentResult = md5.digest(keyBytes + salt) + currentResult.copyInto(result, 0, 0, currentResult.size) + + var keyMaterial = currentResult.size + while (keyMaterial < result.size) { + currentResult = md5.digest(currentResult + keyBytes + salt) + val copyLength = minOf(currentResult.size, result.size - keyMaterial) + currentResult.copyInto(result, keyMaterial, 0, copyLength) + keyMaterial += copyLength + } + + return Pair( + result.copyOfRange(0, KEY_SIZE), + result.copyOfRange(KEY_SIZE, KEY_SIZE + IV_SIZE), + ) + } + } +} diff --git a/src/all/sakuramanhwa/src/eu/kanade/tachiyomi/extension/all/sakuramanhwa/Filter.kt b/src/all/sakuramanhwa/src/eu/kanade/tachiyomi/extension/all/sakuramanhwa/Filter.kt new file mode 100644 index 000000000..974d4b816 --- /dev/null +++ b/src/all/sakuramanhwa/src/eu/kanade/tachiyomi/extension/all/sakuramanhwa/Filter.kt @@ -0,0 +1,143 @@ +package eu.kanade.tachiyomi.extension.all.sakuramanhwa + +import eu.kanade.tachiyomi.source.model.Filter +import okhttp3.HttpUrl.Builder + +internal const val GroupTypeNone = -1 +internal const val GroupTypeSearch = 0 +internal const val GroupTypeLatesUpdates = 1 + +internal class GroupFilter( + i18n: I18nDictionary, +) : Filter.Select( + "", + arrayOf( + i18n.library.title, + i18n.home.lastUpdatesNormal, + ), +) { + fun setUrlPath(builder: Builder): Int { + val path = when (state) { + GroupTypeSearch -> "/v1/manga" + GroupTypeLatesUpdates -> "/v1/manga/search/latesUpdates" + else -> { + throw UnsupportedOperationException() + } + } + builder.encodedPath(path) + return state + } +} + +internal open class UrlFilter( + title: String, + private val requireState: Int, + val lis: List>, +) : Filter.Select( + title, + lis.map { it[0] }.toTypedArray(), +) { + fun checkGroupState(groupState: Int): Boolean { + if (state != 0 && (requireState == GroupTypeNone || requireState == groupState)) { + return true + } + state = 0 + return false + } +} + +internal class CategoryFilter( + i18n: I18nDictionary, + lis: List> = listOf( + listOf(i18n.home.updates.buttons.genres["all"]!!, "author", ""), + listOf(i18n.home.updates.buttons.genres["mature"]!!, "author", "mature"), + listOf(i18n.home.updates.buttons.genres["normal"]!!, "author", "normal"), + ), +) : UrlFilter( + i18n.library.filter["category"]!!, + GroupTypeNone, + lis, +) { + fun setUrlParam(builder: Builder, groupState: Int) { + if (!checkGroupState(groupState)) { + return + } + if (groupState == GroupTypeSearch) { + builder.setQueryParameter("${lis[state][1]}[]", lis[state][2]) + } else { + builder.setQueryParameter(lis[state][1], lis[state][2]) + } + } +} + +internal class SortFilter( + i18n: I18nDictionary, + lis: List> = listOf( + listOf("_", "sort", ""), + listOf("${i18n.library.sort["title"]!!}⬇️", "sort", "title"), + listOf("${i18n.library.sort["title"]!!}⬆️", "sort", "title"), + listOf("${i18n.library.sort["type"]!!}⬇️", "sort", "type"), + listOf("${i18n.library.sort["type"]!!}⬆️", "sort", "type"), + listOf("${i18n.library.sort["rating"]!!}⬇️", "sort", "rating"), + listOf("${i18n.library.sort["rating"]!!}⬆️", "sort", "rating"), + listOf("${i18n.library.sort["date"]!!}⬇️", "sort", "create_at"), + listOf("${i18n.library.sort["date"]!!}⬆️", "sort", "create_at"), + ), +) : UrlFilter( + i18n.library.filter["sortBy"]!!, + GroupTypeSearch, + lis, +) { + fun setUrlParam(builder: Builder, groupState: Int) { + if (!checkGroupState(groupState)) { + return + } + builder.setQueryParameter(lis[state][1], lis[state][2]) + builder.setQueryParameter("order", if (state % 2 == 1) "desc" else "asc") + } +} + +internal class LanguageCheckBoxFilter(name: String, val key: String) : Filter.CheckBox(name) { + override fun toString(): String { + return key + } +} + +internal class LanguageCheckBoxFilterGroup( + i18n: I18nDictionary, + data: LinkedHashMap = linkedMapOf( + i18n.home.updates.buttons.language["all"]!! to "", + i18n.home.updates.buttons.language["spanish"]!! to "esp", + i18n.home.updates.buttons.language["english"]!! to "eng", + i18n.home.updates.buttons.language["chinese"]!! to "ch", + i18n.home.updates.buttons.language["raw"]!! to "raw", + ), +) : Filter.Group( + i18n.library.filter["language"]!!, + data.map { (k, v) -> + LanguageCheckBoxFilter(k, v) + }, +) { + fun setUrlParam(builder: Builder, groupState: Int) { + if (state[0].state) { + // clear + state.forEach { it.state = false } + return + } + var langParam = false + state.forEach { + if (it.state) { + if (groupState == GroupTypeSearch) { + builder.addQueryParameter("language[]", it.toString()) + } else { + if (langParam) { + it.state = false + } else { + builder.addQueryParameter("language", it.toString()) + langParam = true + } + } + } + } + } +} diff --git a/src/all/sakuramanhwa/src/eu/kanade/tachiyomi/extension/all/sakuramanhwa/I18nHelper.kt b/src/all/sakuramanhwa/src/eu/kanade/tachiyomi/extension/all/sakuramanhwa/I18nHelper.kt new file mode 100644 index 000000000..c89fbfaa4 --- /dev/null +++ b/src/all/sakuramanhwa/src/eu/kanade/tachiyomi/extension/all/sakuramanhwa/I18nHelper.kt @@ -0,0 +1,44 @@ +package eu.kanade.tachiyomi.extension.all.sakuramanhwa + +import android.content.SharedPreferences +import eu.kanade.tachiyomi.network.GET +import keiyoushi.utils.parseAs +import keiyoushi.utils.toJsonString +import okhttp3.OkHttpClient +import okio.IOException + +internal class I18nHelper( + val baseUrl: String, + val client: OkHttpClient, + val preference: SharedPreferences, +) { + private val i18nCache: HashMap = run { + val i18nJson = preference.getString(APP_I18N_KEY, null) ?: return@run hashMapOf() + try { + i18nJson.parseAs>() + } catch (_: Exception) { + hashMapOf() + } + } + + @Synchronized + fun getI18nByLanguage(lang: String): I18nDictionary { + var i18nDictionary = i18nCache[lang] + if (i18nDictionary != null) { + return i18nDictionary + } + + val request = GET("$baseUrl/assets/i18n/$lang.json?v=2") + val response = client.newCall(request).execute() + if (!response.isSuccessful) { + response.close() + throw IOException("Unexpected get i18n(${request.url}) error") + } + i18nDictionary = response.parseAs() + i18nCache[lang] = i18nDictionary + + preference.edit().putString(APP_I18N_KEY, i18nCache.toJsonString()).apply() + + return i18nDictionary + } +} diff --git a/src/all/sakuramanhwa/src/eu/kanade/tachiyomi/extension/all/sakuramanhwa/SakuraManhwa.kt b/src/all/sakuramanhwa/src/eu/kanade/tachiyomi/extension/all/sakuramanhwa/SakuraManhwa.kt new file mode 100644 index 000000000..378537868 --- /dev/null +++ b/src/all/sakuramanhwa/src/eu/kanade/tachiyomi/extension/all/sakuramanhwa/SakuraManhwa.kt @@ -0,0 +1,374 @@ +package eu.kanade.tachiyomi.extension.all.sakuramanhwa + +import androidx.preference.ListPreference +import androidx.preference.PreferenceScreen +import eu.kanade.tachiyomi.network.GET +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 keiyoushi.utils.tryParse +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withContext +import okhttp3.HttpUrl.Companion.toHttpUrl +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.Response +import okio.IOException +import rx.Observable +import java.text.SimpleDateFormat +import java.util.Locale + +class SakuraManhwa( + override val lang: String = "all", +) : HttpSource(), ConfigurableSource { + override val name = "SakuraManhwa" + + override val supportsLatest = true + + override val baseUrl = "https://api.sakuramanhwa.com" + + private val apiImageUrl = "https://api.sakuramanhwa.com/v1/images" + private val cdnImageUrl = "https://cdn.sakuramanhwa.com/v1/images" + + private val secretKey = "EA^UfBOF9lNdQDS3i2qAnsqxIrTpH%" + private val encryptKey = "6dFGd4Laa3vE%kLpr5eCtSEaAL%wJm" + + private val apiCryptoHelper = CryptoHelper(baseUrl, secretKey, encryptKey) + + override val client: OkHttpClient = + network.cloudflareClient.newBuilder().addInterceptor(apiCryptoHelper) + .addInterceptor { chain -> + val request = chain.request() + val response = chain.proceed(request) + if (!response.isSuccessful && request.url.toString().startsWith(baseUrl)) { + throw IOException(response.body.string()) + } + response + }.build().also { apiCryptoHelper.setClient(it) } + + private val preference = getPreferences() + + private val i18nHelper: I18nHelper = I18nHelper("https://sakuramanhwa.com", client, preference) + + // Chapter + + private val dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSSSS", Locale.ENGLISH) + + override fun chapterListParse(response: Response): List { + val data = response.parseAs() + val tag = when (data.manga.details.type) { + "manhwa" -> "api" + "manga" -> "cdn" + else -> throw UnsupportedOperationException() + } + + val lis = mutableListOf() + data.chapters.forEach { + val chapterName = getChapterName(it.number) + lis.add( + SChapter.create().apply { + url = "$tag/v1/manga/${data.manga.details.slug}/chapter/$chapterName" + name = "${chapterName}${if (it.title != null) " ${it.title}" else ""}" + date_upload = dateFormat.tryParse(it.create_at) + chapter_number = it.number + }, + ) + } + + return lis + } + + private fun getChapterName(number: Float): String { + return if (number % 1 == 0f) "${number.toInt()}" else "$number" + } + + // Image + + override fun imageRequest(page: Page): Request { + val (tag, realUrl) = getTagUrl(page.imageUrl!!) + val prefixUrl = when (tag) { + "api" -> apiImageUrl + "cdn" -> cdnImageUrl + else -> throw UnsupportedOperationException() + } + return GET("$prefixUrl$realUrl", headers) + } + + override fun imageUrlParse(response: Response): String = throw UnsupportedOperationException() + + // LatestUpdates + + override fun fetchLatestUpdates(page: Int): Observable { + return focusFetchManga(page == 1, this::latestUpdatesRequest, this::latestUpdatesParse) + } + + override fun latestUpdatesParse(response: Response): MangasPage = popularMangaParse(response) + + override fun latestUpdatesRequest(page: Int): Request = GET( + baseUrl.toHttpUrl().newBuilder().encodedPath("/v1/manga/search/latesUpdates") + .addQueryParameter("limit", "72").addQueryParameter("page", "$page").build().toString(), + headers, + ) + + // Details + + override fun mangaDetailsParse(response: Response): SManga { + val data = response.parseAs() + + return mangaDetailsToSManga(data.manga.details).apply { + genre = listOf( + genre!!, + "follows: ${data.manga.follows}", + "views: ${data.manga.views}", + ).joinToString() + } + } + + private fun mangaDetailsToSManga(details: MangaDetails): SManga { + return SManga.create().apply { + url = "/v1/manga/findBySlug/${details.slug}" + title = getTitle(details.title, details.language) + genre = listOf( + "lang: ${details.language}", + "type: ${details.type}", + "author: ${details.author}", + "rating: ${details.rating}", + ).joinToString() + status = if (details.status == "ongoing") SManga.ONGOING else SManga.COMPLETED + thumbnail_url = "$baseUrl/v1/images/manga${details.img}" + } + } + + // Pages + + private fun getTagUrl(tagUrl: String): Pair { + return Pair(tagUrl.substring(0, 3), tagUrl.substring(3)) + } + + override fun fetchPageList(chapter: SChapter): Observable> { + val (tag, realUrl) = getTagUrl(chapter.url) + + val response = client.newCall(GET("$baseUrl$realUrl", headers)).execute() + val data = response.parseAs() + + val lis = mutableListOf() + data.chapter.images.forEachIndexed { index, it -> + lis.add(Page(index, imageUrl = "$tag/chapter$it")) + } + + return Observable.just(lis) + } + + override fun pageListParse(response: Response): List = + throw UnsupportedOperationException() + + // Popular + + // Independent page-turn count, used to force content filtering, single request if there is no content and there is the next page to continue the request + private var focusPage: Int = 1 + + private fun focusFetchManga( + reset: Boolean, + reqFunc: (page: Int) -> Request, + respFunc: (response: Response) -> MangasPage, + ): Observable { + if (reset) { + focusPage = 1 + } + var hasNextPage = true + var mangasPage: MangasPage? = null + while (hasNextPage) { + val request = reqFunc(focusPage) + val response = client.newCall(request).execute() + mangasPage = respFunc(response) + hasNextPage = mangasPage.hasNextPage + focusPage++ + if (mangasPage.mangas.isNotEmpty()) { + break + } + } + return Observable.just(mangasPage!!) + } + + override fun fetchPopularManga(page: Int): Observable { + return focusFetchManga(page == 1, this::popularMangaRequest, this::popularMangaParse) + } + + override fun popularMangaParse(response: Response): MangasPage { + val data = response.parseAs() + + val focus = preference.getString(APP_FOCUS_LANGUAGE_KEY, "")!! + + val lis = mutableListOf() + data.mangas.forEach { + if (focus == "" || focus == it.language) { + lis.add(mangaDetailsToSManga(it)) + } + } + + return MangasPage(lis, data.next_page != null) + } + + override fun popularMangaRequest(page: Int): Request { + return GET( + baseUrl.toHttpUrl().newBuilder().encodedPath("/v1/manga/views/top") + .addQueryParameter("limit", "72").addQueryParameter("page", "$page").build() + .toString(), + headers, + ) + } + + // Search + + override fun fetchSearchManga( + page: Int, + query: String, + filters: FilterList, + ): Observable { + return focusFetchManga( + page == 1, + { currentPage -> this.searchMangaRequest(currentPage, query, filters) }, + this::searchMangaParse, + ) + } + + override fun searchMangaParse(response: Response): MangasPage = popularMangaParse(response) + + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { + val url = baseUrl.toHttpUrl().newBuilder().apply { + var groupState = 0 + (if (filters.isEmpty()) getFilterList() else filters).forEach { filter -> + when (filter) { + is GroupFilter -> { + groupState = filter.setUrlPath(this) + } + + is CategoryFilter -> { + filter.setUrlParam(this, groupState) + } + + is SortFilter -> { + filter.setUrlParam(this, groupState) + } + + is LanguageCheckBoxFilterGroup -> { + filter.setUrlParam(this, groupState) + } + + else -> {} + } + } + + if (groupState == GroupTypeSearch && query.isNotBlank()) { + addQueryParameter("search", query) + } + addQueryParameter("limit", "72") + addQueryParameter("page", page.toString()) + }.build().toString() + + return GET(url, headers) + } + + private fun getTitle(title: String, lang: String): String { + return capitalizeWords(title.removeSuffix(lang)) + } + + private fun capitalizeWords(str: String): String { + return str.split(" ").joinToString(" ") { + it.replaceFirstChar { char -> + if (char.isLowerCase()) char.titlecase() else char.toString() + } + } + } + + // Filter + + override fun getFilterList(): FilterList { + val i18nDictionary = getI18nDictionary() + return FilterList( + GroupFilter(i18nDictionary), + CategoryFilter(i18nDictionary), + SortFilter(i18nDictionary), + LanguageCheckBoxFilterGroup(i18nDictionary), + ) + } + + override fun setupPreferenceScreen(screen: PreferenceScreen) { + ListPreference(screen.context).apply { + title = getI18nDictionary().library.filter["language"] + key = APP_LANGUAGE_KEY + entries = arrayOf( + "🇬🇧English", + "🇪🇸Español", + "🇨🇳中文", + "🇷🇺Русский", + "🇹🇷Türkçe", + "🇮🇩Bahasa Indonesia", + "🇹🇭ไทย", + "🇻🇳Tiếng Việt", + ) + entryValues = arrayOf( + "en", + "es", + "zh", + "ru", + "tr", + "id", + "th", + "vi", + ) + setDefaultValue(entryValues[0]) + setOnPreferenceChangeListener { _, click -> + try { + getI18nDictionary(click as String) + true + } catch (_: Exception) { + false + } + } + }.let { screen.addPreference(it) } + + // Lock the filter language type from the result. + // Non-locked content is simply ignored, which makes the experience more comfortable. + ListPreference(screen.context).apply { + val i18nDictionary = getI18nDictionary() + title = "👀➡️🔒" + key = APP_FOCUS_LANGUAGE_KEY + entries = arrayOf( + "🔓", + "🇬🇧${i18nDictionary.home.updates.buttons.language["english"]!!}🔒", + "🇪🇸${i18nDictionary.home.updates.buttons.language["spanish"]!!}🔒", + "🇨🇳${i18nDictionary.home.updates.buttons.language["chinese"]!!}🔒", + "${i18nDictionary.home.updates.buttons.language["raw"]!!}🔒", + ) + entryValues = arrayOf( + "", + "eng", + "esp", + "ch", + "raw", + ) + setDefaultValue(entryValues[0]) + }.let { screen.addPreference(it) } + } + + private fun getI18nDictionary(language: String? = null): I18nDictionary { + val currentLang = language ?: preference.getString(APP_LANGUAGE_KEY, "en")!! + return runBlocking { + withContext(Dispatchers.IO) { + i18nHelper.getI18nByLanguage(currentLang) + } + } + } +} + +internal const val APP_LANGUAGE_KEY = "APP_LANGUAGE_KEY" +internal const val APP_I18N_KEY = "APP_I18N_KEY" +internal const val APP_FOCUS_LANGUAGE_KEY = "APP_FOCUS_LANGUAGE_KEY"