From 1b37362306aff60c771f15ec7430cf67f50fafba Mon Sep 17 00:00:00 2001 From: Vetle Ledaal Date: Sun, 29 Dec 2024 07:44:53 +0100 Subject: [PATCH] Update domain for NineHentai (+revert removal) (#6842) * Revert 9bc701d65ae9493772848b5d5fd927a87b3d7fb5 (partial) * NineHentai: update domain --- src/en/ninehentai/AndroidManifest.xml | 23 ++ src/en/ninehentai/build.gradle | 8 + .../res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 3817 bytes .../res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 2025 bytes .../res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 5235 bytes .../res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 9524 bytes .../res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 15679 bytes .../extension/en/ninehentai/NineHentai.kt | 379 ++++++++++++++++++ .../extension/en/ninehentai/NineHentaiDto.kt | 84 ++++ .../en/ninehentai/NineHentaiUrlActivity.kt | 38 ++ 10 files changed, 532 insertions(+) create mode 100644 src/en/ninehentai/AndroidManifest.xml create mode 100644 src/en/ninehentai/build.gradle create mode 100644 src/en/ninehentai/res/mipmap-hdpi/ic_launcher.png create mode 100644 src/en/ninehentai/res/mipmap-mdpi/ic_launcher.png create mode 100644 src/en/ninehentai/res/mipmap-xhdpi/ic_launcher.png create mode 100644 src/en/ninehentai/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 src/en/ninehentai/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 src/en/ninehentai/src/eu/kanade/tachiyomi/extension/en/ninehentai/NineHentai.kt create mode 100644 src/en/ninehentai/src/eu/kanade/tachiyomi/extension/en/ninehentai/NineHentaiDto.kt create mode 100644 src/en/ninehentai/src/eu/kanade/tachiyomi/extension/en/ninehentai/NineHentaiUrlActivity.kt diff --git a/src/en/ninehentai/AndroidManifest.xml b/src/en/ninehentai/AndroidManifest.xml new file mode 100644 index 00000000000..24abafc3da4 --- /dev/null +++ b/src/en/ninehentai/AndroidManifest.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + diff --git a/src/en/ninehentai/build.gradle b/src/en/ninehentai/build.gradle new file mode 100644 index 00000000000..276f8e6896e --- /dev/null +++ b/src/en/ninehentai/build.gradle @@ -0,0 +1,8 @@ +ext { + extName = 'NineHentai' + extClass = '.NineHentai' + extVersionCode = 4 + isNsfw = true +} + +apply from: "$rootDir/common.gradle" diff --git a/src/en/ninehentai/res/mipmap-hdpi/ic_launcher.png b/src/en/ninehentai/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..1fa8c130b520a6704d4c46356e411d2bd317ea3c GIT binary patch literal 3817 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 literal 0 HcmV?d00001 diff --git a/src/en/ninehentai/res/mipmap-mdpi/ic_launcher.png b/src/en/ninehentai/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..45a4588851024900c250577e08baea7fde1dffff GIT binary patch literal 2025 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 literal 0 HcmV?d00001 diff --git a/src/en/ninehentai/res/mipmap-xxhdpi/ic_launcher.png b/src/en/ninehentai/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..2d854fe03e1b0389efb172cee8024e7a252ace29 GIT binary patch literal 9524 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 literal 0 HcmV?d00001 diff --git a/src/en/ninehentai/src/eu/kanade/tachiyomi/extension/en/ninehentai/NineHentai.kt b/src/en/ninehentai/src/eu/kanade/tachiyomi/extension/en/ninehentai/NineHentai.kt new file mode 100644 index 00000000000..c4b4c7d7ca9 --- /dev/null +++ b/src/en/ninehentai/src/eu/kanade/tachiyomi/extension/en/ninehentai/NineHentai.kt @@ -0,0 +1,379 @@ +package eu.kanade.tachiyomi.extension.en.ninehentai + +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.HttpSource +import eu.kanade.tachiyomi.util.asJsoup +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.decodeFromJsonElement +import kotlinx.serialization.json.jsonArray +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.put +import okhttp3.MediaType.Companion.toMediaTypeOrNull +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.RequestBody.Companion.toRequestBody +import okhttp3.Response +import okio.Buffer +import org.jsoup.nodes.Element +import rx.Observable +import rx.schedulers.Schedulers +import uy.kohesive.injekt.injectLazy +import java.util.Calendar + +class NineHentai : HttpSource() { + + override val baseUrl = "https://9hentai.so" + + override val name = "NineHentai" + + override val lang = "en" + + override val supportsLatest = true + + override val client: OkHttpClient = network.cloudflareClient + + private val json: Json by injectLazy() + + // Builds request for /api/getBooks endpoint + private fun buildSearchRequest( + searchText: String = "", + page: Int, + sort: Int = 0, + range: List = listOf(0, 2000), + includedTags: List = listOf(), + excludedTags: List = listOf(), + ): Request { + val searchRequest = SearchRequest( + text = searchText, + page = page - 1, // Source starts counting from 0, not 1 + sort = sort, + pages = Range(range), + tag = Items( + items = TagArrays( + included = includedTags, + excluded = excludedTags, + ), + ), + ) + val jsonString = json.encodeToString(SearchRequestPayload(search = searchRequest)) + return POST("$baseUrl$SEARCH_URL", headers, jsonString.toRequestBody(MEDIA_TYPE)) + } + + private fun parseSearchResponse(response: Response): MangasPage { + return response.use { + val page = json.decodeFromString(it.request.bodyString).search.page + json.decodeFromString(it.body.string()).let { searchResponse -> + MangasPage( + searchResponse.results.map { + SManga.create().apply { + url = "/g/${it.id}" + title = it.title + // Cover is the compressed first page (cover might change if page count changes) + thumbnail_url = "${it.image_server}${it.id}/1.jpg?${it.total_page}" + } + }, + searchResponse.totalCount - 1 > page, + ) + } + } + } + + // Builds request for /api/getBookById endpoint + private fun buildDetailRequest(id: Int): Request { + val jsonString = buildJsonObject { put("id", id) }.toString() + return POST("$baseUrl$MANGA_URL", headers, jsonString.toRequestBody(MEDIA_TYPE)) + } + + // Popular + + override fun popularMangaRequest(page: Int): Request = buildSearchRequest(page = page, sort = 1) + + override fun popularMangaParse(response: Response): MangasPage = parseSearchResponse(response) + + // Latest + override fun latestUpdatesRequest(page: Int): Request = buildSearchRequest(page = page) + + override fun latestUpdatesParse(response: Response): MangasPage = parseSearchResponse(response) + + // Search + override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable { + if (query.startsWith("id:")) { + val id = query.substringAfter("id:").toInt() + return client.newCall(buildDetailRequest(id)) + .asObservableSuccess() + .map { response -> + fetchSingleManga(response) + } + } + return super.fetchSearchManga(page, query, filters) + } + + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { + val filterList = if (filters.isEmpty()) getFilterList() else filters + var sort = 0 + val range = mutableListOf(0, 2000) + val includedTags = mutableListOf() + val excludedTags = mutableListOf() + for (filter in filterList) { + when (filter) { + is SortFilter -> { + sort = filter.state + } + is MinPagesFilter -> { + try { + range[0] = filter.state.toInt() + } catch (_: NumberFormatException) { + // Suppress and retain default value + } + } + is MaxPagesFilter -> { + try { + range[1] = filter.state.toInt() + } catch (_: NumberFormatException) { + // Suppress and retain default value + } + } + is IncludedFilter -> { + includedTags += getTags(filter.state, 1) + } + is ExcludedFilter -> { + excludedTags += getTags(filter.state, 1) + } + is GroupFilter -> { + includedTags += getTags(filter.state, 2) + } + is ParodyFilter -> { + includedTags += getTags(filter.state, 3) + } + is ArtistFilter -> { + includedTags += getTags(filter.state, 4) + } + is CharacterFilter -> { + includedTags += getTags(filter.state, 5) + } + is CategoryFilter -> { + includedTags += getTags(filter.state, 6) + } + else -> { /* Do nothing */ } + } + } + return buildSearchRequest( + searchText = query, + page = page, + sort = sort, + range = range, + includedTags = includedTags, + excludedTags = excludedTags, + ) + } + + override fun searchMangaParse(response: Response): MangasPage = parseSearchResponse(response) + + // Manga Details + + override fun mangaDetailsParse(response: Response): SManga { + return SManga.create().apply { + response.asJsoup().selectFirst("div#bigcontainer")!!.let { info -> + title = info.select("h1").text() + thumbnail_url = info.selectFirst("div#cover v-lazy-image")!!.attr("abs:src") + status = SManga.COMPLETED + artist = info.selectTextOrNull("div.field-name:contains(Artist:) a.tag") + author = info.selectTextOrNull("div.field-name:contains(Group:) a.tag") ?: "Unknown circle" + genre = info.selectTextOrNull("div.field-name:contains(Tag:) a.tag") + // Additional details + description = listOf( + Pair("Alternative Title", info.selectTextOrNull("h2")), + Pair("Pages", info.selectTextOrNull("div#info > div:contains(pages)")), + Pair("Parody", info.selectTextOrNull("div.field-name:contains(Parody:) a.tag")), + Pair("Category", info.selectTextOrNull("div.field-name:contains(Category:) a.tag")), + Pair("Language", info.selectTextOrNull("div.field-name:contains(Language:) a.tag")), + ).filterNot { it.second.isNullOrEmpty() }.joinToString("\n\n") { "${it.first}: ${it.second}" } + } + } + } + + // Ensures no exceptions are thrown when scraping additional details + private fun Element.selectTextOrNull(selector: String): String? { + val list = this.select(selector) + return if (list.isEmpty()) { + null + } else { + list.joinToString(", ") { it.text() } + } + } + + // Chapter + + override fun chapterListParse(response: Response): List { + val time = response.asJsoup().select("div#info div time").text() + return listOf( + SChapter.create().apply { + name = "Chapter" + date_upload = parseChapterDate(time) + url = response.request.url.encodedPath + }, + ) + } + + private fun parseChapterDate(date: String): Long { + val dateStringSplit = date.split(" ") + val value = dateStringSplit[0].toInt() + + return when (dateStringSplit[1].removeSuffix("s")) { + "sec" -> Calendar.getInstance().apply { + add(Calendar.SECOND, value * -1) + }.timeInMillis + "min" -> Calendar.getInstance().apply { + add(Calendar.MINUTE, value * -1) + }.timeInMillis + "hour" -> Calendar.getInstance().apply { + add(Calendar.HOUR_OF_DAY, value * -1) + }.timeInMillis + "day" -> Calendar.getInstance().apply { + add(Calendar.DATE, value * -1) + }.timeInMillis + "week" -> Calendar.getInstance().apply { + add(Calendar.DATE, value * 7 * -1) + }.timeInMillis + "month" -> Calendar.getInstance().apply { + add(Calendar.MONTH, value * -1) + }.timeInMillis + "year" -> Calendar.getInstance().apply { + add(Calendar.YEAR, value * -1) + }.timeInMillis + else -> { + return 0 + } + } + } + + // Page List + + override fun pageListRequest(chapter: SChapter): Request { + val mangaId = chapter.url.substringAfter("/g/").toInt() + return buildDetailRequest(mangaId) + } + + override fun pageListParse(response: Response): List { + val resultsObj = json.parseToJsonElement(response.body.string()).jsonObject["results"]!! + val manga = json.decodeFromJsonElement(resultsObj) + val imageUrl = manga.image_server + manga.id + var totalPages = manga.total_page + + client.newCall( + GET( + "$imageUrl/preview/${totalPages}t.jpg", + headersBuilder().build(), + ), + ).execute().code.let { code -> + if (code == 404) totalPages-- + } + + return (1..totalPages).map { + Page(it - 1, "", "$imageUrl/$it.jpg") + } + } + + private fun getTags(queries: String, type: Int): List { + return queries.split(",").map(String::trim) + .filterNot(String::isBlank).mapNotNull { query -> + val jsonString = buildJsonObject { + put("tag_name", query) + put("tag_type", type) + }.toString() + lookupTags(jsonString) + } + } + + // Based on HentaiHand ext + private fun lookupTags(request: String): Tag? { + return client.newCall(POST("$baseUrl$TAG_URL", headers, request.toRequestBody(MEDIA_TYPE))) + .asObservableSuccess() + .subscribeOn(Schedulers.io()) + .map { response -> + // Returns the first matched tag, or null if there are no results + val tagList = json.parseToJsonElement(response.body.string()).jsonObject["results"]!!.jsonArray.map { + json.decodeFromJsonElement(it) + } + if (tagList.isEmpty()) { + return@map null + } else { + tagList.first() + } + }.toBlocking().first() + } + + private fun fetchSingleManga(response: Response): MangasPage { + val resultsObj = json.parseToJsonElement(response.body.string()).jsonObject["results"]!! + val manga = json.decodeFromJsonElement(resultsObj) + val list = listOf( + SManga.create().apply { + setUrlWithoutDomain("/g/${manga.id}") + title = manga.title + thumbnail_url = "${manga.image_server + manga.id}/cover.jpg" + }, + ) + return MangasPage(list, false) + } + + // Filters + + private class SortFilter : Filter.Select( + "Sort by", + arrayOf("Newest", "Popular Right now", "Most Fapped", "Most Viewed", "By Title"), + ) + + private class MinPagesFilter : Filter.Text("Minimum Pages") + private class MaxPagesFilter : Filter.Text("Maximum Pages") + private class IncludedFilter : Filter.Text("Included Tags") + private class ExcludedFilter : Filter.Text("Excluded Tags") + private class ArtistFilter : Filter.Text("Artist") + private class GroupFilter : Filter.Text("Group") + private class ParodyFilter : Filter.Text("Parody") + private class CharacterFilter : Filter.Text("Character") + private class CategoryFilter : Filter.Text("Category") + + override fun getFilterList() = FilterList( + Filter.Header("Search by id with \"id:\" in front of query"), + Filter.Separator(), + SortFilter(), + MinPagesFilter(), + MaxPagesFilter(), + IncludedFilter(), + ExcludedFilter(), + ArtistFilter(), + GroupFilter(), + ParodyFilter(), + CharacterFilter(), + CategoryFilter(), + ) + + override fun imageUrlParse(response: Response): String = throw UnsupportedOperationException() + + private val Request.bodyString: String + get() { + val requestCopy = newBuilder().build() + val buffer = Buffer() + + return runCatching { buffer.apply { requestCopy.body!!.writeTo(this) }.readUtf8() } + .getOrNull() ?: "" + } + + companion object { + private val MEDIA_TYPE = "application/json; charset=utf-8".toMediaTypeOrNull() + private const val SEARCH_URL = "/api/getBook" + private const val MANGA_URL = "/api/getBookByID" + private const val TAG_URL = "/api/getTag" + } +} diff --git a/src/en/ninehentai/src/eu/kanade/tachiyomi/extension/en/ninehentai/NineHentaiDto.kt b/src/en/ninehentai/src/eu/kanade/tachiyomi/extension/en/ninehentai/NineHentaiDto.kt new file mode 100644 index 00000000000..d2e63f05e9f --- /dev/null +++ b/src/en/ninehentai/src/eu/kanade/tachiyomi/extension/en/ninehentai/NineHentaiDto.kt @@ -0,0 +1,84 @@ +package eu.kanade.tachiyomi.extension.en.ninehentai + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class Manga( + val id: Int, + val title: String, + val image_server: String, + val total_page: Int, +) + +/* +The basic search request JSON object looks like this: +{ + "search": { + "text": "", + "page": 1, + "sort": 1, + "pages": { + "range": [0, 2000] + }, + "tag": { + "items": { + "included": [], + "excluded": [] + } + } + } +} +*/ + +/* + Sort = 0, Newest + Sort = 1, Popular right now + Sort = 2, Most Fapped + Sort = 3, Most Viewed + Sort = 4, By title + */ + +@Serializable +data class SearchRequest( + val text: String, + val page: Int, + val sort: Int, + val pages: Range, + val tag: Items, +) + +@Serializable +data class SearchRequestPayload( + val search: SearchRequest, +) + +@Serializable +data class SearchResponse( + @SerialName("total_count") val totalCount: Int, + val results: List, +) + +@Serializable +data class Range( + val range: List, +) + +@Serializable +data class Items( + val items: TagArrays, +) + +@Serializable +data class TagArrays( + val included: List, + val excluded: List, +) + +@Serializable +data class Tag( + val id: Int, + val name: String, + val description: String? = null, + val type: Int = 1, +) diff --git a/src/en/ninehentai/src/eu/kanade/tachiyomi/extension/en/ninehentai/NineHentaiUrlActivity.kt b/src/en/ninehentai/src/eu/kanade/tachiyomi/extension/en/ninehentai/NineHentaiUrlActivity.kt new file mode 100644 index 00000000000..f0f2cd3fbf3 --- /dev/null +++ b/src/en/ninehentai/src/eu/kanade/tachiyomi/extension/en/ninehentai/NineHentaiUrlActivity.kt @@ -0,0 +1,38 @@ +package eu.kanade.tachiyomi.extension.en.ninehentai + +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://9hentai.so/g/xxxxxx intents and redirects them to + * the main Tachiyomi process. + */ +class NineHentaiUrlActivity : 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("NineHentaiUrlActivity", e.toString()) + } + } else { + Log.e("NineHentaiUrlActivity", "could not parse uri from intent $intent") + } + + finish() + exitProcess(0) + } +}