Mangathemesia refactor (#1637)

* remove randomua

* i18n

* add other language based selectors

* countviews in background

* small cleanup

* lint

* fix

* bump

* fix genre resetting

* use enqueue instead of coroutinescope

* fix build

* fix build x2

* add back genre missing warning

* Add ES translations

* lint

* Add available language

* lint

I hate lint

* review

- lowercase match for status
- callback on site

* review x2, also fix smol mistake

* lint

:)

* lowercase some translations

Co-authored-by: stevenyomi <95685115+stevenyomi@users.noreply.github.com>

* lowercase some translations

Co-authored-by: stevenyomi <95685115+stevenyomi@users.noreply.github.com>

* remove "人気"

* inline the labels

* lint

thank you lint, very cool

---------

Co-authored-by: bapeey <90949336+bapeey@users.noreply.github.com>
Co-authored-by: stevenyomi <95685115+stevenyomi@users.noreply.github.com>
This commit is contained in:
AwkwardPeak7 2024-03-03 01:30:17 +05:00 committed by GitHub
parent 10e5b90ec3
commit 6850fb0663
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 383 additions and 222 deletions

View File

@ -0,0 +1,30 @@
alt_names_heading=Alternative Names:
author_filter_title=Author
year_filter_title=Year
status_filter_title=Status
status_filter_option_all=All
status_filter_option_ongoing=Ongoing
status_filter_option_completed=Completed
status_filter_option_hiatus=Hiatus
status_filter_option_dropped=Dropped
type_filter_title=Type
type_filter_option_all=All
type_filter_option_manga=Manga
type_filter_option_manhwa=Manhwa
type_filter_option_manhua=Manhua
type_filter_option_comic=Comic
order_by_filter_title=Sort By
order_by_filter_default=Default
order_by_filter_az=A-Z
order_by_filter_za=Z-A
order_by_filter_latest_update=Latest Update
order_by_filter_latest_added=Latest Added
order_by_filter_popular=Popular
project_filter_title=Filter Project
project_filter_all_manga=Show all manga
project_filter_only_project=Show only project manga
genre_filter_title=Genre
genre_missing_warning=Press 'Reset' to attempt to show the genres
genre_exclusion_warning=Genre exclusion is not available for all sources
project_filter_warning=NOTE: Can't be used with other filter!
project_filter_name=%s Project List page

View File

@ -0,0 +1,23 @@
alt_names_heading=Nombres alternativos:
author_filter_title=Autor
year_filter_title=Año
status_filter_title=Estado
status_filter_option_all=Todos
status_filter_option_ongoing=En curso
status_filter_option_completed=Completado
status_filter_option_hiatus=En pausa
status_filter_option_dropped=Abandonado
type_filter_title=Tipo
type_filter_option_all=Todos
order_by_filter_title=Ordenar por
order_by_filter_default=Por defecto
order_by_filter_latest_update=Última actualización
order_by_filter_latest_added=Último añadido
project_filter_title=Filtrar proyectos
project_filter_all_manga=Mostrar todos los mangas
project_filter_only_project=Mostrar solo los proyectos
genre_filter_title=Género
genre_missing_warning=Presione 'Restablecer' para intentar cargar los géneros
genre_exclusion_warning=La exclusión de géneros puede no funcionar correctamente
project_filter_warning=NOTA: ¡No se puede usar con otros filtros!
project_filter_name=%s Página de proyectos

View File

@ -2,8 +2,8 @@ plugins {
id("lib-multisrc")
}
baseVersionCode = 28
baseVersionCode = 29
dependencies {
api(project(":lib:randomua"))
api(project(":lib:i18n"))
}

View File

@ -1,15 +1,8 @@
package eu.kanade.tachiyomi.multisrc.mangathemesia
import android.app.Application
import android.content.SharedPreferences
import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.lib.randomua.addRandomUAPreferenceToScreen
import eu.kanade.tachiyomi.lib.randomua.getPrefCustomUA
import eu.kanade.tachiyomi.lib.randomua.getPrefUAType
import eu.kanade.tachiyomi.lib.randomua.setRandomUserAgent
import eu.kanade.tachiyomi.lib.i18n.Intl
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST
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
@ -21,64 +14,56 @@ import eu.kanade.tachiyomi.util.asJsoup
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonPrimitive
import okhttp3.Call
import okhttp3.Callback
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.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
import java.lang.IllegalArgumentException
import java.io.IOException
import java.text.SimpleDateFormat
import java.util.Locale
import java.util.concurrent.TimeUnit
// Formerly WPMangaStream & WPMangaReader -> MangaThemesia
abstract class MangaThemesia(
override val name: String,
override val baseUrl: String,
override val lang: String,
final override val lang: String,
val mangaUrlDirectory: String = "/manga",
val dateFormat: SimpleDateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale.US),
) : ParsedHttpSource(), ConfigurableSource {
private val preferences: SharedPreferences by lazy {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
}
) : ParsedHttpSource() {
protected open val json: Json by injectLazy()
override val supportsLatest = true
override val client: OkHttpClient by lazy {
network.cloudflareClient.newBuilder()
.setRandomUserAgent(
preferences.getPrefUAType(),
preferences.getPrefCustomUA(),
)
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.build()
}
override val client = network.cloudflareClient
override fun headersBuilder() = super.headersBuilder()
.set("Referer", "$baseUrl/")
protected val intl = Intl(
language = lang,
baseLanguage = "en",
availableLanguages = setOf("en", "es"),
classLoader = javaClass.classLoader!!,
)
open val projectPageString = "/project"
// Popular (Search with popular order and nothing else)
override fun popularMangaRequest(page: Int) = searchMangaRequest(page, "", FilterList(OrderByFilter("popular")))
override fun popularMangaRequest(page: Int) = searchMangaRequest(page, "", popularFilter)
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 latestUpdatesRequest(page: Int) = searchMangaRequest(page, "", latestFilter)
override fun latestUpdatesParse(response: Response) = searchMangaParse(response)
// Search
@ -166,22 +151,96 @@ abstract class MangaThemesia(
override fun searchMangaNextPageSelector() = "div.pagination .next, div.hpage .r"
// Manga details
private fun selector(selector: String, contains: List<String>): String {
return contains.joinToString(", ") { selector.replace("%s", it) }
}
open val seriesDetailsSelector = "div.bigcontent, div.animefull, div.main-info, div.postbody"
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 seriesTitleSelector = "h1.entry-title, .ts-breadcrumb li:last-child span"
open val seriesArtistSelector = selector(
".infotable tr:contains(%s) td:last-child, .tsinfo .imptdt:contains(%s) i, .fmed b:contains(%s)+span, span:contains(%s)",
listOf(
"artist",
"Artiste",
"Artista",
"الرسام",
"الناشر",
"İllüstratör",
"Çizer",
),
)
open val seriesAuthorSelector = selector(
".infotable tr:contains(%s) td:last-child, .tsinfo .imptdt:contains(%s) i, .fmed b:contains(%s)+span, span:contains(%s)",
listOf(
"Author",
"Auteur",
"autor",
"المؤلف",
"Mangaka",
"seniman",
"Pengarang",
"Yazar",
),
)
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, .tsinfo .imptdt:contains(type) a, .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 seriesAltNameSelector = ".alternative, .wd-full:contains(alt) span, .alter, .seriestualt, " +
selector(
".infotable tr:contains(%s) td:last-child",
listOf(
"Alternative",
"Alternatif",
"الأسماء الثانوية",
),
)
open val seriesGenreSelector = "div.gnr a, .mgen a, .seriestugenre a, " +
selector(
"span:contains(%s)",
listOf(
"genre",
"التصنيف",
),
)
open val seriesTypeSelector = selector(
".infotable tr:contains(%s) td:last-child, .tsinfo .imptdt:contains(%s) i, .tsinfo .imptdt:contains(%s) a, .fmed b:contains(%s)+span, span:contains(%s) a",
listOf(
"type",
"ประเภท",
"النوع",
"tipe",
"Türü",
),
) + ", a[href*=type\\=]"
open val seriesStatusSelector = selector(
".infotable tr:contains(%s) td:last-child, .tsinfo .imptdt:contains(%s) i, .fmed b:contains(%s)+span span:contains(%s)",
listOf(
"status",
"Statut",
"Durum",
"連載状況",
"Estado",
"الحالة",
"حالة العمل",
"สถานะ",
"stato",
"Statüsü",
),
)
open val seriesThumbnailSelector = ".infomanga > div[itemprop=image] img, .thumb img"
open val altNamePrefix = "Alternative Name: "
open val altNamePrefix = "${intl["alt_names_heading"]} "
override fun mangaDetailsParse(document: Document) = SManga.create().apply {
document.selectFirst(seriesDetailsSelector)?.let { seriesDetails ->
title = seriesDetails.selectFirst(seriesTitleSelector)?.text().orEmpty()
title = seriesDetails.selectFirst(seriesTitleSelector)!!.text()
artist = seriesDetails.selectFirst(seriesArtistSelector)?.ownText().removeEmptyPlaceholder()
author = seriesDetails.selectFirst(seriesAuthorSelector)?.ownText().removeEmptyPlaceholder()
description = seriesDetails.select(seriesDescriptionSelector).joinToString("\n") { it.text() }.trim()
@ -210,16 +269,32 @@ abstract class MangaThemesia(
}
protected fun String?.removeEmptyPlaceholder(): String? {
return if (this.isNullOrBlank() || this == "-" || this == "N/A") null else this
return if (this.isNullOrBlank() || this == "-" || this == "N/A" || 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("hiatus", ignoreCase = true) -> SManga.ON_HIATUS
this.contains("completed", ignoreCase = true) -> SManga.COMPLETED
listOf("dropped", "cancelled").any { this.contains(it, ignoreCase = true) } -> SManga.CANCELLED
else -> SManga.UNKNOWN
open fun String?.parseStatus(): Int {
if (this == null) return SManga.UNKNOWN
return when (this.lowercase().trim()) {
"مستمرة", "en curso", "ongoing", "on going", "ativo", "en cours",
"en cours de publication", "đang tiến hành", "em lançamento", "онгоінг", "publishing",
"devam ediyor", "em andamento", "in corso", "güncel", "berjalan", "продолжается", "updating", "lançando", "in arrivo", "emision",
"en emision", "مستمر", "curso", "en marcha", "publicandose", "publicando", "连载中", "devam etmekte", "連載中",
-> SManga.ONGOING
"completed", "completo", "complété", "fini", "achevé", "terminé", "tamamlandı", "đã hoàn thành", "hoàn thành",
"مكتملة", "завершено", "finished", "finalizado", "completata", "one-shot", "bitti", "tamat", "completado", "concluído", "完結",
"concluido", "已完结", "bitmiş",
-> SManga.COMPLETED
"canceled", "cancelled", "cancelado", "cancellato", "cancelados", "dropped", "discontinued", "abandonné",
-> SManga.CANCELLED
"hiatus", "on hold", "pausado", "en espera", "en pause", "en attente",
-> SManga.ON_HIATUS
else -> SManga.UNKNOWN
}
}
// Chapter list
@ -227,6 +302,9 @@ abstract class MangaThemesia(
override fun chapterListParse(response: Response): List<SChapter> {
val document = response.asJsoup()
countViews(document)
val chapters = document.select(chapterListSelector()).map { chapterFromElement(it) }
// Add timestamp to latest chapter, taken from "Updated On".
@ -238,8 +316,6 @@ abstract class MangaThemesia(
if (date.isNotEmpty()) chapters.first().date_upload = parseUpdatedOnDate(date)
}
countViews(document)
return chapters
}
@ -267,13 +343,13 @@ abstract class MangaThemesia(
open val pageSelector = "div#readerarea img"
override fun pageListParse(document: Document): List<Page> {
countViews(document)
val chapterUrl = document.location()
val htmlPages = document.select(pageSelector)
.filterNot { it.imgAttr().isEmpty() }
.mapIndexed { i, img -> Page(i, chapterUrl, img.imgAttr()) }
countViews(document)
// Some sites also loads pages via javascript
if (htmlPages.isNotEmpty()) { return htmlPages }
@ -320,8 +396,6 @@ abstract class MangaThemesia(
.build()
val newHeaders = headersBuilder()
.set("Content-Length", formBody.contentLength().toString())
.set("Content-Type", formBody.contentType().toString())
.set("Referer", document.location())
.build()
@ -339,17 +413,22 @@ abstract class MangaThemesia(
}
val request = countViewsRequest(document) ?: return
runCatching { client.newCall(request).execute().close() }
val callback = object : Callback {
override fun onResponse(call: Call, response: Response) = response.close()
override fun onFailure(call: Call, e: IOException) = Unit
}
client.newCall(request).enqueue(callback)
}
// Filters
protected class AuthorFilter : Filter.Text("Author")
protected class AuthorFilter(name: String) : Filter.Text(name)
protected class YearFilter : Filter.Text("Year")
protected class YearFilter(name: String) : Filter.Text(name)
open class SelectFilter(
displayName: String,
val vals: Array<Pair<String, String>>,
private val vals: Array<Pair<String, String>>,
defaultValue: String? = null,
) : Filter.Select<String>(
displayName,
@ -359,63 +438,91 @@ abstract class MangaThemesia(
fun 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 StatusFilter(
name: String,
options: Array<Pair<String, String>>,
) : SelectFilter(
name,
options,
)
protected class TypeFilter : SelectFilter(
"Type",
arrayOf(
Pair("All", ""),
Pair("Manga", "Manga"),
Pair("Manhwa", "Manhwa"),
Pair("Manhua", "Manhua"),
Pair("Comic", "Comic"),
),
protected open val statusOptions = arrayOf(
Pair(intl["status_filter_option_all"], ""),
Pair(intl["status_filter_option_ongoing"], "ongoing"),
Pair(intl["status_filter_option_completed"], "completed"),
Pair(intl["status_filter_option_hiatus"], "hiatus"),
Pair(intl["status_filter_option_dropped"], "dropped"),
)
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"),
),
protected class TypeFilter(
name: String,
options: Array<Pair<String, String>>,
) : SelectFilter(
name,
options,
)
protected open val typeFilterOptions = arrayOf(
Pair(intl["type_filter_option_all"], ""),
Pair(intl["type_filter_option_manga"], "Manga"),
Pair(intl["type_filter_option_manhwa"], "Manhwa"),
Pair(intl["type_filter_option_manhua"], "Manhua"),
Pair(intl["type_filter_option_comic"], "Comic"),
)
protected class OrderByFilter(
name: String,
options: Array<Pair<String, String>>,
defaultOrder: String? = null,
) : SelectFilter(
name,
options,
defaultOrder,
)
protected class ProjectFilter : SelectFilter(
"Filter Project",
arrayOf(
Pair("Show all manga", ""),
Pair("Show only project manga", "project-filter-on"),
),
protected open val orderByFilterOptions = arrayOf(
Pair(intl["order_by_filter_default"], ""),
Pair(intl["order_by_filter_az"], "title"),
Pair(intl["order_by_filter_za"], "titlereverse"),
Pair(intl["order_by_filter_latest_update"], "update"),
Pair(intl["order_by_filter_latest_added"], "latest"),
Pair(intl["order_by_filter_popular"], "popular"),
)
protected val popularFilter by lazy { FilterList(OrderByFilter("", orderByFilterOptions, "popular")) }
protected val latestFilter by lazy { FilterList(OrderByFilter("", orderByFilterOptions, "update")) }
protected class ProjectFilter(
name: String,
options: Array<Pair<String, String>>,
) : SelectFilter(
name,
options,
)
protected open val projectFilterOptions = arrayOf(
Pair(intl["project_filter_all_manga"], ""),
Pair(intl["project_filter_only_project"], "project-filter-on"),
)
protected class GenreData(
val name: String,
val value: String,
val state: Int = Filter.TriState.STATE_IGNORE,
)
protected class Genre(
name: String,
val value: String,
state: Int = STATE_IGNORE,
state: Int,
) : Filter.TriState(name, state)
protected class GenreListFilter(genres: List<Genre>) : Filter.Group<Genre>("Genre", genres)
protected class GenreListFilter(name: String, genres: List<Genre>) : Filter.Group<Genre>(name, genres)
protected var genrelist: List<GenreData>? = null
private var genrelist: List<Genre>? = null
protected open fun getGenreList(): List<Genre> {
// 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", ""))
return genrelist?.map { Genre(it.name, it.value, it.state) }.orEmpty()
}
open val hasProjectPage = false
@ -423,21 +530,31 @@ abstract class MangaThemesia(
override fun getFilterList(): FilterList {
val filters = mutableListOf<Filter<*>>(
Filter.Separator(),
AuthorFilter(),
YearFilter(),
StatusFilter(),
TypeFilter(),
OrderByFilter(),
Filter.Header("Genre exclusion is not available for all sources"),
GenreListFilter(getGenreList()),
AuthorFilter(intl["author_filter_title"]),
YearFilter(intl["year_filter_title"]),
StatusFilter(intl["status_filter_title"], statusOptions),
TypeFilter(intl["type_filter_title"], typeFilterOptions),
OrderByFilter(intl["order_by_filter_title"], orderByFilterOptions),
)
if (!genrelist.isNullOrEmpty()) {
filters.addAll(
listOf(
Filter.Header(intl["genre_exclusion_warning"]),
GenreListFilter(intl["genre_filter_title"], getGenreList()),
),
)
} else {
filters.add(
Filter.Header(intl["genre_missing_warning"]),
)
}
if (hasProjectPage) {
filters.addAll(
mutableListOf<Filter<*>>(
Filter.Separator(),
Filter.Header("NOTE: Can't be used with other filter!"),
Filter.Header("$name Project List page"),
ProjectFilter(),
Filter.Header(intl["project_filter_warning"]),
Filter.Header(intl.format("project_filter_name", name)),
ProjectFilter(intl["project_filter_title"], projectFilterOptions),
),
)
}
@ -485,9 +602,9 @@ abstract class MangaThemesia(
(!strict && url.pathSegments.size == n + 1 && url.pathSegments[n].isEmpty())
}
private fun parseGenres(document: Document): List<Genre>? {
private fun parseGenres(document: Document): List<GenreData>? {
return document.selectFirst("ul.genrez")?.select("li")?.map { li ->
Genre(
GenreData(
li.selectFirst("label")!!.text(),
li.selectFirst("input[type=checkbox]")!!.attr("value"),
)
@ -514,15 +631,10 @@ abstract class MangaThemesia(
override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException()
override fun setupPreferenceScreen(screen: PreferenceScreen) {
addRandomUAPreferenceToScreen(screen)
}
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()

View File

@ -33,10 +33,11 @@ open class MiauScan(lang: String) : MangaThemesia(
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
val genreFilterIndex = filters.indexOfFirst { it is GenreListFilter }
val genreFilter = filters.getOrNull(genreFilterIndex) as? GenreListFilter
?: GenreListFilter(emptyList())
?: GenreListFilter("", emptyList())
val overloadedGenreFilter = GenreListFilter(
genres = genreFilter.state + listOf(
genreFilter.name,
genreFilter.state + listOf(
Genre("", PORTUGUESE_GENRE_ID, portugueseMode),
),
)

View File

@ -31,8 +31,8 @@ class Mihentai : MangaThemesia("Mihentai", "https://mihentai.com", "all") {
listOf(
StatusFilter(),
TypeFilter(),
OrderByFilter(),
GenreListFilter(getGenreList()),
OrderByFilter(intl["order_by_filter_title"], orderByFilterOptions),
GenreListFilter(intl["genre_filter_title"], getGenreList()),
),
)
}

View File

@ -1,7 +1,6 @@
package eu.kanade.tachiyomi.extension.ar.areamanga
import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia
import eu.kanade.tachiyomi.source.model.SManga
import java.text.SimpleDateFormat
import java.util.Locale
@ -10,21 +9,4 @@ class AreaManga : MangaThemesia(
"https://www.areascans.net",
"ar",
dateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale("ar")),
) {
override val seriesArtistSelector =
".tsinfo .imptdt:contains(الرسام) i, ${super.seriesArtistSelector}"
override val seriesAuthorSelector =
".tsinfo .imptdt:contains(المؤلف) i, ${super.seriesAuthorSelector}"
override val seriesStatusSelector =
".tsinfo .imptdt:contains(الحالة) i, ${super.seriesStatusSelector}"
override val seriesTypeSelector =
".tsinfo .imptdt:contains(النوع) i, ${super.seriesTypeSelector}"
override fun String?.parseStatus() = when {
this == null -> SManga.UNKNOWN
this.contains("مستمر", ignoreCase = true) -> SManga.ONGOING
this.contains("مكتمل", ignoreCase = true) -> SManga.COMPLETED
this.contains("متوقف", ignoreCase = true) -> SManga.ON_HIATUS
else -> SManga.UNKNOWN
}
}
)

View File

@ -7,6 +7,7 @@ import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.extension.BuildConfig
import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia
import eu.kanade.tachiyomi.network.interceptor.rateLimit
import eu.kanade.tachiyomi.source.ConfigurableSource
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.model.SChapter
@ -23,12 +24,14 @@ import java.util.Locale
private const val swatUrl = "https://swatmanhua.com"
class MangaSwat : MangaThemesia(
"MangaSwat",
swatUrl,
"ar",
dateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale("ar")),
) {
class MangaSwat :
MangaThemesia(
"MangaSwat",
swatUrl,
"ar",
dateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale("ar")),
),
ConfigurableSource {
private val defaultBaseUrl = swatUrl
override val baseUrl by lazy { getPrefBaseUrl() }
@ -113,8 +116,6 @@ class MangaSwat : MangaThemesia(
}
}
screen.addPreference(baseUrlPref)
super.setupPreferenceScreen(screen)
}
private fun getPrefBaseUrl(): String = preferences.getString(BASE_URL_PREF, defaultBaseUrl)!!

View File

@ -6,6 +6,7 @@ import androidx.preference.PreferenceScreen
import androidx.preference.SwitchPreferenceCompat
import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia
import eu.kanade.tachiyomi.network.interceptor.rateLimit
import eu.kanade.tachiyomi.source.ConfigurableSource
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.MangasPage
import eu.kanade.tachiyomi.source.model.Page
@ -26,12 +27,14 @@ import java.text.SimpleDateFormat
import java.util.Locale
import java.util.concurrent.TimeUnit
class AsuraScans : MangaThemesia(
"Asura Scans",
"https://asuratoon.com",
"en",
dateFormat = SimpleDateFormat("MMM d, yyyy", Locale.US),
) {
class AsuraScans :
MangaThemesia(
"Asura Scans",
"https://asuratoon.com",
"en",
dateFormat = SimpleDateFormat("MMM d, yyyy", Locale.US),
),
ConfigurableSource {
private val preferences by lazy {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
@ -281,8 +284,6 @@ class AsuraScans : MangaThemesia(
summary = PREF_PERM_MANGA_URL_SUMMARY
setDefaultValue(true)
}.also(screen::addPreference)
super.setupPreferenceScreen(screen)
}
private val SharedPreferences.permaUrlPref

View File

@ -8,3 +8,7 @@ ext {
}
apply from: "$rootDir/common.gradle"
dependencies {
implementation(project(":lib:randomua"))
}

View File

@ -2,11 +2,14 @@ package eu.kanade.tachiyomi.extension.en.constellarscans
import android.app.Application
import android.content.SharedPreferences
import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.lib.randomua.addRandomUAPreferenceToScreen
import eu.kanade.tachiyomi.lib.randomua.getPrefCustomUA
import eu.kanade.tachiyomi.lib.randomua.getPrefUAType
import eu.kanade.tachiyomi.lib.randomua.setRandomUserAgent
import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia
import eu.kanade.tachiyomi.network.interceptor.rateLimit
import eu.kanade.tachiyomi.source.ConfigurableSource
import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.model.SChapter
import kotlinx.serialization.json.jsonArray
@ -19,22 +22,29 @@ import okhttp3.Request
import org.jsoup.nodes.Document
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.util.concurrent.TimeUnit
class ConstellarScans : MangaThemesia("Constellar Scans", "https://constellarcomic.com", "en") {
class ConstellarScans :
MangaThemesia(
"Constellar Scans",
"https://constellarcomic.com",
"en",
),
ConfigurableSource {
private val preferences: SharedPreferences by lazy {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
}
override fun setupPreferenceScreen(screen: PreferenceScreen) {
addRandomUAPreferenceToScreen(screen)
}
override val client: OkHttpClient by lazy {
network.cloudflareClient.newBuilder()
.setRandomUserAgent(
preferences.getPrefUAType(),
preferences.getPrefCustomUA(),
)
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.rateLimit(1, 1)
.build()
}
@ -61,6 +71,8 @@ class ConstellarScans : MangaThemesia("Constellar Scans", "https://constellarcom
.build()
override fun pageListParse(document: Document): List<Page> {
countViews(document)
val html = document.toString()
if (!html.contains("ts_rea_der_._run(\"")) {
return super.pageListParse(document)
@ -80,7 +92,6 @@ class ConstellarScans : MangaThemesia("Constellar Scans", "https://constellarcom
}
.joinToString("")
countViews(document)
return json.parseToJsonElement(tsReaderRawData).jsonObject["sources"]!!.jsonArray[0].jsonObject["images"]!!.jsonArray.mapIndexed { idx, it ->
Page(idx, imageUrl = it.jsonPrimitive.content)
}

View File

@ -9,6 +9,7 @@ import androidx.preference.PreferenceScreen
import androidx.preference.SwitchPreferenceCompat
import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia
import eu.kanade.tachiyomi.network.interceptor.rateLimit
import eu.kanade.tachiyomi.source.ConfigurableSource
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.MangasPage
import eu.kanade.tachiyomi.source.model.Page
@ -25,12 +26,14 @@ import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.io.ByteArrayOutputStream
class FlameComics : MangaThemesia(
"Flame Comics",
"https://flamecomics.com",
"en",
mangaUrlDirectory = "/series",
) {
class FlameComics :
MangaThemesia(
"Flame Comics",
"https://flamecomics.com",
"en",
mangaUrlDirectory = "/series",
),
ConfigurableSource {
// Flame Scans -> Flame Comics
override val id = 6350607071566689772

View File

@ -6,6 +6,7 @@ import androidx.preference.PreferenceScreen
import androidx.preference.SwitchPreferenceCompat
import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia
import eu.kanade.tachiyomi.network.interceptor.rateLimit
import eu.kanade.tachiyomi.source.ConfigurableSource
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.MangasPage
import eu.kanade.tachiyomi.source.model.SChapter
@ -20,7 +21,15 @@ import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.io.IOException
class LuminousScans : MangaThemesia("Luminous Scans", "https://lumitoon.com", "en", mangaUrlDirectory = "/series") {
class LuminousScans :
MangaThemesia(
"Luminous Scans",
"https://lumitoon.com",
"en",
mangaUrlDirectory = "/series",
),
ConfigurableSource {
override val client = super.client.newBuilder()
.addInterceptor(::urlChangeInterceptor)
.rateLimit(2)
@ -201,8 +210,6 @@ class LuminousScans : MangaThemesia("Luminous Scans", "https://lumitoon.com", "e
summary = PREF_PERM_MANGA_URL_SUMMARY
setDefaultValue(true)
}.also(screen::addPreference)
super.setupPreferenceScreen(screen)
}
private val SharedPreferences.permaUrlPref

View File

@ -33,19 +33,29 @@ class LunarScans : MangaThemesia(
val filters = mutableListOf<Filter<*>>(
Filter.Header("Note: Can't be used with text search!"),
Filter.Separator(),
StatusFilter(),
TypeFilter(),
OrderByFilter(),
Filter.Header("Genre exclusion is not available for all sources"),
GenreListFilter(getGenreList()),
StatusFilter(intl["status_filter_title"], statusOptions),
TypeFilter(intl["type_filter_title"], typeFilterOptions),
OrderByFilter(intl["order_by_filter_title"], orderByFilterOptions),
)
if (!genrelist.isNullOrEmpty()) {
filters.addAll(
listOf(
Filter.Header(intl["genre_exclusion_warning"]),
GenreListFilter(intl["genre_filter_title"], getGenreList()),
),
)
} else {
filters.add(
Filter.Header(intl["genre_missing_warning"]),
)
}
if (hasProjectPage) {
filters.addAll(
mutableListOf<Filter<*>>(
Filter.Separator(),
Filter.Header("NOTE: Can't be used with other filter!"),
Filter.Header("$name Project List page"),
ProjectFilter(),
Filter.Header(intl["project_filter_warning"]),
Filter.Header(intl.format("project_filter_name", name)),
ProjectFilter(intl["project_filter_title"], projectFilterOptions),
),
)
}

View File

@ -17,14 +17,4 @@ class CarteldeManhwas : MangaThemesia(
override fun searchMangaSelector() = ".utao .uta .imgu:not(:has(span.novelabel)), " +
".listupd .bs .bsx:not(:has(span.novelabel)), " +
".listo .bs .bsx:not(:has(span.novelabel))"
private class StatusFilter : SelectFilter(
"Status",
arrayOf(
Pair("All", ""),
Pair("Ongoing", "ongoing"),
Pair("Completed", "completed"),
Pair("Hiatus", "hiatus"),
),
)
}

View File

@ -1,5 +1,12 @@
package eu.kanade.tachiyomi.extension.es.gremorymangas
import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia
import java.text.SimpleDateFormat
import java.util.Locale
class GremoryMangas : MangaThemesia("Gremory Mangas", "https://gremorymangas.com", "es")
class GremoryMangas : MangaThemesia(
"Gremory Mangas",
"https://gremorymangas.com",
"es",
dateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale("es")),
)

View File

@ -25,13 +25,13 @@ class NekoScans : MangaThemesia(
override val seriesStatusSelector = ".tsinfo .imptdt:contains(estado) i"
override fun pageListParse(document: Document): List<Page> {
countViews(document)
val chapterUrl = document.location()
val htmlPages = document.select(pageSelector)
.filterNot { it.imgAttr().isEmpty() }
.mapIndexed { i, img -> Page(i, chapterUrl, img.imgAttr()) }
countViews(document)
// Some sites also loads pages via javascript
if (htmlPages.isNotEmpty()) { return htmlPages }

View File

@ -1,26 +1,14 @@
package eu.kanade.tachiyomi.extension.es.senpaiediciones
import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia
import eu.kanade.tachiyomi.source.model.SManga
import java.text.SimpleDateFormat
import java.util.Locale
class SenpaiEdiciones : MangaThemesia(
"Senpai Ediciones",
"http://senpaiediciones.com",
"https://senpaiediciones.com",
"es",
dateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale("es")),
) {
override val seriesAuthorSelector = ".imptdt:contains(Autor) i"
override val seriesStatusSelector = ".imptdt:contains(Estado) i"
override val pageSelector = "div#readerarea img:not(noscript img)"
override fun String?.parseStatus(): Int = when {
this == null -> SManga.UNKNOWN
listOf("curso").any { this.contains(it, ignoreCase = true) } -> SManga.ONGOING
this.contains("hiatus", ignoreCase = true) -> SManga.ON_HIATUS
this.contains("finalizado", ignoreCase = true) -> SManga.COMPLETED
else -> SManga.UNKNOWN
}
}

View File

@ -19,7 +19,7 @@ class CosmicScansID : MangaThemesia("CosmicScans.id", "https://cosmicscans.id",
override val hasProjectPage = true
override val projectPageString = "/semua-komik"
override fun latestUpdatesRequest(page: Int) = GET("$baseUrl" + if (page > 1) "/page/$page" else "", headers)
override fun latestUpdatesRequest(page: Int) = GET(baseUrl + if (page > 1) "/page/$page" else "", headers)
// search
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
@ -47,8 +47,8 @@ class CosmicScansID : MangaThemesia("CosmicScans.id", "https://cosmicscans.id",
val filters = mutableListOf<Filter<*>>(
Filter.Separator(),
Filter.Header("$name Project List page"),
ProjectFilter(),
OrderByFilter(),
ProjectFilter(intl["project_filter_title"], projectFilterOptions),
OrderByFilter(intl["order_by_filter_title"], orderByFilterOptions),
)
return FilterList(filters)
}

View File

@ -241,12 +241,12 @@ class KomikCast : MangaThemesia("Komik Cast", "https://komikcast.lol", "id", "/d
StatusFilter(),
TypeFilter(),
OrderByFilter(),
Filter.Header("Genre exclusion is not available for all sources"),
GenreListFilter(getGenreList()),
Filter.Header(intl["genre_exclusion_warning"]),
GenreListFilter(intl["genre_filter_title"], getGenreList()),
Filter.Separator(),
Filter.Header("NOTE: Can't be used with other filter!"),
Filter.Header("$name Project List page"),
ProjectFilter(),
Filter.Header(intl["project_filter_warning"]),
Filter.Header(intl.format("project_filter_name", name)),
ProjectFilter(intl["project_filter_title"], projectFilterOptions),
)
return FilterList(filters)
}

View File

@ -1,7 +1,6 @@
package eu.kanade.tachiyomi.extension.ja.mangamate
import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia
import eu.kanade.tachiyomi.source.model.SManga
import java.text.SimpleDateFormat
import java.util.Locale
@ -12,12 +11,4 @@ class MangaMate : MangaThemesia(
dateFormat = SimpleDateFormat("MMMM d, yyyy", Locale("ja")),
) {
override val seriesAuthorSelector = ".fmed b:contains(作者) + span"
override val seriesStatusSelector = ".tsinfo .imptdt:contains(連載状況) i"
override fun String?.parseStatus(): Int = when (this) {
"連載中" -> SManga.ONGOING
"完結" -> SManga.COMPLETED
"人気" -> SManga.ON_HIATUS
else -> SManga.UNKNOWN
}
}