From 9e00ce42fee6122e85f983716ff884b05b34dc13 Mon Sep 17 00:00:00 2001 From: e-shl <35057681+e-shl@users.noreply.github.com> Date: Sun, 19 Dec 2021 17:05:21 +0500 Subject: [PATCH] New [RU] Hentailib (#10135) * New [RU] Hentailib * -mangalib * option change language in latest * icon * icon microperfect --- src/ru/libhentai/AndroidManifest.xml | 25 + src/ru/libhentai/build.gradle | 17 + .../libhentai/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 1886 bytes .../libhentai/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 1578 bytes .../res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 2549 bytes .../res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 3670 bytes .../res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 5052 bytes src/ru/libhentai/res/web_hi_res_512.png | Bin 0 -> 10925 bytes .../extension/ru/libhentai/LibHentai.kt | 889 ++++++++++++++++++ .../ru/libhentai/LibHentaiActivity.kt | 41 + 10 files changed, 972 insertions(+) create mode 100644 src/ru/libhentai/AndroidManifest.xml create mode 100644 src/ru/libhentai/build.gradle create mode 100644 src/ru/libhentai/res/mipmap-hdpi/ic_launcher.png create mode 100644 src/ru/libhentai/res/mipmap-mdpi/ic_launcher.png create mode 100644 src/ru/libhentai/res/mipmap-xhdpi/ic_launcher.png create mode 100644 src/ru/libhentai/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 src/ru/libhentai/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 src/ru/libhentai/res/web_hi_res_512.png create mode 100644 src/ru/libhentai/src/eu/kanade/tachiyomi/extension/ru/libhentai/LibHentai.kt create mode 100644 src/ru/libhentai/src/eu/kanade/tachiyomi/extension/ru/libhentai/LibHentaiActivity.kt diff --git a/src/ru/libhentai/AndroidManifest.xml b/src/ru/libhentai/AndroidManifest.xml new file mode 100644 index 000000000..c33cfb934 --- /dev/null +++ b/src/ru/libhentai/AndroidManifest.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="eu.kanade.tachiyomi.extension"> + + <application> + <activity + android:name=".ru.libhentai.LibHentaiActivity" + android:excludeFromRecents="true" + android:exported="true" + android:theme="@android:style/Theme.NoDisplay"> + <intent-filter> + <action android:name="android.intent.action.VIEW" /> + + <category android:name="android.intent.category.DEFAULT" /> + <category android:name="android.intent.category.BROWSABLE" /> + + <!-- LibHentaiActivity sites can be added here. --> + <data + android:host="hentailib.me" + android:pathPattern="/..*/v..*" + android:scheme="https" /> + </intent-filter> + </activity> + </application> +</manifest> diff --git a/src/ru/libhentai/build.gradle b/src/ru/libhentai/build.gradle new file mode 100644 index 000000000..78fa3a4e6 --- /dev/null +++ b/src/ru/libhentai/build.gradle @@ -0,0 +1,17 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlinx-serialization' + +ext { + extName = 'HentaiLib' + pkgNameSuffix = 'ru.libhentai' + extClass = '.LibHentai' + extVersionCode = 1 + isNsfw = true +} + +dependencies { + implementation project(path: ':lib-ratelimit') +} + +apply from: "$rootDir/common.gradle" diff --git a/src/ru/libhentai/res/mipmap-hdpi/ic_launcher.png b/src/ru/libhentai/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..41cb9dd7a2abd57e448c2a5f3232d5cd05bbde10 GIT binary patch literal 1886 zcmV-k2ch_hP)<h;3K|Lk000e1NJLTq002k;002k`1^@s6RqeA!000LiNkl<Zc%1Fr z%TF6e9Ki7(kLJ;)PAd+nDpd;j0k-#Luf6u#UPA~)Ngzb3Ft+gnW1!Fmr6@w}p@$wS z<(zVedg_0Xa}!9e{Tl*3Hy3isWqPn@>|O6;cD*)E<dMEtU_YAK*;!-*499Q`$8ZeC zaD41zq}gb*u@5x-_v7nvWsCyA_}<>0w7>sT{_ybUCyyVSbx1lmFz?Z$$CmByA6WE= zx`z+<pX}}ZxSP*c0{}4Y2GS_QByKF;U3qqEVF}I7-$9w#Z&5nC&`P;YN-6UsIdhAY zOwW-LsaBFaONu8O#S+<8l0p)t3@I9K6p5utf|w%lQJKU=k|b70AVHK-JeftQ%=|L| zxZIooj%Uv0+lxzorn3vWe^f+FsS-q)#7C2ek0ucpNsw5f6~jxUFc&9<*jOt@Bn6o$ zX(rrgIus!Vf&$4u!;^f|9LXDCNenNcWP1L&Q=}097%vpc3dRxR8Kfi>by9|uiKj{F zSc;U2$s}1!wu(05g^L0523QniM6|N9!hwV3HUa=w>h<jdJad$Ug5DO3tqKL>^7%$< zYsTer=xr{CYI4>V5XPYaGQ+`DtF_+%;EG+KQ2@BMv$OLQFDn%g4UQR56Q=>BIGR1} zKu%^4ZhgJ_6acP)&1E(Q0G?WH`v~7}2#^_$&DqlpP`AqqS1eYJ0Kn6{W?MklJAi0$ zC}+>x)l~wd!s$QI_3nVg6jG#2_c*jbHkKC+kP*(5pK$@GxrLhoImB@a<ksxz50Dca z4Nxy<Pv3wt@w6$<@AC_2BQ;B^%2`zvS!tEbkmN|h@$BgaXv)vH1B9Q9JAgDZXCpPM z`^2n$YIgGE+^gl~0#LmZs2!$lSz#c@I2W+ITm?$UQieDf2&1Hw1ytYKLz{I?o4aVe zyh)(!%$^_<MQepBs#Ld|ZIQ~AEhI_`HIA!5_|T~W(zyv&<tzcJZ|$M?@89cocJ?n4 z#FQb9(d=Ob37wvv8UEaIWy|*LnesCzpD(!xWU@=O18vsLf%vFwducKJ00VK61WLtZ zDj<fJOo2{M&(uJaH)a|jyEv1+aQ8rXXQBbZH)3iaily~&0OfM1A}jWQY~auUmCAKn zH)mIYSRsLAF=_gQtODBHH3i}#NvfsgHjoy_6(D@$?Eumo9WYRsi`xRC!I=(4P?(ET z0oj>7y#dmmsW4D7LmmKWfoO5u0Mf&84T#|-BuA6_IL$++T_8I+fuKMIWOw#-4OFex zj_`e@1E>k31q!h-RI>sS61H%<1v0|9=?%FD#0wIt$yrpB72VoT<z_(V=kL(T$!m0c z{0bc(zd|o5J2`oc&d=Z325QIY8BqHHQ!nkI(+sFr(Ew?2Tmhm*X>X(U{XhU&&7PhB znZe+#dk}&2a9ja01u-~{4+4-8PJe+kFfE{+z5}h7Hf=4f-T@h)v_JR_5|9~=J3uBN zM#lv+AiT|+ZtE{kRK0k4f{u@0nQrqKXv)tZVTMBmA&v@sOjVu}QJxc#*XI&YrD6`G zg`ojDJv~DVFLk_A*1lKPzF!V9qUnR;dTEo)nj9ZP|1B@8TUsKq0j76AMkwu1#nlaK zAUil1=*^o`!=GDRuNeci;kXWD1)@DF&;W(GxG7FM3~nYIr3d1~!2r_2;40N^Y9JaM zZ-Au+n)HR$KpLE$0vUm551BMTR!d6z@{0xt?^Xmz2gh|FGZekkHw{pTjalMopNee* z8Q}B<NDoB~v_%bMHfz*C5o(}SHrhWRD-6ANH?%;sI0Fk*Dpf6kwA(ceP>>O+VeA6k z^oA}LNDo9?xnd8<3db8@(R9cf2w!wxfoMRqTec`Es9f1nRVvp}sa!{^Yh|*$XkhU1 z!5d)FcdP5DxL(tZizEjVNN>Bg+PdxHn7z8tJA_^+kQIm-iX9lIIDH4A0qGTtFvFo7 zA8W*ks#e*kVZX+cg9BuSVi!g`YYIZV^Hgr~giKq{fUGc_qUbHBDL;b>7OyP^0*DsH zO&EN?=>T#8r>{UxK&)`uZ@>h|C77NAbqhi>YdU~j#&H#>SS%g6g<=PW{9D-KRDRJw zo~5OgA9{si1f%+|XCHtbJXrk&9DE#V6aYS(oSft?0tV;xg%BHHTyL`Ncig}KU>5*B zw+l1^jgKjQ?e{Z()7*c|K<vWwE3ZHJ0sy`NfKS1}2Ybf>;7gBZBJK?^&wbM@@=deI z7ii`6GnP&H!-h@zLKjfu_j-N7e>|QE2>>Pl;1W3a4E-noTm^s|05CH*H+OjF&fTNs z<@<lpbno8sK(@Ge?+-zUmH;3K0ADq)(eY>J$C^K20vc1%2LJ&8nEpTu0D!l7EfdXa z8-p&sfdPMC$0ca2Qx7z@#4kS3u0!L7d=&sbf!6nUbUiRszrT#6SGfi=9K$gj!_mv} YKXUD{nfr<4-2eap07*qoM6N<$f;uu$vH$=8 literal 0 HcmV?d00001 diff --git a/src/ru/libhentai/res/mipmap-mdpi/ic_launcher.png b/src/ru/libhentai/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..40d1a9c7844e3a332f21ea6202a7527c3aec431f GIT binary patch literal 1578 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 literal 0 HcmV?d00001 diff --git a/src/ru/libhentai/res/mipmap-xhdpi/ic_launcher.png b/src/ru/libhentai/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..ca16a46fd4f52ac8a78839acad9ca5ae6719a76b GIT binary patch literal 2549 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 LNkvXXu0mjfN_n$n literal 0 HcmV?d00001 diff --git a/src/ru/libhentai/res/mipmap-xxhdpi/ic_launcher.png b/src/ru/libhentai/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..01ba951d014e4a838e80248cd40785b71acb2f38 GIT binary patch literal 3670 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 EKSv;pMgRZ+ literal 0 HcmV?d00001 diff --git a/src/ru/libhentai/res/mipmap-xxxhdpi/ic_launcher.png b/src/ru/libhentai/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..8171958cd35ac6f9ee62cab100454dfa4125c3ab GIT binary patch literal 5052 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 b##4S;PpL`QD$Eu9l>>GMoNX$t$;tl*(VaMO literal 0 HcmV?d00001 diff --git a/src/ru/libhentai/res/web_hi_res_512.png b/src/ru/libhentai/res/web_hi_res_512.png new file mode 100644 index 0000000000000000000000000000000000000000..ace364704002a01c29b3553e202ad4a4c95e9ca6 GIT binary patch literal 10925 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 OPd;8W_uNO)fBzqCRPF8n literal 0 HcmV?d00001 diff --git a/src/ru/libhentai/src/eu/kanade/tachiyomi/extension/ru/libhentai/LibHentai.kt b/src/ru/libhentai/src/eu/kanade/tachiyomi/extension/ru/libhentai/LibHentai.kt new file mode 100644 index 000000000..5c9ef8e0b --- /dev/null +++ b/src/ru/libhentai/src/eu/kanade/tachiyomi/extension/ru/libhentai/LibHentai.kt @@ -0,0 +1,889 @@ +package eu.kanade.tachiyomi.extension.ru.libhentai + +import android.app.Application +import android.content.SharedPreferences +import android.widget.Toast +import androidx.preference.ListPreference +import androidx.preference.PreferenceScreen +import eu.kanade.tachiyomi.lib.ratelimit.RateLimitInterceptor +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.network.POST +import eu.kanade.tachiyomi.network.asObservableSuccess +import eu.kanade.tachiyomi.source.ConfigurableSource +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 kotlinx.serialization.decodeFromString +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonArray +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.contentOrNull +import kotlinx.serialization.json.int +import kotlinx.serialization.json.intOrNull +import kotlinx.serialization.json.jsonArray +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive +import okhttp3.Headers +import okhttp3.HttpUrl.Companion.toHttpUrlOrNull +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.Response +import org.jsoup.nodes.Element +import rx.Observable +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get +import uy.kohesive.injekt.injectLazy +import java.text.SimpleDateFormat +import java.util.Locale +import java.util.concurrent.TimeUnit + +class LibHentai : ConfigurableSource, HttpSource() { + + private val json: Json by injectLazy() + + private val preferences: SharedPreferences by lazy { + Injekt.get<Application>().getSharedPreferences("source_${id}_2", 0x0000) + } + + override val name: String = "Hentailib" + + override val lang = "ru" + + override val supportsLatest = true + + override val client: OkHttpClient = network.cloudflareClient.newBuilder() + .connectTimeout(10, TimeUnit.SECONDS) + .readTimeout(30, TimeUnit.SECONDS) + .addNetworkInterceptor(RateLimitInterceptor(3)) + .build() + + override val baseUrl = "https://hentailib.me" + + override fun headersBuilder() = Headers.Builder().apply { + add("User-Agent", "Mozilla/5.0 (Windows NT 6.3; WOW64)") + add("Accept", "image/webp,*/*;q=0.8") + add("Referer", baseUrl) + } + + override fun latestUpdatesRequest(page: Int) = GET(baseUrl, headers) + + private val latestUpdatesSelector = "div.updates__item" + + override fun latestUpdatesParse(response: Response): MangasPage { + val elements = response.asJsoup().select(latestUpdatesSelector) + val latestMangas = elements?.map { latestUpdatesFromElement(it) } + if (latestMangas != null) + return MangasPage(latestMangas, false) // TODO: use API + return MangasPage(emptyList(), false) + } + + private fun latestUpdatesFromElement(element: Element): SManga { + val manga = SManga.create() + element.select("div.cover").first().let { img -> + manga.thumbnail_url = img.attr("data-src").replace("_thumb", "_250x350") + } + + element.select("a").first().let { link -> + manga.setUrlWithoutDomain(link.attr("href")) + manga.title = if (titleLanguage.equals("rus") || element.select(".updates__name_rus").isNullOrEmpty()) { element.select("h4").first().text() } else element.select(".updates__name_rus").first().text() + } + return manga + } + + private var csrfToken: String = "" + + private fun catalogHeaders() = Headers.Builder() + .apply { + add("Accept", "application/json, text/plain, */*") + add("X-Requested-With", "XMLHttpRequest") + add("x-csrf-token", csrfToken) + } + .build() + + override fun popularMangaRequest(page: Int) = GET("$baseUrl/login", headers) + + override fun fetchPopularManga(page: Int): Observable<MangasPage> { + if (csrfToken.isEmpty()) { + return client.newCall(popularMangaRequest(page)) + .asObservableSuccess() + .flatMap { response -> + // Obtain token + val resBody = response.body!!.string() + csrfToken = "_token\" content=\"(.*)\"".toRegex().find(resBody)!!.groups[1]!!.value + return@flatMap fetchPopularMangaFromApi(page) + } + } + return fetchPopularMangaFromApi(page) + } + + private fun fetchPopularMangaFromApi(page: Int): Observable<MangasPage> { + return client.newCall(POST("$baseUrl/filterlist?dir=desc&sort=views&page=$page", catalogHeaders())) + .asObservableSuccess() + .map { response -> + popularMangaParse(response) + } + } + + override fun popularMangaParse(response: Response): MangasPage { + val resBody = response.body!!.string() + val result = json.decodeFromString<JsonObject>(resBody) + val items = result["items"]!!.jsonObject + val popularMangas = items["data"]?.jsonArray?.map { popularMangaFromElement(it) } + + if (popularMangas != null) { + val hasNextPage = items["next_page_url"]?.jsonPrimitive?.contentOrNull != null + return MangasPage(popularMangas, hasNextPage) + } + return MangasPage(emptyList(), false) + } + + private fun popularMangaFromElement(el: JsonElement) = SManga.create().apply { + val slug = el.jsonObject["slug"]!!.jsonPrimitive.content + val cover = el.jsonObject["cover"]!!.jsonPrimitive.content + title = if (titleLanguage.equals("rus")) el.jsonObject["rus_name"]!!.jsonPrimitive.content else el.jsonObject["name"]!!.jsonPrimitive.content + thumbnail_url = "$COVER_URL/huploads/cover/$slug/cover/${cover}_250x350.jpg" + url = "/$slug" + } + + override fun mangaDetailsParse(response: Response): SManga { + val document = response.asJsoup() + + if (document.select("body[data-page=home]").isNotEmpty()) + throw Exception("Can't open manga. Try log in via WebView") + + val manga = SManga.create() + + val body = document.select("div.media-info-list").first() + val rawCategory = body.select("div.media-info-list__title:contains(Тип) + div").text() + val category = when { + rawCategory == "Комикс западный" -> "Комикс" + rawCategory.isNotBlank() -> rawCategory + else -> "Манга" + } + var rawAgeStop = body.select("div.media-info-list__title:contains(Возрастной рейтинг) + div").text() + if (rawAgeStop.isEmpty()) { + rawAgeStop = "0+" + } + + val ratingValue = document.select(".media-rating.media-rating_lg div.media-rating__value").text().toFloat() * 2 + val ratingVotes = document.select(".media-rating.media-rating_lg div.media-rating__votes").text() + val ratingStar = when { + ratingValue > 9.5 -> "★★★★★" + ratingValue > 8.5 -> "★★★★✬" + ratingValue > 7.5 -> "★★★★☆" + ratingValue > 6.5 -> "★★★✬☆" + ratingValue > 5.5 -> "★★★☆☆" + ratingValue > 4.5 -> "★★✬☆☆" + ratingValue > 3.5 -> "★★☆☆☆" + ratingValue > 2.5 -> "★✬☆☆☆" + ratingValue > 1.5 -> "★☆☆☆☆" + ratingValue > 0.5 -> "✬☆☆☆☆" + else -> "☆☆☆☆☆" + } + val genres = document.select(".media-tags > a").map { it.text().capitalize() } + manga.title = if (titleLanguage.equals("rus")) document.select(".media-name__main").text() else document.select(".media-name__alt").text() + manga.thumbnail_url = document.select(".media-sidebar__cover > img").attr("src") + manga.author = body.select("div.media-info-list__title:contains(Автор) + div").text() + manga.artist = body.select("div.media-info-list__title:contains(Художник) + div").text() + manga.status = if (document.html().contains("Манга удалена по просьбе правообладателей") || + document.html().contains("Данный тайтл лицензирован на территории РФ.") + ) { + SManga.LICENSED + } else + when ( + body.select("div.media-info-list__title:contains(Статус перевода) + div") + .text() + .toLowerCase(Locale.ROOT) + ) { + "продолжается" -> SManga.ONGOING + "завершен" -> SManga.COMPLETED + else -> SManga.UNKNOWN + } + manga.genre = genres.plusElement(category).plusElement(rawAgeStop).joinToString { it.trim() } + val altSelector = document.select(".media-info-list__item_alt-names .media-info-list__value div") + var altName = "" + if (altSelector.isNotEmpty()) { + altName = "Альтернативные названия:\n" + altSelector.map { it.text() }.joinToString(" / ") + "\n\n" + } + val mediaNameLanguage = if (titleLanguage.equals("rus")) document.select(".media-name__alt").text() else document.select(".media-name__main").text() + manga.description = mediaNameLanguage + "\n" + ratingStar + " " + ratingValue + " (голосов: " + ratingVotes + ")\n" + altName + document.select(".media-description__text").text() + return manga + } + + override fun chapterListParse(response: Response): List<SChapter> { + val document = response.asJsoup() + if (document.html().contains("Манга удалена по просьбе правообладателей") || + document.html().contains("Данный тайтл лицензирован на территории РФ.") + ) { + return emptyList() + } + val dataStr = document + .toString() + .substringAfter("window.__DATA__ = ") + .substringBefore("window._SITE_COLOR_") + .substringBeforeLast(";") + + val data = json.decodeFromString<JsonObject>(dataStr) + val chaptersList = data["chapters"]!!.jsonObject["list"]?.jsonArray + val slug = data["manga"]!!.jsonObject["slug"]!!.jsonPrimitive.content + val branches = data["chapters"]!!.jsonObject["branches"]!!.jsonArray.reversed() + val sortingList = preferences.getString(SORTING_PREF, "ms_mixing") + + val chapters: List<SChapter>? = if (branches.isNotEmpty() && !sortingList.equals("ms_mixing")) { + sortChaptersByTranslator(sortingList, chaptersList, slug, branches) + } else { + chaptersList + ?.filter { it.jsonObject["status"]?.jsonPrimitive?.intOrNull != 2 } + ?.map { chapterFromElement(it, sortingList, slug) } + } + + return chapters ?: emptyList() + } + + private fun sortChaptersByTranslator + (sortingList: String?, chaptersList: JsonArray?, slug: String, branches: List<JsonElement>): List<SChapter>? { + var chapters: List<SChapter>? = null + when (sortingList) { + "ms_combining" -> { + val tempChaptersList = mutableListOf<SChapter>() + for (currentBranch in branches.withIndex()) { + val teamId = branches[currentBranch.index].jsonObject["id"]!!.jsonPrimitive.int + chapters = chaptersList + ?.filter { it.jsonObject["branch_id"]?.jsonPrimitive?.intOrNull == teamId && it.jsonObject["status"]?.jsonPrimitive?.intOrNull != 2 } + ?.map { chapterFromElement(it, sortingList, slug, teamId, branches) } + chapters?.let { tempChaptersList.addAll(it) } + } + chapters = tempChaptersList + } + "ms_largest" -> { + val sizesChaptersLists = mutableListOf<Int>() + for (currentBranch in branches.withIndex()) { + val teamId = branches[currentBranch.index].jsonObject["id"]!!.jsonPrimitive.int + val chapterSize = chaptersList + ?.filter { it.jsonObject["branch_id"]?.jsonPrimitive?.intOrNull == teamId }!!.size + sizesChaptersLists.add(chapterSize) + } + val max = sizesChaptersLists.indexOfFirst { it == sizesChaptersLists.maxOrNull() ?: 0 } + val teamId = branches[max].jsonObject["id"]!!.jsonPrimitive.int + + chapters = chaptersList + ?.filter { it.jsonObject["branch_id"]?.jsonPrimitive?.intOrNull == teamId && it.jsonObject["status"]?.jsonPrimitive?.intOrNull != 2 } + ?.map { chapterFromElement(it, sortingList, slug, teamId, branches) } + } + "ms_active" -> { + for (currentBranch in branches.withIndex()) { + val teams = branches[currentBranch.index].jsonObject["teams"]!!.jsonArray + for (currentTeam in teams.withIndex()) { + if (teams[currentTeam.index].jsonObject["is_active"]!!.jsonPrimitive.int == 1) { + val teamId = branches[currentBranch.index].jsonObject["id"]!!.jsonPrimitive.int + chapters = chaptersList + ?.filter { it.jsonObject["branch_id"]?.jsonPrimitive?.intOrNull == teamId && it.jsonObject["status"]?.jsonPrimitive?.intOrNull != 2 } + ?.map { chapterFromElement(it, sortingList, slug, teamId, branches) } + break + } + } + } + chapters ?: throw Exception("Активный перевод не назначен на сайте") + } + } + + return chapters + } + + private fun chapterFromElement + (chapterItem: JsonElement, sortingList: String?, slug: String, teamIdParam: Int? = null, branches: List<JsonElement>? = null): SChapter { + val chapter = SChapter.create() + + val volume = chapterItem.jsonObject["chapter_volume"]!!.jsonPrimitive.int + val number = chapterItem.jsonObject["chapter_number"]!!.jsonPrimitive.content + val teamId = if (teamIdParam != null) "?bid=$teamIdParam" else "" + + val url = "$baseUrl/$slug/v$volume/c$number$teamId" + + chapter.setUrlWithoutDomain(url) + + val nameChapter = chapterItem.jsonObject["chapter_name"]?.jsonPrimitive?.contentOrNull + val fullNameChapter = "Том $volume. Глава $number" + + if (!sortingList.equals("ms_mixing")) { + chapter.scanlator = branches?.let { getScanlatorTeamName(it, chapterItem) } ?: chapterItem.jsonObject["username"]!!.jsonPrimitive.content + } + chapter.name = if (nameChapter.isNullOrBlank()) fullNameChapter else "$fullNameChapter - $nameChapter" + chapter.date_upload = SimpleDateFormat("yyyy-MM-dd", Locale.US) + .parse(chapterItem.jsonObject["chapter_created_at"]!!.jsonPrimitive.content.substringBefore(" "))?.time ?: 0L + + return chapter + } + + private fun getScanlatorTeamName(branches: List<JsonElement>, chapterItem: JsonElement): String? { + var scanlatorData: String? = null + for (currentBranch in branches.withIndex()) { + val branch = branches[currentBranch.index].jsonObject + val teams = branch["teams"]!!.jsonArray + if (chapterItem.jsonObject["branch_id"]!!.jsonPrimitive.int == branch["id"]!!.jsonPrimitive.int) { + for (currentTeam in teams.withIndex()) { + val team = teams[currentTeam.index].jsonObject + val scanlatorId = chapterItem.jsonObject["chapter_scanlator_id"]!!.jsonPrimitive.int + scanlatorData = if ((scanlatorId == team.jsonObject["id"]!!.jsonPrimitive.int) || + (scanlatorId == 0 && team["is_active"]!!.jsonPrimitive.int == 1) + ) team["name"]!!.jsonPrimitive.content else branch["teams"]!!.jsonArray[0].jsonObject["name"]!!.jsonPrimitive.content + } + } + } + return scanlatorData + } + + override fun prepareNewChapter(chapter: SChapter, manga: SManga) { + """Глава\s(\d+)""".toRegex().find(chapter.name)?.let { + val number = it.groups[1]?.value!! + chapter.chapter_number = number.toFloat() + } + } + + override fun pageListParse(response: Response): List<Page> { + val document = response.asJsoup() + + val redirect = document.html() + if (!redirect.contains("window.__info")) { + if (redirect.contains("hold-transition login-page")) { + throw Exception("Для просмотра 18+ контента необходима авторизация через WebView") + } else if (redirect.contains("header__logo")) { + throw Exception("Лицензировано - Главы не доступны") + } + } + + val chapInfo = document + .select("script:containsData(window.__info)") + .first() + .html() + .split("window.__info = ") + .last() + .trim() + .split(";") + .first() + + val chapInfoJson = json.decodeFromString<JsonObject>(chapInfo) + val servers = chapInfoJson["servers"]!!.jsonObject.toMap() + val defaultServer: String = chapInfoJson["img"]!!.jsonObject["server"]!!.jsonPrimitive.content + val autoServer = setOf("secondary", "fourth", defaultServer, "compress") + val imgUrl: String = chapInfoJson["img"]!!.jsonObject["url"]!!.jsonPrimitive.content + + val serverToUse = when (this.server) { + null -> autoServer + "auto" -> autoServer + else -> listOf(this.server) + } + + // Get pages + val pagesArr = document + .select("script:containsData(window.__pg)") + .first() + .html() + .trim() + .removePrefix("window.__pg = ") + .removeSuffix(";") + + val pagesJson = json.decodeFromString<JsonArray>(pagesArr) + val pages = mutableListOf<Page>() + + pagesJson.forEach { page -> + val keys = servers.keys.filter { serverToUse.indexOf(it) >= 0 }.sortedBy { serverToUse.indexOf(it) } + val serversUrls = keys.map { + servers[it]?.jsonPrimitive?.contentOrNull + imgUrl + page.jsonObject["u"]!!.jsonPrimitive.content + }.joinToString(separator = ",,") { it } + pages.add(Page(page.jsonObject["p"]!!.jsonPrimitive.int, serversUrls)) + } + + return pages + } + + private fun checkImage(url: String): Boolean { + val response = client.newCall(Request.Builder().url(url).head().headers(headers).build()).execute() + return response.isSuccessful && (response.header("content-length", "0")?.toInt()!! > 320) + } + + override fun fetchImageUrl(page: Page): Observable<String> { + if (page.imageUrl != null) { + return Observable.just(page.imageUrl) + } + + val urls = page.url.split(",,") + if (urls.size == 1) { + return Observable.just(urls[0]) + } + + return Observable.from(urls).filter { checkImage(it) }.first() + } + + override fun imageUrlParse(response: Response): String = "" + + private fun searchMangaByIdRequest(id: String): Request { + return GET("$baseUrl/$id", headers) + } + + override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> { + return if (query.startsWith(PREFIX_SLUG_SEARCH)) { + val realQuery = query.removePrefix(PREFIX_SLUG_SEARCH) + client.newCall(searchMangaByIdRequest(realQuery)) + .asObservableSuccess() + .map { response -> + val details = mangaDetailsParse(response) + details.url = "/$realQuery" + MangasPage(listOf(details), false) + } + } else { + client.newCall(searchMangaRequest(page, query, filters)) + .asObservableSuccess() + .map { response -> + searchMangaParse(response) + } + } + } + + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { + if (csrfToken.isEmpty()) { + val tokenResponse = client.newCall(popularMangaRequest(page)).execute() + val resBody = tokenResponse.body!!.string() + csrfToken = "_token\" content=\"(.*)\"".toRegex().find(resBody)!!.groups[1]!!.value + } + val url = "$baseUrl/filterlist?page=$page".toHttpUrlOrNull()!!.newBuilder() + if (query.isNotEmpty()) { + url.addQueryParameter("name", query) + } + (if (filters.isEmpty()) getFilterList() else filters).forEach { filter -> + when (filter) { + is CategoryList -> filter.state.forEach { category -> + if (category.state) { + url.addQueryParameter("types[]", category.id) + } + } + is FormatList -> filter.state.forEach { forma -> + if (forma.state != Filter.TriState.STATE_IGNORE) { + url.addQueryParameter(if (forma.isIncluded()) "format[include][]" else "format[exclude][]", forma.id) + } + } + is StatusList -> filter.state.forEach { status -> + if (status.state) { + url.addQueryParameter("status[]", status.id) + } + } + is StatusTitleList -> filter.state.forEach { title -> + if (title.state) { + url.addQueryParameter("manga_status[]", title.id) + } + } + is GenreList -> filter.state.forEach { genre -> + if (genre.state != Filter.TriState.STATE_IGNORE) { + url.addQueryParameter(if (genre.isIncluded()) "genres[include][]" else "genres[exclude][]", genre.id) + } + } + is OrderBy -> { + url.addQueryParameter("dir", if (filter.state!!.ascending) "asc" else "desc") + url.addQueryParameter("sort", arrayOf("rate", "name", "views", "created_at", "last_chapter_at", "chap_count")[filter.state!!.index]) + } + is TagList -> filter.state.forEach { tag -> + if (tag.state != Filter.TriState.STATE_IGNORE) { + url.addQueryParameter(if (tag.isIncluded()) "tags[include][]" else "tags[exclude][]", tag.id) + } + } + } + } + return POST(url.toString(), catalogHeaders()) + } + + // Hack search method to add some results from search popup + override fun searchMangaParse(response: Response): MangasPage { + val searchRequest = response.request.url.queryParameter("name") + val mangas = mutableListOf<SManga>() + + if (!searchRequest.isNullOrEmpty()) { + val popupSearchHeaders = headers + .newBuilder() + .add("Accept", "application/json, text/plain, */*") + .add("X-Requested-With", "XMLHttpRequest") + .build() + + // +200ms + val popup = client.newCall( + GET("$baseUrl/search?query=$searchRequest", popupSearchHeaders) + ) + .execute().body!!.string() + + val jsonList = json.decodeFromString<JsonArray>(popup) + jsonList.forEach { + mangas.add(popularMangaFromElement(it)) + } + } + val searchedMangas = popularMangaParse(response) + + // Filtered out what find in popup search + mangas.addAll( + searchedMangas.mangas.filter { search -> + mangas.find { search.title == it.title } == null + } + ) + + return MangasPage(mangas, searchedMangas.hasNextPage) + } + + private class SearchFilter(name: String, val id: String) : Filter.TriState(name) + private class CheckFilter(name: String, val id: String) : Filter.CheckBox(name) + + private class CategoryList(categories: List<CheckFilter>) : Filter.Group<CheckFilter>("Тип", categories) + private class FormatList(formas: List<SearchFilter>) : Filter.Group<SearchFilter>("Формат выпуска", formas) + private class StatusList(statuses: List<CheckFilter>) : Filter.Group<CheckFilter>("Статус перевода", statuses) + private class StatusTitleList(titles: List<CheckFilter>) : Filter.Group<CheckFilter>("Статус тайтла", titles) + private class GenreList(genres: List<SearchFilter>) : Filter.Group<SearchFilter>("Жанры", genres) + private class TagList(tags: List<SearchFilter>) : Filter.Group<SearchFilter>("Теги", tags) + private class AgeList(ages: List<CheckFilter>) : Filter.Group<CheckFilter>("Возрастное ограничение", ages) + + override fun getFilterList() = FilterList( + OrderBy(), + CategoryList(getCategoryList()), + FormatList(getFormatList()), + GenreList(getGenreList()), + TagList(getTagList()), + StatusList(getStatusList()), + StatusTitleList(getStatusTitleList()) + ) + + private class OrderBy : Filter.Sort( + "Сортировка", + arrayOf("Рейтинг", "Имя", "Просмотры", "Дате добавления", "Дате обновления", "Кол-во глав"), + Selection(0, false) + ) + + /* + * Use console + * Object.entries(__FILTER_ITEMS__.types).map(([k, v]) => `SearchFilter("${v.label}", "${v.id}")`).join(',\n') + * on /manga-list + */ + private fun getCategoryList() = listOf( + CheckFilter("Манга", "1"), + CheckFilter("OEL-манга", "4"), + CheckFilter("Манхва", "5"), + CheckFilter("Маньхуа", "6"), + CheckFilter("Руманга", "8"), + CheckFilter("Комикс западный", "9") + ) + + private fun getFormatList() = listOf( + SearchFilter("4-кома (Ёнкома)", "1"), + SearchFilter("Сборник", "2"), + SearchFilter("Додзинси", "3"), + SearchFilter("Сингл", "4"), + SearchFilter("В цвете", "5"), + SearchFilter("Веб", "6") + ) + + /* + * Use console + * Object.entries(__FILTER_ITEMS__.status).map(([k, v]) => `SearchFilter("${v.label}", "${v.id}")`).join(',\n') + * on /manga-list + */ + private fun getStatusList() = listOf( + CheckFilter("Продолжается", "1"), + CheckFilter("Завершен", "2"), + CheckFilter("Заморожен", "3"), + CheckFilter("Заброшен", "4") + ) + + private fun getStatusTitleList() = listOf( + CheckFilter("Онгоинг", "1"), + CheckFilter("Завершён", "2"), + CheckFilter("Анонс", "3"), + CheckFilter("Приостановлен", "4"), + CheckFilter("Выпуск прекращён", "5"), + ) + + /* + * Use console + * __FILTER_ITEMS__.genres.map(it => `SearchFilter("${it.name}", "${it.id}")`).join(',\n') + * on /manga-list + */ + private fun getGenreList() = listOf( + SearchFilter("арт", "32"), + SearchFilter("боевик", "34"), + SearchFilter("боевые искусства", "35"), + SearchFilter("вампиры", "36"), + SearchFilter("гарем", "37"), + SearchFilter("гендерная интрига", "38"), + SearchFilter("героическое фэнтези", "39"), + SearchFilter("детектив", "40"), + SearchFilter("дзёсэй", "41"), + SearchFilter("драма", "43"), + SearchFilter("игра", "44"), + SearchFilter("исекай", "79"), + SearchFilter("история", "45"), + SearchFilter("киберпанк", "46"), + SearchFilter("кодомо", "76"), + SearchFilter("комедия", "47"), + SearchFilter("махо-сёдзё", "48"), + SearchFilter("меха", "49"), + SearchFilter("мистика", "50"), + SearchFilter("научная фантастика", "51"), + SearchFilter("омегаверс", "77"), + SearchFilter("повседневность", "52"), + SearchFilter("постапокалиптика", "53"), + SearchFilter("приключения", "54"), + SearchFilter("психология", "55"), + SearchFilter("романтика", "56"), + SearchFilter("самурайский боевик", "57"), + SearchFilter("сверхъестественное", "58"), + SearchFilter("сёдзё", "59"), + SearchFilter("сёдзё-ай", "60"), + SearchFilter("сёнэн", "61"), + SearchFilter("сёнэн-ай", "62"), + SearchFilter("спорт", "63"), + SearchFilter("сэйнэн", "64"), + SearchFilter("трагедия", "65"), + SearchFilter("триллер", "66"), + SearchFilter("ужасы", "67"), + SearchFilter("фантастика", "68"), + SearchFilter("фэнтези", "69"), + SearchFilter("школа", "70"), + SearchFilter("эротика", "71"), + SearchFilter("этти", "72"), + SearchFilter("юри", "73"), + SearchFilter("яой", "74") + ) + + private fun getTagList() = listOf( + SearchFilter("3D", "1"), + SearchFilter("Defloration", "287"), + SearchFilter("FPP(Вид от первого лица)", "289"), + SearchFilter("Footfuck", "5"), + SearchFilter("Handjob", "6"), + SearchFilter("Lactation", "7"), + SearchFilter("Living clothes", "284"), + SearchFilter("Mind break", "9"), + SearchFilter("Scat", "13"), + SearchFilter("Selfcest", "286"), + SearchFilter("Shemale", "220"), + SearchFilter("Tomboy", "14"), + SearchFilter("Unbirth", "283"), + SearchFilter("X-Ray", "15"), + SearchFilter("Алкоголь", "16"), + SearchFilter("Анал", "17"), + SearchFilter("Андроид", "18"), + SearchFilter("Анилингус", "19"), + SearchFilter("Анимация (GIF)", "350"), + SearchFilter("Арт", "20"), + SearchFilter("Ахэгао", "2"), + SearchFilter("БДСМ", "22"), + SearchFilter("Бакуню", "21"), + SearchFilter("Бара", "293"), + SearchFilter("Без проникновения", "336"), + SearchFilter("Без текста", "23"), + SearchFilter("Без трусиков", "24"), + SearchFilter("Без цензуры", "25"), + SearchFilter("Беременность", "26"), + SearchFilter("Бикини", "27"), + SearchFilter("Близнецы", "28"), + SearchFilter("Боди-арт", "29"), + SearchFilter("Больница", "30"), + SearchFilter("Большая грудь", "31"), + SearchFilter("Большая попка", "32"), + SearchFilter("Борьба", "33"), + SearchFilter("Буккакэ", "34"), + SearchFilter("В бассейне", "35"), + SearchFilter("В ванной", "36"), + SearchFilter("В государственном учреждении", "37"), + SearchFilter("В общественном месте", "38"), + SearchFilter("В очках", "8"), + SearchFilter("В первый раз", "39"), + SearchFilter("В транспорте", "40"), + SearchFilter("Вампиры", "41"), + SearchFilter("Вибратор", "42"), + SearchFilter("Втроём", "43"), + SearchFilter("Гипноз", "44"), + SearchFilter("Глубокий минет", "45"), + SearchFilter("Горячий источник", "46"), + SearchFilter("Групповой секс", "47"), + SearchFilter("Гуро", "307"), + SearchFilter("Гяру и Гангуро", "48"), + SearchFilter("Двойное проникновение", "49"), + SearchFilter("Девочки-волшебницы", "50"), + SearchFilter("Девушка-туалет", "51"), + SearchFilter("Демон", "52"), + SearchFilter("Дилдо", "53"), + SearchFilter("Домохозяйка", "54"), + SearchFilter("Дыра в стене", "55"), + SearchFilter("Жестокость", "56"), + SearchFilter("Золотой дождь", "57"), + SearchFilter("Зомби", "58"), + SearchFilter("Зоофилия", "351"), + SearchFilter("Зрелые женщины", "59"), + SearchFilter("Избиение", "223"), + SearchFilter("Измена", "60"), + SearchFilter("Изнасилование", "61"), + SearchFilter("Инопланетяне", "62"), + SearchFilter("Инцест", "63"), + SearchFilter("Исполнение желаний", "64"), + SearchFilter("Историческое", "65"), + SearchFilter("Камера", "66"), + SearchFilter("Кляп", "288"), + SearchFilter("Колготки", "67"), + SearchFilter("Косплей", "68"), + SearchFilter("Кримпай", "3"), + SearchFilter("Куннилингус", "69"), + SearchFilter("Купальники", "70"), + SearchFilter("ЛГБТ", "343"), + SearchFilter("Латекс и кожа", "71"), + SearchFilter("Магия", "72"), + SearchFilter("Маленькая грудь", "73"), + SearchFilter("Мастурбация", "74"), + SearchFilter("Медсестра", "221"), + SearchFilter("Мейдочка", "75"), + SearchFilter("Мерзкий дядька", "76"), + SearchFilter("Милф", "77"), + SearchFilter("Много девушек", "78"), + SearchFilter("Много спермы", "79"), + SearchFilter("Молоко", "80"), + SearchFilter("Монашка", "353"), + SearchFilter("Монстродевушки", "81"), + SearchFilter("Монстры", "82"), + SearchFilter("Мочеиспускание", "83"), + SearchFilter("На природе", "84"), + SearchFilter("Наблюдение", "85"), + SearchFilter("Насекомые", "285"), + SearchFilter("Небритая киска", "86"), + SearchFilter("Небритые подмышки", "87"), + SearchFilter("Нетораре", "88"), + SearchFilter("Нэтори", "11"), + SearchFilter("Обмен телами", "89"), + SearchFilter("Обычный секс", "90"), + SearchFilter("Огромная грудь", "91"), + SearchFilter("Огромный член", "92"), + SearchFilter("Омораси", "93"), + SearchFilter("Оральный секс", "94"), + SearchFilter("Орки", "95"), + SearchFilter("Остановка времени", "296"), + SearchFilter("Пайзури", "96"), + SearchFilter("Парень пассив", "97"), + SearchFilter("Переодевание", "98"), + SearchFilter("Пирсинг", "308"), + SearchFilter("Пляж", "99"), + SearchFilter("Повседневность", "100"), + SearchFilter("Подвязки", "282"), + SearchFilter("Подглядывание", "101"), + SearchFilter("Подчинение", "102"), + SearchFilter("Похищение", "103"), + SearchFilter("Превозмогание", "104"), + SearchFilter("Принуждение", "105"), + SearchFilter("Прозрачная одежда", "106"), + SearchFilter("Проституция", "107"), + SearchFilter("Психические отклонения", "108"), + SearchFilter("Публично", "109"), + SearchFilter("Пытки", "224"), + SearchFilter("Пьяные", "110"), + SearchFilter("Рабы", "356"), + SearchFilter("Рабыни", "111"), + SearchFilter("С Сюжетом", "337"), + SearchFilter("Сuminside", "4"), + SearchFilter("Секс-игрушки", "112"), + SearchFilter("Сексуально возбуждённая", "113"), + SearchFilter("Сибари", "114"), + SearchFilter("Спортивная форма", "117"), + SearchFilter("Спортивное тело", "335"), + SearchFilter("Спящие", "118"), + SearchFilter("Страпон", "119"), + SearchFilter("Суккуб", "120"), + SearchFilter("Темнокожие", "121"), + SearchFilter("Тентакли", "122"), + SearchFilter("Толстушки", "123"), + SearchFilter("Трагедия", "124"), + SearchFilter("Трап", "125"), + SearchFilter("Ужасы", "126"), + SearchFilter("Униформа", "127"), + SearchFilter("Учитель и ученик", "352"), + SearchFilter("Ушастые", "128"), + SearchFilter("Фантазии", "129"), + SearchFilter("Фемдом", "130"), + SearchFilter("Фестиваль", "131"), + SearchFilter("Фетиш", "132"), + SearchFilter("Фистинг", "133"), + SearchFilter("Фурри", "134"), + SearchFilter("Футанари", "136"), + SearchFilter("Футанари имеет парня", "137"), + SearchFilter("Цельный купальник", "138"), + SearchFilter("Цундэрэ", "139"), + SearchFilter("Чикан", "140"), + SearchFilter("Чулки", "141"), + SearchFilter("Шлюха", "142"), + SearchFilter("Эксгибиционизм", "143"), + SearchFilter("Эльф", "144"), + SearchFilter("Юные", "145"), + SearchFilter("Яндэрэ", "146") + ) + + companion object { + const val PREFIX_SLUG_SEARCH = "slug:" + private const val SERVER_PREF = "MangaLibImageServer" + private const val SERVER_PREF_Title = "Сервер изображений" + + private const val SORTING_PREF = "MangaLibSorting" + private const val SORTING_PREF_Title = "Способ выбора переводчиков" + + private const val LANGUAGE_PREF = "MangaLibTitleLanguage" + private const val LANGUAGE_PREF_Title = "Выбор языка на обложке" + + private const val COVER_URL = "https://staticlib.me" + } + + private var server: String? = preferences.getString(SERVER_PREF, null) + private var titleLanguage: String? = preferences.getString(LANGUAGE_PREF, null) + override fun setupPreferenceScreen(screen: PreferenceScreen) { + val serverPref = ListPreference(screen.context).apply { + key = SERVER_PREF + title = SERVER_PREF_Title + entries = arrayOf("Основной", "Второй (тестовый)", "Третий (эконом трафика)", "Авто") + entryValues = arrayOf("secondary", "fourth", "compress", "auto") + summary = "%s" + setDefaultValue("auto") + setOnPreferenceChangeListener { _, newValue -> + server = newValue.toString() + true + } + } + + val sortingPref = ListPreference(screen.context).apply { + key = SORTING_PREF + title = SORTING_PREF_Title + entries = arrayOf( + "Полный список (без повторных переводов)", "Все переводы (друг за другом)", + "Наибольшее число глав", "Активный перевод" + ) + entryValues = arrayOf("ms_mixing", "ms_combining", "ms_largest", "ms_active") + summary = "%s" + setDefaultValue("ms_mixing") + setOnPreferenceChangeListener { _, newValue -> + val selected = newValue as String + preferences.edit().putString(SORTING_PREF, selected).commit() + } + } + val titleLanguagePref = ListPreference(screen.context).apply { + key = LANGUAGE_PREF + title = LANGUAGE_PREF_Title + entries = arrayOf("Английский (транскрипция)", "Русский") + entryValues = arrayOf("eng", "rus") + summary = "%s" + setDefaultValue("eng") + setOnPreferenceChangeListener { _, newValue -> + titleLanguage = newValue.toString() + val warning = "Если язык обложки не изменился очистите базу данных в приложении (Настройки -> Дополнительно -> Очистить базу данных)" + Toast.makeText(screen.context, warning, Toast.LENGTH_LONG).show() + true + } + } + screen.addPreference(serverPref) + screen.addPreference(sortingPref) + screen.addPreference(titleLanguagePref) + } +} diff --git a/src/ru/libhentai/src/eu/kanade/tachiyomi/extension/ru/libhentai/LibHentaiActivity.kt b/src/ru/libhentai/src/eu/kanade/tachiyomi/extension/ru/libhentai/LibHentaiActivity.kt new file mode 100644 index 000000000..4b29428c0 --- /dev/null +++ b/src/ru/libhentai/src/eu/kanade/tachiyomi/extension/ru/libhentai/LibHentaiActivity.kt @@ -0,0 +1,41 @@ +package eu.kanade.tachiyomi.extension.ru.libhentai + +import android.app.Activity +import android.content.ActivityNotFoundException +import android.content.Intent +import android.os.Bundle +import android.util.Log +import kotlin.system.exitProcess + +/** + * Springboard that accepts https://hentailib.me/xxx intents and redirects them to + * the main tachiyomi process. The idea is to not install the intent filter unless + * you have this extension installed, but still let the main tachiyomi app control + * things. + */ +class LibHentaiActivity : Activity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + val pathSegments = intent?.data?.pathSegments + if (pathSegments != null && pathSegments.size > 0) { + val titleid = pathSegments[0] + val mainIntent = Intent().apply { + action = "eu.kanade.tachiyomi.SEARCH" + putExtra("query", "${LibHentai.PREFIX_SLUG_SEARCH}$titleid") + putExtra("filter", packageName) + } + + try { + startActivity(mainIntent) + } catch (e: ActivityNotFoundException) { + Log.e("LibHentaiActivity", e.toString()) + } + } else { + Log.e("LibHentaiActivity", "could not parse uri from intent $intent") + } + + finish() + exitProcess(0) + } +}