Compare commits

..

No commits in common. "f6cb65688da0559d695847f7d3c11debe7396343" and "a62d90d4aa91d9e78aadaca499be59ea0306576b" have entirely different histories.

361 changed files with 3078 additions and 2563 deletions

View File

@ -1,3 +1,17 @@
buildscript {
repositories {
mavenCentral()
google()
maven(url = "https://plugins.gradle.org/m2/")
}
dependencies {
classpath(libs.gradle.agp)
classpath(libs.gradle.kotlin)
classpath(libs.gradle.serialization)
classpath(libs.gradle.kotlinter)
}
}
allprojects {
repositories {
mavenCentral()

View File

@ -1,25 +0,0 @@
plugins {
id("com.android.library")
kotlin("android")
id("kotlinx-serialization")
}
android {
compileSdk = AndroidConfig.compileSdk
defaultConfig {
minSdk = AndroidConfig.minSdk
}
namespace = "eu.kanade.tachiyomi.lib.${project.name}"
buildFeatures {
androidResources = false
}
}
// TODO: use versionCatalogs.named("libs") in Gradle 8.5
val libs = project.extensions.getByType<VersionCatalogsExtension>().named("libs")
dependencies {
compileOnly(libs.findBundle("common").get())
}

View File

@ -23,6 +23,11 @@ android {
}
}
buildFeatures {
resValues = false
shaders = false
}
kotlinOptions {
freeCompilerArgs += "-opt-in=kotlinx.serialization.ExperimentalSerializationApi"
}
@ -36,6 +41,10 @@ kotlinter {
)
}
repositories {
mavenCentral()
}
// TODO: use versionCatalogs.named("libs") in Gradle 8.5
val libs = project.extensions.getByType<VersionCatalogsExtension>().named("libs")
dependencies {

View File

@ -71,6 +71,8 @@ android {
}
buildFeatures {
resValues false
shaders false
buildConfig true
}
@ -93,6 +95,10 @@ android {
}
}
repositories {
mavenCentral()
}
dependencies {
if (theme != null) implementation(theme) // Overrides core launcher icons
implementation(project(":core"))

View File

@ -21,7 +21,5 @@ org.gradle.caching=true
# Enable AndroidX dependencies
android.useAndroidX=true
android.enableBuildConfigAsBytecode=true
android.defaults.buildfeatures.resvalues=false
android.defaults.buildfeatures.shaders=false
android.nonTransitiveRClass=false
android.nonFinalResIds=false

View File

@ -2,4 +2,4 @@ plugins {
id("lib-multisrc")
}
baseVersionCode = 2
baseVersionCode = 1

View File

@ -4,12 +4,12 @@ import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
class BloggerDto(
data class BloggerDto(
val feed: BloggerFeedDto,
)
@Serializable
class BloggerFeedDto(
data class BloggerFeedDto(
@SerialName("openSearch\$totalResults") val totalResults: BloggerTextDto,
@SerialName("openSearch\$startIndex") val startIndex: BloggerTextDto,
@SerialName("openSearch\$itemsPerPage") val itemsPerPage: BloggerTextDto,
@ -18,26 +18,32 @@ class BloggerFeedDto(
)
@Serializable
class BloggerFeedEntryDto(
data class BloggerFeedEntryDto(
val published: BloggerTextDto,
val category: List<BloggerCategoryDto>? = emptyList(),
val category: List<BloggerCategoryDto>,
val title: BloggerTextDto,
val content: BloggerTextDto,
val link: List<BloggerLinkDto>,
val author: List<BloggerAuthorDto>,
)
@Serializable
class BloggerLinkDto(
data class BloggerLinkDto(
val rel: String,
val href: String,
)
@Serializable
class BloggerCategoryDto(
data class BloggerCategoryDto(
val term: String,
)
@Serializable
class BloggerTextDto(
data class BloggerAuthorDto(
val name: BloggerTextDto,
)
@Serializable
data class BloggerTextDto(
@SerialName("\$t") val t: String,
)

View File

@ -1,5 +1,6 @@
package eu.kanade.tachiyomi.multisrc.gravureblogger
import android.annotation.SuppressLint
import android.os.Build
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.source.model.Filter
@ -22,6 +23,7 @@ import uy.kohesive.injekt.injectLazy
import java.text.SimpleDateFormat
import java.util.Locale
@SuppressLint("ObsoleteSdkInt")
abstract class GravureBlogger(
override val name: String,
override val baseUrl: String,
@ -59,7 +61,7 @@ abstract class GravureBlogger(
setUrlWithoutDomain(entry.link.first { it.rel == "alternate" }.href + "#${entry.published.t}")
title = entry.title.t
thumbnail_url = content.selectFirst("img")?.absUrl("src")
genre = entry.category?.joinToString { it.term }
genre = entry.category.joinToString { it.term }
status = SManga.COMPLETED
update_strategy = UpdateStrategy.ONLY_FETCH_ONCE
initialized = true

View File

@ -2,4 +2,4 @@ plugins {
id("lib-multisrc")
}
baseVersionCode = 2
baseVersionCode = 1

View File

@ -19,9 +19,7 @@ import okhttp3.Response
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import uy.kohesive.injekt.injectLazy
import java.text.ParseException
import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Locale
abstract class Keyoapp(
@ -40,8 +38,6 @@ abstract class Keyoapp(
private val json: Json by injectLazy()
private val dateFormat = SimpleDateFormat("MMM d, yyyy", Locale.ENGLISH)
// Popular
override fun popularMangaRequest(page: Int): Request = GET(baseUrl, headers)
@ -210,7 +206,7 @@ abstract class Keyoapp(
// Chapter list
override fun chapterListSelector(): String = "#chapters > a:not(:has(.text-sm span:matches(Upcoming)))"
override fun chapterListSelector(): String = "#chapters > a"
override fun chapterFromElement(element: Element): SChapter = SChapter.create().apply {
setUrlWithoutDomain(element.selectFirst("a[href]")!!.attr("href"))
@ -263,40 +259,13 @@ abstract class Keyoapp(
}
private fun String.parseDate(): Long {
return if (this.contains("ago")) {
this.parseRelativeDate()
} else {
try {
dateFormat.parse(this)!!.time
} catch (_: ParseException) {
0L
}
}
return runCatching { DATE_FORMATTER.parse(this)?.time }
.getOrNull() ?: 0L
}
private fun String.parseRelativeDate(): Long {
val now = Calendar.getInstance().apply {
set(Calendar.HOUR_OF_DAY, 0)
set(Calendar.MINUTE, 0)
set(Calendar.SECOND, 0)
set(Calendar.MILLISECOND, 0)
companion object {
private val DATE_FORMATTER by lazy {
SimpleDateFormat("MMM d, yyyy", Locale.ENGLISH)
}
val relativeDate = this.split(" ").firstOrNull()
?.replace("one", "1")
?.replace("a", "1")
?.toIntOrNull()
?: return 0L
when {
"second" in this -> now.add(Calendar.SECOND, -relativeDate) // parse: 30 seconds ago
"minute" in this -> now.add(Calendar.MINUTE, -relativeDate) // parses: "42 minutes ago"
"hour" in this -> now.add(Calendar.HOUR, -relativeDate) // parses: "1 hour ago" and "2 hours ago"
"day" in this -> now.add(Calendar.DAY_OF_YEAR, -relativeDate) // parses: "2 days ago"
"week" in this -> now.add(Calendar.WEEK_OF_YEAR, -relativeDate) // parses: "2 weeks ago"
"month" in this -> now.add(Calendar.MONTH, -relativeDate) // parses: "2 months ago"
"year" in this -> now.add(Calendar.YEAR, -relativeDate) // parse: "2 years ago"
}
return now.timeInMillis
}
}

View File

@ -2,4 +2,4 @@ plugins {
id("lib-multisrc")
}
baseVersionCode = 5
baseVersionCode = 4

View File

@ -367,8 +367,8 @@ abstract class LectorTmo(
if (script1 != null) {
val data = script1.data()
val regexParams = """\{\s*uniqid\s*:\s*'(.+)'\s*,\s*cascade\s*:\s*(.+)\s*\}""".toRegex()
val regexAction = """form\.action\s*=\s*'(.+)'""".toRegex()
val regexParams = """\{uniqid:'(.+)',cascade:(.+)\}""".toRegex()
val regexAction = """form\.action\s?=\s?'(.+)'""".toRegex()
val params = regexParams.find(data)
val action = regexAction.find(data)?.groupValues?.get(1)?.unescapeUrl()
@ -393,7 +393,7 @@ abstract class LectorTmo(
if (script3 != null) {
val data = script3.data()
val regexRedirect = """redirectUrl\s*=\s*'(.+)'""".toRegex()
val regexRedirect = """redirectUrl\s?=\s?'(.+)'""".toRegex()
val url = regexRedirect.find(data)?.groupValues?.get(1)?.unescapeUrl()
if (url != null) {

View File

@ -1,27 +0,0 @@
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

@ -1,26 +0,0 @@
author_filter_title=Autor
artist_filter_title=Artista
year_filter_title=Año de lanzamiento
status_filter_title=Estado
status_filter_completed=Completado
status_filter_ongoing=En curso
status_filter_canceled=Cancelado
status_filter_on_hold=En pausa
order_by_filter_title=Ordenar por
order_by_filter_relevance=Relevancia
order_by_filter_latest=Reciente
order_by_filter_rating=Valoración
order_by_filter_trending=Tendencia
order_by_filter_views=Vistas
order_by_filter_new=Nuevos
genre_condition_filter_title=Condición de género
genre_condition_filter_or=O
genre_condition_filter_and=Y
adult_content_filter_title=Contenido adulto
adult_content_filter_all=Todos
adult_content_filter_none=Ninguno
adult_content_filter_only=Solo
genre_filter_header=Es posible que el filtro de géneros no funcione correctamente
genre_filter_title=Géneros
genre_missing_warning=Presione 'Restablecer' para intentar mostrar los géneros
alt_names_heading=Nombres Alternativos:

View File

@ -1,26 +0,0 @@
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 = 35
baseVersionCode = 33
dependencies {
api(project(":lib:cryptoaes"))
api(project(":lib:i18n"))
api(project(":lib:randomua"))
}

View File

@ -1,11 +1,18 @@
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.i18n.Intl
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.network.GET
import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.network.asObservableSuccess
import eu.kanade.tachiyomi.network.asObservable
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
@ -14,55 +21,57 @@ 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() {
) : ParsedHttpSource(), ConfigurableSource {
private val preferences: SharedPreferences by lazy {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
}
override val supportsLatest = true
override val client = network.cloudflareClient
override val client: OkHttpClient by lazy {
network.cloudflareClient.newBuilder()
.setRandomUserAgent(
preferences.getPrefUAType(),
preferences.getPrefCustomUA(),
)
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.build()
}
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", "es"),
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.
@ -84,6 +93,11 @@ 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.
*/
@ -100,58 +114,11 @@ 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 {
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)
runCatching { fetchGenres() }
return super.popularMangaParse(response)
}
// exclude/filter bilibili manga from list
@ -163,12 +130,12 @@ abstract class Madara(
val manga = SManga.create()
with(element) {
selectFirst(popularMangaUrlSelector)!!.let {
select(popularMangaUrlSelector).first()?.let {
manga.setUrlWithoutDomain(it.attr("abs:href"))
manga.title = it.ownText()
}
selectFirst("img")?.let {
select("img").first()?.let {
manga.thumbnail_url = imageFromElement(it)
}
}
@ -176,19 +143,15 @@ abstract class Madara(
return manga
}
override fun popularMangaRequest(page: Int): Request =
if (useLoadMoreRequest()) {
loadMoreRequest(page, popular = true)
} else {
GET("$baseUrl/$mangaSubString/${searchPage(page)}?m_orderby=views", headers)
}
override fun popularMangaRequest(page: Int): Request {
return GET(
url = "$baseUrl/$mangaSubString/${searchPage(page)}?m_orderby=views",
headers = headers,
cache = CacheControl.FORCE_NETWORK,
)
}
override fun popularMangaNextPageSelector(): String? =
if (useLoadMoreRequest()) {
"body:not(:has(.no-posts))"
} else {
"div.nav-previous, nav.navigation-ajax, a.nextpostslink"
}
override fun popularMangaNextPageSelector(): String? = searchMangaNextPageSelector()
// Latest Updates
@ -199,75 +162,53 @@ abstract class Madara(
return popularMangaFromElement(element)
}
override fun latestUpdatesRequest(page: Int): Request =
if (useLoadMoreRequest()) {
loadMoreRequest(page, popular = false)
} else {
GET("$baseUrl/$mangaSubString/${searchPage(page)}?m_orderby=latest", headers)
}
override fun latestUpdatesRequest(page: Int): Request {
return GET(
url = "$baseUrl/$mangaSubString/${searchPage(page)}?m_orderby=latest",
headers = headers,
cache = CacheControl.FORCE_NETWORK,
)
}
override fun latestUpdatesNextPageSelector(): String? = popularMangaNextPageSelector()
override fun latestUpdatesParse(response: Response): MangasPage {
val mp = popularMangaParse(response)
val mp = super.latestUpdatesParse(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 = "/$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)
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)
}
}
return super.fetchSearchManga(page, query, filters)
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!")
} else {
throw Exception("HTTP error ${response.code}")
}
}
}
.map { response ->
searchMangaParse(response)
}
}
protected open fun searchPage(page: Int): String {
return if (page == 1) {
""
} else {
"page/$page/"
}
}
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")
@ -319,182 +260,108 @@ abstract class Madara(
return GET(url.build(), headers)
}
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")
}
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)
}
metaQueryIdx++
}
}
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")
}
"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",
)
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)
}
taxQueryIdx++
}
}
else -> {}
}
}
}.build()
return POST("$baseUrl/wp-admin/admin-ajax.php", xhrHeaders, formBody)
protected open val authorFilterTitle: String = when (lang) {
"pt-BR" -> "Autor"
else -> "Author"
}
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 artistFilterTitle: String = when (lang) {
"pt-BR" -> "Artista"
else -> "Artist"
}
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 yearFilterTitle: String = when (lang) {
"pt-BR" -> "Ano de lançamento"
else -> "Year of Released"
}
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")
}
protected open val statusFilterOptionsValues: Array<String> = arrayOf(
"end",
"on-going",
"canceled",
"on-hold",
)
protected open val genreConditionFilterOptions: Map<String, String> =
mapOf(
intl["genre_condition_filter_or"] to "",
intl["genre_condition_filter_and"] to "1",
)
protected open val adultContentFilterOptions: Map<String, String> =
mapOf(
intl["adult_content_filter_all"] to "",
intl["adult_content_filter_none"] to "0",
intl["adult_content_filter_only"] to "1",
)
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 orderByFilterTitle: String = when (lang) {
"pt-BR" -> "Ordenar por"
else -> "Order By"
}
open class Tag(name: String, val id: String) : Filter.CheckBox(name)
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",
)
}
protected open val orderByFilterOptionsValues: Array<String> = arrayOf(
"",
"latest",
"alphabet",
"rating",
"trending",
"views",
"new-manga",
)
protected open val genreConditionFilterTitle: String = when (lang) {
"pt-BR" -> "Operador dos gêneros"
else -> "Genre condition"
}
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"
}
protected class AuthorFilter(title: String) : Filter.Text(title)
protected class ArtistFilter(title: String) : Filter.Text(title)
@ -505,75 +372,64 @@ abstract class Madara(
protected class OrderByFilter(title: String, options: List<Pair<String, String>>, state: Int = 0) :
UriPartFilter(title, options.toTypedArray(), state)
protected class GenreConditionFilter(title: String, options: List<Pair<String, String>>) : UriPartFilter(
protected class GenreConditionFilter(title: String, options: Array<String>) : UriPartFilter(
title,
options.toTypedArray(),
options.zip(arrayOf("", "1")).toTypedArray(),
)
protected class AdultContentFilter(title: String, options: List<Pair<String, String>>) : UriPartFilter(
protected class AdultContentFilter(title: String, options: Array<String>) : UriPartFilter(
title,
options.toTypedArray(),
options.zip(arrayOf("", "0", "1")).toTypedArray(),
)
protected class GenreList(title: String, genres: List<Genre>) : Filter.Group<GenreCheckBox>(title, genres.map { GenreCheckBox(it.name, it.id) })
class GenreCheckBox(name: String, val id: String = name) : Filter.CheckBox(name)
class Genre(val name: String, val id: String = name)
protected class GenreList(title: String, genres: List<Genre>) : Filter.Group<Genre>(title, genres)
class Genre(name: String, val id: String = name) : Filter.CheckBox(name)
override fun getFilterList(): FilterList {
launchIO { fetchGenres() }
val filters = mutableListOf(
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) },
),
AuthorFilter(authorFilterTitle),
ArtistFilter(artistFilterTitle),
YearFilter(yearFilterTitle),
StatusFilter(statusFilterTitle, getStatusList()),
OrderByFilter(
title = intl["order_by_filter_title"],
options = orderByFilterOptions.toList(),
title = orderByFilterTitle,
options = orderByFilterOptions.zip(orderByFilterOptionsValues),
state = 0,
),
AdultContentFilter(
title = intl["adult_content_filter_title"],
options = adultContentFilterOptions.toList(),
),
AdultContentFilter(adultContentFilterTitle, adultContentFilterOptions),
)
if (genresList.isNotEmpty()) {
filters += listOf(
Filter.Separator(),
Filter.Header(intl["genre_filter_header"]),
GenreConditionFilter(
title = intl["genre_condition_filter_title"],
options = genreConditionFilterOptions.toList(),
),
GenreList(
title = intl["genre_filter_title"],
genres = genresList,
),
Filter.Header(genreFilterHeader),
GenreConditionFilter(genreConditionFilterTitle, genreConditionFilterOptions),
GenreList(genreFilterTitle, genresList),
)
} else if (fetchGenres) {
filters += listOf(
Filter.Separator(),
Filter.Header(intl["genre_missing_warning"]),
Filter.Header(genresMissingWarning),
)
}
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 {
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)
runCatching { fetchGenres() }
return super.searchMangaParse(response)
}
override fun searchMangaSelector() = "div.c-tabs-item__content"
@ -582,11 +438,11 @@ abstract class Madara(
val manga = SManga.create()
with(element) {
selectFirst("div.post-title a")!!.let {
select("div.post-title a").first()?.let {
manga.setUrlWithoutDomain(it.attr("abs:href"))
manga.title = it.ownText()
}
selectFirst("img")?.let {
select("img").first()?.let {
manga.thumbnail_url = imageFromElement(it)
}
}
@ -594,7 +450,7 @@ abstract class Madara(
return manga
}
override fun searchMangaNextPageSelector() = popularMangaNextPageSelector()
override fun searchMangaNextPageSelector(): String? = "div.nav-previous, nav.navigation-ajax, a.nextpostslink"
// Manga Details Parse
@ -637,7 +493,9 @@ abstract class Madara(
override fun mangaDetailsParse(document: Document): SManga {
val manga = SManga.create()
with(document) {
manga.title = selectFirst(mangaDetailsSelectorTitle)!!.ownText()
select(mangaDetailsSelectorTitle).first()?.let {
manga.title = it.ownText()
}
select(mangaDetailsSelectorAuthor).eachText().filter {
it.notUpdating()
}.joinToString().takeIf { it.isNotBlank() }?.let {
@ -657,7 +515,7 @@ abstract class Madara(
manga.description = it.text()
}
}
selectFirst(mangaDetailsSelectorThumbnail)?.let {
select(mangaDetailsSelectorThumbnail).first()?.let {
manga.thumbnail_url = imageFromElement(it)
}
select(mangaDetailsSelectorStatus).last()?.let {
@ -675,6 +533,13 @@ 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() &&
@ -682,7 +547,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(manga.title, true).not() &&
element.text().contains(mangaTitle, true).not() &&
element.text().contains(altName, true).not()
) {
genres.add(element.text().lowercase(Locale.ROOT))
@ -691,13 +556,13 @@ abstract class Madara(
}
// add manga/manhwa/manhua thinggy to genre
document.selectFirst(seriesTypeSelector)?.ownText()?.let {
document.select(seriesTypeSelector).firstOrNull()?.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(
@ -710,11 +575,11 @@ abstract class Madara(
}
// add alternative name to manga description
document.selectFirst(altNameSelector)?.ownText()?.let {
document.select(altNameSelector).firstOrNull()?.ownText()?.let {
if (it.isBlank().not() && it.notUpdating()) {
manga.description = when {
manga.description.isNullOrBlank() -> "$altName $it"
else -> "${manga.description}\n\n$altName $it"
manga.description.isNullOrBlank() -> altName + it
else -> manga.description + "\n\n$altName" + it
}
}
}
@ -724,7 +589,7 @@ abstract class Madara(
}
// Manga Details Selector
open val mangaDetailsSelectorTitle = "div.post-title h3, div.post-title h1, #manga-title > h1"
open val mangaDetailsSelectorTitle = "div.post-title h3, div.post-title h1"
open val mangaDetailsSelectorAuthor = "div.author-content > a"
open val mangaDetailsSelectorArtist = "div.artist-content > a"
open val mangaDetailsSelectorStatus = "div.summary-content"
@ -735,14 +600,17 @@ 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 = intl["alt_names_heading"]
open val altName = when (lang) {
"pt-BR" -> "Nomes alternativos: "
else -> "Alternative Names: "
}
open val updatingRegex = "Updating|Atualizando".toRegex(RegexOption.IGNORE_CASE)
fun String.notUpdating(): Boolean {
return this.contains(updatingRegex).not()
}
private fun String.containsIn(array: Array<String>): Boolean {
fun String.containsIn(array: Array<String>): Boolean {
return this.lowercase() in array.map { it.lowercase() }
}
@ -776,18 +644,25 @@ 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())
@ -817,6 +692,8 @@ abstract class Madara(
xhrResponse.close()
}
countViews(document)
return chapterElements.map(::chapterFromElement)
}
@ -833,7 +710,7 @@ abstract class Madara(
val chapter = SChapter.create()
with(element) {
selectFirst(chapterUrlSelector)!!.let { urlElement ->
select(chapterUrlSelector).first()?.let { urlElement ->
chapter.url = urlElement.attr("abs:href").let {
it.substringBefore("?style=paged") + if (!it.endsWith(chapterUrlSuffix)) chapterUrlSuffix else ""
}
@ -841,9 +718,9 @@ abstract class Madara(
}
// Dates can be part of a "new" graphic or plain text
// Added "title" alternative
chapter.date_upload = selectFirst("img:not(.thumb)")?.attr("alt")?.let { parseRelativeDate(it) }
?: selectFirst("span a")?.attr("title")?.let { parseRelativeDate(it) }
?: parseChapterDate(selectFirst(chapterDateSelector())?.text())
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())
}
return chapter
@ -939,7 +816,7 @@ abstract class Madara(
open val chapterProtectorSelector = "#chapter-protector-data"
override fun pageListParse(document: Document): List<Page> {
launchIO { countViews(document) }
countViews(document)
val chapterProtector = document.selectFirst(chapterProtectorSelector)
?: return document.select(pageListParseSelector).mapIndexed { index, element ->
@ -959,7 +836,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
@ -983,14 +860,15 @@ abstract class Madara(
protected open val sendViewCount: Boolean = true
protected open fun countViewsRequest(document: Document): Request? {
val wpMangaData = document.selectFirst("script#wp-manga-js-extra")
val wpMangaData = document.select("script#wp-manga-js-extra").firstOrNull()
?.data() ?: return null
val wpMangaInfo = wpMangaData
.substringAfter("var manga = ")
.substringBeforeLast(";")
val wpManga = json.parseToJsonElement(wpMangaInfo).jsonObject
val wpManga = runCatching { json.parseToJsonElement(wpMangaInfo).jsonObject }
.getOrNull() ?: return null
if (wpManga["enable_manga_view"]?.jsonPrimitive?.content == "1") {
val formBuilder = FormBody.Builder()
@ -1004,10 +882,14 @@ 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()
return POST("$baseUrl/wp-admin/admin-ajax.php", newHeaders, formBody)
val ajaxUrl = wpManga["ajax_url"]!!.jsonPrimitive.content
return POST(ajaxUrl, newHeaders, formBody)
}
return null
@ -1018,29 +900,28 @@ abstract class Madara(
*
* @param document The response document with the wp-manga data
*/
protected fun countViews(document: Document) {
protected open fun countViews(document: Document) {
if (!sendViewCount) {
return
}
try {
val request = countViewsRequest(document) ?: return
client.newCall(request).execute().close()
} catch (_: Exception) { }
val request = countViewsRequest(document) ?: return
runCatching { client.newCall(request).execute().close() }
}
/**
* Fetch the genres from the source to be used in the filters.
*/
protected fun fetchGenres() {
if (fetchGenres && fetchGenresAttempts < 3 && genresList.isEmpty()) {
try {
genresList = client.newCall(genresRequest()).execute()
protected open fun fetchGenres() {
if (fetchGenres && fetchGenresAttempts <= 3 && (genresList.isEmpty() || fetchGenresFailed)) {
val genres = runCatching {
client.newCall(genresRequest()).execute()
.use { parseGenres(it.asJsoup()) }
} catch (_: Exception) {
} finally {
fetchGenresAttempts++
}
fetchGenresFailed = genres.isFailure
genresList = genres.getOrNull().orEmpty()
fetchGenresAttempts++
}
}
@ -1069,7 +950,7 @@ abstract class Madara(
}
// https://stackoverflow.com/a/66614516
protected fun String.decodeHex(): ByteArray {
private fun String.decodeHex(): ByteArray {
check(length % 2 == 0) { "Must have an even length" }
return chunked(2)
@ -1077,14 +958,13 @@ abstract class Madara(
.toByteArray()
}
protected val salted = "Salted__".toByteArray(Charsets.UTF_8)
private val scope = CoroutineScope(Dispatchers.IO)
protected fun launchIO(block: () -> Unit) = scope.launch { block() }
override fun setupPreferenceScreen(screen: PreferenceScreen) {
addRandomUAPreferenceToScreen(screen)
}
companion object {
const val URL_SEARCH_PREFIX = "slug:"
val SALTED = "Salted__".toByteArray(Charsets.UTF_8)
}
}

View File

@ -1,12 +0,0 @@
filter_ignored_warning=Ignored when using text search
cannot_use_order_by_warning=Cannot use "Order by" filter when genre is "%s" or "%s"
genre_fetch_failed=Failed to fetch genres
genre_missing_warning=Press "Reset" to attempt to show genres
genre_filter_title=Genre
genre_all=All
genre_completed=Completed
order_by_filter_title=Order by
order_by_latest=Latest
order_by_rating=Rating
order_by_most_views=Most views
order_by_new=New

View File

@ -1,12 +0,0 @@
filter_ignored_warning=Không thể dùng chung với tìm kiếm bằng từ khoá
cannot_use_order_by_warning=Không thể sắp xếp nếu chọn thể loại là "%s" hoặc "%s"
genre_fetch_failed=Đã có lỗi khi tải thể loại
genre_missing_warning=Chọn "Đặt lại" để hiển thị thể loại
genre_filter_title=Thể loại
genre_all=Tất cả
genre_completed=Hoàn thành
order_by_filter_title=Sắp xếp theo
order_by_latest=Mới nhất
order_by_rating=Đánh giá cao
order_by_most_views=Xem nhiều
order_by_new=Mới

View File

@ -1,9 +0,0 @@
plugins {
id("lib-multisrc")
}
baseVersionCode = 1
dependencies {
api(project(":lib:i18n"))
}

View File

@ -1,275 +0,0 @@
package eu.kanade.tachiyomi.multisrc.manhwaz
import android.util.Log
import eu.kanade.tachiyomi.lib.i18n.Intl
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.await
import eu.kanade.tachiyomi.source.model.Filter
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.ParsedHttpSource
import eu.kanade.tachiyomi.util.asJsoup
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Request
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import java.util.Calendar
abstract class ManhwaZ(
override val name: String,
override val baseUrl: String,
final override val lang: String,
private val mangaDetailsAuthorHeading: String = "author(s)",
private val mangaDetailsStatusHeading: String = "status",
) : ParsedHttpSource() {
override val supportsLatest = true
override val client = network.cloudflareClient
override fun headersBuilder() = super.headersBuilder()
.add("Origin", baseUrl)
.add("Referer", "$baseUrl/")
protected val intl = Intl(
lang,
setOf("en", "vi"),
"en",
this::class.java.classLoader!!,
)
override fun popularMangaRequest(page: Int) = GET(baseUrl, headers)
override fun popularMangaSelector() = "#slide-top > .item"
override fun popularMangaFromElement(element: Element) = SManga.create().apply {
element.selectFirst(".info-item a")!!.let {
title = it.text()
setUrlWithoutDomain(it.attr("href"))
}
thumbnail_url = element.selectFirst(".img-item img")?.imgAttr()
}
override fun popularMangaNextPageSelector(): String? = null
override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/?page=$page", headers)
override fun latestUpdatesSelector() = ".page-item-detail"
override fun latestUpdatesFromElement(element: Element) = SManga.create().apply {
element.selectFirst(".item-summary a")!!.let {
title = it.text()
setUrlWithoutDomain(it.attr("href"))
}
thumbnail_url = element.selectFirst(".item-thumb img")?.imgAttr()
}
override fun latestUpdatesNextPageSelector(): String? = "ul.pager a[rel=next]"
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
if (query.isNotEmpty()) {
val url = baseUrl.toHttpUrl().newBuilder().apply {
addPathSegment("search")
addQueryParameter("s", query)
addQueryParameter("page", page.toString())
}.build()
return GET(url, headers)
}
val url = baseUrl.toHttpUrl().newBuilder().apply {
val filterList = filters.ifEmpty { getFilterList() }
val genreFilter = filterList.find { it is GenreFilter } as? GenreFilter
val orderByFilter = filterList.find { it is OrderByFilter } as? OrderByFilter
val genreId = genreFilter?.options?.get(genreFilter.state)?.id
if (genreFilter != null && genreFilter.state != 0) {
addPathSegments(genreId!!)
}
// Can't sort in "All" or "Completed"
if (orderByFilter != null && genreId?.startsWith("genre/") == true) {
addQueryParameter(
"m_orderby",
orderByFilter.options[orderByFilter.state].id,
)
}
addQueryParameter("page", page.toString())
}.build()
return GET(url, headers)
}
override fun searchMangaSelector() = latestUpdatesSelector()
override fun searchMangaFromElement(element: Element) = latestUpdatesFromElement(element)
override fun searchMangaNextPageSelector(): String? = latestUpdatesNextPageSelector()
private val ongoingStatusList = listOf("ongoing", "đang ra")
private val completedStatusList = listOf("completed", "hoàn thành")
override fun mangaDetailsParse(document: Document) = SManga.create().apply {
val statusText = document.selectFirst("div.summary-heading:contains($mangaDetailsStatusHeading) + div.summary-content")
?.text()
?.lowercase()
?: ""
title = document.selectFirst("div.post-title h1")!!.text()
author = document.selectFirst("div.summary-heading:contains($mangaDetailsAuthorHeading) + div.summary-content")?.text()
description = document.selectFirst("div.summary__content")?.text()
genre = document.select("div.genres-content a[rel=tag]").joinToString { it.text() }
status = when {
ongoingStatusList.contains(statusText) -> SManga.ONGOING
completedStatusList.contains(statusText) -> SManga.COMPLETED
else -> SManga.UNKNOWN
}
thumbnail_url = document.selectFirst("div.summary_image img")?.imgAttr()
}
override fun chapterListSelector() = "li.wp-manga-chapter"
override fun chapterFromElement(element: Element) = SChapter.create().apply {
element.selectFirst("a")!!.let {
setUrlWithoutDomain(it.attr("href"))
name = it.text()
}
element.selectFirst("span.chapter-release-date")?.text()?.let {
date_upload = parseRelativeDate(it)
}
}
override fun pageListParse(document: Document) =
document.select("div.page-break img").mapIndexed { i, it ->
Page(i, imageUrl = it.imgAttr())
}
override fun imageUrlParse(document: Document) = throw UnsupportedOperationException()
override fun getFilterList(): FilterList {
fetchGenreList()
val filters = buildList {
add(Filter.Header(intl["filter_ignored_warning"]))
add(Filter.Header(intl.format("cannot_use_order_by_warning", intl["genre_all"], intl["genre_completed"])))
if (fetchGenreStatus == FetchGenreStatus.NOT_FETCHED && fetchGenreAttempts >= 3) {
add(Filter.Header(intl["genre_fetch_failed"]))
} else if (fetchGenreStatus != FetchGenreStatus.FETCHED) {
add(Filter.Header(intl["genre_missing_warning"]))
}
add(Filter.Separator())
if (genres.isNotEmpty()) {
add(GenreFilter(intl, genres))
}
add(OrderByFilter(intl))
}
return FilterList(filters)
}
private class GenreFilter(
intl: Intl,
genres: List<SelectOption>,
) : SelectFilter(intl["genre_filter_title"], genres)
private class OrderByFilter(intl: Intl) : SelectFilter(
intl["order_by_filter_title"],
listOf(
SelectOption(intl["order_by_latest"], "latest"),
SelectOption(intl["order_by_rating"], "rating"),
SelectOption(intl["order_by_most_views"], "views"),
SelectOption(intl["order_by_new"], "new"),
),
)
private var genres = emptyList<SelectOption>()
private var fetchGenreStatus = FetchGenreStatus.NOT_FETCHED
private var fetchGenreAttempts = 0
private val scope = CoroutineScope(Dispatchers.IO)
private fun fetchGenreList() {
if (fetchGenreStatus != FetchGenreStatus.NOT_FETCHED || fetchGenreAttempts >= 3) {
return
}
fetchGenreStatus = FetchGenreStatus.FETCHING
fetchGenreAttempts++
scope.launch {
try {
val document = client.newCall(GET("$baseUrl/genre")).await().asJsoup()
genres = buildList {
add(SelectOption(intl["genre_all"], ""))
add(SelectOption(intl["genre_completed"], "completed"))
document.select("ul.page-genres li a").forEach {
val path = it.absUrl("href").toHttpUrl().encodedPath.removePrefix("/")
add(SelectOption(it.ownText(), path))
}
}
fetchGenreStatus = FetchGenreStatus.FETCHED
} catch (e: Exception) {
Log.e("ManhwaZ/$name", "Error fetching genres", e)
fetchGenreStatus = FetchGenreStatus.NOT_FETCHED
}
}
}
private enum class FetchGenreStatus { NOT_FETCHED, FETCHED, FETCHING }
private class SelectOption(val name: String, val id: String)
private open class SelectFilter(
name: String,
val options: List<SelectOption>,
) : Filter.Select<String>(name, options.map { it.name }.toTypedArray())
private val secondsUnit = listOf("second", "seconds", "giây")
private val minutesUnit = listOf("minute", "minutes", "phút")
private val hourUnit = listOf("hour", "hours", "giờ")
private val dayUnit = listOf("day", "days", "ngày")
private val weekUnit = listOf("week", "weeks", "tuần")
private val monthUnit = listOf("month", "months", "tháng")
private val yearUnit = listOf("year", "years", "năm")
private fun parseRelativeDate(date: String): Long {
val (valueString, unit) = date.substringBeforeLast(" ").split(" ", limit = 2)
val value = valueString.toInt()
val calendar = Calendar.getInstance().apply {
val field = when {
secondsUnit.contains(unit) -> Calendar.SECOND
minutesUnit.contains(unit) -> Calendar.MINUTE
hourUnit.contains(unit) -> Calendar.HOUR_OF_DAY
dayUnit.contains(unit) -> Calendar.DAY_OF_MONTH
weekUnit.contains(unit) -> Calendar.WEEK_OF_MONTH
monthUnit.contains(unit) -> Calendar.MONTH
yearUnit.contains(unit) -> Calendar.YEAR
else -> return 0L
}
add(field, -value)
}
return calendar.timeInMillis
}
protected fun Element.imgAttr(): String = when {
hasAttr("data-src") -> attr("abs:data-src")
hasAttr("data-lazy-src") -> attr("abs:data-lazy-src")
hasAttr("srcset") -> attr("abs:srcset").substringBefore(" ")
hasAttr("data-cfsrc") -> attr("abs:data-cfsrc")
else -> attr("abs:src")
}
}

View File

@ -147,9 +147,6 @@ abstract class ZeistManga(
protected open val mangaDetailsSelector = ".grid.gtc-235fr"
protected open val mangaDetailsSelectorDescription = "#synopsis"
protected open val mangaDetailsSelectorGenres = "div.mt-15 > a[rel=tag]"
protected open val mangaDetailsSelectorAuthor = "span#author"
protected open val mangaDetailsSelectorArtist = "span#artist"
protected open val mangaDetailsSelectorAltName = "header > p"
protected open val mangaDetailsSelectorInfo = ".y6x11p"
protected open val mangaDetailsSelectorInfoTitle = "strong"
protected open val mangaDetailsSelectorInfoDescription = "span.dt"
@ -159,18 +156,9 @@ abstract class ZeistManga(
val profileManga = document.selectFirst(mangaDetailsSelector)!!
return SManga.create().apply {
thumbnail_url = profileManga.selectFirst("img")!!.attr("abs:src")
description = buildString {
append(profileManga.select(mangaDetailsSelectorDescription).text())
append("\n\n")
profileManga.selectFirst(mangaDetailsSelectorAltName)?.text()?.takeIf { it.isNotBlank() }?.let {
append("Alternative name(s): ")
append(it)
}
}.trim()
description = profileManga.select(mangaDetailsSelectorDescription).text()
genre = profileManga.select(mangaDetailsSelectorGenres)
.joinToString { it.text() }
author = profileManga.selectFirst(mangaDetailsSelectorAuthor)?.text()
artist = profileManga.selectFirst(mangaDetailsSelectorArtist)?.text()
val infoElement = profileManga.select(mangaDetailsSelectorInfo)
infoElement.forEach { element ->
@ -214,7 +202,7 @@ abstract class ZeistManga(
protected open val useNewChapterFeed = false
protected open val useOldChapterFeed = false
private val chapterFeedRegex = """clwd\.run\(["'](.*?)["']\)""".toRegex()
private val chapterFeedRegex = """clwd\.run\('([^']+)'""".toRegex()
private val scriptSelector = "#clwd > script"
open fun getChapterFeedUrl(doc: Document): String {

View File

@ -1,3 +1,23 @@
plugins {
id("lib-android")
id("com.android.library")
kotlin("android")
}
android {
compileSdk = AndroidConfig.compileSdk
defaultConfig {
minSdk = AndroidConfig.minSdk
}
namespace = "eu.kanade.tachiyomi.lib.cookieinterceptor"
}
repositories {
mavenCentral()
}
dependencies {
compileOnly(libs.kotlin.stdlib)
compileOnly(libs.okhttp)
}

View File

@ -1,3 +1,22 @@
plugins {
id("lib-android")
id("com.android.library")
kotlin("android")
}
android {
compileSdk = AndroidConfig.compileSdk
defaultConfig {
minSdk = AndroidConfig.minSdk
}
namespace = "eu.kanade.tachiyomi.lib.cryptoaes"
}
repositories {
mavenCentral()
}
dependencies {
compileOnly(libs.kotlin.stdlib)
}

View File

@ -1,3 +1,24 @@
plugins {
id("lib-android")
id("com.android.library")
kotlin("android")
}
android {
compileSdk = AndroidConfig.compileSdk
defaultConfig {
minSdk = AndroidConfig.minSdk
}
namespace = "eu.kanade.tachiyomi.lib.dataimage"
}
repositories {
mavenCentral()
}
dependencies {
compileOnly(libs.kotlin.stdlib)
compileOnly(libs.okhttp)
compileOnly(libs.jsoup)
}

View File

@ -1,3 +1,22 @@
plugins {
id("lib-android")
id("com.android.library")
kotlin("android")
}
android {
compileSdk = AndroidConfig.compileSdk
defaultConfig {
minSdk = AndroidConfig.minSdk
}
namespace = "eu.kanade.tachiyomi.lib.i18n"
}
repositories {
mavenCentral()
}
dependencies {
compileOnly(libs.kotlin.stdlib)
}

View File

@ -1,3 +1,12 @@
plugins {
id("lib-android")
`java-library`
kotlin("jvm")
}
repositories {
mavenCentral()
}
dependencies {
compileOnly(libs.kotlin.stdlib)
}

View File

@ -1,3 +1,23 @@
plugins {
id("lib-android")
id("com.android.library")
kotlin("android")
id("kotlinx-serialization")
}
android {
compileSdk = AndroidConfig.compileSdk
defaultConfig {
minSdk = AndroidConfig.minSdk
}
namespace = "eu.kanade.tachiyomi.lib.randomua"
}
repositories {
mavenCentral()
}
dependencies {
compileOnly(libs.bundles.common)
}

View File

@ -1,7 +1,24 @@
plugins {
id("lib-android")
id("com.android.library")
kotlin("android")
id("kotlinx-serialization")
}
android {
compileSdk = AndroidConfig.compileSdk
defaultConfig {
minSdk = AndroidConfig.minSdk
}
namespace = "eu.kanade.tachiyomi.lib.speedbinb"
}
repositories {
mavenCentral()
}
dependencies {
compileOnly(libs.bundles.common)
implementation(project(":lib:textinterceptor"))
}

View File

@ -1,3 +1,22 @@
plugins {
id("lib-android")
id("com.android.library")
kotlin("android")
}
android {
compileSdk = AndroidConfig.compileSdk
defaultConfig {
minSdk = AndroidConfig.minSdk
}
namespace = "eu.kanade.tachiyomi.lib.synchrony"
}
repositories {
mavenCentral()
}
dependencies {
compileOnly(libs.bundles.common)
}

View File

@ -1,3 +1,23 @@
plugins {
id("lib-android")
id("com.android.library")
kotlin("android")
}
android {
compileSdk = AndroidConfig.compileSdk
defaultConfig {
minSdk = AndroidConfig.minSdk
}
namespace = "eu.kanade.tachiyomi.lib.textinterceptor"
}
repositories {
mavenCentral()
}
dependencies {
compileOnly(libs.kotlin.stdlib)
compileOnly(libs.okhttp)
}

View File

@ -1,3 +1,12 @@
plugins {
id("lib-android")
`java-library`
kotlin("jvm")
}
repositories {
mavenCentral()
}
dependencies {
compileOnly(libs.kotlin.stdlib)
}

View File

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

View File

@ -2,8 +2,8 @@ ext {
extName = 'Miau Scan'
extClass = '.MiauScanFactory'
themePkg = 'mangathemesia'
baseUrl = 'https://miauvisor.org'
overrideVersionCode = 3
baseUrl = 'https://miaucomics.org'
overrideVersionCode = 2
}
apply from: "$rootDir/common.gradle"

View File

@ -20,7 +20,7 @@ class MiauScanFactory : SourceFactory {
open class MiauScan(lang: String) : MangaThemesia(
name = "Miau Scan",
baseUrl = "https://miauvisor.org",
baseUrl = "https://miaucomics.org",
lang = lang,
dateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale("es")),
) {
@ -58,9 +58,6 @@ open class MiauScan(lang: String) : MangaThemesia(
}
}
override val seriesAuthorSelector = ".tsinfo .imptdt:contains(autor) i"
override val seriesStatusSelector = ".tsinfo .imptdt:contains(estado) i"
override fun mangaDetailsParse(document: Document): SManga {
return super.mangaDetailsParse(document).apply {
title = title.replace(PORTUGUESE_SUFFIX, "")

View File

@ -1,7 +1,7 @@
ext {
extName = 'NHentai'
extClass = '.NHFactory'
extVersionCode = 40
extVersionCode = 39
isNsfw = true
}

View File

@ -302,7 +302,6 @@ open class NHentai(
"Sort By",
arrayOf(
Pair("Popular: All Time", "popular"),
Pair("Popular: Month", "popular-month"),
Pair("Popular: Week", "popular-week"),
Pair("Popular: Today", "popular-today"),
Pair("Recent", "date"),

View File

@ -1,7 +1,7 @@
ext {
extName = 'Photos18'
extClass = '.Photos18'
extVersionCode = 3
extVersionCode = 2
isNsfw = true
}

View File

@ -51,7 +51,7 @@ class Photos18 : HttpSource(), ConfigurableSource {
SManga.create().apply {
url = link.attr("href").stripLang()
title = link.ownText()
thumbnail_url = baseUrl + it.selectFirst(Evaluator.Tag("img"))!!.attr("src")
thumbnail_url = baseUrl + it.selectFirst(Evaluator.Tag("img"))!!.attr("data-src")
genre = cardBody.selectFirst(Evaluator.Tag("label"))!!.ownText()
status = SManga.COMPLETED
initialized = true
@ -100,7 +100,7 @@ class Photos18 : HttpSource(), ConfigurableSource {
val document = response.asJsoup()
val images = document.selectFirst(Evaluator.Id("content"))!!.select(Evaluator.Tag("img"))
return images.mapIndexed { index, image ->
Page(index, imageUrl = image.attr("src"))
Page(index, imageUrl = image.attr("data-src"))
}
}

View File

@ -2,8 +2,8 @@ ext {
extName = 'ARESNOV'
extClass = '.ARESNOV'
themePkg = 'mangathemesia'
baseUrl = 'https://manhuascarlet.com'
overrideVersionCode = 1
baseUrl = 'https://aresnov.org'
overrideVersionCode = 0
}
apply from: "$rootDir/common.gradle"

View File

@ -12,7 +12,7 @@ import java.util.Locale
class ARESNOV : MangaThemesia(
"ARESNOV",
"https://manhuascarlet.com",
"https://aresnov.org",
"ar",
mangaUrlDirectory = "/series",
dateFormat = SimpleDateFormat("MMMMM dd, yyyy", Locale("ar")),

View File

@ -1,12 +1,16 @@
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,4 +9,12 @@ 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,20 +6,17 @@ 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(
"Empire Webtoon",
"https://webtoonsempireron.com",
"ar",
SimpleDateFormat("d MMMM، yyyy", Locale("ar")),
),
ConfigurableSource {
class EmpireWebtoon : Madara(
"Empire Webtoon",
"https://webtoonsempireron.com",
"ar",
SimpleDateFormat("d MMMM، yyyy", Locale("ar")),
) {
private val defaultBaseUrl = "https://webtoonsempireron.com"
@ -33,6 +30,8 @@ class EmpireWebtoon :
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"
@ -54,6 +53,8 @@ class EmpireWebtoon :
}
}
screen.addPreference(baseUrlPref)
super.setupPreferenceScreen(screen)
}
private fun getPrefBaseUrl(): String = preferences.getString(BASE_URL_PREF, defaultBaseUrl)!!

View File

@ -2,8 +2,8 @@ ext {
extName = 'ARESManga'
extClass = '.ARESManga'
themePkg = 'mangathemesia'
baseUrl = 'https://fl-ares.com'
overrideVersionCode = 3
baseUrl = 'https://en-aresmanga.com'
overrideVersionCode = 2
}
apply from: "$rootDir/common.gradle"

View File

@ -12,7 +12,7 @@ import java.util.Locale
class ARESManga : MangaThemesia(
"ARESManga",
"https://fl-ares.com",
"https://en-aresmanga.com",
"ar",
mangaUrlDirectory = "/series",
dateFormat = SimpleDateFormat("MMMMM dd, yyyy", Locale("ar")),

View File

@ -6,13 +6,14 @@ 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
@ -21,17 +22,8 @@ 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")),
),
ConfigurableSource {
class Mangalek : Madara("مانجا ليك", "https://manga-lek.net", "ar", SimpleDateFormat("MMMM dd, yyyy", Locale("ar"))) {
override val fetchGenres = false
override val useLoadMoreRequest = LoadMoreStrategy.Always
override val chapterUrlSuffix = ""
private val defaultBaseUrl = "https://manga-lek.net"
@ -63,10 +55,22 @@ class Mangalek :
}
}
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

@ -3,7 +3,7 @@ ext {
extClass = '.Mangalink'
themePkg = 'madara'
baseUrl = 'https://manga-link.com'
overrideVersionCode = 2
overrideVersionCode = 1
}
apply from: "$rootDir/common.gradle"

View File

@ -6,23 +6,15 @@ 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")),
),
ConfigurableSource {
class Mangalink : Madara("مانجا لينك", "https://manga-link.com", "ar", SimpleDateFormat("MMMM dd, yyyy", Locale("ar"))) {
override val chapterUrlSuffix = ""
override val useLoadMoreRequest = LoadMoreStrategy.Always
private val defaultBaseUrl = "https://manga-link.com"
override val baseUrl by lazy { getPrefBaseUrl() }
@ -52,6 +44,8 @@ class Mangalink :
}
}
screen.addPreference(baseUrlPref)
super.setupPreferenceScreen(screen)
}
private fun getPrefBaseUrl(): String = preferences.getString(BASE_URL_PREF, defaultBaseUrl)!!
}

View File

@ -3,7 +3,7 @@ ext {
extClass = '.MangaLionz'
themePkg = 'madara'
baseUrl = 'https://mangalionz.org'
overrideVersionCode = 3
overrideVersionCode = 2
}
apply from: "$rootDir/common.gradle"

View File

@ -5,17 +5,16 @@ import eu.kanade.tachiyomi.source.model.SManga
import org.jsoup.nodes.Element
class MangaLionz : Madara("MangaLionz", "https://mangalionz.org", "ar") {
override val useLoadMoreRequest = LoadMoreStrategy.Always
override fun popularMangaFromElement(element: Element): SManga {
val manga = SManga.create()
with(element) {
selectFirst(popularMangaUrlSelector)!!.let {
select(popularMangaUrlSelector).first()?.let {
manga.setUrlWithoutDomain(it.attr("abs:href"))
manga.title = it.ownText()
}
selectFirst("img")?.let {
select("img").first()?.let {
manga.thumbnail_url = imageFromElement(it)?.replace("mangalionz", "mangalek")
}
}

View File

@ -9,4 +9,12 @@ 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

@ -3,7 +3,7 @@ ext {
extClass = '.MangaSpark'
themePkg = 'madara'
baseUrl = 'https://mangaspark.org'
overrideVersionCode = 5
overrideVersionCode = 4
}
apply from: "$rootDir/common.gradle"

View File

@ -11,6 +11,8 @@ class MangaSpark : Madara(
dateFormat = SimpleDateFormat("d MMMM، yyyy", Locale("ar")),
) {
override val chapterUrlSuffix = ""
override val useLoadMoreRequest = LoadMoreStrategy.Always
override val useNewChapterEndpoint = false
override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/"
}

View File

@ -3,7 +3,7 @@ ext {
extClass = '.MangaStarz'
themePkg = 'madara'
baseUrl = 'https://mangastarz.org'
overrideVersionCode = 6
overrideVersionCode = 5
}
apply from: "$rootDir/common.gradle"

View File

@ -11,6 +11,8 @@ class MangaStarz : Madara(
dateFormat = SimpleDateFormat("d MMMM، yyyy", Locale("ar")),
) {
override val chapterUrlSuffix = ""
override val useLoadMoreRequest = LoadMoreStrategy.Always
override val useNewChapterEndpoint = false
override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/"
}

View File

@ -2,8 +2,8 @@ ext {
extName = 'MangaSwat'
extClass = '.MangaSwat'
themePkg = 'mangathemesia'
baseUrl = 'https://swatmanhua.com'
overrideVersionCode = 16
baseUrl = 'https://goldragon.me'
overrideVersionCode = 15
}
apply from: "$rootDir/common.gradle"

View File

@ -21,15 +21,13 @@ import uy.kohesive.injekt.api.get
import java.text.SimpleDateFormat
import java.util.Locale
private const val swatUrl = "https://swatmanhua.com"
class MangaSwat : MangaThemesia(
"MangaSwat",
swatUrl,
"https://goldragon.me",
"ar",
dateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale("ar")),
) {
private val defaultBaseUrl = swatUrl
private val defaultBaseUrl = "https://goldragon.me"
override val baseUrl by lazy { getPrefBaseUrl() }

View File

@ -2,8 +2,8 @@ ext {
extName = 'PotatoManga'
extClass = '.PotatoManga'
themePkg = 'mangathemesia'
baseUrl = 'https://ar.potatomanga.xyz'
overrideVersionCode = 2
baseUrl = 'https://potatomanga.xyz'
overrideVersionCode = 1
}
apply from: "$rootDir/common.gradle"

View File

@ -8,9 +8,9 @@ import java.util.Locale
class PotatoManga : MangaThemesia(
"PotatoManga",
"https://ar.potatomanga.xyz",
"https://potatomanga.xyz",
"ar",
mangaUrlDirectory = "/manga",
mangaUrlDirectory = "/series",
dateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale("ar")),
) {
override val seriesArtistSelector =

View File

@ -1,5 +1,11 @@
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")
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]"
}

View File

@ -8,6 +8,8 @@ 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,4 +4,6 @@ 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,11 +1,37 @@
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 val useLoadMoreRequest = LoadMoreStrategy.Always
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
}
}
}

View File

@ -4,4 +4,6 @@ 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

@ -1,9 +0,0 @@
ext {
extName = 'Blackout Scans'
extClass = '.BlackoutScans'
themePkg = 'keyoapp'
baseUrl = 'https://blackoutscans.com'
overrideVersionCode = 0
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

View File

@ -1,5 +0,0 @@
package eu.kanade.tachiyomi.extension.en.blackoutscans
import eu.kanade.tachiyomi.multisrc.keyoapp.Keyoapp
class BlackoutScans : Keyoapp("Blackout Scans", "https://blackoutscans.com", "en")

View File

@ -6,6 +6,8 @@ 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,4 +11,6 @@ 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,4 +4,6 @@ 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,6 +14,7 @@ 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
@ -34,6 +35,26 @@ 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(
@ -121,13 +142,11 @@ 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 = intl["order_by_filter_title"],
options = orderByFilterOptions.map { Pair(it.key, it.value) },
title = orderByFilterTitle,
options = orderByFilterOptions.zip(orderByFilterOptionsValues),
state = 0,
),
)

View File

@ -4,4 +4,6 @@ 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

@ -1,9 +0,0 @@
ext {
extName = 'DMC Scans'
extClass = '.DMCScans'
themePkg = 'zeistmanga'
baseUrl = 'https://didascans.blogspot.com'
overrideVersionCode = 0
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

View File

@ -1,66 +0,0 @@
package eu.kanade.tachiyomi.extension.en.dmcscans
import eu.kanade.tachiyomi.multisrc.zeistmanga.Genre
import eu.kanade.tachiyomi.multisrc.zeistmanga.ZeistManga
import eu.kanade.tachiyomi.network.interceptor.rateLimit
import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.Response
import org.jsoup.Jsoup
class DMCScans : ZeistManga("DMC Scans", "https://didascans.blogspot.com", "en") {
override val client = super.client.newBuilder()
.rateLimit(2)
.build()
// ============================== Popular ===============================
override val popularMangaSelector = ".PopularPosts > article"
override val popularMangaSelectorTitle = ".post-title a"
override val popularMangaSelectorUrl = ".post-title a"
// =========================== Manga Details ============================
override val mangaDetailsSelectorGenres = "#labels > a[rel=tag]"
override val mangaDetailsSelectorInfo = ".imptdt"
// =============================== Filters ==============================
override val hasFilters = true
override val hasTypeFilter = false
override val hasLanguageFilter = false
override fun getGenreList(): List<Genre> = listOf(
Genre("Adaptation", "Adaptation"),
Genre("Drama", "Drama"),
Genre("Historical", "Historical"),
Genre("Josei(W)", "Josei(W)"),
Genre("Regression", "Regression"),
Genre("Romance", "Romance"),
Genre("Shojo(G)", "Shojo(G)"),
Genre("Slice of Life", "Slice of Life"),
Genre("Transmigration", "Transmigration"),
)
// =============================== Pages ================================
override fun pageListParse(response: Response): List<Page> {
val document = response.asJsoup()
val imgData = document.selectFirst("script:containsData(imgTags)")
?.data()
?.substringAfter("imgTags")
?.substringAfter("`")
?.substringBefore("`")
?.replace("\\\"", "\"")
?.replace("\\\\", "\\")
?.replace("\\/", "/")
?.replace("\\:", ":")
?.let(Jsoup::parseBodyFragment)
?: return super.pageListParse(response)
return imgData.select("img[src]").mapIndexed { i, img ->
Page(i, imageUrl = img.attr("abs:src"))
}
}
}

View File

@ -27,6 +27,14 @@ 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> {
@ -61,8 +69,20 @@ 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

@ -3,7 +3,7 @@ ext {
extClass = '.ElarcPage'
themePkg = 'mangathemesia'
baseUrl = 'https://elarctoon.com'
overrideVersionCode = 4
overrideVersionCode = 3
isNsfw = false
}

View File

@ -1,81 +1,12 @@
package eu.kanade.tachiyomi.extension.en.elarcpage
import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia
import eu.kanade.tachiyomi.network.GET
import okhttp3.Interceptor
import okhttp3.Response
import org.jsoup.Jsoup
import java.io.IOException
class ElarcPage : MangaThemesia(
"Elarc Toon",
"https://elarctoon.com",
"en",
"/readtoons98111",
) {
override val id = 5482125641807211052
override val client = network.cloudflareClient.newBuilder()
.addInterceptor(::dynamicUrlInterceptor)
.build()
private var dynamicUrlUpdated: Long = 0
private val dynamicUrlValidity: Long = 10 * 60 // 10 minutes
private fun dynamicUrlInterceptor(chain: Interceptor.Chain): Response {
val request = chain.request()
val timeNow = System.currentTimeMillis() / 1000
// Check if request requires an up-to-date URL
if (request.url.pathSegments[0] == mangaUrlDirectory.substring(1)) {
// Force update URL if required
if (timeNow - dynamicUrlUpdated > dynamicUrlValidity) {
client.newCall(GET(baseUrl)).execute()
if (timeNow - dynamicUrlUpdated > dynamicUrlValidity) {
throw IOException("Failed to update dynamic url")
}
}
if (request.url.pathSegments[0] != mangaUrlDirectory.substring(1)) {
// Need to rewrite URL
val newUrl = request.url.newBuilder()
.setPathSegment(0, mangaUrlDirectory.substring(1))
.build()
val newRequest = request.newBuilder()
.url(newUrl)
.build()
return chain.proceed(newRequest)
}
}
// Always update URL
val response = chain.proceed(request)
val document = Jsoup.parse(
response.peekBody(Long.MAX_VALUE).string(),
request.url.toString(),
)
document.select("#menu-item-14 > a, a:contains(All Series), #main-menu a, .mm a")
.reversed()
.map { it.attr("href") }
.lastOrNull { it.length >= 2 && it[0] == '/' }
?.let {
setMangaUrlDirectory(it)
dynamicUrlUpdated = timeNow
}
return response
}
private fun setMangaUrlDirectory(mangaUrlDirectory: String) {
try {
// this is fine
val field = this.javaClass.superclass.getDeclaredField("mangaUrlDirectory")
field.isAccessible = true
field.set(this, mangaUrlDirectory)
} catch (_: Exception) {
}
}
}

View File

@ -5,4 +5,6 @@ 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,4 +4,6 @@ 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> {
launchIO { countViews(document) }
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,4 +56,12 @@ 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,4 +4,6 @@ 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,4 +6,6 @@ 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,4 +4,6 @@ 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,4 +4,6 @@ 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,6 +12,8 @@ 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,21 +2,72 @@ 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 val useLoadMoreRequest = LoadMoreStrategy.Always
override fun popularMangaNextPageSelector() = "body:not(:has(.no-posts))"
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,6 +21,8 @@ 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()
@ -75,23 +77,23 @@ class GourmetScans : Madara(
private var genresList: List<Pair<String, String>> = emptyList()
class GenreFilter(vals: List<Pair<String, String>>) :
class GenreFilter(val vals: List<Pair<String, String>>) :
UriPartFilter("Genre", vals.toTypedArray())
override fun getFilterList(): FilterList {
val filters = buildList(4) {
add(YearFilter(intl["year_filter_title"]))
add(YearFilter(yearFilterTitle))
add(
OrderByFilter(
title = intl["order_by_filter_title"],
options = orderByFilterOptions.map { Pair(it.key, it.value) },
title = orderByFilterTitle,
options = orderByFilterOptions.zip(orderByFilterOptionsValues),
state = 0,
),
)
add(Filter.Separator())
if (genresList.isEmpty()) {
add(Filter.Header(intl["genre_missing_warning"]))
add(Filter.Header(genresMissingWarning))
} else {
add(GenreFilter(listOf(Pair("<select>", "")) + genresList))
}

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