Simply Hentai - new extension (#1838)
This commit is contained in:
parent
b6c602545c
commit
1e041cd669
|
@ -0,0 +1,17 @@
|
||||||
|
apply plugin: 'com.android.application'
|
||||||
|
apply plugin: 'kotlin-android'
|
||||||
|
|
||||||
|
ext {
|
||||||
|
appName = 'Tachiyomi: Simply Hentai'
|
||||||
|
pkgNameSuffix = 'all.simplyhentai'
|
||||||
|
extClass = '.SimplyHentaiFactory'
|
||||||
|
extVersionCode = 1
|
||||||
|
libVersion = '1.2'
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
compileOnly 'com.google.code.gson:gson:2.8.2'
|
||||||
|
compileOnly 'com.github.salomonbrys.kotson:kotson:2.5.0'
|
||||||
|
}
|
||||||
|
|
||||||
|
apply from: "$rootDir/common.gradle"
|
Binary file not shown.
After Width: | Height: | Size: 3.7 KiB |
Binary file not shown.
After Width: | Height: | Size: 2.1 KiB |
Binary file not shown.
After Width: | Height: | Size: 5.0 KiB |
Binary file not shown.
After Width: | Height: | Size: 9.6 KiB |
Binary file not shown.
After Width: | Height: | Size: 14 KiB |
Binary file not shown.
After Width: | Height: | Size: 72 KiB |
|
@ -0,0 +1,199 @@
|
||||||
|
package eu.kanade.tachiyomi.extension.all.simplyhentai
|
||||||
|
|
||||||
|
import com.github.salomonbrys.kotson.forEach
|
||||||
|
import com.github.salomonbrys.kotson.fromJson
|
||||||
|
import com.github.salomonbrys.kotson.get
|
||||||
|
import com.github.salomonbrys.kotson.string
|
||||||
|
import com.google.gson.Gson
|
||||||
|
import com.google.gson.JsonObject
|
||||||
|
import eu.kanade.tachiyomi.network.GET
|
||||||
|
import eu.kanade.tachiyomi.source.model.*
|
||||||
|
import eu.kanade.tachiyomi.source.online.ParsedHttpSource
|
||||||
|
import okhttp3.HttpUrl
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
import okhttp3.Request
|
||||||
|
import okhttp3.Response
|
||||||
|
import org.jsoup.nodes.Document
|
||||||
|
import org.jsoup.nodes.Element
|
||||||
|
|
||||||
|
|
||||||
|
abstract class SimplyHentai (
|
||||||
|
override val lang: String,
|
||||||
|
private val urlLang: String,
|
||||||
|
private val searchLang: String
|
||||||
|
) : ParsedHttpSource() {
|
||||||
|
override val name = "Simply Hentai"
|
||||||
|
|
||||||
|
override val baseUrl = "https://www.simply-hentai.com"
|
||||||
|
|
||||||
|
override val supportsLatest = true
|
||||||
|
|
||||||
|
override val client: OkHttpClient = network.cloudflareClient
|
||||||
|
|
||||||
|
private val gson = Gson()
|
||||||
|
|
||||||
|
// Popular
|
||||||
|
|
||||||
|
override fun popularMangaRequest(page: Int): Request {
|
||||||
|
return GET("$baseUrl/album/language/$urlLang/$page/popularity/desc", headers)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun popularMangaSelector() = "div.col-sm-3"
|
||||||
|
|
||||||
|
override fun popularMangaFromElement(element: Element): SManga {
|
||||||
|
val manga = SManga.create()
|
||||||
|
|
||||||
|
element.select("h3.object-title a").let {
|
||||||
|
manga.url = it.attr("href").substringAfter(baseUrl)
|
||||||
|
manga.title = it.text()
|
||||||
|
}
|
||||||
|
manga.thumbnail_url = element.select("img.img-responsive").attr("abs:data-src")
|
||||||
|
|
||||||
|
return manga
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun popularMangaNextPageSelector() = "a[rel=next]"
|
||||||
|
|
||||||
|
// Latest
|
||||||
|
|
||||||
|
override fun latestUpdatesRequest(page: Int): Request {
|
||||||
|
return GET("$baseUrl/album/language/$urlLang/$page", headers)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun latestUpdatesSelector() = popularMangaSelector()
|
||||||
|
|
||||||
|
override fun latestUpdatesFromElement(element: Element): SManga = popularMangaFromElement(element)
|
||||||
|
|
||||||
|
override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector()
|
||||||
|
|
||||||
|
// Search
|
||||||
|
|
||||||
|
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||||
|
val url = HttpUrl.parse("$baseUrl/search")!!.newBuilder()
|
||||||
|
.addQueryParameter("query", query)
|
||||||
|
.addQueryParameter("language_ids[$searchLang]", searchLang)
|
||||||
|
.addQueryParameter("page", page.toString())
|
||||||
|
|
||||||
|
(if (filters.isEmpty()) getFilterList() else filters).forEach { filter ->
|
||||||
|
when (filter) {
|
||||||
|
is GenreList -> {
|
||||||
|
filter.state.forEach {
|
||||||
|
if (it.state) url.addQueryParameter("tag_ids[${it.id}]", it.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is SeriesList -> {
|
||||||
|
filter.state.forEach {
|
||||||
|
if (it.state) url.addQueryParameter("series_id[${it.id}]", it.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is SortOrder -> {
|
||||||
|
url.addQueryParameter("sort", getSortOrder()[filter.state].second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return GET(url.toString(), headers)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun searchMangaSelector() = popularMangaSelector()
|
||||||
|
|
||||||
|
override fun searchMangaFromElement(element: Element) = popularMangaFromElement(element)
|
||||||
|
|
||||||
|
override fun searchMangaNextPageSelector() = popularMangaNextPageSelector()
|
||||||
|
|
||||||
|
// Details
|
||||||
|
|
||||||
|
override fun mangaDetailsParse(document: Document): SManga {
|
||||||
|
val manga = SManga.create()
|
||||||
|
|
||||||
|
document.select("div.padding-md-right-8").let { info ->
|
||||||
|
manga.artist = info.select("div.box-title:contains(Artists) + a").text()
|
||||||
|
manga.author = manga.artist
|
||||||
|
manga.genre = info.select("a[rel=tag]").joinToString { it.text() }
|
||||||
|
manga.description = info.select("div.link-box > div.box-title:contains(Series) ~ a").let { e ->
|
||||||
|
if (e.text().isNotEmpty()) "Series: ${e.joinToString { it.text() }}\n\n" else ""
|
||||||
|
}
|
||||||
|
manga.description += info.select("div.link-box > div.box-title:contains(Characters) ~ a").let { e ->
|
||||||
|
if (e.text().isNotEmpty()) ("Characters: ${e.joinToString { it.text() }}\n\n") else ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
manga.thumbnail_url = document.select("div.col-xs-12 img.img-responsive").attr("abs:data-src")
|
||||||
|
|
||||||
|
return manga
|
||||||
|
}
|
||||||
|
|
||||||
|
// Chapters
|
||||||
|
|
||||||
|
override fun chapterListParse(response: Response): List<SChapter> {
|
||||||
|
val chapter = SChapter.create().apply {
|
||||||
|
name = "Chapter"
|
||||||
|
url = response.request().url().toString().removeSuffix("/").substringAfterLast("/")
|
||||||
|
}
|
||||||
|
return listOf(chapter)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun chapterListSelector() = "not used"
|
||||||
|
|
||||||
|
override fun chapterFromElement(element: Element): SChapter = throw UnsupportedOperationException("Not used")
|
||||||
|
|
||||||
|
// Pages
|
||||||
|
|
||||||
|
override fun pageListRequest(chapter: SChapter): Request {
|
||||||
|
return GET("https://api.simply-hentai.com/v1/images/album/${chapter.url}", headersBuilder().add("X-Requested-With", "XMLHttpRequest").build())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun pageListParse(response: Response): List<Page> {
|
||||||
|
val pages = mutableListOf<Page>()
|
||||||
|
|
||||||
|
gson.fromJson<JsonObject>(response.body()!!.string()).forEach { _, jsonElement ->
|
||||||
|
pages.add(Page(pages.size, "", jsonElement["sizes"]["full"].string))
|
||||||
|
}
|
||||||
|
|
||||||
|
return pages
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun pageListParse(document: Document): List<Page> = throw UnsupportedOperationException("Not used")
|
||||||
|
|
||||||
|
override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not used")
|
||||||
|
|
||||||
|
// Filters
|
||||||
|
|
||||||
|
private class SortOrder(sortPairs: List<Pair<String, String>>): Filter.Select<String>("Sort By", sortPairs.map { it.first }.toTypedArray())
|
||||||
|
private class SearchPair(name: String, val id: String = name) : Filter.CheckBox(name)
|
||||||
|
private class GenreList(searchVal: List<SearchPair>) : Filter.Group<SearchPair>("Genres", searchVal)
|
||||||
|
private class SeriesList(searchVal: List<SearchPair>) : Filter.Group<SearchPair>("Series", searchVal)
|
||||||
|
|
||||||
|
override fun getFilterList() = FilterList(
|
||||||
|
SortOrder(getSortOrder()),
|
||||||
|
GenreList(getGenreList()),
|
||||||
|
SeriesList(getSeriesList())
|
||||||
|
)
|
||||||
|
|
||||||
|
// "Relevance" should be empty, don't add a "Views" sort order
|
||||||
|
private fun getSortOrder() = listOf(
|
||||||
|
Pair("Relevance", ""),
|
||||||
|
Pair("Popularity", "sort_value"),
|
||||||
|
Pair("Upload Date", "created_at")
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO: add more to getGenreList and getSeriesList
|
||||||
|
private fun getGenreList() = listOf(
|
||||||
|
SearchPair("Solo Female", "4807"),
|
||||||
|
SearchPair("Solo Male", "4805"),
|
||||||
|
SearchPair("Big Breasts", "2528"),
|
||||||
|
SearchPair("Nakadashi", "2418"),
|
||||||
|
SearchPair("Blowjob", "64"),
|
||||||
|
SearchPair("Schoolgirl Uniform", "2522"),
|
||||||
|
SearchPair("Stockings", "33")
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun getSeriesList() = listOf(
|
||||||
|
SearchPair("Original Work", "1093"),
|
||||||
|
SearchPair("Kantai Collection", "1316"),
|
||||||
|
SearchPair("Touhou", "747"),
|
||||||
|
SearchPair("Fate Grand Order", "2171"),
|
||||||
|
SearchPair("Idolmaster", "306"),
|
||||||
|
SearchPair("Granblue Fantasy", "2041"),
|
||||||
|
SearchPair("Girls Und Panzer", "1324")
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
package eu.kanade.tachiyomi.extension.all.simplyhentai
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.source.Source
|
||||||
|
import eu.kanade.tachiyomi.source.SourceFactory
|
||||||
|
|
||||||
|
class SimplyHentaiFactory : SourceFactory {
|
||||||
|
override fun createSources(): List<Source> = listOf(
|
||||||
|
SimplyHentaiEN(),
|
||||||
|
SimplyHentaiJA(),
|
||||||
|
SimplyHentaiZH(),
|
||||||
|
SimplyHentaiKO(),
|
||||||
|
SimplyHentaiES(),
|
||||||
|
SimplyHentaiRU(),
|
||||||
|
SimplyHentaiFR(),
|
||||||
|
SimplyHentaiDE(),
|
||||||
|
SimplyHentaiPT()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
class SimplyHentaiEN: SimplyHentai("en", "english", "1")
|
||||||
|
class SimplyHentaiJA: SimplyHentai("ja", "japanese", "2")
|
||||||
|
class SimplyHentaiZH: SimplyHentai("zh", "chinese", "11")
|
||||||
|
class SimplyHentaiKO: SimplyHentai("ko", "korean", "9")
|
||||||
|
class SimplyHentaiES: SimplyHentai("es", "spanish", "8")
|
||||||
|
class SimplyHentaiRU: SimplyHentai("ru", "russian", "7")
|
||||||
|
class SimplyHentaiFR: SimplyHentai("fr", "french", "3")
|
||||||
|
class SimplyHentaiDE: SimplyHentai("de", "german", "4")
|
||||||
|
class SimplyHentaiPT: SimplyHentai("pt", "portuguese", "6")
|
||||||
|
|
Loading…
Reference in New Issue