Compare commits

..

No commits in common. "e0bb39f99adb84553c28dce5f190fbeb60fb80b9" and "5ecf338be0dd21937706e3284fc9d0edc5950e62" have entirely different histories.

721 changed files with 3476 additions and 5057 deletions

View File

@ -1,14 +1,12 @@
# Editor configuration, see https://editorconfig.org # Editor configuration, see https://editorconfig.org
root = true root = true
[*]
insert_final_newline = true
end_of_line = lf
[*.kt] [*.kt]
charset = utf-8 charset = utf-8
end_of_line = lf
indent_size = 4 indent_size = 4
indent_style = space indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true trim_trailing_whitespace = true
ij_kotlin_allow_trailing_comma = true ij_kotlin_allow_trailing_comma = true
ij_kotlin_allow_trailing_comma_on_call_site = 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] [*.properties]
charset = utf-8 charset = utf-8
end_of_line = lf
insert_final_newline = true

View File

@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists 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 networkTimeout=10000
validateDistributionUrl=true validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME

View File

@ -9,7 +9,6 @@ assert !ext.has("libVersion")
assert extName.chars().max().asInt < 0x180 : "Extension name should be romanized" assert extName.chars().max().asInt < 0x180 : "Extension name should be romanized"
Project theme = ext.has("themePkg") ? project(":lib-multisrc:$themePkg") : null Project theme = ext.has("themePkg") ? project(":lib-multisrc:$themePkg") : null
if (theme != null) evaluationDependsOn(theme.path)
android { android {
compileSdk AndroidConfig.compileSdk compileSdk AndroidConfig.compileSdk

View File

@ -25,5 +25,3 @@ android.useAndroidX=true
android.enableBuildConfigAsBytecode=true android.enableBuildConfigAsBytecode=true
android.defaults.buildfeatures.resvalues=false android.defaults.buildfeatures.resvalues=false
android.defaults.buildfeatures.shaders=false android.defaults.buildfeatures.shaders=false
org.gradle.configureondemand=true

View File

@ -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-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" } 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" } rxjava = { module = "io.reactivex:rxjava", version = "1.3.8" }
jsoup = { module = "org.jsoup:jsoup", version = "1.15.1" } jsoup = { module = "org.jsoup:jsoup", version = "1.15.1" }
okhttp = { module = "com.squareup.okhttp3:okhttp", version = "5.0.0-alpha.11" } okhttp = { module = "com.squareup.okhttp3:okhttp", version = "5.0.0-alpha.11" }

View File

@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists 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 networkTimeout=10000
validateDistributionUrl=true validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME

View File

@ -2,7 +2,7 @@ plugins {
id("lib-multisrc") id("lib-multisrc")
} }
baseVersionCode = 4 baseVersionCode = 3
dependencies { dependencies {
api(project(":lib:synchrony")) api(project(":lib:synchrony"))

View File

@ -276,7 +276,7 @@ abstract class ColaManga(
}.also(screen::addPreference) }.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 { private val keyMapping by lazy {
val obfuscatedReadJs = client.newCall(GET("$baseUrl/js/manga.read.js")).execute().body.string() val obfuscatedReadJs = client.newCall(GET("$baseUrl/js/manga.read.js")).execute().body.string()

View File

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

View File

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

View File

@ -83,8 +83,8 @@ class Post<T>(val post: T)
@Serializable @Serializable
class ChapterListResponse( class ChapterListResponse(
val isNovel: Boolean = false, val isNovel: Boolean,
val slug: String? = null, val slug: String,
val chapters: List<Chapter>, val chapters: List<Chapter>,
) )
@ -96,13 +96,11 @@ class Chapter(
private val createdBy: Name, private val createdBy: Name,
private val createdAt: String, private val createdAt: String,
private val chapterStatus: String, private val chapterStatus: String,
private val mangaPost: ChapterPostDetails,
) { ) {
fun isPublic() = chapterStatus == "PUBLIC" fun isPublic() = chapterStatus == "PUBLIC"
fun toSChapter(mangaSlug: String?) = SChapter.create().apply { fun toSChapter(mangaSlug: String) = SChapter.create().apply {
val seriesSlug = mangaSlug ?: mangaPost.slug url = "/series/$mangaSlug/$slug#$id"
url = "/series/$seriesSlug/$slug#$id"
name = "Chapter $number" name = "Chapter $number"
scanlator = createdBy.name scanlator = createdBy.name
date_upload = try { 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) private val dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.ENGLISH)

View File

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

View File

@ -1,6 +1,7 @@
package eu.kanade.tachiyomi.multisrc.keyoapp package eu.kanade.tachiyomi.multisrc.keyoapp
import eu.kanade.tachiyomi.network.GET 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.Filter
import eu.kanade.tachiyomi.source.model.FilterList import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.MangasPage import eu.kanade.tachiyomi.source.model.MangasPage
@ -30,7 +31,9 @@ abstract class Keyoapp(
) : ParsedHttpSource() { ) : ParsedHttpSource() {
override val supportsLatest = true override val supportsLatest = true
override val client = network.cloudflareClient override val client = network.cloudflareClient.newBuilder()
.rateLimit(2)
.build()
override fun headersBuilder() = super.headersBuilder() override fun headersBuilder() = super.headersBuilder()
.add("Referer", "$baseUrl/") .add("Referer", "$baseUrl/")
@ -194,18 +197,7 @@ abstract class Keyoapp(
status = document.selectFirst("div[alt=Status]").parseStatus() status = document.selectFirst("div[alt=Status]").parseStatus()
author = document.selectFirst("div[alt=Author]")?.text() author = document.selectFirst("div[alt=Author]")?.text()
artist = document.selectFirst("div[alt=Artist]")?.text() artist = document.selectFirst("div[alt=Artist]")?.text()
genre = buildList { genre = document.select("div.grid:has(>h1) > div > a").joinToString { it.text() }
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()
} }
private fun Element?.parseStatus(): Int = when (this?.text()?.lowercase()) { private fun Element?.parseStatus(): Int = when (this?.text()?.lowercase()) {
@ -231,27 +223,15 @@ abstract class Keyoapp(
// Image list // Image list
override fun pageListParse(document: Document): List<Page> { 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") return document.select("#pages > img")
.map { it.imgAttr() } .map { it.imgAttr() }
.filter { it.contains(oldImgCdnRegex) } .filter { it.contains(imgCdnRegex) }
.mapIndexed { index, img -> .mapIndexed { index, img ->
Page(index, document.location(), img) Page(index, document.location(), img)
} }
} }
protected val cdnUrl = "https://cdn.igniscans.com" private val imgCdnRegex = Regex("""^(https?:)?//cdn\d*\.keyoapp\.com""")
private val oldImgCdnRegex = Regex("""^(https?:)?//cdn\d*\.keyoapp\.com""")
override fun imageUrlParse(document: Document) = "" override fun imageUrlParse(document: Document) = ""
@ -267,7 +247,7 @@ abstract class Keyoapp(
return url return url
} }
protected open fun Element.getImageUrl(selector: String): String? { private fun Element.getImageUrl(selector: String): String? {
return this.selectFirst(selector)?.let { element -> return this.selectFirst(selector)?.let { element ->
element.attr("style") element.attr("style")
.substringAfter(":url(", "") .substringAfter(":url(", "")

View File

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

View File

@ -622,7 +622,7 @@ abstract class Madara(
"OnGoing", "Продолжается", "Updating", "Em Lançamento", "Em lançamento", "Em andamento", "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", "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", "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", "Đang làm", "Em postagem", "Devam Eden", "Em progresso", "Em curso",
) )

View File

@ -490,8 +490,8 @@ abstract class MangaThemesia(
Pair(intl["order_by_filter_popular"], "popular"), Pair(intl["order_by_filter_popular"], "popular"),
) )
protected open val popularFilter by lazy { FilterList(OrderByFilter("", orderByFilterOptions, "popular")) } protected val popularFilter by lazy { FilterList(OrderByFilter("", orderByFilterOptions, "popular")) }
protected open val latestFilter by lazy { FilterList(OrderByFilter("", orderByFilterOptions, "update")) } protected val latestFilter by lazy { FilterList(OrderByFilter("", orderByFilterOptions, "update")) }
protected class ProjectFilter( protected class ProjectFilter(
name: String, name: String,
@ -603,7 +603,7 @@ abstract class MangaThemesia(
(!strict && url.pathSegments.size == n + 1 && url.pathSegments[n].isEmpty()) (!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 -> return document.selectFirst("ul.genrez")?.select("li")?.map { li ->
GenreData( GenreData(
li.selectFirst("label")!!.text(), li.selectFirst("label")!!.text(),

View File

@ -3,7 +3,7 @@
<application> <application>
<activity <activity
android:name="eu.kanade.tachiyomi.extension.tr.sadscans.SadscansUrlActivity" android:name="eu.kanade.tachiyomi.multisrc.po2scans.PO2ScansUrlActivity"
android:excludeFromRecents="true" android:excludeFromRecents="true"
android:exported="true" android:exported="true"
android:theme="@android:style/Theme.NoDisplay"> android:theme="@android:style/Theme.NoDisplay">
@ -14,9 +14,9 @@
<category android:name="android.intent.category.BROWSABLE" /> <category android:name="android.intent.category.BROWSABLE" />
<data <data
android:host="sadscans.com" android:host="${SOURCEHOST}"
android:pathPattern="/series/..*" android:pathPattern="/series/..*"
android:scheme="https" /> android:scheme="${SOURCESCHEME}" />
</intent-filter> </intent-filter>
</activity> </activity>
</application> </application>

View File

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

View File

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

View File

@ -1,4 +1,4 @@
package eu.kanade.tachiyomi.extension.tr.sadscans package eu.kanade.tachiyomi.multisrc.po2scans
import android.app.Activity import android.app.Activity
import android.content.ActivityNotFoundException import android.content.ActivityNotFoundException
@ -7,7 +7,7 @@ import android.os.Bundle
import android.util.Log import android.util.Log
import kotlin.system.exitProcess import kotlin.system.exitProcess
class SadscansUrlActivity : Activity() { class PO2ScansUrlActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
val pathSegments = intent?.data?.pathSegments val pathSegments = intent?.data?.pathSegments
@ -15,17 +15,17 @@ class SadscansUrlActivity : Activity() {
val slug = pathSegments[1] val slug = pathSegments[1]
val mainIntent = Intent().apply { val mainIntent = Intent().apply {
action = "eu.kanade.tachiyomi.SEARCH" action = "eu.kanade.tachiyomi.SEARCH"
putExtra("query", "${Sadscans.SLUG_SEARCH_PREFIX}$slug") putExtra("query", "${PO2Scans.SLUG_SEARCH_PREFIX}$slug")
putExtra("filter", packageName) putExtra("filter", packageName)
} }
try { try {
startActivity(mainIntent) startActivity(mainIntent)
} catch (e: ActivityNotFoundException) { } catch (e: ActivityNotFoundException) {
Log.e("SadscansUrlActivity", "Could not start activity", e) Log.e("PO2ScansUrlActivity", "Could not start activity", e)
} }
} else { } else {
Log.e("SadscansUrlActivity", "could not parse URI from intent $intent") Log.e("PO2ScansUrlActivity", "could not parse URI from intent $intent")
} }
finish() finish()

View File

@ -1,7 +1,7 @@
ext { ext {
extName = 'Akuma' extName = 'Akuma'
extClass = '.AkumaFactory' extClass = '.AkumaFactory'
extVersionCode = 5 extVersionCode = 4
isNsfw = true isNsfw = true
} }

View File

@ -1,13 +1,8 @@
package eu.kanade.tachiyomi.extension.all.akuma 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.GET
import eu.kanade.tachiyomi.network.POST import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.network.interceptor.rateLimit 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.FilterList
import eu.kanade.tachiyomi.source.model.MangasPage import eu.kanade.tachiyomi.source.model.MangasPage
import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.source.model.Page
@ -25,8 +20,6 @@ import okhttp3.Response
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import rx.Observable import rx.Observable
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.io.IOException import java.io.IOException
import java.text.ParseException import java.text.ParseException
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
@ -36,7 +29,7 @@ import java.util.TimeZone
class Akuma( class Akuma(
override val lang: String, override val lang: String,
private val akumaLang: String, private val akumaLang: String,
) : ConfigurableSource, ParsedHttpSource() { ) : ParsedHttpSource() {
override val name = "Akuma" override val name = "Akuma"
@ -112,23 +105,6 @@ class Akuma(
return storedToken!! 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 { override fun popularMangaRequest(page: Int): Request {
val payload = FormBody.Builder() val payload = FormBody.Builder()
.add("view", "3") .add("view", "3")
@ -173,9 +149,7 @@ class Akuma(
override fun popularMangaFromElement(element: Element): SManga { override fun popularMangaFromElement(element: Element): SManga {
return SManga.create().apply { return SManga.create().apply {
setUrlWithoutDomain(element.select("a").attr("href")) setUrlWithoutDomain(element.select("a").attr("href"))
title = element.select(".overlay-title").text().replace("\"", "").let { title = element.select(".overlay-title").text()
if (displayFullTitle) it.trim() else it.shortenTitle()
}
thumbnail_url = element.select("img").attr("abs:src") thumbnail_url = element.select("img").attr("abs:src")
} }
} }
@ -202,7 +176,7 @@ class Akuma(
val finalQuery: MutableList<String> = mutableListOf(query) val finalQuery: MutableList<String> = mutableListOf(query)
if (lang != "all") { if (lang != "all") {
finalQuery.add("language:$akumaLang$") finalQuery.add("language: $akumaLang$")
} }
filters.forEach { filter -> filters.forEach { filter ->
when (filter) { when (filter) {
@ -246,9 +220,7 @@ class Akuma(
override fun mangaDetailsParse(document: Document) = with(document) { override fun mangaDetailsParse(document: Document) = with(document) {
SManga.create().apply { SManga.create().apply {
title = select(".entry-title").text().replace("\"", "").let { title = select(".entry-title").text()
if (displayFullTitle) it.trim() else it.shortenTitle()
}
thumbnail_url = select(".img-thumbnail").attr("abs:src") thumbnail_url = select(".img-thumbnail").attr("abs:src")
author = select(".group~.value").eachText().joinToString() author = select(".group~.value").eachText().joinToString()
@ -330,7 +302,6 @@ class Akuma(
companion object { companion object {
const val PREFIX_ID = "id:" const val PREFIX_ID = "id:"
private const val PREF_TITLE = "pref_title"
} }
override fun latestUpdatesRequest(page: Int) = throw UnsupportedOperationException() override fun latestUpdatesRequest(page: Int) = throw UnsupportedOperationException()

View File

@ -1,7 +0,0 @@
ext {
extName = 'ComicsKingdom'
extClass = '.ComicsKingdomFactory'
extVersionCode = 1
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

View File

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

View File

@ -1,7 +1,7 @@
ext { ext {
extName = 'Hitomi' extName = 'Hitomi'
extClass = '.HitomiFactory' extClass = '.HitomiFactory'
extVersionCode = 33 extVersionCode = 32
isNsfw = true isNsfw = true
} }

View File

@ -517,9 +517,6 @@ class Hitomi(
"https://${subDomain}tn.$domain/webpbigtn/${thumbPathFromHash(hash)}/$hash.webp" "https://${subDomain}tn.$domain/webpbigtn/${thumbPathFromHash(hash)}/$hash.webp"
} }
description = buildString { description = buildString {
japaneseTitle?.let {
append("Japanese title: ", it, "\n")
}
parodys?.joinToString { it.formatted }?.let { parodys?.joinToString { it.formatted }?.let {
append("Series: ", it, "\n") append("Series: ", it, "\n")
} }

View File

@ -7,7 +7,6 @@ import kotlinx.serialization.json.JsonPrimitive
class Gallery( class Gallery(
val galleryurl: String, val galleryurl: String,
val title: String, val title: String,
val japaneseTitle: String?,
val date: String, val date: String,
val type: String?, val type: String?,
val language: String?, val language: String?,

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

View File

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

View File

@ -1,7 +1,7 @@
ext { ext {
extName = 'LANraragi' extName = 'LANraragi'
extClass = '.LANraragiFactory' extClass = '.LANraragiFactory'
extVersionCode = 18 extVersionCode = 17
} }
apply from: "$rootDir/common.gradle" apply from: "$rootDir/common.gradle"

View File

@ -7,7 +7,6 @@ data class Archive(
val arcid: String, val arcid: String,
val isnew: String, val isnew: String,
val tags: String?, val tags: String?,
val summary: String?,
val title: String, val title: String,
) )
@ -26,6 +25,7 @@ data class ArchiveSearchResult(
@Serializable @Serializable
data class Category( data class Category(
val id: String, val id: String,
val last_used: String,
val name: String, val name: String,
val pinned: String, val pinned: String,
) )

View File

@ -242,7 +242,7 @@ open class LANraragi(private val suffix: String = "") : ConfigurableSource, Unme
private fun archiveToSManga(archive: Archive) = SManga.create().apply { private fun archiveToSManga(archive: Archive) = SManga.create().apply {
url = "/reader?id=${archive.arcid}" url = "/reader?id=${archive.arcid}"
title = archive.title title = archive.title
description = if (archive.summary.isNullOrBlank()) archive.title else archive.summary description = archive.title
thumbnail_url = getThumbnailUri(archive.arcid) thumbnail_url = getThumbnailUri(archive.arcid)
genre = archive.tags?.replace(",", ", ") genre = archive.tags?.replace(",", ", ")
artist = getArtist(archive.tags) 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>> { private fun getCategoryPairs(categories: List<Category>): Array<Pair<String?, String>> {
// Empty pair to disable. Sort by pinned status then name for convenience. // 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 " 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() getCategories()
return listOf(Pair("", "")) return listOf(Pair("", ""))

View File

@ -1,7 +1,7 @@
ext { ext {
extName = 'MangaDex' extName = 'MangaDex'
extClass = '.MangaDexFactory' extClass = '.MangaDexFactory'
extVersionCode = 194 extVersionCode = 193
isNsfw = true isNsfw = true
} }

View File

@ -138,6 +138,13 @@ object MDConstants {
return "${altTitlesInDescPref}_$dexLang" 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 tagGroupContent = "content"
private const val tagGroupFormat = "format" private const val tagGroupFormat = "format"
private const val tagGroupGenre = "genre" private const val tagGroupGenre = "genre"

View File

@ -3,6 +3,7 @@ package eu.kanade.tachiyomi.extension.all.mangadex
import android.app.Application import android.app.Application
import android.content.SharedPreferences import android.content.SharedPreferences
import android.os.Build import android.os.Build
import android.widget.Toast
import androidx.preference.EditTextPreference import androidx.preference.EditTextPreference
import androidx.preference.ListPreference import androidx.preference.ListPreference
import androidx.preference.MultiSelectListPreference 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 { private val preferences: SharedPreferences by lazy {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000) Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
.sanitizeExistingUuidPrefs()
} }
private val helper = MangaDexHelper(lang) private val helper = MangaDexHelper(lang)
@ -67,7 +67,6 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St
"Keiyoushi" "Keiyoushi"
val builder = super.headersBuilder().apply { val builder = super.headersBuilder().apply {
set("User-Agent", "Tachiyomi " + System.getProperty("http.agent"))
set("Referer", "$baseUrl/") set("Referer", "$baseUrl/")
set("Origin", baseUrl) set("Origin", baseUrl)
set("Extra", extraHeader) 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() override val client = network.client.newBuilder()
.rateLimit(3) .rateLimit(3)
.addInterceptor(MdAtHomeReportInterceptor(network.client, headers)) .addInterceptor(MdAtHomeReportInterceptor(network.client, headers))
.addInterceptor(MdUserAgentInterceptor(preferences, dexLang))
.build() .build()
init {
preferences.sanitizeExistingUuidPrefs()
}
// Popular manga section // Popular manga section
override fun popularMangaRequest(page: Int): Request { 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 // Manga Details section
override fun getMangaUrl(manga: SManga): String { 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(coverQualityPref)
screen.addPreference(tryUsingFirstVolumeCoverPref) screen.addPreference(tryUsingFirstVolumeCoverPref)
screen.addPreference(dataSaverPref) screen.addPreference(dataSaverPref)
@ -766,6 +794,7 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St
screen.addPreference(originalLanguagePref) screen.addPreference(originalLanguagePref)
screen.addPreference(blockedGroupsPref) screen.addPreference(blockedGroupsPref)
screen.addPreference(blockedUploaderPref) screen.addPreference(blockedUploaderPref)
screen.addPreference(userAgentPref)
} }
override fun getFilterList(): FilterList = override fun getFilterList(): FilterList =
@ -840,14 +869,20 @@ abstract class MangaDex(final override val lang: String, private val dexLang: St
private val SharedPreferences.altTitlesInDesc private val SharedPreferences.altTitlesInDesc
get() = getBoolean(MDConstants.getAltTitlesInDescPrefKey(dexLang), false) 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 * 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 * preferences. This method clear invalid UUIDs in case the user have updated from
* a previous version with that behaviour. * a previous version with that behaviour.
*/ */
private fun SharedPreferences.sanitizeExistingUuidPrefs(): SharedPreferences { private fun SharedPreferences.sanitizeExistingUuidPrefs() {
if (getBoolean(MDConstants.getHasSanitizedUuidsPrefKey(dexLang), false)) { if (getBoolean(MDConstants.getHasSanitizedUuidsPrefKey(dexLang), false)) {
return this return
} }
val blockedGroups = getString(MDConstants.getBlockedGroupsPrefKey(dexLang), "")!! 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) .putString(MDConstants.getBlockedUploaderPrefKey(dexLang), blockedUploaders)
.putBoolean(MDConstants.getHasSanitizedUuidsPrefKey(dexLang), true) .putBoolean(MDConstants.getHasSanitizedUuidsPrefKey(dexLang), true)
.apply() .apply()
return this
} }
} }

View File

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

View File

@ -17,8 +17,6 @@ schedule_monthly=Monthly
schedule_other=Other schedule_other=Other
schedule_trimonthly=Trimonthly schedule_trimonthly=Trimonthly
schedule_weekly=Weekly schedule_weekly=Weekly
subtitle_only=Show subtitle only
subtitle_only_summary=Removes the redundant chapter number from the chapter name.
serialization=Serialization: %s serialization=Serialization: %s
split_double_pages=Split double pages split_double_pages=Split double pages
split_double_pages_summary=Only a few titles supports disabling this setting. split_double_pages_summary=Only a few titles supports disabling this setting.

View File

@ -1,7 +1,7 @@
ext { ext {
extName = 'MANGA Plus by SHUEISHA' extName = 'MANGA Plus by SHUEISHA'
extClass = '.MangaPlusFactory' extClass = '.MangaPlusFactory'
extVersionCode = 53 extVersionCode = 52
} }
apply from: "$rootDir/common.gradle" apply from: "$rootDir/common.gradle"

View File

@ -289,10 +289,9 @@ class MangaPlus(
} }
val titleDetailView = result.success.titleDetailView!! val titleDetailView = result.success.titleDetailView!!
val subtitleOnly = preferences.subtitleOnly()
return titleDetailView.chapterList return titleDetailView.chapterList
.map { it.toSChapter(subtitleOnly) } .map(Chapter::toSChapter)
.reversed() .reversed()
} }
@ -308,8 +307,8 @@ class MangaPlus(
private fun pageListRequest(chapterId: String): Request { private fun pageListRequest(chapterId: String): Request {
val url = "$APP_API_URL/manga_viewer".toHttpUrl().newBuilder() val url = "$APP_API_URL/manga_viewer".toHttpUrl().newBuilder()
.addQueryParameter("chapter_id", chapterId) .addQueryParameter("chapter_id", chapterId)
.addQueryParameter("split", if (preferences.splitImages()) "yes" else "no") .addQueryParameter("split", if (preferences.splitImages) "yes" else "no")
.addQueryParameter("img_quality", preferences.imageQuality()) .addQueryParameter("img_quality", preferences.imageQuality)
.addQueryParameter("ticket_reading", "no") .addQueryParameter("ticket_reading", "no")
.addQueryParameter("free_reading", "yes") .addQueryParameter("free_reading", "yes")
.addQueryParameter("subscription_reading", "no") .addQueryParameter("subscription_reading", "no")
@ -379,16 +378,8 @@ class MangaPlus(
key = "${VER_PREF_KEY}_$lang", 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(qualityPref)
screen.addPreference(splitPref) screen.addPreference(splitPref)
screen.addPreference(titlePref)
} }
private fun PreferenceScreen.addEditTextPreference( private fun PreferenceScreen.addEditTextPreference(
@ -503,11 +494,11 @@ class MangaPlus(
json.decodeFromString(body.string()) 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 val SharedPreferences.splitImages: Boolean
get() = 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.appVersion: String? private val SharedPreferences.appVersion: String?
get() = getString("${VER_PREF_KEY}_$lang", VER_PREF_DEFAULT_VALUE) 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_KEY = "splitImage"
private const val SPLIT_PREF_DEFAULT_VALUE = true 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_KEY = "appVer"
private const val VER_PREF_DEFAULT_VALUE = "" private const val VER_PREF_DEFAULT_VALUE = ""
private const val SECRET_PREF_KEY = "accountSecret" private const val SECRET_PREF_KEY = "accountSecret"

View File

@ -322,16 +322,11 @@ class Chapter(
val isExpired: Boolean val isExpired: Boolean
get() = subTitle == null get() = subTitle == null
fun toSChapter(subtitlePref: Boolean): SChapter = SChapter.create().apply { fun toSChapter(): SChapter = SChapter.create().apply {
name = if (subtitlePref && subTitle != null) { name = "${this@Chapter.name} - $subTitle"
subTitle
} else {
"${this@Chapter.name} - $subTitle"
}
date_upload = 1000L * startTimeStamp date_upload = 1000L * startTimeStamp
url = "#/viewer/$chapterId" url = "#/viewer/$chapterId"
chapter_number = this@Chapter.name.substringAfter("#").toFloatOrNull() ?: -1f chapter_number = this@Chapter.name.substringAfter("#").toFloatOrNull() ?: -1f
scanlator = "MANGA Plus"
} }
} }

View File

@ -0,0 +1,8 @@
ext {
extName = 'Meitua.top'
extClass = '.MeituaTop'
extVersionCode = 6
isNsfw = true
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

View File

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

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

View File

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

View File

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

View File

@ -136,51 +136,57 @@ open class NHentai(
} }
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
val fixedQuery = query.ifEmpty { "\"\"" }
val filterList = if (filters.isEmpty()) getFilterList() else filters 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 advQuery = combineQuery(filterList)
val favoriteFilter = filterList.findInstance<FavoriteFilter>() val favoriteFilter = filterList.findInstance<FavoriteFilter>()
val isOkayToSort = filterList.findInstance<UploadedFilter>()?.state?.isBlank() ?: true
val offsetPage = val offsetPage =
filterList.findInstance<OffsetPageFilter>()?.state?.toIntOrNull()?.plus(page) ?: page filterList.findInstance<OffsetPageFilter>()?.state?.toIntOrNull()?.plus(page) ?: page
if (favoriteFilter?.state == true) { if (favoriteFilter?.state == true) {
val url = "$baseUrl/favorites/".toHttpUrl().newBuilder() val url = "$baseUrl/favorites".toHttpUrl().newBuilder()
.addQueryParameter("q", "$query $advQuery") .addQueryParameter("q", "$fixedQuery $advQuery")
.addQueryParameter("page", offsetPage.toString()) .addQueryParameter("page", offsetPage.toString())
return GET(url.build(), headers) return GET(url.build(), headers)
} else { } else {
val url = "$baseUrl/search/".toHttpUrl().newBuilder() val url = "$baseUrl/search".toHttpUrl().newBuilder()
// Blank query (Multi + sort by popular month/week/day) shows a 404 page .addQueryParameter("q", "$fixedQuery $nhLangSearch$advQuery")
// Searching for `""` is a hacky way to return everything without any filtering
.addQueryParameter("q", "$query $nhLangSearch$advQuery".ifBlank { "\"\"" })
.addQueryParameter("page", offsetPage.toString()) .addQueryParameter("page", offsetPage.toString())
filterList.findInstance<SortFilter>()?.let { f -> if (isOkayToSort) {
url.addQueryParameter("sort", f.toUriPart()) filterList.findInstance<SortFilter>()?.let { f ->
url.addQueryParameter("sort", f.toUriPart())
}
} }
return GET(url.build(), headers) return GET(url.build(), headers)
} }
} }
private fun combineQuery(filters: FilterList): String = buildString { private fun combineQuery(filters: FilterList): String {
filters.filterIsInstance<AdvSearchEntryFilter>().forEach { filter -> val stringBuilder = StringBuilder()
filter.state.split(",") val advSearch = filters.filterIsInstance<AdvSearchEntryFilter>().flatMap { filter ->
.map(String::trim) val splitState = filter.state.split(",").map(String::trim).filterNot(String::isBlank)
.filterNot(String::isBlank) splitState.map {
.forEach { tag -> AdvSearchEntry(filter.name, it.removePrefix("-"), it.startsWith("-"))
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(" ")
}
} }
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 searchMangaByIdRequest(id: String) = GET("$baseUrl/g/$id", headers)
private fun searchMangaByIdParse(response: Response, id: String): MangasPage { private fun searchMangaByIdParse(response: Response, id: String): MangasPage {
@ -220,7 +226,7 @@ open class NHentai(
.plus("$fullTitle\n") .plus("$fullTitle\n")
.plus("${document.select("div#info h2").text()}\n\n") .plus("${document.select("div#info h2").text()}\n\n")
.plus("Pages: ${getNumPages(document)}\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)) .plus(getTagDescription(document))
genre = getTags(document) genre = getTags(document)
update_strategy = UpdateStrategy.ONLY_FETCH_ONCE update_strategy = UpdateStrategy.ONLY_FETCH_ONCE

View File

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

View File

@ -14,7 +14,7 @@ class ThunderScansFactory : SourceFactory {
class ThunderScansAR : MangaThemesiaAlt( class ThunderScansAR : MangaThemesiaAlt(
"Thunder Scans", "Thunder Scans",
"https://ar-thunderepic.com", "https://thunderscans.com",
"ar", "ar",
dateFormat = SimpleDateFormat("MMM d, yyy", Locale("ar")), dateFormat = SimpleDateFormat("MMM d, yyy", Locale("ar")),
) )

View File

@ -2,8 +2,8 @@ ext {
extName = 'Empire Webtoon' extName = 'Empire Webtoon'
extClass = '.EmpireWebtoon' extClass = '.EmpireWebtoon'
themePkg = 'madara' themePkg = 'madara'
baseUrl = 'https://webtoonempire-ron.com' baseUrl = 'https://webtoonsempireron.com'
overrideVersionCode = 4 overrideVersionCode = 3
isNsfw = true isNsfw = true
} }

View File

@ -4,6 +4,7 @@ import android.app.Application
import android.content.SharedPreferences import android.content.SharedPreferences
import android.widget.Toast import android.widget.Toast
import androidx.preference.PreferenceScreen import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.extension.BuildConfig
import eu.kanade.tachiyomi.multisrc.madara.Madara import eu.kanade.tachiyomi.multisrc.madara.Madara
import eu.kanade.tachiyomi.source.ConfigurableSource import eu.kanade.tachiyomi.source.ConfigurableSource
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
@ -14,56 +15,46 @@ import java.util.Locale
class EmpireWebtoon : class EmpireWebtoon :
Madara( Madara(
"Empire Webtoon", "Empire Webtoon",
"https://webtoonempire-ron.com", "https://webtoonsempireron.com",
"ar", "ar",
SimpleDateFormat("d MMMM، yyyy", Locale("ar")), SimpleDateFormat("d MMMM، yyyy", Locale("ar")),
), ),
ConfigurableSource { ConfigurableSource {
private val preferences: SharedPreferences = private val defaultBaseUrl = "https://webtoonsempireron.com"
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()
}
}
}
override val baseUrl by lazy { getPrefBaseUrl() } 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 mangaSubString = "webtoon"
override val useNewChapterEndpoint = false 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) { override fun setupPreferenceScreen(screen: PreferenceScreen) {
val baseUrlPref = androidx.preference.EditTextPreference(screen.context).apply { val baseUrlPref = androidx.preference.EditTextPreference(screen.context).apply {
key = BASE_URL_PREF key = BASE_URL_PREF
title = BASE_URL_PREF_TITLE title = BASE_URL_PREF_TITLE
summary = BASE_URL_PREF_SUMMARY summary = BASE_URL_PREF_SUMMARY
setDefaultValue(super.baseUrl) this.setDefaultValue(defaultBaseUrl)
dialogTitle = BASE_URL_PREF_TITLE dialogTitle = BASE_URL_PREF_TITLE
dialogMessage = "Default: ${super.baseUrl}"
setOnPreferenceChangeListener { _, _ -> setOnPreferenceChangeListener { _, _ ->
Toast.makeText(screen.context, RESTART_APP, Toast.LENGTH_LONG).show() Toast.makeText(screen.context, RESTART_TACHIYOMI, Toast.LENGTH_LONG).show()
true true
} }
} }
screen.addPreference(baseUrlPref) screen.addPreference(baseUrlPref)
} }
private fun getPrefBaseUrl(): String = preferences.getString(BASE_URL_PREF, super.baseUrl)!! private fun getPrefBaseUrl(): String = preferences.getString(BASE_URL_PREF, defaultBaseUrl)!!
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."
}
} }

View File

@ -2,8 +2,8 @@ ext {
extName = 'MangaNoon' extName = 'MangaNoon'
extClass = '.MangaNoon' extClass = '.MangaNoon'
themePkg = 'mangathemesia' themePkg = 'mangathemesia'
baseUrl = 'https://manjanoon.xyz' baseUrl = 'https://noonscan.net'
overrideVersionCode = 5 overrideVersionCode = 4
isNsfw = false isNsfw = false
} }

View File

@ -7,7 +7,7 @@ import java.util.Calendar
class MangaNoon : MangaThemesia( class MangaNoon : MangaThemesia(
"مانجا نون", "مانجا نون",
"https://manjanoon.xyz", "https://noonscan.net",
"ar", "ar",
) { ) {

View File

@ -2,8 +2,8 @@ ext {
extName = 'MangaSwat' extName = 'MangaSwat'
extClass = '.MangaSwat' extClass = '.MangaSwat'
themePkg = 'mangathemesia' themePkg = 'mangathemesia'
baseUrl = 'https://healteer.com' baseUrl = 'https://tatwt.com'
overrideVersionCode = 23 overrideVersionCode = 21
} }
apply from: "$rootDir/common.gradle" apply from: "$rootDir/common.gradle"

View File

@ -1,6 +1,7 @@
package eu.kanade.tachiyomi.extension.ar.mangaswat package eu.kanade.tachiyomi.extension.ar.mangaswat
import android.app.Application import android.app.Application
import android.content.SharedPreferences
import android.widget.Toast import android.widget.Toast
import androidx.preference.PreferenceScreen import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia 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 eu.kanade.tachiyomi.source.model.SChapter
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.decodeFromString import kotlinx.serialization.decodeFromString
import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
@ -22,7 +24,7 @@ import java.util.Locale
class MangaSwat : class MangaSwat :
MangaThemesia( MangaThemesia(
"MangaSwat", "MangaSwat",
"https://healteer.com", "https://tatwt.com",
"ar", "ar",
dateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale("ar")), dateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale("ar")),
), ),
@ -30,53 +32,29 @@ class MangaSwat :
override val baseUrl by lazy { getPrefBaseUrl() } override val baseUrl by lazy { getPrefBaseUrl() }
private val preferences by lazy { private val preferences: SharedPreferences by lazy {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000) Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
} }
override val client = super.client.newBuilder() override val client: OkHttpClient = super.client.newBuilder()
.rateLimit(1) .rateLimit(1)
.build() .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 { override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
val request = super.searchMangaRequest(page, query, filters) val request = super.searchMangaRequest(page, query, filters)
val urlBuilder = request.url.newBuilder() if (query.isBlank()) return request
// remove trailing slash val url = request.url.newBuilder()
if (request.url.pathSegments.last().isBlank()) { .removePathSegment(0)
urlBuilder.removePathSegment( .removeAllQueryParameters("title")
request.url.pathSegments.lastIndex, .addQueryParameter("s", query)
) .build()
}
if (query.isNotBlank()) {
urlBuilder
.removePathSegment(0)
.removeAllQueryParameters("title")
.addQueryParameter("s", query)
.build()
}
return request.newBuilder() return request.newBuilder()
.url(urlBuilder.build()) .url(url)
.build() .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 fun searchMangaNextPageSelector() = "a[rel=next]"
override val seriesTitleSelector = "h1[itemprop=headline]" override val seriesTitleSelector = "h1[itemprop=headline]"
@ -104,12 +82,13 @@ class MangaSwat :
} }
@Serializable @Serializable
class TSReader( data class TSReader(
val sources: List<ReaderImageSource>, val sources: List<ReaderImageSource>,
) )
@Serializable @Serializable
class ReaderImageSource( data class ReaderImageSource(
val source: String,
val images: List<String>, val images: List<String>,
) )

View File

@ -3,7 +3,7 @@ ext {
extClass = '.RocksManga' extClass = '.RocksManga'
themePkg = 'madara' themePkg = 'madara'
baseUrl = 'https://rocksmanga.com' baseUrl = 'https://rocksmanga.com'
overrideVersionCode = 2 overrideVersionCode = 1
isNsfw = false isNsfw = false
} }

View File

@ -49,7 +49,7 @@ class RocksManga : Madara(
override val mangaDetailsSelectorThumbnail = ".manga-poster img" override val mangaDetailsSelectorThumbnail = ".manga-poster img"
override val mangaDetailsSelectorGenre = "div.meta span:contains(التصنيف:) + span a" override val mangaDetailsSelectorGenre = "div.meta span:contains(التصنيف:) + span a"
override val altNameSelector = "div.alternative" 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 fun chapterDateSelector() = ".chapter-release-date"
override val pageListParseSelector = ".chapter-reading-page img" override val pageListParseSelector = ".chapter-reading-page img"

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

View File

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

View File

@ -2,8 +2,8 @@ ext {
extName = 'YonaBar' extName = 'YonaBar'
extClass = '.YonaBar' extClass = '.YonaBar'
themePkg = 'madara' themePkg = 'madara'
baseUrl = 'https://yonabar.xyz' baseUrl = 'https://yonabar.com'
overrideVersionCode = 3 overrideVersionCode = 2
isNsfw = true isNsfw = true
} }

View File

@ -1,19 +1,7 @@
package eu.kanade.tachiyomi.extension.ar.yonabar package eu.kanade.tachiyomi.extension.ar.yonabar
import eu.kanade.tachiyomi.multisrc.madara.Madara import eu.kanade.tachiyomi.multisrc.madara.Madara
import eu.kanade.tachiyomi.network.interceptor.rateLimit
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Locale import java.util.Locale
class YonaBar : Madara( class YonaBar : Madara("YonaBar", "https://yonabar.com", "ar", SimpleDateFormat("yyyy-MM-dd", Locale("ar")))
"YonaBar",
"https://yonabar.xyz",
"ar",
SimpleDateFormat("MMM dd, yyyy", Locale("ar")),
) {
override val client = super.client.newBuilder()
.rateLimit(3)
.build()
override val mangaSubString = "yaoi"
}

View File

@ -3,7 +3,7 @@ ext {
extClass = '.YuriMoonSub' extClass = '.YuriMoonSub'
themePkg = 'zeistmanga' themePkg = 'zeistmanga'
baseUrl = 'https://yurimoonsub.blogspot.com' baseUrl = 'https://yurimoonsub.blogspot.com'
overrideVersionCode = 1 overrideVersionCode = 0
isNsfw = true isNsfw = true
} }

View File

@ -2,30 +2,9 @@ package eu.kanade.tachiyomi.extension.ar.yurimoonsub
import eu.kanade.tachiyomi.multisrc.zeistmanga.ZeistManga import eu.kanade.tachiyomi.multisrc.zeistmanga.ZeistManga
import eu.kanade.tachiyomi.network.interceptor.rateLimit import eu.kanade.tachiyomi.network.interceptor.rateLimit
import org.jsoup.nodes.Document
import java.net.URLDecoder
import java.nio.charset.StandardCharsets
class YuriMoonSub : ZeistManga( class YuriMoonSub : ZeistManga("Yuri Moon Sub", "https://yurimoonsub.blogspot.com", "ar") {
"Yuri Moon Sub",
"https://yurimoonsub.blogspot.com",
"ar",
) {
override val client = super.client.newBuilder() override val client = super.client.newBuilder()
.rateLimit(2) .rateLimit(2)
.build() .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()
}
} }

View File

@ -0,0 +1,7 @@
ext {
extName = 'WieManga'
extClass = '.WieManga'
extVersionCode = 5
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

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