Henchan: Added sorting things (based on Mangachan because they're similar), (#973)

Sorting by tags and some fixes for "Henchan", new russian source "Nude-Moon"
This commit is contained in:
krogon500 2019-03-26 13:31:59 +05:00 committed by Carlos
parent 2beb568f03
commit a0d60768c0
10 changed files with 539 additions and 43 deletions

View File

@ -5,7 +5,7 @@ ext {
appName = 'Tachiyomi: Henchan' appName = 'Tachiyomi: Henchan'
pkgNameSuffix = 'ru.henchan' pkgNameSuffix = 'ru.henchan'
extClass = '.Henchan' extClass = '.Henchan'
extVersionCode = 6 extVersionCode = 7
libVersion = '1.2' libVersion = '1.2'
} }

View File

@ -8,6 +8,9 @@ import okhttp3.Request
import okhttp3.Response import okhttp3.Response
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import java.text.SimpleDateFormat
import java.util.*
class Henchan : ParsedHttpSource() { class Henchan : ParsedHttpSource() {
@ -28,7 +31,56 @@ class Henchan : ParsedHttpSource() {
GET("$baseUrl/manga/new?offset=${20 * (page - 1)}", headers) GET("$baseUrl/manga/new?offset=${20 * (page - 1)}", headers)
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
val url = "$baseUrl/?do=search&subaction=search&story=$query" var pageNum = 1
when {
page < 1 -> pageNum = 1
page >= 1 -> pageNum = page
}
val url = if (query.isNotEmpty()) {
"$baseUrl/?do=search&subaction=search&story=$query&search_start=$pageNum"
} else {
var genres = ""
var order = ""
for (filter in if (filters.isEmpty()) getFilterList() else filters) {
when(filter){
is GenreList -> {
filter.state.forEach { f ->
if (!f.isIgnored()) {
genres += (if (f.isExcluded()) "-" else "") + f.id + '+'
}
}
}
}
}
if (genres.isNotEmpty()) {
for (filter in filters) {
when (filter) {
is OrderBy -> {
order = if (filter.state!!.ascending) {
arrayOf("&n=dateasc", "&n=favasc", "&n=abcdesc")[filter.state!!.index]
} else {
arrayOf("", "&n=favdesc", "&n=abcasc")[filter.state!!.index]
}
}
}
}
"$baseUrl/tags/${genres.dropLast(1)}&sort=manga$order?offset=${20 * (pageNum - 1)}"
}else{
for (filter in filters) {
when (filter) {
is OrderBy -> {
order = if (filter.state!!.ascending) {
arrayOf("manga/new&n=dateasc", "manga/new&n=favasc", "manga/new&n=abcdesc")[filter.state!!.index]
} else {
arrayOf("manga/new", "mostfavorites&sort=manga", "manga/new&n=abcasc")[filter.state!!.index]
}
}
}
}
"$baseUrl/$order?offset=${20 * (pageNum - 1)}"
}
}
return GET(url, headers) return GET(url, headers)
} }
@ -36,36 +88,21 @@ class Henchan : ParsedHttpSource() {
override fun latestUpdatesSelector() = popularMangaSelector() override fun latestUpdatesSelector() = popularMangaSelector()
override fun searchMangaSelector() = popularMangaSelector() override fun searchMangaSelector() = ".content_row:not(:has(div.item:containsOwn(Тип)))"
override fun searchMangaParse(response: Response): MangasPage { private fun String.getHQThumbnail(): String = this
val document = response.asJsoup() .replace("manganew_thumbs", "showfull_retina/manga")
.replace("img.", "imgcover.")
val mangas = mutableListOf<SManga>() .replace("_henchan.me", "_hentaichan.ru")
document.select(searchMangaSelector()).forEach { element ->
val manga = searchMangaFromElement(element)
if (manga.url != "") {
mangas.add(manga)
}
}
return MangasPage(mangas, false)
}
private fun String.getHQThumbnail(): String = this.replace("manganew_thumbs", "showfull_retina/manga").replace("img.", "imgcover.")
override fun popularMangaFromElement(element: Element): SManga { override fun popularMangaFromElement(element: Element): SManga {
val manga = SManga.create() val manga = SManga.create()
manga.thumbnail_url = element.select("img").first().attr("src").getHQThumbnail() manga.thumbnail_url = element.select("img").first().attr("src").getHQThumbnail()
element.select("h2 > a").first().let {
val url = it.attr("href") val urlElem = element.select("h2 > a").first()
if (url.contains("/manga/")) { manga.setUrlWithoutDomain(urlElem.attr("href"))
manga.setUrlWithoutDomain(url) manga.title = urlElem.text()
manga.title = it.text()
} else {
manga.url = ""
}
}
return manga return manga
} }
@ -79,8 +116,7 @@ class Henchan : ParsedHttpSource() {
override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector() override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector()
override fun searchMangaNextPageSelector() = throw Exception("Not Used") override fun searchMangaNextPageSelector() = "#nextlink, ${popularMangaNextPageSelector()}"
override fun mangaDetailsParse(document: Document): SManga { override fun mangaDetailsParse(document: Document): SManga {
@ -110,8 +146,12 @@ class Henchan : ParsedHttpSource() {
val chap = SChapter.create() val chap = SChapter.create()
chap.setUrlWithoutDomain(responseUrl.removePrefix(baseUrl)) chap.setUrlWithoutDomain(responseUrl.removePrefix(baseUrl))
chap.name = document.select("a.title_top_a").text() chap.name = document.select("a.title_top_a").text()
chap.chapter_number = 0.0F chap.chapter_number = 1F
chap.date_upload = 0L
val date = document.select("div.row4_right b")?.text()?.let {
SimpleDateFormat("dd MMMM yyyy", Locale("ru")).parse(it).time
} ?: 0
chap.date_upload = date
return listOf(chap) return listOf(chap)
} }
@ -119,15 +159,15 @@ class Henchan : ParsedHttpSource() {
val chap = SChapter.create() val chap = SChapter.create()
chap.setUrlWithoutDomain(document.select("#left > div > a").attr("href")) chap.setUrlWithoutDomain(document.select("#left > div > a").attr("href"))
chap.name = document.select("#right > div:nth-child(4)").text().split(" похожий на ")[1] chap.name = document.select("#right > div:nth-child(4)").text().split(" похожий на ")[1]
chap.chapter_number = 0.0F chap.chapter_number = 1F
chap.date_upload = 0L chap.date_upload = 0L
return listOf(chap) return listOf(chap)
} }
val result = mutableListOf<SChapter>() val result = mutableListOf<SChapter>()
result.addAll(document.select(chapterListSelector()).mapIndexed { index, element -> result.addAll(document.select(chapterListSelector()).map {
chapterFromElement(index, element) chapterFromElement(it)
}) })
var url = document.select("div#pagination_related a:contains(Вперед)").attr("href") var url = document.select("div#pagination_related a:contains(Вперед)").attr("href")
@ -137,9 +177,8 @@ class Henchan : ParsedHttpSource() {
headers = headers headers = headers
) )
val nextPage = client.newCall(get).execute().asJsoup() val nextPage = client.newCall(get).execute().asJsoup()
result.addAll(nextPage.select(chapterListSelector()).mapIndexed { result.addAll(nextPage.select(chapterListSelector()).map {
index, element -> chapterFromElement(it)
chapterFromElement(index, element)
}) })
url = nextPage.select("div#pagination_related a:contains(Вперед)").attr("href") url = nextPage.select("div#pagination_related a:contains(Вперед)").attr("href")
@ -148,17 +187,16 @@ class Henchan : ParsedHttpSource() {
return result.reversed() return result.reversed()
} }
private fun chapterFromElement(index: Int, element: Element): SChapter { override fun chapterFromElement(element: Element): SChapter {
val chapter = SChapter.create() val chapter = SChapter.create()
chapter.setUrlWithoutDomain(element.select("h2 a").attr("href")) chapter.setUrlWithoutDomain(element.select("h2 a").attr("href"))
chapter.name = element.select("h2 a").attr("title") val chapterName = element.select("h2 a").attr("title")
chapter.chapter_number = index.toFloat() chapter.name = chapterName
chapter.chapter_number = "(глава\\s|часть\\s)(\\d+)".toRegex(RegexOption.IGNORE_CASE).find(chapterName)?.groupValues?.get(2)?.toFloat() ?: 0F
chapter.date_upload = 0L chapter.date_upload = 0L
return chapter return chapter
} }
override fun chapterFromElement(element: Element): SChapter = throw Exception("Not Used")
override fun pageListRequest(chapter: SChapter): Request { override fun pageListRequest(chapter: SChapter): Request {
return GET(exhentaiBaseUrl + chapter.url.replace("/manga/", "/online/") + "?development_access=true", headers) return GET(exhentaiBaseUrl + chapter.url.replace("/manga/", "/online/") + "?development_access=true", headers)
} }
@ -169,7 +207,7 @@ class Henchan : ParsedHttpSource() {
val resPages = mutableListOf<Page>() val resPages = mutableListOf<Page>()
val imgs = imgString.split(",") val imgs = imgString.split(",")
imgs.forEachIndexed { index, s -> imgs.forEachIndexed { index, s ->
resPages.add(Page(index, imageUrl = s.trim('"', '\'', '[', ' '))) resPages.add(Page(index, imageUrl = s.trim('"', '\'', ' ')))
} }
return resPages return resPages
} }
@ -177,4 +215,168 @@ class Henchan : ParsedHttpSource() {
override fun imageUrlParse(document: Document) = throw Exception("Not Used") override fun imageUrlParse(document: Document) = throw Exception("Not Used")
override fun pageListParse(document: Document): List<Page> = throw Exception("Not Used") override fun pageListParse(document: Document): List<Page> = throw Exception("Not Used")
private class Genre(val id: String, name: String = id.replace('_', ' ').capitalize()) : Filter.TriState(name)
private class GenreList(genres: List<Genre>) : Filter.Group<Genre>("Тэги", genres)
private class OrderBy : Filter.Sort("Сортировка",
arrayOf("Дата", "Популярность", "Алфавит"),
Filter.Sort.Selection(1, false))
override fun getFilterList() = FilterList(
OrderBy(),
GenreList(getGenreList())
)
private fun getGenreList() = listOf(
Genre("3D"),
Genre("action"),
Genre("ahegao"),
Genre("bdsm"),
Genre("foot_fetish"),
Genre("footfuck"),
Genre("gender_bender"),
Genre("live"),
Genre("lolcon"),
Genre("megane"),
Genre("mind_break"),
Genre("monstergirl"),
Genre("netorare"),
Genre("netori"),
Genre("nipple_penetration"),
Genre("paizuri_(titsfuck)"),
Genre("rpg"),
Genre("scat"),
Genre("shemale"),
Genre("shooter"),
Genre("simulation"),
Genre("tomboy"),
Genre("алкоголь"),
Genre("анал"),
Genre("андроид"),
Genre("анилингус"),
Genre("аркада"),
Genre("арт"),
Genre("бабушка"),
Genre("без_текста"),
Genre("без_трусиков"),
Genre("без_цензуры"),
Genre("беременность"),
Genre("бикини"),
Genre("близнецы"),
Genre("боди-арт"),
Genre("больница"),
Genre("большая_грудь"),
Genre("большие_попки"),
Genre("буккаке"),
Genre("в_ванной"),
Genre("в_общественном_месте"),
Genre("в_первый_раз"),
Genre("в_цвете"),
Genre("в_школе"),
Genre("веб"),
Genre("вибратор"),
Genre("визуальная_новелла"),
Genre("внучка"),
Genre("волосатыеенщины"),
Genre("гаремник"),
Genre("гипноз"),
Genre("глубокий_минет"),
Genre("горячий_источник"),
Genre("групповой_секс"),
Genre("гяру_и_гангуро"),
Genre("двойное_проникновение"),
Genre("девочки_волшебницы"),
Genre("девушкауалет"),
Genre("демоны"),
Genre("дилдо"),
Genre("дочь"),
Genre("драма"),
Genre("дыра_в_стене"),
Genre("жестокость"),
Genre("заеньги"),
Genre("зомби"),
Genre("зрелыеенщины"),
Genre("измена"),
Genre("изнасилование"),
Genre("инопланетяне"),
Genre("инцест"),
Genre("исполнениееланий"),
Genre("камера"),
Genre("квест"),
Genre("колготки"),
Genre("комиксы"),
Genre("косплей"),
Genre("кузина"),
Genre("купальники"),
Genre("латекс_и_кожа"),
Genre("магия"),
Genre("маленькая_грудь"),
Genre("мастурбация"),
Genre("мать"),
Genre("мейдочки"),
Genre("мерзкий_дядька"),
Genre("многоевушек"),
Genre("молоко"),
Genre("монстры"),
Genre("мочеиспускание"),
Genre("мужская_озвучка"),
Genre("на_природе"),
Genre("наблюдение"),
Genre("непрямой_инцест"),
Genre("огромная_грудь"),
Genre("огромный_член"),
Genre("остановкаремени"),
Genre("парень_пассив"),
Genre("переодевание"),
Genre("песочница"),
Genre("племянница"),
Genre("пляж"),
Genre("подглядывание"),
Genre("подчинение"),
Genre("похищение"),
Genre("принуждение"),
Genre("прозрачная_одежда"),
Genre("психические_отклонения"),
Genre("публично"),
Genre("рабыни"),
Genre("романтика"),
Genre("сверхъестественное"),
Genre("сексгрушки"),
Genre("сестра"),
Genre("сетакон"),
Genre("спортивная_форма"),
Genre("спящие"),
Genre("страпон"),
Genre("темнокожие"),
Genre("тентакли"),
Genre("толстушки"),
Genre("трап"),
Genre("тётя"),
Genre("учитель_и_ученик"),
Genre("ушастые"),
Genre("фантазии"),
Genre("фантастика"),
Genre("фемдом"),
Genre("фестиваль"),
Genre("фистинг"),
Genre("фурри"),
Genre("футанари"),
Genre("футанари_имеет_парня"),
Genre("фэнтези"),
Genre("хоррор"),
Genre("цундере"),
Genre("чикан"),
Genre("чирлидеры"),
Genre("чулки"),
Genre("школьники"),
Genre("школьницы"),
Genre("школьный_купальник"),
Genre("эксгибиционизм"),
Genre("эльфы"),
Genre("эччи"),
Genre("юмор"),
Genre("юри"),
Genre("яндере"),
Genre("яой")
)
} }

View File

@ -0,0 +1,12 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
ext {
appName = 'Tachiyomi: Nude-Moon'
pkgNameSuffix = 'ru.nudemoon'
extClass = '.Nudemoon'
extVersionCode = 1
libVersion = '1.2'
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

View File

@ -0,0 +1,282 @@
package eu.kanade.tachiyomi.extension.ru.nudemoon
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.source.model.*
import eu.kanade.tachiyomi.source.online.ParsedHttpSource
import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.Request
import okhttp3.Response
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import org.jsoup.nodes.TextNode
import java.net.URLEncoder
import java.text.SimpleDateFormat
import java.util.*
class Nudemoon : ParsedHttpSource() {
override val name = "Nude-Moon"
override val baseUrl = "http://nude-moon.me"
override val lang = "ru"
override val supportsLatest = true
override fun popularMangaRequest(page: Int): Request =
GET("$baseUrl/all_manga?views&rowstart=${30 * (page - 1)}", headers)
override fun latestUpdatesRequest(page: Int): Request =
GET("$baseUrl/all_manga?date&rowstart=${30 * (page - 1)}", headers)
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
//Search by query on this site works really badly, i don't even sure of the need to implement it
val url = if (query.isNotEmpty()) {
"$baseUrl/search?stext=${URLEncoder.encode(query, "CP1251")}&rowstart=${30 * (page - 1)}"
}else{
var genres = ""
var order = ""
for (filter in if (filters.isEmpty()) getFilterList() else filters) {
when(filter){
is GenreList -> {
filter.state.forEach { f ->
if (f.state) {
genres += f.id + '+'
}
}
}
}
}
if (genres.isNotEmpty()) {
for (filter in filters) {
when (filter) {
is OrderBy -> {
//The site has no ascending order
order = arrayOf("&date", "&views", "&like")[filter.state!!.index]
}
}
}
"$baseUrl/tags/${genres.dropLast(1)}$order&rowstart=${30 * (page - 1)}"
}else{
for (filter in filters) {
when (filter) {
is OrderBy -> {
//The site has no ascending order
order = arrayOf("all_manga?date", "all_manga?views", "all_manga?like")[filter.state!!.index]
}
}
}
"$baseUrl/$order&rowstart=${30 * (page - 1)}"
}
}
return GET(url, headers)
}
override fun popularMangaSelector() = "table[cellspacing=\"2\"].news_pic2"
override fun latestUpdatesSelector() = popularMangaSelector()
override fun searchMangaSelector() = popularMangaSelector()
override fun popularMangaFromElement(element: Element): SManga {
val manga = SManga.create()
val thumbnailElem = element.select("img.news_pic2").first()
val parentElem = thumbnailElem.parent()
manga.thumbnail_url = baseUrl + thumbnailElem.attr("src")
manga.title = parentElem.attr("title")
manga.setUrlWithoutDomain(parentElem.attr("href"))
return manga
}
override fun latestUpdatesFromElement(element: Element): SManga =
popularMangaFromElement(element)
override fun searchMangaFromElement(element: Element): SManga =
popularMangaFromElement(element)
override fun popularMangaNextPageSelector() = "a.small:contains(Следующая)"
override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector()
override fun searchMangaNextPageSelector() = popularMangaNextPageSelector()
override fun mangaDetailsParse(document: Document): SManga {
val manga = SManga.create()
manga.author = document.select("a[title^=Показать всю мангу от]").first().text()
manga.genre = document.select("div.tbl2[align] > a").joinToString { it.text() }
manga.description = document.select(".description").text()
return manga
}
override fun chapterListRequest(manga: SManga): Request {
val chapterUrl = if(manga.title.contains("\\s#\\d+".toRegex()))
"/vse_glavy/" + manga.title.split("\\s#\\d+".toRegex())[0].replace("\\W".toRegex(), "_")
else
manga.url
return GET(baseUrl + chapterUrl, headers)
}
override fun chapterListSelector() = popularMangaSelector()
override fun chapterListParse(response: Response): List<SChapter> {
val responseUrl = response.request().url().toString()
val document = response.asJsoup()
if(!responseUrl.contains("/vse_glavy/")){
return listOf(chapterFromElement(document))
}
//Order chapters by its number 'cause on the site they are in random order
return document.select(chapterListSelector()).sortedByDescending {
val regex = "#(\\d+)".toRegex()
val chapterName = it.select("img.news_pic2").first().parent().attr("title")
regex.find(chapterName)?.groupValues?.get(1)?.toInt() ?: 0
}.map { chapterFromElement(it) }
}
override fun chapterFromElement(element: Element): SChapter {
val chapter = SChapter.create()
val infoElem = element.select("img.news_pic2").first().parent()
val chapterName = infoElem.attr("title")
var chapterUrl = infoElem.attr("href")
if(!chapterUrl.contains("-online")) {
chapterUrl = chapterUrl.replace("/\\d+".toRegex(), "$0-online")
} else {
chapter.chapter_number = 1F
}
chapter.setUrlWithoutDomain(if(!chapterUrl.startsWith("/")) "/$chapterUrl" else chapterUrl)
chapter.name = chapterName
chapter.date_upload = (element.select("font:containsOwn(Дата:)")?.first()?.nextSibling() as? TextNode)?.text()?.let {
SimpleDateFormat("dd MMMM yyyy", Locale("ru")).parse(it).time
} ?: 0
return chapter
}
override fun pageListRequest(chapter: SChapter): Request {
return GET(baseUrl + chapter.url, headers)
}
override fun pageListParse(response: Response): List<Page> {
val document = response.asJsoup()
val resPages = mutableListOf<Page>()
val imgScript = document.select("script:containsData(var images)").first().html()
Regex("images\\[(\\d+)].src\\s=\\s'.(.*)'").findAll(imgScript).forEach {
resPages.add(Page(it.groupValues[1].toInt(), imageUrl = baseUrl + it.groupValues[2]))
}
return resPages
}
override fun imageUrlParse(document: Document) = throw Exception("Not Used")
override fun pageListParse(document: Document): List<Page> = throw Exception("Not Used")
private class Genre(name: String, val id: String = name.replace(' ', '_')) : Filter.CheckBox(name.capitalize())
private class GenreList(genres: List<Genre>) : Filter.Group<Genre>("Тэги", genres)
private class OrderBy : Filter.Sort("Сортировка",
arrayOf("Дата", "Просмотры", "Лайки"),
Filter.Sort.Selection(1, false))
override fun getFilterList() = FilterList(
OrderBy(),
GenreList(getGenreList())
)
private fun getGenreList() = listOf(
Genre("анал"),
Genre("без цензуры"),
Genre("беременные"),
Genre("близняшки"),
Genre("большие груди"),
Genre("в бассейне"),
Genre("в больнице"),
Genre("в ванной"),
Genre("в общественном месте"),
Genre("в первый раз"),
Genre("в транспорте"),
Genre("в туалете"),
Genre("гарем"),
Genre("гипноз"),
Genre("горничные"),
Genre("горячий источник"),
Genre("групповой секс"),
Genre("драма"),
Genre("запредельное"),
Genre("золотой дождь"),
Genre("зрелые женщины"),
Genre("идолы"),
Genre("извращение"),
Genre("измена"),
Genre("имеют парня"),
Genre("клизма"),
Genre("колготки"),
Genre("комиксы"),
Genre("комиксы 3D"),
Genre("косплей"),
Genre("мастурбация"),
Genre("мерзкий мужик"),
Genre("много спермы"),
Genre("молоко"),
Genre("монстры"),
Genre("на камеру"),
Genre("на природе"),
Genre("обычный секс"),
Genre("огромный член"),
Genre("пляж"),
Genre("подглядывание"),
Genre("принуждение"),
Genre("продажность"),
Genre("пьяные"),
Genre("рабыни"),
Genre("романтика"),
Genre("с ушками"),
Genre("секс игрушки"),
Genre("спящие"),
Genre("страпон"),
Genre("студенты"),
Genre("суккуб"),
Genre("тентакли"),
Genre("толстушки"),
Genre("трапы"),
Genre("ужасы"),
Genre("униформа"),
Genre("учитель и ученик"),
Genre("фемдом"),
Genre("фетиш"),
Genre("фурри"),
Genre("футанари"),
Genre("футфетиш"),
Genre("фэнтези"),
Genre("цветная"),
Genre("чикан"),
Genre("чулки"),
Genre("шимейл"),
Genre("эксгибиционизм"),
Genre("юмор"),
Genre("юри"),
Genre("ahegao"),
Genre("BDSM"),
Genre("ganguro"),
Genre("gender bender"),
Genre("megane"),
Genre("mind break"),
Genre("monstergirl"),
Genre("netorare"),
Genre("nipple penetration"),
Genre("titsfuck"),
Genre("x-ray")
)
}