Compare commits

..

No commits in common. "9b7a7728238cb4c09fbc8acde3b8f0f9bcdfccd1" and "bbb2922515d7d478cc2e0f5363e8e49a9eac921d" have entirely different histories.

143 changed files with 764 additions and 669 deletions

View File

@ -45,7 +45,7 @@ jobs:
echo ${{ secrets.SIGNING_KEY }} | base64 -d > signingkey.jks
- name: Set up Gradle
uses: gradle/actions/setup-gradle@750cdda3edd6d51b7fdfc069d2e2818cf3c44f4c # v3.3.1
uses: gradle/actions/setup-gradle@6cec5d49d4d6d4bb982fbed7047db31ea6d38f11 # v3.3.0
- name: Build extensions
env:

View File

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

View File

@ -322,17 +322,9 @@ abstract class Liliana(
}
override fun pageListParse(document: Document): List<Page> {
return if (document.selectFirst("div.separator[data-index]") == null) {
document.select("div.separator").mapIndexed { i, page ->
val url = page.selectFirst("a")!!.attr("abs:href")
Page(i, document.location(), url)
}
} else {
document.select("div.separator[data-index]").map { page ->
val index = page.attr("data-index").toInt()
val url = page.selectFirst("a")!!.attr("abs:href")
Page(index, document.location(), url)
}.sortedBy { it.index }
return document.select("div.separator").mapIndexed { i, page ->
val url = page.selectFirst("a")!!.attr("abs:href")
Page(i, document.location(), url)
}
}
@ -342,7 +334,6 @@ abstract class Liliana(
val imgHeaders = headersBuilder().apply {
add("Accept", "image/avif,image/webp,*/*")
add("Host", page.imageUrl!!.toHttpUrl().host)
removeAll("Referer")
}.build()
return GET(page.imageUrl!!, imgHeaders)
}

View File

@ -16,11 +16,11 @@ open class MCCMSConfig(
hasCategoryPage: Boolean = true,
val textSearchOnlyPageOne: Boolean = false,
val useMobilePageList: Boolean = false,
protected val lazyLoadImageAttr: String = "data-original",
private val lazyLoadImageAttr: String = "data-original",
) {
val genreData = GenreData(hasCategoryPage)
open fun pageListParse(response: Response): List<Page> {
fun pageListParse(response: Response): List<Page> {
val document = response.asJsoup()
return if (useMobilePageList) {

View File

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

View File

@ -135,7 +135,7 @@ class CosplayTele : ParsedHttpSource() {
override fun chapterFromElement(element: Element): SChapter {
val chapter = SChapter.create()
chapter.setUrlWithoutDomain(element.select("[rel=\"bookmark\"]").attr("href"))
chapter.setUrlWithoutDomain(element.select("link[rel=\"canonical\"]").attr("href"))
chapter.name = "Gallery"
chapter.date_upload = getDate(element.select("time.updated").attr("datetime"))
return chapter

View File

@ -1,11 +1,7 @@
ext {
extName = 'NineManga'
extClass = '.NineMangaFactory'
extVersionCode = 20
extVersionCode = 19
}
apply from: "$rootDir/common.gradle"
dependencies {
implementation(project(":lib:cookieinterceptor"))
}

View File

@ -1,6 +1,5 @@
package eu.kanade.tachiyomi.extension.all.ninemanga
import eu.kanade.tachiyomi.lib.cookieinterceptor.CookieInterceptor
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.source.model.Filter
import eu.kanade.tachiyomi.source.model.FilterList
@ -27,8 +26,6 @@ open class NineManga(
override val supportsLatest: Boolean = true
private val cookieInterceptor = CookieInterceptor(baseUrl.substringAfter("://"), "ninemanga_list_num" to "1")
override val client: OkHttpClient = network.client.newBuilder()
.addInterceptor { chain ->
val request = chain.request()
@ -40,9 +37,7 @@ open class NineManga(
return@addInterceptor chain.proceed(newRequest)
}
chain.proceed(request)
}
.addNetworkInterceptor(cookieInterceptor)
.build()
}.build()
override fun headersBuilder(): Headers.Builder = Headers.Builder()
.add("Accept-Language", "es-ES,es;q=0.9,en;q=0.8,gl;q=0.7")
@ -102,6 +97,7 @@ open class NineManga(
element.select("a.chapter_list_a").let {
name = it.text().replace(mangaTitleForCleaning, "", true)
url = it.attr("href").substringAfter(baseUrl).replace("%20", " ")
.substringBeforeLast(".html") + "-1-1.html"
}
date_upload = parseChapterDate(element.select("span").text())
}

View File

@ -3,7 +3,7 @@ ext {
extClass = '.ThunderScansFactory'
themePkg = 'mangathemesia'
baseUrl = 'https://thunderscans.com'
overrideVersionCode = 1
overrideVersionCode = 0
}
apply from: "$rootDir/common.gradle"

View File

@ -1,7 +1,6 @@
package eu.kanade.tachiyomi.extension.all.thunderscans
import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia
import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesiaAlt
import eu.kanade.tachiyomi.source.SourceFactory
import java.text.SimpleDateFormat
import java.util.Locale
@ -13,7 +12,7 @@ class ThunderScansFactory : SourceFactory {
)
}
class ThunderScansAR : MangaThemesiaAlt(
class ThunderScansAR : MangaThemesia(
"Thunder Scans",
"https://thunderscans.com",
"ar",

View File

@ -12,7 +12,7 @@
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:host="unionmangasbr.org" />
<data android:host="https://unionmangas.xyz" />
<data android:scheme="https"/>
<data android:pathPattern="/manga-br/..*"/>

View File

@ -1,7 +1,7 @@
ext {
extName = 'Union Mangas'
extClass = '.UnionMangasFactory'
extVersionCode = 2
extVersionCode = 1
isNsfw = true
}

View File

@ -32,7 +32,7 @@ class UnionMangas(private val langOption: LanguageOption) : HttpSource() {
override val name: String = "Union Mangas"
override val baseUrl: String = "https://unionmangasbr.org"
override val baseUrl: String = "https://unionmangas.xyz"
override val supportsLatest = true

View File

@ -1,9 +0,0 @@
ext {
extName = 'Crow Scans'
extClass = '.CrowScans'
themePkg = 'mangathemesia'
baseUrl = 'https://crowscans.com'
overrideVersionCode = 0
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

View File

@ -1,12 +0,0 @@
package eu.kanade.tachiyomi.extension.ar.crowscans
import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia
import java.text.SimpleDateFormat
import java.util.Locale
class CrowScans : MangaThemesia(
"Crow Scans",
"https://crowscans.com",
"ar",
dateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale("ar")),
)

View File

@ -2,8 +2,8 @@ ext {
extName = 'MangaNoon'
extClass = '.MangaNoon'
themePkg = 'mangathemesia'
baseUrl = 'https://manjanoon.org'
overrideVersionCode = 2
baseUrl = 'https://manjanoon.net'
overrideVersionCode = 1
}
apply from: "$rootDir/common.gradle"

View File

@ -6,7 +6,7 @@ import java.util.Locale
class MangaNoon : MangaThemesia(
"مانجا نون",
"https://manjanoon.org",
"https://manjanoon.net",
"ar",
dateFormat = SimpleDateFormat("MMM d, yyy", Locale("ar")),
)

View File

@ -2,8 +2,8 @@ ext {
extName = 'Manga Pro'
extClass = '.MangaPro'
themePkg = 'mangathemesia'
baseUrl = 'https://mangapro.club'
overrideVersionCode = 1
baseUrl = 'https://mangapro.pro'
overrideVersionCode = 0
}
apply from: "$rootDir/common.gradle"

View File

@ -6,7 +6,7 @@ import java.util.Locale
class MangaPro : MangaThemesia(
"Manga Pro",
"https://mangapro.club",
"https://mangapro.pro",
"ar",
dateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale("ar")),
) {

View File

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

View File

@ -22,7 +22,7 @@ import uy.kohesive.injekt.api.get
import java.text.SimpleDateFormat
import java.util.Locale
private const val swatUrl = "https://normoyun.com"
private const val swatUrl = "https://swatmanhua.com"
class MangaSwat :
MangaThemesia(

View File

@ -0,0 +1,142 @@
package eu.kanade.tachiyomi.extension.en.comickiba
import eu.kanade.tachiyomi.source.model.Filter
object Note : Filter.Header("NOTE: Ignored if using text search!")
sealed class Select(
name: String,
val param: String,
values: Array<String>,
) : Filter.Select<String>(name, values) {
open val selection: String
get() = if (state == 0) "" else state.toString()
}
class StatusFilter(
values: Array<String> = statuses.keys.toTypedArray(),
) : Select("Status", "status", values) {
override val selection: String
get() = statuses[values[state]]!!
companion object {
private val statuses = mapOf(
"All" to "",
"Completed" to "completed",
"OnGoing" to "on-going",
"On-Hold" to "on-hold",
"Canceled" to "canceled",
)
}
}
class SortFilter(
values: Array<String> = orders.keys.toTypedArray(),
) : Select("Sort", "sort", values) {
override val selection: String
get() = orders[values[state]]!!
companion object {
private val orders = mapOf(
"Default" to "default",
"Latest Updated" to "latest-updated",
"Most Viewed" to "views",
"Most Viewed Month" to "views_month",
"Most Viewed Week" to "views_week",
"Most Viewed Day" to "views_day",
"Score" to "score",
"Name A-Z" to "az",
"Name Z-A" to "za",
"The highest chapter count" to "chapters",
"Newest" to "new",
"Oldest" to "old",
)
}
}
class Genre(name: String, val id: String) : Filter.CheckBox(name)
class GenresFilter(
values: List<Genre> = genres,
) : Filter.Group<Genre>("Genres", values) {
val param = "genres"
val selection: String
get() = state.filter { it.state }.joinToString(",") { it.id }
companion object {
private val genres: List<Genre>
get() = listOf(
Genre("Action", "37"),
Genre("Adaptation", "19"),
Genre("Adult", "5310"),
Genre("Adventure", "38"),
Genre("Aliens", "5436"),
Genre("Animals", "1552"),
Genre("Award Winning", "39"),
Genre("Comedy", "202"),
Genre("Comic", "287"),
Genre("Cooking", "277"),
Genre("Crime", "2723"),
Genre("Delinquents", "4438"),
Genre("Demons", "379"),
Genre("Drama", "3"),
Genre("Ecchi", "17"),
Genre("Fantasy", "197"),
Genre("Full Color", "13"),
Genre("Gender Bender", "221"),
Genre("Genderswap", "2290"),
Genre("Ghosts", "2866"),
Genre("Gore", "42"),
Genre("Harem", "222"),
Genre("Historical", "4"),
Genre("Horror", "5"),
Genre("Isekai", "259"),
Genre("Josei", "292"),
Genre("Loli", "5449"),
Genre("Long Strip", "7"),
Genre("Magic", "272"),
Genre("Manhwa", "266"),
Genre("Martial Arts", "40"),
Genre("Mature", "5311"),
Genre("Mecha", "2830"),
Genre("Medical", "1598"),
Genre("Military", "43"),
Genre("Monster Girls", "2307"),
Genre("Monsters", "298"),
Genre("Music", "3182"),
Genre("Mystery", "6"),
Genre("Office Workers", "14"),
Genre("Official Colored", "1046"),
Genre("Philosophical", "2776"),
Genre("Post-Apocalyptic", "1059"),
Genre("Psychological", "493"),
Genre("Reincarnation", "204"),
Genre("Reverse", "280"),
Genre("Reverse Harem", "199"),
Genre("Romance", "186"),
Genre("School Life", "601"),
Genre("Sci-Fi", "1845"),
Genre("Sexual Violence", "731"),
Genre("Shoujo", "254"),
Genre("Slice of Life", "10"),
Genre("Sports", "4066"),
Genre("Superhero", "481"),
Genre("Supernatural", "198"),
Genre("Survival", "44"),
Genre("Thriller", "1058"),
Genre("Time Travel", "299"),
Genre("Tragedy", "41"),
Genre("Video Games", "1846"),
Genre("Villainess", "278"),
Genre("Virtual Reality", "1847"),
Genre("Web Comic", "12"),
Genre("Webtoon", "279"),
Genre("Webtoons", "267"),
Genre("Wuxia", "203"),
Genre("Yaoi", "18"),
Genre("Yuri", "11"),
Genre("Zombies", "1060"),
)
}
}

View File

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

View File

@ -2,7 +2,6 @@ package eu.kanade.tachiyomi.extension.en.elarcpage
import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia
import eu.kanade.tachiyomi.network.GET
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Interceptor
import okhttp3.Response
import org.jsoup.Jsoup
@ -64,10 +63,12 @@ class ElarcPage : MangaThemesia(
request.url.toString(),
)
document.selectFirst(".serieslist > ul > li a.series")
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 {
val mangaUrlDirectory = it.attr("abs:href").toHttpUrl().pathSegments.first()
setMangaUrlDirectory("/$mangaUrlDirectory")
setMangaUrlDirectory(it)
dynamicUrlUpdated = timeNow
}

View File

@ -1,7 +1,7 @@
ext {
extName = 'Manga Demon'
extClass = '.MangaDemon'
extVersionCode = 11
extVersionCode = 10
isNsfw = false
}

View File

@ -28,7 +28,7 @@ class MangaDemon : ParsedHttpSource() {
override val lang = "en"
override val supportsLatest = true
override val name = "Manga Demon"
override val baseUrl = "https://comicdemons.com"
override val baseUrl = "https://demonreader.org"
override val client = network.cloudflareClient.newBuilder()
.rateLimit(1)

View File

@ -1,9 +1,7 @@
ext {
extName = 'Manga Sect'
extClass = '.MangaSect'
themePkg = 'liliana'
baseUrl = 'https://mangasect.net'
overrideVersionCode = 0
extVersionCode = 1
}
apply from: "$rootDir/common.gradle"

View File

@ -1,15 +1,238 @@
package eu.kanade.tachiyomi.extension.en.mangasect
import eu.kanade.tachiyomi.multisrc.liliana.Liliana
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.interceptor.rateLimit
import eu.kanade.tachiyomi.source.model.Filter
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.MangasPage
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 kotlinx.serialization.Serializable
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import org.jsoup.Jsoup
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import uy.kohesive.injekt.injectLazy
class MangaSect : Liliana(
"Manga Sect",
"https://mangasect.net",
"en",
usesPostSearch = true,
) {
override val client = super.client.newBuilder()
class MangaSect : ParsedHttpSource() {
override val name = "Manga Sect"
override val baseUrl = "https://mangasect.com"
override val lang = "en"
override val supportsLatest = true
private val json: Json by injectLazy()
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
.rateLimit(1)
.build()
override fun headersBuilder() = super.headersBuilder()
.add("Referer", "$baseUrl/")
// Popular
override fun popularMangaRequest(page: Int): Request = GET("$baseUrl/ranking/week/$page", headers)
override fun popularMangaSelector(): String = "div#main div.grid > div"
override fun popularMangaFromElement(element: Element): SManga = SManga.create().apply {
thumbnail_url = element.selectFirst("img")?.imgAttr()
element.selectFirst(".text-center a")!!.run {
title = text().trim()
setUrlWithoutDomain(attr("href"))
}
}
override fun popularMangaNextPageSelector(): String = ".blog-pager > span.pagecurrent + span"
// Latest
override fun latestUpdatesRequest(page: Int): Request =
GET("$baseUrl/all-manga/$page/?sort=1", headers)
override fun latestUpdatesParse(response: Response): MangasPage = popularMangaParse(response)
override fun latestUpdatesSelector(): String =
throw UnsupportedOperationException()
override fun latestUpdatesFromElement(element: Element): SManga =
throw UnsupportedOperationException()
override fun latestUpdatesNextPageSelector(): String =
throw UnsupportedOperationException()
// Search
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
val url = baseUrl.toHttpUrl().newBuilder().apply {
if (query.isNotBlank()) {
addPathSegment("search")
addQueryParameter("keyword", query)
} else {
addPathSegment("filter")
filters.forEach { filter ->
when (filter) {
is GenreFilter -> {
if (filter.checked.isNotEmpty()) {
addQueryParameter("genres", filter.checked.joinToString(","))
}
}
is StatusFilter -> {
if (filter.selected.isNotBlank()) {
addQueryParameter("status", filter.selected)
}
}
is SortFilter -> {
addQueryParameter("sort", filter.selected)
}
is ChapterCountFilter -> {
addQueryParameter("chapter_count", filter.selected)
}
is GenderFilter -> {
addQueryParameter("sex", filter.selected)
}
else -> {}
}
}
}
addPathSegment(page.toString())
addPathSegment("")
}
return GET(url.build(), headers)
}
override fun searchMangaParse(response: Response): MangasPage = popularMangaParse(response)
override fun searchMangaSelector(): String =
throw UnsupportedOperationException()
override fun searchMangaFromElement(element: Element): SManga =
throw UnsupportedOperationException()
override fun searchMangaNextPageSelector(): String =
throw UnsupportedOperationException()
// Filters
override fun getFilterList(): FilterList = FilterList(
Filter.Header("Ignored when using text search"),
Filter.Separator(),
GenreFilter(),
ChapterCountFilter(),
GenderFilter(),
StatusFilter(),
SortFilter(),
)
// Details
override fun mangaDetailsParse(document: Document): SManga = SManga.create().apply {
description = document.selectFirst("div#syn-target")?.text()
thumbnail_url = document.selectFirst(".a1 > figure img")?.imgAttr()
title = document.selectFirst(".a2 header h1")?.text()?.trim() ?: "N/A"
genre = document.select(".a2 div > a[rel='tag'].label").joinToString(", ") { it.text() }
document.selectFirst(".a1 > aside")?.run {
author = select("div:contains(Authors) > span a")
.joinToString(", ") { it.text().trim() }
.takeUnless { it.isBlank() || it.equals("Updating", true) }
status = selectFirst("div:contains(Status) > span")?.text().let(::parseStatus)
}
}
private fun parseStatus(status: String?): Int = when {
status.equals("ongoing", true) -> SManga.ONGOING
status.equals("completed", true) -> SManga.COMPLETED
status.equals("on-hold", true) -> SManga.ON_HIATUS
status.equals("canceled", true) -> SManga.CANCELLED
else -> SManga.UNKNOWN
}
// Chapters
override fun chapterListSelector() = "ul > li.chapter"
override fun chapterFromElement(element: Element): SChapter = SChapter.create().apply {
element.selectFirst("time[datetime]")?.also {
date_upload = it.attr("datetime").toLongOrNull()?.let { it * 1000L } ?: 0L
}
element.selectFirst("a")!!.run {
text().trim().also {
name = it
chapter_number = it.substringAfter("hapter ").toFloatOrNull() ?: 0F
}
setUrlWithoutDomain(attr("href"))
}
}
// Pages
override fun pageListRequest(chapter: SChapter): Request {
val pageHeaders = headersBuilder().apply {
add("Accept", "application/json, text/javascript, */*; q=0.01")
add("Host", baseUrl.toHttpUrl().host)
add("Referer", baseUrl + chapter.url)
add("X-Requested-With", "XMLHttpRequest")
}.build()
val id = chapter.url.split("/").last()
return GET("$baseUrl/ajax/image/list/chap/$id", pageHeaders)
}
@Serializable
data class PageListResponseDto(val html: String)
override fun pageListParse(response: Response): List<Page> {
val data = response.parseAs<PageListResponseDto>().html
return pageListParse(
Jsoup.parseBodyFragment(
data,
response.request.header("Referer")!!,
),
)
}
override fun pageListParse(document: Document): List<Page> {
return document.select("div.separator").map { page ->
val index = page.attr("data-index").toInt()
val url = page.selectFirst("a")!!.attr("abs:href")
Page(index, document.location(), url)
}.sortedBy { it.index }
}
override fun imageUrlParse(document: Document) = ""
override fun imageRequest(page: Page): Request {
val imgHeaders = headersBuilder().apply {
add("Accept", "image/avif,image/webp,*/*")
add("Host", page.imageUrl!!.toHttpUrl().host)
}.build()
return GET(page.imageUrl!!, imgHeaders)
}
// Utilities
// From mangathemesia
private fun Element.imgAttr(): String = when {
hasAttr("data-lazy-src") -> attr("abs:data-lazy-src")
hasAttr("data-src") -> attr("abs:data-src")
else -> attr("abs:src")
}
private inline fun <reified T> Response.parseAs(): T {
return json.decodeFromString(body.string())
}
}

View File

@ -0,0 +1,167 @@
package eu.kanade.tachiyomi.extension.en.mangasect
import eu.kanade.tachiyomi.source.model.Filter
abstract class SelectFilter(
name: String,
private val options: List<Pair<String, String>>,
) : Filter.Select<String>(
name,
options.map { it.first }.toTypedArray(),
) {
val selected get() = options[state].second
}
class CheckBoxFilter(
name: String,
val value: String,
) : Filter.CheckBox(name)
class ChapterCountFilter : SelectFilter("Chapter count", chapterCount) {
companion object {
private val chapterCount = listOf(
Pair(">= 0", "0"),
Pair(">= 10", "10"),
Pair(">= 30", "30"),
Pair(">= 50", "50"),
Pair(">= 100", "100"),
Pair(">= 200", "200"),
Pair(">= 300", "300"),
Pair(">= 400", "400"),
Pair(">= 500", "500"),
)
}
}
class GenderFilter : SelectFilter("Manga Gender", gender) {
companion object {
private val gender = listOf(
Pair("All", "All"),
Pair("Boy", "Boy"),
Pair("Girl", "Girl"),
)
}
}
class StatusFilter : SelectFilter("Status", status) {
companion object {
private val status = listOf(
Pair("All", ""),
Pair("Completed", "completed"),
Pair("OnGoing", "on-going"),
Pair("On-Hold", "on-hold"),
Pair("Canceled", "canceled"),
)
}
}
class SortFilter : SelectFilter("Sort", sort) {
companion object {
private val sort = listOf(
Pair("Default", "default"),
Pair("Latest Updated", "latest-updated"),
Pair("Most Viewed", "most-viewd"),
Pair("Score", "score"),
Pair("Name A-Z", "az"),
Pair("Name Z-A", "za"),
Pair("Newest", "new"),
Pair("Oldest", "old"),
)
}
}
class GenreFilter : Filter.Group<CheckBoxFilter>(
"Genre",
genres.map { CheckBoxFilter(it.first, it.second) },
) {
val checked get() = state.filter { it.state }.map { it.value }
companion object {
private val genres = listOf(
Pair("Action", "29"),
Pair("Adaptation", "66"),
Pair("Adult", "108"),
Pair("Adventure", "33"),
Pair("Aliens", "2326"),
Pair("Animals", "199"),
Pair("Comedy", "35"),
Pair("Comic", "109"),
Pair("Cooking", "26"),
Pair("Crime", "274"),
Pair("Delinquents", "234"),
Pair("Demons", "136"),
Pair("Drama", "39"),
Pair("Dungeons", "204"),
Pair("Ecchi", "54"),
Pair("Fantasy", "30"),
Pair("Full Color", "27"),
Pair("Genderswap", "1441"),
Pair("Genius MC", "209"),
Pair("Ghosts", "1527"),
Pair("Gore", "1678"),
Pair("Harem", "43"),
Pair("Historical", "49"),
Pair("Horror", "69"),
Pair("Incest", "1189"),
Pair("Isekai", "40"),
Pair("Loli", "198"),
Pair("Long Strip", "233"),
Pair("Magic", "212"),
Pair("Magical Girls", "1676"),
Pair("Manhua", "58"),
Pair("Manhwa", "80"),
Pair("Martial Arts", "32"),
Pair("Mature", "34"),
Pair("Mecha", "70"),
Pair("Medical", "2113"),
Pair("Military", "1531"),
Pair("Monster", "218"),
Pair("Monster Girls", "201"),
Pair("Monsters", "63"),
Pair("Murim", "208"),
Pair("Music", "412"),
Pair("Mystery", "31"),
Pair("One shot", "155"),
Pair("Overpowered", "206"),
Pair("Police", "275"),
Pair("Post-Apocalyptic", "197"),
Pair("Psychological", "36"),
Pair("Rebirth", "1435"),
Pair("Recarnation", "67"),
Pair("Regression", "205"),
Pair("Reincarnation", "64"),
Pair("Return", "1454"),
Pair("Returner", "211"),
Pair("Revenge", "219"),
Pair("Romance", "37"),
Pair("School Life", "44"),
Pair("Sci fi", "42"),
Pair("Sci-fi", "216"),
Pair("Seinen", "52"),
Pair("Sexual Violence", "2325"),
Pair("Shota", "2327"),
Pair("Shoujo", "92"),
Pair("Shounen", "38"),
Pair("Shounen ai", "103"),
Pair("Slice of Life", "68"),
Pair("Super power", "213"),
Pair("Superhero", "1630"),
Pair("Supernatural", "41"),
Pair("Survival", "463"),
Pair("System", "203"),
Pair("Thriller", "462"),
Pair("Time travel", "65"),
Pair("tower", "207"),
Pair("Tragedy", "51"),
Pair("Transmigration", "217"),
Pair("Uncategorized", "55"),
Pair("Vampires", "200"),
Pair("Video Games", "1606"),
Pair("Virtual Reality", "757"),
Pair("Web comic", "98"),
Pair("Webtoons", "77"),
Pair("Wuxia", "202"),
Pair("Zombies", "464"),
)
}
}

View File

@ -0,0 +1,10 @@
ext {
extName = 'MangaStic'
extClass = '.MangaStic'
themePkg = 'madara'
baseUrl = 'https://mangastic9.com'
overrideVersionCode = 3
isNsfw = true
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

@ -0,0 +1,5 @@
package eu.kanade.tachiyomi.extension.en.mangastic
import eu.kanade.tachiyomi.multisrc.madara.Madara
class MangaStic : Madara("MangaStic", "https://mangastic9.com", "en")

View File

@ -2,8 +2,8 @@ ext {
extName = 'Manga Weebs'
extClass = '.MangaWeebs'
themePkg = 'madara'
baseUrl = 'https://mangaweebs.org'
overrideVersionCode = 9
baseUrl = 'https://mangaweebs.in'
overrideVersionCode = 8
}
apply from: "$rootDir/common.gradle"

View File

@ -7,7 +7,7 @@ import java.text.SimpleDateFormat
import java.util.Locale
import java.util.concurrent.TimeUnit
class MangaWeebs : Madara("Manga Weebs", "https://mangaweebs.org", "en", dateFormat = SimpleDateFormat("dd MMMM HH:mm", Locale.US)) {
class MangaWeebs : Madara("Manga Weebs", "https://mangaweebs.in", "en", dateFormat = SimpleDateFormat("dd MMMM HH:mm", Locale.US)) {
override val client: OkHttpClient = super.client.newBuilder()
.rateLimit(20, 4, TimeUnit.SECONDS)

View File

@ -0,0 +1,9 @@
ext {
extName = 'Phantom Scans'
extClass = '.PhantomScans'
themePkg = 'mangathemesia'
baseUrl = 'https://phantomscans.com'
overrideVersionCode = 1
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

View File

@ -0,0 +1,5 @@
package eu.kanade.tachiyomi.extension.en.phantomscans
import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia
class PhantomScans : MangaThemesia("Phantom Scans", "https://phantomscans.com", "en")

View File

@ -0,0 +1,9 @@
ext {
extName = 'TeenManhua'
extClass = '.TeenManhua'
themePkg = 'madara'
baseUrl = 'https://teenmanhua.com'
overrideVersionCode = 1
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View File

@ -0,0 +1,14 @@
package eu.kanade.tachiyomi.extension.en.teenmanhua
import eu.kanade.tachiyomi.multisrc.madara.Madara
import java.text.SimpleDateFormat
import java.util.Locale
class TeenManhua : Madara(
"TeenManhua",
"https://teenmanhua.com",
"en",
dateFormat = SimpleDateFormat("dd/MM/yy", Locale.US),
) {
override val filterNonMangaItems = false
}

View File

@ -1,9 +0,0 @@
ext {
extName = 'Edens Fairy'
extClass = '.Eflee'
themePkg = 'zeistmanga'
baseUrl = 'https://www.eflee.co'
overrideVersionCode = 0
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

View File

@ -1,79 +0,0 @@
package eu.kanade.tachiyomi.extension.es.eflee
import eu.kanade.tachiyomi.multisrc.zeistmanga.Genre
import eu.kanade.tachiyomi.multisrc.zeistmanga.GenreList
import eu.kanade.tachiyomi.multisrc.zeistmanga.Type
import eu.kanade.tachiyomi.multisrc.zeistmanga.ZeistManga
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.source.model.Filter
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.util.asJsoup
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.jsoup.nodes.Document
class Eflee : ZeistManga(
"Edens Fairy",
"https://www.eflee.co",
"es",
) {
override val popularMangaSelector = "#PopularPosts3 article"
override val popularMangaSelectorTitle = ".post-title a"
override val popularMangaSelectorUrl = popularMangaSelectorTitle
override val useNewChapterFeed = true
override val chapterCategory = "Cap"
override val hasFilters = true
override val hasLanguageFilter = false
override val hasGenreFilter = false
override val hasStatusFilter = false
private var genresList: List<Genre> = emptyList()
private var fetchGenresAttempts: Int = 0
override fun getFilterList(): FilterList {
CoroutineScope(Dispatchers.IO).launch { fetchGenres() }
val filters = super.getFilterList().list.toMutableList()
if (genresList.isNotEmpty()) {
filters += GenreList(
title = "Generos",
genres = genresList,
)
} else {
filters += listOf(
Filter.Separator(),
Filter.Header("Presione 'Restablecer' para intentar mostrar los géneros"),
)
}
return FilterList(filters)
}
override fun getTypeList(): List<Type> = listOf(
Type("Todos", ""),
Type("Manga", "Manga"),
Type("Manhua", "Manhua"),
Type("Manhwa", "Manhwa"),
)
private fun fetchGenres() {
if (fetchGenresAttempts < 3 && genresList.isEmpty()) {
try {
genresList = client.newCall(GET(baseUrl, headers)).execute()
.use { parseGenres(it.asJsoup()) }
.sortedBy { it.value }
} catch (_: Exception) {
} finally {
fetchGenresAttempts++
}
}
}
private fun parseGenres(document: Document): List<Genre> {
return document.select(".filters .filter:first-child input:not(.hidden)")
.map { element ->
Genre(element.attr("id"), element.attr("value"))
}
}
}

View File

@ -1,12 +1,8 @@
ext {
extName = 'Ikigai Mangas'
extClass = '.IkigaiMangas'
extVersionCode = 7
extVersionCode = 4
isNsfw = true
}
apply from: "$rootDir/common.gradle"
dependencies {
implementation(project(":lib:cookieinterceptor"))
}

View File

@ -1,6 +1,5 @@
package eu.kanade.tachiyomi.extension.es.ikigaimangas
import eu.kanade.tachiyomi.lib.cookieinterceptor.CookieInterceptor
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.interceptor.rateLimitHost
import eu.kanade.tachiyomi.source.model.Filter
@ -10,7 +9,6 @@ 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.HttpSource
import eu.kanade.tachiyomi.util.asJsoup
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import okhttp3.HttpUrl.Companion.toHttpUrl
@ -32,12 +30,9 @@ class IkigaiMangas : HttpSource() {
override val supportsLatest: Boolean = true
private val cookieInterceptor = CookieInterceptor(baseUrl.substringAfter("://"), "data-saving" to "0")
override val client = network.cloudflareClient.newBuilder()
.rateLimitHost(baseUrl.toHttpUrl(), 1, 2)
.rateLimitHost(apiBaseUrl.toHttpUrl(), 2, 1)
.addNetworkInterceptor(cookieInterceptor)
.build()
override fun headersBuilder() = super.headersBuilder()
@ -107,7 +102,7 @@ class IkigaiMangas : HttpSource() {
return MangasPage(mangaList, result.hasNextPage())
}
override fun getMangaUrl(manga: SManga) = baseUrl + manga.url.substringBefore("#").replace("/series/comic-", "/series/")
override fun getMangaUrl(manga: SManga) = baseUrl + manga.url.substringBefore("#")
override fun mangaDetailsRequest(manga: SManga): Request {
val slug = manga.url
@ -146,13 +141,14 @@ class IkigaiMangas : HttpSource() {
return mangas
}
override fun pageListRequest(chapter: SChapter): Request =
GET(baseUrl + chapter.url.substringBefore("#"), headers)
override fun pageListRequest(chapter: SChapter): Request {
val id = chapter.url.substringAfter("/capitulo/")
return GET("$apiBaseUrl/api/swf/chapters/$id", headers)
}
override fun pageListParse(response: Response): List<Page> {
val document = response.asJsoup()
return document.select("section > div.img > img").mapIndexed { i, element ->
Page(i, imageUrl = element.attr("abs:src"))
return json.decodeFromString<PayloadPagesDto>(response.body.string()).chapter.pages.mapIndexed { i, img ->
Page(i, "", img)
}
}

View File

@ -109,6 +109,16 @@ class ChapterMetaDto(
fun hasNextPage() = currentPage < lastPage
}
@Serializable
class PayloadPagesDto(
val chapter: PageDto,
)
@Serializable
class PageDto(
val pages: List<String>,
)
@Serializable
class SeriesStatusDto(
val id: Long,

View File

@ -0,0 +1,9 @@
ext {
extName = 'KenhuaScan'
extClass = '.KenhuaScan'
themePkg = 'madara'
baseUrl = 'https://kenhuav2scan.com'
overrideVersionCode = 0
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

View File

@ -0,0 +1,22 @@
package eu.kanade.tachiyomi.extension.es.kenhuascan
import eu.kanade.tachiyomi.multisrc.madara.Madara
import eu.kanade.tachiyomi.network.interceptor.rateLimit
import okhttp3.OkHttpClient
import java.text.SimpleDateFormat
import java.util.Locale
import java.util.concurrent.TimeUnit
class KenhuaScan : Madara(
"KenhuaScan",
"https://kenhuav2scan.com",
"es",
dateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale("es")),
) {
override val client: OkHttpClient = super.client.newBuilder()
.rateLimit(2, 1, TimeUnit.SECONDS)
.build()
override val useNewChapterEndpoint = true
override val useLoadMoreRequest = LoadMoreStrategy.Always
}

View File

@ -1,10 +1,9 @@
ext {
extName = 'Manga Mukai'
extClass = '.MangaMukai'
extName = 'MangaShiina'
extClass = '.MangaShiina'
themePkg = 'mangathemesia'
baseUrl = 'https://mangamukai.com'
overrideVersionCode = 1
isNsfw = true
baseUrl = 'https://mangashiina.com'
overrideVersionCode = 0
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.1 KiB

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 35 KiB

View File

@ -4,11 +4,9 @@ import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia
import java.text.SimpleDateFormat
import java.util.Locale
class MangaMukai : MangaThemesia(
"Manga Mukai",
"https://mangamukai.com",
class MangaShiina : MangaThemesia(
"MangaShiina",
"https://mangashiina.com",
"es",
dateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale("es")),
) {
override val id: Long = 711368877221654433
}
)

View File

@ -1,7 +1,7 @@
ext {
extName = 'Olympus Scanlation'
extClass = '.OlympusScanlation'
extVersionCode = 8
extVersionCode = 7
}
apply from: "$rootDir/common.gradle"

View File

@ -24,8 +24,8 @@ class OlympusScanlation : HttpSource() {
override val versionId = 2
override val baseUrl: String = "https://leelolympus.com"
private val apiBaseUrl: String = "https://dashboard.leelolympus.com"
override val baseUrl: String = "https://visorolym.com"
private val apiBaseUrl: String = "https://dashboard.visorolym.com"
override val lang: String = "es"
override val name: String = "Olympus Scanlation"

View File

@ -1,12 +1,8 @@
ext {
extName = 'Plot Twist No Fansub'
extClass = '.PlotTwistNoFansub'
extVersionCode = 4
extVersionCode = 3
isNsfw = true
}
apply from: "$rootDir/common.gradle"
dependencies {
implementation(project(':lib:randomua'))
}

View File

@ -1,16 +1,8 @@
package eu.kanade.tachiyomi.extension.es.plottwistnofansub
import android.app.Application
import android.content.SharedPreferences
import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.lib.randomua.addRandomUAPreferenceToScreen
import eu.kanade.tachiyomi.lib.randomua.getPrefCustomUA
import eu.kanade.tachiyomi.lib.randomua.getPrefUAType
import eu.kanade.tachiyomi.lib.randomua.setRandomUserAgent
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.network.interceptor.rateLimitHost
import eu.kanade.tachiyomi.source.ConfigurableSource
import eu.kanade.tachiyomi.source.model.Filter
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.MangasPage
@ -32,11 +24,9 @@ import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import org.jsoup.nodes.Entities
import org.jsoup.select.Elements
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
class PlotTwistNoFansub : ParsedHttpSource(), ConfigurableSource {
class PlotTwistNoFansub : ParsedHttpSource() {
override val name = "Plot Twist No Fansub"
@ -48,21 +38,10 @@ class PlotTwistNoFansub : ParsedHttpSource(), ConfigurableSource {
private val json: Json by injectLazy()
private val preferences: SharedPreferences =
Injekt.get<Application>().getSharedPreferences("source_$id", 0x000)
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
.setRandomUserAgent(
preferences.getPrefUAType(),
preferences.getPrefCustomUA(),
)
override val client: OkHttpClient = network.client.newBuilder()
.rateLimitHost(baseUrl.toHttpUrl(), 1)
.build()
override fun setupPreferenceScreen(screen: PreferenceScreen) {
addRandomUAPreferenceToScreen(screen)
}
override fun headersBuilder(): Headers.Builder = Headers.Builder()
.add("Referer", "$baseUrl/")
@ -138,7 +117,7 @@ class PlotTwistNoFansub : ParsedHttpSource(), ConfigurableSource {
override fun chapterListParse(response: Response): List<SChapter> {
val document = response.asJsoup()
val mangaId = document.selectFirst(".chapters-container .row.itemlist p[data-mangaid]")!!.attr("data-mangaid")
val mangaId = document.selectFirst("div.td-ss-main-content > article[id^=post-]")!!.id().substringAfter("-")
val key = getKey(document)
val url = "$baseUrl/wp-admin/admin-ajax.php"

View File

@ -1,9 +0,0 @@
ext {
extName = 'SapphireScan'
extClass = '.SapphireScan'
themePkg = 'madara'
baseUrl = 'https://sapphirescan.com'
overrideVersionCode = 0
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

View File

@ -1,45 +0,0 @@
package eu.kanade.tachiyomi.extension.es.sapphirescan
import eu.kanade.tachiyomi.multisrc.madara.Madara
import eu.kanade.tachiyomi.network.interceptor.rateLimitHost
import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.model.SChapter
import okhttp3.HttpUrl.Companion.toHttpUrl
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import java.text.SimpleDateFormat
import java.util.Locale
class SapphireScan : Madara(
"SapphireScan",
"https://sapphirescan.com",
"es",
SimpleDateFormat("MMMM dd, yyyy", Locale("es")),
) {
override val client = super.client.newBuilder()
.rateLimitHost(baseUrl.toHttpUrl(), 3)
.build()
override val useNewChapterEndpoint = true
override val useLoadMoreRequest = LoadMoreStrategy.Always
override fun chapterFromElement(element: Element): SChapter {
return super.chapterFromElement(element).apply {
if (element.select("span.required-login").isNotEmpty()) {
name = "🔒 $name"
}
}
}
override fun pageListParse(document: Document): List<Page> {
val pageList = super.pageListParse(document)
if (
pageList.isEmpty() &&
document.select(".content-blocked, .login-required").isNotEmpty()
) {
throw Exception("Inicie sesión en WebView para ver este capítulo")
}
return pageList
}
}

View File

@ -1,8 +0,0 @@
ext {
extName = 'Senshi Manga'
extClass = '.SenshiManga'
extVersionCode = 1
isNsfw = false
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

View File

@ -1,145 +0,0 @@
package eu.kanade.tachiyomi.extension.es.senshimanga
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.interceptor.rateLimitHost
import eu.kanade.tachiyomi.source.model.Filter
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.MangasPage
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.HttpSource
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import okhttp3.Headers
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import uy.kohesive.injekt.injectLazy
class SenshiManga : HttpSource() {
override val name = "Senshi Manga"
override val baseUrl = "https://senshimanga.com"
override val lang = "es"
override val supportsLatest = true
private val apiBaseUrl = "https://lat-manga.com"
private val json: Json by injectLazy()
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
.rateLimitHost(baseUrl.toHttpUrl(), 3)
.rateLimitHost(apiBaseUrl.toHttpUrl(), 3)
.build()
override fun headersBuilder(): Headers.Builder = super.headersBuilder()
.add("Referer", "$baseUrl/")
private val apiHeaders: Headers = headersBuilder()
.add("Organization-Domain", "senshimanga.com")
.build()
override fun popularMangaRequest(page: Int): Request =
GET("$apiBaseUrl/api/manga-custom?page=$page&limit=$PAGE_LIMIT&order=popular", apiHeaders)
override fun popularMangaParse(response: Response): MangasPage = searchMangaParse(response)
override fun latestUpdatesRequest(page: Int): Request =
GET("$apiBaseUrl/api/manga-custom?page=$page&limit=$PAGE_LIMIT&order=latest", apiHeaders)
override fun latestUpdatesParse(response: Response): MangasPage = searchMangaParse(response)
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
val url = "$apiBaseUrl/api/manga-custom".toHttpUrl().newBuilder()
url.setQueryParameter("page", page.toString())
url.setQueryParameter("limit", PAGE_LIMIT.toString())
filters.forEach { filter ->
when (filter) {
is SortByFilter -> url.setQueryParameter("order", filter.toUriPart())
else -> {}
}
}
if (query.isNotBlank()) url.setQueryParameter("q", query)
return GET(url.build(), apiHeaders)
}
override fun searchMangaParse(response: Response): MangasPage {
val page = response.request.url.queryParameter("page")!!.toInt()
val result = json.decodeFromString<Data<SeriesListDataDto>>(response.body.string())
val mangas = result.data.series.map { it.toSManga() }
val hasNextPage = page < result.data.maxPage
return MangasPage(mangas, hasNextPage)
}
override fun getFilterList() = FilterList(
SortByFilter("Ordenar por", getSortList()),
)
private fun getSortList() = arrayOf(
Pair("Popularidad", "popular"),
Pair("Recientes", "latest"),
)
override fun getMangaUrl(manga: SManga): String = "$baseUrl/manga/${manga.url}"
override fun mangaDetailsRequest(manga: SManga): Request =
GET("$apiBaseUrl/api/manga-custom/${manga.url}", apiHeaders)
override fun mangaDetailsParse(response: Response): SManga {
val result = json.decodeFromString<Data<SeriesDto>>(response.body.string())
return result.data.toSMangaDetails()
}
override fun getChapterUrl(chapter: SChapter): String {
val seriesSlug = chapter.url.substringBefore("/")
val chapterSlug = chapter.url.substringAfter("/")
return "$baseUrl/manga/$seriesSlug/chapters/$chapterSlug"
}
override fun chapterListRequest(manga: SManga): Request = mangaDetailsRequest(manga)
override fun chapterListParse(response: Response): List<SChapter> {
val result = json.decodeFromString<Data<SeriesDto>>(response.body.string())
val seriesSlug = result.data.slug
return result.data.chapters?.map { it.toSChapter(seriesSlug) } ?: emptyList()
}
override fun pageListRequest(chapter: SChapter): Request {
val seriesSlug = chapter.url.substringBefore("/")
val chapterSlug = chapter.url.substringAfter("/")
return GET("$apiBaseUrl/api/manga-custom/$seriesSlug/chapter/$chapterSlug/pages", apiHeaders)
}
override fun pageListParse(response: Response): List<Page> {
val result = json.decodeFromString<Data<List<PageDto>>>(response.body.string())
return result.data.mapIndexed { i, page ->
Page(i, imageUrl = page.imageUrl)
}
}
override fun imageUrlParse(response: Response): String = throw UnsupportedOperationException()
class SortByFilter(title: String, list: Array<Pair<String, String>>) : UriPartFilter(title, list)
open class UriPartFilter(displayName: String, val vals: Array<Pair<String, String>>) :
Filter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) {
fun toUriPart() = vals[state].second
}
companion object {
private const val PAGE_LIMIT = 36
}
}

View File

@ -1,78 +0,0 @@
package eu.kanade.tachiyomi.extension.es.senshimanga
import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import java.text.ParseException
import java.text.SimpleDateFormat
import java.util.Locale
@Serializable
class Data<T>(val data: T)
@Serializable
class SeriesListDataDto(
@SerialName("data") val series: List<SeriesDto> = emptyList(),
val maxPage: Int = 0,
)
@Serializable
class SeriesDto(
val slug: String,
private val imageUrl: String,
private val title: String,
private val status: String? = null,
private val description: String? = null,
private val authors: List<SeriesAuthorDto>? = emptyList(),
val chapters: List<SeriesChapterDto>? = emptyList(),
) {
fun toSManga() = SManga.create().apply {
title = this@SeriesDto.title
thumbnail_url = imageUrl
url = slug
}
fun toSMangaDetails() = toSManga().apply {
status = parseStatus(this@SeriesDto.status)
description = this@SeriesDto.description
title = this@SeriesDto.title
author = authors?.joinToString { it.name }
}
private fun parseStatus(status: String?) = when (status) {
"ongoing" -> SManga.ONGOING
"hiatus" -> SManga.ON_HIATUS
"finished" -> SManga.COMPLETED
else -> SManga.UNKNOWN
}
}
@Serializable
class SeriesAuthorDto(
val name: String,
)
private val dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US)
@Serializable
class SeriesChapterDto(
private val title: String,
private val number: Float,
private val createdAt: String,
) {
fun toSChapter(seriesSlug: String) = SChapter.create().apply {
name = "Capítulo ${number.toString().removeSuffix(".0")} - $title"
date_upload = try {
dateFormat.parse(createdAt)?.time ?: 0L
} catch (_: ParseException) {
0L
}
url = "$seriesSlug/$number"
}
}
@Serializable
class PageDto(
val imageUrl: String,
)

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