From d7439b73e9397d83552ac46479c3db51ed9f8593 Mon Sep 17 00:00:00 2001 From: Pavka
T>mQz9s6HY{YM$kK{QjWFzR z4{%D5)(AA`*IQ+vTOpZ}%((Yn8v=W8Bk@1J^|HUTjPSbg06Ur0fMPs}Yr|s_U$6PO zMVEK=a02?xC9gD#?o9RMpI>s zlQk_U1nr$2u3~)5sB=OJlNCF0^CsgN*_^O$XG0!?pDE}Kx!l}?{p$+AR!^BKhFH?N zRw$;OFx<0USmx^m3q@K*LtXU)Yj{18vNqm;R*FnO;(N=@=Gxad9u8u8pMkJ_5u7Ih z#?mhyi(|_Sm<*D>RxZ1Z*!_LWg_8*K#V@Qm%}EzQ+!MLv9&tip9_r@g2mg@ldS8_L zJ}vFzq{U|#v8O
p9kuzZuW>j$-V&-gvbRZ(RL^I)zC$MJb>hlm6L M=KFj-O&@zD)W1MpoT1rXixKJra*$yhP`@CPk!3<%-Uiz9}ivT!^^yWK>9WYKO zr0NjVMT|G9g$aesj(>oDhdU2vi3rVZ#@P##0~~3;?814Ihl-78f%cK#sDL*XuN%;^ zCfiWl+=ur2`gUZj2r#TjSC&*6Ps&Mq%6tLE6#xtr9JqzU^Qb^I^L>`u8;dc_YEgnw z?cDxID}Lu@|5(}>_f|oI&bfhr9b|+UG-`X7Ael|HEJ=ccWZc{(+=;9Vjlh=XWq5ZK zEm35Cqe}`L3XAReD3SM>E@zJz%`8OPS8l~BSJK~;uwwZ7ct%5kYCB#5-1%6I7G~Fp z)7|Bk7T0U=$#eaJ?f5hh*aKmTbtEgv2^^{3iCw=NB_V%nhGeWYu?Ky2-j(^~>#I|> zt*Z>uZngGyFV0o`aTO@23#ZfXA8!oMGJGh%kv@bl3-{@2r@P5waCT{NM_FA!Qcd@E zVy#uSaJquA#k5anKC>tX0C~$fhnG1B{xi^Y;>^^dx+8F=Ci+JPWq(+RV_DP5f=;=e zIQ_H~H4MqylK ^KKAMD(e cIcazL> zOdCA15!dNwO3@GrST{JN?DFmthfh%mprrPT_}~f+3taajC z {&xsM@Fc?I zN~q$7_wn(-O%upW8BSL{ )XMADn9Cm`pwMk_O0-AYMMuBn-@NwTS zqOxYNy2;KioC6dtNXR`)iQtR1rnJf!{t66Hi6tyi^)_LYgrJOtLiO$Vjzpl{C7B&1 z&xR1oG_0t@^Kx8fGz(q->c%O|W^f9Gr$L8lXGpFSw>a%>R8~;sh{sj6NOC}EOIn~& zqY#0op)|jdFv8tkTsOAGe5B4FCp{Z%Kgh1E633TG4D|D+Z4-kOCr&B0*L>${uy2Kp z-b^iIgw?EWlH@oYqitK=P;ddVZRj8@H!K;+z8#^vN#_;*tg>cK)k*8mCG&ILhL#$< zBB*IkmI1)r8Z_H0Gg1`d>}{Z7J_^ 2e+GrPv_}Ve-F_yzH}B8u7pzWD_?- zD7x~qf%n`VTG4Gi&p{$r^um_$Vn8Ki_h3P{vFkz}DoBq#!Z$bmk+3MxRMF11h4=nu zz@5+X|3TW^MB*8b%qT0bGmB9{MnJQrh@UG_k4|_|;hjC5xZ~$qt~1P{fs8fWe9ZZ8 z#3jMw ?VU9+ zRNuD`=(cAGOQ*LCK7O2*EAu-k6vXh@j)|Pr{=J&)9!D;R?&)bOe0o+IMz|gla03Pz zZbQNP7eR;JLJ*})fu^TK0-SNzFlQMg1qyw~Mh_eWPVFKh@}etCPz_Fx6N3mt)h%-= z5a=Q4iu7UKLKqLpZV|-ToZxR%#V(x3Ien6sL>5vNL|pw}o+WW0MApZTZT(SEp{M-( zFlaf5a~^Zk@RjpOIs_;Q^R^!Me^Czw^T7lFa#fTlryH(89@KvjxLz2_4D2_bKj)7- zXyOOdq8bRcqlFQI0O`6f&-S|NM@=EN?rNZ;*1X1m-xh@mtHCp(fRaUTS=vzxU0JPx z8PZ;@k+GI5ghie6b8nD9eO%<*YrWPp5g@%XX~||$3NH(n3a70C7cFTJWb0n8S?qeG zv{h{eEsimpBgE+b5{ 6V<(shBtBwS$ELPy=AdvC6Dlw~=#@eIcOvvV}L zI1mx#>>CGuGr2L^j4XMfP2pe7m8|x#glst2a3k<_0Xlc_Mmk YO%O}>z8ung{}O~f}r>rS3X45gRp>mev!7*Z@8Y#@WG*u5$I3rZJ=~4 zn_enJz&%Q)@$*vRkr3`=1~9ijy K`e zdyD?~d3@asZfaHY^LlW=BDO52A3gX7ps-i>&jlnw5Ct{eSCV}S%)@6hU`XlmPw}a( zK|WpI_XqiWgC7-*{X$F|t3W2e1T-}bg||e|dt^CXLB>_`VY$EcriQhB1wrjcGhqd} zT&`V~%e#YwFckYj3W6K+Wien7q`dP1F3ts8>QjKq*09__cgSzVHQ(jK=m Q)hpzaIeI NE+c &j|qt|BDOO-3yPowApb z{IMkGAb^7M7Q3xj_UoEroUW&OS;k`y;|*jB=>W?OpeUKb-`Dvijv^qh1&ipTW6Qv9 zy|ep2^xlbeoiO-0a*i1&LWC K1vBsF7{1Rz776Aq3&bP7ofVZS5-PK+GaSP>mR|gYgJCvuw^3 zO=Y*|M8M=LfI&o-^U#y&H9fYD7ZD7eB!K4p`S{x s|ws0hi|I-O@{EN1w4>gVM=4U9MWjt^fXYvu;NDy16Y`>j3xjBtNFBKt_NfKqO z;Xh9lU2IDGeeq4#aof0dR(PC7Nn8vRq=WuI9-)fR-p$_0ZL)wg;xl3AItKw}CS*c; zy(u#=@CS9>%MWLOZ{&XD$?~cwq?49MH7`=p?psmN%`QFhPTZ1i ^68d4G~9oEWaAbc8#a@P7}~{6CyFxkl(ypS#2|Sqx69^E^JCjC zsWE3k5RPZew7mC1K52@X&G#rB_L(6a8jdF6GHR#%#)BAOa8KPg_AcBd4HFb-|6R zUYSA^b(aY$5(1jaba|H{()U}EJJVHDE8_6$UPD4J7E4MPpReI&(Anu4l@@*wRAhpH z{31`jEJ~cWU{=4z6xM=fN&`9y;t_+$i5Ajx*W}$gadxKqhRQBu4I8~YJAmMiI8)O_ zZu-dL7Xdn;J0?AffSLgKQF>WP=+^{cys-5~ZIAYCitK&}at8j-Phj24{9`WNEj!)S zA?M2X|Nb=ZA|mJj+uoC4Z`ZFU@L#y(okauz< kQu&U-{Z_xuif1m+l*m~}5=>JdNOH^}QAKsxyW zq^QINvvHhe3#bFB896aq0 0Z#YMLf8Wq{VCjPxQ98n|7%KXc< (HvT_MeWh;$eG>Q|zFc2KZn38W;dpa}<0pY{0z>ux0`2}78vT3j_(v{3MSI=> z#14b+L|uS3oth4`1MHEFZm~xM?PjY&_)M<&Gc}+p-oN6{K#}YJ!} U z;dGF}0hoXEAy^!mxJPpMi&8fjcJsqFf3d2eAl(g^W`h4iK-U$3$XmI?OmKNE_wqhf zsNp)U`7gaYh+Gb3|A#}W;Ku1xwZGWj0XN2@tEPc+yx}Sb=(QyE+k#s;|6y~GnYF%d zs5ZMt>aS$n!iYS_8n5AOW=I~U`umh!EPx(z?U{)+i#EB0rq2q%vf5w;ini|m*9m}u z>bmD*5{GadRK H&|qVPOzI{@bCnJ>2&s z^(ize#F@!^gPH&Ep9lXcK3P=+HC)l{7r+&314sR_AU)LlUFkISfWl&0`Wp=kV#lJj z92t~KHzy{IXtz-4PF#}XtdtV%P08N}nynOt6`BaAd4_b{RKG)h@|6KZ76-8+z`mZ? z6%uy?7LlSo*P~rd;o;-D*-xvziF!MFa)DQleMXVg+q@+R#l0ZLyE}ppzeom_ww#v! z@E`^EUMU9wSpiob{0+-0`}VpT(IC4DMvUKov{al;@xuv2zyW-!2Nj2_xfwlmC&%C- z!TafU=nuT1&9bzoeIF6-2H)*>a=LEdzw`d_-JdAA4BFh+Mrd4w70e3 |Hf zx_A(Ih%(qfnI9XZ#Tt+biS|6+XOkTU;r#(I8jYdE9?c)TCKE?NzD3=wAX|`mP)2am zC^rDVO=tY*{-gsy{>JhFkgqXv @chY4i4`aJey?8OZWze V^GmZjC zpLStcP570!JyEvX<0XI>2W<>=I>FhE 2hq5$$^JZhv z<7pa|g?Fx&IA(#qQ_Mx;2dTAEri&0v J;~fcMa&$qV8ot?D?MN)gsa>gd;gz+ z^K8GNk4Ko pjS?C%9id?`MkmC}!m@f_Gwd!}mE z^7Xs+D$x7t#AOAOqPkP0R$7DHRf^ANX!wtE{83 $3)F?$0)A&pR zqJjRl{?&HR)#dGM4llRw9Vg{8Z^A-UBhmu6-B^PRV+S`@IC|orlF!p?yMdqT)Q{LA z{{b}mI%+iA+m6w exlK&ky zl)WoMfjK0@4o-qM4uS%w`H&Ds6|}9F!IIwL!v41rfM&4X1KjgNq!sE!1t96_NJKve zH*wU8UdG+nF|86h!EM4`1*^oZdmn+|!UWr2+|)9#(GL#i*?ydh!)W@d@FIUqMi(`F zx^)L jo zljs6rfHTO@q~^N1Je6DJ#YJ=3LwkoR#~@7pV6lj4d8F|q*nRiEi$T^l%n1?Z%WmJt zb2(UfB4AL`a{XczPmSz%tPlrm{w12QE?a!s0TICLfd<-*+I_r07$w0rXIhFGW-VO# zfpO=1;(+6|(rLFbFe|#Vu+1CV{RMzZJ>{lP)E!fZ_fLx6b{&nKfpev3pYMkXPrzw$ zK)QWkWp_h4NF{CYv@&y63bgx`cY2u$V!+-4g(!s$tTJEdsZ$__Kibauaraj|qh+5x z)FlmGwDt+oAe+0QMnyuep{JhOnnnjZvbGDu)1u%_)rs6U)pz5}@QWtaSGaYajwdn1 zmr|U7W*-9__^^let%1= )M zvHOi52oe35<)ZW{*UTw>CR}WvIpv%=bvAoSi|O6gaG8@ozlkHOS&hNja|5NOO5xzI ze5G3Y&xHF7<~2Q|Ww18&we5Elz-O>g=@5Fk<@uVs0b^N*sZrvb)-jf`uZ@v}0}O8L zAY8tv5!T$KVYC+)DB!R8?+7nt%8`~<#JJhk>^(iEL{svll~(LMp;>7m=Zh%~Fn9AS zIPL*JR9!BV{v7x5z=#-pUv2VZcEDo3a=$-Ve=nT9BODv@-tTFkt5X)sM(^N*gF8Mv zf3&%~-~B!Rg9F}&=B1uQnZ}gB6IbDhW|%!gieS#~;4~-V-prJ?%1o|nZ^m_ZPS3yj zHIQ-_y-Jvfo?B@ySKFvmaG5wzO11M}iHd9Xln9TjP<0tkn{v}RIVV>E`mbOe$kuRI zLGG>{=B-{tJK+S5l@#r$o^5#C@By5$>+dd5(J;$*=C8Fkt`TDRo*&wd{mwD(z&xBj zyJuzJ*Q%0ct$p8X8|AoV%>Vwg#{U=pw89ZZw#Li2?(2UIfiLVys}ohn+^_u~G=*W$ literal 0 HcmV?d00001 diff --git a/src/ru/yaoichan/src/eu/kanade/tachiyomi/extension/ru/yaoichan/Yaoichan.kt b/src/ru/yaoichan/src/eu/kanade/tachiyomi/extension/ru/yaoichan/Yaoichan.kt new file mode 100644 index 000000000..36032efd8 --- /dev/null +++ b/src/ru/yaoichan/src/eu/kanade/tachiyomi/extension/ru/yaoichan/Yaoichan.kt @@ -0,0 +1,269 @@ +package eu.kanade.tachiyomi.extension.ru.yaoichan + +import eu.kanade.tachiyomi.annotations.Nsfw +import eu.kanade.tachiyomi.lib.ratelimit.RateLimitInterceptor +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.source.model.Filter +import eu.kanade.tachiyomi.source.model.FilterList +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.ParsedHttpSource +import java.text.SimpleDateFormat +import java.util.Locale +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.Response +import org.jsoup.nodes.Document +import org.jsoup.nodes.Element + +@Nsfw +class Yaoichan : ParsedHttpSource() { + + override val name = "Yaoichan" + + override val baseUrl = "https://yaoi-chan.me" + + override val lang = "ru" + + override val supportsLatest = true + + private val rateLimitInterceptor = RateLimitInterceptor(2) + + override val client: OkHttpClient = network.client.newBuilder() + .addNetworkInterceptor(rateLimitInterceptor).build() + + override fun popularMangaRequest(page: Int): Request = + GET("$baseUrl/mostfavorites?offset=${20 * (page - 1)}", headers) + + override fun latestUpdatesRequest(page: Int): Request = + GET("$baseUrl/manga/new?offset=${20 * (page - 1)}", headers) + + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { + val url = if (query.isNotEmpty()) { + "$baseUrl/?do=search&subaction=search&story=$query&search_start=$page" + } else { + + var genres = "" + var order = "" + var statusParam = true + var status = "" + for (filter in if (filters.isEmpty()) getFilterList() else filters) { + when (filter) { + is GenreList -> { + filter.state.forEach { f -> + if (!f.isIgnored()) { + genres += (if (f.isExcluded()) "-" else "") + f.id + '+' + } + } + } + is OrderBy -> { + if (filter.state!!.ascending && filter.state!!.index == 0) { + statusParam = false + } + } + is Status -> status = arrayOf("", "all_done", "end", "ongoing", "new_ch")[filter.state] + } + } + + if (genres.isNotEmpty()) { + for (filter in filters) { + when (filter) { + is OrderBy -> { + order = if (filter.state!!.ascending) { + arrayOf("", "&n=favasc", "&n=abcdesc", "&n=chasc")[filter.state!!.index] + } else { + arrayOf("&n=dateasc", "&n=favdesc", "&n=abcasc", "&n=chdesc")[filter.state!!.index] + } + } + } + } + if (statusParam) { + "$baseUrl/tags/${genres.dropLast(1)}$order?offset=${20 * (page - 1)}&status=$status" + } else { + "$baseUrl/tags/$status/${genres.dropLast(1)}/$order?offset=${20 * (page - 1)}" + } + } else { + for (filter in filters) { + when (filter) { + is OrderBy -> { + order = if (filter.state!!.ascending) { + arrayOf("manga/new", "manga/new&n=favasc", "manga/new&n=abcdesc", "manga/new&n=chasc")[filter.state!!.index] + } else { + arrayOf("manga/new&n=dateasc", "mostfavorites", "catalog", "sortch")[filter.state!!.index] + } + } + } + } + if (statusParam) { + "$baseUrl/$order?offset=${20 * (page - 1)}&status=$status" + } else { + "$baseUrl/$order/$status?offset=${20 * (page - 1)}" + } + } + } + return GET(url, headers) + } + + override fun popularMangaSelector() = "div.content_row" + + override fun latestUpdatesSelector() = popularMangaSelector() + + override fun searchMangaSelector() = popularMangaSelector() + + override fun popularMangaFromElement(element: Element): SManga { + val manga = SManga.create() + manga.thumbnail_url = element.select("div.manga_images img").first().attr("src") + element.select("h2 > a").first().let { + manga.setUrlWithoutDomain(it.attr("href")) + manga.title = it.text() + } + return manga + } + + override fun latestUpdatesFromElement(element: Element): SManga = popularMangaFromElement(element) + + override fun searchMangaFromElement(element: Element): SManga = popularMangaFromElement(element) + + override fun popularMangaNextPageSelector() = "a:contains(Вперед)" + + override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector() + + override fun searchMangaNextPageSelector() = "a:contains(Далее), ${popularMangaNextPageSelector()}" + + override fun mangaDetailsParse(document: Document): SManga { + val infoElement = document.select("table.mangatitle").first() + val descElement = document.select("div#description").first() + val imgElement = document.select("img#cover").first() + val rawCategory = infoElement.select("tr:eq(1) > td:eq(1)").text() + val category = if (rawCategory.isNotEmpty()) { + rawCategory.toLowerCase() + } else { + "манга" + } + val manga = SManga.create() + manga.author = infoElement.select("tr:eq(2) > td:eq(1)").text() + manga.genre = infoElement.select("tr:eq(5) > td:eq(1)").text().split(",").plusElement(category).joinToString { it.trim() } + manga.status = parseStatus(infoElement.select("tr:eq(4) > td:eq(1)").text()) + manga.description = descElement.textNodes().first().text() + manga.thumbnail_url = imgElement.attr("src") + return manga + } + + private fun parseStatus(element: String): Int = when { + element.contains("перевод завершен") -> SManga.COMPLETED + element.contains("перевод продолжается") -> SManga.ONGOING + else -> SManga.UNKNOWN + } + + override fun chapterListSelector() = "table.table_cha tr:gt(1)" + + override fun chapterFromElement(element: Element): SChapter { + val urlElement = element.select("a").first() + + val chapter = SChapter.create() + chapter.setUrlWithoutDomain(urlElement.attr("href")) + chapter.name = urlElement.text() + chapter.date_upload = element.select("div.date").first()?.text()?.let { + SimpleDateFormat("yyyy-MM-dd", Locale.US).parse(it).time + } ?: 0 + return chapter + } + + override fun pageListParse(response: Response): List { + val html = response.body()!!.string() + val beginIndex = html.indexOf("fullimg\":[") + 10 + val endIndex = html.indexOf(",]", beginIndex) + val trimmedHtml = html.substring(beginIndex, endIndex).replace("\"", "") + val pageUrls = trimmedHtml.split(',') + + return pageUrls.mapIndexed { i, url -> Page(i, "", url) } + } + + override fun pageListParse(document: Document): List { + throw Exception("Not used") + } + + override fun imageUrlParse(document: Document) = "" + + private class GenreList(genres: List ) : Filter.Group ("Тэги", genres) + private class Genre(name: String, val id: String = name.replace(' ', '_')) : Filter.TriState(name) + private class Status : Filter.Select ("Статус", arrayOf("Все", "Перевод завершен", "Выпуск завершен", "Онгоинг", "Новые главы")) + private class OrderBy : Filter.Sort("Сортировка", + arrayOf("Дата", "Популярность", "Имя", "Главы"), + Selection(1, false)) + + override fun getFilterList() = FilterList( + Status(), + OrderBy(), + GenreList(getGenreList()) + ) + + /* [...document.querySelectorAll("li.sidetag > a:nth-child(1)")] + * .map(el => `Genre("${el.getAttribute('href').substr(6)}")`).join(',\n') + * on https://yaoi-chan.me/catalog + */ + private fun getGenreList() = listOf( + Genre("18 плюс"), + Genre("bdsm"), + Genre("арт"), + Genre("бара"), + Genre("боевик"), + Genre("боевые искусства"), + Genre("вампиры"), + Genre("веб"), + Genre("гарем"), + Genre("гендерная интрига"), + Genre("героическое фэнтези"), + Genre("групповой секс"), + Genre("детектив"), + Genre("дзёсэй"), + Genre("додзинси"), + Genre("драма"), + Genre("игра"), + Genre("инцест"), + Genre("искусство"), + Genre("история"), + Genre("киберпанк"), + Genre("комедия"), + Genre("литРПГ"), + Genre("махо-сёдзё"), + Genre("меха"), + Genre("мистика"), + Genre("мужская беременность"), + Genre("музыка"), + Genre("научная фантастика"), + Genre("омегаверс"), + Genre("переодевание"), + Genre("повседневность"), + Genre("постапокалиптика"), + Genre("приключения"), + Genre("психология"), + Genre("романтика"), + Genre("самурайский боевик"), + Genre("сборник"), + Genre("сверхъестественное"), + Genre("сетакон"), + Genre("сказка"), + Genre("спорт"), + Genre("супергерои"), + Genre("сэйнэн"), + Genre("сёдзё"), + Genre("сёдзё-ай"), + Genre("сёнэн"), + Genre("сёнэн-ай"), + Genre("тентакли"), + Genre("трагедия"), + Genre("триллер"), + Genre("ужасы"), + Genre("фантастика"), + Genre("фурри"), + Genre("фэнтези"), + Genre("школа"), + Genre("эротика"), + Genre("юмор"), + Genre("юри"), + Genre("яой"), + Genre("ёнкома") + ) +}