From 1e6d92e0a57389f7b791b0e74923110abad90069 Mon Sep 17 00:00:00 2001
From: Arraiment <76941874+Arraiment@users.noreply.github.com>
Date: Sun, 22 Aug 2021 22:37:42 +0800
Subject: [PATCH] Add url intent and id search for IMHentai (#8700)
* Add url intent and id search
* Slight refactoring and fix image loading
---
src/all/imhentai/AndroidManifest.xml | 23 +++-
src/all/imhentai/build.gradle | 2 +-
.../extension/all/imhentai/IMHentai.kt | 100 ++++++++++++++----
.../all/imhentai/IMHentaiUrlActivity.kt | 38 +++++++
4 files changed, 140 insertions(+), 23 deletions(-)
create mode 100644 src/all/imhentai/src/eu/kanade/tachiyomi/extension/all/imhentai/IMHentaiUrlActivity.kt
diff --git a/src/all/imhentai/AndroidManifest.xml b/src/all/imhentai/AndroidManifest.xml
index 30deb7f79..f9ea1cb39 100644
--- a/src/all/imhentai/AndroidManifest.xml
+++ b/src/all/imhentai/AndroidManifest.xml
@@ -1,2 +1,23 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/all/imhentai/build.gradle b/src/all/imhentai/build.gradle
index 55e554b3e..f68b29058 100644
--- a/src/all/imhentai/build.gradle
+++ b/src/all/imhentai/build.gradle
@@ -5,7 +5,7 @@ ext {
extName = 'IMHentai'
pkgNameSuffix = 'all.imhentai'
extClass = '.IMHentaiFactory'
- extVersionCode = 2
+ extVersionCode = 3
libVersion = '1.2'
containsNsfw = true
}
diff --git a/src/all/imhentai/src/eu/kanade/tachiyomi/extension/all/imhentai/IMHentai.kt b/src/all/imhentai/src/eu/kanade/tachiyomi/extension/all/imhentai/IMHentai.kt
index 75ec365e2..c6a92fab0 100644
--- a/src/all/imhentai/src/eu/kanade/tachiyomi/extension/all/imhentai/IMHentai.kt
+++ b/src/all/imhentai/src/eu/kanade/tachiyomi/extension/all/imhentai/IMHentai.kt
@@ -1,26 +1,29 @@
package eu.kanade.tachiyomi.extension.all.imhentai
import eu.kanade.tachiyomi.network.GET
-import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.network.asObservableSuccess
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.ParsedHttpSource
import eu.kanade.tachiyomi.util.asJsoup
+import kotlinx.serialization.json.Json
+import kotlinx.serialization.json.jsonObject
+import kotlinx.serialization.json.jsonPrimitive
+import okhttp3.FormBody
import okhttp3.Headers
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
-import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.OkHttpClient
import okhttp3.Request
-import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.Response
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import org.jsoup.select.Elements
import rx.Observable
+import uy.kohesive.injekt.injectLazy
class IMHentai(override val lang: String, private val imhLang: String) : ParsedHttpSource() {
@@ -65,6 +68,20 @@ class IMHentai(override val lang: String, private val imhLang: String) : ParsedH
// Search
+ override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable {
+ if (query.startsWith("id:")) {
+ val id = query.substringAfter("id:")
+ return client.newCall(GET("$baseUrl/gallery/$id/"))
+ .asObservableSuccess()
+ .map { response ->
+ val manga = mangaDetailsParse(response)
+ manga.url = "/gallery/$id/"
+ MangasPage(listOf(manga), false)
+ }
+ }
+ return super.fetchSearchManga(page, query, filters)
+ }
+
override fun searchMangaFromElement(element: Element): SManga = popularMangaFromElement(element)
override fun searchMangaNextPageSelector(): String = popularMangaNextPageSelector()
@@ -94,7 +111,8 @@ class IMHentai(override val lang: String, private val imhLang: String) : ParsedH
url.addQueryParameter(pair.second, toBinary(filter.state == index))
}
}
- else -> { }
+ else -> {
+ }
}
}
@@ -119,6 +137,11 @@ class IMHentai(override val lang: String, private val imhLang: String) : ParsedH
}
override fun mangaDetailsParse(document: Document): SManga = SManga.create().apply {
+
+ title = document.selectFirst("div.right_details > h1").text()
+
+ thumbnail_url = document.selectFirst("div.left_cover img").attr("abs:data-src")
+
val mangaInfoElement = document.select(".galleries_info")
val infoMap = mangaInfoElement.select("li:not(.pages)").map {
it.select("span.tags_text").text().removeSuffix(":") to it.select(".tag")
@@ -149,20 +172,30 @@ class IMHentai(override val lang: String, private val imhLang: String) : ParsedH
// Chapters
- private fun pageLoadMetaParse(document: Document): String {
- return document.select(".gallery_divider ~ input[type=\"hidden\"]").map { m ->
- m.attr("id") to m.attr("value")
- }.toMap().let {
- listOf(
+ private fun buildPageListRequest(document: Document): Request {
+ val formBuilder = FormBody.Builder()
+ .add("type", "2")
+ .add("visible_pages", "0")
+ // Extracts form data from webpage
+ document.select("div.gallery_divider ~ input[type=hidden]").forEach { element ->
+ val keys = listOf(
Pair("server", "load_server"),
Pair("u_id", "gallery_id"),
Pair("g_id", "load_id"),
Pair("img_dir", "load_dir"),
Pair("total_pages", "load_pages")
- ).map { meta -> "${meta.first}=${it[meta.second]}" }
- .let { payload -> payload + listOf("type=2", "visible_pages=0") }
- .joinToString("&")
+ )
+ for (key in keys) {
+ if (key.second == element.attr("id")) {
+ formBuilder.add(key.first, element.attr("value"))
+ }
+ }
}
+ return Request.Builder()
+ .url("https://imhentai.xxx/inc/thumbs_loader.php")
+ .headers(pageLoadHeaders)
+ .post(formBuilder.build())
+ .build()
}
override fun chapterListParse(response: Response): List {
@@ -181,27 +214,54 @@ class IMHentai(override val lang: String, private val imhLang: String) : ParsedH
// Pages
+ private val json: Json by injectLazy()
+
override fun fetchPageList(chapter: SChapter): Observable> {
+ val gifPages = mutableListOf()
return client.newCall(GET("$baseUrl${chapter.url}"))
.asObservableSuccess()
- .map { pageLoadMetaParse(it.asJsoup()) }
- .map { it.toRequestBody("application/x-www-form-urlencoded; charset=UTF-8".toMediaTypeOrNull()) }
- .concatMap { client.newCall(POST(PAEG_LOAD_URL, pageLoadHeaders, it)).asObservableSuccess() }
- .map { pageListParse(it) }
+ .concatMap {
+ val document = it.asJsoup()
+ getGifPages(document, gifPages)
+ client.newCall(buildPageListRequest(document))
+ .asObservableSuccess()
+ }.map { response ->
+ apiPageListParse(response.asJsoup(), gifPages)
+ }
}
- override fun pageListParse(document: Document): List {
- return document.select("a").mapIndexed { i, element ->
- Page(i, element.attr("href"), element.select(".lazy.preloader[src]").attr("src").replace("t.", "."))
+ private fun getGifPages(document: Document, gifPages: MutableList) {
+ val imageFormats = document.selectFirst("script:containsData(var g_th)").data()
+ .substringAfter("$.parseJSON('").substringBefore("');").trim()
+ json.parseToJsonElement(imageFormats).jsonObject.forEach { pair ->
+ val isGif = pair.value.jsonPrimitive.content.startsWith("g")
+ if (isGif) gifPages.add(pair.key.toInt())
}
}
+ private fun apiPageListParse(document: Document, gifs: List): List {
+ return document.select("a").mapIndexed { i, element ->
+ Page(
+ i,
+ element.attr("href"),
+ if (gifs.any { page -> page == i + 1 }) {
+ element.select("img.lazy.preloader").attr("data-src").replace("t.jpg", ".gif")
+ } else {
+ element.select("img.lazy.preloader").attr("data-src").replace("t.", ".")
+ }
+ )
+ }
+ }
+
+ override fun pageListParse(document: Document): List = throw UnsupportedOperationException("Not used")
+
override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not used")
// Filters
private class SortOrderFilter(sortOrderURIs: List>, state: Int) :
Filter.Select("Sort By", sortOrderURIs.map { it.first }.toTypedArray(), state)
+
private open class SearchFlagFilter(name: String, val uri: String, state: Boolean = true) : Filter.CheckBox(name, state)
private class LanguageFilter(name: String, uri: String = name) : SearchFlagFilter(name, uri, false)
private class LanguageFilters(flags: List) : Filter.Group("Other Languages", flags)
@@ -261,7 +321,5 @@ class IMHentai(override val lang: String, private val imhLang: String) : ParsedH
const val LANGUAGE_KOREAN = "Korean"
const val LANGUAGE_GERMAN = "German"
const val LANGUAGE_RUSSIAN = "Russian"
-
- private const val PAEG_LOAD_URL: String = "https://imhentai.xxx/inc/thumbs_loader.php"
}
}
diff --git a/src/all/imhentai/src/eu/kanade/tachiyomi/extension/all/imhentai/IMHentaiUrlActivity.kt b/src/all/imhentai/src/eu/kanade/tachiyomi/extension/all/imhentai/IMHentaiUrlActivity.kt
new file mode 100644
index 000000000..4d76b0763
--- /dev/null
+++ b/src/all/imhentai/src/eu/kanade/tachiyomi/extension/all/imhentai/IMHentaiUrlActivity.kt
@@ -0,0 +1,38 @@
+package eu.kanade.tachiyomi.extension.all.imhentai
+
+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://imhentai.xxx/gallery/xxxxxx intents and redirects them to
+ * the main Tachiyomi process.
+ */
+class IMHentaiUrlActivity : Activity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ val pathSegments = intent?.data?.pathSegments
+ if (pathSegments != null && pathSegments.size > 1) {
+ val id = pathSegments[1]
+ val mainIntent = Intent().apply {
+ action = "eu.kanade.tachiyomi.SEARCH"
+ putExtra("query", "id:$id")
+ putExtra("filter", packageName)
+ }
+
+ try {
+ startActivity(mainIntent)
+ } catch (e: ActivityNotFoundException) {
+ Log.e("IMHentaiUrlActivity", e.toString())
+ }
+ } else {
+ Log.e("IMHentaiUrlActivity", "could not parse uri from intent $intent")
+ }
+
+ finish()
+ exitProcess(0)
+ }
+}