From 256b11597f2b3881c62b399564267aa5eb8df345 Mon Sep 17 00:00:00 2001 From: Ejan <35057681+e-shl@users.noreply.github.com> Date: Thu, 9 Jun 2022 23:23:57 +0500 Subject: [PATCH] [RU]MultiChan (MangaChan, HenChan, YaoiChan) (#12125) * [RU]MultiChan (MangaChan, HenChan, YaoiChan) * id * simpleDateFormat --- .../henchan/res/mipmap-hdpi/ic_launcher.png | Bin .../henchan/res/mipmap-mdpi/ic_launcher.png | Bin .../henchan/res/mipmap-xhdpi/ic_launcher.png | Bin .../henchan/res/mipmap-xxhdpi/ic_launcher.png | Bin .../res/mipmap-xxxhdpi/ic_launcher.png | Bin .../multichan}/henchan/res/web_hi_res_512.png | Bin .../multichan/henchan/src/HenChan.kt | 67 ++------ .../mangachan/res/mipmap-hdpi/ic_launcher.png | Bin .../mangachan/res/mipmap-mdpi/ic_launcher.png | Bin .../res/mipmap-xhdpi/ic_launcher.png | Bin .../res/mipmap-xxhdpi/ic_launcher.png | Bin .../res/mipmap-xxxhdpi/ic_launcher.png | Bin .../mangachan/res/web_hi_res_512.png | Bin .../multichan/mangachan/src/MangaChan.kt | 155 +----------------- .../yaoichan/res/mipmap-hdpi/ic_launcher.png | Bin .../yaoichan/res/mipmap-mdpi/ic_launcher.png | Bin .../yaoichan/res/mipmap-xhdpi/ic_launcher.png | Bin .../res/mipmap-xxhdpi/ic_launcher.png | Bin .../res/mipmap-xxxhdpi/ic_launcher.png | Bin .../yaoichan/res/web_hi_res_512.png | Bin .../multichan/yaoichan/src/YaoiChan.kt | 114 +------------ .../multisrc/multichan/ChanGenerator.kt | 26 +++ .../tachiyomi/multisrc/multichan/MultiChan.kt | 145 ++++++++++++++++ src/ru/henchan/AndroidManifest.xml | 2 - src/ru/henchan/build.gradle | 13 -- src/ru/mangachan/AndroidManifest.xml | 2 - src/ru/mangachan/build.gradle | 11 -- src/ru/yaoichan/AndroidManifest.xml | 2 - src/ru/yaoichan/build.gradle | 12 -- 29 files changed, 186 insertions(+), 363 deletions(-) rename {src/ru => multisrc/overrides/multichan}/henchan/res/mipmap-hdpi/ic_launcher.png (100%) rename {src/ru => multisrc/overrides/multichan}/henchan/res/mipmap-mdpi/ic_launcher.png (100%) rename {src/ru => multisrc/overrides/multichan}/henchan/res/mipmap-xhdpi/ic_launcher.png (100%) rename {src/ru => multisrc/overrides/multichan}/henchan/res/mipmap-xxhdpi/ic_launcher.png (100%) rename {src/ru => multisrc/overrides/multichan}/henchan/res/mipmap-xxxhdpi/ic_launcher.png (100%) rename {src/ru => multisrc/overrides/multichan}/henchan/res/web_hi_res_512.png (100%) rename src/ru/henchan/src/eu/kanade/tachiyomi/extension/ru/henchan/Henchan.kt => multisrc/overrides/multichan/henchan/src/HenChan.kt (88%) rename {src/ru => multisrc/overrides/multichan}/mangachan/res/mipmap-hdpi/ic_launcher.png (100%) rename {src/ru => multisrc/overrides/multichan}/mangachan/res/mipmap-mdpi/ic_launcher.png (100%) rename {src/ru => multisrc/overrides/multichan}/mangachan/res/mipmap-xhdpi/ic_launcher.png (100%) rename {src/ru => multisrc/overrides/multichan}/mangachan/res/mipmap-xxhdpi/ic_launcher.png (100%) rename {src/ru => multisrc/overrides/multichan}/mangachan/res/mipmap-xxxhdpi/ic_launcher.png (100%) rename {src/ru => multisrc/overrides/multichan}/mangachan/res/web_hi_res_512.png (100%) mode change 100755 => 100644 rename src/ru/mangachan/src/eu/kanade/tachiyomi/extension/ru/mangachan/Mangachan.kt => multisrc/overrides/multichan/mangachan/src/MangaChan.kt (50%) rename {src/ru => multisrc/overrides/multichan}/yaoichan/res/mipmap-hdpi/ic_launcher.png (100%) rename {src/ru => multisrc/overrides/multichan}/yaoichan/res/mipmap-mdpi/ic_launcher.png (100%) rename {src/ru => multisrc/overrides/multichan}/yaoichan/res/mipmap-xhdpi/ic_launcher.png (100%) rename {src/ru => multisrc/overrides/multichan}/yaoichan/res/mipmap-xxhdpi/ic_launcher.png (100%) rename {src/ru => multisrc/overrides/multichan}/yaoichan/res/mipmap-xxxhdpi/ic_launcher.png (100%) rename {src/ru => multisrc/overrides/multichan}/yaoichan/res/web_hi_res_512.png (100%) rename src/ru/yaoichan/src/eu/kanade/tachiyomi/extension/ru/yaoichan/Yaoichan.kt => multisrc/overrides/multichan/yaoichan/src/YaoiChan.kt (56%) create mode 100644 multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/multichan/ChanGenerator.kt create mode 100644 multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/multichan/MultiChan.kt delete mode 100644 src/ru/henchan/AndroidManifest.xml delete mode 100644 src/ru/henchan/build.gradle delete mode 100644 src/ru/mangachan/AndroidManifest.xml delete mode 100644 src/ru/mangachan/build.gradle delete mode 100644 src/ru/yaoichan/AndroidManifest.xml delete mode 100644 src/ru/yaoichan/build.gradle diff --git a/src/ru/henchan/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/multichan/henchan/res/mipmap-hdpi/ic_launcher.png similarity index 100% rename from src/ru/henchan/res/mipmap-hdpi/ic_launcher.png rename to multisrc/overrides/multichan/henchan/res/mipmap-hdpi/ic_launcher.png diff --git a/src/ru/henchan/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/multichan/henchan/res/mipmap-mdpi/ic_launcher.png similarity index 100% rename from src/ru/henchan/res/mipmap-mdpi/ic_launcher.png rename to multisrc/overrides/multichan/henchan/res/mipmap-mdpi/ic_launcher.png diff --git a/src/ru/henchan/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/multichan/henchan/res/mipmap-xhdpi/ic_launcher.png similarity index 100% rename from src/ru/henchan/res/mipmap-xhdpi/ic_launcher.png rename to multisrc/overrides/multichan/henchan/res/mipmap-xhdpi/ic_launcher.png diff --git a/src/ru/henchan/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/multichan/henchan/res/mipmap-xxhdpi/ic_launcher.png similarity index 100% rename from src/ru/henchan/res/mipmap-xxhdpi/ic_launcher.png rename to multisrc/overrides/multichan/henchan/res/mipmap-xxhdpi/ic_launcher.png diff --git a/src/ru/henchan/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/multichan/henchan/res/mipmap-xxxhdpi/ic_launcher.png similarity index 100% rename from src/ru/henchan/res/mipmap-xxxhdpi/ic_launcher.png rename to multisrc/overrides/multichan/henchan/res/mipmap-xxxhdpi/ic_launcher.png diff --git a/src/ru/henchan/res/web_hi_res_512.png b/multisrc/overrides/multichan/henchan/res/web_hi_res_512.png similarity index 100% rename from src/ru/henchan/res/web_hi_res_512.png rename to multisrc/overrides/multichan/henchan/res/web_hi_res_512.png diff --git a/src/ru/henchan/src/eu/kanade/tachiyomi/extension/ru/henchan/Henchan.kt b/multisrc/overrides/multichan/henchan/src/HenChan.kt similarity index 88% rename from src/ru/henchan/src/eu/kanade/tachiyomi/extension/ru/henchan/Henchan.kt rename to multisrc/overrides/multichan/henchan/src/HenChan.kt index cf03b8786..e6f8538c9 100644 --- a/src/ru/henchan/src/eu/kanade/tachiyomi/extension/ru/henchan/Henchan.kt +++ b/multisrc/overrides/multichan/henchan/src/HenChan.kt @@ -1,47 +1,29 @@ package eu.kanade.tachiyomi.extension.ru.henchan import android.annotation.SuppressLint +import eu.kanade.tachiyomi.multisrc.multichan.MultiChan import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.asObservable -import eu.kanade.tachiyomi.network.interceptor.rateLimit import eu.kanade.tachiyomi.source.model.Filter import eu.kanade.tachiyomi.source.model.FilterList 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.ParsedHttpSource import eu.kanade.tachiyomi.util.asJsoup +import java.net.URL +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale import okhttp3.Headers -import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.Response import org.jsoup.nodes.Document import org.jsoup.nodes.Element import rx.Observable -import java.net.URL -import java.text.SimpleDateFormat -import java.util.Date -import java.util.Locale -class Henchan : ParsedHttpSource() { +class HenChan : MultiChan("HenChan", "https://y.hentaichan.live", "ru"){ - override val name = "Henchan" - - override val baseUrl = "https://xxxx.hentaichan.live" - - override val lang = "ru" - - override val supportsLatest = true - - override val client: OkHttpClient = network.client.newBuilder() - .rateLimit(2) - .build() - - override fun popularMangaRequest(page: Int): Request = - GET("$baseUrl/mostfavorites&sort=manga?offset=${20 * (page - 1)}", headers) - - override fun latestUpdatesRequest(page: Int): Request = - GET("$baseUrl/manga/new?offset=${20 * (page - 1)}", headers) + override val id: Long = 5504588601186153612 override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { @@ -88,10 +70,6 @@ class Henchan : ParsedHttpSource() { return GET(url, headers) } - override fun popularMangaSelector() = "div.content_row" - - override fun latestUpdatesSelector() = popularMangaSelector() - override fun searchMangaSelector() = ".content_row:not(:has(div.item:containsOwn(Тип)))" private fun String.getHQThumbnail(): String { @@ -103,33 +81,13 @@ class Henchan : ParsedHttpSource() { } override fun popularMangaFromElement(element: Element): SManga { - val manga = SManga.create() + val manga = super.popularMangaFromElement(element) manga.thumbnail_url = element.select("img").first().attr("src").getHQThumbnail() - manga.title = element.attr("title") - element.select("h2 > a").first().let { - manga.setUrlWithoutDomain(it.attr("href")) - } return manga } - override fun latestUpdatesFromElement(element: Element): SManga = - popularMangaFromElement(element) - - override fun searchMangaFromElement(element: Element): SManga = - popularMangaFromElement(element) - - override fun popularMangaNextPageSelector() = "#pagination > a:contains(Вперед)" - - override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector() - - override fun searchMangaNextPageSelector() = "#nextlink, ${popularMangaNextPageSelector()}" - override fun mangaDetailsParse(document: Document): SManga { - val manga = SManga.create() - manga.title = document.select("title").text().substringBefore(" »") - manga.author = document.select(".row .item2 h2")[1].text() - manga.genre = document.select(".sidetag > a:eq(2)").joinToString { it.text() } - manga.description = document.select("#description").text().trim() + val manga = super.mangaDetailsParse(document) manga.thumbnail_url = document.select("img#cover").attr("abs:src").getHQThumbnail() return manga } @@ -245,6 +203,7 @@ class Henchan : ParsedHttpSource() { } return GET(url, Headers.Builder().add("Accept", "image/webp,image/apng").build()) } + override fun pageListParse(response: Response): List { val html = response.body!!.string() val prefix = "fullimg\": [" @@ -258,12 +217,6 @@ class Henchan : ParsedHttpSource() { return pageUrls.mapIndexed { i, url -> Page(i, "", url) } } - override fun pageListParse(document: Document): List { - throw Exception("Not used") - } - - override fun imageUrlParse(document: Document) = "" - private class Genre(val id: String, @SuppressLint("DefaultLocale") name: String = id.replace('_', ' ').capitalize()) : Filter.TriState(name) private class GenreList(genres: List) : Filter.Group("Тэги", genres) private class OrderBy : UriPartFilter( diff --git a/src/ru/mangachan/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/multichan/mangachan/res/mipmap-hdpi/ic_launcher.png similarity index 100% rename from src/ru/mangachan/res/mipmap-hdpi/ic_launcher.png rename to multisrc/overrides/multichan/mangachan/res/mipmap-hdpi/ic_launcher.png diff --git a/src/ru/mangachan/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/multichan/mangachan/res/mipmap-mdpi/ic_launcher.png similarity index 100% rename from src/ru/mangachan/res/mipmap-mdpi/ic_launcher.png rename to multisrc/overrides/multichan/mangachan/res/mipmap-mdpi/ic_launcher.png diff --git a/src/ru/mangachan/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/multichan/mangachan/res/mipmap-xhdpi/ic_launcher.png similarity index 100% rename from src/ru/mangachan/res/mipmap-xhdpi/ic_launcher.png rename to multisrc/overrides/multichan/mangachan/res/mipmap-xhdpi/ic_launcher.png diff --git a/src/ru/mangachan/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/multichan/mangachan/res/mipmap-xxhdpi/ic_launcher.png similarity index 100% rename from src/ru/mangachan/res/mipmap-xxhdpi/ic_launcher.png rename to multisrc/overrides/multichan/mangachan/res/mipmap-xxhdpi/ic_launcher.png diff --git a/src/ru/mangachan/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/multichan/mangachan/res/mipmap-xxxhdpi/ic_launcher.png similarity index 100% rename from src/ru/mangachan/res/mipmap-xxxhdpi/ic_launcher.png rename to multisrc/overrides/multichan/mangachan/res/mipmap-xxxhdpi/ic_launcher.png diff --git a/src/ru/mangachan/res/web_hi_res_512.png b/multisrc/overrides/multichan/mangachan/res/web_hi_res_512.png old mode 100755 new mode 100644 similarity index 100% rename from src/ru/mangachan/res/web_hi_res_512.png rename to multisrc/overrides/multichan/mangachan/res/web_hi_res_512.png diff --git a/src/ru/mangachan/src/eu/kanade/tachiyomi/extension/ru/mangachan/Mangachan.kt b/multisrc/overrides/multichan/mangachan/src/MangaChan.kt similarity index 50% rename from src/ru/mangachan/src/eu/kanade/tachiyomi/extension/ru/mangachan/Mangachan.kt rename to multisrc/overrides/multichan/mangachan/src/MangaChan.kt index b5bd0605f..92e0ee3cc 100644 --- a/src/ru/mangachan/src/eu/kanade/tachiyomi/extension/ru/mangachan/Mangachan.kt +++ b/multisrc/overrides/multichan/mangachan/src/MangaChan.kt @@ -1,42 +1,15 @@ package eu.kanade.tachiyomi.extension.ru.mangachan +import eu.kanade.tachiyomi.multisrc.multichan.MultiChan import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.interceptor.rateLimit 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.ParsedHttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.OkHttpClient import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import java.text.SimpleDateFormat -import java.util.Locale -class Mangachan : ParsedHttpSource() { +class MangaChan : MultiChan("MangaChan", "https://manga-chan.me", "ru"){ override val id: Long = 7 - override val name = "Mangachan" - - override val baseUrl = "https://manga-chan.me" - - override val lang = "ru" - - override val supportsLatest = true - - override val client: OkHttpClient = network.client.newBuilder() - .rateLimit(2) - .build() - - override fun popularMangaRequest(page: Int): Request = - GET("$baseUrl/mostfavorites?offset=${20 * (page - 1)}", headers) - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { var pageNum = 1 when { @@ -111,126 +84,6 @@ class Mangachan : ParsedHttpSource() { return GET(url, headers) } - override fun latestUpdatesRequest(page: Int): Request = GET("$baseUrl/manga/new?offset=${20 * (page - 1)}") - - override fun popularMangaSelector() = "div.content_row" - - override fun latestUpdatesSelector() = popularMangaSelector() - - override fun searchMangaSelector() = popularMangaSelector() - - override fun popularMangaFromElement(element: Element): SManga { - val manga = SManga.create() - manga.thumbnail_url = element.select("div.manga_images img").first().attr("src") - manga.title = element.attr("title") - element.select("h2 > a").first().let { - manga.setUrlWithoutDomain(it.attr("href")) - } - return manga - } - - override fun latestUpdatesFromElement(element: Element): SManga { - val manga = SManga.create() - manga.title = element.attr("title") - element.select("a:nth-child(1)").first().let { - manga.setUrlWithoutDomain(it.attr("href")) - } - return manga - } - - override fun searchMangaFromElement(element: Element): SManga = popularMangaFromElement(element) - - override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector() - - override fun popularMangaNextPageSelector() = "a:contains(Вперед)" - - override fun searchMangaNextPageSelector() = "a:contains(Далее)" - - private fun searchGenresNextPageSelector() = popularMangaNextPageSelector() - - override fun searchMangaParse(response: Response): MangasPage { - val document = response.asJsoup() - var hasNextPage = false - - val mangas = document.select(searchMangaSelector()).map { element -> - searchMangaFromElement(element) - } - - val nextSearchPage = document.select(searchMangaNextPageSelector()) - if (nextSearchPage.isNotEmpty()) { - val query = document.select("input#searchinput").first().attr("value") - val pageNum = nextSearchPage.let { selector -> - val onClick = selector.attr("onclick") - onClick?.split("""\\d+""") - } - nextSearchPage.attr("href", "$baseUrl/?do=search&subaction=search&story=$query&search_start=$pageNum") - hasNextPage = true - } - - val nextGenresPage = document.select(searchGenresNextPageSelector()) - if (nextGenresPage.isNotEmpty()) { - hasNextPage = true - } - - return MangasPage(mangas, hasNextPage) - } - - override fun mangaDetailsParse(document: Document): SManga { - val infoElement = document.select("table.mangatitle").first() - val descElement = document.select("div#description").first() - val imgElement = document.select("img#cover").first() - val rawCategory = infoElement.select("tr:eq(1) > td:eq(1)").text() - val category = if (rawCategory.isNotEmpty()) { - rawCategory.lowercase() - } else { - "манга" - } - val manga = SManga.create() - manga.title = document.select("title").text().substringBefore(" »") - manga.author = infoElement.select("tr:eq(2) > td:eq(1)").text() - manga.genre = infoElement.select("tr:eq(5) > td:eq(1)").text().split(",").plusElement(category).joinToString { it.trim() } - manga.status = parseStatus(infoElement.select("tr:eq(4) > td:eq(1)").text()) - manga.description = descElement.textNodes().first().text().trim() - manga.thumbnail_url = imgElement.attr("src") - return manga - } - - private fun parseStatus(element: String): Int = when { - element.contains("перевод завершен") -> SManga.COMPLETED - element.contains("перевод продолжается") -> SManga.ONGOING - else -> SManga.UNKNOWN - } - - override fun chapterListSelector() = "table.table_cha tr:gt(1)" - - override fun chapterFromElement(element: Element): SChapter { - val urlElement = element.select("a").first() - - val chapter = SChapter.create() - chapter.setUrlWithoutDomain(urlElement.attr("href")) - chapter.name = urlElement.text() - chapter.date_upload = element.select("div.date").first()?.text()?.let { - SimpleDateFormat("yyyy-MM-dd", Locale.US).parse(it)?.time ?: 0L - } ?: 0 - return chapter - } - - override fun pageListParse(response: Response): List { - val html = response.body!!.string() - val beginIndex = html.indexOf("fullimg\":[") + 10 - val endIndex = html.indexOf(",]", beginIndex) - val trimmedHtml = html.substring(beginIndex, endIndex).replace("\"", "") - val pageUrls = trimmedHtml.split(',') - - return pageUrls.mapIndexed { i, url -> Page(i, "", url) } - } - - override fun pageListParse(document: Document): List { - throw Exception("Not used") - } - - override fun imageUrlParse(document: Document) = "" - private class GenreList(genres: List) : Filter.Group("Тэги", genres) private class Genre(name: String, val id: String = name.replace(' ', '_')) : Filter.TriState(name) private class Status : Filter.Select("Статус", arrayOf("Все", "Перевод завершен", "Выпуск завершен", "Онгоинг", "Новые главы")) @@ -246,10 +99,6 @@ class Mangachan : ParsedHttpSource() { GenreList(getGenreList()) ) - /* [...document.querySelectorAll("li.sidetag > a:nth-child(1)")] - * .map(el => `Genre("${el.getAttribute('href').substr(6)}")`).join(',\n') - * on https://mangachan.me/ - */ private fun getGenreList() = listOf( Genre("18_плюс"), Genre("bdsm"), diff --git a/src/ru/yaoichan/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/multichan/yaoichan/res/mipmap-hdpi/ic_launcher.png similarity index 100% rename from src/ru/yaoichan/res/mipmap-hdpi/ic_launcher.png rename to multisrc/overrides/multichan/yaoichan/res/mipmap-hdpi/ic_launcher.png diff --git a/src/ru/yaoichan/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/multichan/yaoichan/res/mipmap-mdpi/ic_launcher.png similarity index 100% rename from src/ru/yaoichan/res/mipmap-mdpi/ic_launcher.png rename to multisrc/overrides/multichan/yaoichan/res/mipmap-mdpi/ic_launcher.png diff --git a/src/ru/yaoichan/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/multichan/yaoichan/res/mipmap-xhdpi/ic_launcher.png similarity index 100% rename from src/ru/yaoichan/res/mipmap-xhdpi/ic_launcher.png rename to multisrc/overrides/multichan/yaoichan/res/mipmap-xhdpi/ic_launcher.png diff --git a/src/ru/yaoichan/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/multichan/yaoichan/res/mipmap-xxhdpi/ic_launcher.png similarity index 100% rename from src/ru/yaoichan/res/mipmap-xxhdpi/ic_launcher.png rename to multisrc/overrides/multichan/yaoichan/res/mipmap-xxhdpi/ic_launcher.png diff --git a/src/ru/yaoichan/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/multichan/yaoichan/res/mipmap-xxxhdpi/ic_launcher.png similarity index 100% rename from src/ru/yaoichan/res/mipmap-xxxhdpi/ic_launcher.png rename to multisrc/overrides/multichan/yaoichan/res/mipmap-xxxhdpi/ic_launcher.png diff --git a/src/ru/yaoichan/res/web_hi_res_512.png b/multisrc/overrides/multichan/yaoichan/res/web_hi_res_512.png similarity index 100% rename from src/ru/yaoichan/res/web_hi_res_512.png rename to multisrc/overrides/multichan/yaoichan/res/web_hi_res_512.png diff --git a/src/ru/yaoichan/src/eu/kanade/tachiyomi/extension/ru/yaoichan/Yaoichan.kt b/multisrc/overrides/multichan/yaoichan/src/YaoiChan.kt similarity index 56% rename from src/ru/yaoichan/src/eu/kanade/tachiyomi/extension/ru/yaoichan/Yaoichan.kt rename to multisrc/overrides/multichan/yaoichan/src/YaoiChan.kt index 0cf442d6b..7daa032a2 100644 --- a/src/ru/yaoichan/src/eu/kanade/tachiyomi/extension/ru/yaoichan/Yaoichan.kt +++ b/multisrc/overrides/multichan/yaoichan/src/YaoiChan.kt @@ -1,37 +1,14 @@ package eu.kanade.tachiyomi.extension.ru.yaoichan +import eu.kanade.tachiyomi.multisrc.multichan.MultiChan import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.interceptor.rateLimit import eu.kanade.tachiyomi.source.model.Filter import eu.kanade.tachiyomi.source.model.FilterList -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.ParsedHttpSource -import okhttp3.OkHttpClient import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import java.text.SimpleDateFormat -import java.util.Locale -class Yaoichan : ParsedHttpSource() { +class YaoiChan : MultiChan("YaoiChan", "https://yaoi-chan.me", "ru"){ - override val name = "Yaoichan" - - override val baseUrl = "https://yaoi-chan.me" - - override val lang = "ru" - - override val supportsLatest = false - - override val client: OkHttpClient = network.client.newBuilder() - .rateLimit(2) - .build() - - override fun popularMangaRequest(page: Int): Request = - GET("$baseUrl/mostfavorites?offset=${20 * (page - 1)}", headers) + override val id: Long = 2466512768990363955 override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { val url = if (query.isNotEmpty()) { @@ -99,87 +76,6 @@ class Yaoichan : ParsedHttpSource() { return GET(url, headers) } - override fun popularMangaSelector() = "div.content_row" - - override fun searchMangaSelector() = popularMangaSelector() - - override fun popularMangaFromElement(element: Element): SManga { - val manga = SManga.create() - manga.thumbnail_url = element.select("div.manga_images img").first().attr("src") - manga.title = element.attr("title") - element.select("h2 > a").first().let { - manga.setUrlWithoutDomain(it.attr("href")) - } - return manga - } - - override fun latestUpdatesRequest(page: Int): Request = throw UnsupportedOperationException("Not used") - override fun latestUpdatesSelector() = throw UnsupportedOperationException("Not used") - override fun latestUpdatesFromElement(element: Element) = throw UnsupportedOperationException("Not used") - override fun latestUpdatesNextPageSelector() = throw UnsupportedOperationException("Not used") - - override fun searchMangaFromElement(element: Element): SManga = popularMangaFromElement(element) - - override fun popularMangaNextPageSelector() = "a:contains(Вперед)" - - override fun searchMangaNextPageSelector() = "a:contains(Далее), ${popularMangaNextPageSelector()}" - - override fun mangaDetailsParse(document: Document): SManga { - val infoElement = document.select("table.mangatitle").first() - val descElement = document.select("div#description").first() - val imgElement = document.select("img#cover").first() - val rawCategory = infoElement.select("tr:eq(1) > td:eq(1)").text() - val category = if (rawCategory.isNotEmpty()) { - rawCategory.lowercase() - } else { - "манга" - } - val manga = SManga.create() - manga.title = document.select("title").text().substringBefore(" »") - manga.author = infoElement.select("tr:eq(2) > td:eq(1)").text() - manga.genre = infoElement.select("tr:eq(5) > td:eq(1)").text().split(",").plusElement(category).joinToString { it.trim() } - manga.status = parseStatus(infoElement.select("tr:eq(4) > td:eq(1)").text()) - manga.description = descElement.textNodes().first().text().trim() - manga.thumbnail_url = imgElement.attr("src") - return manga - } - - private fun parseStatus(element: String): Int = when { - element.contains("перевод завершен") -> SManga.COMPLETED - element.contains("перевод продолжается") -> SManga.ONGOING - else -> SManga.UNKNOWN - } - - override fun chapterListSelector() = "table.table_cha tr:gt(1)" - - override fun chapterFromElement(element: Element): SChapter { - val urlElement = element.select("a").first() - - val chapter = SChapter.create() - chapter.setUrlWithoutDomain(urlElement.attr("href")) - chapter.name = urlElement.text() - chapter.date_upload = element.select("div.date").first()?.text()?.let { - SimpleDateFormat("yyyy-MM-dd", Locale.US).parse(it)?.time ?: 0L - } ?: 0 - return chapter - } - - override fun pageListParse(response: Response): List { - val html = response.body!!.string() - val beginIndex = html.indexOf("fullimg\":[") + 10 - val endIndex = html.indexOf(",]", beginIndex) - val trimmedHtml = html.substring(beginIndex, endIndex).replace("\"", "") - val pageUrls = trimmedHtml.split(',') - - return pageUrls.mapIndexed { i, url -> Page(i, "", url) } - } - - override fun pageListParse(document: Document): List { - throw Exception("Not used") - } - - override fun imageUrlParse(document: Document) = "" - private class GenreList(genres: List) : Filter.Group("Тэги", genres) private class Genre(name: String, val id: String = name.replace(' ', '_')) : Filter.TriState(name) private class Status : Filter.Select("Статус", arrayOf("Все", "Перевод завершен", "Выпуск завершен", "Онгоинг", "Новые главы")) @@ -195,10 +91,6 @@ class Yaoichan : ParsedHttpSource() { GenreList(getGenreList()) ) - /* [...document.querySelectorAll("li.sidetag > a:nth-child(1)")] - * .map(el => `Genre("${el.getAttribute('href').substr(6)}")`).join(',\n') - * on https://yaoi-chan.me/catalog - */ private fun getGenreList() = listOf( Genre("18 плюс"), Genre("bdsm"), diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/multichan/ChanGenerator.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/multichan/ChanGenerator.kt new file mode 100644 index 000000000..017e78376 --- /dev/null +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/multichan/ChanGenerator.kt @@ -0,0 +1,26 @@ +package eu.kanade.tachiyomi.multisrc.multichan + +import generator.ThemeSourceData.SingleLang +import generator.ThemeSourceGenerator + +class ChanGenerator: ThemeSourceGenerator { + + override val themePkg = "multichan" + + override val themeClass = "MultiChan" + + override val baseVersionCode: Int = 1 + + override val sources = listOf( + SingleLang("MangaChan", "https://manga-chan.me", "ru", overrideVersionCode = 14), + SingleLang("HenChan", "https://y.hentaichan.live", "ru",isNsfw = true, overrideVersionCode = 35), + SingleLang("YaoiChan", "https://yaoi-chan.me", "ru",isNsfw = true, overrideVersionCode = 4) + ) + + companion object { + @JvmStatic + fun main(args: Array) { + ChanGenerator().createAll() + } + } +} diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/multichan/MultiChan.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/multichan/MultiChan.kt new file mode 100644 index 000000000..42192adad --- /dev/null +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/multichan/MultiChan.kt @@ -0,0 +1,145 @@ +package eu.kanade.tachiyomi.multisrc.multichan + +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.network.interceptor.rateLimit +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.ParsedHttpSource +import eu.kanade.tachiyomi.util.asJsoup +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.Response +import org.jsoup.nodes.Document +import org.jsoup.nodes.Element +import java.text.SimpleDateFormat +import java.util.Locale + +abstract class MultiChan( + override val name: String, + override val baseUrl: String, + final override val lang: String +) : ParsedHttpSource() { + + override val supportsLatest = true + + override val client: OkHttpClient = network.client.newBuilder() + .rateLimit(2) + .build() + + override fun popularMangaRequest(page: Int): Request = + GET("$baseUrl/mostfavorites?offset=${20 * (page - 1)}", headers) + + override fun latestUpdatesRequest(page: Int): Request = GET("$baseUrl/manga/new?offset=${20 * (page - 1)}") + + override fun popularMangaSelector() = "div.content_row" + + override fun latestUpdatesSelector() = popularMangaSelector() + + override fun searchMangaSelector() = popularMangaSelector() + + override fun popularMangaFromElement(element: Element): SManga { + val manga = SManga.create() + manga.thumbnail_url = element.select("img").first().attr("src") + manga.title = element.attr("title") + element.select("h2 > a").first().let { + manga.setUrlWithoutDomain(it.attr("href")) + } + return manga + } + + override fun latestUpdatesFromElement(element: Element): SManga = + popularMangaFromElement(element) + + override fun searchMangaFromElement(element: Element): SManga = popularMangaFromElement(element) + + override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector() + + override fun popularMangaNextPageSelector() = "a:contains(Вперед)" + + override fun searchMangaNextPageSelector() = "a:contains(Далее)" + + private fun searchGenresNextPageSelector() = popularMangaNextPageSelector() + + override fun searchMangaParse(response: Response): MangasPage { + val document = response.asJsoup() + var hasNextPage = false + + val mangas = document.select(searchMangaSelector()).map { element -> + searchMangaFromElement(element) + } + + val nextSearchPage = document.select(searchMangaNextPageSelector()) + if (nextSearchPage.isNotEmpty()) { + val query = document.select("input#searchinput").first().attr("value") + val pageNum = nextSearchPage.let { selector -> + val onClick = selector.attr("onclick") + onClick?.split("""\\d+""") + } + nextSearchPage.attr("href", "$baseUrl/?do=search&subaction=search&story=$query&search_start=$pageNum") + hasNextPage = true + } + + val nextGenresPage = document.select(searchGenresNextPageSelector()) + if (nextGenresPage.isNotEmpty()) { + hasNextPage = true + } + + return MangasPage(mangas, hasNextPage) + } + + override fun mangaDetailsParse(document: Document): SManga { + val infoElement = document.select("#info_wrap tr,#info_wrap > div") + val descElement = document.select("div#description").first() + val rawCategory = infoElement.select(":contains(Тип) a").text().lowercase() + val manga = SManga.create() + manga.title = document.select("title").text().substringBefore(" »") + manga.author = infoElement.select(":contains(Автор)").text() + manga.genre = rawCategory + ", " + document.select(".sidetags ul a:last-child").joinToString { it.text() } + manga.status = parseStatus(infoElement.select(":contains(Загружено)").text()) + manga.description = descElement.textNodes().first().text().trim() + manga.thumbnail_url = document.select("img#cover").first().attr("src") + return manga + } + + private fun parseStatus(element: String): Int = when { + element.contains("перевод завершен") -> SManga.COMPLETED + element.contains("перевод продолжается") -> SManga.ONGOING + else -> SManga.UNKNOWN + } + + override fun chapterListSelector() = "table.table_cha tr:gt(1)" + + override fun chapterFromElement(element: Element): SChapter { + val urlElement = element.select("a").first() + + val chapter = SChapter.create() + chapter.setUrlWithoutDomain(urlElement.attr("href")) + chapter.name = urlElement.text() + chapter.date_upload = simpleDateFormat.parse(element.select("div.date").first().text())?.time ?: 0L + return chapter + } + + override fun pageListParse(response: Response): List { + val html = response.body!!.string() + val beginIndex = html.indexOf("fullimg\":[") + 10 + val endIndex = html.indexOf(",]", beginIndex) + val trimmedHtml = html.substring(beginIndex, endIndex).replace("\"", "") + val pageUrls = trimmedHtml.split(',') + + return pageUrls.mapIndexed { i, url -> Page(i, "", url) } + } + + override fun pageListParse(document: Document): List { + throw Exception("Not used") + } + + override fun imageUrlParse(document: Document) = "" + + companion object { + private val simpleDateFormat by lazy { SimpleDateFormat("yyyy-MM-dd", Locale.US) } + } +} diff --git a/src/ru/henchan/AndroidManifest.xml b/src/ru/henchan/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/ru/henchan/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/src/ru/henchan/build.gradle b/src/ru/henchan/build.gradle deleted file mode 100644 index 135c07db2..000000000 --- a/src/ru/henchan/build.gradle +++ /dev/null @@ -1,13 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' -apply plugin: 'kotlinx-serialization' - -ext { - extName = 'Henchan' - pkgNameSuffix = 'ru.henchan' - extClass = '.Henchan' - extVersionCode = 35 - isNsfw = true -} - -apply from: "$rootDir/common.gradle" diff --git a/src/ru/mangachan/AndroidManifest.xml b/src/ru/mangachan/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/ru/mangachan/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/src/ru/mangachan/build.gradle b/src/ru/mangachan/build.gradle deleted file mode 100644 index 633254258..000000000 --- a/src/ru/mangachan/build.gradle +++ /dev/null @@ -1,11 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Mangachan' - pkgNameSuffix = 'ru.mangachan' - extClass = '.Mangachan' - extVersionCode = 14 -} - -apply from: "$rootDir/common.gradle" diff --git a/src/ru/yaoichan/AndroidManifest.xml b/src/ru/yaoichan/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/ru/yaoichan/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/src/ru/yaoichan/build.gradle b/src/ru/yaoichan/build.gradle deleted file mode 100644 index 4414100be..000000000 --- a/src/ru/yaoichan/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Yaoichan' - pkgNameSuffix = 'ru.yaoichan' - extClass = '.Yaoichan' - extVersionCode = 4 - isNsfw = true -} - -apply from: "$rootDir/common.gradle"