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
|
@ -3,9 +3,9 @@ apply plugin: 'kotlin-android'
|
|||
|
||||
ext {
|
||||
extName = 'nHentai.com (unoriginal)'
|
||||
pkgNameSuffix = 'en.nhentai.com'
|
||||
extClass = '.NHentaiCom'
|
||||
extVersionCode = 1
|
||||
pkgNameSuffix = 'all.nhentaicom'
|
||||
extClass = '.NHentaiComFactory'
|
||||
extVersionCode = 2
|
||||
libVersion = '1.2'
|
||||
containsNsfw = true
|
||||
}
|
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 5.2 KiB |
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 7.2 KiB After Width: | Height: | Size: 7.2 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 101 KiB After Width: | Height: | Size: 101 KiB |
|
@ -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.network.GET
|
||||
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.SManga
|
||||
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.HttpUrl.Companion.toHttpUrlOrNull
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import rx.Observable
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
|
||||
@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 lang = "en"
|
||||
private val langId = toLangId(lang)
|
||||
|
||||
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()
|
||||
.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 {
|
||||
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 {
|
||||
title = json["title"].string
|
||||
thumbnail_url = json["image_url"].string
|
||||
url = json["slug"].string
|
||||
val jsonObj = jsonEl.jsonObject
|
||||
title = jsonObj["title"]!!.jsonPrimitive.content
|
||||
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()
|
||||
.setQueryParameter("nsfw", "false").toString()
|
||||
|
||||
private fun getMangaUrl(page: Int, sort: String): String {
|
||||
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
|
||||
|
||||
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)
|
||||
|
@ -79,7 +98,7 @@ class NHentaiCom : HttpSource() {
|
|||
// Latest
|
||||
|
||||
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)
|
||||
|
@ -90,14 +109,20 @@ class NHentaiCom : HttpSource() {
|
|||
val url = "$baseUrl/api/comics".toHttpUrlOrNull()!!.newBuilder()
|
||||
.addQueryParameter("per_page", "18")
|
||||
.addQueryParameter("page", page.toString())
|
||||
.addQueryParameter("order", "desc")
|
||||
.addQueryParameter("q", query)
|
||||
.addQueryParameter("nsfw", "false")
|
||||
|
||||
if (langId.isNotBlank()) {
|
||||
url.setQueryParameter("languages[]", langId)
|
||||
}
|
||||
url.setQueryParameter("page", "$page")
|
||||
url.setQueryParameter("nsfw", "false")
|
||||
|
||||
filters.forEach { filter ->
|
||||
when (filter) {
|
||||
is SortFilter -> url.addQueryParameter("sort", filter.toUriPart())
|
||||
is DurationFilter -> url.addQueryParameter("duration", filter.toUriPart())
|
||||
is SortOrderFilter -> url.addQueryParameter("order", filter.toUriPart())
|
||||
}
|
||||
}
|
||||
return GET(url.toString(), headers)
|
||||
|
@ -113,32 +138,26 @@ class NHentaiCom : HttpSource() {
|
|||
.map { mangaDetailsParse(it).apply { initialized = true } }
|
||||
|
||||
// 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 {
|
||||
return GET(getMangaUrl("$baseUrl/api/comics/${manga.url}"), headers)
|
||||
return GET("$baseUrl/api/comics/${manga.url}", headers)
|
||||
}
|
||||
|
||||
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 {
|
||||
description = jsonObject["description"].nullString
|
||||
status = jsonObject["status"].nullString.toStatus()
|
||||
thumbnail_url = jsonObject["image_url"].nullString
|
||||
genre = try { jsonObject["tags"].asJsonArray.joinToString { it["name"].string } } catch (_: Exception) { null }
|
||||
artist = try { jsonObject["artists"].asJsonArray.joinToString { it["name"].string } } catch (_: Exception) { null }
|
||||
author = try { jsonObject["authors"].asJsonArray.joinToString { it["name"].string } } catch (_: Exception) { null }
|
||||
description = jsonObject["description"]!!.jsonPrimitive.content
|
||||
status = SManga.COMPLETED
|
||||
thumbnail_url = jsonObject["image_url"]!!.jsonPrimitive.content
|
||||
genre = runCatching{ jsonObject["tags"]!!.jsonArray.joinToString { it.jsonObject["name"]!!.jsonPrimitive.content }}.getOrNull()
|
||||
artist = runCatching{ jsonObject["artists"]!!.jsonArray.joinToString { it.jsonObject["name"]!!.jsonPrimitive.content }}.getOrNull()
|
||||
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
|
||||
|
||||
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
|
||||
|
@ -159,12 +178,13 @@ class NHentaiCom : HttpSource() {
|
|||
// Pages
|
||||
|
||||
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> {
|
||||
return gson.fromJson<JsonObject>(response.body!!.string())["images"].asJsonArray.mapIndexed { i, json ->
|
||||
Page(i, "", json["source_url"].string)
|
||||
return json.parseToJsonElement(response.body!!.string()).jsonObject["images"]!!.jsonArray.mapIndexed { i, jsonEl ->
|
||||
val jsonObj = jsonEl.jsonObject
|
||||
Page(i, "", jsonObj["source_url"]!!.jsonPrimitive.content)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -174,18 +194,26 @@ class NHentaiCom : HttpSource() {
|
|||
|
||||
override fun getFilterList() = FilterList(
|
||||
DurationFilter(getDurationList()),
|
||||
SortFilter(getSortList())
|
||||
SortFilter(getSortList()),
|
||||
SortOrderFilter(getSortOrder())
|
||||
)
|
||||
|
||||
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 SortOrderFilter(pairs: Array<Pair<String, String>>) : UriPartFilter("Order", pairs)
|
||||
|
||||
open class UriPartFilter(displayName: String, private val vals: Array<Pair<String, String>>) :
|
||||
Filter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) {
|
||||
fun toUriPart() = vals[state].second
|
||||
}
|
||||
|
||||
private fun getSortOrder() = arrayOf(
|
||||
Pair("Descending", "desc"),
|
||||
Pair("Ascending", "asc"),
|
||||
)
|
||||
|
||||
private fun getDurationList() = arrayOf(
|
||||
Pair("All time", "all"),
|
||||
Pair("Year", "year"),
|
||||
|
@ -195,7 +223,10 @@ class NHentaiCom : HttpSource() {
|
|||
)
|
||||
|
||||
private fun getSortList() = arrayOf(
|
||||
Pair("Upload date", "uploaded_at"),
|
||||
Pair("Title", "title"),
|
||||
Pair("Pages", "pages"),
|
||||
Pair("Favorites", "favorites"),
|
||||
Pair("Popularity", "popularity"),
|
||||
Pair("Date", "uploaded_at")
|
||||
)
|
||||
}
|
|
@ -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"
|
||||
)
|