Compare commits

...

63 Commits

Author SHA1 Message Date
bapeey f6cb65688d Epsilon Scan: Move to Madara (#1582)
CI / Prepare job (push) Successful in 6s Details
CI / Build individual modules (push) Successful in 5m6s Details
CI / Publish repo (push) Successful in 48s Details
* Save

* Move to Madara
2024-02-27 20:53:54 +00:00
bapeey 6c6f1b4d6b ManhwaWeb: Fix chapter url (#1583)
I blame the IDE for not tell me about unused val
2024-02-27 20:53:54 +00:00
bapeey ee937137c3 Add ManhwaWeb (#1542)
* Add manhwaweb

* Lint

* Remove data class

* Oops

* Oppsi

Co-authored-by: AwkwardPeak7 <48650614+AwkwardPeak7@users.noreply.github.com>

* Remove "Not used"

Co-authored-by: AwkwardPeak7 <48650614+AwkwardPeak7@users.noreply.github.com>

* Requested changes

* Fix chapter url

* Create SChapter in main class

* make extension function

---------

Co-authored-by: AwkwardPeak7 <48650614+AwkwardPeak7@users.noreply.github.com>
2024-02-27 20:53:54 +00:00
bapeey 4f3500d728 TresDaosScan: Move to Madara (#1581)
Move to Madara
2024-02-27 20:53:54 +00:00
Chopper d951e22f52 Add AloneScanlator (#1514)
* Add Alone Scanlator

* Fix date pattern

* Fix overrideVersionCode value

Co-authored-by: Vetle Ledaal <vetle.ledaal@gmail.com>

* Set 'useNewChapterEndpoint' to 'true'

---------

Co-authored-by: Vetle Ledaal <vetle.ledaal@gmail.com>
2024-02-27 20:53:54 +00:00
TheKingTermux f2018fdc4a [Source is Down] Remove JMana & Esomanga (#1540)
* JMana Source is Down

* Esomamga Source is Down
2024-02-27 20:53:49 +00:00
Chopper dfb2092c75 Portuga Mangas: Add Custom User-Agent (#1486)
* Add random and custom User-Agent

* Replace GET request by client request

* Remove joinToString separator

* Add final newline

Co-authored-by: beerpsi <92439990+beerpiss@users.noreply.github.com>

* Remove unnecessary client wrapping

* Fix: add new line at the end

* Remove lazy modifier

Co-authored-by: AwkwardPeak7 <48650614+AwkwardPeak7@users.noreply.github.com>

---------

Co-authored-by: beerpsi <92439990+beerpiss@users.noreply.github.com>
Co-authored-by: AwkwardPeak7 <48650614+AwkwardPeak7@users.noreply.github.com>
2024-02-27 20:53:49 +00:00
bapeey 1fc3a46632 Manga Crab: Add randomUa and change domain (#1539)
* Add randomUa and change domain

* Lint

* Lint?

* Remove lazy

Co-authored-by: AwkwardPeak7 <48650614+AwkwardPeak7@users.noreply.github.com>

---------

Co-authored-by: AwkwardPeak7 <48650614+AwkwardPeak7@users.noreply.github.com>
2024-02-27 20:53:49 +00:00
Chopper 76bd3bb892 Fix BlackoutComics selectors (#1547)
* Fix CSS selectors

* Remove optional manga title

Co-authored-by: AwkwardPeak7 <48650614+AwkwardPeak7@users.noreply.github.com>

* Remove optional manga datails title

Co-authored-by: AwkwardPeak7 <48650614+AwkwardPeak7@users.noreply.github.com>

---------

Co-authored-by: AwkwardPeak7 <48650614+AwkwardPeak7@users.noreply.github.com>
2024-02-27 20:53:49 +00:00
mohamedotaku 79d24a894a Change url Shinigami "id" (#1546) 2024-02-27 20:53:49 +00:00
bapeey aa701c7c5e Manga Demon: Update domain (#1545)
* Update domain

* Remove CHANGELOG.md
2024-02-27 20:53:49 +00:00
bapeey f0d2df86c5 BarManga: Change dateFormat (#1537)
Change dateformat
2024-02-27 20:53:49 +00:00
beerpsi a95fa2dd5c Make ManhwaZ a multisrc + Add UmeTruyen (#1495)
* Make ManhwaZ a multisrc + Add UmeTruyen

* Forgot to commit the most important stuff

* icons

* Update src/en/manhwaz/build.gradle

Co-authored-by: AwkwardPeak7 <48650614+AwkwardPeak7@users.noreply.github.com>

* Don't use GlobalScope

* Remove useless optin

* Add CoroutineScope import

---------

Co-authored-by: AwkwardPeak7 <48650614+AwkwardPeak7@users.noreply.github.com>
2024-02-27 20:53:49 +00:00
Tef 6d16a8908c Add JManga (#1489)
* Add JManga

* Add JManga

* Fixed date parsing

* Removed seconds

* Removed replaceSearchPath

* Removed extra newlines

* Applied requested changes
2024-02-27 20:53:49 +00:00
Cuong M. Tran 1abe238809 Fix Madara was unable to reset Genre’s checkbox (#1476)
* Fix Madara was unable to reset Gener’s checkbox

* revert base version
2024-02-27 20:53:49 +00:00
bapeey d6f01fea0b MangaEsp: Update domain and add alternative names (#1470)
* Update domain

* Get comics from next data (api has old results)

* Add alternative names

* Change name creation logic

Co-authored-by: AwkwardPeak7 <48650614+AwkwardPeak7@users.noreply.github.com>

* Missing replace

---------

Co-authored-by: AwkwardPeak7 <48650614+AwkwardPeak7@users.noreply.github.com>
2024-02-27 20:53:49 +00:00
mohamedotaku 942ca100d8 Add source MajorScans "tr" (#1460)
* Add source MajorScans "tr"

* Update build.gradle
2024-02-27 20:53:49 +00:00
mohamedotaku bbffed1fd8 Change Url Hayalistic "tr" (#1510) 2024-02-27 20:53:49 +00:00
mohamedotaku 01371a728a Change Url SummerToon "tr" (#1511) 2024-02-27 20:53:49 +00:00
Eshlender 8870ff3569 [RU]Remanga processing native errors (#1526)
* [RU]Remanga processing native errors

* typos
2024-02-27 20:53:49 +00:00
Cuong M. Tran aac281ef87 Update MangaDistrict (#1344)
* MangaDistrict: Remove comment from title

e.g. (Official), (Doujinshi)

* MangaDistrict: settings to parse only chapters with full/high quality

# Conflicts:
#	src/en/mangadistrict/src/eu/kanade/tachiyomi/extension/en/mangadistrict/MangaDistrict.kt

* MangaDistrict: fix searchMangaNextPageSelector for last page

* remove unnecessary override

* Rebase to new Madara version

* filter chapter’s url instead of parsing elements

* Clean up, set default values to off/all

* Update MangaDistrict.kt

* Update remove title version pref summary

* typo

---------

Co-authored-by: stevenyomi <95685115+stevenyomi@users.noreply.github.com>
2024-02-27 20:53:49 +00:00
Secozzi 0992869ffc resetscans: update baseurl (#1504)
update baseurl
2024-02-27 20:53:49 +00:00
Secozzi 59a640e696 Shinigami: update baseurl (#1499)
update baseurl
2024-02-27 20:53:49 +00:00
beerpsi 29a8d5ead9 Dua Leo Truyen: Update to new site (#1498)
* Dua Leo Truyen: Update URL

* It's more than a simple URL change

* Fix description
2024-02-27 20:53:49 +00:00
Chopper 0624b1aa3e Add LunarScan (#1463) 2024-02-27 20:53:49 +00:00
Chopper 086475a12e Add Argos Comics (#1471)
* Add Argos Comics

* Remove the slash from the final URL

* Add custom latest update
2024-02-27 20:53:49 +00:00
Secozzi fc5a4825cb add dmcscans (#1481)
* add dmcscans

* dont show empty alt names
2024-02-27 20:53:49 +00:00
Vetle Ledaal fa1170fad7 Elarc Toon: update mangaUrlDirectory (#1413) 2024-02-27 20:53:49 +00:00
AwkwardPeak7 7ef3bcd497 Madara fix status filter (#1434)
* Madara fix status filter

* remove unused override ManhwaClan

site seems to be using default madara options 0, 1 etc
2024-02-27 20:53:49 +00:00
mohamedotaku cb23787606 fix Cloudflare protect for some sources (#1458)
cloudflare protect
2024-02-27 20:53:49 +00:00
Secozzi a4e00cedbe Traducciones Moonlight: filter out novels (#1462)
filter out novels
2024-02-27 20:53:49 +00:00
Secozzi 1d6ed34d77 newtoki: fix crash when changing sort filter (#1464)
* fix crash when changing sort filter

* small touch-up
2024-02-27 20:53:49 +00:00
Cuong M. Tran 9d73eef491 Fix Photos18 parsing image’s url (#1477) 2024-02-27 20:53:49 +00:00
mohamedotaku 838ee2fc97 Change Url Aresnov "ar" (#1482)
Change link Aresnov "ar"
2024-02-27 20:53:49 +00:00
mohamedotaku 94762d7d0d Change Url AresManga "ar" (#1483)
* Change Url AresManga "ar"

* Remove trailing slash from baseUrl

Co-authored-by: Vetle Ledaal <vetle.ledaal@gmail.com>

---------

Co-authored-by: beerpsi <92439990+beerpiss@users.noreply.github.com>
Co-authored-by: Vetle Ledaal <vetle.ledaal@gmail.com>
2024-02-27 20:53:49 +00:00
bapeey 2ec509f3d4 LectorTMO: Fix pages not found (#1494)
* Fix pages not found

* Change regex a bit more
2024-02-27 20:53:49 +00:00
TrungSamSet 26ef357149 NetTruyen: Update domain (#1433)
* NetTruyen: Update URL

* NetTruyen: Update overrideVersionCode
2024-02-27 20:53:49 +00:00
Rama Bondan Prakoso 0f00f9c313 NHentai: Add popular-month filter (#1446)
Signed-off-by: Rama Bondan Prakoso <ramanarubp@gmail.com>
2024-02-27 20:53:49 +00:00
bapeey 4bf383a322 Demon Sect: Fix http 404 on popular and latest tabs (#1449)
Fix http 400 on popular and latest
2024-02-27 20:53:49 +00:00
stevenyomi 3372f9de22 Adjust build features (#1436) 2024-02-27 20:53:49 +00:00
Chopper 2e25acbc36 Manhastro - Fix nullpointer when manga title is set (#1442)
Fix nullpointer when manga title is set
2024-02-27 20:53:49 +00:00
bapeey e5d3eeb7cc MangasNoSekai: Fix cannot found mangaid (#1440)
* easy

* Fix library path and author selector
2024-02-27 20:53:49 +00:00
Eshlender 2e41214ba9 [RU]ComX no Tachiyomi (maybe because of the closure), fix 520 (#1432)
* [RU]ComX no Tachiyomi (maybe because of the closure), fix 520

* Update src/ru/comx/src/eu/kanade/tachiyomi/extension/ru/comx/ComX.kt

Co-authored-by: AwkwardPeak7 <48650614+AwkwardPeak7@users.noreply.github.com>

---------

Co-authored-by: AwkwardPeak7 <48650614+AwkwardPeak7@users.noreply.github.com>
2024-02-27 20:53:49 +00:00
mohamedotaku daef319051 Change URL Swatmanga (#1427)
* change url mangaswat

* Change URL Swatmanga

* Update MangaSwat.kt
2024-02-27 20:53:49 +00:00
Rolando Lecca 8737cc41f2 MangasNoSekai: Fix chapter list (#1431)
cat and mouse
2024-02-27 20:53:49 +00:00
Rolando Lecca 921649ff76 Traducciones Moonlight: Move to MangaThemesia (#1406)
* Move to MangaThemesia

* Is NSFW
2024-02-27 20:53:49 +00:00
Rolando Lecca 3326753fc0 MangasNoSekai: Fix popular and latest tabs again (#1403)
fix
2024-02-27 20:53:28 +00:00
AwkwardPeak7 073a0b7a3f GravureBlogger: make categories optional (#1401)
* GravureBlogger: make categories optional

* remove author field

only contains source name
2024-02-27 20:53:28 +00:00
AwkwardPeak7 014ee88bbb Madara: fix app crash and some sources fixes (#1398)
* Madara: fix crash

* fix NPE in ImperioDaBritannia

* mangalek use LoadMore

* update title selector
2024-02-27 20:53:28 +00:00
Secozzi 678c3de899 Mana/newtoki: add referer (#1372)
* add referer

* remove superfluous headersbuilder
2024-02-27 20:53:28 +00:00
stevenyomi 9ef0b03211 GANMA: remove unused code (#1407) 2024-02-27 20:53:28 +00:00
stevenyomi fdc57d60a4 Happymh: revert to headers custom UA (#1394)
Partially reverts 1eb9f2bc13242c38e99362c198e84133261d1135
2024-02-27 20:53:28 +00:00
stevenyomi fc000a5058 Remove Kuaikuai Manhua 3 (#1393) 2024-02-27 20:53:28 +00:00
Tef be291b1822 Add MANGARAW+ (#1391) 2024-02-27 20:53:28 +00:00
Rolando Lecca c9633f3d0c MiauScan: Update domain (#1386) 2024-02-27 20:53:28 +00:00
stevenyomi b874f0d1c3 Deduplicate lib build scripts (#1392) 2024-02-27 20:53:28 +00:00
Rolando Lecca d311409641 Manhua Fenix: Change name and domain and add Spanish translations to Madara (#1365)
* Change name and domain

* Remove overrides and use loadMoreRequest

* Add Spanish translations

* Review

* Revert bump
2024-02-27 20:53:28 +00:00
Cuong M. Tran 09ae28784a update HM2D URL (doujindistrict.com) (#1375)
* new source: DoujinDistrict (theme: madara)

* Revert "new source: DoujinDistrict (theme: madara)"

This reverts commit 0b12e6be1425192e28166243dbd4fc6e2375ad12.

* update HM2D URL (doujindistrict.com)

* remove unused imports
2024-02-27 20:53:28 +00:00
Secozzi 166dbec86f add blackoutscans (#1367) 2024-02-27 20:53:28 +00:00
mohamedotaku a03bb1f780 Change URL PotatoManga (#1369) 2024-02-27 20:53:28 +00:00
Rolando Lecca b7ecf1b801 MNS: Fix popular and latest tabs again (#1368)
bruh
2024-02-27 20:53:28 +00:00
Vetle Ledaal 0ed4e4147f Manhastro: fix chapter images (#1363)
* Manhastro: fix chapter images

* Update src/pt/manhastro/src/eu/kanade/tachiyomi/extension/pt/manhastro/Manhastro.kt

Co-authored-by: AwkwardPeak7 <48650614+AwkwardPeak7@users.noreply.github.com>

---------

Co-authored-by: AwkwardPeak7 <48650614+AwkwardPeak7@users.noreply.github.com>
2024-02-27 20:53:28 +00:00
AwkwardPeak7 30b13498b0 Madara refactor (#1292)
* remove randomua from madara

* don't use page path for page=1

* add back `madara_load_more`

* cleanup i18n and filters

* load more in a new source

* move back the filters

not worth it

* fix build

* altname to i18n as well

* utf-8

* Revert "utf-8"

This reverts commit 1335bc1b478da54d3a5eb6333ac1a26e3ee2825b.

* utf-8

* autodetect load_more_request

* load genres in background

* make genre classes protected

remove unnecessary change

* fetch genres changes

* launchIO countviews

* don't explicitly optin

* cleanup some request overrides

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

* fix logic

bruh

* use state variables

* defer countViews in overrides as well

* lint

* select().first -> selectFirst

* `load_more` search as well

* detect in search as well

* remove slipped override

* move detection to the function

* remove fetchGenreFailed

* don't use GlobalScope

* tweak load_more_request parameters

* remove ancient connectTimeout/readTimeout

already present in the client provided by the app

* small cleanup
2024-02-27 20:53:28 +00:00
361 changed files with 2562 additions and 3077 deletions

View File

@ -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()

View File

@ -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())
}

View File

@ -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 {

View File

@ -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"))

View File

@ -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

View File

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

View File

@ -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,
) )

View File

@ -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

View File

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

View File

@ -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
}
} }

View File

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

View File

@ -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) {

View File

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

View File

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

View File

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

View File

@ -2,9 +2,9 @@ plugins {
id("lib-multisrc") 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"))
} }

View File

@ -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)
} }
} }

View File

@ -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

View File

@ -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

View File

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

View File

@ -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")
}
}

View File

@ -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 {

View File

@ -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)
} }

View File

@ -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)
} }

View File

@ -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)
} }

View File

@ -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)
} }

View File

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

View File

@ -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)
} }

View File

@ -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"))
} }

View File

@ -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)
} }

View File

@ -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)
} }

View File

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

View File

@ -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/"
} }

View File

@ -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()

View File

@ -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/"
} }

View File

@ -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/"
} }

View File

@ -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"

View File

@ -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, "")

View File

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

View File

@ -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"),

View File

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

View File

@ -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"))
} }
} }

View File

@ -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"

View File

@ -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")),

View File

@ -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()

View File

@ -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 {
""
}
}
}

View File

@ -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)!!

View File

@ -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"

View File

@ -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")),

View File

@ -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",

View File

@ -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"

View File

@ -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)!!
} }

View File

@ -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"

View File

@ -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")
} }
} }

View File

@ -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 {
""
}
}
}

View File

@ -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"

View File

@ -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/"
} }

View File

@ -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"

View File

@ -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/"
} }

View File

@ -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"

View File

@ -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() }

View File

@ -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"

View File

@ -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 =

View File

@ -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]"
}

View File

@ -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")

View File

@ -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/"
} }

View File

@ -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
}
}
} }

View File

@ -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/"
} }

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

View File

@ -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")

View File

@ -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")

View File

@ -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/"
} }

View File

@ -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/"
} }

View File

@ -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,
), ),
) )

View File

@ -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/"
} }

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

View File

@ -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"))
}
}
}

View File

@ -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)
}
} }

View File

@ -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
} }

View File

@ -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) {
}
}
} }

View File

@ -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/"
} }

View File

@ -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/"
} }

View File

@ -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()
}
} }

View File

@ -4,6 +4,4 @@ import eu.kanade.tachiyomi.multisrc.madara.Madara
class FirstKissDashManga : Madara("1st Kiss-Manga (unoriginal)", "https://1stkiss-manga.com", "en") { 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/"
} }

View File

@ -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/"
} }

View File

@ -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/"
} }

View File

@ -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/"
} }

View File

@ -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~=.]"

View File

@ -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 {

View File

@ -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))
} }

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