Compare commits
63 Commits
a62d90d4aa
...
f6cb65688d
Author | SHA1 | Date |
---|---|---|
bapeey | f6cb65688d | |
bapeey | 6c6f1b4d6b | |
bapeey | ee937137c3 | |
bapeey | 4f3500d728 | |
Chopper | d951e22f52 | |
TheKingTermux | f2018fdc4a | |
Chopper | dfb2092c75 | |
bapeey | 1fc3a46632 | |
Chopper | 76bd3bb892 | |
mohamedotaku | 79d24a894a | |
bapeey | aa701c7c5e | |
bapeey | f0d2df86c5 | |
beerpsi | a95fa2dd5c | |
Tef | 6d16a8908c | |
Cuong M. Tran | 1abe238809 | |
bapeey | d6f01fea0b | |
mohamedotaku | 942ca100d8 | |
mohamedotaku | bbffed1fd8 | |
mohamedotaku | 01371a728a | |
Eshlender | 8870ff3569 | |
Cuong M. Tran | aac281ef87 | |
Secozzi | 0992869ffc | |
Secozzi | 59a640e696 | |
beerpsi | 29a8d5ead9 | |
Chopper | 0624b1aa3e | |
Chopper | 086475a12e | |
Secozzi | fc5a4825cb | |
Vetle Ledaal | fa1170fad7 | |
AwkwardPeak7 | 7ef3bcd497 | |
mohamedotaku | cb23787606 | |
Secozzi | a4e00cedbe | |
Secozzi | 1d6ed34d77 | |
Cuong M. Tran | 9d73eef491 | |
mohamedotaku | 838ee2fc97 | |
mohamedotaku | 94762d7d0d | |
bapeey | 2ec509f3d4 | |
TrungSamSet | 26ef357149 | |
Rama Bondan Prakoso | 0f00f9c313 | |
bapeey | 4bf383a322 | |
stevenyomi | 3372f9de22 | |
Chopper | 2e25acbc36 | |
bapeey | e5d3eeb7cc | |
Eshlender | 2e41214ba9 | |
mohamedotaku | daef319051 | |
Rolando Lecca | 8737cc41f2 | |
Rolando Lecca | 921649ff76 | |
Rolando Lecca | 3326753fc0 | |
AwkwardPeak7 | 073a0b7a3f | |
AwkwardPeak7 | 014ee88bbb | |
Secozzi | 678c3de899 | |
stevenyomi | 9ef0b03211 | |
stevenyomi | fdc57d60a4 | |
stevenyomi | fc000a5058 | |
Tef | be291b1822 | |
Rolando Lecca | c9633f3d0c | |
stevenyomi | b874f0d1c3 | |
Rolando Lecca | d311409641 | |
Cuong M. Tran | 09ae28784a | |
Secozzi | 166dbec86f | |
mohamedotaku | a03bb1f780 | |
Rolando Lecca | b7ecf1b801 | |
Vetle Ledaal | 0ed4e4147f | |
AwkwardPeak7 | 30b13498b0 |
|
@ -1,17 +1,3 @@
|
||||||
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 {
|
allprojects {
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
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())
|
||||||
|
}
|
|
@ -23,11 +23,6 @@ android {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
buildFeatures {
|
|
||||||
resValues = false
|
|
||||||
shaders = false
|
|
||||||
}
|
|
||||||
|
|
||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
freeCompilerArgs += "-opt-in=kotlinx.serialization.ExperimentalSerializationApi"
|
freeCompilerArgs += "-opt-in=kotlinx.serialization.ExperimentalSerializationApi"
|
||||||
}
|
}
|
||||||
|
@ -41,10 +36,6 @@ kotlinter {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
repositories {
|
|
||||||
mavenCentral()
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: use versionCatalogs.named("libs") in Gradle 8.5
|
// TODO: use versionCatalogs.named("libs") in Gradle 8.5
|
||||||
val libs = project.extensions.getByType<VersionCatalogsExtension>().named("libs")
|
val libs = project.extensions.getByType<VersionCatalogsExtension>().named("libs")
|
||||||
dependencies {
|
dependencies {
|
||||||
|
|
|
@ -71,8 +71,6 @@ android {
|
||||||
}
|
}
|
||||||
|
|
||||||
buildFeatures {
|
buildFeatures {
|
||||||
resValues false
|
|
||||||
shaders false
|
|
||||||
buildConfig true
|
buildConfig true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,10 +93,6 @@ android {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
repositories {
|
|
||||||
mavenCentral()
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
if (theme != null) implementation(theme) // Overrides core launcher icons
|
if (theme != null) implementation(theme) // Overrides core launcher icons
|
||||||
implementation(project(":core"))
|
implementation(project(":core"))
|
||||||
|
|
|
@ -21,5 +21,7 @@ org.gradle.caching=true
|
||||||
|
|
||||||
# Enable AndroidX dependencies
|
# Enable AndroidX dependencies
|
||||||
android.useAndroidX=true
|
android.useAndroidX=true
|
||||||
android.nonTransitiveRClass=false
|
|
||||||
android.nonFinalResIds=false
|
android.enableBuildConfigAsBytecode=true
|
||||||
|
android.defaults.buildfeatures.resvalues=false
|
||||||
|
android.defaults.buildfeatures.shaders=false
|
||||||
|
|
|
@ -2,4 +2,4 @@ plugins {
|
||||||
id("lib-multisrc")
|
id("lib-multisrc")
|
||||||
}
|
}
|
||||||
|
|
||||||
baseVersionCode = 1
|
baseVersionCode = 2
|
||||||
|
|
|
@ -4,12 +4,12 @@ import kotlinx.serialization.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class BloggerDto(
|
class BloggerDto(
|
||||||
val feed: BloggerFeedDto,
|
val feed: BloggerFeedDto,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class BloggerFeedDto(
|
class BloggerFeedDto(
|
||||||
@SerialName("openSearch\$totalResults") val totalResults: BloggerTextDto,
|
@SerialName("openSearch\$totalResults") val totalResults: BloggerTextDto,
|
||||||
@SerialName("openSearch\$startIndex") val startIndex: BloggerTextDto,
|
@SerialName("openSearch\$startIndex") val startIndex: BloggerTextDto,
|
||||||
@SerialName("openSearch\$itemsPerPage") val itemsPerPage: BloggerTextDto,
|
@SerialName("openSearch\$itemsPerPage") val itemsPerPage: BloggerTextDto,
|
||||||
|
@ -18,32 +18,26 @@ data class BloggerFeedDto(
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class BloggerFeedEntryDto(
|
class BloggerFeedEntryDto(
|
||||||
val published: BloggerTextDto,
|
val published: BloggerTextDto,
|
||||||
val category: List<BloggerCategoryDto>,
|
val category: List<BloggerCategoryDto>? = emptyList(),
|
||||||
val title: BloggerTextDto,
|
val title: BloggerTextDto,
|
||||||
val content: BloggerTextDto,
|
val content: BloggerTextDto,
|
||||||
val link: List<BloggerLinkDto>,
|
val link: List<BloggerLinkDto>,
|
||||||
val author: List<BloggerAuthorDto>,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class BloggerLinkDto(
|
class BloggerLinkDto(
|
||||||
val rel: String,
|
val rel: String,
|
||||||
val href: String,
|
val href: String,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class BloggerCategoryDto(
|
class BloggerCategoryDto(
|
||||||
val term: String,
|
val term: String,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class BloggerAuthorDto(
|
class BloggerTextDto(
|
||||||
val name: BloggerTextDto,
|
|
||||||
)
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class BloggerTextDto(
|
|
||||||
@SerialName("\$t") val t: String,
|
@SerialName("\$t") val t: String,
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package eu.kanade.tachiyomi.multisrc.gravureblogger
|
package eu.kanade.tachiyomi.multisrc.gravureblogger
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import eu.kanade.tachiyomi.network.GET
|
import eu.kanade.tachiyomi.network.GET
|
||||||
import eu.kanade.tachiyomi.source.model.Filter
|
import eu.kanade.tachiyomi.source.model.Filter
|
||||||
|
@ -23,7 +22,6 @@ import uy.kohesive.injekt.injectLazy
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
@SuppressLint("ObsoleteSdkInt")
|
|
||||||
abstract class GravureBlogger(
|
abstract class GravureBlogger(
|
||||||
override val name: String,
|
override val name: String,
|
||||||
override val baseUrl: String,
|
override val baseUrl: String,
|
||||||
|
@ -61,7 +59,7 @@ abstract class GravureBlogger(
|
||||||
setUrlWithoutDomain(entry.link.first { it.rel == "alternate" }.href + "#${entry.published.t}")
|
setUrlWithoutDomain(entry.link.first { it.rel == "alternate" }.href + "#${entry.published.t}")
|
||||||
title = entry.title.t
|
title = entry.title.t
|
||||||
thumbnail_url = content.selectFirst("img")?.absUrl("src")
|
thumbnail_url = content.selectFirst("img")?.absUrl("src")
|
||||||
genre = entry.category.joinToString { it.term }
|
genre = entry.category?.joinToString { it.term }
|
||||||
status = SManga.COMPLETED
|
status = SManga.COMPLETED
|
||||||
update_strategy = UpdateStrategy.ONLY_FETCH_ONCE
|
update_strategy = UpdateStrategy.ONLY_FETCH_ONCE
|
||||||
initialized = true
|
initialized = true
|
||||||
|
|
|
@ -2,4 +2,4 @@ plugins {
|
||||||
id("lib-multisrc")
|
id("lib-multisrc")
|
||||||
}
|
}
|
||||||
|
|
||||||
baseVersionCode = 1
|
baseVersionCode = 2
|
||||||
|
|
|
@ -19,7 +19,9 @@ import okhttp3.Response
|
||||||
import org.jsoup.nodes.Document
|
import org.jsoup.nodes.Document
|
||||||
import org.jsoup.nodes.Element
|
import org.jsoup.nodes.Element
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
import java.text.ParseException
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.Calendar
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
abstract class Keyoapp(
|
abstract class Keyoapp(
|
||||||
|
@ -38,6 +40,8 @@ abstract class Keyoapp(
|
||||||
|
|
||||||
private val json: Json by injectLazy()
|
private val json: Json by injectLazy()
|
||||||
|
|
||||||
|
private val dateFormat = SimpleDateFormat("MMM d, yyyy", Locale.ENGLISH)
|
||||||
|
|
||||||
// Popular
|
// Popular
|
||||||
|
|
||||||
override fun popularMangaRequest(page: Int): Request = GET(baseUrl, headers)
|
override fun popularMangaRequest(page: Int): Request = GET(baseUrl, headers)
|
||||||
|
@ -206,7 +210,7 @@ abstract class Keyoapp(
|
||||||
|
|
||||||
// Chapter list
|
// Chapter list
|
||||||
|
|
||||||
override fun chapterListSelector(): String = "#chapters > a"
|
override fun chapterListSelector(): String = "#chapters > a:not(:has(.text-sm span:matches(Upcoming)))"
|
||||||
|
|
||||||
override fun chapterFromElement(element: Element): SChapter = SChapter.create().apply {
|
override fun chapterFromElement(element: Element): SChapter = SChapter.create().apply {
|
||||||
setUrlWithoutDomain(element.selectFirst("a[href]")!!.attr("href"))
|
setUrlWithoutDomain(element.selectFirst("a[href]")!!.attr("href"))
|
||||||
|
@ -259,13 +263,40 @@ abstract class Keyoapp(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun String.parseDate(): Long {
|
private fun String.parseDate(): Long {
|
||||||
return runCatching { DATE_FORMATTER.parse(this)?.time }
|
return if (this.contains("ago")) {
|
||||||
.getOrNull() ?: 0L
|
this.parseRelativeDate()
|
||||||
}
|
} else {
|
||||||
|
try {
|
||||||
companion object {
|
dateFormat.parse(this)!!.time
|
||||||
private val DATE_FORMATTER by lazy {
|
} catch (_: ParseException) {
|
||||||
SimpleDateFormat("MMM d, yyyy", Locale.ENGLISH)
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,4 +2,4 @@ plugins {
|
||||||
id("lib-multisrc")
|
id("lib-multisrc")
|
||||||
}
|
}
|
||||||
|
|
||||||
baseVersionCode = 4
|
baseVersionCode = 5
|
||||||
|
|
|
@ -367,8 +367,8 @@ abstract class LectorTmo(
|
||||||
|
|
||||||
if (script1 != null) {
|
if (script1 != null) {
|
||||||
val data = script1.data()
|
val data = script1.data()
|
||||||
val regexParams = """\{uniqid:'(.+)',cascade:(.+)\}""".toRegex()
|
val regexParams = """\{\s*uniqid\s*:\s*'(.+)'\s*,\s*cascade\s*:\s*(.+)\s*\}""".toRegex()
|
||||||
val regexAction = """form\.action\s?=\s?'(.+)'""".toRegex()
|
val regexAction = """form\.action\s*=\s*'(.+)'""".toRegex()
|
||||||
val params = regexParams.find(data)
|
val params = regexParams.find(data)
|
||||||
val action = regexAction.find(data)?.groupValues?.get(1)?.unescapeUrl()
|
val action = regexAction.find(data)?.groupValues?.get(1)?.unescapeUrl()
|
||||||
|
|
||||||
|
@ -393,7 +393,7 @@ abstract class LectorTmo(
|
||||||
|
|
||||||
if (script3 != null) {
|
if (script3 != null) {
|
||||||
val data = script3.data()
|
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()
|
val url = regexRedirect.find(data)?.groupValues?.get(1)?.unescapeUrl()
|
||||||
|
|
||||||
if (url != null) {
|
if (url != null) {
|
||||||
|
|
|
@ -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:
|
|
@ -0,0 +1,26 @@
|
||||||
|
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:
|
|
@ -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:
|
|
@ -2,9 +2,9 @@ plugins {
|
||||||
id("lib-multisrc")
|
id("lib-multisrc")
|
||||||
}
|
}
|
||||||
|
|
||||||
baseVersionCode = 33
|
baseVersionCode = 35
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
api(project(":lib:cryptoaes"))
|
api(project(":lib:cryptoaes"))
|
||||||
api(project(":lib:randomua"))
|
api(project(":lib:i18n"))
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,18 +1,11 @@
|
||||||
package eu.kanade.tachiyomi.multisrc.madara
|
package eu.kanade.tachiyomi.multisrc.madara
|
||||||
|
|
||||||
import android.app.Application
|
|
||||||
import android.content.SharedPreferences
|
|
||||||
import android.util.Base64
|
import android.util.Base64
|
||||||
import androidx.preference.PreferenceScreen
|
|
||||||
import eu.kanade.tachiyomi.lib.cryptoaes.CryptoAES
|
import eu.kanade.tachiyomi.lib.cryptoaes.CryptoAES
|
||||||
import eu.kanade.tachiyomi.lib.randomua.addRandomUAPreferenceToScreen
|
import eu.kanade.tachiyomi.lib.i18n.Intl
|
||||||
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.GET
|
||||||
import eu.kanade.tachiyomi.network.POST
|
import eu.kanade.tachiyomi.network.POST
|
||||||
import eu.kanade.tachiyomi.network.asObservable
|
import eu.kanade.tachiyomi.network.asObservableSuccess
|
||||||
import eu.kanade.tachiyomi.source.ConfigurableSource
|
|
||||||
import eu.kanade.tachiyomi.source.model.Filter
|
import eu.kanade.tachiyomi.source.model.Filter
|
||||||
import eu.kanade.tachiyomi.source.model.FilterList
|
import eu.kanade.tachiyomi.source.model.FilterList
|
||||||
import eu.kanade.tachiyomi.source.model.MangasPage
|
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.model.SManga
|
||||||
import eu.kanade.tachiyomi.source.online.ParsedHttpSource
|
import eu.kanade.tachiyomi.source.online.ParsedHttpSource
|
||||||
import eu.kanade.tachiyomi.util.asJsoup
|
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.Json
|
||||||
import kotlinx.serialization.json.jsonArray
|
import kotlinx.serialization.json.jsonArray
|
||||||
import kotlinx.serialization.json.jsonObject
|
import kotlinx.serialization.json.jsonObject
|
||||||
import kotlinx.serialization.json.jsonPrimitive
|
import kotlinx.serialization.json.jsonPrimitive
|
||||||
import okhttp3.CacheControl
|
|
||||||
import okhttp3.FormBody
|
import okhttp3.FormBody
|
||||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||||
import okhttp3.OkHttpClient
|
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
import org.jsoup.nodes.Document
|
import org.jsoup.nodes.Document
|
||||||
import org.jsoup.nodes.Element
|
import org.jsoup.nodes.Element
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import uy.kohesive.injekt.Injekt
|
|
||||||
import uy.kohesive.injekt.api.get
|
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
import java.text.ParseException
|
import java.text.ParseException
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Calendar
|
import java.util.Calendar
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
import java.util.concurrent.TimeUnit
|
|
||||||
|
|
||||||
abstract class Madara(
|
abstract class Madara(
|
||||||
override val name: String,
|
override val name: String,
|
||||||
override val baseUrl: String,
|
override val baseUrl: String,
|
||||||
final override val lang: String,
|
final override val lang: String,
|
||||||
private val dateFormat: SimpleDateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale.US),
|
private val dateFormat: SimpleDateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale.US),
|
||||||
) : ParsedHttpSource(), ConfigurableSource {
|
) : ParsedHttpSource() {
|
||||||
|
|
||||||
private val preferences: SharedPreferences by lazy {
|
|
||||||
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
|
||||||
}
|
|
||||||
|
|
||||||
override val supportsLatest = true
|
override val supportsLatest = true
|
||||||
|
|
||||||
override val client: OkHttpClient by lazy {
|
override val client = network.cloudflareClient
|
||||||
network.cloudflareClient.newBuilder()
|
|
||||||
.setRandomUserAgent(
|
|
||||||
preferences.getPrefUAType(),
|
|
||||||
preferences.getPrefCustomUA(),
|
|
||||||
)
|
|
||||||
.connectTimeout(10, TimeUnit.SECONDS)
|
|
||||||
.readTimeout(30, TimeUnit.SECONDS)
|
|
||||||
.build()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun headersBuilder() = super.headersBuilder()
|
override fun headersBuilder() = super.headersBuilder()
|
||||||
.add("Referer", "$baseUrl/")
|
.add("Referer", "$baseUrl/")
|
||||||
|
|
||||||
|
protected val xhrHeaders by lazy {
|
||||||
|
headersBuilder()
|
||||||
|
.set("X-Requested-With", "XMLHttpRequest")
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
protected open val json: Json by injectLazy()
|
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.
|
* 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.
|
* 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()
|
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.
|
* Inner variable to control how much tries the genres request was called.
|
||||||
*/
|
*/
|
||||||
|
@ -114,11 +100,58 @@ abstract class Madara(
|
||||||
*/
|
*/
|
||||||
protected open val mangaSubString = "manga"
|
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
|
// Popular Manga
|
||||||
|
|
||||||
override fun popularMangaParse(response: Response): MangasPage {
|
override fun popularMangaParse(response: Response): MangasPage {
|
||||||
runCatching { fetchGenres() }
|
val document = response.asJsoup()
|
||||||
return super.popularMangaParse(response)
|
|
||||||
|
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
|
// exclude/filter bilibili manga from list
|
||||||
|
@ -130,12 +163,12 @@ abstract class Madara(
|
||||||
val manga = SManga.create()
|
val manga = SManga.create()
|
||||||
|
|
||||||
with(element) {
|
with(element) {
|
||||||
select(popularMangaUrlSelector).first()?.let {
|
selectFirst(popularMangaUrlSelector)!!.let {
|
||||||
manga.setUrlWithoutDomain(it.attr("abs:href"))
|
manga.setUrlWithoutDomain(it.attr("abs:href"))
|
||||||
manga.title = it.ownText()
|
manga.title = it.ownText()
|
||||||
}
|
}
|
||||||
|
|
||||||
select("img").first()?.let {
|
selectFirst("img")?.let {
|
||||||
manga.thumbnail_url = imageFromElement(it)
|
manga.thumbnail_url = imageFromElement(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -143,15 +176,19 @@ abstract class Madara(
|
||||||
return manga
|
return manga
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun popularMangaRequest(page: Int): Request {
|
override fun popularMangaRequest(page: Int): Request =
|
||||||
return GET(
|
if (useLoadMoreRequest()) {
|
||||||
url = "$baseUrl/$mangaSubString/${searchPage(page)}?m_orderby=views",
|
loadMoreRequest(page, popular = true)
|
||||||
headers = headers,
|
} else {
|
||||||
cache = CacheControl.FORCE_NETWORK,
|
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
|
// Latest Updates
|
||||||
|
|
||||||
|
@ -162,53 +199,75 @@ abstract class Madara(
|
||||||
return popularMangaFromElement(element)
|
return popularMangaFromElement(element)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun latestUpdatesRequest(page: Int): Request {
|
override fun latestUpdatesRequest(page: Int): Request =
|
||||||
return GET(
|
if (useLoadMoreRequest()) {
|
||||||
url = "$baseUrl/$mangaSubString/${searchPage(page)}?m_orderby=latest",
|
loadMoreRequest(page, popular = false)
|
||||||
headers = headers,
|
} else {
|
||||||
cache = CacheControl.FORCE_NETWORK,
|
GET("$baseUrl/$mangaSubString/${searchPage(page)}?m_orderby=latest", headers)
|
||||||
)
|
}
|
||||||
}
|
|
||||||
|
|
||||||
override fun latestUpdatesNextPageSelector(): String? = popularMangaNextPageSelector()
|
override fun latestUpdatesNextPageSelector(): String? = popularMangaNextPageSelector()
|
||||||
|
|
||||||
override fun latestUpdatesParse(response: Response): MangasPage {
|
override fun latestUpdatesParse(response: Response): MangasPage {
|
||||||
val mp = super.latestUpdatesParse(response)
|
val mp = popularMangaParse(response)
|
||||||
val mangas = mp.mangas.distinctBy { it.url }
|
val mangas = mp.mangas.distinctBy { it.url }
|
||||||
return MangasPage(mangas, mp.hasNextPage)
|
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
|
// Search Manga
|
||||||
|
|
||||||
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
|
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
|
||||||
if (query.startsWith(URL_SEARCH_PREFIX)) {
|
if (query.startsWith(URL_SEARCH_PREFIX)) {
|
||||||
val mangaUrl = "$baseUrl/$mangaSubString/${query.substringAfter(URL_SEARCH_PREFIX)}"
|
val mangaUrl = "/$mangaSubString/${query.substringAfter(URL_SEARCH_PREFIX)}"
|
||||||
return client.newCall(GET(mangaUrl, headers))
|
return client.newCall(GET("$baseUrl$mangaUrl", headers))
|
||||||
.asObservable().map { response ->
|
.asObservableSuccess().map { response ->
|
||||||
MangasPage(listOf(mangaDetailsParse(response.asJsoup()).apply { url = "/$mangaSubString/${query.substringAfter(URL_SEARCH_PREFIX)}/" }), false)
|
val manga = mangaDetailsParse(response).apply {
|
||||||
|
url = mangaUrl
|
||||||
|
}
|
||||||
|
|
||||||
|
MangasPage(listOf(manga), false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return client.newCall(searchMangaRequest(page, query, filters))
|
return super.fetchSearchManga(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 = "page/$page/"
|
protected open fun searchPage(page: Int): String {
|
||||||
|
return if (page == 1) {
|
||||||
|
""
|
||||||
|
} else {
|
||||||
|
"page/$page/"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
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()
|
val url = "$baseUrl/${searchPage(page)}".toHttpUrl().newBuilder()
|
||||||
url.addQueryParameter("s", query)
|
url.addQueryParameter("s", query)
|
||||||
url.addQueryParameter("post_type", "wp-manga")
|
url.addQueryParameter("post_type", "wp-manga")
|
||||||
|
@ -260,108 +319,182 @@ abstract class Madara(
|
||||||
return GET(url.build(), headers)
|
return GET(url.build(), headers)
|
||||||
}
|
}
|
||||||
|
|
||||||
protected open val authorFilterTitle: String = when (lang) {
|
protected open fun searchLoadMoreRequest(page: Int, query: String, filters: FilterList): Request {
|
||||||
"pt-BR" -> "Autor"
|
val formBody = FormBody.Builder().apply {
|
||||||
else -> "Author"
|
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 artistFilterTitle: String = when (lang) {
|
protected open val statusFilterOptions: Map<String, String> =
|
||||||
"pt-BR" -> "Artista"
|
mapOf(
|
||||||
else -> "Artist"
|
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 yearFilterTitle: String = when (lang) {
|
protected open val orderByFilterOptions: Map<String, String> = mapOf(
|
||||||
"pt-BR" -> "Ano de lançamento"
|
intl["order_by_filter_relevance"] to "",
|
||||||
else -> "Year of Released"
|
intl["order_by_filter_latest"] to "latest",
|
||||||
}
|
intl["order_by_filter_az"] to "alphabet",
|
||||||
|
intl["order_by_filter_rating"] to "rating",
|
||||||
protected open val statusFilterTitle: String = when (lang) {
|
intl["order_by_filter_trending"] to "trending",
|
||||||
"pt-BR" -> "Estado"
|
intl["order_by_filter_views"] to "views",
|
||||||
else -> "Status"
|
intl["order_by_filter_new"] to "new-manga",
|
||||||
}
|
|
||||||
|
|
||||||
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 orderByFilterTitle: String = when (lang) {
|
protected open val genreConditionFilterOptions: Map<String, String> =
|
||||||
"pt-BR" -> "Ordenar por"
|
mapOf(
|
||||||
else -> "Order By"
|
intl["genre_condition_filter_or"] to "",
|
||||||
}
|
intl["genre_condition_filter_and"] to "1",
|
||||||
|
|
||||||
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",
|
protected open val adultContentFilterOptions: Map<String, String> =
|
||||||
"Latest",
|
mapOf(
|
||||||
"A-Z",
|
intl["adult_content_filter_all"] to "",
|
||||||
"Rating",
|
intl["adult_content_filter_none"] to "0",
|
||||||
"Trending",
|
intl["adult_content_filter_only"] to "1",
|
||||||
"Most Views",
|
|
||||||
"New",
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
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 orderByFilterOptionsValues: Array<String> = arrayOf(
|
open class Tag(name: String, val id: String) : Filter.CheckBox(name)
|
||||||
"",
|
|
||||||
"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 AuthorFilter(title: String) : Filter.Text(title)
|
||||||
protected class ArtistFilter(title: String) : Filter.Text(title)
|
protected class ArtistFilter(title: String) : Filter.Text(title)
|
||||||
|
@ -372,64 +505,75 @@ abstract class Madara(
|
||||||
protected class OrderByFilter(title: String, options: List<Pair<String, String>>, state: Int = 0) :
|
protected class OrderByFilter(title: String, options: List<Pair<String, String>>, state: Int = 0) :
|
||||||
UriPartFilter(title, options.toTypedArray(), state)
|
UriPartFilter(title, options.toTypedArray(), state)
|
||||||
|
|
||||||
protected class GenreConditionFilter(title: String, options: Array<String>) : UriPartFilter(
|
protected class GenreConditionFilter(title: String, options: List<Pair<String, String>>) : UriPartFilter(
|
||||||
title,
|
title,
|
||||||
options.zip(arrayOf("", "1")).toTypedArray(),
|
options.toTypedArray(),
|
||||||
)
|
)
|
||||||
|
|
||||||
protected class AdultContentFilter(title: String, options: Array<String>) : UriPartFilter(
|
protected class AdultContentFilter(title: String, options: List<Pair<String, String>>) : UriPartFilter(
|
||||||
title,
|
title,
|
||||||
options.zip(arrayOf("", "0", "1")).toTypedArray(),
|
options.toTypedArray(),
|
||||||
)
|
)
|
||||||
|
|
||||||
protected class GenreList(title: String, genres: List<Genre>) : Filter.Group<Genre>(title, genres)
|
protected class GenreList(title: String, genres: List<Genre>) : Filter.Group<GenreCheckBox>(title, genres.map { GenreCheckBox(it.name, it.id) })
|
||||||
class Genre(name: String, val id: String = name) : Filter.CheckBox(name)
|
class GenreCheckBox(name: String, val id: String = name) : Filter.CheckBox(name)
|
||||||
|
class Genre(val name: String, val id: String = name)
|
||||||
|
|
||||||
override fun getFilterList(): FilterList {
|
override fun getFilterList(): FilterList {
|
||||||
|
launchIO { fetchGenres() }
|
||||||
|
|
||||||
val filters = mutableListOf(
|
val filters = mutableListOf(
|
||||||
AuthorFilter(authorFilterTitle),
|
AuthorFilter(intl["author_filter_title"]),
|
||||||
ArtistFilter(artistFilterTitle),
|
ArtistFilter(intl["artist_filter_title"]),
|
||||||
YearFilter(yearFilterTitle),
|
YearFilter(intl["year_filter_title"]),
|
||||||
StatusFilter(statusFilterTitle, getStatusList()),
|
StatusFilter(
|
||||||
|
title = intl["status_filter_title"],
|
||||||
|
status = statusFilterOptions.map { Tag(it.key, it.value) },
|
||||||
|
),
|
||||||
OrderByFilter(
|
OrderByFilter(
|
||||||
title = orderByFilterTitle,
|
title = intl["order_by_filter_title"],
|
||||||
options = orderByFilterOptions.zip(orderByFilterOptionsValues),
|
options = orderByFilterOptions.toList(),
|
||||||
state = 0,
|
state = 0,
|
||||||
),
|
),
|
||||||
AdultContentFilter(adultContentFilterTitle, adultContentFilterOptions),
|
AdultContentFilter(
|
||||||
|
title = intl["adult_content_filter_title"],
|
||||||
|
options = adultContentFilterOptions.toList(),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
if (genresList.isNotEmpty()) {
|
if (genresList.isNotEmpty()) {
|
||||||
filters += listOf(
|
filters += listOf(
|
||||||
Filter.Separator(),
|
Filter.Separator(),
|
||||||
Filter.Header(genreFilterHeader),
|
Filter.Header(intl["genre_filter_header"]),
|
||||||
GenreConditionFilter(genreConditionFilterTitle, genreConditionFilterOptions),
|
GenreConditionFilter(
|
||||||
GenreList(genreFilterTitle, genresList),
|
title = intl["genre_condition_filter_title"],
|
||||||
|
options = genreConditionFilterOptions.toList(),
|
||||||
|
),
|
||||||
|
GenreList(
|
||||||
|
title = intl["genre_filter_title"],
|
||||||
|
genres = genresList,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
} else if (fetchGenres) {
|
} else if (fetchGenres) {
|
||||||
filters += listOf(
|
filters += listOf(
|
||||||
Filter.Separator(),
|
Filter.Separator(),
|
||||||
Filter.Header(genresMissingWarning),
|
Filter.Header(intl["genre_missing_warning"]),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return FilterList(filters)
|
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 {
|
override fun searchMangaParse(response: Response): MangasPage {
|
||||||
runCatching { fetchGenres() }
|
val document = response.asJsoup()
|
||||||
return super.searchMangaParse(response)
|
|
||||||
|
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"
|
override fun searchMangaSelector() = "div.c-tabs-item__content"
|
||||||
|
@ -438,11 +582,11 @@ abstract class Madara(
|
||||||
val manga = SManga.create()
|
val manga = SManga.create()
|
||||||
|
|
||||||
with(element) {
|
with(element) {
|
||||||
select("div.post-title a").first()?.let {
|
selectFirst("div.post-title a")!!.let {
|
||||||
manga.setUrlWithoutDomain(it.attr("abs:href"))
|
manga.setUrlWithoutDomain(it.attr("abs:href"))
|
||||||
manga.title = it.ownText()
|
manga.title = it.ownText()
|
||||||
}
|
}
|
||||||
select("img").first()?.let {
|
selectFirst("img")?.let {
|
||||||
manga.thumbnail_url = imageFromElement(it)
|
manga.thumbnail_url = imageFromElement(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -450,7 +594,7 @@ abstract class Madara(
|
||||||
return manga
|
return manga
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun searchMangaNextPageSelector(): String? = "div.nav-previous, nav.navigation-ajax, a.nextpostslink"
|
override fun searchMangaNextPageSelector() = popularMangaNextPageSelector()
|
||||||
|
|
||||||
// Manga Details Parse
|
// Manga Details Parse
|
||||||
|
|
||||||
|
@ -493,9 +637,7 @@ abstract class Madara(
|
||||||
override fun mangaDetailsParse(document: Document): SManga {
|
override fun mangaDetailsParse(document: Document): SManga {
|
||||||
val manga = SManga.create()
|
val manga = SManga.create()
|
||||||
with(document) {
|
with(document) {
|
||||||
select(mangaDetailsSelectorTitle).first()?.let {
|
manga.title = selectFirst(mangaDetailsSelectorTitle)!!.ownText()
|
||||||
manga.title = it.ownText()
|
|
||||||
}
|
|
||||||
select(mangaDetailsSelectorAuthor).eachText().filter {
|
select(mangaDetailsSelectorAuthor).eachText().filter {
|
||||||
it.notUpdating()
|
it.notUpdating()
|
||||||
}.joinToString().takeIf { it.isNotBlank() }?.let {
|
}.joinToString().takeIf { it.isNotBlank() }?.let {
|
||||||
|
@ -515,7 +657,7 @@ abstract class Madara(
|
||||||
manga.description = it.text()
|
manga.description = it.text()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
select(mangaDetailsSelectorThumbnail).first()?.let {
|
selectFirst(mangaDetailsSelectorThumbnail)?.let {
|
||||||
manga.thumbnail_url = imageFromElement(it)
|
manga.thumbnail_url = imageFromElement(it)
|
||||||
}
|
}
|
||||||
select(mangaDetailsSelectorStatus).last()?.let {
|
select(mangaDetailsSelectorStatus).last()?.let {
|
||||||
|
@ -533,13 +675,6 @@ abstract class Madara(
|
||||||
.map { element -> element.text().lowercase(Locale.ROOT) }
|
.map { element -> element.text().lowercase(Locale.ROOT) }
|
||||||
.toMutableSet()
|
.toMutableSet()
|
||||||
|
|
||||||
// add tag(s) to genre
|
|
||||||
val mangaTitle = try {
|
|
||||||
manga.title
|
|
||||||
} catch (_: UninitializedPropertyAccessException) {
|
|
||||||
"not initialized"
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mangaDetailsSelectorTag.isNotEmpty()) {
|
if (mangaDetailsSelectorTag.isNotEmpty()) {
|
||||||
select(mangaDetailsSelectorTag).forEach { element ->
|
select(mangaDetailsSelectorTag).forEach { element ->
|
||||||
if (genres.contains(element.text()).not() &&
|
if (genres.contains(element.text()).not() &&
|
||||||
|
@ -547,7 +682,7 @@ abstract class Madara(
|
||||||
element.text().contains("read", true).not() &&
|
element.text().contains("read", true).not() &&
|
||||||
element.text().contains(name, true).not() &&
|
element.text().contains(name, true).not() &&
|
||||||
element.text().contains(name.replace(" ", ""), 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()
|
element.text().contains(altName, true).not()
|
||||||
) {
|
) {
|
||||||
genres.add(element.text().lowercase(Locale.ROOT))
|
genres.add(element.text().lowercase(Locale.ROOT))
|
||||||
|
@ -556,13 +691,13 @@ abstract class Madara(
|
||||||
}
|
}
|
||||||
|
|
||||||
// add manga/manhwa/manhua thinggy to genre
|
// 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()) {
|
if (it.isEmpty().not() && it.notUpdating() && it != "-" && genres.contains(it).not()) {
|
||||||
genres.add(it.lowercase(Locale.ROOT))
|
genres.add(it.lowercase(Locale.ROOT))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
manga.genre = genres.toList().joinToString(", ") { genre ->
|
manga.genre = genres.toList().joinToString { genre ->
|
||||||
genre.replaceFirstChar {
|
genre.replaceFirstChar {
|
||||||
if (it.isLowerCase()) {
|
if (it.isLowerCase()) {
|
||||||
it.titlecase(
|
it.titlecase(
|
||||||
|
@ -575,11 +710,11 @@ abstract class Madara(
|
||||||
}
|
}
|
||||||
|
|
||||||
// add alternative name to manga description
|
// add alternative name to manga description
|
||||||
document.select(altNameSelector).firstOrNull()?.ownText()?.let {
|
document.selectFirst(altNameSelector)?.ownText()?.let {
|
||||||
if (it.isBlank().not() && it.notUpdating()) {
|
if (it.isBlank().not() && it.notUpdating()) {
|
||||||
manga.description = when {
|
manga.description = when {
|
||||||
manga.description.isNullOrBlank() -> altName + it
|
manga.description.isNullOrBlank() -> "$altName $it"
|
||||||
else -> manga.description + "\n\n$altName" + it
|
else -> "${manga.description}\n\n$altName $it"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -589,7 +724,7 @@ abstract class Madara(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Manga Details Selector
|
// Manga Details Selector
|
||||||
open val mangaDetailsSelectorTitle = "div.post-title h3, div.post-title h1"
|
open val mangaDetailsSelectorTitle = "div.post-title h3, div.post-title h1, #manga-title > h1"
|
||||||
open val mangaDetailsSelectorAuthor = "div.author-content > a"
|
open val mangaDetailsSelectorAuthor = "div.author-content > a"
|
||||||
open val mangaDetailsSelectorArtist = "div.artist-content > a"
|
open val mangaDetailsSelectorArtist = "div.artist-content > a"
|
||||||
open val mangaDetailsSelectorStatus = "div.summary-content"
|
open val mangaDetailsSelectorStatus = "div.summary-content"
|
||||||
|
@ -600,17 +735,14 @@ abstract class Madara(
|
||||||
|
|
||||||
open val seriesTypeSelector = ".post-content_item:contains(Type) .summary-content"
|
open val seriesTypeSelector = ".post-content_item:contains(Type) .summary-content"
|
||||||
open val altNameSelector = ".post-content_item:contains(Alt) .summary-content"
|
open val altNameSelector = ".post-content_item:contains(Alt) .summary-content"
|
||||||
open val altName = when (lang) {
|
open val altName = intl["alt_names_heading"]
|
||||||
"pt-BR" -> "Nomes alternativos: "
|
|
||||||
else -> "Alternative Names: "
|
|
||||||
}
|
|
||||||
open val updatingRegex = "Updating|Atualizando".toRegex(RegexOption.IGNORE_CASE)
|
open val updatingRegex = "Updating|Atualizando".toRegex(RegexOption.IGNORE_CASE)
|
||||||
|
|
||||||
fun String.notUpdating(): Boolean {
|
fun String.notUpdating(): Boolean {
|
||||||
return this.contains(updatingRegex).not()
|
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() }
|
return this.lowercase() in array.map { it.lowercase() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -644,25 +776,18 @@ abstract class Madara(
|
||||||
.add("manga", mangaId)
|
.add("manga", mangaId)
|
||||||
.build()
|
.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)
|
return POST("$baseUrl/wp-admin/admin-ajax.php", xhrHeaders, form)
|
||||||
}
|
}
|
||||||
|
|
||||||
protected open fun xhrChaptersRequest(mangaUrl: String): Request {
|
protected open fun xhrChaptersRequest(mangaUrl: String): Request {
|
||||||
val xhrHeaders = headersBuilder()
|
|
||||||
.add("X-Requested-With", "XMLHttpRequest")
|
|
||||||
.build()
|
|
||||||
|
|
||||||
return POST("$mangaUrl/ajax/chapters", xhrHeaders)
|
return POST("$mangaUrl/ajax/chapters", xhrHeaders)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun chapterListParse(response: Response): List<SChapter> {
|
override fun chapterListParse(response: Response): List<SChapter> {
|
||||||
val document = response.asJsoup()
|
val document = response.asJsoup()
|
||||||
|
|
||||||
|
launchIO { countViews(document) }
|
||||||
|
|
||||||
val chaptersWrapper = document.select("div[id^=manga-chapters-holder]")
|
val chaptersWrapper = document.select("div[id^=manga-chapters-holder]")
|
||||||
|
|
||||||
var chapterElements = document.select(chapterListSelector())
|
var chapterElements = document.select(chapterListSelector())
|
||||||
|
@ -692,8 +817,6 @@ abstract class Madara(
|
||||||
xhrResponse.close()
|
xhrResponse.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
countViews(document)
|
|
||||||
|
|
||||||
return chapterElements.map(::chapterFromElement)
|
return chapterElements.map(::chapterFromElement)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -710,7 +833,7 @@ abstract class Madara(
|
||||||
val chapter = SChapter.create()
|
val chapter = SChapter.create()
|
||||||
|
|
||||||
with(element) {
|
with(element) {
|
||||||
select(chapterUrlSelector).first()?.let { urlElement ->
|
selectFirst(chapterUrlSelector)!!.let { urlElement ->
|
||||||
chapter.url = urlElement.attr("abs:href").let {
|
chapter.url = urlElement.attr("abs:href").let {
|
||||||
it.substringBefore("?style=paged") + if (!it.endsWith(chapterUrlSuffix)) chapterUrlSuffix else ""
|
it.substringBefore("?style=paged") + if (!it.endsWith(chapterUrlSuffix)) chapterUrlSuffix else ""
|
||||||
}
|
}
|
||||||
|
@ -718,9 +841,9 @@ abstract class Madara(
|
||||||
}
|
}
|
||||||
// Dates can be part of a "new" graphic or plain text
|
// Dates can be part of a "new" graphic or plain text
|
||||||
// Added "title" alternative
|
// Added "title" alternative
|
||||||
chapter.date_upload = select("img:not(.thumb)").firstOrNull()?.attr("alt")?.let { parseRelativeDate(it) }
|
chapter.date_upload = selectFirst("img:not(.thumb)")?.attr("alt")?.let { parseRelativeDate(it) }
|
||||||
?: select("span a").firstOrNull()?.attr("title")?.let { parseRelativeDate(it) }
|
?: selectFirst("span a")?.attr("title")?.let { parseRelativeDate(it) }
|
||||||
?: parseChapterDate(select(chapterDateSelector()).firstOrNull()?.text())
|
?: parseChapterDate(selectFirst(chapterDateSelector())?.text())
|
||||||
}
|
}
|
||||||
|
|
||||||
return chapter
|
return chapter
|
||||||
|
@ -816,7 +939,7 @@ abstract class Madara(
|
||||||
open val chapterProtectorSelector = "#chapter-protector-data"
|
open val chapterProtectorSelector = "#chapter-protector-data"
|
||||||
|
|
||||||
override fun pageListParse(document: Document): List<Page> {
|
override fun pageListParse(document: Document): List<Page> {
|
||||||
countViews(document)
|
launchIO { countViews(document) }
|
||||||
|
|
||||||
val chapterProtector = document.selectFirst(chapterProtectorSelector)
|
val chapterProtector = document.selectFirst(chapterProtectorSelector)
|
||||||
?: return document.select(pageListParseSelector).mapIndexed { index, element ->
|
?: return document.select(pageListParseSelector).mapIndexed { index, element ->
|
||||||
|
@ -836,7 +959,7 @@ abstract class Madara(
|
||||||
|
|
||||||
val unsaltedCiphertext = Base64.decode(chapterData["ct"]!!.jsonPrimitive.content, Base64.DEFAULT)
|
val unsaltedCiphertext = Base64.decode(chapterData["ct"]!!.jsonPrimitive.content, Base64.DEFAULT)
|
||||||
val salt = chapterData["s"]!!.jsonPrimitive.content.decodeHex()
|
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 rawImgArray = CryptoAES.decrypt(Base64.encodeToString(ciphertext, Base64.DEFAULT), password)
|
||||||
val imgArrayString = json.parseToJsonElement(rawImgArray).jsonPrimitive.content
|
val imgArrayString = json.parseToJsonElement(rawImgArray).jsonPrimitive.content
|
||||||
|
@ -860,15 +983,14 @@ abstract class Madara(
|
||||||
protected open val sendViewCount: Boolean = true
|
protected open val sendViewCount: Boolean = true
|
||||||
|
|
||||||
protected open fun countViewsRequest(document: Document): Request? {
|
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
|
?.data() ?: return null
|
||||||
|
|
||||||
val wpMangaInfo = wpMangaData
|
val wpMangaInfo = wpMangaData
|
||||||
.substringAfter("var manga = ")
|
.substringAfter("var manga = ")
|
||||||
.substringBeforeLast(";")
|
.substringBeforeLast(";")
|
||||||
|
|
||||||
val wpManga = runCatching { json.parseToJsonElement(wpMangaInfo).jsonObject }
|
val wpManga = json.parseToJsonElement(wpMangaInfo).jsonObject
|
||||||
.getOrNull() ?: return null
|
|
||||||
|
|
||||||
if (wpManga["enable_manga_view"]?.jsonPrimitive?.content == "1") {
|
if (wpManga["enable_manga_view"]?.jsonPrimitive?.content == "1") {
|
||||||
val formBuilder = FormBody.Builder()
|
val formBuilder = FormBody.Builder()
|
||||||
|
@ -882,14 +1004,10 @@ abstract class Madara(
|
||||||
val formBody = formBuilder.build()
|
val formBody = formBuilder.build()
|
||||||
|
|
||||||
val newHeaders = headersBuilder()
|
val newHeaders = headersBuilder()
|
||||||
.set("Content-Length", formBody.contentLength().toString())
|
|
||||||
.set("Content-Type", formBody.contentType().toString())
|
|
||||||
.set("Referer", document.location())
|
.set("Referer", document.location())
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
val ajaxUrl = wpManga["ajax_url"]!!.jsonPrimitive.content
|
return POST("$baseUrl/wp-admin/admin-ajax.php", newHeaders, formBody)
|
||||||
|
|
||||||
return POST(ajaxUrl, newHeaders, formBody)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return null
|
return null
|
||||||
|
@ -900,28 +1018,29 @@ abstract class Madara(
|
||||||
*
|
*
|
||||||
* @param document The response document with the wp-manga data
|
* @param document The response document with the wp-manga data
|
||||||
*/
|
*/
|
||||||
protected open fun countViews(document: Document) {
|
protected fun countViews(document: Document) {
|
||||||
if (!sendViewCount) {
|
if (!sendViewCount) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val request = countViewsRequest(document) ?: return
|
try {
|
||||||
runCatching { client.newCall(request).execute().close() }
|
val request = countViewsRequest(document) ?: return
|
||||||
|
client.newCall(request).execute().close()
|
||||||
|
} catch (_: Exception) { }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch the genres from the source to be used in the filters.
|
* Fetch the genres from the source to be used in the filters.
|
||||||
*/
|
*/
|
||||||
protected open fun fetchGenres() {
|
protected fun fetchGenres() {
|
||||||
if (fetchGenres && fetchGenresAttempts <= 3 && (genresList.isEmpty() || fetchGenresFailed)) {
|
if (fetchGenres && fetchGenresAttempts < 3 && genresList.isEmpty()) {
|
||||||
val genres = runCatching {
|
try {
|
||||||
client.newCall(genresRequest()).execute()
|
genresList = client.newCall(genresRequest()).execute()
|
||||||
.use { parseGenres(it.asJsoup()) }
|
.use { parseGenres(it.asJsoup()) }
|
||||||
|
} catch (_: Exception) {
|
||||||
|
} finally {
|
||||||
|
fetchGenresAttempts++
|
||||||
}
|
}
|
||||||
|
|
||||||
fetchGenresFailed = genres.isFailure
|
|
||||||
genresList = genres.getOrNull().orEmpty()
|
|
||||||
fetchGenresAttempts++
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -950,7 +1069,7 @@ abstract class Madara(
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://stackoverflow.com/a/66614516
|
// https://stackoverflow.com/a/66614516
|
||||||
private fun String.decodeHex(): ByteArray {
|
protected fun String.decodeHex(): ByteArray {
|
||||||
check(length % 2 == 0) { "Must have an even length" }
|
check(length % 2 == 0) { "Must have an even length" }
|
||||||
|
|
||||||
return chunked(2)
|
return chunked(2)
|
||||||
|
@ -958,13 +1077,14 @@ abstract class Madara(
|
||||||
.toByteArray()
|
.toByteArray()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setupPreferenceScreen(screen: PreferenceScreen) {
|
protected val salted = "Salted__".toByteArray(Charsets.UTF_8)
|
||||||
addRandomUAPreferenceToScreen(screen)
|
|
||||||
}
|
private val scope = CoroutineScope(Dispatchers.IO)
|
||||||
|
|
||||||
|
protected fun launchIO(block: () -> Unit) = scope.launch { block() }
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val URL_SEARCH_PREFIX = "slug:"
|
const val URL_SEARCH_PREFIX = "slug:"
|
||||||
val SALTED = "Salted__".toByteArray(Charsets.UTF_8)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
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
|
|
@ -0,0 +1,12 @@
|
||||||
|
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
|
|
@ -0,0 +1,9 @@
|
||||||
|
plugins {
|
||||||
|
id("lib-multisrc")
|
||||||
|
}
|
||||||
|
|
||||||
|
baseVersionCode = 1
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
api(project(":lib:i18n"))
|
||||||
|
}
|
|
@ -0,0 +1,275 @@
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
}
|
|
@ -147,6 +147,9 @@ abstract class ZeistManga(
|
||||||
protected open val mangaDetailsSelector = ".grid.gtc-235fr"
|
protected open val mangaDetailsSelector = ".grid.gtc-235fr"
|
||||||
protected open val mangaDetailsSelectorDescription = "#synopsis"
|
protected open val mangaDetailsSelectorDescription = "#synopsis"
|
||||||
protected open val mangaDetailsSelectorGenres = "div.mt-15 > a[rel=tag]"
|
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 mangaDetailsSelectorInfo = ".y6x11p"
|
||||||
protected open val mangaDetailsSelectorInfoTitle = "strong"
|
protected open val mangaDetailsSelectorInfoTitle = "strong"
|
||||||
protected open val mangaDetailsSelectorInfoDescription = "span.dt"
|
protected open val mangaDetailsSelectorInfoDescription = "span.dt"
|
||||||
|
@ -156,9 +159,18 @@ abstract class ZeistManga(
|
||||||
val profileManga = document.selectFirst(mangaDetailsSelector)!!
|
val profileManga = document.selectFirst(mangaDetailsSelector)!!
|
||||||
return SManga.create().apply {
|
return SManga.create().apply {
|
||||||
thumbnail_url = profileManga.selectFirst("img")!!.attr("abs:src")
|
thumbnail_url = profileManga.selectFirst("img")!!.attr("abs:src")
|
||||||
description = profileManga.select(mangaDetailsSelectorDescription).text()
|
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()
|
||||||
genre = profileManga.select(mangaDetailsSelectorGenres)
|
genre = profileManga.select(mangaDetailsSelectorGenres)
|
||||||
.joinToString { it.text() }
|
.joinToString { it.text() }
|
||||||
|
author = profileManga.selectFirst(mangaDetailsSelectorAuthor)?.text()
|
||||||
|
artist = profileManga.selectFirst(mangaDetailsSelectorArtist)?.text()
|
||||||
|
|
||||||
val infoElement = profileManga.select(mangaDetailsSelectorInfo)
|
val infoElement = profileManga.select(mangaDetailsSelectorInfo)
|
||||||
infoElement.forEach { element ->
|
infoElement.forEach { element ->
|
||||||
|
@ -202,7 +214,7 @@ abstract class ZeistManga(
|
||||||
protected open val useNewChapterFeed = false
|
protected open val useNewChapterFeed = false
|
||||||
protected open val useOldChapterFeed = false
|
protected open val useOldChapterFeed = false
|
||||||
|
|
||||||
private val chapterFeedRegex = """clwd\.run\('([^']+)'""".toRegex()
|
private val chapterFeedRegex = """clwd\.run\(["'](.*?)["']\)""".toRegex()
|
||||||
private val scriptSelector = "#clwd > script"
|
private val scriptSelector = "#clwd > script"
|
||||||
|
|
||||||
open fun getChapterFeedUrl(doc: Document): String {
|
open fun getChapterFeedUrl(doc: Document): String {
|
||||||
|
|
|
@ -1,23 +1,3 @@
|
||||||
plugins {
|
plugins {
|
||||||
id("com.android.library")
|
id("lib-android")
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,22 +1,3 @@
|
||||||
plugins {
|
plugins {
|
||||||
id("com.android.library")
|
id("lib-android")
|
||||||
kotlin("android")
|
|
||||||
}
|
|
||||||
|
|
||||||
android {
|
|
||||||
compileSdk = AndroidConfig.compileSdk
|
|
||||||
|
|
||||||
defaultConfig {
|
|
||||||
minSdk = AndroidConfig.minSdk
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace = "eu.kanade.tachiyomi.lib.cryptoaes"
|
|
||||||
}
|
|
||||||
|
|
||||||
repositories {
|
|
||||||
mavenCentral()
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
compileOnly(libs.kotlin.stdlib)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,24 +1,3 @@
|
||||||
plugins {
|
plugins {
|
||||||
id("com.android.library")
|
id("lib-android")
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,22 +1,3 @@
|
||||||
plugins {
|
plugins {
|
||||||
id("com.android.library")
|
id("lib-android")
|
||||||
kotlin("android")
|
|
||||||
}
|
|
||||||
|
|
||||||
android {
|
|
||||||
compileSdk = AndroidConfig.compileSdk
|
|
||||||
|
|
||||||
defaultConfig {
|
|
||||||
minSdk = AndroidConfig.minSdk
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace = "eu.kanade.tachiyomi.lib.i18n"
|
|
||||||
}
|
|
||||||
|
|
||||||
repositories {
|
|
||||||
mavenCentral()
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
compileOnly(libs.kotlin.stdlib)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,3 @@
|
||||||
plugins {
|
plugins {
|
||||||
`java-library`
|
id("lib-android")
|
||||||
kotlin("jvm")
|
|
||||||
}
|
|
||||||
|
|
||||||
repositories {
|
|
||||||
mavenCentral()
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
compileOnly(libs.kotlin.stdlib)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,23 +1,3 @@
|
||||||
plugins {
|
plugins {
|
||||||
id("com.android.library")
|
id("lib-android")
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,24 +1,7 @@
|
||||||
plugins {
|
plugins {
|
||||||
id("com.android.library")
|
id("lib-android")
|
||||||
kotlin("android")
|
|
||||||
id("kotlinx-serialization")
|
|
||||||
}
|
|
||||||
|
|
||||||
android {
|
|
||||||
compileSdk = AndroidConfig.compileSdk
|
|
||||||
|
|
||||||
defaultConfig {
|
|
||||||
minSdk = AndroidConfig.minSdk
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace = "eu.kanade.tachiyomi.lib.speedbinb"
|
|
||||||
}
|
|
||||||
|
|
||||||
repositories {
|
|
||||||
mavenCentral()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compileOnly(libs.bundles.common)
|
|
||||||
implementation(project(":lib:textinterceptor"))
|
implementation(project(":lib:textinterceptor"))
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,22 +1,3 @@
|
||||||
plugins {
|
plugins {
|
||||||
id("com.android.library")
|
id("lib-android")
|
||||||
kotlin("android")
|
|
||||||
}
|
|
||||||
|
|
||||||
android {
|
|
||||||
compileSdk = AndroidConfig.compileSdk
|
|
||||||
|
|
||||||
defaultConfig {
|
|
||||||
minSdk = AndroidConfig.minSdk
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace = "eu.kanade.tachiyomi.lib.synchrony"
|
|
||||||
}
|
|
||||||
|
|
||||||
repositories {
|
|
||||||
mavenCentral()
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
compileOnly(libs.bundles.common)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,23 +1,3 @@
|
||||||
plugins {
|
plugins {
|
||||||
id("com.android.library")
|
id("lib-android")
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,3 @@
|
||||||
plugins {
|
plugins {
|
||||||
`java-library`
|
id("lib-android")
|
||||||
kotlin("jvm")
|
|
||||||
}
|
|
||||||
|
|
||||||
repositories {
|
|
||||||
mavenCentral()
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
compileOnly(libs.kotlin.stdlib)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,4 @@ import eu.kanade.tachiyomi.multisrc.madara.Madara
|
||||||
|
|
||||||
class ComiczNetV2 : Madara("Comicz.net v2", "https://v2.comiz.net", "all") {
|
class ComiczNetV2 : Madara("Comicz.net v2", "https://v2.comiz.net", "all") {
|
||||||
override val useNewChapterEndpoint = false
|
override val useNewChapterEndpoint = false
|
||||||
|
|
||||||
override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/"
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,14 +14,6 @@ class GrabberZone : Madara(
|
||||||
) {
|
) {
|
||||||
override val mangaSubString = "comics"
|
override val mangaSubString = "comics"
|
||||||
|
|
||||||
override fun searchPage(page: Int): String {
|
|
||||||
return if (page > 1) {
|
|
||||||
"page/$page/"
|
|
||||||
} else {
|
|
||||||
""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun chapterFromElement(element: Element): SChapter {
|
override fun chapterFromElement(element: Element): SChapter {
|
||||||
return super.chapterFromElement(element).apply {
|
return super.chapterFromElement(element).apply {
|
||||||
name = element.selectFirst("a + a")!!.text()
|
name = element.selectFirst("a + a")!!.text()
|
||||||
|
|
|
@ -4,6 +4,4 @@ import eu.kanade.tachiyomi.multisrc.madara.Madara
|
||||||
|
|
||||||
class MangaCrazy : Madara("MangaCrazy", "https://mangacrazy.net", "all") {
|
class MangaCrazy : Madara("MangaCrazy", "https://mangacrazy.net", "all") {
|
||||||
override val useNewChapterEndpoint = true
|
override val useNewChapterEndpoint = true
|
||||||
|
|
||||||
override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/"
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,4 @@ class MangaTopSite : Madara(
|
||||||
) {
|
) {
|
||||||
override val useNewChapterEndpoint = false
|
override val useNewChapterEndpoint = false
|
||||||
override val chapterUrlSuffix = ""
|
override val chapterUrlSuffix = ""
|
||||||
|
|
||||||
override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/"
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,8 +2,8 @@ ext {
|
||||||
extName = 'Miau Scan'
|
extName = 'Miau Scan'
|
||||||
extClass = '.MiauScanFactory'
|
extClass = '.MiauScanFactory'
|
||||||
themePkg = 'mangathemesia'
|
themePkg = 'mangathemesia'
|
||||||
baseUrl = 'https://miaucomics.org'
|
baseUrl = 'https://miauvisor.org'
|
||||||
overrideVersionCode = 2
|
overrideVersionCode = 3
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: "$rootDir/common.gradle"
|
apply from: "$rootDir/common.gradle"
|
||||||
|
|
|
@ -20,7 +20,7 @@ class MiauScanFactory : SourceFactory {
|
||||||
|
|
||||||
open class MiauScan(lang: String) : MangaThemesia(
|
open class MiauScan(lang: String) : MangaThemesia(
|
||||||
name = "Miau Scan",
|
name = "Miau Scan",
|
||||||
baseUrl = "https://miaucomics.org",
|
baseUrl = "https://miauvisor.org",
|
||||||
lang = lang,
|
lang = lang,
|
||||||
dateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale("es")),
|
dateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale("es")),
|
||||||
) {
|
) {
|
||||||
|
@ -58,6 +58,9 @@ 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 {
|
override fun mangaDetailsParse(document: Document): SManga {
|
||||||
return super.mangaDetailsParse(document).apply {
|
return super.mangaDetailsParse(document).apply {
|
||||||
title = title.replace(PORTUGUESE_SUFFIX, "")
|
title = title.replace(PORTUGUESE_SUFFIX, "")
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
ext {
|
ext {
|
||||||
extName = 'NHentai'
|
extName = 'NHentai'
|
||||||
extClass = '.NHFactory'
|
extClass = '.NHFactory'
|
||||||
extVersionCode = 39
|
extVersionCode = 40
|
||||||
isNsfw = true
|
isNsfw = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -302,6 +302,7 @@ open class NHentai(
|
||||||
"Sort By",
|
"Sort By",
|
||||||
arrayOf(
|
arrayOf(
|
||||||
Pair("Popular: All Time", "popular"),
|
Pair("Popular: All Time", "popular"),
|
||||||
|
Pair("Popular: Month", "popular-month"),
|
||||||
Pair("Popular: Week", "popular-week"),
|
Pair("Popular: Week", "popular-week"),
|
||||||
Pair("Popular: Today", "popular-today"),
|
Pair("Popular: Today", "popular-today"),
|
||||||
Pair("Recent", "date"),
|
Pair("Recent", "date"),
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
ext {
|
ext {
|
||||||
extName = 'Photos18'
|
extName = 'Photos18'
|
||||||
extClass = '.Photos18'
|
extClass = '.Photos18'
|
||||||
extVersionCode = 2
|
extVersionCode = 3
|
||||||
isNsfw = true
|
isNsfw = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -51,7 +51,7 @@ class Photos18 : HttpSource(), ConfigurableSource {
|
||||||
SManga.create().apply {
|
SManga.create().apply {
|
||||||
url = link.attr("href").stripLang()
|
url = link.attr("href").stripLang()
|
||||||
title = link.ownText()
|
title = link.ownText()
|
||||||
thumbnail_url = baseUrl + it.selectFirst(Evaluator.Tag("img"))!!.attr("data-src")
|
thumbnail_url = baseUrl + it.selectFirst(Evaluator.Tag("img"))!!.attr("src")
|
||||||
genre = cardBody.selectFirst(Evaluator.Tag("label"))!!.ownText()
|
genre = cardBody.selectFirst(Evaluator.Tag("label"))!!.ownText()
|
||||||
status = SManga.COMPLETED
|
status = SManga.COMPLETED
|
||||||
initialized = true
|
initialized = true
|
||||||
|
@ -100,7 +100,7 @@ class Photos18 : HttpSource(), ConfigurableSource {
|
||||||
val document = response.asJsoup()
|
val document = response.asJsoup()
|
||||||
val images = document.selectFirst(Evaluator.Id("content"))!!.select(Evaluator.Tag("img"))
|
val images = document.selectFirst(Evaluator.Id("content"))!!.select(Evaluator.Tag("img"))
|
||||||
return images.mapIndexed { index, image ->
|
return images.mapIndexed { index, image ->
|
||||||
Page(index, imageUrl = image.attr("data-src"))
|
Page(index, imageUrl = image.attr("src"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,8 +2,8 @@ ext {
|
||||||
extName = 'ARESNOV'
|
extName = 'ARESNOV'
|
||||||
extClass = '.ARESNOV'
|
extClass = '.ARESNOV'
|
||||||
themePkg = 'mangathemesia'
|
themePkg = 'mangathemesia'
|
||||||
baseUrl = 'https://aresnov.org'
|
baseUrl = 'https://manhuascarlet.com'
|
||||||
overrideVersionCode = 0
|
overrideVersionCode = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: "$rootDir/common.gradle"
|
apply from: "$rootDir/common.gradle"
|
||||||
|
|
|
@ -12,7 +12,7 @@ import java.util.Locale
|
||||||
|
|
||||||
class ARESNOV : MangaThemesia(
|
class ARESNOV : MangaThemesia(
|
||||||
"ARESNOV",
|
"ARESNOV",
|
||||||
"https://aresnov.org",
|
"https://manhuascarlet.com",
|
||||||
"ar",
|
"ar",
|
||||||
mangaUrlDirectory = "/series",
|
mangaUrlDirectory = "/series",
|
||||||
dateFormat = SimpleDateFormat("MMMMM dd, yyyy", Locale("ar")),
|
dateFormat = SimpleDateFormat("MMMMM dd, yyyy", Locale("ar")),
|
||||||
|
|
|
@ -1,16 +1,12 @@
|
||||||
package eu.kanade.tachiyomi.extension.ar.azora
|
package eu.kanade.tachiyomi.extension.ar.azora
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.multisrc.madara.Madara
|
import eu.kanade.tachiyomi.multisrc.madara.Madara
|
||||||
import eu.kanade.tachiyomi.network.GET
|
|
||||||
import eu.kanade.tachiyomi.source.model.SChapter
|
import eu.kanade.tachiyomi.source.model.SChapter
|
||||||
import okhttp3.Request
|
|
||||||
import org.jsoup.nodes.Element
|
import org.jsoup.nodes.Element
|
||||||
|
|
||||||
class Azora : Madara("Azora", "https://azoramoon.com", "ar") {
|
class Azora : Madara("Azora", "https://azoramoon.com", "ar") {
|
||||||
override val mangaSubString = "series"
|
override val mangaSubString = "series"
|
||||||
override val useNewChapterEndpoint = false
|
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 chapterListSelector() = "li.wp-manga-chapter:not(.premium-block)" // Filter fake chapters
|
||||||
override fun chapterFromElement(element: Element): SChapter {
|
override fun chapterFromElement(element: Element): SChapter {
|
||||||
val chapter = SChapter.create()
|
val chapter = SChapter.create()
|
||||||
|
|
|
@ -9,12 +9,4 @@ class ComicArab : Madara(
|
||||||
"https://comicarab.com",
|
"https://comicarab.com",
|
||||||
"ar",
|
"ar",
|
||||||
dateFormat = SimpleDateFormat("dd MMMM، yyyy", Locale("ar")),
|
dateFormat = SimpleDateFormat("dd MMMM، yyyy", Locale("ar")),
|
||||||
) {
|
)
|
||||||
override fun searchPage(page: Int): String {
|
|
||||||
return if (page > 1) {
|
|
||||||
"page/$page/"
|
|
||||||
} else {
|
|
||||||
""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -6,17 +6,20 @@ import android.widget.Toast
|
||||||
import androidx.preference.PreferenceScreen
|
import androidx.preference.PreferenceScreen
|
||||||
import eu.kanade.tachiyomi.extension.BuildConfig
|
import eu.kanade.tachiyomi.extension.BuildConfig
|
||||||
import eu.kanade.tachiyomi.multisrc.madara.Madara
|
import eu.kanade.tachiyomi.multisrc.madara.Madara
|
||||||
|
import eu.kanade.tachiyomi.source.ConfigurableSource
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
class EmpireWebtoon : Madara(
|
class EmpireWebtoon :
|
||||||
"Empire Webtoon",
|
Madara(
|
||||||
"https://webtoonsempireron.com",
|
"Empire Webtoon",
|
||||||
"ar",
|
"https://webtoonsempireron.com",
|
||||||
SimpleDateFormat("d MMMM، yyyy", Locale("ar")),
|
"ar",
|
||||||
) {
|
SimpleDateFormat("d MMMM، yyyy", Locale("ar")),
|
||||||
|
),
|
||||||
|
ConfigurableSource {
|
||||||
|
|
||||||
private val defaultBaseUrl = "https://webtoonsempireron.com"
|
private val defaultBaseUrl = "https://webtoonsempireron.com"
|
||||||
|
|
||||||
|
@ -30,8 +33,6 @@ class EmpireWebtoon : Madara(
|
||||||
|
|
||||||
override val useNewChapterEndpoint = false
|
override val useNewChapterEndpoint = false
|
||||||
|
|
||||||
override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/"
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val RESTART_TACHIYOMI = "Restart Tachiyomi to apply new setting."
|
private const val RESTART_TACHIYOMI = "Restart Tachiyomi to apply new setting."
|
||||||
private const val BASE_URL_PREF_TITLE = "Override BaseUrl"
|
private const val BASE_URL_PREF_TITLE = "Override BaseUrl"
|
||||||
|
@ -53,8 +54,6 @@ class EmpireWebtoon : Madara(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
screen.addPreference(baseUrlPref)
|
screen.addPreference(baseUrlPref)
|
||||||
|
|
||||||
super.setupPreferenceScreen(screen)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getPrefBaseUrl(): String = preferences.getString(BASE_URL_PREF, defaultBaseUrl)!!
|
private fun getPrefBaseUrl(): String = preferences.getString(BASE_URL_PREF, defaultBaseUrl)!!
|
||||||
|
|
|
@ -2,8 +2,8 @@ ext {
|
||||||
extName = 'ARESManga'
|
extName = 'ARESManga'
|
||||||
extClass = '.ARESManga'
|
extClass = '.ARESManga'
|
||||||
themePkg = 'mangathemesia'
|
themePkg = 'mangathemesia'
|
||||||
baseUrl = 'https://en-aresmanga.com'
|
baseUrl = 'https://fl-ares.com'
|
||||||
overrideVersionCode = 2
|
overrideVersionCode = 3
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: "$rootDir/common.gradle"
|
apply from: "$rootDir/common.gradle"
|
||||||
|
|
|
@ -12,7 +12,7 @@ import java.util.Locale
|
||||||
|
|
||||||
class ARESManga : MangaThemesia(
|
class ARESManga : MangaThemesia(
|
||||||
"ARESManga",
|
"ARESManga",
|
||||||
"https://en-aresmanga.com",
|
"https://fl-ares.com",
|
||||||
"ar",
|
"ar",
|
||||||
mangaUrlDirectory = "/series",
|
mangaUrlDirectory = "/series",
|
||||||
dateFormat = SimpleDateFormat("MMMMM dd, yyyy", Locale("ar")),
|
dateFormat = SimpleDateFormat("MMMMM dd, yyyy", Locale("ar")),
|
||||||
|
|
|
@ -6,14 +6,13 @@ import android.widget.Toast
|
||||||
import androidx.preference.PreferenceScreen
|
import androidx.preference.PreferenceScreen
|
||||||
import eu.kanade.tachiyomi.extension.BuildConfig
|
import eu.kanade.tachiyomi.extension.BuildConfig
|
||||||
import eu.kanade.tachiyomi.multisrc.madara.Madara
|
import eu.kanade.tachiyomi.multisrc.madara.Madara
|
||||||
import eu.kanade.tachiyomi.network.GET
|
|
||||||
import eu.kanade.tachiyomi.network.POST
|
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.FilterList
|
||||||
import eu.kanade.tachiyomi.source.model.MangasPage
|
import eu.kanade.tachiyomi.source.model.MangasPage
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.decodeFromString
|
import kotlinx.serialization.decodeFromString
|
||||||
import okhttp3.CacheControl
|
|
||||||
import okhttp3.FormBody
|
import okhttp3.FormBody
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
|
@ -22,8 +21,17 @@ import uy.kohesive.injekt.api.get
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Locale
|
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 useLoadMoreRequest = LoadMoreStrategy.Always
|
||||||
override val chapterUrlSuffix = ""
|
override val chapterUrlSuffix = ""
|
||||||
|
|
||||||
private val defaultBaseUrl = "https://manga-lek.net"
|
private val defaultBaseUrl = "https://manga-lek.net"
|
||||||
|
@ -55,22 +63,10 @@ class Mangalek : Madara("مانجا ليك", "https://manga-lek.net", "ar", Simp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
screen.addPreference(baseUrlPref)
|
screen.addPreference(baseUrlPref)
|
||||||
|
|
||||||
super.setupPreferenceScreen(screen)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getPrefBaseUrl(): String = preferences.getString(BASE_URL_PREF, defaultBaseUrl)!!
|
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 =
|
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request =
|
||||||
POST(
|
POST(
|
||||||
"$baseUrl/wp-admin/admin-ajax.php",
|
"$baseUrl/wp-admin/admin-ajax.php",
|
||||||
|
|
|
@ -3,7 +3,7 @@ ext {
|
||||||
extClass = '.Mangalink'
|
extClass = '.Mangalink'
|
||||||
themePkg = 'madara'
|
themePkg = 'madara'
|
||||||
baseUrl = 'https://manga-link.com'
|
baseUrl = 'https://manga-link.com'
|
||||||
overrideVersionCode = 1
|
overrideVersionCode = 2
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: "$rootDir/common.gradle"
|
apply from: "$rootDir/common.gradle"
|
||||||
|
|
|
@ -6,15 +6,23 @@ import android.widget.Toast
|
||||||
import androidx.preference.PreferenceScreen
|
import androidx.preference.PreferenceScreen
|
||||||
import eu.kanade.tachiyomi.extension.BuildConfig
|
import eu.kanade.tachiyomi.extension.BuildConfig
|
||||||
import eu.kanade.tachiyomi.multisrc.madara.Madara
|
import eu.kanade.tachiyomi.multisrc.madara.Madara
|
||||||
|
import eu.kanade.tachiyomi.source.ConfigurableSource
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Locale
|
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 = ""
|
override val chapterUrlSuffix = ""
|
||||||
|
override val useLoadMoreRequest = LoadMoreStrategy.Always
|
||||||
private val defaultBaseUrl = "https://manga-link.com"
|
private val defaultBaseUrl = "https://manga-link.com"
|
||||||
override val baseUrl by lazy { getPrefBaseUrl() }
|
override val baseUrl by lazy { getPrefBaseUrl() }
|
||||||
|
|
||||||
|
@ -44,8 +52,6 @@ class Mangalink : Madara("مانجا لينك", "https://manga-link.com", "ar",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
screen.addPreference(baseUrlPref)
|
screen.addPreference(baseUrlPref)
|
||||||
|
|
||||||
super.setupPreferenceScreen(screen)
|
|
||||||
}
|
}
|
||||||
private fun getPrefBaseUrl(): String = preferences.getString(BASE_URL_PREF, defaultBaseUrl)!!
|
private fun getPrefBaseUrl(): String = preferences.getString(BASE_URL_PREF, defaultBaseUrl)!!
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ ext {
|
||||||
extClass = '.MangaLionz'
|
extClass = '.MangaLionz'
|
||||||
themePkg = 'madara'
|
themePkg = 'madara'
|
||||||
baseUrl = 'https://mangalionz.org'
|
baseUrl = 'https://mangalionz.org'
|
||||||
overrideVersionCode = 2
|
overrideVersionCode = 3
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: "$rootDir/common.gradle"
|
apply from: "$rootDir/common.gradle"
|
||||||
|
|
|
@ -5,16 +5,17 @@ import eu.kanade.tachiyomi.source.model.SManga
|
||||||
import org.jsoup.nodes.Element
|
import org.jsoup.nodes.Element
|
||||||
|
|
||||||
class MangaLionz : Madara("MangaLionz", "https://mangalionz.org", "ar") {
|
class MangaLionz : Madara("MangaLionz", "https://mangalionz.org", "ar") {
|
||||||
|
override val useLoadMoreRequest = LoadMoreStrategy.Always
|
||||||
override fun popularMangaFromElement(element: Element): SManga {
|
override fun popularMangaFromElement(element: Element): SManga {
|
||||||
val manga = SManga.create()
|
val manga = SManga.create()
|
||||||
|
|
||||||
with(element) {
|
with(element) {
|
||||||
select(popularMangaUrlSelector).first()?.let {
|
selectFirst(popularMangaUrlSelector)!!.let {
|
||||||
manga.setUrlWithoutDomain(it.attr("abs:href"))
|
manga.setUrlWithoutDomain(it.attr("abs:href"))
|
||||||
manga.title = it.ownText()
|
manga.title = it.ownText()
|
||||||
}
|
}
|
||||||
|
|
||||||
select("img").first()?.let {
|
selectFirst("img")?.let {
|
||||||
manga.thumbnail_url = imageFromElement(it)?.replace("mangalionz", "mangalek")
|
manga.thumbnail_url = imageFromElement(it)?.replace("mangalionz", "mangalek")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,12 +9,4 @@ class MangaRose : Madara(
|
||||||
"https://mangarose.net",
|
"https://mangarose.net",
|
||||||
"ar",
|
"ar",
|
||||||
dateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale("ar")),
|
dateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale("ar")),
|
||||||
) {
|
)
|
||||||
override fun searchPage(page: Int): String {
|
|
||||||
return if (page > 1) {
|
|
||||||
"page/$page/"
|
|
||||||
} else {
|
|
||||||
""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ ext {
|
||||||
extClass = '.MangaSpark'
|
extClass = '.MangaSpark'
|
||||||
themePkg = 'madara'
|
themePkg = 'madara'
|
||||||
baseUrl = 'https://mangaspark.org'
|
baseUrl = 'https://mangaspark.org'
|
||||||
overrideVersionCode = 4
|
overrideVersionCode = 5
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: "$rootDir/common.gradle"
|
apply from: "$rootDir/common.gradle"
|
||||||
|
|
|
@ -11,8 +11,6 @@ class MangaSpark : Madara(
|
||||||
dateFormat = SimpleDateFormat("d MMMM، yyyy", Locale("ar")),
|
dateFormat = SimpleDateFormat("d MMMM، yyyy", Locale("ar")),
|
||||||
) {
|
) {
|
||||||
override val chapterUrlSuffix = ""
|
override val chapterUrlSuffix = ""
|
||||||
|
override val useLoadMoreRequest = LoadMoreStrategy.Always
|
||||||
override val useNewChapterEndpoint = false
|
override val useNewChapterEndpoint = false
|
||||||
|
|
||||||
override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/"
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ ext {
|
||||||
extClass = '.MangaStarz'
|
extClass = '.MangaStarz'
|
||||||
themePkg = 'madara'
|
themePkg = 'madara'
|
||||||
baseUrl = 'https://mangastarz.org'
|
baseUrl = 'https://mangastarz.org'
|
||||||
overrideVersionCode = 5
|
overrideVersionCode = 6
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: "$rootDir/common.gradle"
|
apply from: "$rootDir/common.gradle"
|
||||||
|
|
|
@ -11,8 +11,6 @@ class MangaStarz : Madara(
|
||||||
dateFormat = SimpleDateFormat("d MMMM، yyyy", Locale("ar")),
|
dateFormat = SimpleDateFormat("d MMMM، yyyy", Locale("ar")),
|
||||||
) {
|
) {
|
||||||
override val chapterUrlSuffix = ""
|
override val chapterUrlSuffix = ""
|
||||||
|
override val useLoadMoreRequest = LoadMoreStrategy.Always
|
||||||
override val useNewChapterEndpoint = false
|
override val useNewChapterEndpoint = false
|
||||||
|
|
||||||
override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/"
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,8 +2,8 @@ ext {
|
||||||
extName = 'MangaSwat'
|
extName = 'MangaSwat'
|
||||||
extClass = '.MangaSwat'
|
extClass = '.MangaSwat'
|
||||||
themePkg = 'mangathemesia'
|
themePkg = 'mangathemesia'
|
||||||
baseUrl = 'https://goldragon.me'
|
baseUrl = 'https://swatmanhua.com'
|
||||||
overrideVersionCode = 15
|
overrideVersionCode = 16
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: "$rootDir/common.gradle"
|
apply from: "$rootDir/common.gradle"
|
||||||
|
|
|
@ -21,13 +21,15 @@ import uy.kohesive.injekt.api.get
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
|
private const val swatUrl = "https://swatmanhua.com"
|
||||||
|
|
||||||
class MangaSwat : MangaThemesia(
|
class MangaSwat : MangaThemesia(
|
||||||
"MangaSwat",
|
"MangaSwat",
|
||||||
"https://goldragon.me",
|
swatUrl,
|
||||||
"ar",
|
"ar",
|
||||||
dateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale("ar")),
|
dateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale("ar")),
|
||||||
) {
|
) {
|
||||||
private val defaultBaseUrl = "https://goldragon.me"
|
private val defaultBaseUrl = swatUrl
|
||||||
|
|
||||||
override val baseUrl by lazy { getPrefBaseUrl() }
|
override val baseUrl by lazy { getPrefBaseUrl() }
|
||||||
|
|
||||||
|
|
|
@ -2,8 +2,8 @@ ext {
|
||||||
extName = 'PotatoManga'
|
extName = 'PotatoManga'
|
||||||
extClass = '.PotatoManga'
|
extClass = '.PotatoManga'
|
||||||
themePkg = 'mangathemesia'
|
themePkg = 'mangathemesia'
|
||||||
baseUrl = 'https://potatomanga.xyz'
|
baseUrl = 'https://ar.potatomanga.xyz'
|
||||||
overrideVersionCode = 1
|
overrideVersionCode = 2
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: "$rootDir/common.gradle"
|
apply from: "$rootDir/common.gradle"
|
||||||
|
|
|
@ -8,9 +8,9 @@ import java.util.Locale
|
||||||
|
|
||||||
class PotatoManga : MangaThemesia(
|
class PotatoManga : MangaThemesia(
|
||||||
"PotatoManga",
|
"PotatoManga",
|
||||||
"https://potatomanga.xyz",
|
"https://ar.potatomanga.xyz",
|
||||||
"ar",
|
"ar",
|
||||||
mangaUrlDirectory = "/series",
|
mangaUrlDirectory = "/manga",
|
||||||
dateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale("ar")),
|
dateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale("ar")),
|
||||||
) {
|
) {
|
||||||
override val seriesArtistSelector =
|
override val seriesArtistSelector =
|
||||||
|
|
|
@ -1,11 +1,5 @@
|
||||||
package eu.kanade.tachiyomi.extension.en.allporncomic
|
package eu.kanade.tachiyomi.extension.en.allporncomic
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.multisrc.madara.Madara
|
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]"
|
|
||||||
}
|
|
||||||
|
|
|
@ -8,8 +8,6 @@ import kotlin.random.Random
|
||||||
class AquaManga : Madara("Aqua Manga", "https://aquamanga.org", "en") {
|
class AquaManga : Madara("Aqua Manga", "https://aquamanga.org", "en") {
|
||||||
override val useNewChapterEndpoint = false
|
override val useNewChapterEndpoint = false
|
||||||
|
|
||||||
override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/"
|
|
||||||
|
|
||||||
override fun headersBuilder(): Headers.Builder = super.headersBuilder()
|
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", "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")
|
.add("Accept-Language", "en-US,en;q=0.5")
|
||||||
|
|
|
@ -4,6 +4,4 @@ import eu.kanade.tachiyomi.multisrc.madara.Madara
|
||||||
|
|
||||||
class AsuraScansUs : Madara("Asura Scans.us (unoriginal)", "https://asurascans.us", "en") {
|
class AsuraScansUs : Madara("Asura Scans.us (unoriginal)", "https://asurascans.us", "en") {
|
||||||
override val useNewChapterEndpoint = true
|
override val useNewChapterEndpoint = true
|
||||||
|
|
||||||
override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/"
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,37 +1,11 @@
|
||||||
package eu.kanade.tachiyomi.extension.en.babelwuxia
|
package eu.kanade.tachiyomi.extension.en.babelwuxia
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.multisrc.madara.Madara
|
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") {
|
class BabelWuxia : Madara("Babel Wuxia", "https://babelwuxia.com", "en") {
|
||||||
|
|
||||||
// moved from MangaThemesia
|
// moved from MangaThemesia
|
||||||
override val versionId = 2
|
override val versionId = 2
|
||||||
override val useNewChapterEndpoint = true
|
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,4 @@ import eu.kanade.tachiyomi.multisrc.madara.Madara
|
||||||
|
|
||||||
class BananaManga : Madara("Banana Manga", "https://bananamanga.net", "en") {
|
class BananaManga : Madara("Banana Manga", "https://bananamanga.net", "en") {
|
||||||
override val useNewChapterEndpoint = true
|
override val useNewChapterEndpoint = true
|
||||||
|
|
||||||
override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/"
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
ext {
|
||||||
|
extName = 'Blackout Scans'
|
||||||
|
extClass = '.BlackoutScans'
|
||||||
|
themePkg = 'keyoapp'
|
||||||
|
baseUrl = 'https://blackoutscans.com'
|
||||||
|
overrideVersionCode = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
apply from: "$rootDir/common.gradle"
|
After Width: | Height: | Size: 7.4 KiB |
After Width: | Height: | Size: 3.6 KiB |
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 24 KiB |
After Width: | Height: | Size: 38 KiB |
|
@ -0,0 +1,5 @@
|
||||||
|
package eu.kanade.tachiyomi.extension.en.blackoutscans
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.multisrc.keyoapp.Keyoapp
|
||||||
|
|
||||||
|
class BlackoutScans : Keyoapp("Blackout Scans", "https://blackoutscans.com", "en")
|
|
@ -6,8 +6,6 @@ import org.jsoup.nodes.Element
|
||||||
class CoffeeManga : Madara("Coffee Manga", "https://coffeemanga.io", "en") {
|
class CoffeeManga : Madara("Coffee Manga", "https://coffeemanga.io", "en") {
|
||||||
override val useNewChapterEndpoint = false
|
override val useNewChapterEndpoint = false
|
||||||
|
|
||||||
override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/"
|
|
||||||
|
|
||||||
override fun imageFromElement(element: Element): String? {
|
override fun imageFromElement(element: Element): String? {
|
||||||
return when {
|
return when {
|
||||||
element.hasAttr("data-src") && element.attr("data-src").isNotEmpty() -> element.attr("abs:data-src")
|
element.hasAttr("data-src") && element.attr("data-src").isNotEmpty() -> element.attr("abs:data-src")
|
||||||
|
|
|
@ -11,6 +11,4 @@ class ColoredManga : Madara(
|
||||||
dateFormat = SimpleDateFormat("dd-MMM", Locale.ENGLISH),
|
dateFormat = SimpleDateFormat("dd-MMM", Locale.ENGLISH),
|
||||||
) {
|
) {
|
||||||
override val useNewChapterEndpoint = true
|
override val useNewChapterEndpoint = true
|
||||||
|
|
||||||
override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/"
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,4 @@ import eu.kanade.tachiyomi.multisrc.madara.Madara
|
||||||
|
|
||||||
class ComicScans : Madara("Comic Scans", "https://www.comicscans.org", "en") {
|
class ComicScans : Madara("Comic Scans", "https://www.comicscans.org", "en") {
|
||||||
override val useNewChapterEndpoint = true
|
override val useNewChapterEndpoint = true
|
||||||
|
|
||||||
override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/"
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,6 @@ import kotlinx.serialization.json.boolean
|
||||||
import kotlinx.serialization.json.jsonArray
|
import kotlinx.serialization.json.jsonArray
|
||||||
import kotlinx.serialization.json.jsonObject
|
import kotlinx.serialization.json.jsonObject
|
||||||
import kotlinx.serialization.json.jsonPrimitive
|
import kotlinx.serialization.json.jsonPrimitive
|
||||||
import okhttp3.CacheControl
|
|
||||||
import okhttp3.FormBody
|
import okhttp3.FormBody
|
||||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
|
@ -35,26 +34,6 @@ class CreepyScans : Madara(
|
||||||
|
|
||||||
override val useNewChapterEndpoint = true
|
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
|
// Search
|
||||||
|
|
||||||
override fun fetchSearchManga(
|
override fun fetchSearchManga(
|
||||||
|
@ -142,11 +121,13 @@ class CreepyScans : Madara(
|
||||||
Filter.Select<String>("Genre", vals.map { it.first }.toTypedArray())
|
Filter.Select<String>("Genre", vals.map { it.first }.toTypedArray())
|
||||||
|
|
||||||
override fun getFilterList(): FilterList {
|
override fun getFilterList(): FilterList {
|
||||||
|
launchIO { fetchGenres() }
|
||||||
|
|
||||||
val filters = buildList(4) {
|
val filters = buildList(4) {
|
||||||
add(
|
add(
|
||||||
OrderByFilter(
|
OrderByFilter(
|
||||||
title = orderByFilterTitle,
|
title = intl["order_by_filter_title"],
|
||||||
options = orderByFilterOptions.zip(orderByFilterOptionsValues),
|
options = orderByFilterOptions.map { Pair(it.key, it.value) },
|
||||||
state = 0,
|
state = 0,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
|
@ -4,6 +4,4 @@ import eu.kanade.tachiyomi.multisrc.madara.Madara
|
||||||
|
|
||||||
class DarkScan : Madara("Dark-scan", "https://dark-scan.com", "en") {
|
class DarkScan : Madara("Dark-scan", "https://dark-scan.com", "en") {
|
||||||
override val useNewChapterEndpoint = true
|
override val useNewChapterEndpoint = true
|
||||||
|
|
||||||
override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/"
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
ext {
|
||||||
|
extName = 'DMC Scans'
|
||||||
|
extClass = '.DMCScans'
|
||||||
|
themePkg = 'zeistmanga'
|
||||||
|
baseUrl = 'https://didascans.blogspot.com'
|
||||||
|
overrideVersionCode = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
apply from: "$rootDir/common.gradle"
|
After Width: | Height: | Size: 5.4 KiB |
After Width: | Height: | Size: 2.7 KiB |
After Width: | Height: | Size: 8.0 KiB |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 26 KiB |
|
@ -0,0 +1,66 @@
|
||||||
|
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"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -27,14 +27,6 @@ class DragonTea : Madara(
|
||||||
|
|
||||||
override val useNewChapterEndpoint = true
|
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)
|
private val pageIndexRegex = Regex("""image-(\d+)[a-z]+""", RegexOption.IGNORE_CASE)
|
||||||
|
|
||||||
override fun pageListParse(document: Document): List<Page> {
|
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 unsaltedCiphertext = Base64.decode(cipherData["ct"]!!.jsonPrimitive.content, Base64.DEFAULT)
|
||||||
val salt = cipherData["s"]!!.jsonPrimitive.content.decodeHex()
|
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))
|
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ ext {
|
||||||
extClass = '.ElarcPage'
|
extClass = '.ElarcPage'
|
||||||
themePkg = 'mangathemesia'
|
themePkg = 'mangathemesia'
|
||||||
baseUrl = 'https://elarctoon.com'
|
baseUrl = 'https://elarctoon.com'
|
||||||
overrideVersionCode = 3
|
overrideVersionCode = 4
|
||||||
isNsfw = false
|
isNsfw = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,81 @@
|
||||||
package eu.kanade.tachiyomi.extension.en.elarcpage
|
package eu.kanade.tachiyomi.extension.en.elarcpage
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia
|
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(
|
class ElarcPage : MangaThemesia(
|
||||||
"Elarc Toon",
|
"Elarc Toon",
|
||||||
"https://elarctoon.com",
|
"https://elarctoon.com",
|
||||||
"en",
|
"en",
|
||||||
"/readtoons98111",
|
|
||||||
) {
|
) {
|
||||||
override val id = 5482125641807211052
|
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) {
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,4 @@ import eu.kanade.tachiyomi.multisrc.madara.Madara
|
||||||
class EliteManga : Madara("Elite Manga", "https://www.elitemanga.org", "en") {
|
class EliteManga : Madara("Elite Manga", "https://www.elitemanga.org", "en") {
|
||||||
override val useNewChapterEndpoint = true
|
override val useNewChapterEndpoint = true
|
||||||
override val filterNonMangaItems = false
|
override val filterNonMangaItems = false
|
||||||
|
|
||||||
override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/"
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,4 @@ import eu.kanade.tachiyomi.multisrc.madara.Madara
|
||||||
|
|
||||||
class FactManga : Madara("FactManga", "https://factmanga.com", "en") {
|
class FactManga : Madara("FactManga", "https://factmanga.com", "en") {
|
||||||
override val useNewChapterEndpoint = true
|
override val useNewChapterEndpoint = true
|
||||||
|
|
||||||
override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/"
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,7 @@ class FireScans : Madara("Fire Scans", "https://firescans.xyz", "en") {
|
||||||
override val useNewChapterEndpoint: Boolean = true
|
override val useNewChapterEndpoint: Boolean = true
|
||||||
|
|
||||||
override fun pageListParse(document: Document): List<Page> {
|
override fun pageListParse(document: Document): List<Page> {
|
||||||
countViews(document)
|
launchIO { countViews(document) }
|
||||||
|
|
||||||
val chapterProtector = document.selectFirst(chapterProtectorSelector)
|
val chapterProtector = document.selectFirst(chapterProtectorSelector)
|
||||||
?: return document.select(pageListParseSelector).mapIndexed { index, element ->
|
?: 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 unsaltedCiphertext = Base64.decode(chapterData["ct"]!!.jsonPrimitive.content, Base64.DEFAULT)
|
||||||
val salt = chapterData["s"]!!.jsonPrimitive.content.decodeHex()
|
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 rawImgArray = CryptoAES.decrypt(Base64.encodeToString(ciphertext, Base64.DEFAULT), password)
|
||||||
val imgArrayString = json.parseToJsonElement(rawImgArray).jsonPrimitive.content
|
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)
|
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()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,4 @@ import eu.kanade.tachiyomi.multisrc.madara.Madara
|
||||||
|
|
||||||
class FirstKissDashManga : Madara("1st Kiss-Manga (unoriginal)", "https://1stkiss-manga.com", "en") {
|
class FirstKissDashManga : Madara("1st Kiss-Manga (unoriginal)", "https://1stkiss-manga.com", "en") {
|
||||||
override val useNewChapterEndpoint = false
|
override val useNewChapterEndpoint = false
|
||||||
|
|
||||||
override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/"
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,4 @@ class FirstManhwa : Madara("1st Manhwa", "https://1stmanhwa.com", "en") {
|
||||||
override val useNewChapterEndpoint = true
|
override val useNewChapterEndpoint = true
|
||||||
override val filterNonMangaItems = false
|
override val filterNonMangaItems = false
|
||||||
override val mangaDetailsSelectorStatus = "div.summary-heading:contains(Status) + div.summary-content"
|
override val mangaDetailsSelectorStatus = "div.summary-heading:contains(Status) + div.summary-content"
|
||||||
|
|
||||||
override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/"
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,4 @@ import eu.kanade.tachiyomi.multisrc.madara.Madara
|
||||||
|
|
||||||
class FreeManhwa : Madara("Free Manhwa", "https://manhwas.com", "en") {
|
class FreeManhwa : Madara("Free Manhwa", "https://manhwas.com", "en") {
|
||||||
override val useNewChapterEndpoint = false
|
override val useNewChapterEndpoint = false
|
||||||
|
|
||||||
override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/"
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,4 @@ import eu.kanade.tachiyomi.multisrc.madara.Madara
|
||||||
|
|
||||||
class GirlsLoveManga : Madara("Girls Love Manga!", "https://glmanga.com", "en") {
|
class GirlsLoveManga : Madara("Girls Love Manga!", "https://glmanga.com", "en") {
|
||||||
override val useNewChapterEndpoint = true
|
override val useNewChapterEndpoint = true
|
||||||
|
|
||||||
override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/"
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,8 +12,6 @@ class GlobalBloging : Madara(
|
||||||
) {
|
) {
|
||||||
override val useNewChapterEndpoint = true
|
override val useNewChapterEndpoint = true
|
||||||
|
|
||||||
override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/"
|
|
||||||
|
|
||||||
// =========================== Manga Details ============================
|
// =========================== Manga Details ============================
|
||||||
|
|
||||||
override val mangaDetailsSelectorThumbnail = "${super.mangaDetailsSelectorThumbnail}[src~=.]"
|
override val mangaDetailsSelectorThumbnail = "${super.mangaDetailsSelectorThumbnail}[src~=.]"
|
||||||
|
|
|
@ -2,72 +2,21 @@ package eu.kanade.tachiyomi.extension.en.goodgirlsscan
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.multisrc.madara.Madara
|
import eu.kanade.tachiyomi.multisrc.madara.Madara
|
||||||
import eu.kanade.tachiyomi.network.GET
|
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.FilterList
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
import okhttp3.FormBody
|
|
||||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import org.jsoup.nodes.Element
|
import org.jsoup.nodes.Element
|
||||||
|
|
||||||
class GoodGirlsScan : Madara("Good Girls Scan", "https://goodgirls.moe", "en") {
|
class GoodGirlsScan : Madara("Good Girls Scan", "https://goodgirls.moe", "en") {
|
||||||
|
|
||||||
override val fetchGenres = false
|
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 searchMangaSelector() = "article.wp-manga"
|
||||||
override fun searchMangaNextPageSelector() = "div.paginator .nav-next"
|
override fun searchMangaNextPageSelector() = "div.paginator .nav-next"
|
||||||
override val mangaDetailsSelectorStatus = "div.summary-heading:contains(Status) + div.summary-content"
|
override val mangaDetailsSelectorStatus = "div.summary-heading:contains(Status) + div.summary-content"
|
||||||
override val mangaDetailsSelectorDescription = "div.summary-specialfields"
|
override val mangaDetailsSelectorDescription = "div.summary-specialfields"
|
||||||
override fun chapterListSelector() = "li.wp-manga-chapter:not(.vip-permission)"
|
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
|
// heavily modified madara theme, throws 5xx errors on any search filter
|
||||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||||
val url = "$baseUrl/${searchPage(page)}".toHttpUrl().newBuilder().apply {
|
val url = "$baseUrl/${searchPage(page)}".toHttpUrl().newBuilder().apply {
|
||||||
|
|
|
@ -21,8 +21,6 @@ class GourmetScans : Madara(
|
||||||
|
|
||||||
// Search
|
// Search
|
||||||
|
|
||||||
override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/"
|
|
||||||
|
|
||||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||||
val url = baseUrl.toHttpUrl().newBuilder()
|
val url = baseUrl.toHttpUrl().newBuilder()
|
||||||
|
|
||||||
|
@ -77,23 +75,23 @@ class GourmetScans : Madara(
|
||||||
|
|
||||||
private var genresList: List<Pair<String, String>> = emptyList()
|
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())
|
UriPartFilter("Genre", vals.toTypedArray())
|
||||||
|
|
||||||
override fun getFilterList(): FilterList {
|
override fun getFilterList(): FilterList {
|
||||||
val filters = buildList(4) {
|
val filters = buildList(4) {
|
||||||
add(YearFilter(yearFilterTitle))
|
add(YearFilter(intl["year_filter_title"]))
|
||||||
add(
|
add(
|
||||||
OrderByFilter(
|
OrderByFilter(
|
||||||
title = orderByFilterTitle,
|
title = intl["order_by_filter_title"],
|
||||||
options = orderByFilterOptions.zip(orderByFilterOptionsValues),
|
options = orderByFilterOptions.map { Pair(it.key, it.value) },
|
||||||
state = 0,
|
state = 0,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
add(Filter.Separator())
|
add(Filter.Separator())
|
||||||
|
|
||||||
if (genresList.isEmpty()) {
|
if (genresList.isEmpty()) {
|
||||||
add(Filter.Header(genresMissingWarning))
|
add(Filter.Header(intl["genre_missing_warning"]))
|
||||||
} else {
|
} else {
|
||||||
add(GenreFilter(listOf(Pair("<select>", "")) + genresList))
|
add(GenreFilter(listOf(Pair("<select>", "")) + genresList))
|
||||||
}
|
}
|
||||||
|
|