diff --git a/multisrc/overrides/wpmangastream/asurascans/src/AsuraScans.kt b/multisrc/overrides/wpmangastream/asurascans/src/AsuraScans.kt deleted file mode 100644 index 87751a2d8..000000000 --- a/multisrc/overrides/wpmangastream/asurascans/src/AsuraScans.kt +++ /dev/null @@ -1,28 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.asurascans - -import eu.kanade.tachiyomi.lib.ratelimit.RateLimitInterceptor -import eu.kanade.tachiyomi.multisrc.wpmangastream.WPMangaStream -import eu.kanade.tachiyomi.source.model.Page -import okhttp3.OkHttpClient -import org.jsoup.nodes.Document -import java.util.concurrent.TimeUnit - -class AsuraScans : WPMangaStream("AsuraScans", "https://www.asurascans.com", "en") { - - private val rateLimitInterceptor = RateLimitInterceptor(1, 3, TimeUnit.SECONDS) - - override val client: OkHttpClient = network.cloudflareClient.newBuilder() - .connectTimeout(10, TimeUnit.SECONDS) - .readTimeout(30, TimeUnit.SECONDS) - .addNetworkInterceptor(rateLimitInterceptor) - .build() - - override val pageSelector = "div.rdminimal img[loading*=lazy]" - - // Skip scriptPages - override fun pageListParse(document: Document): List { - return document.select(pageSelector) - .filterNot { it.attr("abs:src").isNullOrEmpty() } - .mapIndexed { i, img -> Page(i, "", img.attr("abs:src")) } - } -} diff --git a/multisrc/overrides/wpmangastream/asurascans/src/AsuraScansFactory.kt b/multisrc/overrides/wpmangastream/asurascans/src/AsuraScansFactory.kt new file mode 100644 index 000000000..44535689e --- /dev/null +++ b/multisrc/overrides/wpmangastream/asurascans/src/AsuraScansFactory.kt @@ -0,0 +1,98 @@ +package eu.kanade.tachiyomi.extension.all.asurascans + +import eu.kanade.tachiyomi.lib.ratelimit.RateLimitInterceptor +import eu.kanade.tachiyomi.multisrc.wpmangastream.WPMangaStream +import eu.kanade.tachiyomi.source.SourceFactory +import eu.kanade.tachiyomi.source.model.Page +import eu.kanade.tachiyomi.source.model.SManga +import okhttp3.OkHttpClient +import org.jsoup.nodes.Document +import java.text.SimpleDateFormat +import java.util.Locale +import java.util.concurrent.TimeUnit + +class AsuraScansFactory : SourceFactory { + override fun createSources() = listOf( + AsuraScansEn(), + AsuraScansTr() + ) +} + +abstract class AsuraScans( + override val baseUrl: String, + override val lang: String, + dateFormat: SimpleDateFormat +) : WPMangaStream("Asura Scans", baseUrl, lang, dateFormat) { + private val rateLimitInterceptor = RateLimitInterceptor(1, 3, TimeUnit.SECONDS) + + override val client: OkHttpClient = network.cloudflareClient.newBuilder() + .connectTimeout(10, TimeUnit.SECONDS) + .readTimeout(30, TimeUnit.SECONDS) + .addNetworkInterceptor(rateLimitInterceptor) + .build() +} + +class AsuraScansEn : AsuraScans("https://asurascans.com/", "en", SimpleDateFormat("MMM d, yyyy", Locale.US)) { + override val pageSelector = "div.rdminimal img[loading*=lazy]" + + // Skip scriptPages + override fun pageListParse(document: Document): List { + return document.select(pageSelector) + .filterNot { it.attr("abs:src").isNullOrEmpty() } + .mapIndexed { i, img -> Page(i, "", img.attr("abs:src")) } + } +} + +class AsuraScansTr : AsuraScans("https://tr.asurascans.com/", "tr", SimpleDateFormat("MMM d, yyyy", Locale("tr"))) { + override fun mangaDetailsParse(document: Document): SManga { + return SManga.create().apply { + document.select("div.bigcontent").firstOrNull()?.let { infoElement -> + status = parseStatus(infoElement.select(".imptdt:contains(Durum) i").firstOrNull()?.ownText()) + infoElement.select(".fmed b:contains(Yazar)+span").firstOrNull()?.ownText().let { + if (it.isNullOrBlank().not() && it != "N/A" && it != "-") { + author = it + } + } + infoElement.select(".fmed b:contains(Çizer)+span").firstOrNull()?.ownText().let { + if (it.isNullOrBlank().not() && it != "N/A" && it != "-") { + artist = it + } + } + description = infoElement.select("div.desc p, div.entry-content p").joinToString("\n") { it.text() } + thumbnail_url = infoElement.select("div.thumb img").imgAttr() + + val genres = infoElement.select(".mgen a") + .map { element -> element.text().toLowerCase(Locale.ROOT) } + .toMutableSet() + + // add series type(manga/manhwa/manhua/other) thinggy to genre + document.select(seriesTypeSelector).firstOrNull()?.ownText()?.let { + if (it.isEmpty().not() && genres.contains(it).not()) { + genres.add(it.toLowerCase(Locale.ROOT)) + } + } + + genre = genres.toList().map { it.capitalize(Locale.ROOT) }.joinToString(", ") + + // add alternative name to manga description + document.select(altNameSelector).firstOrNull()?.ownText()?.let { + if (it.isBlank().not() && it != "N/A" && it != "-") { + description = when { + description.isNullOrBlank() -> altName + it + else -> description + "\n\n$altName" + it + } + } + } + } + } + } + override val seriesTypeSelector = ".imptdt:contains(Tür) a" + override val altName: String = "Alternatif isim: " + + override fun parseStatus(element: String?): Int = when { + element == null -> SManga.UNKNOWN + listOf("Devam Ediyor").any { it.contains(element, ignoreCase = true) } -> SManga.ONGOING + listOf("Tamamlandı").any { it.contains(element, ignoreCase = true) } -> SManga.COMPLETED + else -> SManga.UNKNOWN + } +} diff --git a/multisrc/overrides/wpmangastream/mihentai/src/Mihentai.kt b/multisrc/overrides/wpmangastream/mihentai/src/Mihentai.kt index 90f18c50a..0b2a4c4c4 100644 --- a/multisrc/overrides/wpmangastream/mihentai/src/Mihentai.kt +++ b/multisrc/overrides/wpmangastream/mihentai/src/Mihentai.kt @@ -2,6 +2,180 @@ package eu.kanade.tachiyomi.extension.en.mihentai import eu.kanade.tachiyomi.annotations.Nsfw import eu.kanade.tachiyomi.multisrc.wpmangastream.WPMangaStream +import eu.kanade.tachiyomi.network.GET +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.SManga +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.jsonArray +import kotlinx.serialization.json.jsonPrimitive +import okhttp3.HttpUrl.Companion.toHttpUrl +import okhttp3.HttpUrl.Companion.toHttpUrlOrNull +import okhttp3.Request +import org.jsoup.nodes.Document +import uy.kohesive.injekt.injectLazy +import java.util.Locale @Nsfw -class Mihentai : WPMangaStream("Mihentai", "https://mihentai.com", "en") +class Mihentai : WPMangaStream("Mihentai", "https://mihentai.com", "en") { + override fun popularMangaRequest(page: Int): Request { + return GET("$baseUrl/manga/page/$page/?order=popular", headers) + } + + override fun latestUpdatesRequest(page: Int): Request { + return GET("$baseUrl/manga/page/$page/?order=update", headers) + } + + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { + val url = "$baseUrl/manga/page/$page/".toHttpUrlOrNull()!!.newBuilder() + url.addQueryParameter("title", query) + filters.forEach { filter -> + when (filter) { + is StatusFilter -> url.addQueryParameter("status", filter.toUriPart()) + is TypeFilter -> url.addQueryParameter("type", filter.toUriPart()) + is SortByFilter -> url.addQueryParameter("order", filter.toUriPart()) + is GenreListFilter -> { + filter.state + .filter { it.state != Filter.TriState.STATE_IGNORE } + .forEach { url.addQueryParameter("genre[]", it.id) } + } + } + } + return GET(url.build().toString(), headers) + } + + override fun mangaDetailsParse(document: Document): SManga { + return SManga.create().apply { + document.select("div.bigcontent, div.animefull, div.main-info").firstOrNull()?.let { infoElement -> + status = parseStatus(infoElement.select("span:contains(Status:), .imptdt:contains(Status) i").firstOrNull()?.ownText()) + thumbnail_url = infoElement.select("div.thumb img").imgAttr() + + val genres = infoElement.select("span:contains(Tag) a") + .map { element -> element.text().toLowerCase(Locale.ROOT) } + .toMutableSet() + + // add series type(manga/manhwa/manhua/other) thinggy to genre + document.select("span:contains(Type)").firstOrNull()?.ownText()?.let { + if (it.isEmpty().not() && genres.contains(it).not()) { + genres.add(it.toLowerCase(Locale.ROOT)) + } + } + + genre = genres.toList().joinToString(", ") { it.capitalize(Locale.ROOT) } + } + } + } + + override fun parseStatus(element: String?): Int = when { + element == null -> SManga.UNKNOWN + listOf("ongoing", "publishing").any { it.contains(element, ignoreCase = true) } -> SManga.ONGOING + listOf("finished").any { it.contains(element, ignoreCase = true) } -> SManga.COMPLETED + else -> SManga.UNKNOWN + } + + private val json: Json by injectLazy() + + override fun pageListParse(document: Document): List { + val htmlPages = document.select(pageSelector) + .filterNot { it.attr("abs:src").isNullOrEmpty() } + .mapIndexed { i, img -> + val pageUrl = img.attr("abs:src").substringAfter(baseUrl).prependIndent(baseUrl) + Page(i, "", pageUrl) + } + .toMutableList() + + val docString = document.toString() + val imageListRegex = Regex("\\\"images.*?:.*?(\\[.*?\\])") + val imageListJson = imageListRegex.find(docString)?.destructured?.toList()?.get(0) + if (imageListJson != null) { + val imageList = json.parseToJsonElement(imageListJson).jsonArray + val baseResolver = baseUrl.toHttpUrl() + + val scriptPages = imageList.mapIndexed { i, jsonEl -> + val imageUrl = jsonEl.jsonPrimitive.content + Page(i, "", baseResolver.resolve(imageUrl).toString()) + } + + if (htmlPages.size < scriptPages.size) { + htmlPages += scriptPages + } + } + + countViews(document) + + return htmlPages.distinctBy { it.imageUrl } + } + + private class StatusFilter : UriPartFilter( + "Status", + arrayOf( + Pair("All", ""), + Pair("Publishing", "publishing"), + Pair("Finished", "finished"), + Pair("Dropped", "drop") + ) + ) + + private class TypeFilter : UriPartFilter( + "Type", + arrayOf( + Pair("Default", ""), + Pair("Manga", "Manga"), + Pair("Manhwa", "Manhwa"), + Pair("Manhua", "Manhua"), + Pair("Webtoon", "webtoon"), + Pair("One-Shot", "One-Shot"), + Pair("Doujin", "doujin") + ) + ) + + override fun getFilterList(): FilterList = FilterList( + listOf( + StatusFilter(), + TypeFilter(), + SortByFilter(), + GenreListFilter(getGenreList()) + ) + ) + + override fun getGenreList(): List = listOf( + Genre("Adventure", "adventure"), + Genre("Ahego", "ahego"), + Genre("Anal", "anal"), + Genre("Battle", "battle"), + Genre("Big Breasts", "big-breasts"), + Genre("Blowjob", "blowjob"), + Genre("Comic 3D", "comic-3d"), + Genre("Doujin", "doujin"), + Genre("Dragon ball", "dragon-ball"), + Genre("Fingering", "fingering"), + Genre("Full color", "full-color"), + Genre("Futanari", "futanari"), + Genre("Girlfriend", "girlfriend"), + Genre("Grouped", "grouped"), + Genre("Handjob", "handjob"), + Genre("Hijab", "hijab"), + Genre("Incest", "incest"), + Genre("Kissing", "kissing"), + Genre("Mama", "mama"), + Genre("Manga", "manga"), + Genre("Masturbation", "masturbation"), + Genre("Milf", "milf"), + Genre("Mom & Son", "mom-son"), + Genre("Naruto", "naruto"), + Genre("One Piece", "one-piece"), + Genre("Pregnancy", "pregnancy"), + Genre("Rape", "rape"), + Genre("Romance", "romance"), + Genre("School", "school"), + Genre("Scooby-Doo", "scooby-doo"), + Genre("Sister", "sister"), + Genre("Stocking", "stocking"), + Genre("Sub Indo", "sub-indo"), + Genre("Threesome", "threesome"), + Genre("Uncensored", "uncensored"), + Genre("Western", "western"), + Genre("Yuri", "yuri") + ) +} diff --git a/multisrc/overrides/wpmangastream/phoenixfansub/src/PhoenixFansub.kt b/multisrc/overrides/wpmangastream/phoenixfansub/src/PhoenixFansub.kt new file mode 100644 index 000000000..f6644087d --- /dev/null +++ b/multisrc/overrides/wpmangastream/phoenixfansub/src/PhoenixFansub.kt @@ -0,0 +1,23 @@ +package eu.kanade.tachiyomi.extension.es.phoenixfansub + +import eu.kanade.tachiyomi.multisrc.wpmangastream.WPMangaStream +import eu.kanade.tachiyomi.source.model.SManga +import org.jsoup.nodes.Element +import java.text.SimpleDateFormat +import java.util.Locale + +class PhoenixFansub : WPMangaStream( + "Phoenix Fansub", + "https://phoenixfansub.com", + "es", + SimpleDateFormat("MMM d, yyyy", Locale("es")) +) { + override fun popularMangaFromElement(element: Element): SManga = SManga.create().apply { + thumbnail_url = element.select("div.limit img").imgAttr() + element.select("div.bsx > a").first().let { + url = "/manga/${it.attr("href")}" + title = it.attr("title") + } + } + override val altName: String = "Nombre alternativo: " +} diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/wpmangastream/WPMangaStreamGenerator.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/wpmangastream/WPMangaStreamGenerator.kt index 6b261b63d..646ef6579 100644 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/wpmangastream/WPMangaStreamGenerator.kt +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/wpmangastream/WPMangaStreamGenerator.kt @@ -1,5 +1,6 @@ package eu.kanade.tachiyomi.multisrc.wpmangastream +import generator.ThemeSourceData.MultiLang import generator.ThemeSourceData.SingleLang import generator.ThemeSourceGenerator @@ -12,7 +13,7 @@ class WPMangaStreamGenerator : ThemeSourceGenerator { override val baseVersionCode: Int = 11 override val sources = listOf( - SingleLang("Asura Scans", "https://www.asurascans.com", "en", overrideVersionCode = 5), + MultiLang("Asura Scans", "https://www.asurascans.com", listOf("en", "tr"), className = "AsuraScansFactory", pkgName = "asurascans", overrideVersionCode = 6), SingleLang("KlanKomik", "https://klankomik.com", "id", overrideVersionCode = 1), SingleLang("MasterKomik", "https://masterkomik.com", "id", overrideVersionCode = 1), SingleLang("Kaisar Komik", "https://kaisarkomik.com", "id", overrideVersionCode = 1), @@ -35,7 +36,7 @@ class WPMangaStreamGenerator : ThemeSourceGenerator { SingleLang("MangaSwat", "https://mangaswat.com", "ar", overrideVersionCode = 3), SingleLang("Manga Raw.org", "https://mangaraw.org", "ja", className = "MangaRawOrg", overrideVersionCode = 1), SingleLang("Manga Pro Z", "https://mangaproz.com", "ar"), - SingleLang("Mihentai", "https://mihentai.com", "en", isNsfw = true), + SingleLang("Mihentai", "https://mihentai.com", "en", isNsfw = true, overrideVersionCode = 1), SingleLang("Kuma Scans (Kuma Translation)", "https://kumascans.com", "en", className = "KumaScans", overrideVersionCode = 1), SingleLang("Tempest Manga", "https://manga.tempestfansub.com", "tr"), SingleLang("xCaliBR Scans", "https://xcalibrscans.com", "en", overrideVersionCode = 2), @@ -44,7 +45,7 @@ class WPMangaStreamGenerator : ThemeSourceGenerator { SingleLang("The Apollo Team", "https://theapollo.team", "en"), SingleLang("Sekte Doujin", "https://sektedoujin.xyz", "id", isNsfw = true, overrideVersionCode = 2), SingleLang("Lemon Juice Scan", "https://lemonjuicescan.com", "pt-BR", isNsfw = true, overrideVersionCode = 1), - SingleLang("Phoenix Fansub", "https://phoenixfansub.com", "es"), + SingleLang("Phoenix Fansub", "https://phoenixfansub.com", "es", overrideVersionCode = 1), SingleLang("Geass Scanlator", "https://geassscan.xyz", "pt-BR", overrideVersionCode = 2), SingleLang("Imagine Scan", "https://imaginescan.com.br", "pt-BR", isNsfw = true), SingleLang("Vapo Scan", "https://vaposcans.com", "pt-BR", overrideVersionCode = 2),