Compare commits
No commits in common. "9b7a7728238cb4c09fbc8acde3b8f0f9bcdfccd1" and "bbb2922515d7d478cc2e0f5363e8e49a9eac921d" have entirely different histories.
9b7a772823
...
bbb2922515
|
@ -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:
|
||||
|
|
|
@ -2,4 +2,4 @@ plugins {
|
|||
id("lib-multisrc")
|
||||
}
|
||||
|
||||
baseVersionCode = 3
|
||||
baseVersionCode = 1
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
ext {
|
||||
extName = 'CosplayTele'
|
||||
extClass = '.CosplayTele'
|
||||
extVersionCode = 2
|
||||
extVersionCode = 1
|
||||
isNsfw = true
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,11 +1,7 @@
|
|||
ext {
|
||||
extName = 'NineManga'
|
||||
extClass = '.NineMangaFactory'
|
||||
extVersionCode = 20
|
||||
extVersionCode = 19
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
||||
|
||||
dependencies {
|
||||
implementation(project(":lib:cookieinterceptor"))
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ ext {
|
|||
extClass = '.ThunderScansFactory'
|
||||
themePkg = 'mangathemesia'
|
||||
baseUrl = 'https://thunderscans.com'
|
||||
overrideVersionCode = 1
|
||||
overrideVersionCode = 0
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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/..*"/>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
ext {
|
||||
extName = 'Union Mangas'
|
||||
extClass = '.UnionMangasFactory'
|
||||
extVersionCode = 2
|
||||
extVersionCode = 1
|
||||
isNsfw = true
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
ext {
|
||||
extName = 'Crow Scans'
|
||||
extClass = '.CrowScans'
|
||||
themePkg = 'mangathemesia'
|
||||
baseUrl = 'https://crowscans.com'
|
||||
overrideVersionCode = 0
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
Before Width: | Height: | Size: 4.6 KiB |
Before Width: | Height: | Size: 2.6 KiB |
Before Width: | Height: | Size: 7.1 KiB |
Before Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 21 KiB |
|
@ -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")),
|
||||
)
|
|
@ -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"
|
||||
|
|
|
@ -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")),
|
||||
)
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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")),
|
||||
) {
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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"),
|
||||
)
|
||||
}
|
||||
}
|
|
@ -3,7 +3,7 @@ ext {
|
|||
extClass = '.ElarcPage'
|
||||
themePkg = 'mangathemesia'
|
||||
baseUrl = 'https://elarctoons.com'
|
||||
overrideVersionCode = 7
|
||||
overrideVersionCode = 6
|
||||
isNsfw = false
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
ext {
|
||||
extName = 'Manga Demon'
|
||||
extClass = '.MangaDemon'
|
||||
extVersionCode = 11
|
||||
extVersionCode = 10
|
||||
isNsfw = false
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"),
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
ext {
|
||||
extName = 'MangaStic'
|
||||
extClass = '.MangaStic'
|
||||
themePkg = 'madara'
|
||||
baseUrl = 'https://mangastic9.com'
|
||||
overrideVersionCode = 3
|
||||
isNsfw = true
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
After Width: | Height: | Size: 4.1 KiB |
After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 5.9 KiB |
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 18 KiB |
|
@ -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")
|
|
@ -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"
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
ext {
|
||||
extName = 'Phantom Scans'
|
||||
extClass = '.PhantomScans'
|
||||
themePkg = 'mangathemesia'
|
||||
baseUrl = 'https://phantomscans.com'
|
||||
overrideVersionCode = 1
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
After Width: | Height: | Size: 6.3 KiB |
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 8.9 KiB |
After Width: | Height: | Size: 17 KiB |
After Width: | Height: | Size: 24 KiB |
|
@ -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")
|
|
@ -0,0 +1,9 @@
|
|||
ext {
|
||||
extName = 'TeenManhua'
|
||||
extClass = '.TeenManhua'
|
||||
themePkg = 'madara'
|
||||
baseUrl = 'https://teenmanhua.com'
|
||||
overrideVersionCode = 1
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
After Width: | Height: | Size: 4.4 KiB |
After Width: | Height: | Size: 2.3 KiB |
After Width: | Height: | Size: 6.7 KiB |
After Width: | Height: | Size: 13 KiB |
After Width: | Height: | Size: 22 KiB |
|
@ -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
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
ext {
|
||||
extName = 'Edens Fairy'
|
||||
extClass = '.Eflee'
|
||||
themePkg = 'zeistmanga'
|
||||
baseUrl = 'https://www.eflee.co'
|
||||
overrideVersionCode = 0
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
Before Width: | Height: | Size: 5.6 KiB |
Before Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 8.2 KiB |
Before Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 24 KiB |
|
@ -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"))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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"))
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
ext {
|
||||
extName = 'KenhuaScan'
|
||||
extClass = '.KenhuaScan'
|
||||
themePkg = 'madara'
|
||||
baseUrl = 'https://kenhuav2scan.com'
|
||||
overrideVersionCode = 0
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
After Width: | Height: | Size: 5.8 KiB |
After Width: | Height: | Size: 3.0 KiB |
After Width: | Height: | Size: 9.4 KiB |
After Width: | Height: | Size: 18 KiB |
After Width: | Height: | Size: 31 KiB |
|
@ -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
|
||||
}
|
|
@ -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"
|
||||
|
|
Before Width: | Height: | Size: 8.1 KiB After Width: | Height: | Size: 6.7 KiB |
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 3.4 KiB |
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 35 KiB |
|
@ -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
|
||||
}
|
||||
)
|
|
@ -1,7 +1,7 @@
|
|||
ext {
|
||||
extName = 'Olympus Scanlation'
|
||||
extClass = '.OlympusScanlation'
|
||||
extVersionCode = 8
|
||||
extVersionCode = 7
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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'))
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
ext {
|
||||
extName = 'SapphireScan'
|
||||
extClass = '.SapphireScan'
|
||||
themePkg = 'madara'
|
||||
baseUrl = 'https://sapphirescan.com'
|
||||
overrideVersionCode = 0
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
Before Width: | Height: | Size: 5.6 KiB |
Before Width: | Height: | Size: 3.0 KiB |
Before Width: | Height: | Size: 8.0 KiB |
Before Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 22 KiB |
|
@ -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
|
||||
}
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
ext {
|
||||
extName = 'Senshi Manga'
|
||||
extClass = '.SenshiManga'
|
||||
extVersionCode = 1
|
||||
isNsfw = false
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
Before Width: | Height: | Size: 4.2 KiB |
Before Width: | Height: | Size: 2.5 KiB |
Before Width: | Height: | Size: 5.9 KiB |
Before Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 16 KiB |
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
)
|