Compare commits
No commits in common. "e0bb39f99adb84553c28dce5f190fbeb60fb80b9" and "5ecf338be0dd21937706e3284fc9d0edc5950e62" have entirely different histories.
e0bb39f99a
...
5ecf338be0
@ -1,14 +1,12 @@
|
||||
# Editor configuration, see https://editorconfig.org
|
||||
root = true
|
||||
|
||||
[*]
|
||||
insert_final_newline = true
|
||||
end_of_line = lf
|
||||
|
||||
[*.kt]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
indent_size = 4
|
||||
indent_style = space
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
ij_kotlin_allow_trailing_comma = true
|
||||
ij_kotlin_allow_trailing_comma_on_call_site = true
|
||||
@ -17,3 +15,5 @@ ij_kotlin_name_count_to_use_star_import_for_members = 2147483647
|
||||
|
||||
[*.properties]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
|
@ -1,6 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
@ -9,7 +9,6 @@ assert !ext.has("libVersion")
|
||||
assert extName.chars().max().asInt < 0x180 : "Extension name should be romanized"
|
||||
|
||||
Project theme = ext.has("themePkg") ? project(":lib-multisrc:$themePkg") : null
|
||||
if (theme != null) evaluationDependsOn(theme.path)
|
||||
|
||||
android {
|
||||
compileSdk AndroidConfig.compileSdk
|
||||
|
@ -25,5 +25,3 @@ android.useAndroidX=true
|
||||
android.enableBuildConfigAsBytecode=true
|
||||
android.defaults.buildfeatures.resvalues=false
|
||||
android.defaults.buildfeatures.shaders=false
|
||||
|
||||
org.gradle.configureondemand=true
|
||||
|
@ -18,7 +18,7 @@ kotlin-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", ver
|
||||
coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines_version" }
|
||||
coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "coroutines_version" }
|
||||
|
||||
injekt-core = { module = "com.github.null2264.injekt:injekt-core", version = "4135455a2a" }
|
||||
injekt-core = { module = "com.github.inorichi.injekt:injekt-core", version = "65b0440" }
|
||||
rxjava = { module = "io.reactivex:rxjava", version = "1.3.8" }
|
||||
jsoup = { module = "org.jsoup:jsoup", version = "1.15.1" }
|
||||
okhttp = { module = "com.squareup.okhttp3:okhttp", version = "5.0.0-alpha.11" }
|
||||
|
2
gradle/wrapper/gradle-wrapper.properties
vendored
@ -1,6 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
@ -2,7 +2,7 @@ plugins {
|
||||
id("lib-multisrc")
|
||||
}
|
||||
|
||||
baseVersionCode = 4
|
||||
baseVersionCode = 3
|
||||
|
||||
dependencies {
|
||||
api(project(":lib:synchrony"))
|
||||
|
@ -276,7 +276,7 @@ abstract class ColaManga(
|
||||
}.also(screen::addPreference)
|
||||
}
|
||||
|
||||
private val keyMappingRegex = Regex("""if\s*\(\s*([a-zA-Z0-9_]+)\s*==\s*(?<keyType>\d+)\s*\)\s*\{\s*return\s*'(?<key>[a-zA-Z0-9_]+)'\s*;""")
|
||||
private val keyMappingRegex = Regex("""[0-9A-Za-z_]+\s*==\s*['"](?<keyType>\d+)['"]\s*&&\s*\([0-9A-Za-z_]+\s*=\s*['"](?<key>[a-zA-Z0-9]+)['"]\)""")
|
||||
|
||||
private val keyMapping by lazy {
|
||||
val obfuscatedReadJs = client.newCall(GET("$baseUrl/js/manga.read.js")).execute().body.string()
|
||||
|
@ -2,4 +2,4 @@ plugins {
|
||||
id("lib-multisrc")
|
||||
}
|
||||
|
||||
baseVersionCode = 24
|
||||
baseVersionCode = 23
|
||||
|
@ -2,4 +2,4 @@ plugins {
|
||||
id("lib-multisrc")
|
||||
}
|
||||
|
||||
baseVersionCode = 4
|
||||
baseVersionCode = 3
|
||||
|
@ -83,8 +83,8 @@ class Post<T>(val post: T)
|
||||
|
||||
@Serializable
|
||||
class ChapterListResponse(
|
||||
val isNovel: Boolean = false,
|
||||
val slug: String? = null,
|
||||
val isNovel: Boolean,
|
||||
val slug: String,
|
||||
val chapters: List<Chapter>,
|
||||
)
|
||||
|
||||
@ -96,13 +96,11 @@ class Chapter(
|
||||
private val createdBy: Name,
|
||||
private val createdAt: String,
|
||||
private val chapterStatus: String,
|
||||
private val mangaPost: ChapterPostDetails,
|
||||
) {
|
||||
fun isPublic() = chapterStatus == "PUBLIC"
|
||||
|
||||
fun toSChapter(mangaSlug: String?) = SChapter.create().apply {
|
||||
val seriesSlug = mangaSlug ?: mangaPost.slug
|
||||
url = "/series/$seriesSlug/$slug#$id"
|
||||
fun toSChapter(mangaSlug: String) = SChapter.create().apply {
|
||||
url = "/series/$mangaSlug/$slug#$id"
|
||||
name = "Chapter $number"
|
||||
scanlator = createdBy.name
|
||||
date_upload = try {
|
||||
@ -113,9 +111,4 @@ class Chapter(
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
class ChapterPostDetails(
|
||||
val slug: String,
|
||||
)
|
||||
|
||||
private val dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.ENGLISH)
|
||||
|
@ -2,4 +2,4 @@ plugins {
|
||||
id("lib-multisrc")
|
||||
}
|
||||
|
||||
baseVersionCode = 7
|
||||
baseVersionCode = 4
|
||||
|
@ -1,6 +1,7 @@
|
||||
package eu.kanade.tachiyomi.multisrc.keyoapp
|
||||
|
||||
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
|
||||
@ -30,7 +31,9 @@ abstract class Keyoapp(
|
||||
) : ParsedHttpSource() {
|
||||
override val supportsLatest = true
|
||||
|
||||
override val client = network.cloudflareClient
|
||||
override val client = network.cloudflareClient.newBuilder()
|
||||
.rateLimit(2)
|
||||
.build()
|
||||
|
||||
override fun headersBuilder() = super.headersBuilder()
|
||||
.add("Referer", "$baseUrl/")
|
||||
@ -194,18 +197,7 @@ abstract class Keyoapp(
|
||||
status = document.selectFirst("div[alt=Status]").parseStatus()
|
||||
author = document.selectFirst("div[alt=Author]")?.text()
|
||||
artist = document.selectFirst("div[alt=Artist]")?.text()
|
||||
genre = buildList {
|
||||
document.selectFirst("div[alt='Series Type']")?.text()?.replaceFirstChar {
|
||||
if (it.isLowerCase()) {
|
||||
it.titlecase(
|
||||
Locale.getDefault(),
|
||||
)
|
||||
} else {
|
||||
it.toString()
|
||||
}
|
||||
}.let(::add)
|
||||
document.select("div.grid:has(>h1) > div > a").forEach { add(it.text()) }
|
||||
}.joinToString()
|
||||
genre = document.select("div.grid:has(>h1) > div > a").joinToString { it.text() }
|
||||
}
|
||||
|
||||
private fun Element?.parseStatus(): Int = when (this?.text()?.lowercase()) {
|
||||
@ -231,27 +223,15 @@ abstract class Keyoapp(
|
||||
// Image list
|
||||
|
||||
override fun pageListParse(document: Document): List<Page> {
|
||||
document.select("#pages > img")
|
||||
.map { it.attr("uid") }
|
||||
.filter { it.isNotEmpty() }
|
||||
.mapIndexed { index, img ->
|
||||
Page(index, document.location(), "$cdnUrl/uploads/$img")
|
||||
}
|
||||
.takeIf { it.isNotEmpty() }
|
||||
?.also { return it }
|
||||
|
||||
// Fallback, old method
|
||||
return document.select("#pages > img")
|
||||
.map { it.imgAttr() }
|
||||
.filter { it.contains(oldImgCdnRegex) }
|
||||
.filter { it.contains(imgCdnRegex) }
|
||||
.mapIndexed { index, img ->
|
||||
Page(index, document.location(), img)
|
||||
}
|
||||
}
|
||||
|
||||
protected val cdnUrl = "https://cdn.igniscans.com"
|
||||
|
||||
private val oldImgCdnRegex = Regex("""^(https?:)?//cdn\d*\.keyoapp\.com""")
|
||||
private val imgCdnRegex = Regex("""^(https?:)?//cdn\d*\.keyoapp\.com""")
|
||||
|
||||
override fun imageUrlParse(document: Document) = ""
|
||||
|
||||
@ -267,7 +247,7 @@ abstract class Keyoapp(
|
||||
return url
|
||||
}
|
||||
|
||||
protected open fun Element.getImageUrl(selector: String): String? {
|
||||
private fun Element.getImageUrl(selector: String): String? {
|
||||
return this.selectFirst(selector)?.let { element ->
|
||||
element.attr("style")
|
||||
.substringAfter(":url(", "")
|
||||
|
@ -2,4 +2,4 @@ plugins {
|
||||
id("lib-multisrc")
|
||||
}
|
||||
|
||||
baseVersionCode = 31
|
||||
baseVersionCode = 30
|
||||
|
@ -622,7 +622,7 @@ abstract class Madara(
|
||||
"OnGoing", "Продолжается", "Updating", "Em Lançamento", "Em lançamento", "Em andamento",
|
||||
"Em Andamento", "En cours", "En Cours", "En cours de publication", "Ativo", "Lançando", "Đang Tiến Hành", "Devam Ediyor",
|
||||
"Devam ediyor", "In Corso", "In Arrivo", "مستمرة", "مستمر", "En Curso", "En curso", "Emision",
|
||||
"Curso", "En marcha", "Publicandose", "Publicándose", "En emision", "连载中", "Em Lançamento", "Devam Ediyo",
|
||||
"Curso", "En marcha", "Publicandose", "En emision", "连载中", "Em Lançamento", "Devam Ediyo",
|
||||
"Đang làm", "Em postagem", "Devam Eden", "Em progresso", "Em curso",
|
||||
)
|
||||
|
||||
|
@ -490,8 +490,8 @@ abstract class MangaThemesia(
|
||||
Pair(intl["order_by_filter_popular"], "popular"),
|
||||
)
|
||||
|
||||
protected open val popularFilter by lazy { FilterList(OrderByFilter("", orderByFilterOptions, "popular")) }
|
||||
protected open val latestFilter by lazy { FilterList(OrderByFilter("", orderByFilterOptions, "update")) }
|
||||
protected val popularFilter by lazy { FilterList(OrderByFilter("", orderByFilterOptions, "popular")) }
|
||||
protected val latestFilter by lazy { FilterList(OrderByFilter("", orderByFilterOptions, "update")) }
|
||||
|
||||
protected class ProjectFilter(
|
||||
name: String,
|
||||
@ -603,7 +603,7 @@ abstract class MangaThemesia(
|
||||
(!strict && url.pathSegments.size == n + 1 && url.pathSegments[n].isEmpty())
|
||||
}
|
||||
|
||||
protected open fun parseGenres(document: Document): List<GenreData>? {
|
||||
private fun parseGenres(document: Document): List<GenreData>? {
|
||||
return document.selectFirst("ul.genrez")?.select("li")?.map { li ->
|
||||
GenreData(
|
||||
li.selectFirst("label")!!.text(),
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
<application>
|
||||
<activity
|
||||
android:name="eu.kanade.tachiyomi.extension.tr.sadscans.SadscansUrlActivity"
|
||||
android:name="eu.kanade.tachiyomi.multisrc.po2scans.PO2ScansUrlActivity"
|
||||
android:excludeFromRecents="true"
|
||||
android:exported="true"
|
||||
android:theme="@android:style/Theme.NoDisplay">
|
||||
@ -14,9 +14,9 @@
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data
|
||||
android:host="sadscans.com"
|
||||
android:host="${SOURCEHOST}"
|
||||
android:pathPattern="/series/..*"
|
||||
android:scheme="https" />
|
||||
android:scheme="${SOURCESCHEME}" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
9
lib-multisrc/po2scans/build.gradle.kts
Normal file
@ -0,0 +1,9 @@
|
||||
plugins {
|
||||
id("lib-multisrc")
|
||||
}
|
||||
|
||||
baseVersionCode = 1
|
||||
|
||||
dependencies {
|
||||
api(project(":lib:dataimage"))
|
||||
}
|
@ -0,0 +1,141 @@
|
||||
package eu.kanade.tachiyomi.multisrc.po2scans
|
||||
|
||||
import eu.kanade.tachiyomi.lib.dataimage.DataImageInterceptor
|
||||
import eu.kanade.tachiyomi.lib.dataimage.dataImageAsUrl
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
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 org.jsoup.nodes.Document
|
||||
import org.jsoup.nodes.Element
|
||||
import rx.Observable
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Locale
|
||||
|
||||
abstract class PO2Scans(
|
||||
override val name: String,
|
||||
override val baseUrl: String,
|
||||
override val lang: String,
|
||||
private val dateFormat: SimpleDateFormat = SimpleDateFormat("dd MMMM, yy", Locale.ENGLISH),
|
||||
) : ParsedHttpSource() {
|
||||
|
||||
override val supportsLatest = true
|
||||
|
||||
override val client = network.cloudflareClient.newBuilder()
|
||||
.addInterceptor(DataImageInterceptor())
|
||||
.build()
|
||||
|
||||
override fun headersBuilder() = super.headersBuilder()
|
||||
.add("Referer", "$baseUrl/")
|
||||
|
||||
// popular
|
||||
override fun popularMangaRequest(page: Int) = GET("$baseUrl/series", headers)
|
||||
|
||||
override fun popularMangaSelector() = "div.series-list"
|
||||
|
||||
override fun popularMangaFromElement(element: Element) = SManga.create().apply {
|
||||
setUrlWithoutDomain(element.selectFirst("div > a")!!.absUrl("href"))
|
||||
title = element.selectFirst("div > h2")!!.text()
|
||||
thumbnail_url = element.selectFirst("img")?.absUrl("data-src")
|
||||
}
|
||||
|
||||
// TODO: add page selectors & url parameters when site have enough series for pagination
|
||||
override fun popularMangaNextPageSelector() = null
|
||||
|
||||
// latest
|
||||
override fun latestUpdatesRequest(page: Int) = GET(baseUrl, headers)
|
||||
|
||||
override fun latestUpdatesSelector() = "div.chap"
|
||||
|
||||
override fun latestUpdatesFromElement(element: Element) = SManga.create().apply {
|
||||
element.selectFirst("div.chap-title a")!!.let {
|
||||
setUrlWithoutDomain(it.absUrl("href"))
|
||||
title = it.text()
|
||||
}
|
||||
thumbnail_url = element.selectFirst("img")?.absUrl("data-src")
|
||||
}
|
||||
|
||||
override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector()
|
||||
|
||||
// search
|
||||
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
|
||||
if (!query.startsWith(SLUG_SEARCH_PREFIX)) {
|
||||
return super.fetchSearchManga(page, query, filters)
|
||||
}
|
||||
|
||||
val url = "/series/${query.substringAfter(SLUG_SEARCH_PREFIX)}"
|
||||
return fetchMangaDetails(SManga.create().apply { this.url = url })
|
||||
.map {
|
||||
it.url = url
|
||||
MangasPage(listOf(it), false)
|
||||
}
|
||||
}
|
||||
|
||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList) =
|
||||
GET("$baseUrl/series?search=$query", headers)
|
||||
|
||||
override fun searchMangaSelector() = popularMangaSelector()
|
||||
|
||||
override fun searchMangaFromElement(element: Element) = popularMangaFromElement(element)
|
||||
|
||||
override fun searchMangaNextPageSelector() = popularMangaNextPageSelector()
|
||||
|
||||
// manga details
|
||||
override fun mangaDetailsParse(document: Document): SManga {
|
||||
return SManga.create().apply {
|
||||
title = document.selectFirst(".title")!!.text()
|
||||
author = document.select(".author > span:nth-child(2)").text()
|
||||
artist = author
|
||||
status = document.select(".status > span:nth-child(2)").text().parseStatus()
|
||||
description = document.select(".summary p").text()
|
||||
thumbnail_url = document.select("div.series-image img").attr("abs:src")
|
||||
}
|
||||
}
|
||||
|
||||
// chapter list
|
||||
override fun chapterListSelector() = "div.chap"
|
||||
|
||||
override fun chapterFromElement(element: Element) = SChapter.create().apply {
|
||||
element.selectFirst("a")!!.let {
|
||||
setUrlWithoutDomain(it.absUrl("href"))
|
||||
name = it.text()
|
||||
}
|
||||
date_upload = parseDate(element.select("div > div > span:nth-child(2)").text())
|
||||
}
|
||||
|
||||
// page list
|
||||
override fun pageListParse(document: Document) =
|
||||
document.select(".swiper-slide img").mapIndexed { index, img ->
|
||||
Page(index, imageUrl = img.imgAttr())
|
||||
}
|
||||
|
||||
override fun imageUrlParse(document: Document) = throw UnsupportedOperationException()
|
||||
|
||||
private val statusOngoing = listOf("ongoing", "devam ediyor")
|
||||
private val statusCompleted = listOf("complete", "tamamlandı", "bitti")
|
||||
|
||||
private fun String.parseStatus(): Int {
|
||||
return when (this.lowercase()) {
|
||||
in statusOngoing -> SManga.ONGOING
|
||||
in statusCompleted -> SManga.COMPLETED
|
||||
else -> SManga.UNKNOWN
|
||||
}
|
||||
}
|
||||
|
||||
private fun Element.imgAttr(): String = when {
|
||||
hasAttr("data-pagespeed-high-res-src") -> dataImageAsUrl("data-pagespeed-high-res-src")
|
||||
hasAttr("data-pagespeed-lazy-src") -> dataImageAsUrl("data-pagespeed-lazy-src")
|
||||
else -> dataImageAsUrl("src")
|
||||
}
|
||||
|
||||
private fun parseDate(dateStr: String) =
|
||||
runCatching { dateFormat.parse(dateStr)!!.time }
|
||||
.getOrDefault(0L)
|
||||
|
||||
companion object {
|
||||
const val SLUG_SEARCH_PREFIX = "slug:"
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package eu.kanade.tachiyomi.extension.tr.sadscans
|
||||
package eu.kanade.tachiyomi.multisrc.po2scans
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.ActivityNotFoundException
|
||||
@ -7,7 +7,7 @@ import android.os.Bundle
|
||||
import android.util.Log
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
class SadscansUrlActivity : Activity() {
|
||||
class PO2ScansUrlActivity : Activity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
val pathSegments = intent?.data?.pathSegments
|
||||
@ -15,17 +15,17 @@ class SadscansUrlActivity : Activity() {
|
||||
val slug = pathSegments[1]
|
||||
val mainIntent = Intent().apply {
|
||||
action = "eu.kanade.tachiyomi.SEARCH"
|
||||
putExtra("query", "${Sadscans.SLUG_SEARCH_PREFIX}$slug")
|
||||
putExtra("query", "${PO2Scans.SLUG_SEARCH_PREFIX}$slug")
|
||||
putExtra("filter", packageName)
|
||||
}
|
||||
|
||||
try {
|
||||
startActivity(mainIntent)
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
Log.e("SadscansUrlActivity", "Could not start activity", e)
|
||||
Log.e("PO2ScansUrlActivity", "Could not start activity", e)
|
||||
}
|
||||
} else {
|
||||
Log.e("SadscansUrlActivity", "could not parse URI from intent $intent")
|
||||
Log.e("PO2ScansUrlActivity", "could not parse URI from intent $intent")
|
||||
}
|
||||
|
||||
finish()
|
@ -1,7 +1,7 @@
|
||||
ext {
|
||||
extName = 'Akuma'
|
||||
extClass = '.AkumaFactory'
|
||||
extVersionCode = 5
|
||||
extVersionCode = 4
|
||||
isNsfw = true
|
||||
}
|
||||
|
||||
|
@ -1,13 +1,8 @@
|
||||
package eu.kanade.tachiyomi.extension.all.akuma
|
||||
|
||||
import android.app.Application
|
||||
import android.content.SharedPreferences
|
||||
import androidx.preference.PreferenceScreen
|
||||
import androidx.preference.SwitchPreferenceCompat
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.network.POST
|
||||
import eu.kanade.tachiyomi.network.interceptor.rateLimit
|
||||
import eu.kanade.tachiyomi.source.ConfigurableSource
|
||||
import eu.kanade.tachiyomi.source.model.FilterList
|
||||
import eu.kanade.tachiyomi.source.model.MangasPage
|
||||
import eu.kanade.tachiyomi.source.model.Page
|
||||
@ -25,8 +20,6 @@ import okhttp3.Response
|
||||
import org.jsoup.nodes.Document
|
||||
import org.jsoup.nodes.Element
|
||||
import rx.Observable
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.io.IOException
|
||||
import java.text.ParseException
|
||||
import java.text.SimpleDateFormat
|
||||
@ -36,7 +29,7 @@ import java.util.TimeZone
|
||||
class Akuma(
|
||||
override val lang: String,
|
||||
private val akumaLang: String,
|
||||
) : ConfigurableSource, ParsedHttpSource() {
|
||||
) : ParsedHttpSource() {
|
||||
|
||||
override val name = "Akuma"
|
||||
|
||||
@ -112,23 +105,6 @@ class Akuma(
|
||||
return storedToken!!
|
||||
}
|
||||
|
||||
private val preferences: SharedPreferences by lazy {
|
||||
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
||||
}
|
||||
|
||||
private val displayFullTitle: Boolean get() = preferences.getBoolean(PREF_TITLE, false)
|
||||
|
||||
private val shortenTitleRegex = Regex("""(\[[^]]*]|[({][^)}]*[)}])""")
|
||||
private fun String.shortenTitle() = this.replace(shortenTitleRegex, "").trim()
|
||||
|
||||
override fun setupPreferenceScreen(screen: PreferenceScreen) {
|
||||
SwitchPreferenceCompat(screen.context).apply {
|
||||
key = PREF_TITLE
|
||||
title = "Display manga title as full title"
|
||||
setDefaultValue(false)
|
||||
}.also(screen::addPreference)
|
||||
}
|
||||
|
||||
override fun popularMangaRequest(page: Int): Request {
|
||||
val payload = FormBody.Builder()
|
||||
.add("view", "3")
|
||||
@ -173,9 +149,7 @@ class Akuma(
|
||||
override fun popularMangaFromElement(element: Element): SManga {
|
||||
return SManga.create().apply {
|
||||
setUrlWithoutDomain(element.select("a").attr("href"))
|
||||
title = element.select(".overlay-title").text().replace("\"", "").let {
|
||||
if (displayFullTitle) it.trim() else it.shortenTitle()
|
||||
}
|
||||
title = element.select(".overlay-title").text()
|
||||
thumbnail_url = element.select("img").attr("abs:src")
|
||||
}
|
||||
}
|
||||
@ -246,9 +220,7 @@ class Akuma(
|
||||
|
||||
override fun mangaDetailsParse(document: Document) = with(document) {
|
||||
SManga.create().apply {
|
||||
title = select(".entry-title").text().replace("\"", "").let {
|
||||
if (displayFullTitle) it.trim() else it.shortenTitle()
|
||||
}
|
||||
title = select(".entry-title").text()
|
||||
thumbnail_url = select(".img-thumbnail").attr("abs:src")
|
||||
|
||||
author = select(".group~.value").eachText().joinToString()
|
||||
@ -330,7 +302,6 @@ class Akuma(
|
||||
|
||||
companion object {
|
||||
const val PREFIX_ID = "id:"
|
||||
private const val PREF_TITLE = "pref_title"
|
||||
}
|
||||
|
||||
override fun latestUpdatesRequest(page: Int) = throw UnsupportedOperationException()
|
||||
|
@ -1,7 +0,0 @@
|
||||
ext {
|
||||
extName = 'ComicsKingdom'
|
||||
extClass = '.ComicsKingdomFactory'
|
||||
extVersionCode = 1
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
Before Width: | Height: | Size: 7.1 KiB |
Before Width: | Height: | Size: 3.5 KiB |
Before Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 29 KiB |
@ -1,44 +0,0 @@
|
||||
package eu.kanade.tachiyomi.extension.all.comicskingdom
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
class Chapter(
|
||||
val id: Int,
|
||||
val date: String,
|
||||
val assets: Assets?,
|
||||
val link: String,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
class Assets(
|
||||
val single: AssetData,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
class AssetData(
|
||||
val url: String,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
class Manga(
|
||||
val id: Int,
|
||||
val link: String,
|
||||
val title: Rendered,
|
||||
val content: Rendered,
|
||||
val meta: MangaMeta,
|
||||
val yoast_head: String,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
class MangaMeta(
|
||||
val ck_byline_on_app: String,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
class Rendered(
|
||||
val rendered: String,
|
||||
)
|
||||
|
||||
val ChapterFields = Chapter.javaClass.fields.joinToString(",") { it.name }
|
||||
val MangaFields = Manga.javaClass.fields.joinToString(",") { it.name }
|
@ -1,311 +0,0 @@
|
||||
package eu.kanade.tachiyomi.extension.all.comicskingdom
|
||||
|
||||
import android.app.Application
|
||||
import android.content.SharedPreferences
|
||||
import androidx.preference.PreferenceScreen
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
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
|
||||
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.HttpUrl
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import org.jsoup.Jsoup
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Locale
|
||||
import kotlin.math.ceil
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
class ComicsKingdom(override val lang: String) : ConfigurableSource, HttpSource() {
|
||||
|
||||
override val name = "Comics Kingdom"
|
||||
override val baseUrl = "https://wp.comicskingdom.com"
|
||||
override val supportsLatest = true
|
||||
|
||||
private val json: Json by injectLazy()
|
||||
private val dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.getDefault())
|
||||
private val compactChapterCountRegex = Regex("\"totalItems\":(\\d+)")
|
||||
private val thumbnailUrlRegex = Regex("thumbnailUrl\":\"(\\S+)\",\"dateP")
|
||||
|
||||
private val mangaPerPage = 20
|
||||
private val chapterPerPage = 100
|
||||
|
||||
private fun mangaApiUrl(): HttpUrl.Builder =
|
||||
baseUrl.toHttpUrl().newBuilder().apply {
|
||||
addPathSegments("wp-json/wp/v2")
|
||||
addPathSegment("ck_feature")
|
||||
addQueryParameter("per_page", mangaPerPage.toString())
|
||||
addQueryParameter("_fields", MangaFields)
|
||||
addQueryParameter("ck_language", if (lang == "es") "spanish" else "english")
|
||||
}
|
||||
|
||||
private fun chapterApiUrl(): HttpUrl.Builder = baseUrl.toHttpUrl().newBuilder().apply {
|
||||
addPathSegments("wp-json/wp/v2")
|
||||
addPathSegment("ck_comic")
|
||||
addQueryParameter("per_page", chapterPerPage.toString())
|
||||
addQueryParameter("_fields", ChapterFields)
|
||||
}
|
||||
|
||||
private fun getReq(orderBy: String, page: Int): Request = GET(
|
||||
mangaApiUrl().apply {
|
||||
addQueryParameter("orderBy", orderBy)
|
||||
addQueryParameter("page", page.toString())
|
||||
}.build(),
|
||||
headers,
|
||||
)
|
||||
|
||||
override fun popularMangaRequest(page: Int): Request = getReq("relevance", page)
|
||||
override fun latestUpdatesRequest(page: Int): Request = getReq("modified", page)
|
||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request =
|
||||
GET(
|
||||
mangaApiUrl().apply {
|
||||
addQueryParameter("search", query)
|
||||
addQueryParameter("page", page.toString())
|
||||
|
||||
if (!filters.isEmpty()) {
|
||||
for (filter in filters) {
|
||||
when (filter) {
|
||||
is OrderFilter -> {
|
||||
addQueryParameter("orderby", filter.getValue())
|
||||
}
|
||||
|
||||
is GenreList -> {
|
||||
if (filter.included.isNotEmpty()) {
|
||||
addQueryParameter(
|
||||
"ck_genre",
|
||||
filter.included.joinToString(","),
|
||||
)
|
||||
}
|
||||
if (filter.excluded.isNotEmpty()) {
|
||||
addQueryParameter(
|
||||
"ck_genre_exclude",
|
||||
filter.excluded.joinToString(","),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}.build(),
|
||||
headers,
|
||||
)
|
||||
|
||||
override fun searchMangaParse(response: Response): MangasPage {
|
||||
val list = json.decodeFromString<List<Manga>>(response.body.string())
|
||||
return MangasPage(
|
||||
list.map {
|
||||
SManga.create().apply {
|
||||
thumbnail_url = thumbnailUrlRegex.find(it.yoast_head)?.groupValues?.get(1)
|
||||
setUrlWithoutDomain(
|
||||
mangaApiUrl().apply {
|
||||
addPathSegment(it.id.toString())
|
||||
addQueryParameter("slug", it.link.toHttpUrl().pathSegments.last())
|
||||
}
|
||||
.build().toString(),
|
||||
)
|
||||
title = it.title.rendered
|
||||
}
|
||||
},
|
||||
list.count() == mangaPerPage,
|
||||
)
|
||||
}
|
||||
|
||||
override fun popularMangaParse(response: Response): MangasPage = searchMangaParse(response)
|
||||
override fun latestUpdatesParse(response: Response): MangasPage = searchMangaParse(response)
|
||||
|
||||
override fun mangaDetailsParse(response: Response): SManga = SManga.create().apply {
|
||||
val mangaData = json.decodeFromString<Manga>(response.body.string())
|
||||
title = mangaData.title.rendered
|
||||
author = mangaData.meta.ck_byline_on_app.substringAfter("By").trim()
|
||||
description = Jsoup.parse(mangaData.content.rendered).text()
|
||||
status = SManga.UNKNOWN
|
||||
thumbnail_url = thumbnailUrlRegex.find(mangaData.yoast_head)?.groupValues?.get(1)
|
||||
}
|
||||
|
||||
override fun getMangaUrl(manga: SManga): String =
|
||||
"$baseUrl/${(baseUrl + manga.url).toHttpUrl().queryParameter("slug")}"
|
||||
|
||||
override fun chapterListParse(response: Response): List<SChapter> {
|
||||
val mangaData = json.decodeFromString<Manga>(response.body.string())
|
||||
val mangaName = mangaData.link.toHttpUrl().pathSegments.last()
|
||||
|
||||
if (shouldCompact()) {
|
||||
val res = client.newCall(GET(mangaData.link)).execute()
|
||||
val postCount = compactChapterCountRegex.findAll(res.body.string())
|
||||
.find { result -> result.groupValues[1].toDouble() > 0 }!!.groupValues[1].toDouble()
|
||||
res.close()
|
||||
val maxPage = ceil(postCount / chapterPerPage)
|
||||
return List(maxPage.roundToInt()) { idx ->
|
||||
SChapter.create().apply {
|
||||
chapter_number = idx * 0.01F
|
||||
name =
|
||||
"${idx * chapterPerPage + 1}-${if (postCount - (idx + 1) * chapterPerPage < 0) postCount.toInt() else (idx + 1) * chapterPerPage}"
|
||||
setUrlWithoutDomain(
|
||||
chapterApiUrl().apply {
|
||||
addQueryParameter("orderBy", "date")
|
||||
addQueryParameter("order", "asc")
|
||||
addQueryParameter("ck_feature", mangaName)
|
||||
addQueryParameter("page", (idx + 1).toString())
|
||||
}.build().toString(),
|
||||
)
|
||||
}
|
||||
}.reversed()
|
||||
}
|
||||
|
||||
val chapters = mutableListOf<SChapter>()
|
||||
var pageNum = 1
|
||||
|
||||
var chapterData = getChapterList(mangaName, pageNum)
|
||||
var chapterNum = 0.0F
|
||||
|
||||
while (chapterData != null) {
|
||||
val list = chapterData.map {
|
||||
chapterNum += 0.01F
|
||||
SChapter.create().apply {
|
||||
chapter_number = chapterNum
|
||||
setUrlWithoutDomain(
|
||||
chapterApiUrl().apply {
|
||||
addPathSegment(it.id.toString())
|
||||
addQueryParameter("slug", it.link.substringAfter(baseUrl))
|
||||
}
|
||||
.toString(),
|
||||
)
|
||||
date_upload = dateFormat.parse(it.date).time
|
||||
name = it.date.substringBefore("T")
|
||||
}
|
||||
}
|
||||
chapters.addAll(list)
|
||||
|
||||
if (list.count() < 100) {
|
||||
break
|
||||
}
|
||||
|
||||
pageNum++
|
||||
try {
|
||||
chapterData = getChapterList(mangaName, pageNum)
|
||||
} catch (exception: Exception) {
|
||||
if (chapters.isNotEmpty()) {
|
||||
return chapters
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return chapters
|
||||
}
|
||||
|
||||
private fun getChapterList(mangaName: String, page: Int): List<Chapter> {
|
||||
val url = chapterApiUrl().apply {
|
||||
addQueryParameter("order", "desc")
|
||||
addQueryParameter("ck_feature", mangaName)
|
||||
addQueryParameter("page", page.toString())
|
||||
}.build()
|
||||
|
||||
val call = client.newCall(GET(url, headers)).execute()
|
||||
val body = call.body.string()
|
||||
call.close()
|
||||
return json.decodeFromString<List<Chapter>>(body)
|
||||
}
|
||||
|
||||
override fun getChapterUrl(chapter: SChapter): String {
|
||||
if (shouldCompact()) {
|
||||
return "$baseUrl/${(baseUrl + chapter.url).toHttpUrl().queryParameter("ck_feature")}"
|
||||
}
|
||||
return "$baseUrl/${(baseUrl + chapter.url).toHttpUrl().queryParameter("slug")}"
|
||||
}
|
||||
|
||||
override fun pageListParse(response: Response): List<Page> {
|
||||
if (shouldCompact()) {
|
||||
return json.decodeFromString<List<Chapter>>(response.body.string())
|
||||
.mapIndexed { idx, chapter ->
|
||||
Page(idx, imageUrl = chapter.assets!!.single.url)
|
||||
}
|
||||
}
|
||||
val chapter = json.decodeFromString<Chapter>(response.body.string())
|
||||
return listOf(Page(0, imageUrl = chapter.assets!!.single.url))
|
||||
}
|
||||
|
||||
private class OrderFilter :
|
||||
Filter.Select<String>(
|
||||
"Order by",
|
||||
arrayOf(
|
||||
"author",
|
||||
"date",
|
||||
"id",
|
||||
"include",
|
||||
"modified",
|
||||
"parent",
|
||||
"relevance",
|
||||
"title",
|
||||
"rand",
|
||||
),
|
||||
) {
|
||||
fun getValue(): String = values[state]
|
||||
}
|
||||
|
||||
private class Genre(name: String, val gid: String) : Filter.TriState(name)
|
||||
private class GenreList(genres: List<Genre>) : Filter.Group<Genre>("Genres", genres) {
|
||||
val included: List<String>
|
||||
get() = state.filter { it.isIncluded() }.map { it.gid }
|
||||
|
||||
val excluded: List<String>
|
||||
get() = state.filter { it.isExcluded() }.map { it.gid }
|
||||
}
|
||||
|
||||
override fun getFilterList() = FilterList(
|
||||
OrderFilter(),
|
||||
GenreList(getGenreList()),
|
||||
)
|
||||
|
||||
private fun getGenreList() = listOf(
|
||||
Genre("Action", "action"),
|
||||
Genre("Adventure", "adventure"),
|
||||
Genre("Classic", "classic"),
|
||||
Genre("Comedy", "comedy"),
|
||||
Genre("Crime", "crime"),
|
||||
Genre("Fantasy", "fantasy"),
|
||||
Genre("Gag Cartoons", "gag-cartoons"),
|
||||
Genre("Mystery", "mystery"),
|
||||
Genre("New Arrivals", "new-arrivals"),
|
||||
Genre("Non-Fiction", "non-fiction"),
|
||||
Genre("OffBeat", "offbeat"),
|
||||
Genre("Political Cartoons", "political-cartoons"),
|
||||
Genre("Romance", "romance"),
|
||||
Genre("Sci-Fi", "sci-fi"),
|
||||
Genre("Slice Of Life", "slice-of-life"),
|
||||
Genre("Superhero", "superhero"),
|
||||
Genre("Vintage", "vintage"),
|
||||
)
|
||||
|
||||
override fun setupPreferenceScreen(screen: PreferenceScreen) {
|
||||
val compactpref = androidx.preference.CheckBoxPreference(screen.context).apply {
|
||||
key = "compactPref"
|
||||
title = "Compact chapters"
|
||||
summary =
|
||||
"Unchecking this will make each daily/weekly upload into a chapter which can be very slow because some comics have 8000+ uploads"
|
||||
isChecked = true
|
||||
}
|
||||
|
||||
screen.addPreference(compactpref)
|
||||
}
|
||||
|
||||
private val preferences: SharedPreferences by lazy {
|
||||
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
||||
}
|
||||
|
||||
private fun shouldCompact() = preferences.getBoolean("compactPref", true)
|
||||
|
||||
override fun imageUrlParse(response: Response): String = throw UnsupportedOperationException()
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
package eu.kanade.tachiyomi.extension.all.comicskingdom
|
||||
|
||||
import eu.kanade.tachiyomi.source.Source
|
||||
import eu.kanade.tachiyomi.source.SourceFactory
|
||||
|
||||
class ComicsKingdomFactory : SourceFactory {
|
||||
override fun createSources(): List<Source> = listOf(ComicsKingdom("en"), ComicsKingdom("es"))
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
package eu.kanade.tachiyomi.extension.all.comicsvalley
|
||||
|
||||
import eu.kanade.tachiyomi.multisrc.madara.Madara
|
||||
|
||||
class ComicsValley : Madara(
|
||||
"Comics Valley",
|
||||
"https://comicsvalley.com",
|
||||
"all",
|
||||
) {
|
||||
override val mangaSubString = "comics-new"
|
||||
override val useNewChapterEndpoint = true
|
||||
override val useLoadMoreRequest = LoadMoreStrategy.Always
|
||||
override val id = 1103204227230640533
|
||||
}
|
Before Width: | Height: | Size: 4.1 KiB |
Before Width: | Height: | Size: 2.4 KiB |
Before Width: | Height: | Size: 5.8 KiB |
Before Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 16 KiB |
@ -1,12 +0,0 @@
|
||||
package eu.kanade.tachiyomi.extension.all.eromanhwa
|
||||
|
||||
import eu.kanade.tachiyomi.multisrc.madara.Madara
|
||||
|
||||
class Eromanhwa : Madara(
|
||||
"Eromanhwa",
|
||||
"https://eromanhwa.org",
|
||||
"all",
|
||||
) {
|
||||
override val id = 3597355706480775153 // accidently set lang to en...
|
||||
override val useNewChapterEndpoint = true
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
ext {
|
||||
extName = 'Hitomi'
|
||||
extClass = '.HitomiFactory'
|
||||
extVersionCode = 33
|
||||
extVersionCode = 32
|
||||
isNsfw = true
|
||||
}
|
||||
|
||||
|
@ -517,9 +517,6 @@ class Hitomi(
|
||||
"https://${subDomain}tn.$domain/webpbigtn/${thumbPathFromHash(hash)}/$hash.webp"
|
||||
}
|
||||
description = buildString {
|
||||
japaneseTitle?.let {
|
||||
append("Japanese title: ", it, "\n")
|
||||
}
|
||||
parodys?.joinToString { it.formatted }?.let {
|
||||
append("Series: ", it, "\n")
|
||||
}
|
||||
|
@ -7,7 +7,6 @@ import kotlinx.serialization.json.JsonPrimitive
|
||||
class Gallery(
|
||||
val galleryurl: String,
|
||||
val title: String,
|
||||
val japaneseTitle: String?,
|
||||
val date: String,
|
||||
val type: String?,
|
||||
val language: String?,
|
||||
|
Before Width: | Height: | Size: 3.3 KiB |
Before Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 4.8 KiB |
Before Width: | Height: | Size: 7.7 KiB |
Before Width: | Height: | Size: 12 KiB |
@ -1,41 +0,0 @@
|
||||
package eu.kanade.tachiyomi.extension.all.kdtscans
|
||||
|
||||
import eu.kanade.tachiyomi.multisrc.madara.Madara
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import org.jsoup.nodes.Document
|
||||
import org.jsoup.nodes.Element
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Locale
|
||||
|
||||
class KdtScans : Madara(
|
||||
"KDT Scans",
|
||||
"https://kdtscans.com",
|
||||
"all",
|
||||
dateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale("es")),
|
||||
) {
|
||||
override val useNewChapterEndpoint = true
|
||||
override val fetchGenres = false
|
||||
|
||||
override fun popularMangaFromElement(element: Element): SManga {
|
||||
return super.popularMangaFromElement(element).apply {
|
||||
title = title.cleanupTitle()
|
||||
}
|
||||
}
|
||||
|
||||
override fun searchMangaFromElement(element: Element): SManga {
|
||||
return super.searchMangaFromElement(element).apply {
|
||||
title = title.cleanupTitle()
|
||||
}
|
||||
}
|
||||
|
||||
override fun mangaDetailsParse(document: Document): SManga {
|
||||
return super.mangaDetailsParse(document).apply {
|
||||
title = title.cleanupTitle()
|
||||
}
|
||||
}
|
||||
|
||||
private fun String.cleanupTitle() = replace(titleCleanupRegex, "").trim()
|
||||
|
||||
private val titleCleanupRegex =
|
||||
Regex("""^\[(ESPAÑOL|English)\]\s+(–\s+)?""", RegexOption.IGNORE_CASE)
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
ext {
|
||||
extName = 'LANraragi'
|
||||
extClass = '.LANraragiFactory'
|
||||
extVersionCode = 18
|
||||
extVersionCode = 17
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
||||
|
@ -7,7 +7,6 @@ data class Archive(
|
||||
val arcid: String,
|
||||
val isnew: String,
|
||||
val tags: String?,
|
||||
val summary: String?,
|
||||
val title: String,
|
||||
)
|
||||
|
||||
@ -26,6 +25,7 @@ data class ArchiveSearchResult(
|
||||
@Serializable
|
||||
data class Category(
|
||||
val id: String,
|
||||
val last_used: String,
|
||||
val name: String,
|
||||
val pinned: String,
|
||||
)
|
||||
|
@ -242,7 +242,7 @@ open class LANraragi(private val suffix: String = "") : ConfigurableSource, Unme
|
||||
private fun archiveToSManga(archive: Archive) = SManga.create().apply {
|
||||
url = "/reader?id=${archive.arcid}"
|
||||
title = archive.title
|
||||
description = if (archive.summary.isNullOrBlank()) archive.title else archive.summary
|
||||
description = archive.title
|
||||
thumbnail_url = getThumbnailUri(archive.arcid)
|
||||
genre = archive.tags?.replace(",", ", ")
|
||||
artist = getArtist(archive.tags)
|
||||
@ -406,10 +406,12 @@ open class LANraragi(private val suffix: String = "") : ConfigurableSource, Unme
|
||||
|
||||
private fun getCategoryPairs(categories: List<Category>): Array<Pair<String?, String>> {
|
||||
// Empty pair to disable. Sort by pinned status then name for convenience.
|
||||
// Web client sort is pinned > last_used but reflects between page changes.
|
||||
|
||||
val pin = "\uD83D\uDCCC "
|
||||
|
||||
// Maintain categories sync for next FilterList reset.
|
||||
// Maintain categories sync for next FilterList reset. If there's demand for it, it's now
|
||||
// possible to sort by last_used similar to the web client. Maybe an option toggle?
|
||||
getCategories()
|
||||
|
||||
return listOf(Pair("", ""))
|
||||
|
@ -1,7 +1,7 @@
|
||||
ext {
|
||||
extName = 'MangaDex'
|
||||
extClass = '.MangaDexFactory'
|
||||
extVersionCode = 194
|
||||
extVersionCode = 193
|
||||
isNsfw = true
|
||||
}
|
||||
|
||||
|
@ -138,6 +138,13 @@ object MDConstants {
|
||||
return "${altTitlesInDescPref}_$dexLang"
|
||||
}
|
||||
|
||||
private const val customUserAgentPref = "customUserAgent"
|
||||
fun getCustomUserAgentPrefKey(dexLang: String): String {
|
||||
return "${customUserAgentPref}_$dexLang"
|
||||
}
|
||||
|
||||
val defaultUserAgent = "Tachiyomi " + System.getProperty("http.agent")
|
||||
|
||||
private const val tagGroupContent = "content"
|
||||
private const val tagGroupFormat = "format"
|
||||
private const val tagGroupGenre = "genre"
|
||||
|
@ -3,6 +3,7 @@ package eu.kanade.tachiyomi.extension.all.mangadex
|
||||
import android.app.Application
|
||||
import android.content.SharedPreferences
|
||||
import android.os.Build
|
||||
import android.widget.Toast
|
||||
import androidx.preference.EditTextPreference
|
||||
import androidx.preference.ListPreference
|
||||
import androidx.preference.MultiSelectListPreference
|
||||
@ -55,7 +56,6 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St
|
||||
|
||||
private val preferences: SharedPreferences by lazy {
|
||||
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
||||
.sanitizeExistingUuidPrefs()
|
||||
}
|
||||
|
||||
private val helper = MangaDexHelper(lang)
|
||||
@ -67,7 +67,6 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St
|
||||
"Keiyoushi"
|
||||
|
||||
val builder = super.headersBuilder().apply {
|
||||
set("User-Agent", "Tachiyomi " + System.getProperty("http.agent"))
|
||||
set("Referer", "$baseUrl/")
|
||||
set("Origin", baseUrl)
|
||||
set("Extra", extraHeader)
|
||||
@ -79,8 +78,13 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St
|
||||
override val client = network.client.newBuilder()
|
||||
.rateLimit(3)
|
||||
.addInterceptor(MdAtHomeReportInterceptor(network.client, headers))
|
||||
.addInterceptor(MdUserAgentInterceptor(preferences, dexLang))
|
||||
.build()
|
||||
|
||||
init {
|
||||
preferences.sanitizeExistingUuidPrefs()
|
||||
}
|
||||
|
||||
// Popular manga section
|
||||
|
||||
override fun popularMangaRequest(page: Int): Request {
|
||||
@ -391,7 +395,7 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St
|
||||
// Manga Details section
|
||||
|
||||
override fun getMangaUrl(manga: SManga): String {
|
||||
return baseUrl + manga.url.replace("/manga/", "/title/") + "/" + helper.titleToSlug(manga.title)
|
||||
return baseUrl + manga.url + "/" + helper.titleToSlug(manga.title)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -757,6 +761,30 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St
|
||||
}
|
||||
}
|
||||
|
||||
val userAgentPref = EditTextPreference(screen.context).apply {
|
||||
key = MDConstants.getCustomUserAgentPrefKey(dexLang)
|
||||
title = helper.intl["set_custom_useragent"]
|
||||
summary = helper.intl["set_custom_useragent_summary"]
|
||||
dialogMessage = helper.intl.format(
|
||||
"set_custom_useragent_dialog",
|
||||
MDConstants.defaultUserAgent,
|
||||
)
|
||||
|
||||
setDefaultValue(MDConstants.defaultUserAgent)
|
||||
|
||||
setOnPreferenceChangeListener { _, newValue ->
|
||||
try {
|
||||
Headers.Builder().add("User-Agent", newValue as String)
|
||||
summary = newValue
|
||||
true
|
||||
} catch (e: Throwable) {
|
||||
val errorMessage = helper.intl.format("set_custom_useragent_error_invalid", e.message)
|
||||
Toast.makeText(screen.context, errorMessage, Toast.LENGTH_LONG).show()
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
screen.addPreference(coverQualityPref)
|
||||
screen.addPreference(tryUsingFirstVolumeCoverPref)
|
||||
screen.addPreference(dataSaverPref)
|
||||
@ -766,6 +794,7 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St
|
||||
screen.addPreference(originalLanguagePref)
|
||||
screen.addPreference(blockedGroupsPref)
|
||||
screen.addPreference(blockedUploaderPref)
|
||||
screen.addPreference(userAgentPref)
|
||||
}
|
||||
|
||||
override fun getFilterList(): FilterList =
|
||||
@ -840,14 +869,20 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St
|
||||
private val SharedPreferences.altTitlesInDesc
|
||||
get() = getBoolean(MDConstants.getAltTitlesInDescPrefKey(dexLang), false)
|
||||
|
||||
private val SharedPreferences.customUserAgent
|
||||
get() = getString(
|
||||
MDConstants.getCustomUserAgentPrefKey(dexLang),
|
||||
MDConstants.defaultUserAgent,
|
||||
)
|
||||
|
||||
/**
|
||||
* Previous versions of the extension allowed invalid UUID values to be stored in the
|
||||
* preferences. This method clear invalid UUIDs in case the user have updated from
|
||||
* a previous version with that behaviour.
|
||||
*/
|
||||
private fun SharedPreferences.sanitizeExistingUuidPrefs(): SharedPreferences {
|
||||
private fun SharedPreferences.sanitizeExistingUuidPrefs() {
|
||||
if (getBoolean(MDConstants.getHasSanitizedUuidsPrefKey(dexLang), false)) {
|
||||
return this
|
||||
return
|
||||
}
|
||||
|
||||
val blockedGroups = getString(MDConstants.getBlockedGroupsPrefKey(dexLang), "")!!
|
||||
@ -867,7 +902,5 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St
|
||||
.putString(MDConstants.getBlockedUploaderPrefKey(dexLang), blockedUploaders)
|
||||
.putBoolean(MDConstants.getHasSanitizedUuidsPrefKey(dexLang), true)
|
||||
.apply()
|
||||
|
||||
return this
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,39 @@
|
||||
package eu.kanade.tachiyomi.extension.all.mangadex
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.Response
|
||||
|
||||
/**
|
||||
* Interceptor to set custom useragent for MangaDex
|
||||
*/
|
||||
class MdUserAgentInterceptor(
|
||||
private val preferences: SharedPreferences,
|
||||
private val dexLang: String,
|
||||
) : Interceptor {
|
||||
|
||||
private val SharedPreferences.customUserAgent
|
||||
get() = getString(
|
||||
MDConstants.getCustomUserAgentPrefKey(dexLang),
|
||||
MDConstants.defaultUserAgent,
|
||||
)
|
||||
|
||||
override fun intercept(chain: Interceptor.Chain): Response {
|
||||
val originalRequest = chain.request()
|
||||
|
||||
val newUserAgent = preferences.customUserAgent
|
||||
?: return chain.proceed(originalRequest)
|
||||
|
||||
val originalHeaders = originalRequest.headers
|
||||
|
||||
val modifiedHeaders = originalHeaders.newBuilder()
|
||||
.set("User-Agent", newUserAgent)
|
||||
.build()
|
||||
|
||||
val modifiedRequest = originalRequest.newBuilder()
|
||||
.headers(modifiedHeaders)
|
||||
.build()
|
||||
|
||||
return chain.proceed(modifiedRequest)
|
||||
}
|
||||
}
|
@ -17,8 +17,6 @@ schedule_monthly=Monthly
|
||||
schedule_other=Other
|
||||
schedule_trimonthly=Trimonthly
|
||||
schedule_weekly=Weekly
|
||||
subtitle_only=Show subtitle only
|
||||
subtitle_only_summary=Removes the redundant chapter number from the chapter name.
|
||||
serialization=Serialization: %s
|
||||
split_double_pages=Split double pages
|
||||
split_double_pages_summary=Only a few titles supports disabling this setting.
|
||||
|
@ -1,7 +1,7 @@
|
||||
ext {
|
||||
extName = 'MANGA Plus by SHUEISHA'
|
||||
extClass = '.MangaPlusFactory'
|
||||
extVersionCode = 53
|
||||
extVersionCode = 52
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
||||
|
@ -289,10 +289,9 @@ class MangaPlus(
|
||||
}
|
||||
|
||||
val titleDetailView = result.success.titleDetailView!!
|
||||
val subtitleOnly = preferences.subtitleOnly()
|
||||
|
||||
return titleDetailView.chapterList
|
||||
.map { it.toSChapter(subtitleOnly) }
|
||||
.map(Chapter::toSChapter)
|
||||
.reversed()
|
||||
}
|
||||
|
||||
@ -308,8 +307,8 @@ class MangaPlus(
|
||||
private fun pageListRequest(chapterId: String): Request {
|
||||
val url = "$APP_API_URL/manga_viewer".toHttpUrl().newBuilder()
|
||||
.addQueryParameter("chapter_id", chapterId)
|
||||
.addQueryParameter("split", if (preferences.splitImages()) "yes" else "no")
|
||||
.addQueryParameter("img_quality", preferences.imageQuality())
|
||||
.addQueryParameter("split", if (preferences.splitImages) "yes" else "no")
|
||||
.addQueryParameter("img_quality", preferences.imageQuality)
|
||||
.addQueryParameter("ticket_reading", "no")
|
||||
.addQueryParameter("free_reading", "yes")
|
||||
.addQueryParameter("subscription_reading", "no")
|
||||
@ -379,16 +378,8 @@ class MangaPlus(
|
||||
key = "${VER_PREF_KEY}_$lang",
|
||||
)
|
||||
|
||||
val titlePref = SwitchPreferenceCompat(screen.context).apply {
|
||||
key = "${SUBTITLE_ONLY_KEY}_$lang"
|
||||
title = intl["subtitle_only"]
|
||||
summary = intl["subtitle_only_summary"]
|
||||
setDefaultValue(SUBTITLE_ONLY_DEFAULT_VALUE)
|
||||
}
|
||||
|
||||
screen.addPreference(qualityPref)
|
||||
screen.addPreference(splitPref)
|
||||
screen.addPreference(titlePref)
|
||||
}
|
||||
|
||||
private fun PreferenceScreen.addEditTextPreference(
|
||||
@ -503,11 +494,11 @@ class MangaPlus(
|
||||
json.decodeFromString(body.string())
|
||||
}
|
||||
|
||||
private fun SharedPreferences.imageQuality(): String = getString("${QUALITY_PREF_KEY}_$lang", QUALITY_PREF_DEFAULT_VALUE)!!
|
||||
private val SharedPreferences.imageQuality: String
|
||||
get() = getString("${QUALITY_PREF_KEY}_$lang", QUALITY_PREF_DEFAULT_VALUE)!!
|
||||
|
||||
private fun SharedPreferences.splitImages(): Boolean = getBoolean("${SPLIT_PREF_KEY}_$lang", SPLIT_PREF_DEFAULT_VALUE)
|
||||
|
||||
private fun SharedPreferences.subtitleOnly(): Boolean = getBoolean("${SUBTITLE_ONLY_KEY}_$lang", SUBTITLE_ONLY_DEFAULT_VALUE)
|
||||
private val SharedPreferences.splitImages: Boolean
|
||||
get() = getBoolean("${SPLIT_PREF_KEY}_$lang", SPLIT_PREF_DEFAULT_VALUE)
|
||||
|
||||
private val SharedPreferences.appVersion: String?
|
||||
get() = getString("${VER_PREF_KEY}_$lang", VER_PREF_DEFAULT_VALUE)
|
||||
@ -535,9 +526,6 @@ private val QUALITY_PREF_DEFAULT_VALUE = QUALITY_PREF_ENTRY_VALUES[2]
|
||||
private const val SPLIT_PREF_KEY = "splitImage"
|
||||
private const val SPLIT_PREF_DEFAULT_VALUE = true
|
||||
|
||||
private const val SUBTITLE_ONLY_KEY = "subtitleOnly"
|
||||
private const val SUBTITLE_ONLY_DEFAULT_VALUE = false
|
||||
|
||||
private const val VER_PREF_KEY = "appVer"
|
||||
private const val VER_PREF_DEFAULT_VALUE = ""
|
||||
private const val SECRET_PREF_KEY = "accountSecret"
|
||||
|
@ -322,16 +322,11 @@ class Chapter(
|
||||
val isExpired: Boolean
|
||||
get() = subTitle == null
|
||||
|
||||
fun toSChapter(subtitlePref: Boolean): SChapter = SChapter.create().apply {
|
||||
name = if (subtitlePref && subTitle != null) {
|
||||
subTitle
|
||||
} else {
|
||||
"${this@Chapter.name} - $subTitle"
|
||||
}
|
||||
fun toSChapter(): SChapter = SChapter.create().apply {
|
||||
name = "${this@Chapter.name} - $subTitle"
|
||||
date_upload = 1000L * startTimeStamp
|
||||
url = "#/viewer/$chapterId"
|
||||
chapter_number = this@Chapter.name.substringAfter("#").toFloatOrNull() ?: -1f
|
||||
scanlator = "MANGA Plus"
|
||||
}
|
||||
}
|
||||
|
||||
|
8
src/all/meituatop/build.gradle
Normal file
@ -0,0 +1,8 @@
|
||||
ext {
|
||||
extName = 'Meitua.top'
|
||||
extClass = '.MeituaTop'
|
||||
extVersionCode = 6
|
||||
isNsfw = true
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
BIN
src/all/meituatop/res/mipmap-hdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 2.2 KiB |
BIN
src/all/meituatop/res/mipmap-mdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
src/all/meituatop/res/mipmap-xhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 2.6 KiB |
BIN
src/all/meituatop/res/mipmap-xxhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 4.3 KiB |
BIN
src/all/meituatop/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 5.5 KiB |
@ -0,0 +1,111 @@
|
||||
package eu.kanade.tachiyomi.extension.all.meituatop
|
||||
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
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 eu.kanade.tachiyomi.util.asJsoup
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import org.jsoup.select.Evaluator
|
||||
import rx.Observable
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Locale
|
||||
|
||||
// Uses MACCMS http://www.maccms.la/
|
||||
class MeituaTop : HttpSource() {
|
||||
override val name = "Meitua.top"
|
||||
override val lang = "all"
|
||||
override val supportsLatest = false
|
||||
|
||||
override val baseUrl = "https://mt1.meitu1.sbs"
|
||||
|
||||
override fun popularMangaRequest(page: Int) = GET("$baseUrl/arttype/0b-$page.html", headers)
|
||||
|
||||
override fun popularMangaParse(response: Response): MangasPage {
|
||||
val document = response.asJsoup()
|
||||
val mangas = document.selectFirst(Evaluator.Class("thumbnail-group"))!!.children().map {
|
||||
SManga.create().apply {
|
||||
url = it.selectFirst(Evaluator.Tag("a"))!!.attr("href")
|
||||
val image = it.selectFirst(Evaluator.Tag("img"))!!
|
||||
title = image.attr("alt")
|
||||
thumbnail_url = image.attr("src")
|
||||
val info = it.selectFirst(Evaluator.Tag("p"))!!.ownText().split(" - ")
|
||||
genre = info[0]
|
||||
description = info[1]
|
||||
status = SManga.COMPLETED
|
||||
initialized = true
|
||||
}
|
||||
}
|
||||
val pageLinks = document.select(Evaluator.Class("page_link"))
|
||||
if (pageLinks.isEmpty()) return MangasPage(mangas, false)
|
||||
val lastPage = pageLinks[3].attr("href")
|
||||
val hasNextPage = document.location().pageNumber() != lastPage.pageNumber()
|
||||
return MangasPage(mangas, hasNextPage)
|
||||
}
|
||||
|
||||
override fun latestUpdatesRequest(page: Int) = throw UnsupportedOperationException()
|
||||
|
||||
override fun latestUpdatesParse(response: Response) = throw UnsupportedOperationException()
|
||||
|
||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||
if (query.isNotEmpty()) {
|
||||
val url = "$baseUrl/artsearch/-------.html".toHttpUrl().newBuilder()
|
||||
.addQueryParameter("wd", query)
|
||||
.addQueryParameter("page", page.toString())
|
||||
.toString()
|
||||
return GET(url, headers)
|
||||
}
|
||||
|
||||
val filter = filters.filterIsInstance<RegionFilter>().firstOrNull() ?: return popularMangaRequest(page)
|
||||
return GET("$baseUrl/arttype/${21 + filter.state}b-$page.html", headers)
|
||||
}
|
||||
|
||||
override fun searchMangaParse(response: Response) = popularMangaParse(response)
|
||||
|
||||
override fun fetchMangaDetails(manga: SManga): Observable<SManga> = Observable.just(manga)
|
||||
|
||||
override fun mangaDetailsParse(response: Response) = throw UnsupportedOperationException()
|
||||
|
||||
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
|
||||
val chapter = SChapter.create().apply {
|
||||
url = manga.url
|
||||
name = "Gallery"
|
||||
date_upload = dateFormat.parse(manga.description!!)!!.time
|
||||
chapter_number = -2f
|
||||
}
|
||||
return Observable.just(listOf(chapter))
|
||||
}
|
||||
|
||||
override fun chapterListParse(response: Response) = throw UnsupportedOperationException()
|
||||
|
||||
override fun pageListParse(response: Response): List<Page> {
|
||||
val document = response.asJsoup()
|
||||
val images = document.selectFirst(Evaluator.Class("ttnr"))!!.select(Evaluator.Tag("img"))
|
||||
.map { it.attr("src") }.distinct()
|
||||
return images.mapIndexed { index, imageUrl -> Page(index, imageUrl = imageUrl) }
|
||||
}
|
||||
|
||||
override fun imageUrlParse(response: Response) = throw UnsupportedOperationException()
|
||||
|
||||
override fun getFilterList() = FilterList(
|
||||
Filter.Header("Category (ignored for text search)"),
|
||||
RegionFilter(),
|
||||
)
|
||||
|
||||
private class RegionFilter : Filter.Select<String>(
|
||||
"Region",
|
||||
arrayOf("All", "国产美女", "韩国美女", "台湾美女", "日本美女", "欧美美女", "泰国美女"),
|
||||
)
|
||||
|
||||
private fun String.pageNumber() = numberRegex.findAll(this).last().value.toInt()
|
||||
|
||||
private val numberRegex by lazy { Regex("""\d+""") }
|
||||
|
||||
private val dateFormat by lazy { SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH) }
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
ext {
|
||||
extName = 'Mitaku'
|
||||
extClass = '.Mitaku'
|
||||
extVersionCode = 1
|
||||
isNsfw = true
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
Before Width: | Height: | Size: 7.2 KiB |
Before Width: | Height: | Size: 3.6 KiB |
Before Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 23 KiB |
Before Width: | Height: | Size: 37 KiB |
@ -1,171 +0,0 @@
|
||||
package eu.kanade.tachiyomi.extension.all.mitaku
|
||||
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.source.model.Filter
|
||||
import eu.kanade.tachiyomi.source.model.FilterList
|
||||
import eu.kanade.tachiyomi.source.model.Page
|
||||
import eu.kanade.tachiyomi.source.model.SChapter
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import eu.kanade.tachiyomi.source.model.UpdateStrategy
|
||||
import eu.kanade.tachiyomi.source.online.ParsedHttpSource
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import okhttp3.Request
|
||||
import org.jsoup.nodes.Document
|
||||
import org.jsoup.nodes.Element
|
||||
import rx.Observable
|
||||
|
||||
class Mitaku : ParsedHttpSource() {
|
||||
override val name = "Mitaku"
|
||||
|
||||
override val baseUrl = "https://mitaku.net"
|
||||
|
||||
override val lang = "all"
|
||||
|
||||
override val supportsLatest = false
|
||||
|
||||
// ============================== Popular ===============================
|
||||
override fun popularMangaRequest(page: Int) = GET("$baseUrl/category/ero-cosplay/page/$page", headers)
|
||||
|
||||
override fun popularMangaSelector() = "div.article-container article"
|
||||
|
||||
override fun popularMangaFromElement(element: Element) = SManga.create().apply {
|
||||
setUrlWithoutDomain(element.selectFirst("a")!!.absUrl("href"))
|
||||
|
||||
title = element.selectFirst("a")!!.attr("title")
|
||||
|
||||
thumbnail_url = element.selectFirst("img")?.absUrl("src")
|
||||
}
|
||||
|
||||
override fun popularMangaNextPageSelector() = "div.wp-pagenavi a.page.larger"
|
||||
|
||||
// =============================== Latest ===============================
|
||||
override fun latestUpdatesRequest(page: Int): Request {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
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 filterList = if (filters.isEmpty()) getFilterList() else filters
|
||||
val tagFilter = filterList.findInstance<TagFilter>()!!
|
||||
val categoryFilter = filterList.findInstance<CategoryFilter>()!!
|
||||
|
||||
return when {
|
||||
query.isEmpty() && categoryFilter.state != 0 -> {
|
||||
val url = "$baseUrl/category/${categoryFilter.toUriPart()}/page/$page/"
|
||||
GET(url, headers)
|
||||
}
|
||||
query.isEmpty() && tagFilter.state.isNotEmpty() -> {
|
||||
val url = baseUrl.toHttpUrl().newBuilder()
|
||||
.addPathSegment("tag")
|
||||
.addPathSegment(tagFilter.toUriPart())
|
||||
.addPathSegment("page")
|
||||
.addPathSegment(page.toString())
|
||||
.build()
|
||||
GET(url, headers)
|
||||
}
|
||||
query.isNotEmpty() -> {
|
||||
val url = "$baseUrl/page/$page/".toHttpUrl().newBuilder()
|
||||
.addQueryParameter("s", query)
|
||||
.build()
|
||||
GET(url, headers)
|
||||
}
|
||||
else -> latestUpdatesRequest(page)
|
||||
}
|
||||
}
|
||||
|
||||
override fun searchMangaSelector() = popularMangaSelector()
|
||||
|
||||
override fun searchMangaFromElement(element: Element) = popularMangaFromElement(element)
|
||||
|
||||
override fun searchMangaNextPageSelector() = popularMangaNextPageSelector()
|
||||
|
||||
override fun mangaDetailsParse(document: Document) = SManga.create().apply {
|
||||
status = SManga.COMPLETED
|
||||
update_strategy = UpdateStrategy.ONLY_FETCH_ONCE
|
||||
with(document.selectFirst("article")!!) {
|
||||
title = selectFirst("h1")!!.text()
|
||||
val catGenres = select("span.cat-links a").joinToString { it.text() }
|
||||
val tagGenres = select("span.tag-links a").joinToString { it.text() }
|
||||
genre = listOf(catGenres, tagGenres).filter { it.isNotEmpty() }.joinToString()
|
||||
}
|
||||
}
|
||||
|
||||
// ============================== Chapters ==============================
|
||||
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
|
||||
val chapter = SChapter.create().apply {
|
||||
url = manga.url
|
||||
chapter_number = 1F
|
||||
name = "Chapter"
|
||||
}
|
||||
|
||||
return Observable.just(listOf(chapter))
|
||||
}
|
||||
|
||||
override fun chapterListSelector(): String {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
override fun chapterFromElement(element: Element): SChapter {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
// =============================== Pages ================================
|
||||
|
||||
override fun pageListParse(document: Document): List<Page> {
|
||||
val imageElements = document.select("a.msacwl-img-link")
|
||||
|
||||
return imageElements.mapIndexed { index, element ->
|
||||
val imageUrl = element.absUrl("data-mfp-src")
|
||||
Page(index, imageUrl = imageUrl)
|
||||
}
|
||||
}
|
||||
|
||||
override fun imageUrlParse(document: Document): String {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
open class UriPartFilter(
|
||||
displayName: String,
|
||||
private val valuePair: Array<Pair<String, String>>,
|
||||
) : Filter.Select<String>(displayName, valuePair.map { it.first }.toTypedArray()) {
|
||||
fun toUriPart() = valuePair[state].second
|
||||
}
|
||||
|
||||
class CategoryFilter : UriPartFilter(
|
||||
"Category",
|
||||
arrayOf(
|
||||
Pair("Any", ""),
|
||||
Pair("Ero Cosplay", "/ero-cosplay"),
|
||||
Pair("Nude", "/nude"),
|
||||
Pair("Sexy Set", "/sexy-set"),
|
||||
Pair("Online Video", "/online-video"),
|
||||
),
|
||||
)
|
||||
override fun getFilterList(): FilterList = FilterList(
|
||||
Filter.Header("NOTE: Only one tag search"),
|
||||
Filter.Separator(),
|
||||
CategoryFilter(),
|
||||
TagFilter(),
|
||||
)
|
||||
|
||||
class TagFilter : Filter.Text("Tag") {
|
||||
fun toUriPart(): String {
|
||||
return state.trim().lowercase().replace(" ", "-")
|
||||
}
|
||||
}
|
||||
|
||||
private inline fun <reified T> Iterable<*>.findInstance() = find { it is T } as? T
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
ext {
|
||||
extName = 'NHentai'
|
||||
extClass = '.NHFactory'
|
||||
extVersionCode = 46
|
||||
extVersionCode = 41
|
||||
isNsfw = true
|
||||
}
|
||||
|
||||
|
@ -136,51 +136,57 @@ open class NHentai(
|
||||
}
|
||||
|
||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||
val fixedQuery = query.ifEmpty { "\"\"" }
|
||||
val filterList = if (filters.isEmpty()) getFilterList() else filters
|
||||
val nhLangSearch = if (nhLang.isBlank()) "" else "language:$nhLang "
|
||||
val nhLangSearch = if (nhLang.isBlank()) "" else "+$nhLang "
|
||||
val advQuery = combineQuery(filterList)
|
||||
val favoriteFilter = filterList.findInstance<FavoriteFilter>()
|
||||
val isOkayToSort = filterList.findInstance<UploadedFilter>()?.state?.isBlank() ?: true
|
||||
val offsetPage =
|
||||
filterList.findInstance<OffsetPageFilter>()?.state?.toIntOrNull()?.plus(page) ?: page
|
||||
|
||||
if (favoriteFilter?.state == true) {
|
||||
val url = "$baseUrl/favorites/".toHttpUrl().newBuilder()
|
||||
.addQueryParameter("q", "$query $advQuery")
|
||||
val url = "$baseUrl/favorites".toHttpUrl().newBuilder()
|
||||
.addQueryParameter("q", "$fixedQuery $advQuery")
|
||||
.addQueryParameter("page", offsetPage.toString())
|
||||
|
||||
return GET(url.build(), headers)
|
||||
} else {
|
||||
val url = "$baseUrl/search/".toHttpUrl().newBuilder()
|
||||
// Blank query (Multi + sort by popular month/week/day) shows a 404 page
|
||||
// Searching for `""` is a hacky way to return everything without any filtering
|
||||
.addQueryParameter("q", "$query $nhLangSearch$advQuery".ifBlank { "\"\"" })
|
||||
val url = "$baseUrl/search".toHttpUrl().newBuilder()
|
||||
.addQueryParameter("q", "$fixedQuery $nhLangSearch$advQuery")
|
||||
.addQueryParameter("page", offsetPage.toString())
|
||||
|
||||
if (isOkayToSort) {
|
||||
filterList.findInstance<SortFilter>()?.let { f ->
|
||||
url.addQueryParameter("sort", f.toUriPart())
|
||||
}
|
||||
}
|
||||
|
||||
return GET(url.build(), headers)
|
||||
}
|
||||
}
|
||||
|
||||
private fun combineQuery(filters: FilterList): String = buildString {
|
||||
filters.filterIsInstance<AdvSearchEntryFilter>().forEach { filter ->
|
||||
filter.state.split(",")
|
||||
.map(String::trim)
|
||||
.filterNot(String::isBlank)
|
||||
.forEach { tag ->
|
||||
val y = !(filter.name == "Pages" || filter.name == "Uploaded")
|
||||
if (tag.startsWith("-")) append("-")
|
||||
append(filter.name, ':')
|
||||
if (y) append('"')
|
||||
append(tag.removePrefix("-"))
|
||||
if (y) append('"')
|
||||
append(" ")
|
||||
private fun combineQuery(filters: FilterList): String {
|
||||
val stringBuilder = StringBuilder()
|
||||
val advSearch = filters.filterIsInstance<AdvSearchEntryFilter>().flatMap { filter ->
|
||||
val splitState = filter.state.split(",").map(String::trim).filterNot(String::isBlank)
|
||||
splitState.map {
|
||||
AdvSearchEntry(filter.name, it.removePrefix("-"), it.startsWith("-"))
|
||||
}
|
||||
}
|
||||
|
||||
advSearch.forEach { entry ->
|
||||
if (entry.exclude) stringBuilder.append("-")
|
||||
stringBuilder.append("${entry.name}:")
|
||||
stringBuilder.append(entry.text)
|
||||
stringBuilder.append(" ")
|
||||
}
|
||||
|
||||
return stringBuilder.toString()
|
||||
}
|
||||
|
||||
data class AdvSearchEntry(val name: String, val text: String, val exclude: Boolean)
|
||||
|
||||
private fun searchMangaByIdRequest(id: String) = GET("$baseUrl/g/$id", headers)
|
||||
|
||||
private fun searchMangaByIdParse(response: Response, id: String): MangasPage {
|
||||
@ -220,7 +226,7 @@ open class NHentai(
|
||||
.plus("$fullTitle\n")
|
||||
.plus("${document.select("div#info h2").text()}\n\n")
|
||||
.plus("Pages: ${getNumPages(document)}\n")
|
||||
.plus("Favorited by: ${document.select("div#info i.fa-heart ~ span span").text().removeSurrounding("(", ")")}\n")
|
||||
.plus("Favorited by: ${document.select("div#info i.fa-heart + span span").text().removeSurrounding("(", ")")}\n")
|
||||
.plus(getTagDescription(document))
|
||||
genre = getTags(document)
|
||||
update_strategy = UpdateStrategy.ONLY_FETCH_ONCE
|
||||
|
@ -3,7 +3,7 @@ ext {
|
||||
extClass = '.ThunderScansFactory'
|
||||
themePkg = 'mangathemesia'
|
||||
baseUrl = 'https://en-thunderscans.com'
|
||||
overrideVersionCode = 5
|
||||
overrideVersionCode = 4
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
||||
|
@ -14,7 +14,7 @@ class ThunderScansFactory : SourceFactory {
|
||||
|
||||
class ThunderScansAR : MangaThemesiaAlt(
|
||||
"Thunder Scans",
|
||||
"https://ar-thunderepic.com",
|
||||
"https://thunderscans.com",
|
||||
"ar",
|
||||
dateFormat = SimpleDateFormat("MMM d, yyy", Locale("ar")),
|
||||
)
|
||||
|
@ -2,8 +2,8 @@ ext {
|
||||
extName = 'Empire Webtoon'
|
||||
extClass = '.EmpireWebtoon'
|
||||
themePkg = 'madara'
|
||||
baseUrl = 'https://webtoonempire-ron.com'
|
||||
overrideVersionCode = 4
|
||||
baseUrl = 'https://webtoonsempireron.com'
|
||||
overrideVersionCode = 3
|
||||
isNsfw = true
|
||||
}
|
||||
|
||||
|
@ -4,6 +4,7 @@ import android.app.Application
|
||||
import android.content.SharedPreferences
|
||||
import android.widget.Toast
|
||||
import androidx.preference.PreferenceScreen
|
||||
import eu.kanade.tachiyomi.extension.BuildConfig
|
||||
import eu.kanade.tachiyomi.multisrc.madara.Madara
|
||||
import eu.kanade.tachiyomi.source.ConfigurableSource
|
||||
import uy.kohesive.injekt.Injekt
|
||||
@ -14,56 +15,46 @@ import java.util.Locale
|
||||
class EmpireWebtoon :
|
||||
Madara(
|
||||
"Empire Webtoon",
|
||||
"https://webtoonempire-ron.com",
|
||||
"https://webtoonsempireron.com",
|
||||
"ar",
|
||||
SimpleDateFormat("d MMMM، yyyy", Locale("ar")),
|
||||
),
|
||||
ConfigurableSource {
|
||||
|
||||
private val preferences: SharedPreferences =
|
||||
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
||||
|
||||
init {
|
||||
preferences.getString(DEFAULT_BASE_URL_PREF, null).let { prefDefaultBaseUrl ->
|
||||
if (prefDefaultBaseUrl != super.baseUrl) {
|
||||
preferences.edit()
|
||||
.putString(BASE_URL_PREF, super.baseUrl)
|
||||
.putString(DEFAULT_BASE_URL_PREF, super.baseUrl)
|
||||
.apply()
|
||||
}
|
||||
}
|
||||
}
|
||||
private val defaultBaseUrl = "https://webtoonsempireron.com"
|
||||
|
||||
override val baseUrl by lazy { getPrefBaseUrl() }
|
||||
|
||||
private val preferences: SharedPreferences by lazy {
|
||||
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
||||
}
|
||||
|
||||
override val mangaSubString = "webtoon"
|
||||
|
||||
override val useNewChapterEndpoint = false
|
||||
|
||||
companion object {
|
||||
private const val RESTART_TACHIYOMI = "Restart Tachiyomi to apply new setting."
|
||||
private const val BASE_URL_PREF_TITLE = "Override BaseUrl"
|
||||
private const val BASE_URL_PREF = "overrideBaseUrl_v${BuildConfig.VERSION_CODE}"
|
||||
private const val BASE_URL_PREF_SUMMARY = "For temporary uses. Updating the extension will erase this setting."
|
||||
}
|
||||
|
||||
override fun setupPreferenceScreen(screen: PreferenceScreen) {
|
||||
val baseUrlPref = androidx.preference.EditTextPreference(screen.context).apply {
|
||||
key = BASE_URL_PREF
|
||||
title = BASE_URL_PREF_TITLE
|
||||
summary = BASE_URL_PREF_SUMMARY
|
||||
setDefaultValue(super.baseUrl)
|
||||
this.setDefaultValue(defaultBaseUrl)
|
||||
dialogTitle = BASE_URL_PREF_TITLE
|
||||
dialogMessage = "Default: ${super.baseUrl}"
|
||||
|
||||
setOnPreferenceChangeListener { _, _ ->
|
||||
Toast.makeText(screen.context, RESTART_APP, Toast.LENGTH_LONG).show()
|
||||
Toast.makeText(screen.context, RESTART_TACHIYOMI, Toast.LENGTH_LONG).show()
|
||||
true
|
||||
}
|
||||
}
|
||||
screen.addPreference(baseUrlPref)
|
||||
}
|
||||
|
||||
private fun getPrefBaseUrl(): String = preferences.getString(BASE_URL_PREF, super.baseUrl)!!
|
||||
|
||||
companion object {
|
||||
private const val DEFAULT_BASE_URL_PREF = "defaultBaseUrl"
|
||||
private const val RESTART_APP = "Restart app to apply new setting."
|
||||
private const val BASE_URL_PREF_TITLE = "Override BaseUrl"
|
||||
private const val BASE_URL_PREF = "overrideBaseUrl"
|
||||
private const val BASE_URL_PREF_SUMMARY = "For temporary uses. Updating the extension will erase this setting."
|
||||
}
|
||||
private fun getPrefBaseUrl(): String = preferences.getString(BASE_URL_PREF, defaultBaseUrl)!!
|
||||
}
|
||||
|
@ -2,8 +2,8 @@ ext {
|
||||
extName = 'MangaNoon'
|
||||
extClass = '.MangaNoon'
|
||||
themePkg = 'mangathemesia'
|
||||
baseUrl = 'https://manjanoon.xyz'
|
||||
overrideVersionCode = 5
|
||||
baseUrl = 'https://noonscan.net'
|
||||
overrideVersionCode = 4
|
||||
isNsfw = false
|
||||
}
|
||||
|
||||
|
@ -7,7 +7,7 @@ import java.util.Calendar
|
||||
|
||||
class MangaNoon : MangaThemesia(
|
||||
"مانجا نون",
|
||||
"https://manjanoon.xyz",
|
||||
"https://noonscan.net",
|
||||
"ar",
|
||||
) {
|
||||
|
||||
|
@ -2,8 +2,8 @@ ext {
|
||||
extName = 'MangaSwat'
|
||||
extClass = '.MangaSwat'
|
||||
themePkg = 'mangathemesia'
|
||||
baseUrl = 'https://healteer.com'
|
||||
overrideVersionCode = 23
|
||||
baseUrl = 'https://tatwt.com'
|
||||
overrideVersionCode = 21
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
||||
|
@ -1,6 +1,7 @@
|
||||
package eu.kanade.tachiyomi.extension.ar.mangaswat
|
||||
|
||||
import android.app.Application
|
||||
import android.content.SharedPreferences
|
||||
import android.widget.Toast
|
||||
import androidx.preference.PreferenceScreen
|
||||
import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia
|
||||
@ -11,6 +12,7 @@ import eu.kanade.tachiyomi.source.model.Page
|
||||
import eu.kanade.tachiyomi.source.model.SChapter
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.decodeFromString
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import org.jsoup.nodes.Document
|
||||
import org.jsoup.nodes.Element
|
||||
@ -22,7 +24,7 @@ import java.util.Locale
|
||||
class MangaSwat :
|
||||
MangaThemesia(
|
||||
"MangaSwat",
|
||||
"https://healteer.com",
|
||||
"https://tatwt.com",
|
||||
"ar",
|
||||
dateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale("ar")),
|
||||
),
|
||||
@ -30,53 +32,29 @@ class MangaSwat :
|
||||
|
||||
override val baseUrl by lazy { getPrefBaseUrl() }
|
||||
|
||||
private val preferences by lazy {
|
||||
private val preferences: SharedPreferences by lazy {
|
||||
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
||||
}
|
||||
|
||||
override val client = super.client.newBuilder()
|
||||
override val client: OkHttpClient = super.client.newBuilder()
|
||||
.rateLimit(1)
|
||||
.build()
|
||||
|
||||
override fun latestUpdatesRequest(page: Int): Request {
|
||||
val filter = FilterList(OrderByFilter("", orderByFilterOptions, "added"))
|
||||
|
||||
return searchMangaRequest(page, "", filter)
|
||||
}
|
||||
|
||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||
val request = super.searchMangaRequest(page, query, filters)
|
||||
val urlBuilder = request.url.newBuilder()
|
||||
if (query.isBlank()) return request
|
||||
|
||||
// remove trailing slash
|
||||
if (request.url.pathSegments.last().isBlank()) {
|
||||
urlBuilder.removePathSegment(
|
||||
request.url.pathSegments.lastIndex,
|
||||
)
|
||||
}
|
||||
|
||||
if (query.isNotBlank()) {
|
||||
urlBuilder
|
||||
val url = request.url.newBuilder()
|
||||
.removePathSegment(0)
|
||||
.removeAllQueryParameters("title")
|
||||
.addQueryParameter("s", query)
|
||||
.build()
|
||||
}
|
||||
|
||||
return request.newBuilder()
|
||||
.url(urlBuilder.build())
|
||||
.url(url)
|
||||
.build()
|
||||
}
|
||||
|
||||
override val orderByFilterOptions = arrayOf(
|
||||
Pair(intl["order_by_filter_default"], ""),
|
||||
Pair(intl["order_by_filter_az"], "a-z"),
|
||||
Pair(intl["order_by_filter_za"], "z-a"),
|
||||
Pair(intl["order_by_filter_latest_update"], "update"),
|
||||
Pair(intl["order_by_filter_latest_added"], "added"),
|
||||
Pair(intl["order_by_filter_popular"], "popular"),
|
||||
)
|
||||
|
||||
override fun searchMangaNextPageSelector() = "a[rel=next]"
|
||||
|
||||
override val seriesTitleSelector = "h1[itemprop=headline]"
|
||||
@ -104,12 +82,13 @@ class MangaSwat :
|
||||
}
|
||||
|
||||
@Serializable
|
||||
class TSReader(
|
||||
data class TSReader(
|
||||
val sources: List<ReaderImageSource>,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
class ReaderImageSource(
|
||||
data class ReaderImageSource(
|
||||
val source: String,
|
||||
val images: List<String>,
|
||||
)
|
||||
|
||||
|
@ -3,7 +3,7 @@ ext {
|
||||
extClass = '.RocksManga'
|
||||
themePkg = 'madara'
|
||||
baseUrl = 'https://rocksmanga.com'
|
||||
overrideVersionCode = 2
|
||||
overrideVersionCode = 1
|
||||
isNsfw = false
|
||||
}
|
||||
|
||||
|
@ -49,7 +49,7 @@ class RocksManga : Madara(
|
||||
override val mangaDetailsSelectorThumbnail = ".manga-poster img"
|
||||
override val mangaDetailsSelectorGenre = "div.meta span:contains(التصنيف:) + span a"
|
||||
override val altNameSelector = "div.alternative"
|
||||
override fun chapterListSelector() = "li.chapter-item"
|
||||
override fun chapterListSelector() = ".chapters-list li.chapter-item"
|
||||
override fun chapterDateSelector() = ".chapter-release-date"
|
||||
override val pageListParseSelector = ".chapter-reading-page img"
|
||||
|
||||
|
@ -1,10 +0,0 @@
|
||||
ext {
|
||||
extName = 'Scans 4u'
|
||||
extClass = '.Scans4u'
|
||||
themePkg = 'keyoapp'
|
||||
baseUrl = 'https://4uscans.com'
|
||||
overrideVersionCode = 0
|
||||
isNsfw = false
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
Before Width: | Height: | Size: 5.2 KiB |
Before Width: | Height: | Size: 2.9 KiB |
Before Width: | Height: | Size: 7.4 KiB |
Before Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 23 KiB |
@ -1,5 +0,0 @@
|
||||
package eu.kanade.tachiyomi.extension.ar.scans4u
|
||||
|
||||
import eu.kanade.tachiyomi.multisrc.keyoapp.Keyoapp
|
||||
|
||||
class Scans4u : Keyoapp("Scans 4u", "https://4uscans.com", "ar")
|
@ -2,8 +2,8 @@ ext {
|
||||
extName = 'YonaBar'
|
||||
extClass = '.YonaBar'
|
||||
themePkg = 'madara'
|
||||
baseUrl = 'https://yonabar.xyz'
|
||||
overrideVersionCode = 3
|
||||
baseUrl = 'https://yonabar.com'
|
||||
overrideVersionCode = 2
|
||||
isNsfw = true
|
||||
}
|
||||
|
||||
|
@ -1,19 +1,7 @@
|
||||
package eu.kanade.tachiyomi.extension.ar.yonabar
|
||||
|
||||
import eu.kanade.tachiyomi.multisrc.madara.Madara
|
||||
import eu.kanade.tachiyomi.network.interceptor.rateLimit
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Locale
|
||||
|
||||
class YonaBar : Madara(
|
||||
"YonaBar",
|
||||
"https://yonabar.xyz",
|
||||
"ar",
|
||||
SimpleDateFormat("MMM dd, yyyy", Locale("ar")),
|
||||
) {
|
||||
override val client = super.client.newBuilder()
|
||||
.rateLimit(3)
|
||||
.build()
|
||||
|
||||
override val mangaSubString = "yaoi"
|
||||
}
|
||||
class YonaBar : Madara("YonaBar", "https://yonabar.com", "ar", SimpleDateFormat("yyyy-MM-dd", Locale("ar")))
|
||||
|
@ -3,7 +3,7 @@ ext {
|
||||
extClass = '.YuriMoonSub'
|
||||
themePkg = 'zeistmanga'
|
||||
baseUrl = 'https://yurimoonsub.blogspot.com'
|
||||
overrideVersionCode = 1
|
||||
overrideVersionCode = 0
|
||||
isNsfw = true
|
||||
}
|
||||
|
||||
|
@ -2,30 +2,9 @@ package eu.kanade.tachiyomi.extension.ar.yurimoonsub
|
||||
|
||||
import eu.kanade.tachiyomi.multisrc.zeistmanga.ZeistManga
|
||||
import eu.kanade.tachiyomi.network.interceptor.rateLimit
|
||||
import org.jsoup.nodes.Document
|
||||
import java.net.URLDecoder
|
||||
import java.nio.charset.StandardCharsets
|
||||
|
||||
class YuriMoonSub : ZeistManga(
|
||||
"Yuri Moon Sub",
|
||||
"https://yurimoonsub.blogspot.com",
|
||||
"ar",
|
||||
) {
|
||||
class YuriMoonSub : ZeistManga("Yuri Moon Sub", "https://yurimoonsub.blogspot.com", "ar") {
|
||||
override val client = super.client.newBuilder()
|
||||
.rateLimit(2)
|
||||
.build()
|
||||
|
||||
override fun getChapterFeedUrl(doc: Document): String {
|
||||
return URLDecoder.decode(super.getChapterFeedUrl(doc), StandardCharsets.UTF_8.toString())
|
||||
.removeArabicChars()
|
||||
}
|
||||
|
||||
private fun String.removeArabicChars() =
|
||||
this.replace(ARABIC_CHARS_REGEX, "")
|
||||
.replace(EXTRA_SPACES_REGEX, "")
|
||||
|
||||
companion object {
|
||||
val ARABIC_CHARS_REGEX = "[\\u0600-\\u06FF]".toRegex()
|
||||
val EXTRA_SPACES_REGEX = "\\s{2,}".toRegex()
|
||||
}
|
||||
}
|
||||
|
7
src/de/wiemanga/build.gradle
Normal file
@ -0,0 +1,7 @@
|
||||
ext {
|
||||
extName = 'WieManga'
|
||||
extClass = '.WieManga'
|
||||
extVersionCode = 5
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
BIN
src/de/wiemanga/res/mipmap-hdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 3.5 KiB |
BIN
src/de/wiemanga/res/mipmap-mdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 2.0 KiB |
BIN
src/de/wiemanga/res/mipmap-xhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 4.3 KiB |
BIN
src/de/wiemanga/res/mipmap-xxhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 7.9 KiB |