Madara refactor (#1292)

* remove randomua from madara

* don't use page path for page=1

* add back `madara_load_more`

* cleanup i18n and filters

* load more in a new source

* move back the filters

not worth it

* fix build

* altname to i18n as well

* utf-8

* Revert "utf-8"

This reverts commit 1335bc1b478da54d3a5eb6333ac1a26e3ee2825b.

* utf-8

* autodetect load_more_request

* load genres in background

* make genre classes protected

remove unnecessary change

* fetch genres changes

* launchIO countviews

* don't explicitly optin

* cleanup some request overrides

* make `useLoadMoreRequest` enum to be able to disable autodection where necessary

* fix logic

bruh

* use state variables

* defer countViews in overrides as well

* lint

* select().first -> selectFirst

* `load_more` search as well

* detect in search as well

* remove slipped override

* move detection to the function

* remove fetchGenreFailed

* don't use GlobalScope

* tweak load_more_request parameters

* remove ancient connectTimeout/readTimeout

already present in the client provided by the app

* small cleanup
This commit is contained in:
AwkwardPeak7 2024-02-18 19:57:05 +05:00 committed by Draff
parent a62d90d4aa
commit 30b13498b0
151 changed files with 572 additions and 1375 deletions

View File

@ -0,0 +1,27 @@
author_filter_title=Author
artist_filter_title=Artist
year_filter_title=Year of Released
status_filter_title=Status
status_filter_completed=Completed
status_filter_ongoing=Ongoing
status_filter_canceled=Canceled
status_filter_on_hold=On Hold
order_by_filter_title=Order By
order_by_filter_relevance=Relevance
order_by_filter_latest=Latest
order_by_filter_az=A-Z
order_by_filter_rating=Rating
order_by_filter_trending=Trending
order_by_filter_views=Most Views
order_by_filter_new=New
genre_condition_filter_title=Genre condition
genre_condition_filter_or=OR
genre_condition_filter_and=AND
adult_content_filter_title=Adult Content
adult_content_filter_all=All
adult_content_filter_none=None
adult_content_filter_only=Only
genre_filter_header=Genres filter may not work for all sources
genre_filter_title=Genres
genre_missing_warning=Press 'Reset' to attempt to show the genres
alt_names_heading=Alternative Names:

View File

@ -0,0 +1,26 @@
author_filter_title=Autor
artist_filter_title=Artista
year_filter_title=Ano de lançamento
status_filter_title=Estado
status_filter_completed=Completo
status_filter_ongoing=Em andamento
status_filter_canceled=Cancelado
status_filter_on_hold=Pausado
order_by_filter_title=Ordenar por
order_by_filter_relevance=Relevância
order_by_filter_latest=Recentes
order_by_filter_rating=Avaliação
order_by_filter_trending=Tendência
order_by_filter_views=Visualizações
order_by_filter_new=Novos
genre_condition_filter_title=Operador dos gêneros
genre_condition_filter_or=OU
genre_condition_filter_and=E
adult_content_filter_title=Conteúdo adulto
adult_content_filter_all=Indiferente
adult_content_filter_none=Nenhum
adult_content_filter_only=Somente
genre_filter_header=O filtro de gêneros pode não funcionar
genre_filter_title=Gêneros
genre_missing_warning=Aperte 'Redefinir' para tentar mostrar os gêneros
alt_names_heading=Nomes alternativos:

View File

@ -2,9 +2,9 @@ plugins {
id("lib-multisrc")
}
baseVersionCode = 33
baseVersionCode = 34
dependencies {
api(project(":lib:cryptoaes"))
api(project(":lib:randomua"))
api(project(":lib:i18n"))
}

View File

@ -1,18 +1,11 @@
package eu.kanade.tachiyomi.multisrc.madara
import android.app.Application
import android.content.SharedPreferences
import android.util.Base64
import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.lib.cryptoaes.CryptoAES
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.network.asObservable
import eu.kanade.tachiyomi.source.ConfigurableSource
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
@ -21,57 +14,55 @@ 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.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import okhttp3.CacheControl
import okhttp3.FormBody
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import rx.Observable
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
import java.text.ParseException
import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Locale
import java.util.concurrent.TimeUnit
abstract class Madara(
override val name: String,
override val baseUrl: String,
final override val lang: String,
private 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() {
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()
.add("Referer", "$baseUrl/")
protected val xhrHeaders by lazy {
headersBuilder()
.set("X-Requested-With", "XMLHttpRequest")
.build()
}
protected open val json: Json by injectLazy()
protected val intl = Intl(
language = lang,
baseLanguage = "en",
availableLanguages = setOf("en", "pt-BR"),
classLoader = this::class.java.classLoader!!,
)
/**
* If enabled, will attempt to remove non-manga items in popular and latest.
* The filter will not be used in search as the theme doesn't set the CSS class.
@ -93,11 +84,6 @@ abstract class Madara(
*/
private var genresList: List<Genre> = emptyList()
/**
* Inner variable to control the genre fetching failed state.
*/
private var fetchGenresFailed: Boolean = false
/**
* Inner variable to control how much tries the genres request was called.
*/
@ -114,11 +100,58 @@ abstract class Madara(
*/
protected open val mangaSubString = "manga"
/**
* enable if the site use "madara_load_more" to load manga on the site
* Typically has "load More" instead of next/previous page
*
* with LoadMoreStrategy.AutoDetect it tries to detect if site uses `madara_load_more`
*/
protected open val useLoadMoreRequest = LoadMoreStrategy.AutoDetect
enum class LoadMoreStrategy {
AutoDetect, Always, Never
}
/**
* internal variable to save if site uses load_more or not
*/
private var loadMoreRequestDetected = LoadMoreDetection.Pending
private enum class LoadMoreDetection {
Pending, True, False
}
protected fun detectLoadMore(document: Document) {
if (useLoadMoreRequest == LoadMoreStrategy.AutoDetect &&
loadMoreRequestDetected == LoadMoreDetection.Pending
) {
loadMoreRequestDetected = when (document.selectFirst("nav.navigation-ajax") != null) {
true -> LoadMoreDetection.True
false -> LoadMoreDetection.False
}
}
}
protected fun useLoadMoreRequest(): Boolean {
return when (useLoadMoreRequest) {
LoadMoreStrategy.Always -> true
LoadMoreStrategy.Never -> false
else -> loadMoreRequestDetected == LoadMoreDetection.True
}
}
// Popular Manga
override fun popularMangaParse(response: Response): MangasPage {
runCatching { fetchGenres() }
return super.popularMangaParse(response)
val document = response.asJsoup()
val entries = document.select(popularMangaSelector())
.map(::popularMangaFromElement)
val hasNextPage = popularMangaNextPageSelector()?.let { document.selectFirst(it) } != null
detectLoadMore(document)
return MangasPage(entries, hasNextPage)
}
// exclude/filter bilibili manga from list
@ -130,12 +163,12 @@ abstract class Madara(
val manga = SManga.create()
with(element) {
select(popularMangaUrlSelector).first()?.let {
selectFirst(popularMangaUrlSelector)!!.let {
manga.setUrlWithoutDomain(it.attr("abs:href"))
manga.title = it.ownText()
}
select("img").first()?.let {
selectFirst("img")?.let {
manga.thumbnail_url = imageFromElement(it)
}
}
@ -143,15 +176,19 @@ abstract class Madara(
return manga
}
override fun popularMangaRequest(page: Int): Request {
return GET(
url = "$baseUrl/$mangaSubString/${searchPage(page)}?m_orderby=views",
headers = headers,
cache = CacheControl.FORCE_NETWORK,
)
override fun popularMangaRequest(page: Int): Request =
if (useLoadMoreRequest()) {
loadMoreRequest(page, popular = true)
} else {
GET("$baseUrl/$mangaSubString/${searchPage(page)}?m_orderby=views", headers)
}
override fun popularMangaNextPageSelector(): String? = searchMangaNextPageSelector()
override fun popularMangaNextPageSelector(): String? =
if (useLoadMoreRequest()) {
"body:not(:has(.no-posts))"
} else {
"div.nav-previous, nav.navigation-ajax, a.nextpostslink"
}
// Latest Updates
@ -162,53 +199,75 @@ abstract class Madara(
return popularMangaFromElement(element)
}
override fun latestUpdatesRequest(page: Int): Request {
return GET(
url = "$baseUrl/$mangaSubString/${searchPage(page)}?m_orderby=latest",
headers = headers,
cache = CacheControl.FORCE_NETWORK,
)
override fun latestUpdatesRequest(page: Int): Request =
if (useLoadMoreRequest()) {
loadMoreRequest(page, popular = false)
} else {
GET("$baseUrl/$mangaSubString/${searchPage(page)}?m_orderby=latest", headers)
}
override fun latestUpdatesNextPageSelector(): String? = popularMangaNextPageSelector()
override fun latestUpdatesParse(response: Response): MangasPage {
val mp = super.latestUpdatesParse(response)
val mp = popularMangaParse(response)
val mangas = mp.mangas.distinctBy { it.url }
return MangasPage(mangas, mp.hasNextPage)
}
// load more
protected fun loadMoreRequest(page: Int, popular: Boolean): Request {
val formBody = FormBody.Builder().apply {
add("action", "madara_load_more")
add("page", (page - 1).toString())
add("template", "madara-core/content/content-archive")
add("vars[orderby]", "meta_value_num")
add("vars[paged]", "1")
add("vars[post_type]", "wp-manga")
add("vars[post_status]", "publish")
add("vars[meta_key]", if (popular) "_wp_manga_views" else "_latest_update")
add("vars[order]", "desc")
add("vars[sidebar]", "right")
add("vars[manga_archives_item_layout]", "big_thumbnail")
}.build()
return POST("$baseUrl/wp-admin/admin-ajax.php", xhrHeaders, formBody)
}
// Search Manga
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
if (query.startsWith(URL_SEARCH_PREFIX)) {
val mangaUrl = "$baseUrl/$mangaSubString/${query.substringAfter(URL_SEARCH_PREFIX)}"
return client.newCall(GET(mangaUrl, headers))
.asObservable().map { response ->
MangasPage(listOf(mangaDetailsParse(response.asJsoup()).apply { url = "/$mangaSubString/${query.substringAfter(URL_SEARCH_PREFIX)}/" }), false)
val mangaUrl = "/$mangaSubString/${query.substringAfter(URL_SEARCH_PREFIX)}"
return client.newCall(GET("$baseUrl$mangaUrl", headers))
.asObservableSuccess().map { response ->
val manga = mangaDetailsParse(response).apply {
url = mangaUrl
}
MangasPage(listOf(manga), false)
}
}
return client.newCall(searchMangaRequest(page, query, filters))
.asObservable().doOnNext { response ->
if (!response.isSuccessful) {
response.close()
// Error message for exceeding last page
if (response.code == 404) {
error("Already on the Last Page!")
return super.fetchSearchManga(page, query, filters)
}
protected open fun searchPage(page: Int): String {
return if (page == 1) {
""
} else {
throw Exception("HTTP error ${response.code}")
"page/$page/"
}
}
}
.map { response ->
searchMangaParse(response)
}
}
protected open fun searchPage(page: Int): String = "page/$page/"
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
return if (useLoadMoreRequest()) {
searchLoadMoreRequest(page, query, filters)
} else {
searchRequest(page, query, filters)
}
}
protected open fun searchRequest(page: Int, query: String, filters: FilterList): Request {
val url = "$baseUrl/${searchPage(page)}".toHttpUrl().newBuilder()
url.addQueryParameter("s", query)
url.addQueryParameter("post_type", "wp-manga")
@ -260,108 +319,182 @@ abstract class Madara(
return GET(url.build(), headers)
}
protected open val authorFilterTitle: String = when (lang) {
"pt-BR" -> "Autor"
else -> "Author"
protected open fun searchLoadMoreRequest(page: Int, query: String, filters: FilterList): Request {
val formBody = FormBody.Builder().apply {
add("action", "madara_load_more")
add("page", (page - 1).toString())
add("template", "madara-core/content/content-search")
add("vars[paged]", "1")
add("vars[template]", "archive")
add("vars[sidebar]", "right")
add("vars[post_type]", "wp-manga")
add("vars[post_status]", "publish")
add("vars[manga_archives_item_layout]", "big_thumbnail")
if (filterNonMangaItems) {
add("vars[meta_query][0][key]", "_wp_manga_chapter_type")
add("vars[meta_query][0][value]", "manga")
}
protected open val artistFilterTitle: String = when (lang) {
"pt-BR" -> "Artista"
else -> "Artist"
add("vars[s]", query)
var metaQueryIdx = if (filterNonMangaItems) 1 else 0
var taxQueryIdx = 0
val genres = filters.filterIsInstance<GenreList>().firstOrNull()?.state
?.filter { it.state }
?.map { it.id }
.orEmpty()
filters.forEach { filter ->
when (filter) {
is AuthorFilter -> {
if (filter.state.isNotBlank()) {
add("vars[tax_query][$taxQueryIdx][taxonomy]", "wp-manga-author")
add("vars[tax_query][$taxQueryIdx][field]", "name")
add("vars[tax_query][$taxQueryIdx][terms]", filter.state)
taxQueryIdx++
}
}
is ArtistFilter -> {
if (filter.state.isNotBlank()) {
add("vars[tax_query][$taxQueryIdx][taxonomy]", "wp-manga-artist")
add("vars[tax_query][$taxQueryIdx][field]", "name")
add("vars[tax_query][$taxQueryIdx][terms]", filter.state)
taxQueryIdx++
}
}
is YearFilter -> {
if (filter.state.isNotBlank()) {
add("vars[tax_query][$taxQueryIdx][taxonomy]", "wp-manga-release")
add("vars[tax_query][$taxQueryIdx][field]", "name")
add("vars[tax_query][$taxQueryIdx][terms]", filter.state)
taxQueryIdx++
}
}
is StatusFilter -> {
val statuses = filter.state
.filter { it.state }
.map { it.id }
if (statuses.isNotEmpty()) {
add("vars[meta_query][$metaQueryIdx][key]", "_wp_manga_status")
statuses.forEachIndexed { i, slug ->
add("vars[meta_query][$metaQueryIdx][value][$i]", slug)
}
protected open val yearFilterTitle: String = when (lang) {
"pt-BR" -> "Ano de lançamento"
else -> "Year of Released"
metaQueryIdx++
}
protected open val statusFilterTitle: String = when (lang) {
"pt-BR" -> "Estado"
else -> "Status"
}
protected open val statusFilterOptions: Array<String> = when (lang) {
"pt-BR" -> arrayOf("Completo", "Em andamento", "Cancelado", "Pausado")
else -> arrayOf("Completed", "Ongoing", "Canceled", "On Hold")
is OrderByFilter -> {
if (filter.state != 0) {
when (filter.toUriPart()) {
"latest" -> {
add("vars[orderby]", "meta_value_num")
add("vars[order]", "DESC")
add("vars[meta_key]", "_latest_update")
}
protected open val statusFilterOptionsValues: Array<String> = arrayOf(
"end",
"on-going",
"canceled",
"on-hold",
"alphabet" -> {
add("vars[orderby]", "post_title")
add("vars[order]", "ASC")
}
"rating" -> {
add("vars[orderby][query_average_reviews]", "DESC")
add("vars[orderby][query_total_reviews]", "DESC")
}
"trending" -> {
add("vars[orderby]", "meta_value_num")
add("vars[meta_key]", "_wp_manga_week_views_value")
add("vars[order]", "DESC")
}
"views" -> {
add("vars[orderby]", "meta_value_num")
add("vars[meta_key]", "_wp_manga_views")
add("vars[order]", "DESC")
}
else -> {
add("vars[orderby]", "date")
add("vars[order]", "DESC")
}
}
}
}
is AdultContentFilter -> {
if (filter.state != 0) {
add("vars[meta_query][$metaQueryIdx][key]", "manga_adult_content")
add(
"vars[meta_query][$metaQueryIdx][compare]",
if (filter.state == 1) "not exists" else "exists",
)
protected open val orderByFilterTitle: String = when (lang) {
"pt-BR" -> "Ordenar por"
else -> "Order By"
metaQueryIdx++
}
}
is GenreConditionFilter -> {
if (filter.state == 1 && genres.isNotEmpty()) {
add("vars[tax_query][$taxQueryIdx][operation]", "AND")
}
}
is GenreList -> {
if (genres.isNotEmpty()) {
add("vars[tax_query][$taxQueryIdx][taxonomy]", "wp-manga-genre")
add("vars[tax_query][$taxQueryIdx][field]", "slug")
genres.forEachIndexed { i, slug ->
add("vars[tax_query][$taxQueryIdx][terms][$i]", slug)
}
protected open val orderByFilterOptions: Array<String> = when (lang) {
"pt-BR" -> arrayOf(
"Relevância",
"Recentes",
"A-Z",
"Avaliação",
"Tendência",
"Visualizações",
"Novos",
)
else -> arrayOf(
"Relevance",
"Latest",
"A-Z",
"Rating",
"Trending",
"Most Views",
"New",
)
taxQueryIdx++
}
}
else -> {}
}
}
}.build()
return POST("$baseUrl/wp-admin/admin-ajax.php", xhrHeaders, formBody)
}
protected open val orderByFilterOptionsValues: Array<String> = arrayOf(
"",
"latest",
"alphabet",
"rating",
"trending",
"views",
"new-manga",
protected open val statusFilterOptions: Map<String, String> =
mapOf(
intl["status_filter_completed"] to "end",
intl["status_filter_ongoing"] to "on-going",
intl["status_filter_canceled"] to "canceled",
intl["status_filter_on_hold"] to "on-hold",
)
protected open val genreConditionFilterTitle: String = when (lang) {
"pt-BR" -> "Operador dos gêneros"
else -> "Genre condition"
protected open val orderByFilterOptions: Map<String, String> = mapOf(
intl["order_by_filter_relevance"] to "",
intl["order_by_filter_latest"] to "latest",
intl["order_by_filter_az"] to "alphabet",
intl["order_by_filter_rating"] to "rating",
intl["order_by_filter_trending"] to "trending",
intl["order_by_filter_views"] to "views",
intl["order_by_filter_new"] to "new-manga",
)
protected open val genreConditionFilterOptions: Array<String> =
arrayOf(
intl["genre_condition_filter_or"],
intl["genre_condition_filter_and"],
)
protected open val adultContentFilterOptions: Array<String> =
arrayOf(
intl["adult_content_filter_all"],
intl["adult_content_filter_none"],
intl["adult_content_filter_only"],
)
open class UriPartFilter(displayName: String, private val vals: Array<Pair<String, String>>, state: Int = 0) :
Filter.Select<String>(displayName, vals.map { it.first }.toTypedArray(), state) {
fun toUriPart() = vals[state].second
}
protected open val genreConditionFilterOptions: Array<String> = when (lang) {
"pt-BR" -> arrayOf("OU", "E")
else -> arrayOf("OR", "AND")
}
protected open val adultContentFilterTitle: String = when (lang) {
"pt-BR" -> "Conteúdo adulto"
else -> "Adult Content"
}
protected open val adultContentFilterOptions: Array<String> = when (lang) {
"pt-BR" -> arrayOf("Indiferente", "Nenhum", "Somente")
else -> arrayOf("All", "None", "Only")
}
protected open val genreFilterHeader: String = when (lang) {
"pt-BR" -> "O filtro de gêneros pode não funcionar"
else -> "Genres filter may not work for all sources"
}
protected open val genreFilterTitle: String = when (lang) {
"pt-BR" -> "Gêneros"
else -> "Genres"
}
protected open val genresMissingWarning: String = when (lang) {
"pt-BR" -> "Aperte 'Redefinir' para tentar mostrar os gêneros"
else -> "Press 'Reset' to attempt to show the genres"
}
open class Tag(val id: String, name: String) : Filter.CheckBox(name)
protected class AuthorFilter(title: String) : Filter.Text(title)
protected class ArtistFilter(title: String) : Filter.Text(title)
@ -386,50 +519,60 @@ abstract class Madara(
class Genre(name: String, val id: String = name) : Filter.CheckBox(name)
override fun getFilterList(): FilterList {
launchIO { fetchGenres() }
val filters = mutableListOf(
AuthorFilter(authorFilterTitle),
ArtistFilter(artistFilterTitle),
YearFilter(yearFilterTitle),
StatusFilter(statusFilterTitle, getStatusList()),
AuthorFilter(intl["author_filter_title"]),
ArtistFilter(intl["artist_filter_title"]),
YearFilter(intl["year_filter_title"]),
StatusFilter(
title = intl["status_filter_title"],
status = statusFilterOptions.map { Tag(it.key, it.value) },
),
OrderByFilter(
title = orderByFilterTitle,
options = orderByFilterOptions.zip(orderByFilterOptionsValues),
title = intl["order_by_filter_title"],
options = orderByFilterOptions.map { Pair(it.key, it.value) },
state = 0,
),
AdultContentFilter(adultContentFilterTitle, adultContentFilterOptions),
AdultContentFilter(
title = intl["adult_content_filter_title"],
options = adultContentFilterOptions,
),
)
if (genresList.isNotEmpty()) {
filters += listOf(
Filter.Separator(),
Filter.Header(genreFilterHeader),
GenreConditionFilter(genreConditionFilterTitle, genreConditionFilterOptions),
GenreList(genreFilterTitle, genresList),
Filter.Header(intl["genre_filter_header"]),
GenreConditionFilter(
title = intl["genre_condition_filter_title"],
options = genreConditionFilterOptions,
),
GenreList(
title = intl["genre_filter_title"],
genres = genresList,
),
)
} else if (fetchGenres) {
filters += listOf(
Filter.Separator(),
Filter.Header(genresMissingWarning),
Filter.Header(intl["genre_missing_warning"]),
)
}
return FilterList(filters)
}
protected fun getStatusList() = statusFilterOptionsValues
.zip(statusFilterOptions)
.map { Tag(it.first, it.second) }
open class UriPartFilter(displayName: String, private val vals: Array<Pair<String, String>>, state: Int = 0) :
Filter.Select<String>(displayName, vals.map { it.first }.toTypedArray(), state) {
fun toUriPart() = vals[state].second
}
open class Tag(val id: String, name: String) : Filter.CheckBox(name)
override fun searchMangaParse(response: Response): MangasPage {
runCatching { fetchGenres() }
return super.searchMangaParse(response)
val document = response.asJsoup()
val entries = document.select(searchMangaSelector())
.map(::searchMangaFromElement)
val hasNextPage = searchMangaNextPageSelector()?.let { document.selectFirst(it) } != null
detectLoadMore(document)
return MangasPage(entries, hasNextPage)
}
override fun searchMangaSelector() = "div.c-tabs-item__content"
@ -438,11 +581,11 @@ abstract class Madara(
val manga = SManga.create()
with(element) {
select("div.post-title a").first()?.let {
selectFirst("div.post-title a")!!.let {
manga.setUrlWithoutDomain(it.attr("abs:href"))
manga.title = it.ownText()
}
select("img").first()?.let {
selectFirst("img")?.let {
manga.thumbnail_url = imageFromElement(it)
}
}
@ -450,7 +593,7 @@ abstract class Madara(
return manga
}
override fun searchMangaNextPageSelector(): String? = "div.nav-previous, nav.navigation-ajax, a.nextpostslink"
override fun searchMangaNextPageSelector() = popularMangaNextPageSelector()
// Manga Details Parse
@ -493,9 +636,7 @@ abstract class Madara(
override fun mangaDetailsParse(document: Document): SManga {
val manga = SManga.create()
with(document) {
select(mangaDetailsSelectorTitle).first()?.let {
manga.title = it.ownText()
}
manga.title = selectFirst(mangaDetailsSelectorTitle)!!.ownText()
select(mangaDetailsSelectorAuthor).eachText().filter {
it.notUpdating()
}.joinToString().takeIf { it.isNotBlank() }?.let {
@ -515,7 +656,7 @@ abstract class Madara(
manga.description = it.text()
}
}
select(mangaDetailsSelectorThumbnail).first()?.let {
selectFirst(mangaDetailsSelectorThumbnail)?.let {
manga.thumbnail_url = imageFromElement(it)
}
select(mangaDetailsSelectorStatus).last()?.let {
@ -533,13 +674,6 @@ abstract class Madara(
.map { element -> element.text().lowercase(Locale.ROOT) }
.toMutableSet()
// add tag(s) to genre
val mangaTitle = try {
manga.title
} catch (_: UninitializedPropertyAccessException) {
"not initialized"
}
if (mangaDetailsSelectorTag.isNotEmpty()) {
select(mangaDetailsSelectorTag).forEach { element ->
if (genres.contains(element.text()).not() &&
@ -547,7 +681,7 @@ abstract class Madara(
element.text().contains("read", true).not() &&
element.text().contains(name, true).not() &&
element.text().contains(name.replace(" ", ""), true).not() &&
element.text().contains(mangaTitle, true).not() &&
element.text().contains(manga.title, true).not() &&
element.text().contains(altName, true).not()
) {
genres.add(element.text().lowercase(Locale.ROOT))
@ -556,13 +690,13 @@ abstract class Madara(
}
// add manga/manhwa/manhua thinggy to genre
document.select(seriesTypeSelector).firstOrNull()?.ownText()?.let {
document.selectFirst(seriesTypeSelector)?.ownText()?.let {
if (it.isEmpty().not() && it.notUpdating() && it != "-" && genres.contains(it).not()) {
genres.add(it.lowercase(Locale.ROOT))
}
}
manga.genre = genres.toList().joinToString(", ") { genre ->
manga.genre = genres.toList().joinToString { genre ->
genre.replaceFirstChar {
if (it.isLowerCase()) {
it.titlecase(
@ -575,7 +709,7 @@ abstract class Madara(
}
// add alternative name to manga description
document.select(altNameSelector).firstOrNull()?.ownText()?.let {
document.selectFirst(altNameSelector)?.ownText()?.let {
if (it.isBlank().not() && it.notUpdating()) {
manga.description = when {
manga.description.isNullOrBlank() -> altName + it
@ -600,17 +734,14 @@ abstract class Madara(
open val seriesTypeSelector = ".post-content_item:contains(Type) .summary-content"
open val altNameSelector = ".post-content_item:contains(Alt) .summary-content"
open val altName = when (lang) {
"pt-BR" -> "Nomes alternativos: "
else -> "Alternative Names: "
}
open val altName = intl["alt_names_heading"]
open val updatingRegex = "Updating|Atualizando".toRegex(RegexOption.IGNORE_CASE)
fun String.notUpdating(): Boolean {
return this.contains(updatingRegex).not()
}
fun String.containsIn(array: Array<String>): Boolean {
private fun String.containsIn(array: Array<String>): Boolean {
return this.lowercase() in array.map { it.lowercase() }
}
@ -644,25 +775,18 @@ abstract class Madara(
.add("manga", mangaId)
.build()
val xhrHeaders = headersBuilder()
.add("Content-Length", form.contentLength().toString())
.add("Content-Type", form.contentType().toString())
.add("X-Requested-With", "XMLHttpRequest")
.build()
return POST("$baseUrl/wp-admin/admin-ajax.php", xhrHeaders, form)
}
protected open fun xhrChaptersRequest(mangaUrl: String): Request {
val xhrHeaders = headersBuilder()
.add("X-Requested-With", "XMLHttpRequest")
.build()
return POST("$mangaUrl/ajax/chapters", xhrHeaders)
}
override fun chapterListParse(response: Response): List<SChapter> {
val document = response.asJsoup()
launchIO { countViews(document) }
val chaptersWrapper = document.select("div[id^=manga-chapters-holder]")
var chapterElements = document.select(chapterListSelector())
@ -692,8 +816,6 @@ abstract class Madara(
xhrResponse.close()
}
countViews(document)
return chapterElements.map(::chapterFromElement)
}
@ -710,7 +832,7 @@ abstract class Madara(
val chapter = SChapter.create()
with(element) {
select(chapterUrlSelector).first()?.let { urlElement ->
selectFirst(chapterUrlSelector)!!.let { urlElement ->
chapter.url = urlElement.attr("abs:href").let {
it.substringBefore("?style=paged") + if (!it.endsWith(chapterUrlSuffix)) chapterUrlSuffix else ""
}
@ -718,9 +840,9 @@ abstract class Madara(
}
// Dates can be part of a "new" graphic or plain text
// Added "title" alternative
chapter.date_upload = select("img:not(.thumb)").firstOrNull()?.attr("alt")?.let { parseRelativeDate(it) }
?: select("span a").firstOrNull()?.attr("title")?.let { parseRelativeDate(it) }
?: parseChapterDate(select(chapterDateSelector()).firstOrNull()?.text())
chapter.date_upload = selectFirst("img:not(.thumb)")?.attr("alt")?.let { parseRelativeDate(it) }
?: selectFirst("span a")?.attr("title")?.let { parseRelativeDate(it) }
?: parseChapterDate(selectFirst(chapterDateSelector())?.text())
}
return chapter
@ -816,7 +938,7 @@ abstract class Madara(
open val chapterProtectorSelector = "#chapter-protector-data"
override fun pageListParse(document: Document): List<Page> {
countViews(document)
launchIO { countViews(document) }
val chapterProtector = document.selectFirst(chapterProtectorSelector)
?: return document.select(pageListParseSelector).mapIndexed { index, element ->
@ -836,7 +958,7 @@ abstract class Madara(
val unsaltedCiphertext = Base64.decode(chapterData["ct"]!!.jsonPrimitive.content, Base64.DEFAULT)
val salt = chapterData["s"]!!.jsonPrimitive.content.decodeHex()
val ciphertext = SALTED + salt + unsaltedCiphertext
val ciphertext = salted + salt + unsaltedCiphertext
val rawImgArray = CryptoAES.decrypt(Base64.encodeToString(ciphertext, Base64.DEFAULT), password)
val imgArrayString = json.parseToJsonElement(rawImgArray).jsonPrimitive.content
@ -860,7 +982,7 @@ abstract class Madara(
protected open val sendViewCount: Boolean = true
protected open fun countViewsRequest(document: Document): Request? {
val wpMangaData = document.select("script#wp-manga-js-extra").firstOrNull()
val wpMangaData = document.selectFirst("script#wp-manga-js-extra")
?.data() ?: return null
val wpMangaInfo = wpMangaData
@ -882,8 +1004,6 @@ abstract class Madara(
val formBody = formBuilder.build()
val newHeaders = headersBuilder()
.set("Content-Length", formBody.contentLength().toString())
.set("Content-Type", formBody.contentType().toString())
.set("Referer", document.location())
.build()
@ -912,18 +1032,17 @@ abstract class Madara(
/**
* Fetch the genres from the source to be used in the filters.
*/
protected open fun fetchGenres() {
if (fetchGenres && fetchGenresAttempts <= 3 && (genresList.isEmpty() || fetchGenresFailed)) {
val genres = runCatching {
client.newCall(genresRequest()).execute()
protected fun fetchGenres() {
if (fetchGenres && fetchGenresAttempts < 3 && genresList.isEmpty()) {
try {
genresList = client.newCall(genresRequest()).execute()
.use { parseGenres(it.asJsoup()) }
}
fetchGenresFailed = genres.isFailure
genresList = genres.getOrNull().orEmpty()
} catch (_: Exception) {
} finally {
fetchGenresAttempts++
}
}
}
/**
* The request to the search page (or another one) that have the genres list.
@ -950,7 +1069,7 @@ abstract class Madara(
}
// https://stackoverflow.com/a/66614516
private fun String.decodeHex(): ByteArray {
protected fun String.decodeHex(): ByteArray {
check(length % 2 == 0) { "Must have an even length" }
return chunked(2)
@ -958,13 +1077,14 @@ abstract class Madara(
.toByteArray()
}
override fun setupPreferenceScreen(screen: PreferenceScreen) {
addRandomUAPreferenceToScreen(screen)
}
protected val salted = "Salted__".toByteArray(Charsets.UTF_8)
private val scope = CoroutineScope(Dispatchers.IO)
protected fun launchIO(block: () -> Unit) = scope.launch { block() }
companion object {
const val URL_SEARCH_PREFIX = "slug:"
val SALTED = "Salted__".toByteArray(Charsets.UTF_8)
}
}

View File

@ -4,6 +4,4 @@ import eu.kanade.tachiyomi.multisrc.madara.Madara
class ComiczNetV2 : Madara("Comicz.net v2", "https://v2.comiz.net", "all") {
override val useNewChapterEndpoint = false
override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/"
}

View File

@ -14,14 +14,6 @@ class GrabberZone : Madara(
) {
override val mangaSubString = "comics"
override fun searchPage(page: Int): String {
return if (page > 1) {
"page/$page/"
} else {
""
}
}
override fun chapterFromElement(element: Element): SChapter {
return super.chapterFromElement(element).apply {
name = element.selectFirst("a + a")!!.text()

View File

@ -4,6 +4,4 @@ import eu.kanade.tachiyomi.multisrc.madara.Madara
class MangaCrazy : Madara("MangaCrazy", "https://mangacrazy.net", "all") {
override val useNewChapterEndpoint = true
override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/"
}

View File

@ -12,6 +12,4 @@ class MangaTopSite : Madara(
) {
override val useNewChapterEndpoint = false
override val chapterUrlSuffix = ""
override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/"
}

View File

@ -1,16 +1,12 @@
package eu.kanade.tachiyomi.extension.ar.azora
import eu.kanade.tachiyomi.multisrc.madara.Madara
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.source.model.SChapter
import okhttp3.Request
import org.jsoup.nodes.Element
class Azora : Madara("Azora", "https://azoramoon.com", "ar") {
override val mangaSubString = "series"
override val useNewChapterEndpoint = false
override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/"
override fun popularMangaRequest(page: Int): Request = GET("$baseUrl/page/$page/?m_orderby=views", headers)
override fun chapterListSelector() = "li.wp-manga-chapter:not(.premium-block)" // Filter fake chapters
override fun chapterFromElement(element: Element): SChapter {
val chapter = SChapter.create()

View File

@ -9,12 +9,4 @@ class ComicArab : Madara(
"https://comicarab.com",
"ar",
dateFormat = SimpleDateFormat("dd MMMM، yyyy", Locale("ar")),
) {
override fun searchPage(page: Int): String {
return if (page > 1) {
"page/$page/"
} else {
""
}
}
}
)

View File

@ -6,17 +6,20 @@ import android.widget.Toast
import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.extension.BuildConfig
import eu.kanade.tachiyomi.multisrc.madara.Madara
import eu.kanade.tachiyomi.source.ConfigurableSource
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.text.SimpleDateFormat
import java.util.Locale
class EmpireWebtoon : Madara(
class EmpireWebtoon :
Madara(
"Empire Webtoon",
"https://webtoonsempireron.com",
"ar",
SimpleDateFormat("d MMMM، yyyy", Locale("ar")),
) {
),
ConfigurableSource {
private val defaultBaseUrl = "https://webtoonsempireron.com"
@ -30,8 +33,6 @@ class EmpireWebtoon : Madara(
override val useNewChapterEndpoint = false
override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/"
companion object {
private const val RESTART_TACHIYOMI = "Restart Tachiyomi to apply new setting."
private const val BASE_URL_PREF_TITLE = "Override BaseUrl"
@ -53,8 +54,6 @@ class EmpireWebtoon : Madara(
}
}
screen.addPreference(baseUrlPref)
super.setupPreferenceScreen(screen)
}
private fun getPrefBaseUrl(): String = preferences.getString(BASE_URL_PREF, defaultBaseUrl)!!

View File

@ -6,14 +6,13 @@ import android.widget.Toast
import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.extension.BuildConfig
import eu.kanade.tachiyomi.multisrc.madara.Madara
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST
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.SManga
import kotlinx.serialization.Serializable
import kotlinx.serialization.decodeFromString
import okhttp3.CacheControl
import okhttp3.FormBody
import okhttp3.Request
import okhttp3.Response
@ -22,7 +21,16 @@ import uy.kohesive.injekt.api.get
import java.text.SimpleDateFormat
import java.util.Locale
class Mangalek : Madara("مانجا ليك", "https://manga-lek.net", "ar", SimpleDateFormat("MMMM dd, yyyy", Locale("ar"))) {
class Mangalek :
Madara(
"مانجا ليك",
"https://manga-lek.net",
"ar",
SimpleDateFormat("MMMM dd, yyyy", Locale("ar")),
),
ConfigurableSource {
override val fetchGenres = false
override val chapterUrlSuffix = ""
@ -55,22 +63,10 @@ class Mangalek : Madara("مانجا ليك", "https://manga-lek.net", "ar", Simp
}
}
screen.addPreference(baseUrlPref)
super.setupPreferenceScreen(screen)
}
private fun getPrefBaseUrl(): String = preferences.getString(BASE_URL_PREF, defaultBaseUrl)!!
override fun popularMangaRequest(page: Int): Request {
return GET(
url = "$baseUrl/$mangaSubString/${searchPage(page)}",
headers = headers,
cache = CacheControl.FORCE_NETWORK,
)
}
override fun latestUpdatesRequest(page: Int): Request = popularMangaRequest(page)
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request =
POST(
"$baseUrl/wp-admin/admin-ajax.php",

View File

@ -6,12 +6,20 @@ import android.widget.Toast
import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.extension.BuildConfig
import eu.kanade.tachiyomi.multisrc.madara.Madara
import eu.kanade.tachiyomi.source.ConfigurableSource
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.text.SimpleDateFormat
import java.util.Locale
class Mangalink : Madara("مانجا لينك", "https://manga-link.com", "ar", SimpleDateFormat("MMMM dd, yyyy", Locale("ar"))) {
class Mangalink :
Madara(
"مانجا لينك",
"https://manga-link.com",
"ar",
SimpleDateFormat("MMMM dd, yyyy", Locale("ar")),
),
ConfigurableSource {
override val chapterUrlSuffix = ""
@ -44,8 +52,6 @@ class Mangalink : Madara("مانجا لينك", "https://manga-link.com", "ar",
}
}
screen.addPreference(baseUrlPref)
super.setupPreferenceScreen(screen)
}
private fun getPrefBaseUrl(): String = preferences.getString(BASE_URL_PREF, defaultBaseUrl)!!
}

View File

@ -9,12 +9,12 @@ class MangaLionz : Madara("MangaLionz", "https://mangalionz.org", "ar") {
val manga = SManga.create()
with(element) {
select(popularMangaUrlSelector).first()?.let {
selectFirst(popularMangaUrlSelector)!!.let {
manga.setUrlWithoutDomain(it.attr("abs:href"))
manga.title = it.ownText()
}
select("img").first()?.let {
selectFirst("img")?.let {
manga.thumbnail_url = imageFromElement(it)?.replace("mangalionz", "mangalek")
}
}

View File

@ -9,12 +9,4 @@ class MangaRose : Madara(
"https://mangarose.net",
"ar",
dateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale("ar")),
) {
override fun searchPage(page: Int): String {
return if (page > 1) {
"page/$page/"
} else {
""
}
}
}
)

View File

@ -13,6 +13,4 @@ class MangaSpark : Madara(
override val chapterUrlSuffix = ""
override val useNewChapterEndpoint = false
override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/"
}

View File

@ -13,6 +13,4 @@ class MangaStarz : Madara(
override val chapterUrlSuffix = ""
override val useNewChapterEndpoint = false
override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/"
}

View File

@ -1,11 +1,5 @@
package eu.kanade.tachiyomi.extension.en.allporncomic
import eu.kanade.tachiyomi.multisrc.madara.Madara
import eu.kanade.tachiyomi.network.GET
import okhttp3.Request
class AllPornComic : Madara("AllPornComic", "https://allporncomic.com", "en") {
override fun popularMangaRequest(page: Int): Request = GET("$baseUrl/manga/page/$page/?m_orderby=views", headers)
override fun latestUpdatesRequest(page: Int): Request = GET("$baseUrl/manga/page/$page/?m_orderby=latest", headers)
override fun searchMangaNextPageSelector() = "a[rel=next]"
}
class AllPornComic : Madara("AllPornComic", "https://allporncomic.com", "en")

View File

@ -8,8 +8,6 @@ import kotlin.random.Random
class AquaManga : Madara("Aqua Manga", "https://aquamanga.org", "en") {
override val useNewChapterEndpoint = false
override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/"
override fun headersBuilder(): Headers.Builder = super.headersBuilder()
.add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8")
.add("Accept-Language", "en-US,en;q=0.5")

View File

@ -4,6 +4,4 @@ import eu.kanade.tachiyomi.multisrc.madara.Madara
class AsuraScansUs : Madara("Asura Scans.us (unoriginal)", "https://asurascans.us", "en") {
override val useNewChapterEndpoint = true
override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/"
}

View File

@ -1,37 +1,11 @@
package eu.kanade.tachiyomi.extension.en.babelwuxia
import eu.kanade.tachiyomi.multisrc.madara.Madara
import eu.kanade.tachiyomi.source.model.MangasPage
import okhttp3.Response
class BabelWuxia : Madara("Babel Wuxia", "https://babelwuxia.com", "en") {
// moved from MangaThemesia
override val versionId = 2
override val useNewChapterEndpoint = true
override fun searchPage(page: Int): String {
return if (page > 1) {
"page/$page/"
} else {
""
}
}
override fun popularMangaParse(response: Response) =
super.popularMangaParse(response).fixNextPage()
override fun latestUpdatesParse(response: Response) =
super.latestUpdatesParse(response).fixNextPage()
override fun searchMangaParse(response: Response) =
super.searchMangaParse(response).fixNextPage()
private fun MangasPage.fixNextPage(): MangasPage {
return if (mangas.size < 12) {
MangasPage(mangas, false)
} else {
this
}
}
override val useLoadMoreRequest = LoadMoreStrategy.Always
}

View File

@ -4,6 +4,4 @@ import eu.kanade.tachiyomi.multisrc.madara.Madara
class BananaManga : Madara("Banana Manga", "https://bananamanga.net", "en") {
override val useNewChapterEndpoint = true
override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/"
}

View File

@ -6,8 +6,6 @@ import org.jsoup.nodes.Element
class CoffeeManga : Madara("Coffee Manga", "https://coffeemanga.io", "en") {
override val useNewChapterEndpoint = false
override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/"
override fun imageFromElement(element: Element): String? {
return when {
element.hasAttr("data-src") && element.attr("data-src").isNotEmpty() -> element.attr("abs:data-src")

View File

@ -11,6 +11,4 @@ class ColoredManga : Madara(
dateFormat = SimpleDateFormat("dd-MMM", Locale.ENGLISH),
) {
override val useNewChapterEndpoint = true
override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/"
}

View File

@ -4,6 +4,4 @@ import eu.kanade.tachiyomi.multisrc.madara.Madara
class ComicScans : Madara("Comic Scans", "https://www.comicscans.org", "en") {
override val useNewChapterEndpoint = true
override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/"
}

View File

@ -14,7 +14,6 @@ import kotlinx.serialization.json.boolean
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import okhttp3.CacheControl
import okhttp3.FormBody
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient
@ -35,26 +34,6 @@ class CreepyScans : Madara(
override val useNewChapterEndpoint = true
// Popular
override fun popularMangaRequest(page: Int): Request {
return GET(
url = "$baseUrl/$mangaSubString/?m_orderby=views",
headers = headers,
cache = CacheControl.FORCE_NETWORK,
)
}
// Latest
override fun latestUpdatesRequest(page: Int): Request {
return GET(
url = "$baseUrl/$mangaSubString/?m_orderby=latest",
headers = headers,
cache = CacheControl.FORCE_NETWORK,
)
}
// Search
override fun fetchSearchManga(
@ -142,11 +121,13 @@ class CreepyScans : Madara(
Filter.Select<String>("Genre", vals.map { it.first }.toTypedArray())
override fun getFilterList(): FilterList {
launchIO { fetchGenres() }
val filters = buildList(4) {
add(
OrderByFilter(
title = orderByFilterTitle,
options = orderByFilterOptions.zip(orderByFilterOptionsValues),
title = intl["order_by_filter_title"],
options = orderByFilterOptions.map { Pair(it.key, it.value) },
state = 0,
),
)

View File

@ -4,6 +4,4 @@ import eu.kanade.tachiyomi.multisrc.madara.Madara
class DarkScan : Madara("Dark-scan", "https://dark-scan.com", "en") {
override val useNewChapterEndpoint = true
override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/"
}

View File

@ -27,14 +27,6 @@ class DragonTea : Madara(
override val useNewChapterEndpoint = true
override fun searchPage(page: Int): String {
return if (page > 1) {
"page/$page/"
} else {
""
}
}
private val pageIndexRegex = Regex("""image-(\d+)[a-z]+""", RegexOption.IGNORE_CASE)
override fun pageListParse(document: Document): List<Page> {
@ -69,20 +61,8 @@ class DragonTea : Madara(
val unsaltedCiphertext = Base64.decode(cipherData["ct"]!!.jsonPrimitive.content, Base64.DEFAULT)
val salt = cipherData["s"]!!.jsonPrimitive.content.decodeHex()
val saltedCiphertext = SALTED + salt + unsaltedCiphertext
val saltedCiphertext = salted + salt + unsaltedCiphertext
return json.parseToJsonElement(CryptoAES.decrypt(Base64.encodeToString(saltedCiphertext, Base64.DEFAULT), key))
}
private fun String.decodeHex(): ByteArray {
check(length % 2 == 0) { "Must have an even length" }
return chunked(2)
.map { it.toInt(16).toByte() }
.toByteArray()
}
companion object {
private val SALTED = "Salted__".toByteArray(Charsets.UTF_8)
}
}

View File

@ -5,6 +5,4 @@ import eu.kanade.tachiyomi.multisrc.madara.Madara
class EliteManga : Madara("Elite Manga", "https://www.elitemanga.org", "en") {
override val useNewChapterEndpoint = true
override val filterNonMangaItems = false
override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/"
}

View File

@ -4,6 +4,4 @@ import eu.kanade.tachiyomi.multisrc.madara.Madara
class FactManga : Madara("FactManga", "https://factmanga.com", "en") {
override val useNewChapterEndpoint = true
override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/"
}

View File

@ -20,7 +20,7 @@ class FireScans : Madara("Fire Scans", "https://firescans.xyz", "en") {
override val useNewChapterEndpoint: Boolean = true
override fun pageListParse(document: Document): List<Page> {
countViews(document)
launchIO { countViews(document) }
val chapterProtector = document.selectFirst(chapterProtectorSelector)
?: return document.select(pageListParseSelector).mapIndexed { index, element ->
@ -46,7 +46,7 @@ class FireScans : Madara("Fire Scans", "https://firescans.xyz", "en") {
val unsaltedCiphertext = Base64.decode(chapterData["ct"]!!.jsonPrimitive.content, Base64.DEFAULT)
val salt = chapterData["s"]!!.jsonPrimitive.content.decodeHex()
val ciphertext = SALTED + salt + unsaltedCiphertext
val ciphertext = salted + salt + unsaltedCiphertext
val rawImgArray = CryptoAES.decrypt(Base64.encodeToString(ciphertext, Base64.DEFAULT), password)
val imgArrayString = json.parseToJsonElement(rawImgArray).jsonPrimitive.content
@ -56,12 +56,4 @@ class FireScans : Madara("Fire Scans", "https://firescans.xyz", "en") {
Page(idx, document.location(), it.jsonPrimitive.content)
}
}
private fun String.decodeHex(): ByteArray {
check(length % 2 == 0) { "Must have an even length" }
return chunked(2)
.map { it.toInt(16).toByte() }
.toByteArray()
}
}

View File

@ -4,6 +4,4 @@ import eu.kanade.tachiyomi.multisrc.madara.Madara
class FirstKissDashManga : Madara("1st Kiss-Manga (unoriginal)", "https://1stkiss-manga.com", "en") {
override val useNewChapterEndpoint = false
override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/"
}

View File

@ -6,6 +6,4 @@ class FirstManhwa : Madara("1st Manhwa", "https://1stmanhwa.com", "en") {
override val useNewChapterEndpoint = true
override val filterNonMangaItems = false
override val mangaDetailsSelectorStatus = "div.summary-heading:contains(Status) + div.summary-content"
override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/"
}

View File

@ -4,6 +4,4 @@ import eu.kanade.tachiyomi.multisrc.madara.Madara
class FreeManhwa : Madara("Free Manhwa", "https://manhwas.com", "en") {
override val useNewChapterEndpoint = false
override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/"
}

View File

@ -4,6 +4,4 @@ import eu.kanade.tachiyomi.multisrc.madara.Madara
class GirlsLoveManga : Madara("Girls Love Manga!", "https://glmanga.com", "en") {
override val useNewChapterEndpoint = true
override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/"
}

View File

@ -12,8 +12,6 @@ class GlobalBloging : Madara(
) {
override val useNewChapterEndpoint = true
override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/"
// =========================== Manga Details ============================
override val mangaDetailsSelectorThumbnail = "${super.mangaDetailsSelectorThumbnail}[src~=.]"

View File

@ -2,72 +2,21 @@ package eu.kanade.tachiyomi.extension.en.goodgirlsscan
import eu.kanade.tachiyomi.multisrc.madara.Madara
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.SManga
import okhttp3.FormBody
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Request
import org.jsoup.nodes.Element
class GoodGirlsScan : Madara("Good Girls Scan", "https://goodgirls.moe", "en") {
override val fetchGenres = false
override fun popularMangaNextPageSelector() = "body:not(:has(.no-posts))"
override val useLoadMoreRequest = LoadMoreStrategy.Always
override fun searchMangaSelector() = "article.wp-manga"
override fun searchMangaNextPageSelector() = "div.paginator .nav-next"
override val mangaDetailsSelectorStatus = "div.summary-heading:contains(Status) + div.summary-content"
override val mangaDetailsSelectorDescription = "div.summary-specialfields"
override fun chapterListSelector() = "li.wp-manga-chapter:not(.vip-permission)"
private fun madaraLoadMoreRequest(page: Int, metaKey: String): Request {
val formBody = FormBody.Builder().apply {
add("action", "madara_load_more")
add("page", page.toString())
add("template", "madara-core/content/content-archive")
add("vars[paged]", "1")
add("vars[orderby]", "meta_value_num")
add("vars[template]", "archive")
add("vars[sidebar]", "right")
add("vars[post_type]", "wp-manga")
add("vars[post_status]", "publish")
add("vars[meta_key]", metaKey)
add("vars[meta_query][0][paged]", "1")
add("vars[meta_query][0][orderby]", "meta_value_num")
add("vars[meta_query][0][template]", "archive")
add("vars[meta_query][0][sidebar]", "right")
add("vars[meta_query][0][post_type]", "wp-manga")
add("vars[meta_query][0][post_status]", "publish")
add("vars[meta_query][0][meta_key]", metaKey)
add("vars[meta_query][relation]", "AND")
add("vars[manga_archives_item_layout]", "default")
}.build()
val xhrHeaders = headersBuilder()
.add("Content-Length", formBody.contentLength().toString())
.add("Content-Type", formBody.contentType().toString())
.add("X-Requested-With", "XMLHttpRequest")
.build()
return POST("$baseUrl/wp-admin/admin-ajax.php", xhrHeaders, formBody)
}
override fun popularMangaRequest(page: Int): Request {
return madaraLoadMoreRequest(page - 1, "_wp_manga_views")
}
override fun latestUpdatesRequest(page: Int): Request {
return madaraLoadMoreRequest(page - 1, "_latest_update")
}
override fun searchPage(page: Int): String {
return if (page > 1) {
"page/$page/"
} else {
""
}
}
// heavily modified madara theme, throws 5xx errors on any search filter
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
val url = "$baseUrl/${searchPage(page)}".toHttpUrl().newBuilder().apply {

View File

@ -21,8 +21,6 @@ class GourmetScans : Madara(
// Search
override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/"
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
val url = baseUrl.toHttpUrl().newBuilder()
@ -77,23 +75,23 @@ class GourmetScans : Madara(
private var genresList: List<Pair<String, String>> = emptyList()
class GenreFilter(val vals: List<Pair<String, String>>) :
class GenreFilter(vals: List<Pair<String, String>>) :
UriPartFilter("Genre", vals.toTypedArray())
override fun getFilterList(): FilterList {
val filters = buildList(4) {
add(YearFilter(yearFilterTitle))
add(YearFilter(intl["year_filter_title"]))
add(
OrderByFilter(
title = orderByFilterTitle,
options = orderByFilterOptions.zip(orderByFilterOptionsValues),
title = intl["order_by_filter_title"],
options = orderByFilterOptions.map { Pair(it.key, it.value) },
state = 0,
),
)
add(Filter.Separator())
if (genresList.isEmpty()) {
add(Filter.Header(genresMissingWarning))
add(Filter.Header(intl["genre_missing_warning"]))
} else {
add(GenreFilter(listOf(Pair("<select>", "")) + genresList))
}

View File

@ -4,6 +4,4 @@ import eu.kanade.tachiyomi.multisrc.madara.Madara
class Hentai3z : Madara("Hentai3z", "https://hentai3z.xyz", "en") {
override val useNewChapterEndpoint = false
override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/"
}

View File

@ -8,8 +8,6 @@ class Hentai4Free : Madara("Hentai4Free", "https://hentai4free.net", "en") {
override val useNewChapterEndpoint = true
override val mangaSubString = "hentai"
override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/"
override fun popularMangaSelector() = searchMangaSelector()
override fun popularMangaRequest(page: Int): Request =

View File

@ -22,10 +22,10 @@ class Hentairead : Madara("HentaiRead", "https://hentairead.com", "en", dateForm
override val pageListParseSelector = "li.chapter-image-item > a > div.image-wrapper"
override fun pageListParse(document: Document): List<Page> {
super.countViews(document)
launchIO { countViews(document) }
return document.select(pageListParseSelector).mapIndexed { index, element ->
var pageUri: String? = element.select("img").first()?.let {
val pageUri: String? = element.selectFirst("img")!!.let {
it.absUrl(if (it.hasAttr("data-src")) "data-src" else "src")
}
Page(

View File

@ -7,14 +7,6 @@ import org.jsoup.nodes.Document
class HentaiXDickgirl : Madara("HentaiXDickgirl", "https://hentaixdickgirl.com", "en") {
override fun searchPage(page: Int): String {
return if (page > 1) {
"page/$page/"
} else {
""
}
}
override fun mangaDetailsParse(document: Document): SManga {
return super.mangaDetailsParse(document).apply {
update_strategy = UpdateStrategy.ONLY_FETCH_ONCE

View File

@ -6,23 +6,22 @@ import android.widget.Toast
import androidx.preference.EditTextPreference
import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.multisrc.madara.Madara
import eu.kanade.tachiyomi.source.ConfigurableSource
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Interceptor
import okhttp3.Response
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
class Hiperdex : Madara("Hiperdex", "https://hiperdex.com", "en") {
class Hiperdex :
Madara(
"Hiperdex",
"https://hiperdex.com",
"en",
),
ConfigurableSource {
override val useNewChapterEndpoint: Boolean = true
override fun searchPage(page: Int): String {
return if (page > 1) {
"page/$page/"
} else {
""
}
}
override val baseUrl by lazy { getPrefBaseUrl() }
private val preferences by lazy {
@ -103,8 +102,6 @@ class Hiperdex : Madara("Hiperdex", "https://hiperdex.com", "en") {
}
}
screen.addPreference(baseUrlPref)
super.setupPreferenceScreen(screen)
}
private var SharedPreferences.baseUrlHost

View File

@ -40,9 +40,6 @@ class IsekaiScanTop : Madara(
if (chapterElements.isEmpty() && !chaptersWrapper.isNullOrEmpty()) {
val mangaId = chaptersWrapper.attr("data-id")
val xhrHeaders = headersBuilder()
.add("X-Requested-With", "XMLHttpRequest")
.build()
val xhrRequest = GET("$baseUrl/ajax-list-chapter?mangaID=$mangaId", xhrHeaders)
val xhrResponse = client.newCall(xhrRequest).execute()
@ -50,7 +47,7 @@ class IsekaiScanTop : Madara(
xhrResponse.close()
}
countViews(document)
launchIO { countViews(document) }
return chapterElements.map(::chapterFromElement)
}
@ -65,7 +62,7 @@ class IsekaiScanTop : Madara(
}
}
override fun searchPage(page: Int): String = "search?page=$page"
override fun searchPage(page: Int) = "search?page=$page"
override fun searchMangaNextPageSelector(): String? = "ul.pagination li:last-child a"
override fun searchMangaNextPageSelector() = "ul.pagination li:last-child a"
}

View File

@ -5,6 +5,4 @@ import eu.kanade.tachiyomi.multisrc.madara.Madara
class Jimanga : Madara("Jimanga", "https://jimanga.com", "en") {
override val useNewChapterEndpoint = false
override val filterNonMangaItems = false
override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/"
}

View File

@ -4,6 +4,4 @@ import eu.kanade.tachiyomi.multisrc.madara.Madara
class LuffyManga : Madara("Luffy Manga", "https://luffymanga.com", "en") {
override val useNewChapterEndpoint = false
override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/"
}

View File

@ -4,6 +4,4 @@ import eu.kanade.tachiyomi.multisrc.madara.Madara
class LuxManga : Madara("LuxManga", "https://luxmanga.net", "en") {
override val useNewChapterEndpoint = false
override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/"
}

View File

@ -4,6 +4,4 @@ import eu.kanade.tachiyomi.multisrc.madara.Madara
class Manga18h : Madara("Manga 18h", "https://manga18h.com", "en") {
override val useNewChapterEndpoint = false
override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/"
}

View File

@ -4,6 +4,4 @@ import eu.kanade.tachiyomi.multisrc.madara.Madara
class Manga18x : Madara("Manga 18x", "https://manga18x.net", "en") {
override val useNewChapterEndpoint = true
override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/"
}

View File

@ -12,6 +12,4 @@ class MangaBee : Madara(
) {
override val useNewChapterEndpoint = true
override val filterNonMangaItems = false
override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/"
}

View File

@ -4,6 +4,4 @@ import eu.kanade.tachiyomi.multisrc.madara.Madara
class MangaClashTv : Madara("MangaClash.tv (unoriginal)", "https://mangaclash.tv", "en") {
override val useNewChapterEndpoint = false
override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/"
}

View File

@ -4,6 +4,4 @@ import eu.kanade.tachiyomi.multisrc.madara.Madara
class MangaDash1001Com : Madara("Manga-1001.com", "https://manga-1001.com", "en") {
override val useNewChapterEndpoint = false
override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/"
}

View File

@ -4,6 +4,4 @@ import eu.kanade.tachiyomi.multisrc.madara.Madara
class MangaDinoTop : Madara("MangaDino.top (unoriginal)", "https://mangadino.top", "en") {
override val useNewChapterEndpoint = false
override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/"
}

View File

@ -6,6 +6,4 @@ class MangaHentai : Madara("Manga Hentai", "https://mangahentai.me", "en") {
override val mangaSubString = "manga-hentai"
override val useNewChapterEndpoint: Boolean = true
override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/"
}

View File

@ -5,6 +5,4 @@ import eu.kanade.tachiyomi.multisrc.madara.Madara
class MangaKitsu : Madara("Manga Kitsu", "https://mangakitsu.com", "en") {
override val useNewChapterEndpoint = false
override val mangaDetailsSelectorStatus = "div.summary-heading:contains(Status) + div.summary-content"
override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/"
}

View File

@ -12,7 +12,7 @@ class MangaLeveling : Madara("Manga Leveling", "https://mangaleveling.com", "en"
val chapter = SChapter.create()
with(element) {
select(chapterUrlSelector).first()?.let { urlElement ->
selectFirst(chapterUrlSelector)!!.let { urlElement ->
chapter.url = urlElement.attr("abs:href").let {
it.substringBefore("?style=paged") + if (!it.endsWith(chapterUrlSuffix)) chapterUrlSuffix else ""
}
@ -20,9 +20,9 @@ class MangaLeveling : Madara("Manga Leveling", "https://mangaleveling.com", "en"
}
// Dates can be part of a "new" graphic or plain text
// Added "title" alternative
chapter.date_upload = select("img:not(.thumb)").firstOrNull()?.attr("alt")?.let { parseRelativeDate(it) }
?: select("span a").firstOrNull()?.attr("title")?.let { parseRelativeDate(it) }
?: parseChapterDate(select(chapterDateSelector()).firstOrNull()?.text())
chapter.date_upload = selectFirst("img:not(.thumb)")?.attr("alt")?.let { parseRelativeDate(it) }
?: selectFirst("span a")?.attr("title")?.let { parseRelativeDate(it) }
?: parseChapterDate(selectFirst(chapterDateSelector())?.text())
}
return chapter

View File

@ -4,6 +4,4 @@ import eu.kanade.tachiyomi.multisrc.madara.Madara
class ManganeloBiz : Madara("Manganelo.biz", "https://manganelo.biz", "en") {
override val useNewChapterEndpoint = false
override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/"
}

View File

@ -4,6 +4,4 @@ import eu.kanade.tachiyomi.multisrc.madara.Madara
class ManganeloWebsite : Madara("Manganelo.website (unoriginal)", "https://manganelo.website", "en") {
override val useNewChapterEndpoint = false
override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/"
}

View File

@ -4,6 +4,4 @@ import eu.kanade.tachiyomi.multisrc.madara.Madara
class MangaNerds : Madara("Manga Nerds", "https://manganerds.com", "en") {
override val useNewChapterEndpoint = true
override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/"
}

View File

@ -4,6 +4,4 @@ import eu.kanade.tachiyomi.multisrc.madara.Madara
class MangaOnlineTeamUnoriginal : Madara("MangaOnline.team (unoriginal)", "https://mangaonline.team", "en") {
override val useNewChapterEndpoint = false
override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/"
}

View File

@ -4,6 +4,4 @@ import eu.kanade.tachiyomi.multisrc.madara.Madara
class MangaOwlBlog : Madara("MangaOwl.blog (unoriginal)", "https://mangaowl.blog", "en") {
override val useNewChapterEndpoint = false
override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/"
}

View File

@ -4,6 +4,4 @@ import eu.kanade.tachiyomi.multisrc.madara.Madara
class MangaOwlIo : Madara("MangaOwl.io (unoriginal)", "https://mangaowl.io", "en") {
override val useNewChapterEndpoint = true
override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/"
}

View File

@ -5,6 +5,4 @@ import eu.kanade.tachiyomi.multisrc.madara.Madara
class MangaOwlOne : Madara("MangaOwl.one (unoriginal)", "https://mangaowl.one", "en") {
override val useNewChapterEndpoint = false
override val filterNonMangaItems = false
override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/"
}

View File

@ -4,6 +4,4 @@ import eu.kanade.tachiyomi.multisrc.madara.Madara
class MangaOwlUs : Madara("MangaOwl.us (unoriginal)", "https://mangaowl.us", "en") {
override val useNewChapterEndpoint = true
override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/"
}

View File

@ -51,7 +51,7 @@ class MangaPure : Madara(
xhrResponse.close()
}
countViews(document)
launchIO { countViews(document) }
return chapterElements.map(::chapterFromElement)
}

View File

@ -4,6 +4,4 @@ import eu.kanade.tachiyomi.multisrc.madara.Madara
class MangaQueenCom : Madara("Manga Queen.com", "https://mangaqueen.com", "en") {
override val useNewChapterEndpoint = false
override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/"
}

View File

@ -4,6 +4,4 @@ import eu.kanade.tachiyomi.multisrc.madara.Madara
class MangaRawInfo : Madara("Manga-Raw.info (unoriginal)", "https://manga-raw.info", "en") {
override val useNewChapterEndpoint = false
override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/"
}

View File

@ -4,6 +4,4 @@ import eu.kanade.tachiyomi.multisrc.madara.Madara
class MangaRockTeamUnoriginal : Madara("Manga Rock.team (unoriginal)", "https://mangarock.team", "en") {
override val useNewChapterEndpoint = false
override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/"
}

View File

@ -5,6 +5,4 @@ import eu.kanade.tachiyomi.multisrc.madara.Madara
class MangaRosie : Madara("MangaRosie", "https://mangarosie.in", "en") {
override val useNewChapterEndpoint = false
override val mangaDetailsSelectorStatus = "div.summary-heading:contains(Status) + div.summary-content"
override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/"
}

View File

@ -5,6 +5,4 @@ import eu.kanade.tachiyomi.multisrc.madara.Madara
class MangaRubyCom : Madara("MangaRuby.com", "https://mangaruby.com", "en") {
override val useNewChapterEndpoint = true
override val filterNonMangaItems = false
override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/"
}

View File

@ -4,6 +4,4 @@ import eu.kanade.tachiyomi.multisrc.madara.Madara
class Mangaryu : Madara("Mangaryu", "https://mangaryu.com", "en") {
override val useNewChapterEndpoint = false
override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/"
}

View File

@ -4,6 +4,4 @@ import eu.kanade.tachiyomi.multisrc.madara.Madara
class MangaTxGg : Madara("Manga Tx.gg (unoriginal)", "https://mangatx.gg", "en") {
override val useNewChapterEndpoint = false
override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/"
}

View File

@ -5,6 +5,4 @@ import eu.kanade.tachiyomi.multisrc.madara.Madara
class MangaTyrant : Madara("MangaTyrant", "https://mangatyrant.com", "en") {
override val useNewChapterEndpoint = true
override val filterNonMangaItems = false
override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/"
}

View File

@ -4,6 +4,4 @@ import eu.kanade.tachiyomi.multisrc.madara.Madara
class MangaUpdatesTop : Madara("MangaUpdates.top (unoriginal)", "https://mangaupdates.top", "en") {
override val useNewChapterEndpoint = false
override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/"
}

View File

@ -4,6 +4,4 @@ import eu.kanade.tachiyomi.multisrc.madara.Madara
class ManhuaDex : Madara("ManhuaDex", "https://manhuadex.com", "en") {
override val useNewChapterEndpoint = true
override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/"
}

View File

@ -1,11 +1,8 @@
package eu.kanade.tachiyomi.extension.en.manhuafast
import eu.kanade.tachiyomi.multisrc.madara.Madara
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.interceptor.rateLimit
import okhttp3.CacheControl
import okhttp3.OkHttpClient
import okhttp3.Request
import java.util.concurrent.TimeUnit
class ManhuaFast : Madara("ManhuaFast", "https://manhuafast.com", "en") {
@ -16,22 +13,4 @@ class ManhuaFast : Madara("ManhuaFast", "https://manhuafast.com", "en") {
// The website does not flag the content.
override val filterNonMangaItems = false
override fun popularMangaRequest(page: Int): Request {
return GET(
url = "$baseUrl/${searchPage(page)}?s&post_type=wp-manga&m_orderby=views",
headers = headers,
cache = CacheControl.FORCE_NETWORK,
)
}
override fun popularMangaSelector() = searchMangaSelector()
override fun latestUpdatesRequest(page: Int): Request {
return GET(
url = "$baseUrl/${searchPage(page)}?s&post_type=wp-manga&m_orderby=latest",
headers = headers,
cache = CacheControl.FORCE_NETWORK,
)
}
}

View File

@ -4,6 +4,4 @@ import eu.kanade.tachiyomi.multisrc.madara.Madara
class ManhuaFastNet : Madara("ManhuaFast.net (unoriginal)", "https://manhuafast.net", "en") {
override val useNewChapterEndpoint = true
override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/"
}

View File

@ -12,6 +12,4 @@ class ManhuaManhwa : Madara(
) {
override val useNewChapterEndpoint = true
override val filterNonMangaItems = false
override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/"
}

View File

@ -4,6 +4,4 @@ import eu.kanade.tachiyomi.multisrc.madara.Madara
class ManhuaManhwaOnline : Madara("ManhuaManhwa.online", "https://manhuamanhwa.online", "en") {
override val useNewChapterEndpoint = false
override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/"
}

View File

@ -4,6 +4,4 @@ import eu.kanade.tachiyomi.multisrc.madara.Madara
class ManhuaScanInfo : Madara("ManhuaScan.info (unoriginal)", "https://manhuascan.info", "en") {
override val useNewChapterEndpoint = false
override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/"
}

View File

@ -6,6 +6,4 @@ class ManhuaZonghe : Madara("Manhua Zonghe", "https://manhuazonghe.com", "en") {
override val useNewChapterEndpoint = false
override val filterNonMangaItems = false
override val mangaSubString = "manhua"
override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/"
}

View File

@ -4,6 +4,4 @@ import eu.kanade.tachiyomi.multisrc.madara.Madara
class Manhwa2Read : Madara("Manhwa2Read", "https://manhwa2read.com", "en") {
override val useNewChapterEndpoint = true
override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/"
}

View File

@ -1,26 +1,7 @@
package eu.kanade.tachiyomi.extension.en.manhwafull
import eu.kanade.tachiyomi.multisrc.madara.Madara
import eu.kanade.tachiyomi.network.GET
import okhttp3.CacheControl
import okhttp3.Request
import java.text.SimpleDateFormat
import java.util.Locale
class Manhwafull : Madara("Manhwafull", "https://manhwafull.com", "en") {
override fun popularMangaNextPageSelector(): String? = "a.nextpostslink"
override fun popularMangaRequest(page: Int): Request {
return GET(
"$baseUrl/manga-mwf/page/$page/?m_orderby=views",
headers,
CacheControl.FORCE_NETWORK,
)
}
override fun latestUpdatesRequest(page: Int): Request {
return GET(
"$baseUrl/manga-mwf/page/$page/?m_orderby=latest",
headers,
CacheControl.FORCE_NETWORK,
)
}
}
class Manhwafull : Madara("Manhwafull", "https://manhwafull.com", "en", SimpleDateFormat("MM/dd/yyyy", Locale.ENGLISH))

View File

@ -5,6 +5,4 @@ import eu.kanade.tachiyomi.multisrc.madara.Madara
class ManhwaManhua : Madara("ManhwaManhua", "https://manhwamanhua.com", "en") {
override val useNewChapterEndpoint = true
override val filterNonMangaItems = false
override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/"
}

View File

@ -12,6 +12,4 @@ class ManhwaNew : Madara(
) {
override val useNewChapterEndpoint = true
override val filterNonMangaItems = false
override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/"
}

View File

@ -108,9 +108,7 @@ class ManhwaZ : Madara(
val mangaList = document.select(".page-search > .container > .row > div")
.map(::searchMangaFromElement)
val hasNextPage = searchMangaNextPageSelector().let { selector ->
document.select(selector).first()
} != null
val hasNextPage = document.selectFirst(searchMangaNextPageSelector()) != null
return MangasPage(mangaList, hasNextPage)
}

View File

@ -12,19 +12,19 @@ class MidnightMessScans : Madara("Midnight Mess Scans", "https://midnightmess.or
override fun mangaDetailsParse(document: Document): SManga {
val manga = SManga.create()
with(document) {
select("div.post-title h3").first()?.let {
selectFirst("div.post-title h3")!!.let {
manga.title = it.ownText()
}
select("div.author-content").first()?.let {
selectFirst("div.author-content")?.let {
if (it.text().notUpdating()) manga.author = it.text()
}
select("div.artist-content").first()?.let {
selectFirst("div.artist-content")?.let {
if (it.text().notUpdating()) manga.artist = it.text()
}
select("div.summary_content div.post-content").let {
manga.description = it.select("div.manga-excerpt").text()
}
select("div.summary_image img").first()?.let {
selectFirst("div.summary_image img")?.let {
manga.thumbnail_url = imageFromElement(it)
}
select("div.summary-content").last()?.let {
@ -48,7 +48,7 @@ class MidnightMessScans : Madara("Midnight Mess Scans", "https://midnightmess.or
}
// add manga/manhwa/manhua thinggy to genre
document.select(seriesTypeSelector).firstOrNull()?.ownText()?.let {
document.selectFirst(seriesTypeSelector)?.ownText()?.let {
if (it.isEmpty().not() && it.notUpdating() && it != "-" && genres.contains(it).not()) {
genres.add(it.lowercase(Locale.ROOT))
}

View File

@ -1,10 +1,7 @@
package eu.kanade.tachiyomi.extension.en.milftoon
import eu.kanade.tachiyomi.multisrc.madara.Madara
import eu.kanade.tachiyomi.network.GET
import okhttp3.Request
class Milftoon : Madara("Milftoon", "https://milftoon.xxx", "en") {
override fun popularMangaRequest(page: Int): Request = GET("$baseUrl/page/$page/?m_orderby=views", headers)
override fun latestUpdatesRequest(page: Int): Request = GET("$baseUrl/page/$page/?m_orderby=latest", headers)
override val mangaSubString = "comics"
}

View File

@ -1,71 +1,30 @@
package eu.kanade.tachiyomi.extension.en.mmscans
import eu.kanade.tachiyomi.multisrc.madara.Madara
import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga
import okhttp3.FormBody
import okhttp3.Request
import org.jsoup.nodes.Element
class MMScans : Madara("MMScans", "https://mm-scans.org", "en") {
// The site customized the listing and does not include a .manga class.
override val filterNonMangaItems = false
override val useLoadMoreRequest = LoadMoreStrategy.Always
override val popularMangaUrlSelector = "div.item-summary a"
override fun chapterListSelector() = "li.chapter-li"
override fun searchMangaSelector() = ".search-wrap >.tab-content-wrap > a"
override fun searchMangaNextPageSelector(): String? = "body:not(:has(.no-posts))"
fun oldLoadMoreRequest(page: Int, metaKey: String): Request {
val form = FormBody.Builder()
.add("action", "madara_load_more")
.add("page", page.toString())
.add("template", "madara-core/content/content-archive")
.add("vars[paged]", "1")
.add("vars[orderby]", "meta_value_num")
.add("vars[template]", "archive")
.add("vars[sidebar]", "right")
.add("vars[post_type]", "wp-manga")
.add("vars[post_status]", "publish")
.add("vars[meta_key]", metaKey)
.add("vars[meta_query][0][paged]", "1")
.add("vars[meta_query][0][orderby]", "meta_value_num")
.add("vars[meta_query][0][template]", "archive")
.add("vars[meta_query][0][sidebar]", "right")
.add("vars[meta_query][0][post_type]", "wp-manga")
.add("vars[meta_query][0][post_status]", "publish")
.add("vars[meta_query][0][meta_key]", metaKey)
.add("vars[meta_query][relation]", "AND")
.add("vars[manga_archives_item_layout]", "default")
.build()
val xhrHeaders = headersBuilder()
.add("Content-Length", form.contentLength().toString())
.add("Content-Type", form.contentType().toString())
.add("X-Requested-With", "XMLHttpRequest")
.build()
return POST("$baseUrl/wp-admin/admin-ajax.php", xhrHeaders, form)
}
override fun popularMangaRequest(page: Int): Request {
return oldLoadMoreRequest(page - 1, "_wp_manga_views")
}
override fun latestUpdatesRequest(page: Int): Request {
return oldLoadMoreRequest(page - 1, "_latest_update")
}
override fun popularMangaFromElement(element: Element): SManga {
val manga = SManga.create()
with(element) {
select(popularMangaUrlSelector).first()?.let {
selectFirst(popularMangaUrlSelector)?.let {
manga.setUrlWithoutDomain(it.attr("abs:href"))
manga.title = it.selectFirst("h3")!!.ownText()
}
select("img").first()?.let {
selectFirst("img")?.let {
manga.thumbnail_url = imageFromElement(it)
}
}
@ -77,13 +36,13 @@ class MMScans : Madara("MMScans", "https://mm-scans.org", "en") {
val chapter = SChapter.create()
with(element) {
select(chapterUrlSelector).first()?.let { urlElement ->
selectFirst(chapterUrlSelector)!!.let { urlElement ->
chapter.url = urlElement.attr("abs:href").let {
it.substringBefore("?style=paged") + if (!it.endsWith(chapterUrlSuffix)) chapterUrlSuffix else ""
}
chapter.name = urlElement.selectFirst(".chapter-title-date p")!!.text()
}
chapter.date_upload = parseChapterDate(select(chapterDateSelector()).firstOrNull()?.text())
chapter.date_upload = parseChapterDate(selectFirst(chapterDateSelector())?.text())
}
return chapter

View File

@ -10,6 +10,4 @@ class NitroScans : Madara("Nitro Manga", "https://nitromanga.com", "en") {
override val filterNonMangaItems = false
override val useNewChapterEndpoint = true
override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/"
}

View File

@ -13,6 +13,4 @@ class OnlyManhwa : Madara(
override val useNewChapterEndpoint = true
override val mangaSubString = "manhwa"
override val mangaDetailsSelectorStatus = "div.summary-heading:contains(Status) + div.summary-content"
override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/"
}

View File

@ -4,12 +4,4 @@ import eu.kanade.tachiyomi.multisrc.madara.Madara
class OPSCANS : Madara("OPSCANS", "https://opchapters.com", "en") {
override val versionId = 2
override fun searchPage(page: Int): String {
return if (page > 1) {
"page/$page/"
} else {
""
}
}
}

View File

@ -14,8 +14,6 @@ class ParagonScans : Madara(
override val useNewChapterEndpoint = true
override val mangaSubString = "mangax"
override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/"
override fun parseChapterDate(date: String?): Long {
date ?: return 0

View File

@ -4,6 +4,4 @@ import eu.kanade.tachiyomi.multisrc.madara.Madara
class PawManga : Madara("Paw Manga", "https://pawmanga.com", "en") {
override val useNewChapterEndpoint = true
override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/"
}

View File

@ -12,6 +12,4 @@ class PMScans : Madara(
) {
override val versionId = 2
override val useNewChapterEndpoint = true
override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/"
}

View File

@ -4,6 +4,4 @@ import eu.kanade.tachiyomi.multisrc.madara.Madara
class PonyManga : Madara("Pony Manga", "https://ponymanga.com", "en") {
override val useNewChapterEndpoint = false
override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/"
}

View File

@ -19,7 +19,7 @@ class ReadManhua : Madara(
val year = Calendar.getInstance().get(Calendar.YEAR).toLong()
with(element) {
select(chapterUrlSelector).first()?.let { urlElement ->
selectFirst(chapterUrlSelector)?.let { urlElement ->
chapter.url = urlElement.attr("abs:href").let {
it.substringBefore("?style=paged") + if (!it.endsWith(chapterUrlSuffix)) chapterUrlSuffix else ""
}
@ -27,8 +27,8 @@ class ReadManhua : Madara(
}
// Dates can be part of a "new" graphic or plain text
chapter.date_upload = select("img").firstOrNull()?.attr("alt")?.let { parseRelativeDate(it) }
?: parseChapterDate(select("span.chapter-release-date i").firstOrNull()?.text() + " " + year)
chapter.date_upload = selectFirst("img")?.attr("alt")?.let { parseRelativeDate(it) }
?: parseChapterDate(selectFirst("span.chapter-release-date i")?.text() + " " + year)
}
return chapter

View File

@ -19,8 +19,6 @@ class SectScans : Madara("SectScans", "https://sectscans.com", "en") {
override val useNewChapterEndpoint = true
override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/"
// =========================== Manga Details ============================
override val mangaDetailsSelectorTitle = ".post-title"

View File

@ -2,7 +2,6 @@ package eu.kanade.tachiyomi.extension.en.setsuscans
import eu.kanade.tachiyomi.multisrc.madara.Madara
import eu.kanade.tachiyomi.network.interceptor.rateLimit
import eu.kanade.tachiyomi.source.model.MangasPage
import okhttp3.Interceptor
import okhttp3.Response
@ -60,31 +59,7 @@ class SetsuScans : Madara(
}
override val useNewChapterEndpoint = true
override fun searchPage(page: Int): String {
return if (page > 1) {
"page/$page/"
} else {
""
}
}
override fun popularMangaParse(response: Response) =
super.popularMangaParse(response).fixNextPage()
override fun latestUpdatesParse(response: Response) =
super.latestUpdatesParse(response).fixNextPage()
override fun searchMangaParse(response: Response) =
super.searchMangaParse(response).fixNextPage()
private fun MangasPage.fixNextPage(): MangasPage {
return if (mangas.size < 12) {
MangasPage(mangas, false)
} else {
this
}
}
override val useLoadMoreRequest = LoadMoreStrategy.Always
override val mangaDetailsSelectorStatus = "div.summary-heading:contains(status) + div.summary-content"
}

View File

@ -12,12 +12,4 @@ class ShibaManga : Madara(
) {
override val filterNonMangaItems = false
override val useNewChapterEndpoint = true
override fun searchPage(page: Int): String {
return if (page > 1) {
"page/$page/"
} else {
""
}
}
}

Some files were not shown because too many files have changed in this diff Show More