X&LJZu$W
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 eABN|{TVE!)+oGcmi=;-*IKsH%F>;&NZ=n+9
zdIkpWBjqtOACCLjd?_>GXQ6lul}d%EOGk6nmg%WluYFR>&9CF!z*1_gGJb0t7*oFafGSMc
z(MJb5MfIpGwL9>{$-?S~KJCw~unS1lEP+|v%kCTuZ+z@eO~ZwzN$6pp#~A{x?eiSf
z1zcdr8O#KmyCz>)&^@}RM%sPat+pvl0fGxj915>|$N3m{fm+;ouQMAT&yDgs?o-M{_^y`wBd3`dh?S>
z4djcCl4>9jZ|lzMP#JIGib(CV9-qege&
zHRca~1;>i*3u=@kC(!0O)+~9R=5wvcB+gDKc_NlA6XfZvI|naJjgEg4gI+`|Ni})y
znPQ%m8~J8El>BX`qp2TdK@@0SDwZeD*_HZwliUs{Cq`wWew58`)6qk_n{Kf5D?5%#
zV`%%+dVEz;et+M`Hms<=Dl2|cz*u(6QaAMi(rT=dV9_41K#dQ)Kyh6SOTN4v
z#hL-i2T;?KGg5Ai_c%((FHgc)p#h-dquKp=7
zxy)U>(xZ~6*!nV4f))R+MN3u2m*M+k)~=2%*h5wWGilHNe4ycC*U|$L=e=#dm`7eu
zs_$3f+wdIw)JK{_^v5N2Z-o>_AM#`Pid8f@zpxn`C=R7ESBkgZ#j-{m)c{-K@Ud^*
zkiAe*Aqr4PO7V_qx&0dl%(I56b<@|C)7`5g>ebW~w#*x6G-Ru%yD3*BiLm5+K4O#r
vx{8tJ3|UB$eVU!bU}!)acErPup$*zvr+*TAKe^cd^Z7DdyqzC9g{J)%mDxLn
diff --git a/multisrc/overrides/wpmangastream/default/res/web_hi_res_512.png b/multisrc/overrides/wpmangastream/default/res/web_hi_res_512.png
deleted file mode 100644
index 047e5438b41809658596516c66f734617640256c..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 10941
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
diff --git a/multisrc/overrides/wpmangastream/infernalvoidscans/src/InfernalVoidScans.kt b/multisrc/overrides/wpmangastream/infernalvoidscans/src/InfernalVoidScans.kt
deleted file mode 100644
index d59bf1937..000000000
--- a/multisrc/overrides/wpmangastream/infernalvoidscans/src/InfernalVoidScans.kt
+++ /dev/null
@@ -1,20 +0,0 @@
-package eu.kanade.tachiyomi.extension.en.infernalvoidscans
-
-import eu.kanade.tachiyomi.multisrc.wpmangastream.WPMangaStream
-import eu.kanade.tachiyomi.source.model.Page
-import org.jsoup.nodes.Document
-
-class InfernalVoidScans : WPMangaStream("Infernal Void Scans", "https://void-scans.com", "en") {
- // Site dynamically replaces a placeholder image in the "src" tag with the actual url in "data-src"
- override fun pageListParse(document: Document): List {
- return super.pageListParse(
- document.apply {
- select(pageSelector).forEach { pageElem ->
- pageElem.attr("data-src")
- .takeIf { ! it.isNullOrBlank() }
- ?.let { pageElem.attr("src", it) }
- }
- }
- )
- }
-}
diff --git a/multisrc/overrides/wpmangastream/komikcast/src/KomikCast.kt b/multisrc/overrides/wpmangastream/komikcast/src/KomikCast.kt
deleted file mode 100644
index 651836f3a..000000000
--- a/multisrc/overrides/wpmangastream/komikcast/src/KomikCast.kt
+++ /dev/null
@@ -1,194 +0,0 @@
-package eu.kanade.tachiyomi.extension.id.komikcast
-
-import eu.kanade.tachiyomi.multisrc.wpmangastream.WPMangaStream
-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 kotlinx.serialization.decodeFromString
-import kotlinx.serialization.json.Json
-import kotlinx.serialization.json.jsonArray
-import kotlinx.serialization.json.jsonObject
-import okhttp3.Headers
-import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
-import okhttp3.OkHttpClient
-import okhttp3.Request
-import org.jsoup.Jsoup
-import org.jsoup.nodes.Document
-import org.jsoup.nodes.Element
-import uy.kohesive.injekt.injectLazy
-import java.util.concurrent.TimeUnit
-
-class KomikCast : WPMangaStream("Komik Cast", "https://komikcast.me", "id") {
- // Formerly "Komik Cast (WP Manga Stream)"
- override val id = 972717448578983812
-
- override val client: OkHttpClient = network.cloudflareClient.newBuilder()
- .connectTimeout(10, TimeUnit.SECONDS)
- .readTimeout(30, TimeUnit.SECONDS)
- .rateLimit(3)
- .build()
-
- override fun headersBuilder(): Headers.Builder = Headers.Builder()
- .add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9")
- .add("Accept-language", "en-US,en;q=0.9,id;q=0.8")
- .add("Referer", baseUrl)
- .add("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:25.0) Gecko/20100101 Firefox/25.0")
-
- override fun imageRequest(page: Page): Request {
- val newHeaders = headersBuilder()
- .set("Accept", "image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8")
- .set("Referer", baseUrl)
- .build()
-
- return GET(page.imageUrl!!, newHeaders)
- }
- override fun popularMangaSelector() = "div.list-update_item"
-
- override fun popularMangaRequest(page: Int): Request {
- return GET("$baseUrl/daftar-komik/page/$page/?orderby=popular", headers)
- }
-
- override fun latestUpdatesRequest(page: Int): Request {
- return GET("$baseUrl/komik/page/$page/", headers)
- }
-
- override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
- val url = if (query.isNotBlank()) {
- val url = "$baseUrl/page/$page".toHttpUrlOrNull()!!.newBuilder()
- val pattern = "\\s+".toRegex()
- val q = query.replace(pattern, "+")
- if (query.isNotEmpty()) {
- url.addQueryParameter("s", q)
- } else {
- url.addQueryParameter("s", "")
- }
- url.toString()
- } else {
- var url = "$baseUrl/daftar-komik/page/$page".toHttpUrlOrNull()!!.newBuilder()
- var orderBy: String
- (if (filters.isEmpty()) getFilterList() else filters).forEach { filter ->
- when (filter) {
- is StatusFilter -> url.addQueryParameter("status", arrayOf("", "ongoing", "completed")[filter.state])
- is GenreListFilter -> {
- val genreInclude = mutableListOf()
- filter.state.forEach {
- if (it.state == 1) {
- genreInclude.add(it.id)
- }
- }
- if (genreInclude.isNotEmpty()) {
- genreInclude.forEach { genre ->
- url.addQueryParameter("genre[]", genre)
- }
- }
- }
- is SortByFilter -> {
- orderBy = filter.toUriPart()
- url.addQueryParameter("orderby", orderBy)
- }
- is ProjectFilter -> {
- if (filter.toUriPart() == "project-filter-on") {
- url = "$baseUrl/project-list/page/$page".toHttpUrlOrNull()!!.newBuilder()
- }
- }
- }
- }
- url.toString()
- }
- return GET(url, headers)
- }
-
- override fun popularMangaFromElement(element: Element): SManga {
- val manga = SManga.create()
- element.select("a").first().let {
- manga.setUrlWithoutDomain(it.attr("href"))
- manga.title = it.select(".list-update_item-info h3.title").text()
- manga.thumbnail_url = element.select("div.list-update_item-image img").imgAttr()
- }
- return manga
- }
-
- override fun mangaDetailsParse(document: Document): SManga {
- return SManga.create().apply {
- document.select("div.komik_info").firstOrNull()?.let { infoElement ->
- genre = infoElement.select(".komik_info-content-genre a").joinToString { it.text() }
- status = parseStatus(infoElement.select("span:contains(Status:)").firstOrNull()?.ownText())
- author = infoElement.select("span:contains(Author:)").firstOrNull()?.ownText()
- artist = infoElement.select("span:contains(Author:)").firstOrNull()?.ownText()
- description = infoElement.select("div.komik_info-description-sinopsis p").joinToString("\n") { it.text() }
- thumbnail_url = infoElement.select("div.komik_info-content-thumbnail img").imgAttr()
-
- // add series type(manga/manhwa/manhua/other) thinggy to genre
- document.select(seriesTypeSelector).firstOrNull()?.ownText()?.let {
- if (it.isEmpty().not() && genre!!.contains(it, true).not()) {
- genre += if (genre!!.isEmpty()) it else ", $it"
- }
- }
-
- // 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 = "span:contains(Type) a"
- override val altNameSelector = ".komik_info-content-native"
-
- override fun chapterListSelector() = "div.komik_info-chapters li"
-
- 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(".chapter-link-time").firstOrNull()?.text()?.let { parseChapterDate(it) } ?: 0
- return chapter
- }
-
- override fun pageListParse(document: Document): List {
- var doc = document
- var cssQuery = "div#chapter_body .main-reading-area img.size-full"
- val imageListRegex = Regex("chapterImages = (.*) \\|\\|")
- val imageListMatchResult = imageListRegex.find(document.toString())
-
- if (imageListMatchResult != null) {
- val imageListJson = imageListMatchResult.destructured.toList()[0]
- val imageList = json.parseToJsonElement(imageListJson).jsonObject
-
- var imageServer = "cdn"
- if (!imageList.containsKey(imageServer)) imageServer = imageList.keys.first()
- val imageElement = imageList[imageServer]!!.jsonArray.joinToString("")
- doc = Jsoup.parse(json.decodeFromString(imageElement))
- cssQuery = "img.size-full"
- }
-
- return doc.select(cssQuery)
- .mapIndexed { i, img -> Page(i, "", img.attr("abs:Src")) }
- }
-
- override fun getFilterList() = FilterList(
- Filter.Header("NOTE: Ignored if using text search!"),
- Filter.Separator(),
- SortByFilter(),
- Filter.Separator(),
- StatusFilter(),
- Filter.Separator(),
- GenreListFilter(getGenreList()),
- Filter.Header("NOTE: cant be used with other filter!"),
- Filter.Header("$name Project List page"),
- ProjectFilter()
- )
-
- private val json: Json by injectLazy()
-}
diff --git a/multisrc/overrides/wpmangastream/komikindoco/src/KomikindoCo.kt b/multisrc/overrides/wpmangastream/komikindoco/src/KomikindoCo.kt
deleted file mode 100644
index de477b2ed..000000000
--- a/multisrc/overrides/wpmangastream/komikindoco/src/KomikindoCo.kt
+++ /dev/null
@@ -1,81 +0,0 @@
-package eu.kanade.tachiyomi.extension.id.komikindoco
-
-import eu.kanade.tachiyomi.multisrc.wpmangastream.WPMangaStream
-import eu.kanade.tachiyomi.network.interceptor.rateLimit
-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.OkHttpClient
-import org.jsoup.nodes.Document
-import uy.kohesive.injekt.injectLazy
-import java.text.SimpleDateFormat
-import java.util.Locale
-import java.util.concurrent.TimeUnit
-
-class KomikindoCo : WPMangaStream("KomikIndo.co", "https://komikindo.co", "id", SimpleDateFormat("MMM dd, yyyy", Locale("id"))) {
- // Formerly "Komikindo.co"
- override val id = 734619124437406170
-
- override val client: OkHttpClient = network.cloudflareClient.newBuilder()
- .connectTimeout(10, TimeUnit.SECONDS)
- .readTimeout(30, TimeUnit.SECONDS)
- .rateLimit(4)
- .build()
-
- override val hasProjectPage = true
-
- override fun mangaDetailsParse(document: Document): SManga {
- return SManga.create().apply {
- document.select(".seriestucontent").firstOrNull()?.let { infoElement ->
- genre = infoElement.select(".seriestugenre a").joinToString { it.text() }
- status = parseStatus(infoElement.select(".infotable tr:contains(Status) td:last-child").firstOrNull()?.ownText())
- author = infoElement.select(".infotable tr:contains(Author) td:last-child").firstOrNull()?.ownText()
- description = infoElement.select(".entry-content-single[itemprop=\"description\"]").joinToString("\n") { it.text() }
- thumbnail_url = infoElement.select("div.thumb img").imgAttr()
-
- // add series type(manga/manhwa/manhua/other) thinggy to genre
- document.select(seriesTypeSelector).firstOrNull()?.ownText()?.let {
- if (it.isEmpty().not() && genre!!.contains(it, true).not()) {
- genre += if (genre!!.isEmpty()) it else ", $it"
- }
- }
-
- // 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
- }
- }
- }
- }
- }
- }
-
- private val json: Json by injectLazy()
-
- override fun pageListParse(document: Document): List {
- val pages = mutableListOf()
- document.select(pageSelector)
- .filterNot { it.attr("src").isNullOrEmpty() }
- .mapIndexed { i, img -> pages.add(Page(i, "", img.attr("abs:src"))) }
-
- // Some sites like mangakita now load pages via javascript
- if (pages.isNotEmpty()) { return pages }
-
- val docString = document.toString()
- val imageListRegex = Regex("\\\"images.*?:.*?(\\[.*?\\])")
- val imageListJson = imageListRegex.find(docString)!!.destructured.toList()[0]
-
- val imageList = json.parseToJsonElement(imageListJson).jsonArray
-
- pages += imageList.mapIndexed { i, jsonEl ->
- Page(i, "", jsonEl.jsonPrimitive.content)
- }
-
- return pages
- }
-}
diff --git a/multisrc/overrides/wpmangastream/komikstation/src/KomikStation.kt b/multisrc/overrides/wpmangastream/komikstation/src/KomikStation.kt
deleted file mode 100644
index 0ae354eb6..000000000
--- a/multisrc/overrides/wpmangastream/komikstation/src/KomikStation.kt
+++ /dev/null
@@ -1,51 +0,0 @@
-package eu.kanade.tachiyomi.extension.id.komikstation
-
-import eu.kanade.tachiyomi.multisrc.wpmangastream.WPMangaStream
-import eu.kanade.tachiyomi.network.interceptor.rateLimit
-import eu.kanade.tachiyomi.source.model.Page
-import kotlinx.serialization.json.Json
-import kotlinx.serialization.json.jsonArray
-import kotlinx.serialization.json.jsonPrimitive
-import okhttp3.OkHttpClient
-import org.jsoup.nodes.Document
-import uy.kohesive.injekt.injectLazy
-import java.util.concurrent.TimeUnit
-
-class KomikStation : WPMangaStream("Komik Station", "https://komikstation.co", "id") {
- // Formerly "Komik Station (WP Manga Stream)"
- override val id = 6148605743576635261
-
- override val client: OkHttpClient = network.cloudflareClient.newBuilder()
- .connectTimeout(10, TimeUnit.SECONDS)
- .readTimeout(30, TimeUnit.SECONDS)
- .rateLimit(4)
- .build()
-
- private val json: Json by injectLazy()
-
- override fun pageListParse(document: Document): List {
- val pages = mutableListOf()
- document.select(pageSelector)
- .filterNot { it.attr("abs:src").isNullOrEmpty() }
- .mapIndexed { i, img -> pages.add(Page(i, "", img.attr("abs:src"))) }
-
- // Some sites like mangakita now load pages via javascript
- if (pages.isNotEmpty()) { return pages }
-
- val docString = document.toString()
- val imageListRegex = Regex("\\\"images.*?:.*?(\\[.*?\\])")
- val imageListJson = imageListRegex.find(docString)!!.destructured.toList()[0]
-
- val imageList = json.parseToJsonElement(imageListJson).jsonArray
-
- pages += imageList.mapIndexed { i, jsonEl ->
- Page(i, "", jsonEl.jsonPrimitive.content)
- }
-
- return pages
- }
-
- override val projectPageString = "/project-list"
-
- override val hasProjectPage = true
-}
diff --git a/multisrc/overrides/wpmangastream/mangaswat/src/MangaSwat.kt b/multisrc/overrides/wpmangastream/mangaswat/src/MangaSwat.kt
deleted file mode 100644
index d51eb5266..000000000
--- a/multisrc/overrides/wpmangastream/mangaswat/src/MangaSwat.kt
+++ /dev/null
@@ -1,170 +0,0 @@
-package eu.kanade.tachiyomi.extension.ar.mangaswat
-
-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 okhttp3.Headers
-import okhttp3.Request
-import org.json.JSONObject
-import org.jsoup.nodes.Document
-import java.text.SimpleDateFormat
-import java.util.Locale
-
-class MangaSwat : WPMangaStream(
- "MangaSwat",
- "https://swatmanga.co",
- "ar",
- SimpleDateFormat("yyyy-MM-dd", Locale.US)
-) {
-
- override fun headersBuilder(): Headers.Builder = Headers.Builder()
- .add(
- "Accept",
- "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9"
- )
- .add("Accept-language", "en-US,en;q=0.9")
- .add("Referer", baseUrl)
-
- override fun imageRequest(page: Page): Request {
- val newHeaders = headersBuilder()
- .set("Accept", "image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8")
- .set("Referer", baseUrl)
- .build()
- return GET(page.imageUrl!!, newHeaders)
- }
-
- override fun mangaDetailsParse(document: Document): SManga {
- return SManga.create().apply {
- document.select("div.bigcontent").firstOrNull()?.let { infoElement ->
- genre = infoElement.select("span:contains(التصنيف) a").joinToString { it.text() }
- status = parseStatus(
- infoElement.select("span:contains(الحالة)").firstOrNull()?.ownText()
- )
- author = infoElement.select("span:contains(المؤلف)").firstOrNull()?.ownText()
- artist = infoElement.select("span:contains(الناشر) i").firstOrNull()?.ownText()
- description = infoElement.select("div.desc").text()
- thumbnail_url = infoElement.select("img").imgAttr()
-
- val genres = infoElement.select("span:contains(التصنيف) a, .mgen a")
- .map { element -> element.text().lowercase() }
- .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.lowercase())
- }
- }
-
- genre = genres.toList().map { it.capitalize() }.joinToString(", ")
- }
- }
- }
-
- override val seriesTypeSelector = "span:contains(النوع) a"
-
- override val pageSelector = "div#readerarea img"
-
- override fun pageListParse(document: Document): List {
- var page: List? = null
- val scriptContent = document.selectFirst("script:containsData(ts_reader)").data()
- val removeHead = scriptContent.replace("ts_reader.run(", "").replace(");", "")
- val jsonObject = JSONObject(removeHead)
- val sourcesArray = jsonObject.getJSONArray("sources")
- val imagesArray = sourcesArray.getJSONObject(0).getJSONArray("images")
- page = List(imagesArray.length()) { i ->
- Page(i, "", imagesArray[i].toString())
- }
-
- return page!!
- }
-
- override fun getFilterList() = FilterList(
- SortByFilter(),
- Filter.Separator(),
- StatusFilter(),
- Filter.Separator(),
- TypeFilter(),
- Filter.Separator(),
- Filter.Header("Genre exclusion not available for all sources"),
- GenreListFilter(getGenreList()),
- )
-
- override fun getGenreList(): List = listOf(
- Genre("<--->", ""),
- Genre("Adult", "adult"),
- Genre("آلات", "%d8%a2%d9%84%d8%a7%d8%aa"),
- Genre("أكشن", "%d8%a3%d9%83%d8%b4%d9%86"),
- Genre("إثارة", "%d8%a5%d8%ab%d8%a7%d8%b1%d8%a9"),
- Genre("إعادة إحياء", "%d8%a5%d8%b9%d8%a7%d8%af%d8%a9-%d8%a5%d8%ad%d9%8a%d8%a7%d8%a1"),
- Genre(
- "الحياة المدرسية",
- "%d8%a7%d9%84%d8%ad%d9%8a%d8%a7%d8%a9-%d8%a7%d9%84%d9%85%d8%af%d8%b1%d8%b3%d9%8a%d8%a9"
- ),
- Genre(
- "الحياة اليومية",
- "%d8%a7%d9%84%d8%ad%d9%8a%d8%a7%d8%a9-%d8%a7%d9%84%d9%8a%d9%88%d9%85%d9%8a%d8%a9"
- ),
- Genre("العاب فيديو", "%d8%a7%d9%84%d8%b9%d8%a7%d8%a8-%d9%81%d9%8a%d8%af%d9%8a%d9%88"),
- Genre("ايتشي", "%d8%a7%d9%8a%d8%aa%d8%b4%d9%8a"),
- Genre("ايسكاي", "%d8%a7%d9%8a%d8%b3%d9%83%d8%a7%d9%8a"),
- Genre("بالغ", "%d8%a8%d8%a7%d9%84%d8%ba"),
- Genre("تاريخي", "%d8%aa%d8%a7%d8%b1%d9%8a%d8%ae%d9%8a"),
- Genre("تراجيدي", "%d8%aa%d8%b1%d8%a7%d8%ac%d9%8a%d8%af%d9%8a"),
- Genre("تناسخ", "%d8%aa%d9%86%d8%a7%d8%b3%d8%ae"),
- Genre("جريمة", "%d8%ac%d8%b1%d9%8a%d9%85%d8%a9"),
- Genre("جوسيه", "%d8%ac%d9%88%d8%b3%d9%8a%d9%87"),
- Genre("جيندر بندر", "%d8%ac%d9%8a%d9%86%d8%af%d8%b1-%d8%a8%d9%86%d8%af%d8%b1"),
- Genre("حديث", "%d8%ad%d8%af%d9%8a%d8%ab"),
- Genre("حربي", "%d8%ad%d8%b1%d8%a8%d9%8a"),
- Genre("حريم", "%d8%ad%d8%b1%d9%8a%d9%85"),
- Genre(
- "خارق للطبيعة",
- "%d8%ae%d8%a7%d8%b1%d9%82-%d9%84%d9%84%d8%b7%d8%a8%d9%8a%d8%b9%d8%a9"
- ),
- Genre("خيال", "%d8%ae%d9%8a%d8%a7%d9%84"),
- Genre("خيال علمي", "%d8%ae%d9%8a%d8%a7%d9%84-%d8%b9%d9%84%d9%85%d9%8a"),
- Genre("دراما", "%d8%af%d8%b1%d8%a7%d9%85%d8%a7"),
- Genre("دموي", "%d8%af%d9%85%d9%88%d9%8a"),
- Genre("راشد", "%d8%af%d9%85%d9%88%d9%8a"),
- Genre("رعب", "%d8%b1%d8%b9%d8%a8"),
- Genre("رومانسي", "%d8%b1%d9%88%d9%85%d8%a7%d9%86%d8%b3%d9%8a"),
- Genre("رياضة", "%d8%b1%d9%8a%d8%a7%d8%b6%d8%a9"),
- Genre("زمكاني", "%d8%b2%d9%85%d9%83%d8%a7%d9%86%d9%8a"),
- Genre("زومبي", "%d8%b2%d9%88%d9%85%d8%a8%d9%8a"),
- Genre("سحر", "%d8%b3%d8%ad%d8%b1"),
- Genre("سينين", "%d8%b3%d9%8a%d9%86%d9%8a%d9%86"),
- Genre(
- "شريحة من الحياة",
- "%d8%b4%d8%b1%d9%8a%d8%ad%d8%a9-%d9%85%d9%86-%d8%a7%d9%84%d8%ad%d9%8a%d8%a7%d8%a9"
- ),
- Genre("شوجو", "%d8%b4%d9%88%d8%ac%d9%88"),
- Genre("شونين", "%d8%b4%d9%88%d9%86%d9%8a%d9%86"),
- Genre("شياطين", "%d8%b4%d9%8a%d8%a7%d8%b7%d9%8a%d9%86"),
- Genre("طبخ", "%d8%b7%d8%a8%d8%ae"),
- Genre("طبي", "%d8%b7%d8%a8%d9%8a"),
- Genre("غموض", "%d8%ba%d9%85%d9%88%d8%b6"),
- Genre("فانتازي", "%d9%81%d8%a7%d9%86%d8%aa%d8%a7%d8%b2%d9%8a"),
- Genre("فنون قتالية", "%d9%81%d9%86%d9%88%d9%86-%d9%82%d8%aa%d8%a7%d9%84%d9%8a%d8%a9"),
- Genre("فوق الطبيعة", "%d9%81%d9%88%d9%82-%d8%a7%d9%84%d8%b7%d8%a8%d9%8a%d8%b9%d8%a9"),
- Genre("قوى خارقة", "%d9%82%d9%88%d9%89-%d8%ae%d8%a7%d8%b1%d9%82%d8%a9"),
- Genre("كوميدي", "%d9%83%d9%88%d9%85%d9%8a%d8%af%d9%8a"),
- Genre("لعبة", "%d9%84%d8%b9%d8%a8%d8%a9"),
- Genre("مافيا", "%d9%85%d8%a7%d9%81%d9%8a%d8%a7"),
- Genre(
- "مصاصى الدماء",
- "%d9%85%d8%b5%d8%a7%d8%b5%d9%89-%d8%a7%d9%84%d8%af%d9%85%d8%a7%d8%a1"
- ),
- Genre("مغامرات", "%d9%85%d8%ba%d8%a7%d9%85%d8%b1%d8%a7%d8%aa"),
- Genre("موريم", "%d9%85%d9%88%d8%b1%d9%8a%d9%85"),
- Genre("موسيقي", "%d9%85%d9%88%d8%b3%d9%8a%d9%82%d9%89"),
- Genre("ميشا", "%d9%85%d9%8a%d8%b4%d8%a7"),
- Genre("ميكا", "%d9%85%d9%8a%d9%83%d8%a7"),
- Genre("نفسي", "%d9%86%d9%81%d8%b3%d9%8a"),
- Genre("وحوش", "%d9%88%d8%ad%d9%88%d8%b4"),
- Genre("ويب-تون", "%d9%88%d9%8a%d8%a8-%d8%aa%d9%88%d9%86")
- )
-}
diff --git a/multisrc/overrides/wpmangastream/mihentai/src/Mihentai.kt b/multisrc/overrides/wpmangastream/mihentai/src/Mihentai.kt
deleted file mode 100644
index 2aec935c2..000000000
--- a/multisrc/overrides/wpmangastream/mihentai/src/Mihentai.kt
+++ /dev/null
@@ -1,179 +0,0 @@
-package eu.kanade.tachiyomi.extension.en.mihentai
-
-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
-
-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().lowercase(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.lowercase(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/noxsubs/src/NoxSubs.kt b/multisrc/overrides/wpmangastream/noxsubs/src/NoxSubs.kt
deleted file mode 100644
index 147f82474..000000000
--- a/multisrc/overrides/wpmangastream/noxsubs/src/NoxSubs.kt
+++ /dev/null
@@ -1,7 +0,0 @@
-package eu.kanade.tachiyomi.extension.tr.noxsubs
-
-import eu.kanade.tachiyomi.multisrc.wpmangastream.WPMangaStream
-import java.text.SimpleDateFormat
-import java.util.Locale
-
-class NoxSubs : WPMangaStream("NoxSubs", "https://noxsubs.com", "tr", SimpleDateFormat("MMM d, yyyy", Locale("tr")))
diff --git a/multisrc/overrides/wpmangastream/phoenixfansub/src/PhoenixFansub.kt b/multisrc/overrides/wpmangastream/phoenixfansub/src/PhoenixFansub.kt
deleted file mode 100644
index e78e4aca6..000000000
--- a/multisrc/overrides/wpmangastream/phoenixfansub/src/PhoenixFansub.kt
+++ /dev/null
@@ -1,15 +0,0 @@
-package eu.kanade.tachiyomi.extension.es.phoenixfansub
-
-import eu.kanade.tachiyomi.multisrc.wpmangastream.WPMangaStream
-import java.text.SimpleDateFormat
-import java.util.Locale
-
-class PhoenixFansub : WPMangaStream(
- "Phoenix Fansub",
- "https://phoenixfansub.com",
- "es",
- SimpleDateFormat("MMM d, yyyy", Locale("es"))
-) {
-
- override val altName: String = "Nombre alternativo: "
-}
diff --git a/multisrc/overrides/wpmangastream/westmanga/src/WestManga.kt b/multisrc/overrides/wpmangastream/westmanga/src/WestManga.kt
deleted file mode 100644
index 32c7999c6..000000000
--- a/multisrc/overrides/wpmangastream/westmanga/src/WestManga.kt
+++ /dev/null
@@ -1,121 +0,0 @@
-package eu.kanade.tachiyomi.extension.id.westmanga
-
-import eu.kanade.tachiyomi.multisrc.wpmangastream.WPMangaStream
-import eu.kanade.tachiyomi.network.interceptor.rateLimit
-import eu.kanade.tachiyomi.source.model.SManga
-import okhttp3.OkHttpClient
-import org.jsoup.nodes.Document
-import java.util.concurrent.TimeUnit
-
-class WestManga : WPMangaStream("West Manga", "https://westmanga.info", "id") {
- // Formerly "West Manga (WP Manga Stream)"
- override val id = 8883916630998758688
-
- override val client: OkHttpClient = network.cloudflareClient.newBuilder()
- .connectTimeout(10, TimeUnit.SECONDS)
- .readTimeout(30, TimeUnit.SECONDS)
- .rateLimit(4)
- .build()
-
- override fun mangaDetailsParse(document: Document): SManga {
- return SManga.create().apply {
- document.select(".seriestucontent").firstOrNull()?.let { infoElement ->
- genre = infoElement.select(".seriestugenre a").joinToString { it.text() }
- status = parseStatus(infoElement.select(".infotable tr:contains(Status) td:last-child").firstOrNull()?.ownText())
- author = infoElement.select(".infotable tr:contains(Author) td:last-child").firstOrNull()?.ownText()
- description = infoElement.select(".entry-content-single[itemprop=\"description\"]").joinToString("\n") { it.text() }
- thumbnail_url = infoElement.select("div.thumb img").imgAttr()
-
- // add series type(manga/manhwa/manhua/other) thinggy to genre
- document.select(seriesTypeSelector).firstOrNull()?.ownText()?.let {
- if (it.isEmpty().not() && genre!!.contains(it, true).not()) {
- genre += if (genre!!.isEmpty()) it else ", $it"
- }
- }
-
- // 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 = ".infotable tr:contains(Type) td:last-child"
- override fun getGenreList(): List = listOf(
- Genre("4 Koma", "344"),
- Genre("Action", "13"),
- Genre("Adventure", "4"),
- Genre("Anthology", "1494"),
- Genre("Comedy", "5"),
- Genre("Cooking", "54"),
- Genre("Crime", "856"),
- Genre("Crossdressing", "1306"),
- Genre("Demon", "64"),
- Genre("Drama", "6"),
- Genre("Ecchi", "14"),
- Genre("Fantasy", "7"),
- Genre("Game", "36"),
- Genre("Gender Bender", "149"),
- Genre("Genderswap", "157"),
- Genre("Gore", "56"),
- Genre("Gyaru", "812"),
- Genre("Harem", "17"),
- Genre("Historical", "44"),
- Genre("Horror", "211"),
- Genre("Isekai", "20"),
- Genre("Isekai Action", "742"),
- Genre("Josei", "164"),
- Genre("Magic", "65"),
- Genre("Manga", "268"),
- Genre("Manhua", "32"),
- Genre("Martial Art", "754"),
- Genre("Martial Arts", "8"),
- Genre("Mature", "46"),
- Genre("Mecha", "22"),
- Genre("Medical", "704"),
- Genre("Medy", "1439"),
- Genre("Monsters", "91"),
- Genre("Music", "457"),
- Genre("Mystery", "30"),
- Genre("Office Workers", "1501"),
- Genre("Oneshot", "405"),
- Genre("Project", "313"),
- Genre("Psychological", "23"),
- Genre("Reincarnation", "57"),
- Genre("Reinkarnasi", "1170"),
- Genre("Romance", "15"),
- Genre("School", "102"),
- Genre("School Life", "9"),
- Genre("Sci-fi", "33"),
- Genre("Seinen", "18"),
- Genre("Shotacon", "1070"),
- Genre("Shoujo", "110"),
- Genre("Shoujo Ai", "113"),
- Genre("Shounen", "10"),
- Genre("Shounen Ai", "shounen-ai"),
- Genre("Si-fi", "776"),
- Genre("Slice of Lif", "773"),
- Genre("Slice of Life", "11"),
- Genre("Smut", "586"),
- Genre("Sports", "103"),
- Genre("Super Power", "274"),
- Genre("Supernatural", "34"),
- Genre("Suspense", "181"),
- Genre("Thriller", "170"),
- Genre("Tragedy", "92"),
- Genre("Urban", "1050"),
- Genre("Vampire", "160"),
- Genre("Video Games", "1093"),
- Genre("Webtoons", "486"),
- Genre("Yaoi", "yaoi"),
- Genre("Zombies", "377")
- )
-
- override val hasProjectPage = true
-}
diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangathemesia/MangaThemesia.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangathemesia/MangaThemesia.kt
new file mode 100644
index 000000000..70e58bb3a
--- /dev/null
+++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangathemesia/MangaThemesia.kt
@@ -0,0 +1,476 @@
+package eu.kanade.tachiyomi.multisrc.mangathemesia
+
+import eu.kanade.tachiyomi.network.GET
+import eu.kanade.tachiyomi.network.POST
+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 kotlinx.serialization.json.Json
+import kotlinx.serialization.json.jsonArray
+import kotlinx.serialization.json.jsonPrimitive
+import okhttp3.FormBody
+import okhttp3.HttpUrl
+import okhttp3.HttpUrl.Companion.toHttpUrl
+import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
+import okhttp3.OkHttpClient
+import okhttp3.Request
+import okhttp3.Response
+import org.jsoup.nodes.Document
+import org.jsoup.nodes.Element
+import org.jsoup.select.Elements
+import rx.Observable
+import uy.kohesive.injekt.injectLazy
+import java.lang.IllegalArgumentException
+import java.text.SimpleDateFormat
+import java.util.Locale
+import java.util.concurrent.TimeUnit
+
+abstract class MangaThemesia(
+ override val name: String,
+ override val baseUrl: String,
+ override val lang: String,
+ val mangaUrlDirectory: String = "/manga",
+ private val dateFormat: SimpleDateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale.US)
+) : ParsedHttpSource() {
+
+ protected open val json: Json by injectLazy()
+
+ override val supportsLatest = true
+
+ override val client: OkHttpClient = network.cloudflareClient.newBuilder()
+ .connectTimeout(10, TimeUnit.SECONDS)
+ .readTimeout(30, TimeUnit.SECONDS)
+ .build()
+
+ open val projectPageString = "/project"
+
+ // Popular (Search with popular order and nothing else)
+ override fun popularMangaRequest(page: Int) = searchMangaRequest(page, "", FilterList(OrderByFilter("popular")))
+ override fun popularMangaParse(response: Response) = searchMangaParse(response)
+
+ // Latest (Search with update order and nothing else)
+ override fun latestUpdatesRequest(page: Int) = searchMangaRequest(page, "", FilterList(OrderByFilter("update")))
+ override fun latestUpdatesParse(response: Response) = searchMangaParse(response)
+
+ // Search
+ override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable {
+ if (query.startsWith(URL_SEARCH_PREFIX).not()) return super.fetchSearchManga(page, query, filters)
+
+ val mangaPath = try {
+ mangaPathFromUrl(query.substringAfter(URL_SEARCH_PREFIX))
+ ?: return Observable.just(MangasPage(emptyList(), false))
+ } catch (e: Exception) {
+ return Observable.error(e)
+ }
+
+ return fetchMangaDetails(
+ SManga.create()
+ .apply { this.url = "$mangaUrlDirectory/$mangaPath" }
+ )
+ .map {
+ // Isn't set in returned manga
+ it.url = "$mangaUrlDirectory/$id"
+ MangasPage(listOf(it), false)
+ }
+ }
+
+ override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
+ val url = baseUrl.toHttpUrl().newBuilder()
+ if (query.isNotEmpty()) {
+ url.addPathSegments("page/$page").addQueryParameter("s", query)
+ } else {
+ url.addPathSegment(mangaUrlDirectory.substring(1)).addQueryParameter("page", page.toString())
+ filters.forEach { filter ->
+ when (filter) {
+ is AuthorFilter -> {
+ url.addQueryParameter("author", filter.state)
+ }
+ is YearFilter -> {
+ url.addQueryParameter("yearx", filter.state)
+ }
+ is StatusFilter -> {
+ url.addQueryParameter("status", filter.selectedValue)
+ }
+ is TypeFilter -> {
+ url.addQueryParameter("type", filter.selectedValue)
+ }
+ is OrderByFilter -> {
+ url.addQueryParameter("order", filter.selectedValue)
+ }
+ is GenreListFilter -> {
+ filter.state
+ .filter { it.state != Filter.TriState.STATE_IGNORE }
+ .forEach {
+ val value = if (it.state == Filter.TriState.STATE_EXCLUDE) "-${it.value}" else it.value
+ url.addQueryParameter("genre[]", value)
+ }
+ }
+ // if site has project page, default value "hasProjectPage" = false
+ is ProjectFilter -> {
+ if (filter.selectedValue == "project-filter-on") {
+ url.setPathSegment(0, projectPageString.substring(1))
+ }
+ }
+ else -> { /* Do Nothing */ }
+ }
+ }
+ }
+ return GET(url.toString())
+ }
+
+ override fun searchMangaParse(response: Response): MangasPage {
+ if (genrelist == null) {
+ genrelist = parseGenres(response.asJsoup(response.peekBody(Long.MAX_VALUE).string()))
+ }
+
+ return super.searchMangaParse(response)
+ }
+
+ override fun searchMangaSelector() = ".utao .uta .imgu, .listupd .bs .bsx, .listo .bs .bsx"
+
+ override fun searchMangaFromElement(element: Element) = SManga.create().apply {
+ thumbnail_url = element.select("img").imgAttr()
+ title = element.select("a").attr("title")
+ setUrlWithoutDomain(element.select("a").attr("href"))
+ }
+
+ override fun searchMangaNextPageSelector() = "div.pagination .next, div.hpage .r"
+
+ // Manga details
+ open val seriesDetailsSelector = "div.bigcontent, div.animefull, div.main-info"
+ open val seriesTitleSelector = "h1.entry-title"
+ open val seriesArtistSelector = ".infotable tr:contains(artist) td:last-child, .tsinfo .imptdt:contains(artist) i, .fmed b:contains(artist)+span, span:contains(artist)"
+ open val seriesAuthorSelector = ".infotable tr:contains(author) td:last-child, .tsinfo .imptdt:contains(author) i, .fmed b:contains(author)+span, span:contains(author)"
+ open val seriesDescriptionSelector = ".desc, .entry-content[itemprop=description]"
+ open val seriesAltNameSelector = ".alternative, .wd-full:contains(alt) span, .alter, .seriestualt"
+ open val seriesGenreSelector = "div.gnr a, .mgen a, .seriestugenre a, span:contains(genre)"
+ open val seriesTypeSelector = ".infotable tr:contains(type) td:last-child, .tsinfo .imptdt:contains(type) i, .fmed b:contains(type)+span, span:contains(type) a, a[href*=type\\=]"
+ open val seriesStatusSelector = ".infotable tr:contains(status) td:last-child, .tsinfo .imptdt:contains(status) i, .fmed b:contains(status)+span span:contains(status)"
+ open val seriesThumbnailSelector = ".infomanga > div[itemprop=image] img, .thumb img"
+
+ open val altNamePrefix = "Alternative Name: "
+
+ override fun mangaDetailsParse(document: Document) = SManga.create().apply {
+ document.selectFirst(seriesDetailsSelector).let { seriesDetails ->
+ title = seriesDetails.selectFirst(seriesTitleSelector)?.text().orEmpty()
+ artist = seriesDetails.selectFirst(seriesArtistSelector)?.ownText().removeEmptyPlaceholder()
+ author = seriesDetails.selectFirst(seriesAuthorSelector)?.ownText().removeEmptyPlaceholder()
+ description = seriesDetails.select(seriesDescriptionSelector).joinToString("\n") { it.text() }
+ // Add alternative name to manga description
+ val altName = seriesDetails.selectFirst(seriesAltNameSelector)?.ownText().takeIf { it.isNullOrBlank().not() }
+ altName?.let {
+ description = "$description\n\n$altNamePrefix$altName".trim()
+ }
+ val genres = seriesDetails.select(seriesGenreSelector).map { it.text() }.toMutableList()
+ // Add series type (manga/manhwa/manhua/other) to genre
+ seriesDetails.selectFirst(seriesTypeSelector)?.ownText().takeIf { it.isNullOrBlank().not() }?.let { genres.add(it) }
+ genre = genres.map { genre ->
+ genre.lowercase(Locale.forLanguageTag(lang)).replaceFirstChar { char ->
+ if (char.isLowerCase()) char.titlecase(Locale.forLanguageTag(lang))
+ else char.toString()
+ }
+ }
+ .joinToString { it.trim() }
+
+ status = seriesDetails.selectFirst(seriesStatusSelector)?.text().parseStatus()
+ thumbnail_url = seriesDetails.select(seriesThumbnailSelector).imgAttr()
+ }
+ }
+
+ private fun String?.removeEmptyPlaceholder(): String? {
+ return if (this.isNullOrBlank() || this == "-" || this == "N/A") null else this
+ }
+
+ open fun String?.parseStatus(): Int = when {
+ this == null -> SManga.UNKNOWN
+ listOf("ongoing", "publishing").any { this.contains(it, ignoreCase = true) } -> SManga.ONGOING
+ this.contains("completed", ignoreCase = true) -> SManga.COMPLETED
+ else -> SManga.UNKNOWN
+ }
+
+ // Chapter list
+ override fun chapterListSelector() = "div.bxcl li, div.cl li, #chapterlist li .eph-num, li:has(div.chbox):has(div.eph-num)"
+
+ override fun chapterListParse(response: Response): List {
+ val document = response.asJsoup()
+ val chapters = document.select(chapterListSelector()).map { chapterFromElement(it) }
+
+ // Add timestamp to latest chapter, taken from "Updated On".
+ // So source which not provide chapter timestamp will have at least one
+ if (chapters.isNotEmpty() && chapters.first().date_upload == 0L) {
+ val date = document
+ .select(".listinfo time[itemprop=dateModified], .fmed:contains(update) time, span:contains(update) time")
+ .attr("datetime")
+ if (date.isNotEmpty()) chapters.first().date_upload = parseUpdatedOnDate(date)
+ }
+
+ countViews(document)
+
+ return chapters
+ }
+
+ private fun parseUpdatedOnDate(date: String): Long {
+ return SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH).parse(date)?.time ?: 0L
+ }
+
+ override fun chapterFromElement(element: Element) = SChapter.create().apply {
+ val urlElements = element.select("a")
+ setUrlWithoutDomain(urlElements.attr("href"))
+ name = element.select(".lch a, .chapternum").text().ifBlank { urlElements.first().text() }
+ date_upload = element.selectFirst(".chapterdate")?.text().parseChapterDate()
+ }
+
+ protected open fun String?.parseChapterDate(): Long {
+ if (this == null) return 0
+ return try {
+ dateFormat.parse(this)?.time ?: 0
+ } catch (_: Exception) {
+ 0
+ }
+ }
+
+ // Pages
+ open val pageSelector = "div#readerarea img"
+
+ override fun pageListParse(document: Document): List {
+ val htmlPages = document.select(pageSelector)
+ .filterNot { it.imgAttr().isEmpty() }
+ .mapIndexed { i, img -> Page(i, "", img.imgAttr()) }
+
+ countViews(document)
+
+ // Some sites also loads pages via javascript
+ if (htmlPages.isNotEmpty()) { return htmlPages }
+
+ val docString = document.toString()
+ val imageListJson = JSON_IMAGE_LIST_REGEX.find(docString)?.destructured?.toList()?.get(0).orEmpty()
+ val imageList = try {
+ json.parseToJsonElement(imageListJson).jsonArray
+ } catch (_: IllegalArgumentException) {
+ emptyList()
+ }
+ val scriptPages = imageList.mapIndexed { i, jsonEl ->
+ Page(i, "", jsonEl.jsonPrimitive.content)
+ }
+
+ return scriptPages
+ }
+
+ /**
+ * Set it to false if you want to disable the extension reporting the view count
+ * back to the source website through admin-ajax.php.
+ */
+ protected open val sendViewCount: Boolean = true
+
+ protected open fun countViewsRequest(document: Document): Request? {
+ val wpMangaData = document.select("script:containsData(dynamic_view_ajax)").firstOrNull()
+ ?.data() ?: return null
+
+ val postId = CHAPTER_PAGE_ID_REGEX.find(wpMangaData)?.groupValues?.get(1)
+ ?: MANGA_PAGE_ID_REGEX.find(wpMangaData)?.groupValues?.get(1)
+ ?: return null
+
+ val formBody = FormBody.Builder()
+ .add("action", "dynamic_view_ajax")
+ .add("post_id", postId)
+ .build()
+
+ val newHeaders = headersBuilder()
+ .set("Content-Length", formBody.contentLength().toString())
+ .set("Content-Type", formBody.contentType().toString())
+ .set("Referer", document.location())
+ .build()
+
+ return POST("$baseUrl/wp-admin/admin-ajax.php", newHeaders, formBody)
+ }
+
+ /**
+ * Send the view count request to the Madara endpoint.
+ *
+ * @param document The response document with the wp-manga data
+ */
+ protected open fun countViews(document: Document) {
+ if (!sendViewCount) {
+ return
+ }
+
+ val request = countViewsRequest(document) ?: return
+ runCatching { client.newCall(request).execute().close() }
+ }
+
+ // Filters
+ private class AuthorFilter : Filter.Text("Author")
+
+ private class YearFilter : Filter.Text("Year")
+
+ open class SelectFilter(
+ displayName: String,
+ vals: Array>,
+ defaultValue: String? = null
+ ) : Filter.Select(
+ displayName,
+ vals.map { it.first }.toTypedArray(),
+ vals.indexOfFirst { it.second == defaultValue }.takeIf { it != -1 } ?: 0
+ ) {
+ val selectedValue = vals[state].second
+ }
+
+ protected class StatusFilter : SelectFilter(
+ "Status",
+ arrayOf(
+ Pair("All", ""),
+ Pair("Ongoing", "ongoing"),
+ Pair("Completed", "completed"),
+ Pair("Hiatus", "hiatus"),
+ Pair("Dropped", "dropped")
+ )
+ )
+
+ protected class TypeFilter : SelectFilter(
+ "Type",
+ arrayOf(
+ Pair("All", ""),
+ Pair("Manga", "Manga"),
+ Pair("Manhwa", "Manhwa"),
+ Pair("Manhua", "Manhua"),
+ Pair("Comic", "Comic")
+ )
+ )
+
+ protected class OrderByFilter(defaultOrder: String? = null) : SelectFilter(
+ "Sort By",
+ arrayOf(
+ Pair("Default", ""),
+ Pair("A-Z", "title"),
+ Pair("Z-A", "titlereverse"),
+ Pair("Latest Update", "update"),
+ Pair("Latest Added", "latest"),
+ Pair("Popular", "popular")
+ ),
+ defaultOrder
+ )
+
+ protected class ProjectFilter : SelectFilter(
+ "Filter Project",
+ arrayOf(
+ Pair("Show all manga", ""),
+ Pair("Show only project manga", "project-filter-on")
+ )
+ )
+
+ protected class Genre(name: String, val value: String) : Filter.TriState(name)
+ protected class GenreListFilter(genres: List) : Filter.Group("Genre", genres)
+
+ private var genrelist: List? = null
+ protected open fun getGenreList(): List {
+ // Filters are fetched immediately once an extension loads
+ // We're only able to get filters after a loading the manga directory,
+ // and resetting the filters is the only thing that seems to reinflate the view
+ return genrelist ?: listOf(Genre("Press reset to attempt to fetch genres", ""))
+ }
+
+ open val hasProjectPage = false
+
+ override fun getFilterList(): FilterList {
+ val filters = mutableListOf>(
+ Filter.Header("NOTE: Ignored if using text search!"),
+ Filter.Separator(),
+ AuthorFilter(),
+ YearFilter(),
+ StatusFilter(),
+ TypeFilter(),
+ OrderByFilter(),
+ Filter.Header("Genre exclusion is not available for all sources"),
+ GenreListFilter(getGenreList()),
+ )
+ if (hasProjectPage) {
+ filters.addAll(
+ mutableListOf>(
+ Filter.Separator(),
+ Filter.Header("NOTE: Can't be used with other filter!"),
+ Filter.Header("$name Project List page"),
+ ProjectFilter(),
+ )
+ )
+ }
+ return FilterList(filters)
+ }
+
+ // Helpers
+ /**
+ * Given some string which represents an http urlString, returns path for a manga
+ * which can be used to fetch its details at "$baseUrl$mangaUrlDirectory/$mangaPath"
+ *
+ * @param urlString: String
+ *
+ * @returns Path of a manga, or null if none could be found
+ */
+ protected open fun mangaPathFromUrl(urlString: String): String? {
+ val baseMangaUrl = "$baseUrl$mangaUrlDirectory".toHttpUrl()
+ val url = urlString.toHttpUrlOrNull() ?: return null
+
+ val isMangaUrl = (baseMangaUrl.host == url.host && pathLengthIs(url, 2) && url.pathSegments[0] == baseMangaUrl.pathSegments[0])
+ if (isMangaUrl) return url.pathSegments[1]
+
+ val potentiallyChapterUrl = pathLengthIs(url, 1)
+ if (potentiallyChapterUrl) {
+ val response = client.newCall(GET(urlString, headers)).execute()
+ if (response.isSuccessful.not()) {
+ response.close()
+ throw IllegalStateException("HTTP error ${response.code}")
+ } else if (response.isSuccessful) {
+ val links = response.asJsoup().select("a[itemprop=item]")
+ // near the top of page: home > manga > current chapter
+ if (links.size == 3) {
+ return links[1].attr("href").toHttpUrlOrNull()?.encodedPath
+ }
+ }
+ }
+
+ return null
+ }
+
+ private fun pathLengthIs(url: HttpUrl, n: Int, strict: Boolean = false): Boolean {
+ return url.pathSegments.size == n && url.pathSegments[n - 1].isNotEmpty() ||
+ (!strict && url.pathSegments.size == n + 1 && url.pathSegments[n].isEmpty())
+ }
+
+ private fun parseGenres(document: Document): List? {
+ return document.selectFirst("ul.genrez")?.select("li")?.map { li ->
+ Genre(
+ li.selectFirst("label").text(),
+ li.selectFirst("input[type=checkbox]").attr("value")
+ )
+ }
+ }
+
+ protected open fun Element.imgAttr(): String = if (this.hasAttr("data-src")) this.attr("abs:data-src") else this.attr("abs:src")
+ protected open fun Elements.imgAttr(): String = this.first().imgAttr()
+
+ // Unused
+ override fun popularMangaSelector(): String = throw UnsupportedOperationException("Not used")
+ override fun popularMangaFromElement(element: Element): SManga = throw UnsupportedOperationException("Not used")
+ override fun popularMangaNextPageSelector(): String? = throw UnsupportedOperationException("Not used")
+
+ override fun latestUpdatesSelector(): String = throw UnsupportedOperationException("Not used")
+ override fun latestUpdatesFromElement(element: Element): SManga = throw UnsupportedOperationException("Not used")
+ override fun latestUpdatesNextPageSelector(): String? = throw UnsupportedOperationException("Not used")
+
+ override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not Used")
+
+ companion object {
+ const val URL_SEARCH_PREFIX = "url:"
+
+ // More info: https://issuetracker.google.com/issues/36970498
+ @Suppress("RegExpRedundantEscape")
+ private val MANGA_PAGE_ID_REGEX = "post_id\\s*:\\s*(\\d+)\\}".toRegex()
+ private val CHAPTER_PAGE_ID_REGEX = "chapter_id\\s*=\\s*(\\d+);".toRegex()
+
+ val JSON_IMAGE_LIST_REGEX = "\"images\"\\s*:\\s*(\\[.*?])".toRegex()
+ }
+}
diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/wpmangareader/WPMangaReaderGenerator.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangathemesia/MangaThemesiaGenerator.kt
similarity index 51%
rename from multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/wpmangareader/WPMangaReaderGenerator.kt
rename to multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangathemesia/MangaThemesiaGenerator.kt
index 94a02a3d9..3b30ec98a 100644
--- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/wpmangareader/WPMangaReaderGenerator.kt
+++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangathemesia/MangaThemesiaGenerator.kt
@@ -1,67 +1,108 @@
-package eu.kanade.tachiyomi.multisrc.wpmangareader
+package eu.kanade.tachiyomi.multisrc.mangathemesia
import generator.ThemeSourceData.MultiLang
import generator.ThemeSourceData.SingleLang
import generator.ThemeSourceGenerator
-class WPMangaReaderGenerator : ThemeSourceGenerator {
+class MangaThemesiaGenerator : ThemeSourceGenerator {
- override val themePkg = "wpmangareader"
+ override val themePkg = "mangathemesia"
- override val themeClass = "WPMangaReader"
+ override val themeClass = "MangaThemesia"
- override val baseVersionCode: Int = 14
+ override val baseVersionCode: Int = 16
override val sources = listOf(
+ MultiLang("Asura Scans", "https://www.asurascans.com", listOf("en", "tr"), className = "AsuraScansFactory", pkgName = "asurascans", overrideVersionCode = 16),
MultiLang("Flame Scans", "https://flamescans.org", listOf("ar", "en"), className = "FlameScansFactory", pkgName = "flamescans", overrideVersionCode = 1),
SingleLang("Ace Scans", "https://acescans.xyz", "en", isNsfw = true, overrideVersionCode = 2),
SingleLang("Alpha Scans", "https://alpha-scans.org", "en", overrideVersionCode = 1),
+ SingleLang("Animated Glitched Scans", "https://anigliscans.com", "en"),
SingleLang("Arcane scan", "https://arcanescan.fr", "fr"),
+ SingleLang("ARESManga", "https://aresmanga.com", "ar", pkgName = "iimanga", overrideVersionCode = 2),
SingleLang("Azure Scans", "https://azuremanga.com", "en", overrideVersionCode = 1),
SingleLang("BeastScans", "https://beastscans.com", "en"),
+ SingleLang("Boosei", "https://boosei.com", "id", overrideVersionCode = 1),
SingleLang("Franxx Mangás", "https://franxxmangas.net", "pt-BR", className = "FranxxMangas", isNsfw = true),
SingleLang("Fusion Scanlation", "https://fusionscanlation.com", "es", className = "FusionScanlation", overrideVersionCode = 2),
SingleLang("Gabut Scans", "https://gabutscans.com", "id", overrideVersionCode = 1),
SingleLang("Gecenin Lordu", "https://geceninlordu.com", "tr", overrideVersionCode = 1),
+ SingleLang("GoGoManga", "https://gogomanga.fun", "en", overrideVersionCode = 1),
+ SingleLang("Imagine Scan", "https://imaginescan.com.br", "pt-BR", isNsfw = true, overrideVersionCode = 1),
+ SingleLang("Imperfect Comics", "https://imperfectcomic.com", "en", overrideVersionCode = 8),
SingleLang("InariManga", "https://inarimanga.com", "es"),
+ SingleLang("Infernal Void Scans", "https://void-scans.com", "en", overrideVersionCode = 4),
+ SingleLang("Kanzenin", "https://kanzenin.xyz", "id", isNsfw = true),
SingleLang("Kiryuu", "https://kiryuu.id", "id", overrideVersionCode = 6),
+ SingleLang("KlanKomik", "https://klankomik.com", "id", overrideVersionCode = 1),
+ SingleLang("Komik AV", "https://komikav.com", "id", overrideVersionCode = 1),
+ SingleLang("Komik Cast", "https://komikcast.me", "id", overrideVersionCode = 12),
SingleLang("Komik Lab", "https://komiklab.com", "id"),
+ SingleLang("Komik Station", "https://komikstation.co", "id", overrideVersionCode = 3),
+ SingleLang("KomikIndo.co", "https://komikindo.co", "id", className = "KomikindoCo", overrideVersionCode = 3),
SingleLang("KomikMama", "https://komikmama.co", "id", overrideVersionCode = 1),
+ SingleLang("Komiku.com", "https://komiku.com", "id", className = "KomikuCom"),
+ SingleLang("Kuma Scans (Kuma Translation)", "https://kumascans.com", "en", className = "KumaScans", overrideVersionCode = 1),
SingleLang("Legion Scan", "https://legionscans.com", "es"),
SingleLang("Magus Manga", "https://magusmanga.com", "ar"),
- SingleLang("MangKomik", "https://mangkomik.com", "id"),
+ SingleLang("Manga Pro", "https://mangaprotm.com", "ar", pkgName = "mangaproz", overrideVersionCode = 3),
+ SingleLang("Manga Raw.org", "https://mangaraw.org", "ja", className = "MangaRawOrg", overrideVersionCode = 1),
SingleLang("MangaKita", "https://mangakita.net", "id", overrideVersionCode = 1),
+ SingleLang("Mangakyo", "https://www.mangakyo.me", "id"),
SingleLang("Mangasusu", "https://mangasusu.co.in", "id", isNsfw = true, overrideVersionCode = 1),
+ SingleLang("MangaSwat", "https://swatmanga.co", "ar", overrideVersionCode = 7),
+ SingleLang("MangKomik", "https://mangkomik.com", "id"),
SingleLang("Mangás Chan", "https://mangaschan.com", "pt-BR", className = "MangasChan"),
SingleLang("Manhua Raw", "https://manhuaraw.com", "en"),
SingleLang("ManhwaIndo", "https://manhwaindo.id", "id", isNsfw = true, overrideVersionCode = 2),
+ SingleLang("Manhwax", "https://manhwax.com", "en", isNsfw = true),
+ SingleLang("Mareceh", "https://mareceh.com", "id", isNsfw = true, pkgName = "mangceh", overrideVersionCode = 10),
SingleLang("Martial Manga", "https://martialmanga.com", "es"),
+ SingleLang("MasterKomik", "https://masterkomik.com", "id", overrideVersionCode = 1),
SingleLang("Miau Scan", "https://miauscan.com", "es"),
+ SingleLang("Mihentai", "https://mihentai.com", "all", isNsfw = true, overrideVersionCode = 1),
SingleLang("Mode Scanlator", "https://modescanlator.com", "pt-BR", overrideVersionCode = 8),
SingleLang("Ngomik", "https://ngomik.net", "id", overrideVersionCode = 1),
+ SingleLang("Non-Stop Scans", "https://www.nonstopscans.com", "en", className = "NonStopScans"),
+ SingleLang("NoxSubs", "https://noxsubs.com", "tr"),
+ SingleLang("Omega Scans", "https://omegascans.org", "en", isNsfw = true),
SingleLang("Origami Orpheans", "https://origami-orpheans.com.br", "pt-BR", overrideVersionCode = 9),
SingleLang("Ozul Scans", "https://ozulscans.com", "ar"),
- SingleLang("PMScans", "http://www.rackusreader.org", "en", overrideVersionCode = 2),
SingleLang("Patatescans", "https://patatescans.com", "fr", isNsfw = true, overrideVersionCode = 2),
+ SingleLang("Phantom Scans", "https://phantomscans.com", "en", overrideVersionCode = 1),
+ SingleLang("Phoenix Fansub", "https://phoenixfansub.com", "es", overrideVersionCode = 2),
+ SingleLang("PMScans", "http://www.rackusreader.org", "en", overrideVersionCode = 2),
+ SingleLang("Random Scans", "https://randomscans.xyz", "en"),
+ SingleLang("Rawkuma", "https://rawkuma.com/", "ja"),
+ SingleLang("Readkomik", "https://readkomik.com", "en", className = "ReadKomik", overrideVersionCode = 1),
SingleLang("Realm Scans", "https://realmscans.com", "en", overrideVersionCode = 3),
SingleLang("Sekaikomik", "https://www.sekaikomik.live", "id", isNsfw = true, overrideVersionCode = 9),
SingleLang("Sekaikomik", "https://www.sekaikomik.site", "id", isNsfw = true, overrideVersionCode = 8),
+ SingleLang("Sekte Doujin", "https://sektedoujin.club", "id", isNsfw = true, overrideVersionCode = 3),
+ SingleLang("Sekte Komik", "https://sektekomik.com", "id", overrideVersionCode = 4),
+ SingleLang("Shadow Mangas", "https://shadowmangas.com", "es"),
+ SingleLang("Shea Manga", "https://sheakomik.com", "id", overrideVersionCode = 4),
SingleLang("Shooting Star Scans", "https://shootingstarscans.com", "en", overrideVersionCode = 3),
SingleLang("Silence Scan", "https://silencescan.com.br", "pt-BR", isNsfw = true, overrideVersionCode = 5),
SingleLang("Skull Scans", "https://www.skullscans.com", "en", overrideVersionCode = 1),
- SingleLang("Tsundoku Traduções", "https://tsundoku.com.br", "pt-BR", className = "TsundokuTraducoes", overrideVersionCode = 9),
- SingleLang("TurkToon", "https://turktoon.com", "tr"),
- SingleLang("World Romance Translation", "https://wrt.my.id", "id", overrideVersionCode = 10),
- SingleLang("White Cloud Pavilion (New)", "https://www.whitecloudpavilion.com", "en", pkgName = "whitecloudpavilionnew", className = "WhiteCloudPavilion"),
- SingleLang("ARESManga", "https://aresmanga.com", "ar", pkgName = "iimanga", overrideVersionCode = 2),
+ SingleLang("Snudae Scans", "https://snudaescans.com", "en", isNsfw = true, className = "BatotoScans", overrideVersionCode = 1),
+ SingleLang("Summer Fansub", "https://smmr.in", "pt-BR", isNsfw = true),
SingleLang("Sushi-Scan", "https://sushiscan.su", "fr", className = "SushiScan"),
- SingleLang("Komiku.com", "https://komiku.com", "id", className = "KomikuCom"),
+ SingleLang("Tempest Manga", "https://manga.tempestfansub.com", "tr"),
+ SingleLang("The Apollo Team", "https://theapollo.team", "en"),
+ SingleLang("Tsundoku Traduções", "https://tsundoku.com.br", "pt-BR", className = "TsundokuTraducoes", overrideVersionCode = 9),
+ SingleLang("TukangKomik", "https://tukangkomik.com", "id"),
+ SingleLang("TurkToon", "https://turktoon.com", "tr"),
+ SingleLang("West Manga", "https://westmanga.info", "id", overrideVersionCode = 1),
+ SingleLang("White Cloud Pavilion (New)", "https://www.whitecloudpavilion.com", "en", pkgName = "whitecloudpavilionnew", className = "WhiteCloudPavilion"),
+ SingleLang("World Romance Translation", "https://wrt.my.id", "id", overrideVersionCode = 10),
+ SingleLang("xCaliBR Scans", "https://xcalibrscans.com", "en", overrideVersionCode = 3),
)
companion object {
@JvmStatic
fun main(args: Array) {
- WPMangaReaderGenerator().createAll()
+ MangaThemesiaGenerator().createAll()
}
}
}
diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/wpmangareader/WPMangaReaderUrlActivity.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangathemesia/MangaThemesiaUrlActivity.kt
similarity index 72%
rename from multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/wpmangareader/WPMangaReaderUrlActivity.kt
rename to multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangathemesia/MangaThemesiaUrlActivity.kt
index b897b063c..6999ab7ae 100644
--- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/wpmangareader/WPMangaReaderUrlActivity.kt
+++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangathemesia/MangaThemesiaUrlActivity.kt
@@ -1,4 +1,4 @@
-package eu.kanade.tachiyomi.multisrc.wpmangareader
+package eu.kanade.tachiyomi.multisrc.mangathemesia
import android.app.Activity
import android.content.ActivityNotFoundException
@@ -7,7 +7,7 @@ import android.os.Bundle
import android.util.Log
import kotlin.system.exitProcess
-class WPMangaReaderUrlActivity : Activity() {
+class MangaThemesiaUrlActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -17,16 +17,16 @@ class WPMangaReaderUrlActivity : Activity() {
val mainIntent = Intent().apply {
action = "eu.kanade.tachiyomi.SEARCH"
- putExtra("query", "${WPMangaReader.URL_SEARCH_PREFIX}${intent?.data?.toString()}")
+ putExtra("query", "${MangaThemesia.URL_SEARCH_PREFIX}${intent?.data?.toString()}")
putExtra("filter", packageName)
}
try {
startActivity(mainIntent)
} catch (e: ActivityNotFoundException) {
- Log.e("WPMangaReaderUrl", e.toString())
+ Log.e("MangaThemesiaUrlActivity", e.toString())
}
} else {
- Log.e("WPMangaReaderUrl", "could not parse uri from intent $intent")
+ Log.e("MangaThemesiaUrlActivity", "Could not parse uri from intent $intent")
}
finish()
diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangathemesia/README.md b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangathemesia/README.md
new file mode 100644
index 000000000..37dfb2e07
--- /dev/null
+++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangathemesia/README.md
@@ -0,0 +1 @@
+MangaThemesia is WPMangaReader and WPMangaStream merged together as they both had similar code. Both theme was made by [Themesia Studios](https://themesia.com)
\ No newline at end of file
diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/wpmangareader/WPMangaReader.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/wpmangareader/WPMangaReader.kt
deleted file mode 100644
index f20ab2bef..000000000
--- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/wpmangareader/WPMangaReader.kt
+++ /dev/null
@@ -1,431 +0,0 @@
-package eu.kanade.tachiyomi.multisrc.wpmangareader
-
-import eu.kanade.tachiyomi.network.GET
-import eu.kanade.tachiyomi.network.POST
-import eu.kanade.tachiyomi.network.asObservableSuccess
-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 kotlinx.serialization.json.Json
-import kotlinx.serialization.json.jsonArray
-import kotlinx.serialization.json.jsonPrimitive
-import okhttp3.FormBody
-import okhttp3.HttpUrl
-import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
-import okhttp3.OkHttpClient
-import okhttp3.Request
-import okhttp3.Response
-import org.jsoup.nodes.Document
-import org.jsoup.nodes.Element
-import rx.Observable
-import rx.Single
-import uy.kohesive.injekt.injectLazy
-import java.text.SimpleDateFormat
-import java.util.Locale
-
-abstract class WPMangaReader(
- override val name: String,
- override val baseUrl: String,
- override val lang: String,
- val mangaUrlDirectory: String = "/manga",
- private val dateFormat: SimpleDateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale.US)
-) : ParsedHttpSource() {
-
- override val supportsLatest = true
-
- override val client: OkHttpClient = network.cloudflareClient
-
- protected val json: Json by injectLazy()
-
- // popular
- override fun popularMangaRequest(page: Int) = searchMangaRequest(page, "", FilterList(OrderByFilter(5)))
- override fun popularMangaParse(response: Response) = searchMangaParse(response)
-
- override fun popularMangaFromElement(element: Element) = throw UnsupportedOperationException("Not used")
- override fun popularMangaSelector() = throw UnsupportedOperationException("Not used")
- override fun popularMangaNextPageSelector() = throw UnsupportedOperationException("Not used")
-
- // latest
- override fun latestUpdatesRequest(page: Int) = searchMangaRequest(page, "", FilterList(OrderByFilter(3)))
- override fun latestUpdatesParse(response: Response) = searchMangaParse(response)
-
- override fun latestUpdatesSelector() = throw UnsupportedOperationException("Not used")
- override fun latestUpdatesFromElement(element: Element) = throw UnsupportedOperationException("Not used")
- override fun latestUpdatesNextPageSelector() = throw UnsupportedOperationException("Not used")
-
- // search
- override fun searchMangaSelector() = ".utao .uta .imgu, .listupd .bs .bsx, .listo .bs .bsx"
-
- /**
- * Given some string which represents an http url, returns a identifier (id) for a manga
- * which can be used to fetch its details at "$baseUrl$mangaUrlDirectory/$id"
- *
- * @param s: String - url
- *
- * @returns An identifier for a manga, or null if none could be found
- */
- protected open fun mangaIdFromUrl(s: String): Single {
- val baseMangaUrl = "$baseUrl$mangaUrlDirectory".toHttpUrlOrNull()!!
- return s.toHttpUrlOrNull()?.let { url ->
- fun pathLengthIs(url: HttpUrl, n: Int, strict: Boolean = false) = url.pathSegments.size == n && url.pathSegments[n - 1].isNotEmpty() || (!strict && url.pathSegments.size == n + 1 && url.pathSegments[n].isEmpty())
- val isMangaUrl = listOf(
- baseMangaUrl.host == url.host,
- pathLengthIs(url, 2),
- url.pathSegments[0] == baseMangaUrl.pathSegments[0]
- ).all { it }
- val potentiallyChapterUrl = pathLengthIs(url, 1)
- if (isMangaUrl)
- Single.just(url.pathSegments[1])
- else if (potentiallyChapterUrl)
- client.newCall(GET(s, headers)).asObservableSuccess().map {
- val links = it.asJsoup().select("a[itemprop=item]")
- if (links.size == 3) // near the top of page: home > manga > current chapter
- links[1].attr("href").toHttpUrlOrNull()?.pathSegments?.get(1)
- else
- null
- }.toSingle()
- else
- Single.just(null)
- } ?: Single.just(null)
- }
-
- override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable {
- if (!query.startsWith(URL_SEARCH_PREFIX))
- return super.fetchSearchManga(page, query, filters)
-
- return mangaIdFromUrl(query.substringAfter(URL_SEARCH_PREFIX))
- .toObservable()
- .concatMap { id ->
- if (id == null)
- Observable.just(MangasPage(emptyList(), false))
- else
- fetchMangaDetails(SManga.create().apply { this.url = "$mangaUrlDirectory/$id" })
- .map {
- it.url = "$mangaUrlDirectory/$id" // isn't set in returned manga
- MangasPage(listOf(it), false)
- }
- }
- }
-
- override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
- var url = "$baseUrl".toHttpUrlOrNull()!!.newBuilder()
- if (query.isNotEmpty()) {
- url.addPathSegments("page/$page").addQueryParameter("s", query)
- } else {
- url.addPathSegment(mangaUrlDirectory.substring(1)).addQueryParameter("page", "$page")
- filters.forEach { filter ->
- when (filter) {
- is UrlEncoded -> filter.encode(url)
- // if site has project page, default value "hasProjectPage" = false
- is ProjectFilter -> {
- if (filter.toUriPart() == "project-filter-on") {
- url = "$baseUrl$projectPageString/page/$page".toHttpUrlOrNull()!!.newBuilder()
- }
- }
- }
- }
- }
- return GET("$url")
- }
-
- open val projectPageString = "/project"
-
- override fun searchMangaParse(response: Response): MangasPage {
- if (genrelist == null)
- genrelist = parseGenres(response.asJsoup(response.peekBody(Long.MAX_VALUE).string()))
- return super.searchMangaParse(response)
- }
-
- private fun parseGenres(document: Document): List? {
- return document.selectFirst("ul.c4")?.select("li")?.map { li ->
- LabeledValue(li.selectFirst("label").text(), li.selectFirst("input[type=checkbox]").`val`())
- }
- }
-
- override fun searchMangaFromElement(element: Element) = SManga.create().apply {
- thumbnail_url = element.select("img").attr("abs:src")
- title = element.select("a").attr("title")
- setUrlWithoutDomain(element.select("a").attr("href"))
- }
-
- override fun searchMangaNextPageSelector() = "div.pagination .next, div.hpage .r"
-
- // manga details
- override fun mangaDetailsParse(document: Document) = SManga.create().apply {
- author = document.select(seriesAuthorSelector).firstOrNull()?.ownText()
- artist = document.select(seriesArtistSelector).firstOrNull()?.ownText()
- genre = document.select(seriesGenreSelector).joinToString { it.text() }
- status = parseStatus(document.select(seriesStatusSelector).text())
- title = document.selectFirst(seriesTitleSelector).text()
- thumbnail_url = document.select(seriesThumbnailSelector).attr("abs:src")
- description = document.select(seriesDescriptionSelector).joinToString("\n") { it.text() }
-
- // add series type(manga/manhwa/manhua/other) thinggy to genre
- document.select(seriesTypeSelector).firstOrNull()?.ownText()?.let {
- if (it.isEmpty().not() && genre!!.contains(it, true).not()) {
- genre += if (genre!!.isEmpty()) it else ", $it"
- }
- }
-
- // add alternative name to manga description
- document.select(altNameSelector).firstOrNull()?.ownText()?.let {
- if (it.isEmpty().not()) {
- description += when {
- description!!.isEmpty() -> altName + it
- else -> "\n\n$altName" + it
- }
- }
- }
- }
-
- open val seriesAuthorSelector = ".listinfo li:contains(Author), .tsinfo .imptdt:nth-child(4) i, .infotable tr:contains(author) td:last-child"
- open val seriesArtistSelector = ".infotable tr:contains(artist) td:last-child, .tsinfo .imptdt:contains(artist) i"
- open val seriesGenreSelector = "div.gnr a, .mgen a, .seriestugenre a"
- open val seriesStatusSelector = "div.listinfo li:contains(Status), .tsinfo .imptdt:contains(status), .tsinfo .imptdt:contains(الحالة), .infotable tr:contains(status) td"
- open val seriesTitleSelector = "h1.entry-title"
- open val seriesThumbnailSelector = ".infomanga > div[itemprop=image] img, .thumb img"
- open val seriesDescriptionSelector = ".desc, .entry-content[itemprop=description]"
- open val seriesTypeSelector = "span:contains(Type) a, .imptdt:contains(Type) :last-child, a[href*=type\\=], .infotable tr:contains(Type) td:last-child"
- open val altNameSelector = ".alternative, .seriestualt"
- open val altName = "Alternative Name" + ": "
-
- open fun parseStatus(status: String) = when {
- status.contains("Ongoing") -> SManga.ONGOING
- status.contains("Completed") -> SManga.COMPLETED
- else -> SManga.UNKNOWN
- }
-
- // chapters
- override fun chapterListSelector() = "div.bxcl li, #chapterlist li .eph-num a"
-
- override fun chapterListParse(response: Response): List {
- val document = response.asJsoup()
- val chapters = document.select(chapterListSelector()).map { chapterFromElement(it) }
-
- // Add timestamp to latest chapter, taken from "Updated On". so source which not provide chapter timestamp will have atleast one
- val date = document.select(".listinfo time[itemprop=dateModified]").attr("datetime")
- val checkChapter = document.select(chapterListSelector()).firstOrNull()
- if (date != "" && checkChapter != null) chapters[0].date_upload = parseDate(date)
-
- countViews(document)
-
- return chapters
- }
-
- private fun parseChapterDate(date: String): Long {
- return try {
- dateFormat.parse(date)?.time ?: 0
- } catch (_: Exception) {
- 0L
- }
- }
-
- private fun parseDate(date: String): Long {
- return SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH).parse(date)?.time ?: 0L
- }
-
- override fun chapterFromElement(element: Element) = SChapter.create().apply {
- setUrlWithoutDomain(element.select("a").attr("href").substringAfter(baseUrl))
- name = element.select(".lch a, .chapternum").text()
- date_upload = element.select(".chapterdate").firstOrNull()?.text()?.let { parseChapterDate(it) } ?: 0
- }
-
- // pages
- open val pageSelector = "div#readerarea img"
-
- override fun pageListParse(document: Document): List {
- val pages = mutableListOf()
- document.select(pageSelector)
- .filterNot { it.attr("abs:src").isNullOrEmpty() }
- .mapIndexed { i, img -> pages.add(Page(i, "", img.attr("abs:src"))) }
-
- countViews(document)
-
- // Some sites like Mangakita, MangKomik now load pages via javascript
- if (pages.isNotEmpty()) { return pages }
-
- val docString = document.toString()
- val imageListRegex = Regex("\\\"images\\\"\\s*:\\s*(\\[.*?\\])")
- val imageListJson = imageListRegex.find(docString)!!.destructured.toList()[0]
-
- val imageList = json.parseToJsonElement(imageListJson).jsonArray
-
- pages += imageList.mapIndexed { i, jsonEl ->
- Page(i, "", jsonEl.jsonPrimitive.content)
- }
-
- return pages
- }
-
- override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not Used")
-
- /**
- * Set it to false if you want to disable the extension reporting the view count
- * back to the source website through admin-ajax.php.
- */
- protected open val sendViewCount: Boolean = true
-
- protected open fun countViewsRequest(document: Document): Request? {
- val wpMangaData = document.select("script:containsData(dynamic_view_ajax)").firstOrNull()
- ?.data() ?: return null
-
- val postId = CHAPTER_PAGE_ID_REGEX.find(wpMangaData)?.groupValues?.get(1)
- ?: MANGA_PAGE_ID_REGEX.find(wpMangaData)?.groupValues?.get(1)
- ?: return null
-
- val formBody = FormBody.Builder()
- .add("action", "dynamic_view_ajax")
- .add("post_id", postId)
- .build()
-
- val newHeaders = headersBuilder()
- .set("Content-Length", formBody.contentLength().toString())
- .set("Content-Type", formBody.contentType().toString())
- .set("Referer", document.location())
- .build()
-
- return POST("$baseUrl/wp-admin/admin-ajax.php", newHeaders, formBody)
- }
-
- /**
- * Send the view count request to the Madara endpoint.
- *
- * @param document The response document with the wp-manga data
- */
- protected open fun countViews(document: Document) {
- if (!sendViewCount) {
- return
- }
-
- val request = countViewsRequest(document) ?: return
- runCatching { client.newCall(request).execute().close() }
- }
-
- private interface UrlEncoded {
- fun encode(url: HttpUrl.Builder)
- }
-
- // essentially a named pair
- protected class LabeledValue(val displayname: String, val _value: String?) {
- val value: String get() = _value ?: displayname
- override fun toString(): String = displayname
- }
-
- private open class Select(header: String, values: Array, state: Int = 0) : Filter.Select(header, values, state) {
- val selected: T
- get() = this.values[this.state]
- }
-
- private open class MultiSelect(header: String, val elems: List) :
- Filter.Group(header, elems.map { object : Filter.CheckBox("$it") {} }) {
- val selected: Sequence
- get() = this.elems.asSequence().filterIndexed { i, _ -> this.state[i].state }
- }
-
- open val hasProjectPage = false
-
- // filters
- override fun getFilterList(): FilterList {
- val filters = mutableListOf>(
- Filter.Header("NOTE: Ignored if using text search!"),
- GenreFilter(),
- StatusFilter(),
- TypesFilter(),
- OrderByFilter(),
- )
- if (hasProjectPage) {
- filters.addAll(
- mutableListOf>(
- Filter.Separator(),
- Filter.Header("NOTE: cant be used with other filter!"),
- Filter.Header("$name Project List page"),
- ProjectFilter(),
- )
- )
- }
- return FilterList(filters)
- }
-
- protected class ProjectFilter : UriPartFilter(
- "Filter Project",
- arrayOf(
- Pair("Show all manga", ""),
- Pair("Show only project manga", "project-filter-on")
- )
- )
-
- open class UriPartFilter(displayName: String, private val vals: Array>) :
- Filter.Select(displayName, vals.map { it.first }.toTypedArray()) {
- fun toUriPart() = vals[state].second
- }
-
- private fun GenreFilter() = object : MultiSelect("Genre", getGenreList()), UrlEncoded {
- override fun encode(url: HttpUrl.Builder) {
- selected.forEach { url.addQueryParameter("genre[]", it.value) }
- }
- }
-
- private fun StatusFilter() = object : Select("Status", getPublicationStatus()), UrlEncoded {
- override fun encode(url: HttpUrl.Builder) {
- url.addQueryParameter("status", selected.value)
- }
- }
-
- private fun TypesFilter() = object : Select("Type", getContentType()), UrlEncoded {
- override fun encode(url: HttpUrl.Builder) {
- url.addQueryParameter("type", selected.value)
- }
- }
-
- private fun OrderByFilter(state: Int = 0) = object : Select("Order By", getOrderBy(), state), UrlEncoded {
- override fun encode(url: HttpUrl.Builder) {
- url.addQueryParameter("order", selected.value)
- }
- }
-
- // overridable
- // some sources have numeric values for filters
- private var genrelist: List? = null
- protected open fun getGenreList(): List {
- // Filters are fetched immediately once an extension loads
- // We're only able to get filters after a loading the manga directory, and resetting
- // the filters is the only thing that seems to reinflate the view
- return genrelist ?: listOf(LabeledValue("Press reset to attempt to fetch genres", ""))
- }
-
- private fun getPublicationStatus() = arrayOf(
- LabeledValue("All", ""),
- LabeledValue("Ongoing", "ongoing"),
- LabeledValue("Completed", "completed"),
- LabeledValue("Hiatus", "hiatus")
- )
-
- private fun getContentType() = arrayOf(
- LabeledValue("All", ""),
- LabeledValue("Manga", "manga"),
- LabeledValue("Manhwa", "manhwa"),
- LabeledValue("Manhua", "manhua"),
- LabeledValue("Comic", "comic")
- )
-
- private fun getOrderBy() = arrayOf(
- LabeledValue("Default", ""),
- LabeledValue("A-Z", "title"),
- LabeledValue("Z-A", "titlereverse"),
- LabeledValue("Update", "update"),
- LabeledValue("Added", "latest"),
- LabeledValue("Popular", "popular")
- )
-
- companion object {
- const val URL_SEARCH_PREFIX = "url:"
-
- private val MANGA_PAGE_ID_REGEX = "post_id\\s*:\\s*(\\d+)\\}".toRegex()
- private val CHAPTER_PAGE_ID_REGEX = "post_id\\s*=\\s*(\\d+);?".toRegex()
- }
-}
diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/wpmangastream/WPMangaStream.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/wpmangastream/WPMangaStream.kt
deleted file mode 100644
index b302688a0..000000000
--- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/wpmangastream/WPMangaStream.kt
+++ /dev/null
@@ -1,607 +0,0 @@
-package eu.kanade.tachiyomi.multisrc.wpmangastream
-
-import android.app.Application
-import android.content.SharedPreferences
-import eu.kanade.tachiyomi.network.GET
-import eu.kanade.tachiyomi.network.POST
-import eu.kanade.tachiyomi.network.asObservableSuccess
-import eu.kanade.tachiyomi.source.ConfigurableSource
-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 kotlinx.serialization.json.Json
-import kotlinx.serialization.json.jsonArray
-import kotlinx.serialization.json.jsonPrimitive
-import okhttp3.FormBody
-import okhttp3.Headers
-import okhttp3.HttpUrl
-import okhttp3.HttpUrl.Companion.toHttpUrl
-import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
-import okhttp3.OkHttpClient
-import okhttp3.Request
-import okhttp3.Response
-import org.jsoup.nodes.Document
-import org.jsoup.nodes.Element
-import org.jsoup.select.Elements
-import rx.Observable
-import rx.Single
-import uy.kohesive.injekt.Injekt
-import uy.kohesive.injekt.api.get
-import uy.kohesive.injekt.injectLazy
-import java.text.SimpleDateFormat
-import java.util.Calendar
-import java.util.Locale
-import java.util.concurrent.TimeUnit
-
-abstract class WPMangaStream(
- override val name: String,
- override val baseUrl: String,
- override val lang: String,
- private val dateFormat: SimpleDateFormat = SimpleDateFormat("MMM d, yyyy", Locale.US)
-) : ConfigurableSource, ParsedHttpSource() {
- override val supportsLatest = true
-
- private val preferences: SharedPreferences by lazy {
- Injekt.get().getSharedPreferences("source_$id", 0x0000)
- }
-
- override fun setupPreferenceScreen(screen: androidx.preference.PreferenceScreen) {
- val thumbsPref = androidx.preference.ListPreference(screen.context).apply {
- key = SHOW_THUMBNAIL_PREF_Title
- title = SHOW_THUMBNAIL_PREF_Title
- entries = arrayOf("Show high quality", "Show mid quality", "Show low quality")
- entryValues = arrayOf("0", "1", "2")
- summary = "%s"
-
- setOnPreferenceChangeListener { _, newValue ->
- val selected = newValue as String
- val index = this.findIndexOfValue(selected)
- preferences.edit().putInt(SHOW_THUMBNAIL_PREF, index).commit()
- }
- }
- screen.addPreference(thumbsPref)
- }
-
- private fun getShowThumbnail(): Int = preferences.getInt(SHOW_THUMBNAIL_PREF, 0)
-
- override val client: OkHttpClient = network.cloudflareClient.newBuilder()
- .connectTimeout(10, TimeUnit.SECONDS)
- .readTimeout(30, TimeUnit.SECONDS)
- .build()
-
- protected fun Element.imgAttr(): String = if (this.hasAttr("data-src")) this.attr("abs:data-src") else this.attr("abs:src")
- protected fun Elements.imgAttr(): String = this.first().imgAttr()
-
- private val json: Json by injectLazy()
-
- 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)
- }
-
- /**
- * Given some string which represents an http url, returns the URI path to the corresponding series
- * if the original pointed to either a series or a chapter
- *
- * @param s: String - url
- *
- * @returns URI path or null
- */
- protected open fun mangaPathFromUrl(s: String): Single {
- val baseMangaUrl = baseUrl.toHttpUrlOrNull()!!
- // Would be dope if wpmangastream had a mangaUrlDirectory like wpmangareader
- val mangaDirectories = listOf("manga", "comics", "komik")
- return s.toHttpUrlOrNull()?.let { url ->
- fun pathLengthIs(url: HttpUrl, n: Int, strict: Boolean = false) = url.pathSegments.size == n && url.pathSegments[n - 1].isNotEmpty() || (!strict && url.pathSegments.size == n + 1 && url.pathSegments[n].isEmpty())
- val potentiallyChapterUrl = pathLengthIs(url, 1)
- val isMangaUrl = listOf(
- baseMangaUrl.topPrivateDomain() == url.topPrivateDomain(),
- pathLengthIs(url, 2),
- url.pathSegments[0] in mangaDirectories
- ).all { it }
- if (isMangaUrl)
- Single.just(url.encodedPath)
- else if (potentiallyChapterUrl)
- client.newCall(GET(s, headers)).asObservableSuccess().map {
- val links = it.asJsoup().select("a[itemprop=item]")
- if (links.size == 3) // near the top of page: home > manga > current chapter
- links[1].attr("href").toHttpUrlOrNull()?.encodedPath
- else
- null
- }.toSingle()
- else
- Single.just(null)
- } ?: Single.just(null)
- }
-
- override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable {
- if (!query.startsWith(URL_SEARCH_PREFIX))
- return super.fetchSearchManga(page, query, filters)
-
- return mangaPathFromUrl(query.substringAfter(URL_SEARCH_PREFIX))
- .toObservable()
- .concatMap { path ->
- if (path == null)
- Observable.just(MangasPage(emptyList(), false))
- else
- fetchMangaDetails(SManga.create().apply { this.url = path })
- .map {
- it.url = path // isn't set in returned manga
- MangasPage(listOf(it), false)
- }
- }
- }
-
- override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
- var url = "$baseUrl/manga/".toHttpUrlOrNull()!!.newBuilder()
- url.addQueryParameter("title", query)
- url.addQueryParameter("page", page.toString())
- filters.forEach { filter ->
- when (filter) {
- is AuthorFilter -> {
- url.addQueryParameter("author", filter.state)
- }
- is YearFilter -> {
- url.addQueryParameter("yearx", filter.state)
- }
- 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) }
- }
- // if site has project page, default value "hasProjectPage" = false
- is ProjectFilter -> {
- if (filter.toUriPart() == "project-filter-on") {
- url = "$baseUrl$projectPageString/page/$page".toHttpUrlOrNull()!!.newBuilder()
- }
- }
- }
- }
- return GET(url.build().toString(), headers)
- }
-
- open val projectPageString = "/project"
-
- override fun popularMangaSelector() = "div.bs"
- override fun latestUpdatesSelector() = popularMangaSelector()
- override fun searchMangaSelector() = popularMangaSelector()
-
- override fun popularMangaFromElement(element: Element): SManga {
- val manga = SManga.create()
- manga.thumbnail_url = element.select("div.limit img").imgAttr()
- element.select("div.bsx > a").first().let {
- manga.setUrlWithoutDomain(it.attr("href"))
- manga.title = it.attr("title")
- }
- return manga
- }
-
- override fun searchMangaFromElement(element: Element): SManga = popularMangaFromElement(element)
- override fun latestUpdatesFromElement(element: Element): SManga = popularMangaFromElement(element)
-
- override fun popularMangaNextPageSelector(): String? = "a.next.page-numbers, a.r"
- override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector()
- override fun searchMangaNextPageSelector() = popularMangaNextPageSelector()
-
- 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(mangaDetailsSelectorStatus).firstOrNull()?.ownText())
- author = isEmptyPlaceholder(infoElement.select(mangaDetailsSelectorAuthor).firstOrNull()?.ownText())
- artist = isEmptyPlaceholder(infoElement.select(mangaDetailsSelectorArtist).firstOrNull()?.ownText())
- description = infoElement.select(mangaDetailsSelectorDescription).joinToString("\n") { it.text() }
- thumbnail_url = infoElement.select(mangaDetailsSelectorThumbnail).imgAttr()
-
- val genres = infoElement.select(mangaDetailsSelectorGenre)
- .map { element -> element.text().lowercase() }
- .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.lowercase())
- }
- }
-
- genre = genres.toList().map { it.capitalize() }.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
- }
- }
- }
- }
- }
- }
- // Manga Details Selector
- open val mangaDetailsSelectorAuthor = "span:contains(Author:), span:contains(Pengarang:), .fmed b:contains(Author)+span, .imptdt:contains(Author) i"
- open val mangaDetailsSelectorArtist = ".fmed b:contains(Artist)+span, .imptdt:contains(Artist) i"
- open val mangaDetailsSelectorStatus = "span:contains(Status:), .imptdt:contains(Status) i"
- open val mangaDetailsSelectorDescription = "div.desc p, div.entry-content p"
- open val mangaDetailsSelectorThumbnail = "div.thumb img"
- open val mangaDetailsSelectorGenre = "span:contains(Genre) a, .mgen a"
-
- open val seriesTypeSelector = "span:contains(Type) a, .imptdt:contains(Type) a, a[href*=type\\=], .infotable tr:contains(Type) td:last-child"
- open val altNameSelector = ".alternative, .wd-full:contains(Alt) span, .alter, .seriestualt"
- open val altName = "Alternative Name" + ": "
-
- protected open fun parseStatus(element: String?): Int = when {
- element == null -> SManga.UNKNOWN
- listOf("ongoing", "publishing").any { it.contains(element, ignoreCase = true) } -> SManga.ONGOING
- listOf("completed").any { it.contains(element, ignoreCase = true) } -> SManga.COMPLETED
- else -> SManga.UNKNOWN
- }
-
- private fun isEmptyPlaceholder(string: String?): String? {
- return if (string == "-" || string == "N/A") "" else string
- }
-
- override fun chapterListSelector() = "div.bxcl ul li, div.cl ul li, ul li:has(div.chbox):has(div.eph-num)"
-
- override fun chapterListParse(response: Response): List {
- val document = response.asJsoup()
- val chapters = document.select(chapterListSelector()).map { chapterFromElement(it) }
-
- // Add timestamp to latest chapter, taken from "Updated On". so source which not provide chapter timestamp will have atleast one
- val date = document.select(".fmed:contains(update) time ,span:contains(update) time").attr("datetime")
- val checkChapter = document.select(chapterListSelector()).firstOrNull()
- if (date != "" && checkChapter != null) chapters[0].date_upload = parseDate(date)
-
- countViews(document)
-
- return chapters
- }
-
- private fun parseDate(date: String): Long {
- return SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH).parse(date)?.time ?: 0L
- }
-
- override fun chapterFromElement(element: Element): SChapter {
- val urlElement = element.select(".lchx > a, span.leftoff a, div.eph-num > a").first()
- val chapter = SChapter.create()
- chapter.setUrlWithoutDomain(urlElement.attr("href"))
- chapter.name = if (urlElement.select("span.chapternum").isNotEmpty()) urlElement.select("span.chapternum").text() else urlElement.text()
- chapter.date_upload = element.select("span.rightoff, time, span.chapterdate").firstOrNull()?.text()?.let { parseChapterDate(it) }
- ?: 0
- return chapter
- }
-
- fun parseChapterDate(date: String): Long {
- return if (date.endsWith("ago")) {
- val value = date.split(' ')[0].toInt()
- when {
- "min" in date -> Calendar.getInstance().apply {
- add(Calendar.MINUTE, value * -1)
- }.timeInMillis
- "hour" in date -> Calendar.getInstance().apply {
- add(Calendar.HOUR_OF_DAY, value * -1)
- }.timeInMillis
- "day" in date -> Calendar.getInstance().apply {
- add(Calendar.DATE, value * -1)
- }.timeInMillis
- "week" in date -> Calendar.getInstance().apply {
- add(Calendar.DATE, value * 7 * -1)
- }.timeInMillis
- "month" in date -> Calendar.getInstance().apply {
- add(Calendar.MONTH, value * -1)
- }.timeInMillis
- "year" in date -> Calendar.getInstance().apply {
- add(Calendar.YEAR, value * -1)
- }.timeInMillis
- else -> {
- 0L
- }
- }
- } else {
- try {
- dateFormat.parse(date)?.time ?: 0
- } catch (_: Exception) {
- 0L
- }
- }
- }
-
- override fun prepareNewChapter(chapter: SChapter, manga: SManga) {
- val basic = Regex("""Chapter\s([0-9]+)""")
- when {
- basic.containsMatchIn(chapter.name) -> {
- basic.find(chapter.name)?.let {
- chapter.chapter_number = it.groups[1]?.value!!.toFloat()
- }
- }
- }
- }
-
- open val pageSelector = "div#readerarea img"
-
- override fun pageListParse(document: Document): List {
- val htmlPages = document.select(pageSelector)
- .filterNot { it.attr("abs:src").isNullOrEmpty() }
- .mapIndexed { i, img -> Page(i, "", img.attr("abs:src")) }
- .toMutableList()
-
- val docString = document.toString()
- val imageListRegex = Regex("\\\"images.*?:.*?(\\[.*?\\])")
- val imageListJson = imageListRegex.find(docString)!!.destructured.toList()[0]
-
- 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 }
- }
-
- override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not used")
-
- override fun imageRequest(page: Page): Request {
- val headers = Headers.Builder()
- headers.apply {
- add("Accept", "image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8")
- add("Referer", baseUrl)
- add("User-Agent", "Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; LGMS323 Build/KOT49I.MS32310c) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/76.0.3809.100 Mobile Safari/537.36")
- }
-
- if (page.imageUrl!!.contains(".wp.com")) {
- headers.apply {
- set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3")
- }
- }
-
- return GET(getImageUrl(page.imageUrl!!, getShowThumbnail()), headers.build())
- }
-
- private fun getImageUrl(originalUrl: String, quality: Int): String {
- val url = originalUrl.substringAfter("//")
- return when (quality) {
- LOW_QUALITY -> "https://images.weserv.nl/?w=300&q=70&url=$url"
- MID_QUALITY -> "https://images.weserv.nl/?w=600&q=70&url=$url"
- else -> originalUrl
- }
- }
-
- /**
- * Set it to false if you want to disable the extension reporting the view count
- * back to the source website through admin-ajax.php.
- */
- protected open val sendViewCount: Boolean = true
-
- protected open fun countViewsRequest(document: Document): Request? {
- val wpMangaData = document.select("script:containsData(dynamic_view_ajax)").firstOrNull()
- ?.data() ?: return null
-
- val postId = CHAPTER_PAGE_ID_REGEX.find(wpMangaData)?.groupValues?.get(1)
- ?: MANGA_PAGE_ID_REGEX.find(wpMangaData)?.groupValues?.get(1)
- ?: return null
-
- val formBody = FormBody.Builder()
- .add("action", "dynamic_view_ajax")
- .add("post_id", postId)
- .build()
-
- val newHeaders = headersBuilder()
- .set("Content-Length", formBody.contentLength().toString())
- .set("Content-Type", formBody.contentType().toString())
- .set("Referer", document.location())
- .build()
-
- return POST("$baseUrl/wp-admin/admin-ajax.php", newHeaders, formBody)
- }
-
- /**
- * Send the view count request to the Madara endpoint.
- *
- * @param document The response document with the wp-manga data
- */
- protected open fun countViews(document: Document) {
- if (!sendViewCount) {
- return
- }
-
- val request = countViewsRequest(document) ?: return
- runCatching { client.newCall(request).execute().close() }
- }
-
- private class AuthorFilter : Filter.Text("Author")
-
- private class YearFilter : Filter.Text("Year")
-
- protected class TypeFilter : UriPartFilter(
- "Type",
- arrayOf(
- Pair("Default", ""),
- Pair("Manga", "Manga"),
- Pair("Manhwa", "Manhwa"),
- Pair("Manhua", "Manhua"),
- Pair("Comic", "Comic")
- )
- )
-
- protected class SortByFilter : UriPartFilter(
- "Sort By",
- arrayOf(
- Pair("Default", ""),
- Pair("A-Z", "title"),
- Pair("Z-A", "titlereverse"),
- Pair("Latest Update", "update"),
- Pair("Latest Added", "latest"),
- Pair("Popular", "popular")
- )
- )
-
- protected class StatusFilter : UriPartFilter(
- "Status",
- arrayOf(
- Pair("All", ""),
- Pair("Ongoing", "ongoing"),
- Pair("Completed", "completed"),
- Pair("Hiatus", "hiatus"),
- Pair("Dropped", "dropped")
- )
- )
-
- protected class ProjectFilter : UriPartFilter(
- "Filter Project",
- arrayOf(
- Pair("Show all manga", ""),
- Pair("Show only project manga", "project-filter-on")
- )
- )
-
- protected class Genre(name: String, val id: String = name) : Filter.TriState(name)
- protected class GenreListFilter(genres: List) : Filter.Group("Genre", genres)
-
- open val hasProjectPage = false
-
- override fun getFilterList(): FilterList {
- val filters = mutableListOf>(
- Filter.Header("NOTE: Ignored if using text search!"),
- Filter.Header("Genre exclusion not available for all sources"),
- Filter.Separator(),
- AuthorFilter(),
- YearFilter(),
- StatusFilter(),
- TypeFilter(),
- SortByFilter(),
- GenreListFilter(getGenreList()),
- )
- if (hasProjectPage) {
- filters.addAll(
- mutableListOf>(
- Filter.Separator(),
- Filter.Header("NOTE: cant be used with other filter!"),
- Filter.Header("$name Project List page"),
- ProjectFilter(),
- )
- )
- }
- return FilterList(filters)
- }
-
- protected open fun getGenreList(): List = listOf(
- Genre("4 Koma", "4-koma"),
- Genre("Action", "action"),
- Genre("Adult", "adult"),
- Genre("Adventure", "adventure"),
- Genre("Comedy", "comedy"),
- Genre("Completed", "completed"),
- Genre("Cooking", "cooking"),
- Genre("Crime", "crime"),
- Genre("Cultivation", "cultivation"),
- Genre("Demon", "demon"),
- Genre("Demons", "demons"),
- Genre("Doujinshi", "doujinshi"),
- Genre("Drama", "drama"),
- Genre("Dungeons", "dungeons"),
- Genre("Ecchi", "ecchi"),
- Genre("Fantasy", "fantasy"),
- Genre("Game", "game"),
- Genre("Games", "games"),
- Genre("Gender Bender", "gender-bender"),
- Genre("Genius", "genius"),
- Genre("Gore", "gore"),
- Genre("Harem", "harem"),
- Genre("Hero", "hero"),
- Genre("Historical", "historical"),
- Genre("Horror", "horror"),
- Genre("Isekai", "isekai"),
- Genre("Josei", "josei"),
- Genre("Magic", "magic"),
- Genre("Manga", "manga"),
- Genre("Manhua", "manhua"),
- Genre("Manhwa", "manhwa"),
- Genre("Martial Art", "martial-art"),
- Genre("Martial Arts", "martial-arts"),
- Genre("Mature", "mature"),
- Genre("Mecha", "mecha"),
- Genre("Military", "military"),
- Genre("Monster", "monster"),
- Genre("Monster Girls", "monster-girls"),
- Genre("Monsters", "monsters"),
- Genre("Music", "music"),
- Genre("Murim", "murim"),
- Genre("Mystery", "mystery"),
- Genre("One-shot", "one-shot"),
- Genre("Oneshot", "oneshot"),
- Genre("Overpowered", "overpowered"),
- Genre("Police", "police"),
- Genre("Pshycological", "pshycological"),
- Genre("Psychological", "psychological"),
- Genre("Reincarnation", "reincarnation"),
- Genre("Reverse Harem", "reverse-harem"),
- Genre("Return", "return"),
- Genre("Romancce", "romancce"),
- Genre("Romance", "romance"),
- Genre("Samurai", "samurai"),
- Genre("School", "school"),
- Genre("School Life", "school-life"),
- Genre("Sci-fi", "sci-fi"),
- Genre("Seinen", "seinen"),
- Genre("Shoujo", "shoujo"),
- Genre("Shoujo Ai", "shoujo-ai"),
- Genre("Shounen", "shounen"),
- Genre("Shounen Ai", "shounen-ai"),
- Genre("Slice of Life", "slice-of-life"),
- Genre("Sports", "sports"),
- Genre("Super Power", "super-power"),
- Genre("Supernatural", "supernatural"),
- Genre("Thriller", "thriller"),
- Genre("Time Travel", "time-travel"),
- Genre("Tragedy", "tragedy"),
- Genre("Vampire", "vampire"),
- Genre("Villain", "villain"),
- Genre("Webtoon", "webtoon"),
- Genre("Webtoons", "webtoons"),
- Genre("Yaoi", "yaoi"),
- Genre("Yuri", "yuri"),
- Genre("Zombies", "zombies")
- )
-
- open class UriPartFilter(displayName: String, private val vals: Array>) :
- Filter.Select(displayName, vals.map { it.first }.toTypedArray()) {
- fun toUriPart() = vals[state].second
- }
-
- companion object {
- private const val MID_QUALITY = 1
- private const val LOW_QUALITY = 2
-
- private const val SHOW_THUMBNAIL_PREF_Title = "Default thumbnail quality"
- private const val SHOW_THUMBNAIL_PREF = "showThumbnailDefault"
-
- const val URL_SEARCH_PREFIX = "url:"
-
- private val MANGA_PAGE_ID_REGEX = "post_id\\s*:\\s*(\\d+)\\}".toRegex()
- private val CHAPTER_PAGE_ID_REGEX = "chapter_id\\s*=\\s*(\\d+);?".toRegex()
- }
-}
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
deleted file mode 100644
index d812e4361..000000000
--- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/wpmangastream/WPMangaStreamGenerator.kt
+++ /dev/null
@@ -1,65 +0,0 @@
-package eu.kanade.tachiyomi.multisrc.wpmangastream
-
-import generator.ThemeSourceData.MultiLang
-import generator.ThemeSourceData.SingleLang
-import generator.ThemeSourceGenerator
-
-class WPMangaStreamGenerator : ThemeSourceGenerator {
-
- override val themePkg = "wpmangastream"
-
- override val themeClass = "WPMangaStream"
-
- override val baseVersionCode: Int = 15
-
- override val sources = listOf(
- MultiLang("Asura Scans", "https://www.asurascans.com", listOf("en", "tr"), className = "AsuraScansFactory", pkgName = "asurascans", overrideVersionCode = 16),
- SingleLang("Animated Glitched Scans", "https://anigliscans.com", "en"),
- SingleLang("Boosei", "https://boosei.com", "id", overrideVersionCode = 1),
- SingleLang("GoGoManga", "https://gogomanga.fun", "en", overrideVersionCode = 1),
- SingleLang("Imagine Scan", "https://imaginescan.com.br", "pt-BR", isNsfw = true, overrideVersionCode = 1),
- SingleLang("Imperfect Comics", "https://imperfectcomic.com", "en", overrideVersionCode = 8),
- SingleLang("Infernal Void Scans", "https://void-scans.com", "en", overrideVersionCode = 4),
- SingleLang("Kanzenin", "https://kanzenin.xyz", "id", isNsfw = true),
- SingleLang("KlanKomik", "https://klankomik.com", "id", overrideVersionCode = 1),
- SingleLang("Komik AV", "https://komikav.com", "id", overrideVersionCode = 1),
- SingleLang("Komik Cast", "https://komikcast.me", "id", overrideVersionCode = 12),
- SingleLang("Komik Station", "https://komikstation.co", "id", overrideVersionCode = 3),
- SingleLang("KomikIndo.co", "https://komikindo.co", "id", className = "KomikindoCo", overrideVersionCode = 3),
- SingleLang("Kuma Scans (Kuma Translation)", "https://kumascans.com", "en", className = "KumaScans", overrideVersionCode = 1),
- SingleLang("Manga Pro", "https://mangaprotm.com", "ar", pkgName = "mangaproz", overrideVersionCode = 3),
- SingleLang("Manga Raw.org", "https://mangaraw.org", "ja", className = "MangaRawOrg", overrideVersionCode = 1),
- SingleLang("Manhwax", "https://manhwax.com", "en", isNsfw = true),
- SingleLang("MangaSwat", "https://swatmanga.co", "ar", overrideVersionCode = 7),
- SingleLang("Mangakyo", "https://www.mangakyo.me", "id"),
- SingleLang("Mareceh", "https://mareceh.com", "id", isNsfw = true, pkgName = "mangceh", overrideVersionCode = 10),
- SingleLang("MasterKomik", "https://masterkomik.com", "id", overrideVersionCode = 1),
- SingleLang("Mihentai", "https://mihentai.com", "en", isNsfw = true, overrideVersionCode = 1),
- SingleLang("Non-Stop Scans", "https://www.nonstopscans.com", "en", className = "NonStopScans"),
- SingleLang("NoxSubs", "https://noxsubs.com", "tr"),
- SingleLang("Omega Scans", "https://omegascans.org", "en", isNsfw = true),
- SingleLang("Phantom Scans", "https://phantomscans.com", "en", overrideVersionCode = 1),
- SingleLang("Phoenix Fansub", "https://phoenixfansub.com", "es", overrideVersionCode = 2),
- SingleLang("Random Scans", "https://randomscans.xyz", "en"),
- SingleLang("Rawkuma", "https://rawkuma.com/", "ja"),
- SingleLang("Readkomik", "https://readkomik.com", "en", className = "ReadKomik", overrideVersionCode = 1),
- SingleLang("Sekte Doujin", "https://sektedoujin.club", "id", isNsfw = true, overrideVersionCode = 3),
- SingleLang("Sekte Komik", "https://sektekomik.com", "id", overrideVersionCode = 4),
- SingleLang("Shadow Mangas", "https://shadowmangas.com", "es"),
- SingleLang("Shea Manga", "https://sheakomik.com", "id", overrideVersionCode = 4),
- SingleLang("Snudae Scans", "https://snudaescans.com", "en", isNsfw = true, className = "BatotoScans", overrideVersionCode = 1),
- SingleLang("Summer Fansub", "https://smmr.in", "pt-BR", isNsfw = true),
- SingleLang("Tempest Manga", "https://manga.tempestfansub.com", "tr"),
- SingleLang("The Apollo Team", "https://theapollo.team", "en"),
- SingleLang("TukangKomik", "https://tukangkomik.com", "id"),
- SingleLang("West Manga", "https://westmanga.info", "id", overrideVersionCode = 1),
- SingleLang("xCaliBR Scans", "https://xcalibrscans.com", "en", overrideVersionCode = 3),
- )
-
- companion object {
- @JvmStatic
- fun main(args: Array) {
- WPMangaStreamGenerator().createAll()
- }
- }
-}
diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/wpmangastream/WPMangaStreamUrlActivity.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/wpmangastream/WPMangaStreamUrlActivity.kt
deleted file mode 100644
index 2e145cf09..000000000
--- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/wpmangastream/WPMangaStreamUrlActivity.kt
+++ /dev/null
@@ -1,35 +0,0 @@
-package eu.kanade.tachiyomi.multisrc.wpmangastream
-
-import android.app.Activity
-import android.content.ActivityNotFoundException
-import android.content.Intent
-import android.os.Bundle
-import android.util.Log
-import kotlin.system.exitProcess
-
-class WPMangaStreamUrlActivity : Activity() {
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- val pathSegments = intent?.data?.pathSegments
-
- if (pathSegments != null && pathSegments.size >= 1) {
-
- val mainIntent = Intent().apply {
- action = "eu.kanade.tachiyomi.SEARCH"
- putExtra("query", "${WPMangaStream.URL_SEARCH_PREFIX}${intent?.data?.toString()}")
- putExtra("filter", packageName)
- }
- try {
- startActivity(mainIntent)
- } catch (e: ActivityNotFoundException) {
- Log.e("WPMangaStreamUrl", e.toString())
- }
- } else {
- Log.e("WPMangaStreamUrl", "could not parse uri from intent $intent")
- }
-
- finish()
- exitProcess(0)
- }
-}