nHentai.com switch to kotlinx and add support for multilang (#7990)

* Create NHentaiCom.kt

* Create NHentaiComFactory.kt

* Create NHentaiCom.kt

* Delete NHentaiCom.kt

* Create build.gradle

* Create AndroidManifest.xml

* Added Icons

* Remove Single Lang files

* Added (unoriginal) back to naming

* Changed name back to (unoriginal) ending

* Removed Langues

* Update NHentaiComFactory.kt

* Update NHentaiCom.kt

* change from try to runCatching
This commit is contained in:
Johannes Joens 2021-07-07 21:52:33 +12:00 committed by GitHub
parent 6f00690c8e
commit 03850d43a7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 105 additions and 60 deletions

View File

@ -3,9 +3,9 @@ apply plugin: 'kotlin-android'
ext { ext {
extName = 'nHentai.com (unoriginal)' extName = 'nHentai.com (unoriginal)'
pkgNameSuffix = 'en.nhentai.com' pkgNameSuffix = 'all.nhentaicom'
extClass = '.NHentaiCom' extClass = '.NHentaiComFactory'
extVersionCode = 1 extVersionCode = 2
libVersion = '1.2' libVersion = '1.2'
containsNsfw = true containsNsfw = true
} }

View File

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

View File

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

Before

Width:  |  Height:  |  Size: 7.2 KiB

After

Width:  |  Height:  |  Size: 7.2 KiB

View File

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

View File

Before

Width:  |  Height:  |  Size: 101 KiB

After

Width:  |  Height:  |  Size: 101 KiB

View File

@ -1,14 +1,5 @@
package eu.kanade.tachiyomi.extension.en.nhentai.com package eu.kanade.tachiyomi.extension.all.nhentaicom
import android.app.Application
import android.content.SharedPreferences
import com.github.salomonbrys.kotson.fromJson
import com.github.salomonbrys.kotson.get
import com.github.salomonbrys.kotson.int
import com.github.salomonbrys.kotson.nullString
import com.github.salomonbrys.kotson.string
import com.google.gson.Gson
import com.google.gson.JsonObject
import eu.kanade.tachiyomi.annotations.Nsfw import eu.kanade.tachiyomi.annotations.Nsfw
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.asObservableSuccess import eu.kanade.tachiyomi.network.asObservableSuccess
@ -19,59 +10,87 @@ import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.source.online.HttpSource
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import okhttp3.Headers import okhttp3.Headers
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import okhttp3.Response import okhttp3.Response
import rx.Observable import rx.Observable
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.injectLazy
import uy.kohesive.injekt.api.get
@Nsfw @Nsfw
class NHentaiCom : HttpSource() { class NHentaiCom(override val lang: String) : HttpSource() {
override val name = "nHentai.com (unoriginal)" override val name = when (lang) {
"other" -> "nHentai.com (unoriginal)(Text Cleaned)"
"all" -> "nHentai.com (unoriginal)(Unfiltered)"
else -> "nHentai.com (unoriginal)"
}
override val id = when (lang) {
"en" -> 5591830863732393712
else -> super.id
}
override val baseUrl = "https://nhentai.com" override val baseUrl = "https://nhentai.com"
override val lang = "en" private val langId = toLangId(lang)
override val supportsLatest = true override val supportsLatest = true
private val json: Json by injectLazy()
private fun toLangId(langCode: String): String {
return when (langCode) {
"en" -> "1"
"zh" -> "2"
"ja" -> "3"
"other" -> "4"
"cz" -> "5"
"ar" -> "6"
"sk" -> "7"
"eo" -> "8"
else -> ""
}
}
override fun headersBuilder(): Headers.Builder = Headers.Builder() override fun headersBuilder(): Headers.Builder = Headers.Builder()
.add("User-Agent", "Mozilla/5.0 (Windows NT 6.3; WOW64)") .add("User-Agent", "Mozilla/5.0 (Windows NT 6.3; WOW64)")
private val preferences: SharedPreferences by lazy {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
}
override val client: OkHttpClient = network.cloudflareClient
private val gson = Gson()
private fun parseMangaFromJson(response: Response): MangasPage { private fun parseMangaFromJson(response: Response): MangasPage {
val jsonObject = gson.fromJson<JsonObject>(response.body!!.string()) val jsonRaw = response.body!!.string()
val jsonResult = json.parseToJsonElement(jsonRaw).jsonObject
val mangas = jsonObject["data"].asJsonArray.map { json -> val mangas = jsonResult["data"]!!.jsonArray.map { jsonEl ->
SManga.create().apply { SManga.create().apply {
title = json["title"].string val jsonObj = jsonEl.jsonObject
thumbnail_url = json["image_url"].string title = jsonObj["title"]!!.jsonPrimitive.content
url = json["slug"].string thumbnail_url = jsonObj["image_url"]!!.jsonPrimitive.content
url = jsonObj["slug"]!!.jsonPrimitive.content
} }
} }
return MangasPage(mangas, jsonObject["current_page"].int < jsonObject["last_page"].int) return MangasPage(mangas, jsonResult["current_page"]!!.jsonPrimitive.content.toInt() < jsonResult["last_page"]!!.jsonPrimitive.content.toInt())
} }
private fun getMangaUrl(url: String): String {
return url.toHttpUrlOrNull()!!.newBuilder() private fun getMangaUrl(page: Int, sort: String): String {
.setQueryParameter("nsfw", "false").toString() val url = "$baseUrl/api/comics".toHttpUrlOrNull()!!.newBuilder()
if (langId.isNotBlank()) {
url.setQueryParameter("languages[]", langId)
}
url.setQueryParameter("page", "$page")
url.setQueryParameter("sort", sort)
url.setQueryParameter("nsfw", "false")
return url.toString()
} }
// Popular // Popular
override fun popularMangaRequest(page: Int): Request { override fun popularMangaRequest(page: Int): Request {
return GET(getMangaUrl("$baseUrl/api/comics?page=$page&q=&sort=popularity&order=desc&duration=week"), headers) return GET(getMangaUrl(page, "popularity"), headers)
} }
override fun popularMangaParse(response: Response): MangasPage = parseMangaFromJson(response) override fun popularMangaParse(response: Response): MangasPage = parseMangaFromJson(response)
@ -79,7 +98,7 @@ class NHentaiCom : HttpSource() {
// Latest // Latest
override fun latestUpdatesRequest(page: Int): Request { override fun latestUpdatesRequest(page: Int): Request {
return GET(getMangaUrl("$baseUrl/api/comics?page=$page&q=&sort=uploaded_at&order=desc&duration=day"), headers) return GET(getMangaUrl(page, "uploaded_at"), headers)
} }
override fun latestUpdatesParse(response: Response): MangasPage = parseMangaFromJson(response) override fun latestUpdatesParse(response: Response): MangasPage = parseMangaFromJson(response)
@ -90,14 +109,20 @@ class NHentaiCom : HttpSource() {
val url = "$baseUrl/api/comics".toHttpUrlOrNull()!!.newBuilder() val url = "$baseUrl/api/comics".toHttpUrlOrNull()!!.newBuilder()
.addQueryParameter("per_page", "18") .addQueryParameter("per_page", "18")
.addQueryParameter("page", page.toString()) .addQueryParameter("page", page.toString())
.addQueryParameter("order", "desc")
.addQueryParameter("q", query) .addQueryParameter("q", query)
.addQueryParameter("nsfw", "false") .addQueryParameter("nsfw", "false")
if (langId.isNotBlank()) {
url.setQueryParameter("languages[]", langId)
}
url.setQueryParameter("page", "$page")
url.setQueryParameter("nsfw", "false")
filters.forEach { filter -> filters.forEach { filter ->
when (filter) { when (filter) {
is SortFilter -> url.addQueryParameter("sort", filter.toUriPart()) is SortFilter -> url.addQueryParameter("sort", filter.toUriPart())
is DurationFilter -> url.addQueryParameter("duration", filter.toUriPart()) is DurationFilter -> url.addQueryParameter("duration", filter.toUriPart())
is SortOrderFilter -> url.addQueryParameter("order", filter.toUriPart())
} }
} }
return GET(url.toString(), headers) return GET(url.toString(), headers)
@ -113,32 +138,26 @@ class NHentaiCom : HttpSource() {
.map { mangaDetailsParse(it).apply { initialized = true } } .map { mangaDetailsParse(it).apply { initialized = true } }
// Return the real URL for "Open in browser" // Return the real URL for "Open in browser"
override fun mangaDetailsRequest(manga: SManga) = GET(getMangaUrl("$baseUrl/en/webtoon/${manga.url}"), headers) override fun mangaDetailsRequest(manga: SManga) = GET("$baseUrl/en/comic/${manga.url}", headers)
private fun apiMangaDetailsRequest(manga: SManga): Request { private fun apiMangaDetailsRequest(manga: SManga): Request {
return GET(getMangaUrl("$baseUrl/api/comics/${manga.url}"), headers) return GET("$baseUrl/api/comics/${manga.url}", headers)
} }
override fun mangaDetailsParse(response: Response): SManga { override fun mangaDetailsParse(response: Response): SManga {
val jsonObject = gson.fromJson<JsonObject>(response.body!!.string()) val jsonRaw = response.body!!.string()
val jsonObject = json.parseToJsonElement(jsonRaw).jsonObject
return SManga.create().apply { return SManga.create().apply {
description = jsonObject["description"].nullString description = jsonObject["description"]!!.jsonPrimitive.content
status = jsonObject["status"].nullString.toStatus() status = SManga.COMPLETED
thumbnail_url = jsonObject["image_url"].nullString thumbnail_url = jsonObject["image_url"]!!.jsonPrimitive.content
genre = try { jsonObject["tags"].asJsonArray.joinToString { it["name"].string } } catch (_: Exception) { null } genre = runCatching{ jsonObject["tags"]!!.jsonArray.joinToString { it.jsonObject["name"]!!.jsonPrimitive.content }}.getOrNull()
artist = try { jsonObject["artists"].asJsonArray.joinToString { it["name"].string } } catch (_: Exception) { null } artist = runCatching{ jsonObject["artists"]!!.jsonArray.joinToString { it.jsonObject["name"]!!.jsonPrimitive.content }}.getOrNull()
author = try { jsonObject["authors"].asJsonArray.joinToString { it["name"].string } } catch (_: Exception) { null } author = runCatching{ jsonObject["authors"]!!.jsonArray.joinToString { it.jsonObject["name"]!!.jsonPrimitive.content }}.getOrNull()
} }
} }
private fun String?.toStatus() = when {
this == null -> SManga.UNKNOWN
this.contains("ongoing", ignoreCase = true) -> SManga.ONGOING
this.contains("complete", ignoreCase = true) -> SManga.COMPLETED
else -> SManga.UNKNOWN
}
// Chapters // Chapters
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> { override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
@ -159,12 +178,13 @@ class NHentaiCom : HttpSource() {
// Pages // Pages
override fun pageListRequest(chapter: SChapter): Request { override fun pageListRequest(chapter: SChapter): Request {
return GET(getMangaUrl("$baseUrl/api/comics/${chapter.url}/images"), headers) return GET("$baseUrl/api/comics/${chapter.url}/images", headers)
} }
override fun pageListParse(response: Response): List<Page> { override fun pageListParse(response: Response): List<Page> {
return gson.fromJson<JsonObject>(response.body!!.string())["images"].asJsonArray.mapIndexed { i, json -> return json.parseToJsonElement(response.body!!.string()).jsonObject["images"]!!.jsonArray.mapIndexed { i, jsonEl ->
Page(i, "", json["source_url"].string) val jsonObj = jsonEl.jsonObject
Page(i, "", jsonObj["source_url"]!!.jsonPrimitive.content)
} }
} }
@ -174,18 +194,26 @@ class NHentaiCom : HttpSource() {
override fun getFilterList() = FilterList( override fun getFilterList() = FilterList(
DurationFilter(getDurationList()), DurationFilter(getDurationList()),
SortFilter(getSortList()) SortFilter(getSortList()),
SortOrderFilter(getSortOrder())
) )
private class DurationFilter(pairs: Array<Pair<String, String>>) : UriPartFilter("Duration", pairs) private class DurationFilter(pairs: Array<Pair<String, String>>) : UriPartFilter("Duration", pairs)
private class SortFilter(pairs: Array<Pair<String, String>>) : UriPartFilter("Sorted by", pairs) private class SortFilter(pairs: Array<Pair<String, String>>) : UriPartFilter("Sorted by", pairs)
private class SortOrderFilter(pairs: Array<Pair<String, String>>) : UriPartFilter("Order", pairs)
open class UriPartFilter(displayName: String, private val vals: Array<Pair<String, String>>) : open class UriPartFilter(displayName: String, private val vals: Array<Pair<String, String>>) :
Filter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) { Filter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) {
fun toUriPart() = vals[state].second fun toUriPart() = vals[state].second
} }
private fun getSortOrder() = arrayOf(
Pair("Descending", "desc"),
Pair("Ascending", "asc"),
)
private fun getDurationList() = arrayOf( private fun getDurationList() = arrayOf(
Pair("All time", "all"), Pair("All time", "all"),
Pair("Year", "year"), Pair("Year", "year"),
@ -195,7 +223,10 @@ class NHentaiCom : HttpSource() {
) )
private fun getSortList() = arrayOf( private fun getSortList() = arrayOf(
Pair("Upload date", "uploaded_at"),
Pair("Title", "title"),
Pair("Pages", "pages"),
Pair("Favorites", "favorites"),
Pair("Popularity", "popularity"), Pair("Popularity", "popularity"),
Pair("Date", "uploaded_at")
) )
} }

View File

@ -0,0 +1,14 @@
package eu.kanade.tachiyomi.extension.all.nhentaicom
import eu.kanade.tachiyomi.annotations.Nsfw
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.SourceFactory
@Nsfw
class NHentaiComFactory : SourceFactory {
override fun createSources(): List<Source> = languages.map { NHentaiCom(it) }
}
private val languages = listOf(
"all", "en", "zh", "ja", "other", "eo", "cz", "ar", "sk"
)