Compare commits
No commits in common. "07eba2f8f2890c1aa39196ce16d4fb771c5745c6" and "43bd7342e7b6da152c3ea3e0ccb72fcceed0c358" have entirely different histories.
07eba2f8f2
...
43bd7342e7
@ -86,7 +86,7 @@ small, just do a normal full clone instead.**
|
|||||||
```bash
|
```bash
|
||||||
git sparse-checkout set --cone --sparse-index
|
git sparse-checkout set --cone --sparse-index
|
||||||
# add project folders
|
# add project folders
|
||||||
git sparse-checkout add buildSrc core gradle lib lib-multisrc utils
|
git sparse-checkout add buildSrc core gradle lib lib-multisrc
|
||||||
# add a single source
|
# add a single source
|
||||||
git sparse-checkout add src/<lang>/<source>
|
git sparse-checkout add src/<lang>/<source>
|
||||||
```
|
```
|
||||||
|
@ -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.13-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12.1-bin.zip
|
||||||
networkTimeout=10000
|
networkTimeout=10000
|
||||||
validateDistributionUrl=true
|
validateDistributionUrl=true
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@ -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.13-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12.1-bin.zip
|
||||||
networkTimeout=10000
|
networkTimeout=10000
|
||||||
validateDistributionUrl=true
|
validateDistributionUrl=true
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
2
gradlew
generated
vendored
2
gradlew
generated
vendored
@ -205,7 +205,7 @@ fi
|
|||||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||||
|
|
||||||
# Collect all arguments for the java command:
|
# Collect all arguments for the java command:
|
||||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||||
# and any embedded shellness will be escaped.
|
# and any embedded shellness will be escaped.
|
||||||
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||||
# treated as '${Hostname}' itself on the command line.
|
# treated as '${Hostname}' itself on the command line.
|
||||||
|
@ -2,4 +2,4 @@ plugins {
|
|||||||
id("lib-multisrc")
|
id("lib-multisrc")
|
||||||
}
|
}
|
||||||
|
|
||||||
baseVersionCode = 7
|
baseVersionCode = 6
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package eu.kanade.tachiyomi.multisrc.bakkin
|
package eu.kanade.tachiyomi.multisrc.bakkin
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import androidx.preference.ListPreference
|
import androidx.preference.ListPreference
|
||||||
import androidx.preference.PreferenceScreen
|
import androidx.preference.PreferenceScreen
|
||||||
@ -13,7 +14,6 @@ import eu.kanade.tachiyomi.source.model.Page
|
|||||||
import eu.kanade.tachiyomi.source.model.SChapter
|
import eu.kanade.tachiyomi.source.model.SChapter
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||||
import keiyoushi.utils.getPreferencesLazy
|
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import kotlinx.serialization.json.decodeFromJsonElement
|
import kotlinx.serialization.json.decodeFromJsonElement
|
||||||
import kotlinx.serialization.json.jsonObject
|
import kotlinx.serialization.json.jsonObject
|
||||||
@ -33,7 +33,9 @@ abstract class BakkinReaderX(
|
|||||||
"Android ${Build.VERSION.RELEASE}; Mobile) " +
|
"Android ${Build.VERSION.RELEASE}; Mobile) " +
|
||||||
"Tachiyomi/${AppInfo.getVersionName()}"
|
"Tachiyomi/${AppInfo.getVersionName()}"
|
||||||
|
|
||||||
protected val preferences by getPreferencesLazy()
|
protected val preferences by lazy {
|
||||||
|
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)!!
|
||||||
|
}
|
||||||
|
|
||||||
private val json by lazy { Injekt.get<Json>() }
|
private val json by lazy { Injekt.get<Json>() }
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@ plugins {
|
|||||||
id("lib-multisrc")
|
id("lib-multisrc")
|
||||||
}
|
}
|
||||||
|
|
||||||
baseVersionCode = 9
|
baseVersionCode = 7
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
api(project(":lib:synchrony"))
|
api(project(":lib:synchrony"))
|
||||||
|
@ -19,23 +19,18 @@ import eu.kanade.tachiyomi.source.model.Page
|
|||||||
import eu.kanade.tachiyomi.source.model.SChapter
|
import eu.kanade.tachiyomi.source.model.SChapter
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
import eu.kanade.tachiyomi.source.online.ParsedHttpSource
|
import eu.kanade.tachiyomi.source.online.ParsedHttpSource
|
||||||
import eu.kanade.tachiyomi.util.asJsoup
|
|
||||||
import keiyoushi.utils.getPreferencesLazy
|
|
||||||
import keiyoushi.utils.tryParse
|
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import kotlinx.serialization.json.jsonArray
|
import kotlinx.serialization.json.jsonArray
|
||||||
import kotlinx.serialization.json.jsonObject
|
import kotlinx.serialization.json.jsonObject
|
||||||
import kotlinx.serialization.json.jsonPrimitive
|
import kotlinx.serialization.json.jsonPrimitive
|
||||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
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.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
import java.text.SimpleDateFormat
|
|
||||||
import java.util.concurrent.CountDownLatch
|
import java.util.concurrent.CountDownLatch
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
@ -51,7 +46,9 @@ abstract class ColaManga(
|
|||||||
|
|
||||||
private val intl = ColaMangaIntl(lang)
|
private val intl = ColaMangaIntl(lang)
|
||||||
|
|
||||||
private val preferences by getPreferencesLazy()
|
private val preferences by lazy {
|
||||||
|
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
||||||
|
}
|
||||||
|
|
||||||
override val client = network.cloudflareClient.newBuilder()
|
override val client = network.cloudflareClient.newBuilder()
|
||||||
.rateLimitHost(
|
.rateLimitHost(
|
||||||
@ -153,8 +150,6 @@ abstract class ColaManga(
|
|||||||
protected abstract val genreTitle: String
|
protected abstract val genreTitle: String
|
||||||
protected abstract val statusOngoing: String
|
protected abstract val statusOngoing: String
|
||||||
protected abstract val statusCompleted: String
|
protected abstract val statusCompleted: String
|
||||||
protected abstract val lastUpdated: String
|
|
||||||
private val dateFormat: SimpleDateFormat = SimpleDateFormat("yyyy-MM-dd")
|
|
||||||
|
|
||||||
override fun mangaDetailsParse(document: Document) = SManga.create().apply {
|
override fun mangaDetailsParse(document: Document) = SManga.create().apply {
|
||||||
title = document.selectFirst("h1.fed-part-eone")!!.text()
|
title = document.selectFirst("h1.fed-part-eone")!!.text()
|
||||||
@ -173,15 +168,6 @@ abstract class ColaManga(
|
|||||||
|
|
||||||
override fun chapterListSelector(): String = "div:not(.fed-hidden) > div.all_data_list > ul.fed-part-rows a"
|
override fun chapterListSelector(): String = "div:not(.fed-hidden) > div.all_data_list > ul.fed-part-rows a"
|
||||||
|
|
||||||
override fun chapterListParse(response: Response): List<SChapter> {
|
|
||||||
val document = response.asJsoup()
|
|
||||||
return document.select(chapterListSelector()).map { chapterFromElement(it) }.apply {
|
|
||||||
if (isNotEmpty()) {
|
|
||||||
this[0].date_upload = dateFormat.tryParse(document.selectFirst("span.fed-text-muted:contains($lastUpdated) + a")?.text())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun chapterFromElement(element: Element) = SChapter.create().apply {
|
override fun chapterFromElement(element: Element) = SChapter.create().apply {
|
||||||
setUrlWithoutDomain(element.attr("href"))
|
setUrlWithoutDomain(element.attr("href"))
|
||||||
name = element.attr("title")
|
name = element.attr("title")
|
||||||
|
@ -2,7 +2,7 @@ plugins {
|
|||||||
id("lib-multisrc")
|
id("lib-multisrc")
|
||||||
}
|
}
|
||||||
|
|
||||||
baseVersionCode = 9
|
baseVersionCode = 8
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
api(project(":lib:speedbinb"))
|
api(project(":lib:speedbinb"))
|
||||||
|
@ -28,7 +28,7 @@ open class ComicGamma(
|
|||||||
|
|
||||||
private val json = Injekt.get<Json>()
|
private val json = Injekt.get<Json>()
|
||||||
|
|
||||||
override val client = network.cloudflareClient.newBuilder()
|
override val client = network.client.newBuilder()
|
||||||
.addInterceptor(SpeedBinbInterceptor(json))
|
.addInterceptor(SpeedBinbInterceptor(json))
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
|
@ -2,4 +2,4 @@ plugins {
|
|||||||
id("lib-multisrc")
|
id("lib-multisrc")
|
||||||
}
|
}
|
||||||
|
|
||||||
baseVersionCode = 6
|
baseVersionCode = 5
|
||||||
|
@ -39,7 +39,7 @@ abstract class FansubsCat(
|
|||||||
override fun headersBuilder(): Headers.Builder = Headers.Builder()
|
override fun headersBuilder(): Headers.Builder = Headers.Builder()
|
||||||
.add("User-Agent", "Tachiyomi/${AppInfo.getVersionName()}")
|
.add("User-Agent", "Tachiyomi/${AppInfo.getVersionName()}")
|
||||||
|
|
||||||
override val client: OkHttpClient = network.cloudflareClient
|
override val client: OkHttpClient = network.client
|
||||||
|
|
||||||
private val json: Json by injectLazy()
|
private val json: Json by injectLazy()
|
||||||
|
|
||||||
|
@ -2,4 +2,4 @@ plugins {
|
|||||||
id("lib-multisrc")
|
id("lib-multisrc")
|
||||||
}
|
}
|
||||||
|
|
||||||
baseVersionCode = 10
|
baseVersionCode = 9
|
||||||
|
@ -19,7 +19,6 @@ import org.jsoup.nodes.Document
|
|||||||
import org.jsoup.nodes.Element
|
import org.jsoup.nodes.Element
|
||||||
import org.jsoup.select.Elements
|
import org.jsoup.select.Elements
|
||||||
import java.nio.charset.Charset
|
import java.nio.charset.Charset
|
||||||
import java.text.ParseException
|
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Calendar
|
import java.util.Calendar
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
@ -247,13 +246,7 @@ abstract class FMReader(
|
|||||||
name = element.attr(chapterNameAttrSelector).substringAfter("$mangaTitle ")
|
name = element.attr(chapterNameAttrSelector).substringAfter("$mangaTitle ")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
date_upload = element.select(chapterTimeSelector).let { dateElement ->
|
date_upload = element.select(chapterTimeSelector).let { if (it.hasText()) parseRelativeDate(it.text()) else 0 }
|
||||||
if (dateElement.hasText()) {
|
|
||||||
parseRelativeDate(dateElement.text()).takeIf { it != 0L } ?: parseAbsoluteDate(dateElement.text())
|
|
||||||
} else {
|
|
||||||
0L
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -264,63 +257,55 @@ abstract class FMReader(
|
|||||||
open val dateWordIndex = 1
|
open val dateWordIndex = 1
|
||||||
|
|
||||||
open fun parseRelativeDate(date: String): Long {
|
open fun parseRelativeDate(date: String): Long {
|
||||||
try {
|
val value = date.split(' ')[dateValueIndex].toInt()
|
||||||
val value = date.split(' ')[dateValueIndex].toInt()
|
val dateWord = date.split(' ')[dateWordIndex].let {
|
||||||
val dateWord = date.split(' ')[dateWordIndex].let {
|
if (it.contains("(")) {
|
||||||
if (it.contains("(")) {
|
it.substringBefore("(")
|
||||||
it.substringBefore("(")
|
} else {
|
||||||
} else {
|
it.substringBefore("s")
|
||||||
it.substringBefore("s")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// languages: en, vi, es, tr
|
// languages: en, vi, es, tr
|
||||||
return when (dateWord) {
|
return when (dateWord) {
|
||||||
"min", "minute", "phút", "minuto", "dakika" -> Calendar.getInstance().apply {
|
"min", "minute", "phút", "minuto", "dakika" -> Calendar.getInstance().apply {
|
||||||
add(Calendar.MINUTE, -value)
|
add(Calendar.MINUTE, -value)
|
||||||
set(Calendar.SECOND, 0)
|
set(Calendar.SECOND, 0)
|
||||||
set(Calendar.MILLISECOND, 0)
|
set(Calendar.MILLISECOND, 0)
|
||||||
}.timeInMillis
|
}.timeInMillis
|
||||||
"hour", "giờ", "hora", "saat" -> Calendar.getInstance().apply {
|
"hour", "giờ", "hora", "saat" -> Calendar.getInstance().apply {
|
||||||
add(Calendar.HOUR_OF_DAY, -value)
|
add(Calendar.HOUR_OF_DAY, -value)
|
||||||
set(Calendar.SECOND, 0)
|
set(Calendar.SECOND, 0)
|
||||||
set(Calendar.MILLISECOND, 0)
|
set(Calendar.MILLISECOND, 0)
|
||||||
}.timeInMillis
|
}.timeInMillis
|
||||||
"day", "ngày", "día", "gün" -> Calendar.getInstance().apply {
|
"day", "ngày", "día", "gün" -> Calendar.getInstance().apply {
|
||||||
add(Calendar.DATE, -value)
|
add(Calendar.DATE, -value)
|
||||||
set(Calendar.SECOND, 0)
|
set(Calendar.SECOND, 0)
|
||||||
set(Calendar.MILLISECOND, 0)
|
set(Calendar.MILLISECOND, 0)
|
||||||
}.timeInMillis
|
}.timeInMillis
|
||||||
"week", "tuần", "semana", "hafta" -> Calendar.getInstance().apply {
|
"week", "tuần", "semana", "hafta" -> Calendar.getInstance().apply {
|
||||||
add(Calendar.DATE, -value * 7)
|
add(Calendar.DATE, -value * 7)
|
||||||
set(Calendar.SECOND, 0)
|
set(Calendar.SECOND, 0)
|
||||||
set(Calendar.MILLISECOND, 0)
|
set(Calendar.MILLISECOND, 0)
|
||||||
}.timeInMillis
|
}.timeInMillis
|
||||||
"month", "tháng", "mes", "ay" -> Calendar.getInstance().apply {
|
"month", "tháng", "mes", "ay" -> Calendar.getInstance().apply {
|
||||||
add(Calendar.MONTH, -value)
|
add(Calendar.MONTH, -value)
|
||||||
set(Calendar.SECOND, 0)
|
set(Calendar.SECOND, 0)
|
||||||
set(Calendar.MILLISECOND, 0)
|
set(Calendar.MILLISECOND, 0)
|
||||||
}.timeInMillis
|
}.timeInMillis
|
||||||
"year", "năm", "año", "yıl" -> Calendar.getInstance().apply {
|
"year", "năm", "año", "yıl" -> Calendar.getInstance().apply {
|
||||||
add(Calendar.YEAR, -value)
|
add(Calendar.YEAR, -value)
|
||||||
set(Calendar.SECOND, 0)
|
set(Calendar.SECOND, 0)
|
||||||
set(Calendar.MILLISECOND, 0)
|
set(Calendar.MILLISECOND, 0)
|
||||||
}.timeInMillis
|
}.timeInMillis
|
||||||
else -> {
|
else -> {
|
||||||
return 0L
|
return 0
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (_: Exception) {
|
|
||||||
return 0L
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
open fun parseAbsoluteDate(dateStr: String): Long {
|
open fun parseAbsoluteDate(dateStr: String): Long {
|
||||||
return try {
|
return runCatching { dateFormat.parse(dateStr)?.time }
|
||||||
dateFormat.parse(dateStr)?.time ?: 0L
|
.getOrNull() ?: 0L
|
||||||
} catch (_: ParseException) {
|
|
||||||
0L
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
open val pageListImageSelector = "img.chapter-img"
|
open val pageListImageSelector = "img.chapter-img"
|
||||||
|
@ -2,4 +2,4 @@ plugins {
|
|||||||
id("lib-multisrc")
|
id("lib-multisrc")
|
||||||
}
|
}
|
||||||
|
|
||||||
baseVersionCode = 4
|
baseVersionCode = 3
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package eu.kanade.tachiyomi.multisrc.foolslide
|
package eu.kanade.tachiyomi.multisrc.foolslide
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
import androidx.preference.CheckBoxPreference
|
import androidx.preference.CheckBoxPreference
|
||||||
import androidx.preference.PreferenceScreen
|
import androidx.preference.PreferenceScreen
|
||||||
import eu.kanade.tachiyomi.network.GET
|
import eu.kanade.tachiyomi.network.GET
|
||||||
@ -11,7 +12,6 @@ import eu.kanade.tachiyomi.source.model.Page
|
|||||||
import eu.kanade.tachiyomi.source.model.SChapter
|
import eu.kanade.tachiyomi.source.model.SChapter
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
import eu.kanade.tachiyomi.source.online.ParsedHttpSource
|
import eu.kanade.tachiyomi.source.online.ParsedHttpSource
|
||||||
import keiyoushi.utils.getPreferencesLazy
|
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import kotlinx.serialization.json.jsonArray
|
import kotlinx.serialization.json.jsonArray
|
||||||
import kotlinx.serialization.json.jsonObject
|
import kotlinx.serialization.json.jsonObject
|
||||||
@ -273,7 +273,9 @@ abstract class FoolSlide(
|
|||||||
|
|
||||||
override fun imageUrlParse(document: Document) = throw UnsupportedOperationException()
|
override fun imageUrlParse(document: Document) = throw UnsupportedOperationException()
|
||||||
|
|
||||||
protected val preferences by getPreferencesLazy()
|
protected val preferences by lazy {
|
||||||
|
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)!!
|
||||||
|
}
|
||||||
|
|
||||||
override fun setupPreferenceScreen(screen: PreferenceScreen) {
|
override fun setupPreferenceScreen(screen: PreferenceScreen) {
|
||||||
CheckBoxPreference(screen.context).apply {
|
CheckBoxPreference(screen.context).apply {
|
||||||
|
@ -2,4 +2,4 @@ plugins {
|
|||||||
id("lib-multisrc")
|
id("lib-multisrc")
|
||||||
}
|
}
|
||||||
|
|
||||||
baseVersionCode = 5
|
baseVersionCode = 4
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package eu.kanade.tachiyomi.multisrc.galleryadults
|
package eu.kanade.tachiyomi.multisrc.galleryadults
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.preference.PreferenceScreen
|
import androidx.preference.PreferenceScreen
|
||||||
@ -17,7 +18,6 @@ import eu.kanade.tachiyomi.source.model.SManga
|
|||||||
import eu.kanade.tachiyomi.source.model.UpdateStrategy
|
import eu.kanade.tachiyomi.source.model.UpdateStrategy
|
||||||
import eu.kanade.tachiyomi.source.online.ParsedHttpSource
|
import eu.kanade.tachiyomi.source.online.ParsedHttpSource
|
||||||
import eu.kanade.tachiyomi.util.asJsoup
|
import eu.kanade.tachiyomi.util.asJsoup
|
||||||
import keiyoushi.utils.getPreferencesLazy
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
@ -36,6 +36,8 @@ import okhttp3.Response
|
|||||||
import org.jsoup.nodes.Document
|
import org.jsoup.nodes.Document
|
||||||
import org.jsoup.nodes.Element
|
import org.jsoup.nodes.Element
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
|
|
||||||
@ -54,7 +56,9 @@ abstract class GalleryAdults(
|
|||||||
.build()
|
.build()
|
||||||
|
|
||||||
/* Preferences */
|
/* Preferences */
|
||||||
protected val preferences: SharedPreferences by getPreferencesLazy()
|
protected val preferences: SharedPreferences by lazy {
|
||||||
|
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
||||||
|
}
|
||||||
|
|
||||||
protected open val useShortTitlePreference = true
|
protected open val useShortTitlePreference = true
|
||||||
|
|
||||||
|
@ -2,4 +2,4 @@ plugins {
|
|||||||
id("lib-multisrc")
|
id("lib-multisrc")
|
||||||
}
|
}
|
||||||
|
|
||||||
baseVersionCode = 31
|
baseVersionCode = 30
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package eu.kanade.tachiyomi.multisrc.grouple
|
package eu.kanade.tachiyomi.multisrc.grouple
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import eu.kanade.tachiyomi.network.GET
|
import eu.kanade.tachiyomi.network.GET
|
||||||
@ -13,7 +14,6 @@ import eu.kanade.tachiyomi.source.model.SChapter
|
|||||||
import eu.kanade.tachiyomi.source.model.SManga
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
import eu.kanade.tachiyomi.source.online.ParsedHttpSource
|
import eu.kanade.tachiyomi.source.online.ParsedHttpSource
|
||||||
import eu.kanade.tachiyomi.util.asJsoup
|
import eu.kanade.tachiyomi.util.asJsoup
|
||||||
import keiyoushi.utils.getPreferencesLazy
|
|
||||||
import okhttp3.Headers
|
import okhttp3.Headers
|
||||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
@ -22,6 +22,8 @@ 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.DecimalFormat
|
import java.text.DecimalFormat
|
||||||
import java.text.ParseException
|
import java.text.ParseException
|
||||||
@ -35,7 +37,9 @@ abstract class GroupLe(
|
|||||||
final override val lang: String,
|
final override val lang: String,
|
||||||
) : ConfigurableSource, ParsedHttpSource() {
|
) : ConfigurableSource, ParsedHttpSource() {
|
||||||
|
|
||||||
private val preferences: SharedPreferences by getPreferencesLazy()
|
private val preferences: SharedPreferences by lazy {
|
||||||
|
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
||||||
|
}
|
||||||
|
|
||||||
override val supportsLatest = true
|
override val supportsLatest = true
|
||||||
|
|
||||||
|
@ -2,4 +2,4 @@ plugins {
|
|||||||
id("lib-multisrc")
|
id("lib-multisrc")
|
||||||
}
|
}
|
||||||
|
|
||||||
baseVersionCode = 6
|
baseVersionCode = 5
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package eu.kanade.tachiyomi.multisrc.guya
|
package eu.kanade.tachiyomi.multisrc.guya
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import androidx.preference.ListPreference
|
import androidx.preference.ListPreference
|
||||||
@ -15,7 +16,6 @@ import eu.kanade.tachiyomi.source.model.Page
|
|||||||
import eu.kanade.tachiyomi.source.model.SChapter
|
import eu.kanade.tachiyomi.source.model.SChapter
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||||
import keiyoushi.utils.getPreferencesLazy
|
|
||||||
import okhttp3.Headers
|
import okhttp3.Headers
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
@ -25,6 +25,8 @@ import org.jsoup.Jsoup
|
|||||||
import org.jsoup.select.Evaluator
|
import org.jsoup.select.Evaluator
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import rx.schedulers.Schedulers
|
import rx.schedulers.Schedulers
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
abstract class Guya(
|
abstract class Guya(
|
||||||
override val name: String,
|
override val name: String,
|
||||||
@ -46,7 +48,9 @@ abstract class Guya(
|
|||||||
private val scanlators: ScanlatorStore = ScanlatorStore()
|
private val scanlators: ScanlatorStore = ScanlatorStore()
|
||||||
|
|
||||||
// Preferences configuration
|
// Preferences configuration
|
||||||
private val preferences: SharedPreferences by getPreferencesLazy()
|
private val preferences: SharedPreferences by lazy {
|
||||||
|
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
||||||
|
}
|
||||||
|
|
||||||
// Request builder for the "browse" page of the manga
|
// Request builder for the "browse" page of the manga
|
||||||
override fun popularMangaRequest(page: Int): Request {
|
override fun popularMangaRequest(page: Int): Request {
|
||||||
|
@ -2,7 +2,7 @@ plugins {
|
|||||||
id("lib-multisrc")
|
id("lib-multisrc")
|
||||||
}
|
}
|
||||||
|
|
||||||
baseVersionCode = 29
|
baseVersionCode = 28
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
api(project(":lib:i18n"))
|
api(project(":lib:i18n"))
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package eu.kanade.tachiyomi.multisrc.heancms
|
package eu.kanade.tachiyomi.multisrc.heancms
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import androidx.preference.EditTextPreference
|
import androidx.preference.EditTextPreference
|
||||||
import androidx.preference.PreferenceScreen
|
import androidx.preference.PreferenceScreen
|
||||||
@ -15,7 +16,6 @@ import eu.kanade.tachiyomi.source.model.Page
|
|||||||
import eu.kanade.tachiyomi.source.model.SChapter
|
import eu.kanade.tachiyomi.source.model.SChapter
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||||
import keiyoushi.utils.getPreferencesLazy
|
|
||||||
import kotlinx.serialization.decodeFromString
|
import kotlinx.serialization.decodeFromString
|
||||||
import kotlinx.serialization.encodeToString
|
import kotlinx.serialization.encodeToString
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
@ -26,6 +26,8 @@ import okhttp3.OkHttpClient
|
|||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
import kotlin.concurrent.thread
|
import kotlin.concurrent.thread
|
||||||
@ -37,7 +39,9 @@ abstract class HeanCms(
|
|||||||
protected val apiUrl: String = baseUrl.replace("://", "://api."),
|
protected val apiUrl: String = baseUrl.replace("://", "://api."),
|
||||||
) : ConfigurableSource, HttpSource() {
|
) : ConfigurableSource, HttpSource() {
|
||||||
|
|
||||||
protected val preferences: SharedPreferences by getPreferencesLazy()
|
protected val preferences: SharedPreferences by lazy {
|
||||||
|
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
||||||
|
}
|
||||||
|
|
||||||
override val supportsLatest = true
|
override val supportsLatest = true
|
||||||
|
|
||||||
|
@ -2,4 +2,4 @@ plugins {
|
|||||||
id("lib-multisrc")
|
id("lib-multisrc")
|
||||||
}
|
}
|
||||||
|
|
||||||
baseVersionCode = 3
|
baseVersionCode = 2
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package eu.kanade.tachiyomi.multisrc.hentaihand
|
package eu.kanade.tachiyomi.multisrc.hentaihand
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.text.InputType
|
import android.text.InputType
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
@ -15,7 +16,6 @@ import eu.kanade.tachiyomi.source.model.Page
|
|||||||
import eu.kanade.tachiyomi.source.model.SChapter
|
import eu.kanade.tachiyomi.source.model.SChapter
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||||
import keiyoushi.utils.getPreferencesLazy
|
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import kotlinx.serialization.json.JsonObject
|
import kotlinx.serialization.json.JsonObject
|
||||||
import kotlinx.serialization.json.buildJsonObject
|
import kotlinx.serialization.json.buildJsonObject
|
||||||
@ -32,6 +32,8 @@ import okhttp3.RequestBody.Companion.toRequestBody
|
|||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import rx.schedulers.Schedulers
|
import rx.schedulers.Schedulers
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
@ -316,7 +318,9 @@ abstract class HentaiHand(
|
|||||||
|
|
||||||
// Preferences
|
// Preferences
|
||||||
|
|
||||||
private val preferences: SharedPreferences by getPreferencesLazy()
|
private val preferences: SharedPreferences by lazy {
|
||||||
|
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
||||||
|
}
|
||||||
|
|
||||||
override fun setupPreferenceScreen(screen: PreferenceScreen) {
|
override fun setupPreferenceScreen(screen: PreferenceScreen) {
|
||||||
screen.addPreference(screen.editTextPreference(USERNAME_TITLE, USERNAME_DEFAULT, username))
|
screen.addPreference(screen.editTextPreference(USERNAME_TITLE, USERNAME_DEFAULT, username))
|
||||||
|
@ -2,4 +2,4 @@ plugins {
|
|||||||
id("lib-multisrc")
|
id("lib-multisrc")
|
||||||
}
|
}
|
||||||
|
|
||||||
baseVersionCode = 8
|
baseVersionCode = 6
|
||||||
|
@ -93,24 +93,21 @@ class Chapter(
|
|||||||
private val id: Int,
|
private val id: Int,
|
||||||
private val slug: String,
|
private val slug: String,
|
||||||
private val number: JsonPrimitive,
|
private val number: JsonPrimitive,
|
||||||
|
private val createdBy: Name,
|
||||||
private val createdAt: String,
|
private val createdAt: String,
|
||||||
private val chapterStatus: String,
|
private val chapterStatus: String,
|
||||||
private val isAccessible: Boolean,
|
private val isAccessible: Boolean,
|
||||||
private val isLocked: Boolean? = false,
|
|
||||||
private val isTimeLocked: Boolean? = false,
|
|
||||||
private val mangaPost: ChapterPostDetails,
|
private val mangaPost: ChapterPostDetails,
|
||||||
) {
|
) {
|
||||||
fun isPublic() = chapterStatus == "PUBLIC"
|
fun isPublic() = chapterStatus == "PUBLIC"
|
||||||
|
|
||||||
fun isAccessible() = isAccessible
|
fun isAccessible() = isAccessible
|
||||||
|
|
||||||
fun isLocked() = (isLocked == true) || (isTimeLocked == true)
|
|
||||||
|
|
||||||
fun toSChapter(mangaSlug: String?) = SChapter.create().apply {
|
fun toSChapter(mangaSlug: String?) = SChapter.create().apply {
|
||||||
val prefix = if (isLocked()) "🔒 " else ""
|
|
||||||
val seriesSlug = mangaSlug ?: mangaPost.slug
|
val seriesSlug = mangaSlug ?: mangaPost.slug
|
||||||
url = "/series/$seriesSlug/$slug#$id"
|
url = "/series/$seriesSlug/$slug#$id"
|
||||||
name = "${prefix}Chapter $number"
|
name = "Chapter $number"
|
||||||
|
scanlator = createdBy.name
|
||||||
date_upload = try {
|
date_upload = try {
|
||||||
dateFormat.parse(createdAt)!!.time
|
dateFormat.parse(createdAt)!!.time
|
||||||
} catch (_: ParseException) {
|
} catch (_: ParseException) {
|
||||||
|
@ -1,10 +1,6 @@
|
|||||||
package eu.kanade.tachiyomi.multisrc.iken
|
package eu.kanade.tachiyomi.multisrc.iken
|
||||||
|
|
||||||
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.source.ConfigurableSource
|
|
||||||
import eu.kanade.tachiyomi.source.model.Filter
|
import eu.kanade.tachiyomi.source.model.Filter
|
||||||
import eu.kanade.tachiyomi.source.model.FilterList
|
import eu.kanade.tachiyomi.source.model.FilterList
|
||||||
import eu.kanade.tachiyomi.source.model.MangasPage
|
import eu.kanade.tachiyomi.source.model.MangasPage
|
||||||
@ -13,34 +9,32 @@ import eu.kanade.tachiyomi.source.model.SChapter
|
|||||||
import eu.kanade.tachiyomi.source.model.SManga
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||||
import eu.kanade.tachiyomi.util.asJsoup
|
import eu.kanade.tachiyomi.util.asJsoup
|
||||||
import keiyoushi.utils.getPreferencesLazy
|
import kotlinx.serialization.decodeFromString
|
||||||
import keiyoushi.utils.parseAs
|
import kotlinx.serialization.json.Json
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
import org.jsoup.nodes.Document
|
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
|
||||||
abstract class Iken(
|
abstract class Iken(
|
||||||
override val name: String,
|
override val name: String,
|
||||||
override val lang: String,
|
override val lang: String,
|
||||||
override val baseUrl: String,
|
override val baseUrl: String,
|
||||||
val apiUrl: String = baseUrl,
|
) : HttpSource() {
|
||||||
) : HttpSource(), ConfigurableSource {
|
|
||||||
|
|
||||||
override val supportsLatest = true
|
override val supportsLatest = true
|
||||||
|
|
||||||
override val client = network.cloudflareClient
|
override val client = network.cloudflareClient
|
||||||
|
|
||||||
private val preferences: SharedPreferences by getPreferencesLazy()
|
private val json by injectLazy<Json>()
|
||||||
|
|
||||||
override fun headersBuilder() = super.headersBuilder()
|
override fun headersBuilder() = super.headersBuilder()
|
||||||
.set("Referer", "$baseUrl/")
|
.set("Referer", "$baseUrl/")
|
||||||
|
|
||||||
private var genres = emptyList<Pair<String, String>>()
|
private var genres = emptyList<Pair<String, String>>()
|
||||||
protected val titleCache by lazy {
|
protected val titleCache by lazy {
|
||||||
val response = client.newCall(GET("$apiUrl/api/query?perPage=9999", headers)).execute()
|
val response = client.newCall(GET("$baseUrl/api/query?perPage=9999", headers)).execute()
|
||||||
val data = response.parseAs<SearchResponse>()
|
val data = response.parseAs<SearchResponse>()
|
||||||
|
|
||||||
data.posts
|
data.posts
|
||||||
@ -71,7 +65,7 @@ abstract class Iken(
|
|||||||
override fun latestUpdatesParse(response: Response) = searchMangaParse(response)
|
override fun latestUpdatesParse(response: Response) = searchMangaParse(response)
|
||||||
|
|
||||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||||
val url = "$apiUrl/api/query".toHttpUrl().newBuilder().apply {
|
val url = "$baseUrl/api/query".toHttpUrl().newBuilder().apply {
|
||||||
addQueryParameter("page", page.toString())
|
addQueryParameter("page", page.toString())
|
||||||
addQueryParameter("perPage", perPage.toString())
|
addQueryParameter("perPage", perPage.toString())
|
||||||
addQueryParameter("searchTerm", query.trim())
|
addQueryParameter("searchTerm", query.trim())
|
||||||
@ -120,76 +114,35 @@ abstract class Iken(
|
|||||||
throw UnsupportedOperationException()
|
throw UnsupportedOperationException()
|
||||||
|
|
||||||
override fun chapterListRequest(manga: SManga): Request {
|
override fun chapterListRequest(manga: SManga): Request {
|
||||||
return GET("$baseUrl/series/${manga.url}", headers)
|
val id = manga.url.substringAfterLast("#")
|
||||||
|
val url = "$baseUrl/api/chapters?postId=$id&skip=0&take=1000&order=desc&userid="
|
||||||
|
|
||||||
|
return GET(url, headers)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun chapterListParse(response: Response): List<SChapter> {
|
override fun chapterListParse(response: Response): List<SChapter> {
|
||||||
val userId = userIdRegex.find(response.body.string())?.groupValues?.get(1) ?: ""
|
val data = response.parseAs<Post<ChapterListResponse>>()
|
||||||
|
|
||||||
val id = response.request.url.fragment!!
|
|
||||||
val chapterUrl = "$apiUrl/api/chapters?postId=$id&skip=0&take=1000&order=desc&userid=$userId"
|
|
||||||
val chapterResponse = client.newCall(GET(chapterUrl, headers)).execute()
|
|
||||||
|
|
||||||
val data = chapterResponse.parseAs<Post<ChapterListResponse>>()
|
|
||||||
|
|
||||||
assert(!data.post.isNovel) { "Novels are unsupported" }
|
assert(!data.post.isNovel) { "Novels are unsupported" }
|
||||||
|
|
||||||
return data.post.chapters
|
return data.post.chapters
|
||||||
.filter { it.isPublic() && (it.isAccessible() || (preferences.getBoolean(showLockedChapterPrefKey, false) && it.isLocked())) }
|
.filter { it.isPublic() && it.isAccessible() }
|
||||||
.map { it.toSChapter(data.post.slug) }
|
.map { it.toSChapter(data.post.slug) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun pageListParse(response: Response): List<Page> {
|
override fun pageListParse(response: Response): List<Page> {
|
||||||
val document = response.asJsoup()
|
val document = response.asJsoup()
|
||||||
|
|
||||||
if (document.selectFirst("svg.lucide-lock") != null) {
|
return document.select("main section img").mapIndexed { idx, img ->
|
||||||
throw Exception("Unlock chapter in webview")
|
Page(idx, imageUrl = img.absUrl("src"))
|
||||||
}
|
}
|
||||||
|
|
||||||
return document.getNextJson("images").parseAs<List<PageParseDto>>().mapIndexed { idx, p ->
|
|
||||||
Page(idx, imageUrl = p.url)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
class PageParseDto(
|
|
||||||
val url: String,
|
|
||||||
)
|
|
||||||
|
|
||||||
override fun setupPreferenceScreen(screen: PreferenceScreen) {
|
|
||||||
SwitchPreferenceCompat(screen.context).apply {
|
|
||||||
key = showLockedChapterPrefKey
|
|
||||||
title = "Show locked chapters"
|
|
||||||
setDefaultValue(false)
|
|
||||||
}.also(screen::addPreference)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun imageUrlParse(response: Response) =
|
override fun imageUrlParse(response: Response) =
|
||||||
throw UnsupportedOperationException()
|
throw UnsupportedOperationException()
|
||||||
|
|
||||||
protected fun Document.getNextJson(key: String): String {
|
private inline fun <reified T> Response.parseAs(): T =
|
||||||
val data = selectFirst("script:containsData($key)")
|
json.decodeFromString(body.string())
|
||||||
?.data()
|
|
||||||
?: throw Exception("Unable to retrieve NEXT data")
|
|
||||||
|
|
||||||
val keyIndex = data.indexOf(key)
|
|
||||||
val start = data.indexOf('[', keyIndex)
|
|
||||||
|
|
||||||
var depth = 1
|
|
||||||
var i = start + 1
|
|
||||||
|
|
||||||
while (i < data.length && depth > 0) {
|
|
||||||
when (data[i]) {
|
|
||||||
'[' -> depth++
|
|
||||||
']' -> depth--
|
|
||||||
}
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
|
|
||||||
return "\"${data.substring(start, i)}\"".parseAs<String>()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private const val perPage = 18
|
private const val perPage = 18
|
||||||
private const val showLockedChapterPrefKey = "pref_show_locked_chapters"
|
|
||||||
private val userIdRegex = Regex(""""user\\":\{\\"id\\":\\"([^"']+)\\"""")
|
|
||||||
|
@ -2,4 +2,4 @@ plugins {
|
|||||||
id("lib-multisrc")
|
id("lib-multisrc")
|
||||||
}
|
}
|
||||||
|
|
||||||
baseVersionCode = 20
|
baseVersionCode = 18
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package eu.kanade.tachiyomi.multisrc.kemono
|
package eu.kanade.tachiyomi.multisrc.kemono
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
import androidx.preference.ListPreference
|
import androidx.preference.ListPreference
|
||||||
import androidx.preference.PreferenceScreen
|
import androidx.preference.PreferenceScreen
|
||||||
import androidx.preference.SwitchPreferenceCompat
|
import androidx.preference.SwitchPreferenceCompat
|
||||||
@ -14,12 +15,13 @@ import eu.kanade.tachiyomi.source.model.Page
|
|||||||
import eu.kanade.tachiyomi.source.model.SChapter
|
import eu.kanade.tachiyomi.source.model.SChapter
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||||
import keiyoushi.utils.getPreferences
|
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import kotlinx.serialization.json.decodeFromStream
|
import kotlinx.serialization.json.decodeFromStream
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
import java.lang.Thread.sleep
|
import java.lang.Thread.sleep
|
||||||
import java.util.TimeZone
|
import java.util.TimeZone
|
||||||
@ -32,14 +34,15 @@ open class Kemono(
|
|||||||
) : HttpSource(), ConfigurableSource {
|
) : HttpSource(), ConfigurableSource {
|
||||||
override val supportsLatest = true
|
override val supportsLatest = true
|
||||||
|
|
||||||
override val client = network.cloudflareClient.newBuilder().rateLimit(1).build()
|
override val client = network.client.newBuilder().rateLimit(1).build()
|
||||||
|
|
||||||
override fun headersBuilder() = super.headersBuilder()
|
override fun headersBuilder() = super.headersBuilder()
|
||||||
.add("Referer", "$baseUrl/")
|
.add("Referer", "$baseUrl/")
|
||||||
|
|
||||||
private val json: Json by injectLazy()
|
private val json: Json by injectLazy()
|
||||||
|
|
||||||
private val preferences = getPreferences()
|
private val preferences =
|
||||||
|
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
||||||
|
|
||||||
private val apiPath = "api/v1"
|
private val apiPath = "api/v1"
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@ plugins {
|
|||||||
id("lib-multisrc")
|
id("lib-multisrc")
|
||||||
}
|
}
|
||||||
|
|
||||||
baseVersionCode = 15
|
baseVersionCode = 13
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
api(project(":lib:i18n"))
|
api(project(":lib:i18n"))
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package eu.kanade.tachiyomi.multisrc.keyoapp
|
package eu.kanade.tachiyomi.multisrc.keyoapp
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import androidx.preference.PreferenceScreen
|
import androidx.preference.PreferenceScreen
|
||||||
import androidx.preference.SwitchPreferenceCompat
|
import androidx.preference.SwitchPreferenceCompat
|
||||||
@ -14,7 +15,6 @@ import eu.kanade.tachiyomi.source.model.SChapter
|
|||||||
import eu.kanade.tachiyomi.source.model.SManga
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
import eu.kanade.tachiyomi.source.online.ParsedHttpSource
|
import eu.kanade.tachiyomi.source.online.ParsedHttpSource
|
||||||
import eu.kanade.tachiyomi.util.asJsoup
|
import eu.kanade.tachiyomi.util.asJsoup
|
||||||
import keiyoushi.utils.getPreferencesLazy
|
|
||||||
import kotlinx.serialization.decodeFromString
|
import kotlinx.serialization.decodeFromString
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||||
@ -23,6 +23,8 @@ import okhttp3.Request
|
|||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
import org.jsoup.nodes.Document
|
import org.jsoup.nodes.Document
|
||||||
import org.jsoup.nodes.Element
|
import org.jsoup.nodes.Element
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
import java.text.ParseException
|
import java.text.ParseException
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
@ -35,7 +37,9 @@ abstract class Keyoapp(
|
|||||||
final override val lang: String,
|
final override val lang: String,
|
||||||
) : ParsedHttpSource(), ConfigurableSource {
|
) : ParsedHttpSource(), ConfigurableSource {
|
||||||
|
|
||||||
protected val preferences: SharedPreferences by getPreferencesLazy()
|
protected val preferences: SharedPreferences by lazy {
|
||||||
|
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
||||||
|
}
|
||||||
|
|
||||||
override val supportsLatest = true
|
override val supportsLatest = true
|
||||||
|
|
||||||
@ -59,8 +63,7 @@ abstract class Keyoapp(
|
|||||||
|
|
||||||
override fun popularMangaRequest(page: Int): Request = GET(baseUrl, headers)
|
override fun popularMangaRequest(page: Int): Request = GET(baseUrl, headers)
|
||||||
|
|
||||||
override fun popularMangaSelector(): String =
|
override fun popularMangaSelector(): String = "div.flex-col div.grid > div.group.border"
|
||||||
"div.flex-col div.grid > div.group.border, div:has(h2:contains(Trending)) + div .group.overflow-hidden.grid"
|
|
||||||
|
|
||||||
override fun popularMangaFromElement(element: Element): SManga = SManga.create().apply {
|
override fun popularMangaFromElement(element: Element): SManga = SManga.create().apply {
|
||||||
thumbnail_url = element.getImageUrl("*[style*=background-image]")
|
thumbnail_url = element.getImageUrl("*[style*=background-image]")
|
||||||
@ -188,7 +191,7 @@ abstract class Keyoapp(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected open fun genresRequest(): Request = GET("$baseUrl/series/", headers)
|
private fun genresRequest(): Request = GET("$baseUrl/series/", headers)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the genres from the search page document.
|
* Get the genres from the search page document.
|
||||||
|
@ -2,4 +2,4 @@ plugins {
|
|||||||
id("lib-multisrc")
|
id("lib-multisrc")
|
||||||
}
|
}
|
||||||
|
|
||||||
baseVersionCode = 2
|
baseVersionCode = 1
|
||||||
|
@ -111,11 +111,7 @@ abstract class LectorMoe(
|
|||||||
override fun chapterListParse(response: Response): List<SChapter> {
|
override fun chapterListParse(response: Response): List<SChapter> {
|
||||||
val result = json.decodeFromString<Data<SeriesDto>>(response.body.string())
|
val result = json.decodeFromString<Data<SeriesDto>>(response.body.string())
|
||||||
val seriesSlug = result.data.slug
|
val seriesSlug = result.data.slug
|
||||||
return result.data.chapters
|
return result.data.chapters?.map { it.toSChapter(seriesSlug) } ?: emptyList()
|
||||||
?.filter { it.subscribersOnly.not() }
|
|
||||||
?.map { it.toSChapter(seriesSlug) }
|
|
||||||
?.filter { it.date_upload < System.currentTimeMillis() }
|
|
||||||
?: emptyList()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun pageListRequest(chapter: SChapter): Request {
|
override fun pageListRequest(chapter: SChapter): Request {
|
||||||
|
@ -7,7 +7,6 @@ import kotlinx.serialization.Serializable
|
|||||||
import java.text.ParseException
|
import java.text.ParseException
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
import java.util.TimeZone
|
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
class Data<T>(val data: T)
|
class Data<T>(val data: T)
|
||||||
@ -54,21 +53,18 @@ class SeriesAuthorDto(
|
|||||||
val name: String,
|
val name: String,
|
||||||
)
|
)
|
||||||
|
|
||||||
private val dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US).apply {
|
private val dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US)
|
||||||
timeZone = TimeZone.getTimeZone("UTC")
|
|
||||||
}
|
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
class SeriesChapterDto(
|
class SeriesChapterDto(
|
||||||
private val title: String,
|
private val title: String,
|
||||||
private val number: Float,
|
private val number: Float,
|
||||||
private val releasedAt: String,
|
private val createdAt: String,
|
||||||
val subscribersOnly: Boolean,
|
|
||||||
) {
|
) {
|
||||||
fun toSChapter(seriesSlug: String) = SChapter.create().apply {
|
fun toSChapter(seriesSlug: String) = SChapter.create().apply {
|
||||||
name = "Capítulo ${number.toString().removeSuffix(".0")} - $title"
|
name = "Capítulo ${number.toString().removeSuffix(".0")} - $title"
|
||||||
date_upload = try {
|
date_upload = try {
|
||||||
dateFormat.parse(releasedAt)?.time ?: 0L
|
dateFormat.parse(createdAt)?.time ?: 0L
|
||||||
} catch (_: ParseException) {
|
} catch (_: ParseException) {
|
||||||
0L
|
0L
|
||||||
}
|
}
|
||||||
|
@ -2,4 +2,4 @@ plugins {
|
|||||||
id("lib-multisrc")
|
id("lib-multisrc")
|
||||||
}
|
}
|
||||||
|
|
||||||
baseVersionCode = 36
|
baseVersionCode = 35
|
||||||
|
@ -2,6 +2,7 @@ package eu.kanade.tachiyomi.multisrc.libgroup
|
|||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
|
import android.content.SharedPreferences
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
@ -23,7 +24,6 @@ import eu.kanade.tachiyomi.source.model.Page
|
|||||||
import eu.kanade.tachiyomi.source.model.SChapter
|
import eu.kanade.tachiyomi.source.model.SChapter
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||||
import keiyoushi.utils.getPreferencesLazy
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@ -57,10 +57,9 @@ abstract class LibGroup(
|
|||||||
encodeDefaults = true
|
encodeDefaults = true
|
||||||
}
|
}
|
||||||
|
|
||||||
private val preferences by getPreferencesLazy {
|
private val preferences: SharedPreferences by lazy {
|
||||||
if (getString(SERVER_PREF, "main") == "fourth") {
|
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
||||||
edit().putString(SERVER_PREF, "secondary").apply()
|
.migrateOldImageServer()
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override val supportsLatest = true
|
override val supportsLatest = true
|
||||||
@ -686,4 +685,11 @@ abstract class LibGroup(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// api changed id of servers, remap SERVER_PREF old("fourth") to new("secondary")
|
||||||
|
private fun SharedPreferences.migrateOldImageServer(): SharedPreferences {
|
||||||
|
if (getString(SERVER_PREF, "main") != "fourth") return this
|
||||||
|
edit().putString(SERVER_PREF, "secondary").apply()
|
||||||
|
return this
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ plugins {
|
|||||||
id("lib-multisrc")
|
id("lib-multisrc")
|
||||||
}
|
}
|
||||||
|
|
||||||
baseVersionCode = 5
|
baseVersionCode = 4
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
api(project(":lib:i18n"))
|
api(project(":lib:i18n"))
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package eu.kanade.tachiyomi.multisrc.machinetranslations
|
package eu.kanade.tachiyomi.multisrc.machinetranslations
|
||||||
|
|
||||||
|
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 android.widget.Toast
|
||||||
@ -19,7 +20,6 @@ import eu.kanade.tachiyomi.source.model.Page
|
|||||||
import eu.kanade.tachiyomi.source.model.SChapter
|
import eu.kanade.tachiyomi.source.model.SChapter
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
import eu.kanade.tachiyomi.source.online.ParsedHttpSource
|
import eu.kanade.tachiyomi.source.online.ParsedHttpSource
|
||||||
import keiyoushi.utils.getPreferencesLazy
|
|
||||||
import kotlinx.serialization.decodeFromString
|
import kotlinx.serialization.decodeFromString
|
||||||
import kotlinx.serialization.encodeToString
|
import kotlinx.serialization.encodeToString
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
@ -30,6 +30,8 @@ import okhttp3.Request
|
|||||||
import org.jsoup.nodes.Document
|
import org.jsoup.nodes.Document
|
||||||
import org.jsoup.nodes.Element
|
import org.jsoup.nodes.Element
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Calendar
|
import java.util.Calendar
|
||||||
@ -49,7 +51,9 @@ abstract class MachineTranslations(
|
|||||||
|
|
||||||
override val lang = language.lang
|
override val lang = language.lang
|
||||||
|
|
||||||
protected val preferences: SharedPreferences by getPreferencesLazy()
|
protected val preferences: SharedPreferences by lazy {
|
||||||
|
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A flag that tracks whether the settings have been changed. It is used to indicate if
|
* A flag that tracks whether the settings have been changed. It is used to indicate if
|
||||||
|
@ -2,7 +2,7 @@ plugins {
|
|||||||
id("lib-multisrc")
|
id("lib-multisrc")
|
||||||
}
|
}
|
||||||
|
|
||||||
baseVersionCode = 41
|
baseVersionCode = 40
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
api(project(":lib:cryptoaes"))
|
api(project(":lib:cryptoaes"))
|
||||||
|
@ -160,7 +160,7 @@ abstract class Madara(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// exclude/filter bilibili manga from list
|
// exclude/filter bilibili manga from list
|
||||||
override fun popularMangaSelector() = "div.page-item-detail:not(:has(a[href*='bilibilicomics.com']))$mangaEntrySelector , .manga__item"
|
override fun popularMangaSelector() = "div.page-item-detail:not(:has(a[href*='bilibilicomics.com']))$mangaEntrySelector"
|
||||||
|
|
||||||
open val popularMangaUrlSelector = "div.post-title a"
|
open val popularMangaUrlSelector = "div.post-title a"
|
||||||
|
|
||||||
@ -584,7 +584,7 @@ abstract class Madara(
|
|||||||
return MangasPage(entries, hasNextPage)
|
return MangasPage(entries, hasNextPage)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun searchMangaSelector() = "div.c-tabs-item__content , .manga__item"
|
override fun searchMangaSelector() = "div.c-tabs-item__content"
|
||||||
|
|
||||||
protected open val searchMangaUrlSelector = "div.post-title a"
|
protected open val searchMangaUrlSelector = "div.post-title a"
|
||||||
|
|
||||||
@ -754,7 +754,7 @@ abstract class Madara(
|
|||||||
open val mangaDetailsSelectorTitle = "div.post-title h3, div.post-title h1, #manga-title > h1"
|
open val mangaDetailsSelectorTitle = "div.post-title h3, div.post-title h1, #manga-title > h1"
|
||||||
open val mangaDetailsSelectorAuthor = "div.author-content > a, div.manga-authors > a"
|
open val mangaDetailsSelectorAuthor = "div.author-content > a, div.manga-authors > a"
|
||||||
open val mangaDetailsSelectorArtist = "div.artist-content > a"
|
open val mangaDetailsSelectorArtist = "div.artist-content > a"
|
||||||
open val mangaDetailsSelectorStatus = "div.summary-content, div.summary-heading:contains(Status) + div"
|
open val mangaDetailsSelectorStatus = "div.summary-content"
|
||||||
open val mangaDetailsSelectorDescription = "div.description-summary div.summary__content, div.summary_content div.post-content_item > h5 + div, div.summary_content div.manga-excerpt"
|
open val mangaDetailsSelectorDescription = "div.description-summary div.summary__content, div.summary_content div.post-content_item > h5 + div, div.summary_content div.manga-excerpt"
|
||||||
open val mangaDetailsSelectorThumbnail = "div.summary_image img"
|
open val mangaDetailsSelectorThumbnail = "div.summary_image img"
|
||||||
open val mangaDetailsSelectorGenre = "div.genres-content a"
|
open val mangaDetailsSelectorGenre = "div.genres-content a"
|
||||||
|
@ -2,4 +2,4 @@ plugins {
|
|||||||
id("lib-multisrc")
|
id("lib-multisrc")
|
||||||
}
|
}
|
||||||
|
|
||||||
baseVersionCode = 18
|
baseVersionCode = 17
|
||||||
|
@ -41,10 +41,6 @@ abstract class MadTheme(
|
|||||||
.rateLimit(1, 1, TimeUnit.SECONDS)
|
.rateLimit(1, 1, TimeUnit.SECONDS)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
protected open val useLegacyApi = false
|
|
||||||
|
|
||||||
protected open val useSlugSearch = false
|
|
||||||
|
|
||||||
// TODO: better cookie sharing
|
// TODO: better cookie sharing
|
||||||
// TODO: don't count cached responses against rate limit
|
// TODO: don't count cached responses against rate limit
|
||||||
private val chapterClient: OkHttpClient = network.cloudflareClient.newBuilder()
|
private val chapterClient: OkHttpClient = network.cloudflareClient.newBuilder()
|
||||||
@ -181,57 +177,59 @@ abstract class MadTheme(
|
|||||||
|
|
||||||
val document = response.asJsoup()
|
val document = response.asJsoup()
|
||||||
|
|
||||||
|
// Need the total chapters to check against the request
|
||||||
|
val totalChapters = document.selectFirst(".title span:containsOwn(CHAPTERS \\()")?.text()
|
||||||
|
?.substringAfter("(")
|
||||||
|
?.substringBefore(")")
|
||||||
|
?.toIntOrNull()
|
||||||
|
|
||||||
val script = document.selectFirst("script:containsData(bookId)")
|
val script = document.selectFirst("script:containsData(bookId)")
|
||||||
?: throw Exception("Cannot find script")
|
?: throw Exception("Cannot find script")
|
||||||
val bookId = script.data().substringAfter("bookId = ").substringBefore(";")
|
val bookId = script.data().substringAfter("bookId = ").substringBefore(";")
|
||||||
val bookSlug = script.data().substringAfter("bookSlug = \"").substringBefore("\";")
|
val bookSlug = script.data().substringAfter("bookSlug = \"").substringBefore("\";")
|
||||||
|
|
||||||
var chaptersList = document.select(chapterListSelector()).map { chapterFromElement(it) }
|
// Use slug search by default
|
||||||
|
val slugRequest = chapterClient.newCall(GET(buildChapterUrl(bookSlug), headers)).execute()
|
||||||
val fetchApi = document.selectFirst("div#show-more-chapters > span")
|
if (!slugRequest.isSuccessful) {
|
||||||
?.attr("onclick")?.equals("getChapters()")
|
throw Exception("HTTP error ${slugRequest.code}")
|
||||||
?: false
|
|
||||||
|
|
||||||
if (fetchApi) {
|
|
||||||
val apiChapters = client.newCall(GET(buildChapterUrl(bookId, bookSlug), headers)).execute()
|
|
||||||
.asJsoup().select(chapterListSelector()).map { chapterFromElement(it) }
|
|
||||||
|
|
||||||
val cutIndex = chaptersList.indexOfFirst { chapter ->
|
|
||||||
apiChapters.any { it.url == chapter.url }
|
|
||||||
}.takeIf { it != -1 } ?: chaptersList.size
|
|
||||||
|
|
||||||
chaptersList = (chaptersList.subList(0, cutIndex) + apiChapters)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return chaptersList
|
var finalDocument = slugRequest.asJsoup().select(chapterListSelector())
|
||||||
|
|
||||||
|
if (totalChapters != null && finalDocument.size < totalChapters) {
|
||||||
|
val idRequest = chapterClient.newCall(GET(buildChapterUrl(bookId), headers)).execute()
|
||||||
|
finalDocument = idRequest.asJsoup().select(chapterListSelector())
|
||||||
|
}
|
||||||
|
|
||||||
|
return finalDocument.map {
|
||||||
|
SChapter.create().apply {
|
||||||
|
url = it.selectFirst("a")!!.absUrl("href").removePrefix(baseUrl)
|
||||||
|
name = it.selectFirst(".chapter-title")!!.text()
|
||||||
|
date_upload = parseChapterDate(it.selectFirst(".chapter-update")?.text())
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildChapterUrl(mangaId: String, mangaSlug: String): HttpUrl {
|
private fun buildChapterUrl(fetchByParam: String): HttpUrl {
|
||||||
return baseUrl.toHttpUrl().newBuilder().apply {
|
return baseUrl.toHttpUrl().newBuilder().apply {
|
||||||
addPathSegment("api")
|
addPathSegment("api")
|
||||||
addPathSegment("manga")
|
addPathSegment("manga")
|
||||||
addPathSegment(if (useSlugSearch) mangaSlug else mangaId)
|
addPathSegment(fetchByParam)
|
||||||
addPathSegment("chapters")
|
addPathSegment("chapters")
|
||||||
addQueryParameter("source", "detail")
|
addQueryParameter("source", "detail")
|
||||||
}.build()
|
}.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun chapterListRequest(manga: SManga): Request {
|
override fun chapterListRequest(manga: SManga): Request =
|
||||||
if (useLegacyApi) {
|
MANGA_ID_REGEX.find(manga.url)?.groupValues?.get(1)?.let { mangaId ->
|
||||||
val mangaId = MANGA_ID_REGEX.find(manga.url)?.groupValues?.get(1)
|
val url = "$baseUrl/service/backend/chaplist/".toHttpUrl().newBuilder()
|
||||||
val url = mangaId?.let {
|
.addQueryParameter("manga_id", mangaId)
|
||||||
"$baseUrl/service/backend/chaplist/".toHttpUrl().newBuilder()
|
.addQueryParameter("manga_name", manga.title)
|
||||||
.addQueryParameter("manga_id", it)
|
.fragment("idFound")
|
||||||
.addQueryParameter("manga_name", manga.title)
|
.build()
|
||||||
.fragment("idFound")
|
|
||||||
.build()
|
|
||||||
.toString()
|
|
||||||
} ?: (baseUrl + manga.url)
|
|
||||||
|
|
||||||
return GET(url, headers)
|
GET(url, headers)
|
||||||
}
|
} ?: GET("$baseUrl${manga.url}", headers)
|
||||||
return GET(baseUrl + manga.url, headers)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun searchMangaParse(response: Response): MangasPage {
|
override fun searchMangaParse(response: Response): MangasPage {
|
||||||
if (genresList == null) {
|
if (genresList == null) {
|
||||||
|
@ -2,4 +2,4 @@ plugins {
|
|||||||
id("lib-multisrc")
|
id("lib-multisrc")
|
||||||
}
|
}
|
||||||
|
|
||||||
baseVersionCode = 6
|
baseVersionCode = 5
|
||||||
|
@ -1,11 +1,7 @@
|
|||||||
package eu.kanade.tachiyomi.multisrc.mangabox
|
package eu.kanade.tachiyomi.multisrc.mangabox
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.content.SharedPreferences
|
|
||||||
import androidx.preference.ListPreference
|
|
||||||
import androidx.preference.PreferenceScreen
|
|
||||||
import eu.kanade.tachiyomi.network.GET
|
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.Filter
|
||||||
import eu.kanade.tachiyomi.source.model.FilterList
|
import eu.kanade.tachiyomi.source.model.FilterList
|
||||||
import eu.kanade.tachiyomi.source.model.Page
|
import eu.kanade.tachiyomi.source.model.Page
|
||||||
@ -13,144 +9,42 @@ import eu.kanade.tachiyomi.source.model.SChapter
|
|||||||
import eu.kanade.tachiyomi.source.model.SManga
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
import eu.kanade.tachiyomi.source.online.ParsedHttpSource
|
import eu.kanade.tachiyomi.source.online.ParsedHttpSource
|
||||||
import eu.kanade.tachiyomi.util.asJsoup
|
import eu.kanade.tachiyomi.util.asJsoup
|
||||||
import keiyoushi.utils.getPreferencesLazy
|
|
||||||
import keiyoushi.utils.tryParse
|
|
||||||
import okhttp3.Headers
|
import okhttp3.Headers
|
||||||
import okhttp3.HttpUrl
|
|
||||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||||
import okhttp3.Interceptor
|
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
import okio.IOException
|
|
||||||
import org.jsoup.nodes.Document
|
import org.jsoup.nodes.Document
|
||||||
import org.jsoup.nodes.Element
|
import org.jsoup.nodes.Element
|
||||||
|
import java.text.ParseException
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.Calendar
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
import java.util.TimeZone
|
import java.util.concurrent.TimeUnit
|
||||||
import java.util.regex.Pattern
|
|
||||||
|
|
||||||
|
// Based off of Mangakakalot 1.2.8
|
||||||
abstract class MangaBox(
|
abstract class MangaBox(
|
||||||
override val name: String,
|
override val name: String,
|
||||||
private val mirrorEntries: Array<String>,
|
override val baseUrl: String,
|
||||||
override val lang: String,
|
override val lang: String,
|
||||||
private val dateFormat: SimpleDateFormat = SimpleDateFormat(
|
private val dateformat: SimpleDateFormat = SimpleDateFormat("MMM-dd-yy", Locale.ENGLISH),
|
||||||
"MMM-dd-yyyy HH:mm",
|
) : ParsedHttpSource() {
|
||||||
Locale.ENGLISH,
|
|
||||||
).apply {
|
|
||||||
timeZone = TimeZone.getTimeZone("UTC")
|
|
||||||
},
|
|
||||||
) : ParsedHttpSource(), ConfigurableSource {
|
|
||||||
|
|
||||||
override val supportsLatest = true
|
override val supportsLatest = true
|
||||||
|
|
||||||
override val baseUrl: String get() = mirror
|
|
||||||
|
|
||||||
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
|
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
|
||||||
.addInterceptor(::useAltCdnInterceptor)
|
.connectTimeout(15, TimeUnit.SECONDS)
|
||||||
|
.readTimeout(30, TimeUnit.SECONDS)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
private fun SharedPreferences.getMirrorPref(): String =
|
|
||||||
getString(PREF_USE_MIRROR, mirrorEntries[0])!!
|
|
||||||
|
|
||||||
private val preferences: SharedPreferences by getPreferencesLazy {
|
|
||||||
// if current mirror is not in mirrorEntries, set default
|
|
||||||
if (getMirrorPref() !in mirrorEntries.map { "${URL_PREFIX}$it" }) {
|
|
||||||
edit().putString(PREF_USE_MIRROR, "${URL_PREFIX}${mirrorEntries[0]}").apply()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private var mirror = ""
|
|
||||||
get() {
|
|
||||||
if (field.isNotEmpty()) {
|
|
||||||
return field
|
|
||||||
}
|
|
||||||
|
|
||||||
field = preferences.getMirrorPref()
|
|
||||||
return field
|
|
||||||
}
|
|
||||||
|
|
||||||
private val cdnSet =
|
|
||||||
MangaBoxLinkedCdnSet() // Stores all unique CDNs that the extension can use to retrieve chapter images
|
|
||||||
|
|
||||||
private class MangaBoxFallBackTag // Custom empty class tag to use as an identifier that the specific request is fallback-able
|
|
||||||
|
|
||||||
private fun HttpUrl.getBaseUrl(): String =
|
|
||||||
"${URL_PREFIX}${this.host}${
|
|
||||||
when (this.port) {
|
|
||||||
80, 443 -> ""
|
|
||||||
else -> ":${this.port}"
|
|
||||||
}
|
|
||||||
}"
|
|
||||||
|
|
||||||
private fun useAltCdnInterceptor(chain: Interceptor.Chain): Response {
|
|
||||||
val request = chain.request()
|
|
||||||
val requestTag = request.tag(MangaBoxFallBackTag::class.java)
|
|
||||||
val originalResponse: Response? = try {
|
|
||||||
chain.proceed(request)
|
|
||||||
} catch (e: IOException) {
|
|
||||||
if (requestTag == null) {
|
|
||||||
throw e
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (requestTag == null || originalResponse?.isSuccessful == true) {
|
|
||||||
requestTag?.let {
|
|
||||||
// Move working cdn to first so it gets priority during iteration
|
|
||||||
cdnSet.moveItemToFirst(request.url.getBaseUrl())
|
|
||||||
}
|
|
||||||
|
|
||||||
return originalResponse!!
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close the original response if it's not successful
|
|
||||||
originalResponse?.close()
|
|
||||||
|
|
||||||
for (cdnUrl in cdnSet) {
|
|
||||||
var tryResponse: Response? = null
|
|
||||||
|
|
||||||
try {
|
|
||||||
val newUrl = cdnUrl.toHttpUrl().newBuilder()
|
|
||||||
.encodedPath(request.url.encodedPath)
|
|
||||||
.fragment(request.url.fragment)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
// Create a new request with the updated URL
|
|
||||||
val newRequest = request.newBuilder()
|
|
||||||
.url(newUrl)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
// Proceed with the new request
|
|
||||||
tryResponse = chain.proceed(newRequest)
|
|
||||||
|
|
||||||
// Check if the response is successful
|
|
||||||
if (tryResponse.isSuccessful) {
|
|
||||||
// Move working cdn to first so it gets priority during iteration
|
|
||||||
cdnSet.moveItemToFirst(newRequest.url.getBaseUrl())
|
|
||||||
|
|
||||||
return tryResponse
|
|
||||||
}
|
|
||||||
|
|
||||||
tryResponse.close()
|
|
||||||
} catch (_: IOException) {
|
|
||||||
tryResponse?.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If all CDNs fail, throw an error
|
|
||||||
return throw IOException("All CDN attempts failed.")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun headersBuilder(): Headers.Builder = super.headersBuilder()
|
override fun headersBuilder(): Headers.Builder = super.headersBuilder()
|
||||||
.add("Referer", "$baseUrl/")
|
.add("Referer", baseUrl) // for covers
|
||||||
|
|
||||||
open val popularUrlPath = "manga-list/hot-manga?page="
|
open val popularUrlPath = "manga_list?type=topview&category=all&state=all&page="
|
||||||
|
|
||||||
open val latestUrlPath = "manga-list/latest-manga?page="
|
open val latestUrlPath = "manga_list?type=latest&category=all&state=all&page="
|
||||||
|
|
||||||
open val simpleQueryPath = "search/story/"
|
open val simpleQueryPath = "search/"
|
||||||
|
|
||||||
override fun popularMangaSelector() = "div.truyen-list > div.list-truyen-item-wrap"
|
override fun popularMangaSelector() = "div.truyen-list > div.list-truyen-item-wrap"
|
||||||
|
|
||||||
@ -164,11 +58,10 @@ abstract class MangaBox(
|
|||||||
return GET("$baseUrl/$latestUrlPath$page", headers)
|
return GET("$baseUrl/$latestUrlPath$page", headers)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun mangaFromElement(element: Element, urlSelector: String = "h3 a"): SManga {
|
protected fun mangaFromElement(element: Element, urlSelector: String = "h3 a"): SManga {
|
||||||
return SManga.create().apply {
|
return SManga.create().apply {
|
||||||
element.select(urlSelector).first()!!.let {
|
element.select(urlSelector).first()!!.let {
|
||||||
url = it.attr("abs:href")
|
url = it.attr("abs:href").substringAfter(baseUrl) // intentionally not using setUrlWithoutDomain
|
||||||
.substringAfter(baseUrl) // intentionally not using setUrlWithoutDomain
|
|
||||||
title = it.text()
|
title = it.text()
|
||||||
}
|
}
|
||||||
thumbnail_url = element.select("img").first()!!.attr("abs:src")
|
thumbnail_url = element.select("img").first()!!.attr("abs:src")
|
||||||
@ -179,47 +72,62 @@ abstract class MangaBox(
|
|||||||
|
|
||||||
override fun latestUpdatesFromElement(element: Element): SManga = mangaFromElement(element)
|
override fun latestUpdatesFromElement(element: Element): SManga = mangaFromElement(element)
|
||||||
|
|
||||||
override fun popularMangaNextPageSelector() =
|
override fun popularMangaNextPageSelector() = "div.group_page, div.group-page a:not([href]) + a:not(:contains(Last))"
|
||||||
"div.group_page, div.group-page a:not([href]) + a:not(:contains(Last))"
|
|
||||||
|
|
||||||
override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector()
|
override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector()
|
||||||
|
|
||||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||||
return if (query.isNotBlank()) {
|
return if (query.isNotBlank() && getAdvancedGenreFilters().isEmpty()) {
|
||||||
val url = "$baseUrl/$simpleQueryPath".toHttpUrl().newBuilder()
|
GET("$baseUrl/$simpleQueryPath${normalizeSearchQuery(query)}?page=$page", headers)
|
||||||
.addPathSegment(normalizeSearchQuery(query))
|
|
||||||
.addQueryParameter("page", page.toString())
|
|
||||||
.build()
|
|
||||||
|
|
||||||
return GET(url, headers)
|
|
||||||
} else {
|
} else {
|
||||||
val url = "$baseUrl/genre".toHttpUrl().newBuilder()
|
val url = baseUrl.toHttpUrl().newBuilder()
|
||||||
url.addQueryParameter("page", page.toString())
|
if (getAdvancedGenreFilters().isNotEmpty()) {
|
||||||
filters.forEach { filter ->
|
url.addPathSegment("advanced_search")
|
||||||
when (filter) {
|
url.addQueryParameter("page", page.toString())
|
||||||
is SortFilter -> url.addQueryParameter("type", filter.toUriPart())
|
url.addQueryParameter("keyw", normalizeSearchQuery(query))
|
||||||
is StatusFilter -> url.addQueryParameter("state", filter.toUriPart())
|
var genreInclude = ""
|
||||||
is GenreFilter -> url.addPathSegment(filter.toUriPart()!!)
|
var genreExclude = ""
|
||||||
else -> {}
|
filters.forEach { filter ->
|
||||||
|
when (filter) {
|
||||||
|
is KeywordFilter -> filter.toUriPart()?.let { url.addQueryParameter("keyt", it) }
|
||||||
|
is SortFilter -> url.addQueryParameter("orby", filter.toUriPart())
|
||||||
|
is StatusFilter -> url.addQueryParameter("sts", filter.toUriPart())
|
||||||
|
is AdvGenreFilter -> {
|
||||||
|
filter.state.forEach { if (it.isIncluded()) genreInclude += "_${it.id}" }
|
||||||
|
filter.state.forEach { if (it.isExcluded()) genreExclude += "_${it.id}" }
|
||||||
|
}
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
url.addQueryParameter("g_i", genreInclude)
|
||||||
|
url.addQueryParameter("g_e", genreExclude)
|
||||||
|
} else {
|
||||||
|
url.addPathSegment("manga_list")
|
||||||
|
url.addQueryParameter("page", page.toString())
|
||||||
|
filters.forEach { filter ->
|
||||||
|
when (filter) {
|
||||||
|
is SortFilter -> url.addQueryParameter("type", filter.toUriPart())
|
||||||
|
is StatusFilter -> url.addQueryParameter("state", filter.toUriPart())
|
||||||
|
is GenreFilter -> url.addQueryParameter("category", filter.toUriPart())
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
GET(url.build(), headers)
|
GET(url.build(), headers)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun searchMangaSelector() = ".panel_story_list .story_item, div.list-truyen-item-wrap"
|
override fun searchMangaSelector() = ".panel_story_list .story_item"
|
||||||
|
|
||||||
override fun searchMangaFromElement(element: Element) = mangaFromElement(element)
|
override fun searchMangaFromElement(element: Element) = mangaFromElement(element)
|
||||||
|
|
||||||
override fun searchMangaNextPageSelector() =
|
override fun searchMangaNextPageSelector() = "a.page_select + a:not(.page_last), a.page-select + a:not(.page-last)"
|
||||||
"a.page_select + a:not(.page_last), a.page-select + a:not(.page-last)"
|
|
||||||
|
|
||||||
open val mangaDetailsMainSelector = "div.manga-info-top, div.panel-story-info"
|
open val mangaDetailsMainSelector = "div.manga-info-top, div.panel-story-info"
|
||||||
|
|
||||||
open val thumbnailSelector = "div.manga-info-pic img, span.info-image img"
|
open val thumbnailSelector = "div.manga-info-pic img, span.info-image img"
|
||||||
|
|
||||||
open val descriptionSelector = "div#noidungm, div#panel-story-info-description, div#contentBox"
|
open val descriptionSelector = "div#noidungm, div#panel-story-info-description"
|
||||||
|
|
||||||
override fun mangaDetailsRequest(manga: SManga): Request {
|
override fun mangaDetailsRequest(manga: SManga): Request {
|
||||||
if (manga.url.startsWith("http")) {
|
if (manga.url.startsWith("http")) {
|
||||||
@ -238,15 +146,11 @@ abstract class MangaBox(
|
|||||||
return SManga.create().apply {
|
return SManga.create().apply {
|
||||||
document.select(mangaDetailsMainSelector).firstOrNull()?.let { infoElement ->
|
document.select(mangaDetailsMainSelector).firstOrNull()?.let { infoElement ->
|
||||||
title = infoElement.select("h1, h2").first()!!.text()
|
title = infoElement.select("h1, h2").first()!!.text()
|
||||||
author = infoElement.select("li:contains(author) a, td:containsOwn(author) + td a")
|
author = infoElement.select("li:contains(author) a, td:containsOwn(author) + td a").eachText().joinToString()
|
||||||
.eachText().joinToString()
|
status = parseStatus(infoElement.select("li:contains(status), td:containsOwn(status) + td").text())
|
||||||
status = parseStatus(
|
|
||||||
infoElement.select("li:contains(status), td:containsOwn(status) + td").text(),
|
|
||||||
)
|
|
||||||
genre = infoElement.select("div.manga-info-top li:contains(genres)").firstOrNull()
|
genre = infoElement.select("div.manga-info-top li:contains(genres)").firstOrNull()
|
||||||
?.select("a")?.joinToString { it.text() } // kakalot
|
?.select("a")?.joinToString { it.text() } // kakalot
|
||||||
?: infoElement.select("td:containsOwn(genres) + td a")
|
?: infoElement.select("td:containsOwn(genres) + td a").joinToString { it.text() } // nelo
|
||||||
.joinToString { it.text() } // nelo
|
|
||||||
} ?: checkForRedirectMessage(document)
|
} ?: checkForRedirectMessage(document)
|
||||||
description = document.select(descriptionSelector).firstOrNull()?.ownText()
|
description = document.select(descriptionSelector).firstOrNull()?.ownText()
|
||||||
?.replace("""^$title summary:\s""".toRegex(), "")
|
?.replace("""^$title summary:\s""".toRegex(), "")
|
||||||
@ -297,21 +201,42 @@ abstract class MangaBox(
|
|||||||
|
|
||||||
private fun Element.selectDateFromElement(): Element {
|
private fun Element.selectDateFromElement(): Element {
|
||||||
val defaultChapterDateSelector = "span"
|
val defaultChapterDateSelector = "span"
|
||||||
return this.select(defaultChapterDateSelector).lastOrNull() ?: this.select(
|
return this.select(defaultChapterDateSelector).lastOrNull() ?: this.select(alternateChapterDateSelector).last()!!
|
||||||
alternateChapterDateSelector,
|
|
||||||
).last()!!
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun chapterFromElement(element: Element): SChapter {
|
override fun chapterFromElement(element: Element): SChapter {
|
||||||
return SChapter.create().apply {
|
return SChapter.create().apply {
|
||||||
element.select("a").let {
|
element.select("a").let {
|
||||||
url = it.attr("abs:href")
|
url = it.attr("abs:href").substringAfter(baseUrl) // intentionally not using setUrlWithoutDomain
|
||||||
.substringAfter(baseUrl) // intentionally not using setUrlWithoutDomain
|
|
||||||
name = it.text()
|
name = it.text()
|
||||||
scanlator =
|
scanlator =
|
||||||
it.attr("abs:href").toHttpUrl().host // show where chapters are actually from
|
it.attr("abs:href").toHttpUrl().host // show where chapters are actually from
|
||||||
}
|
}
|
||||||
date_upload = dateFormat.tryParse(element.selectDateFromElement().attr("title"))
|
date_upload = parseChapterDate(element.selectDateFromElement().text(), scanlator!!) ?: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parseChapterDate(date: String, host: String): Long? {
|
||||||
|
return if ("ago" in date) {
|
||||||
|
val value = date.split(' ')[0].toIntOrNull()
|
||||||
|
val cal = Calendar.getInstance()
|
||||||
|
when {
|
||||||
|
value != null && "min" in date -> cal.apply { add(Calendar.MINUTE, -value) }
|
||||||
|
value != null && "hour" in date -> cal.apply { add(Calendar.HOUR_OF_DAY, -value) }
|
||||||
|
value != null && "day" in date -> cal.apply { add(Calendar.DATE, -value) }
|
||||||
|
else -> null
|
||||||
|
}?.timeInMillis
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
if (host.contains("manganato", ignoreCase = true)) {
|
||||||
|
// Nelo's date format
|
||||||
|
SimpleDateFormat("MMM dd,yy", Locale.ENGLISH).parse(date)
|
||||||
|
} else {
|
||||||
|
dateformat.parse(date)
|
||||||
|
}
|
||||||
|
} catch (e: ParseException) {
|
||||||
|
null
|
||||||
|
}?.time
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -322,59 +247,26 @@ abstract class MangaBox(
|
|||||||
return super.pageListRequest(chapter)
|
return super.pageListRequest(chapter)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun extractArray(scriptContent: String, arrayName: String): List<String> {
|
open val pageListSelector = "div#vungdoc img, div.container-chapter-reader img"
|
||||||
val pattern = Pattern.compile("$arrayName\\s*=\\s*\\[([^]]+)]")
|
|
||||||
val matcher = pattern.matcher(scriptContent)
|
|
||||||
val arrayValues = mutableListOf<String>()
|
|
||||||
|
|
||||||
if (matcher.find()) {
|
|
||||||
val arrayContent = matcher.group(1)
|
|
||||||
val values = arrayContent?.split(",")
|
|
||||||
if (values != null) {
|
|
||||||
for (value in values) {
|
|
||||||
arrayValues.add(
|
|
||||||
value.trim()
|
|
||||||
.removeSurrounding("\"")
|
|
||||||
.replace("\\/", "/")
|
|
||||||
.removeSuffix("/"),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return arrayValues
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun pageListParse(document: Document): List<Page> {
|
override fun pageListParse(document: Document): List<Page> {
|
||||||
val element = document.select("head > script").lastOrNull()
|
return document.select(pageListSelector)
|
||||||
?: return emptyList()
|
// filter out bad elements for mangakakalots
|
||||||
val cdns =
|
.filterNot { it.attr("src").endsWith("log") }
|
||||||
extractArray(element.html(), "cdns") + extractArray(element.html(), "backupImage")
|
.mapIndexed { i, element ->
|
||||||
val chapterImages = extractArray(element.html(), "chapterImages")
|
val url = element.attr("abs:src").let { src ->
|
||||||
|
if (src.startsWith("https://convert_image_digi.mgicdn.com")) {
|
||||||
// Add all parsed cdns to set
|
"https://images.weserv.nl/?url=" + src.substringAfter("//")
|
||||||
cdnSet.addAll(cdns)
|
} else {
|
||||||
|
src
|
||||||
return chapterImages.mapIndexed { i, imagePath ->
|
}
|
||||||
val parsedUrl = cdns[0].toHttpUrl().run {
|
}
|
||||||
newBuilder()
|
Page(i, document.location(), url)
|
||||||
.encodedPath(
|
|
||||||
"/$imagePath".replace(
|
|
||||||
"//",
|
|
||||||
"/",
|
|
||||||
),
|
|
||||||
) // replace ensures that there's at least one trailing slash prefix
|
|
||||||
.build()
|
|
||||||
.toString()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Page(i, document.location(), parsedUrl)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun imageRequest(page: Page): Request {
|
override fun imageRequest(page: Page): Request {
|
||||||
return GET(page.imageUrl!!, headers).newBuilder()
|
return GET(page.imageUrl!!, headersBuilder().set("Referer", page.url).build())
|
||||||
.tag(MangaBoxFallBackTag::class.java, MangaBoxFallBackTag()).build()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException()
|
override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException()
|
||||||
@ -390,27 +282,46 @@ abstract class MangaBox(
|
|||||||
str = str.replace("[ùúụủũưừứựửữ]".toRegex(), "u")
|
str = str.replace("[ùúụủũưừứựửữ]".toRegex(), "u")
|
||||||
str = str.replace("[ỳýỵỷỹ]".toRegex(), "y")
|
str = str.replace("[ỳýỵỷỹ]".toRegex(), "y")
|
||||||
str = str.replace("đ".toRegex(), "d")
|
str = str.replace("đ".toRegex(), "d")
|
||||||
str = str.replace(
|
str = str.replace("""!|@|%|\^|\*|\(|\)|\+|=|<|>|\?|/|,|\.|:|;|'| |"|&|#|\[|]|~|-|$|_""".toRegex(), "_")
|
||||||
"""!|@|%|\^|\*|\(|\)|\+|=|<|>|\?|/|,|\.|:|;|'| |"|&|#|\[|]|~|-|$|_""".toRegex(),
|
|
||||||
"_",
|
|
||||||
)
|
|
||||||
str = str.replace("_+_".toRegex(), "_")
|
str = str.replace("_+_".toRegex(), "_")
|
||||||
str = str.replace("""^_+|_+$""".toRegex(), "")
|
str = str.replace("""^_+|_+$""".toRegex(), "")
|
||||||
return str
|
return str
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getFilterList() = FilterList(
|
override fun getFilterList() = if (getAdvancedGenreFilters().isNotEmpty()) {
|
||||||
Filter.Header("NOTE: Ignored if using text search!"),
|
FilterList(
|
||||||
Filter.Separator(),
|
KeywordFilter(getKeywordFilters()),
|
||||||
SortFilter(getSortFilters()),
|
SortFilter(getSortFilters()),
|
||||||
StatusFilter(getStatusFilters()),
|
StatusFilter(getStatusFilters()),
|
||||||
GenreFilter(getGenreFilters()),
|
AdvGenreFilter(getAdvancedGenreFilters()),
|
||||||
)
|
)
|
||||||
|
} else {
|
||||||
|
FilterList(
|
||||||
|
Filter.Header("NOTE: Ignored if using text search!"),
|
||||||
|
Filter.Separator(),
|
||||||
|
SortFilter(getSortFilters()),
|
||||||
|
StatusFilter(getStatusFilters()),
|
||||||
|
GenreFilter(getGenreFilters()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private class KeywordFilter(vals: Array<Pair<String?, String>>) : UriPartFilter("Keyword search ", vals)
|
||||||
private class SortFilter(vals: Array<Pair<String?, String>>) : UriPartFilter("Order by", vals)
|
private class SortFilter(vals: Array<Pair<String?, String>>) : UriPartFilter("Order by", vals)
|
||||||
private class StatusFilter(vals: Array<Pair<String?, String>>) : UriPartFilter("Status", vals)
|
private class StatusFilter(vals: Array<Pair<String?, String>>) : UriPartFilter("Status", vals)
|
||||||
private class GenreFilter(vals: Array<Pair<String?, String>>) : UriPartFilter("Category", vals)
|
private class GenreFilter(vals: Array<Pair<String?, String>>) : UriPartFilter("Category", vals)
|
||||||
|
|
||||||
|
// For advanced search, specifically tri-state genres
|
||||||
|
private class AdvGenreFilter(vals: List<AdvGenre>) : Filter.Group<AdvGenre>("Category", vals)
|
||||||
|
class AdvGenre(val id: String?, name: String) : Filter.TriState(name)
|
||||||
|
|
||||||
|
// keyt query parameter
|
||||||
|
private fun getKeywordFilters(): Array<Pair<String?, String>> = arrayOf(
|
||||||
|
Pair(null, "Everything"),
|
||||||
|
Pair("title", "Title"),
|
||||||
|
Pair("alternative", "Alt title"),
|
||||||
|
Pair("author", "Author"),
|
||||||
|
)
|
||||||
|
|
||||||
private fun getSortFilters(): Array<Pair<String?, String>> = arrayOf(
|
private fun getSortFilters(): Array<Pair<String?, String>> = arrayOf(
|
||||||
Pair("latest", "Latest"),
|
Pair("latest", "Latest"),
|
||||||
Pair("newest", "Newest"),
|
Pair("newest", "Newest"),
|
||||||
@ -426,72 +337,53 @@ abstract class MangaBox(
|
|||||||
|
|
||||||
open fun getGenreFilters(): Array<Pair<String?, String>> = arrayOf(
|
open fun getGenreFilters(): Array<Pair<String?, String>> = arrayOf(
|
||||||
Pair("all", "ALL"),
|
Pair("all", "ALL"),
|
||||||
Pair("action", "Action"),
|
Pair("2", "Action"),
|
||||||
Pair("adult", "Adult"),
|
Pair("3", "Adult"),
|
||||||
Pair("adventure", "Adventure"),
|
Pair("4", "Adventure"),
|
||||||
Pair("comedy", "Comedy"),
|
Pair("6", "Comedy"),
|
||||||
Pair("cooking", "Cooking"),
|
Pair("7", "Cooking"),
|
||||||
Pair("doujinshi", "Doujinshi"),
|
Pair("9", "Doujinshi"),
|
||||||
Pair("drama", "Drama"),
|
Pair("10", "Drama"),
|
||||||
Pair("ecchi", "Ecchi"),
|
Pair("11", "Ecchi"),
|
||||||
Pair("fantasy", "Fantasy"),
|
Pair("12", "Fantasy"),
|
||||||
Pair("gender-bender", "Gender bender"),
|
Pair("13", "Gender bender"),
|
||||||
Pair("harem", "Harem"),
|
Pair("14", "Harem"),
|
||||||
Pair("historical", "Historical"),
|
Pair("15", "Historical"),
|
||||||
Pair("horror", "Horror"),
|
Pair("16", "Horror"),
|
||||||
Pair("isekai", "Isekai"),
|
Pair("45", "Isekai"),
|
||||||
Pair("josei", "Josei"),
|
Pair("17", "Josei"),
|
||||||
Pair("manhua", "Manhua"),
|
Pair("44", "Manhua"),
|
||||||
Pair("manhwa", "Manhwa"),
|
Pair("43", "Manhwa"),
|
||||||
Pair("martial-arts", "Martial arts"),
|
Pair("19", "Martial arts"),
|
||||||
Pair("mature", "Mature"),
|
Pair("20", "Mature"),
|
||||||
Pair("mecha", "Mecha"),
|
Pair("21", "Mecha"),
|
||||||
Pair("medical", "Medical"),
|
Pair("22", "Medical"),
|
||||||
Pair("mystery", "Mystery"),
|
Pair("24", "Mystery"),
|
||||||
Pair("one-shot", "One shot"),
|
Pair("25", "One shot"),
|
||||||
Pair("psychological", "Psychological"),
|
Pair("26", "Psychological"),
|
||||||
Pair("romance", "Romance"),
|
Pair("27", "Romance"),
|
||||||
Pair("school-life", "School life"),
|
Pair("28", "School life"),
|
||||||
Pair("sci-fi", "Sci fi"),
|
Pair("29", "Sci fi"),
|
||||||
Pair("seinen", "Seinen"),
|
Pair("30", "Seinen"),
|
||||||
Pair("shoujo", "Shoujo"),
|
Pair("31", "Shoujo"),
|
||||||
Pair("shoujo-ai", "Shoujo ai"),
|
Pair("32", "Shoujo ai"),
|
||||||
Pair("shounen", "Shounen"),
|
Pair("33", "Shounen"),
|
||||||
Pair("shounen-ai", "Shounen ai"),
|
Pair("34", "Shounen ai"),
|
||||||
Pair("slice-of-life", "Slice of life"),
|
Pair("35", "Slice of life"),
|
||||||
Pair("smut", "Smut"),
|
Pair("36", "Smut"),
|
||||||
Pair("sports", "Sports"),
|
Pair("37", "Sports"),
|
||||||
Pair("supernatural", "Supernatural"),
|
Pair("38", "Supernatural"),
|
||||||
Pair("tragedy", "Tragedy"),
|
Pair("39", "Tragedy"),
|
||||||
Pair("webtoons", "Webtoons"),
|
Pair("40", "Webtoons"),
|
||||||
Pair("yaoi", "Yaoi"),
|
Pair("41", "Yaoi"),
|
||||||
Pair("yuri", "Yuri"),
|
Pair("42", "Yuri"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// To be overridden if using tri-state genres
|
||||||
|
protected open fun getAdvancedGenreFilters(): List<AdvGenre> = emptyList()
|
||||||
|
|
||||||
open class UriPartFilter(displayName: String, private val vals: Array<Pair<String?, String>>) :
|
open class UriPartFilter(displayName: String, private val vals: Array<Pair<String?, String>>) :
|
||||||
Filter.Select<String>(displayName, vals.map { it.second }.toTypedArray()) {
|
Filter.Select<String>(displayName, vals.map { it.second }.toTypedArray()) {
|
||||||
fun toUriPart() = vals[state].first
|
fun toUriPart() = vals[state].first
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setupPreferenceScreen(screen: PreferenceScreen) {
|
|
||||||
ListPreference(screen.context).apply {
|
|
||||||
key = PREF_USE_MIRROR
|
|
||||||
title = "Mirror"
|
|
||||||
entries = mirrorEntries
|
|
||||||
entryValues = mirrorEntries.map { "${URL_PREFIX}$it" }.toTypedArray()
|
|
||||||
setDefaultValue(entryValues[0])
|
|
||||||
summary = "%s"
|
|
||||||
|
|
||||||
setOnPreferenceChangeListener { _, newValue ->
|
|
||||||
// Update values
|
|
||||||
mirror = newValue as String
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}.let(screen::addPreference)
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private const val PREF_USE_MIRROR = "pref_use_mirror"
|
|
||||||
private const val URL_PREFIX = "https://"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,20 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.multisrc.mangabox
|
|
||||||
|
|
||||||
class MangaBoxLinkedCdnSet : LinkedHashSet<String>() {
|
|
||||||
fun moveItemToFirst(item: String) {
|
|
||||||
// Lock the object to avoid multi threading issues
|
|
||||||
synchronized(this) {
|
|
||||||
if (this.contains(item) && this.first() != item) {
|
|
||||||
// Remove the item from the current set
|
|
||||||
this.remove(item)
|
|
||||||
// Create a new list with the item at the first position
|
|
||||||
val newItems = mutableListOf(item)
|
|
||||||
// Add the remaining items
|
|
||||||
newItems.addAll(this)
|
|
||||||
// Clear the current set and add all items from the new list
|
|
||||||
this.clear()
|
|
||||||
this.addAll(newItems)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -2,7 +2,7 @@ plugins {
|
|||||||
id("lib-multisrc")
|
id("lib-multisrc")
|
||||||
}
|
}
|
||||||
|
|
||||||
baseVersionCode = 4
|
baseVersionCode = 3
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
api(project(":lib:i18n"))
|
api(project(":lib:i18n"))
|
||||||
|
@ -48,7 +48,7 @@ abstract class MangaEsp(
|
|||||||
|
|
||||||
protected open val useApiSearch = false
|
protected open val useApiSearch = false
|
||||||
|
|
||||||
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
|
override val client: OkHttpClient = network.client.newBuilder()
|
||||||
.rateLimitHost(baseUrl.toHttpUrl(), 2)
|
.rateLimitHost(baseUrl.toHttpUrl(), 2)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@ plugins {
|
|||||||
id("lib-multisrc")
|
id("lib-multisrc")
|
||||||
}
|
}
|
||||||
|
|
||||||
baseVersionCode = 30
|
baseVersionCode = 29
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
api(project(":lib:randomua"))
|
api(project(":lib:randomua"))
|
||||||
|
@ -17,6 +17,7 @@ import kotlinx.serialization.decodeFromString
|
|||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import kotlinx.serialization.json.buildJsonObject
|
import kotlinx.serialization.json.buildJsonObject
|
||||||
import kotlinx.serialization.json.put
|
import kotlinx.serialization.json.put
|
||||||
|
import kotlinx.serialization.json.putJsonObject
|
||||||
import okhttp3.Cookie
|
import okhttp3.Cookie
|
||||||
import okhttp3.Headers
|
import okhttp3.Headers
|
||||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||||
@ -30,7 +31,7 @@ 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.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
import java.io.IOException
|
import java.net.URLEncoder
|
||||||
import java.text.ParseException
|
import java.text.ParseException
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Calendar
|
import java.util.Calendar
|
||||||
@ -48,7 +49,6 @@ abstract class MangaHub(
|
|||||||
|
|
||||||
private var baseApiUrl = "https://api.mghcdn.com"
|
private var baseApiUrl = "https://api.mghcdn.com"
|
||||||
private var baseCdnUrl = "https://imgx.mghcdn.com"
|
private var baseCdnUrl = "https://imgx.mghcdn.com"
|
||||||
private val regex = Regex("mhub_access=([^;]+)")
|
|
||||||
|
|
||||||
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
|
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
|
||||||
.setRandomUserAgent(
|
.setRandomUserAgent(
|
||||||
@ -91,6 +91,8 @@ abstract class MangaHub(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun refreshApiKey(chapter: SChapter) {
|
private fun refreshApiKey(chapter: SChapter) {
|
||||||
|
val now = Calendar.getInstance().time.time
|
||||||
|
|
||||||
val slug = "$baseUrl${chapter.url}"
|
val slug = "$baseUrl${chapter.url}"
|
||||||
.toHttpUrlOrNull()
|
.toHttpUrlOrNull()
|
||||||
?.pathSegments
|
?.pathSegments
|
||||||
@ -102,28 +104,32 @@ abstract class MangaHub(
|
|||||||
baseUrl.toHttpUrl()
|
baseUrl.toHttpUrl()
|
||||||
}
|
}
|
||||||
|
|
||||||
val oldKey = client.cookieJar
|
// Clear key cookie
|
||||||
.loadForRequest(baseUrl.toHttpUrl())
|
val cookie = Cookie.parse(url, "mhub_access=; Max-Age=0; Path=/")!!
|
||||||
.firstOrNull { it.name == "mhub_access" && it.value.isNotEmpty() }?.value
|
client.cookieJar.saveFromResponse(url, listOf(cookie))
|
||||||
|
|
||||||
for (i in 1..2) {
|
// Set required cookie (for cache busting?)
|
||||||
// Clear key cookie
|
val recently = buildJsonObject {
|
||||||
val cookie = Cookie.parse(url, "mhub_access=; Max-Age=0; Path=/")!!
|
putJsonObject((now - (0..3600).random()).toString()) {
|
||||||
client.cookieJar.saveFromResponse(url, listOf(cookie))
|
put("mangaID", (1..42_000).random())
|
||||||
|
put("number", (1..20).random())
|
||||||
// We try requesting again with param if the first one fails
|
|
||||||
val query = if (i == 2) "?reloadKey=1" else ""
|
|
||||||
|
|
||||||
try {
|
|
||||||
val response = client.newCall(GET("$url$query", headers)).execute()
|
|
||||||
val returnedKey = response.headers["set-cookie"]?.let { regex.find(it)?.groupValues?.get(1) }
|
|
||||||
response.close() // Avoid potential resource leaks
|
|
||||||
|
|
||||||
if (returnedKey != oldKey) break; // Break out of loop since we got an allegedly valid API key
|
|
||||||
} catch (_: IOException) {
|
|
||||||
throw IOException("An error occurred while obtaining a new API key") // Show error
|
|
||||||
}
|
}
|
||||||
}
|
}.toString()
|
||||||
|
|
||||||
|
client.cookieJar.saveFromResponse(
|
||||||
|
url,
|
||||||
|
listOf(
|
||||||
|
Cookie.Builder()
|
||||||
|
.domain(url.host)
|
||||||
|
.name("recently")
|
||||||
|
.value(URLEncoder.encode(recently, "utf-8"))
|
||||||
|
.expiresAt(now + 2 * 60 * 60 * 24 * 31) // +2 months
|
||||||
|
.build(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
val request = GET("$url?reloadKey=1", headers)
|
||||||
|
client.newCall(request).execute()
|
||||||
}
|
}
|
||||||
|
|
||||||
data class SMangaDTO(
|
data class SMangaDTO(
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package eu.kanade.tachiyomi.multisrc.mangathemesia
|
package eu.kanade.tachiyomi.multisrc.mangathemesia
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import androidx.preference.PreferenceScreen
|
import androidx.preference.PreferenceScreen
|
||||||
import androidx.preference.SwitchPreferenceCompat
|
import androidx.preference.SwitchPreferenceCompat
|
||||||
@ -8,7 +9,6 @@ import eu.kanade.tachiyomi.source.ConfigurableSource
|
|||||||
import eu.kanade.tachiyomi.source.model.MangasPage
|
import eu.kanade.tachiyomi.source.model.MangasPage
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
import eu.kanade.tachiyomi.util.asJsoup
|
import eu.kanade.tachiyomi.util.asJsoup
|
||||||
import keiyoushi.utils.getPreferencesLazy
|
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import kotlinx.coroutines.sync.withLock
|
import kotlinx.coroutines.sync.withLock
|
||||||
@ -16,6 +16,8 @@ import kotlinx.serialization.decodeFromString
|
|||||||
import kotlinx.serialization.encodeToString
|
import kotlinx.serialization.encodeToString
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
import java.lang.ref.SoftReference
|
import java.lang.ref.SoftReference
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
@ -32,12 +34,14 @@ abstract class MangaThemesiaAlt(
|
|||||||
protected open val listUrl = "$mangaUrlDirectory/list-mode/"
|
protected open val listUrl = "$mangaUrlDirectory/list-mode/"
|
||||||
protected open val listSelector = "div#content div.soralist ul li a.series"
|
protected open val listSelector = "div#content div.soralist ul li a.series"
|
||||||
|
|
||||||
protected val preferences by getPreferencesLazy {
|
protected val preferences: SharedPreferences by lazy {
|
||||||
if (contains("__random_part_cache")) {
|
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000).also {
|
||||||
edit().remove("__random_part_cache").apply()
|
if (it.contains("__random_part_cache")) {
|
||||||
}
|
it.edit().remove("__random_part_cache").apply()
|
||||||
if (contains("titles_without_random_part")) {
|
}
|
||||||
edit().remove("titles_without_random_part").apply()
|
if (it.contains("titles_without_random_part")) {
|
||||||
|
it.edit().remove("titles_without_random_part").apply()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,4 +2,4 @@ plugins {
|
|||||||
id("lib-multisrc")
|
id("lib-multisrc")
|
||||||
}
|
}
|
||||||
|
|
||||||
baseVersionCode = 7
|
baseVersionCode = 6
|
||||||
|
@ -33,7 +33,7 @@ open class MCCMS(
|
|||||||
private val json: Json by injectLazy()
|
private val json: Json by injectLazy()
|
||||||
|
|
||||||
override val client by lazy {
|
override val client by lazy {
|
||||||
network.cloudflareClient.newBuilder()
|
network.client.newBuilder()
|
||||||
.rateLimitHost(baseUrl.toHttpUrl(), 2)
|
.rateLimitHost(baseUrl.toHttpUrl(), 2)
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,7 @@ open class MCCMSWeb(
|
|||||||
override val supportsLatest get() = true
|
override val supportsLatest get() = true
|
||||||
|
|
||||||
override val client by lazy {
|
override val client by lazy {
|
||||||
network.cloudflareClient.newBuilder()
|
network.client.newBuilder()
|
||||||
.rateLimitHost(baseUrl.toHttpUrl(), 2)
|
.rateLimitHost(baseUrl.toHttpUrl(), 2)
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
@ -2,4 +2,4 @@ plugins {
|
|||||||
id("lib-multisrc")
|
id("lib-multisrc")
|
||||||
}
|
}
|
||||||
|
|
||||||
baseVersionCode = 4
|
baseVersionCode = 3
|
||||||
|
@ -26,7 +26,7 @@ abstract class MultiChan(
|
|||||||
|
|
||||||
override val supportsLatest = true
|
override val supportsLatest = true
|
||||||
|
|
||||||
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
|
override val client: OkHttpClient = network.client.newBuilder()
|
||||||
.connectTimeout(30, TimeUnit.SECONDS)
|
.connectTimeout(30, TimeUnit.SECONDS)
|
||||||
.readTimeout(30, TimeUnit.SECONDS)
|
.readTimeout(30, TimeUnit.SECONDS)
|
||||||
.rateLimit(2)
|
.rateLimit(2)
|
||||||
|
@ -2,4 +2,4 @@ plugins {
|
|||||||
id("lib-multisrc")
|
id("lib-multisrc")
|
||||||
}
|
}
|
||||||
|
|
||||||
baseVersionCode = 4
|
baseVersionCode = 3
|
||||||
|
@ -40,7 +40,7 @@ abstract class Senkuro(
|
|||||||
.add("Content-Type", "application/json")
|
.add("Content-Type", "application/json")
|
||||||
|
|
||||||
override val client: OkHttpClient =
|
override val client: OkHttpClient =
|
||||||
network.cloudflareClient.newBuilder()
|
network.client.newBuilder()
|
||||||
.rateLimit(3)
|
.rateLimit(3)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
|
@ -2,4 +2,4 @@ plugins {
|
|||||||
id("lib-multisrc")
|
id("lib-multisrc")
|
||||||
}
|
}
|
||||||
|
|
||||||
baseVersionCode = 12
|
baseVersionCode = 11
|
||||||
|
@ -34,7 +34,7 @@ abstract class SinMH(
|
|||||||
protected open val mobileUrl = _baseUrl.replaceFirst("www.", "m.")
|
protected open val mobileUrl = _baseUrl.replaceFirst("www.", "m.")
|
||||||
override val supportsLatest = true
|
override val supportsLatest = true
|
||||||
|
|
||||||
override val client = network.cloudflareClient.newBuilder().rateLimit(2).build()
|
override val client = network.client.newBuilder().rateLimit(2).build()
|
||||||
|
|
||||||
override fun headersBuilder(): Headers.Builder = Headers.Builder()
|
override fun headersBuilder(): Headers.Builder = Headers.Builder()
|
||||||
.add("User-Agent", System.getProperty("http.agent")!!)
|
.add("User-Agent", System.getProperty("http.agent")!!)
|
||||||
|
@ -2,4 +2,4 @@ plugins {
|
|||||||
id("lib-multisrc")
|
id("lib-multisrc")
|
||||||
}
|
}
|
||||||
|
|
||||||
baseVersionCode = 4
|
baseVersionCode = 2
|
||||||
|
@ -45,7 +45,7 @@ open class Webtoons(
|
|||||||
|
|
||||||
override val supportsLatest = true
|
override val supportsLatest = true
|
||||||
|
|
||||||
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
|
override val client: OkHttpClient = super.client.newBuilder()
|
||||||
.cookieJar(
|
.cookieJar(
|
||||||
object : CookieJar {
|
object : CookieJar {
|
||||||
override fun saveFromResponse(url: HttpUrl, cookies: List<Cookie>) {}
|
override fun saveFromResponse(url: HttpUrl, cookies: List<Cookie>) {}
|
||||||
@ -197,7 +197,7 @@ open class Webtoons(
|
|||||||
open fun parseDetailsThumbnail(document: Document): String? {
|
open fun parseDetailsThumbnail(document: Document): String? {
|
||||||
val picElement = document.select("#content > div.cont_box > div.detail_body")
|
val picElement = document.select("#content > div.cont_box > div.detail_body")
|
||||||
val discoverPic = document.select("#content > div.cont_box > div.detail_header > span.thmb")
|
val discoverPic = document.select("#content > div.cont_box > div.detail_header > span.thmb")
|
||||||
return picElement.attr("style").substringAfter("url(").substringBeforeLast(")").removeSurrounding("\"").removeSurrounding("'")
|
return picElement.attr("style").substringAfter("url(").substringBeforeLast(")")
|
||||||
.ifBlank { discoverPic.select("img").not("[alt='Representative image']").first()?.attr("src") }
|
.ifBlank { discoverPic.select("img").not("[alt='Representative image']").first()?.attr("src") }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +0,0 @@
|
|||||||
plugins {
|
|
||||||
id("lib-multisrc")
|
|
||||||
}
|
|
||||||
|
|
||||||
baseVersionCode = 1
|
|
@ -1,206 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.multisrc.yuyu
|
|
||||||
|
|
||||||
import android.net.Uri
|
|
||||||
import eu.kanade.tachiyomi.network.GET
|
|
||||||
import eu.kanade.tachiyomi.network.asObservableSuccess
|
|
||||||
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 eu.kanade.tachiyomi.util.asJsoup
|
|
||||||
import keiyoushi.utils.parseAs
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
|
||||||
import okhttp3.Request
|
|
||||||
import okhttp3.Response
|
|
||||||
import org.jsoup.Jsoup
|
|
||||||
import org.jsoup.nodes.Document
|
|
||||||
import org.jsoup.nodes.Element
|
|
||||||
import rx.Observable
|
|
||||||
import java.net.URLEncoder
|
|
||||||
|
|
||||||
abstract class YuYu(
|
|
||||||
override val name: String,
|
|
||||||
override val baseUrl: String,
|
|
||||||
override val lang: String,
|
|
||||||
) : ParsedHttpSource() {
|
|
||||||
|
|
||||||
override val client = network.cloudflareClient
|
|
||||||
|
|
||||||
override val supportsLatest = true
|
|
||||||
|
|
||||||
// ============================== Popular ===============================
|
|
||||||
|
|
||||||
override fun popularMangaRequest(page: Int) = GET(baseUrl, headers)
|
|
||||||
|
|
||||||
override fun popularMangaSelector() = ".top10-section .top10-item a"
|
|
||||||
|
|
||||||
override fun popularMangaFromElement(element: Element) = SManga.create().apply {
|
|
||||||
title = element.selectFirst("h3")!!.text()
|
|
||||||
thumbnail_url = element.selectFirst("img")?.absUrl("src")
|
|
||||||
setUrlWithoutDomain(element.absUrl("href"))
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun popularMangaNextPageSelector() = null
|
|
||||||
|
|
||||||
// ============================== Latest ===============================
|
|
||||||
|
|
||||||
override fun latestUpdatesRequest(page: Int): Request {
|
|
||||||
val url = baseUrl.toHttpUrl().newBuilder()
|
|
||||||
.addQueryParameter("pagina", page.toString())
|
|
||||||
.build()
|
|
||||||
return GET(url, headers)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun latestUpdatesSelector() = ".manga-list .manga-card"
|
|
||||||
|
|
||||||
override fun latestUpdatesNextPageSelector() = "a.page-link:contains(>)"
|
|
||||||
|
|
||||||
override fun latestUpdatesFromElement(element: Element) = SManga.create().apply {
|
|
||||||
val url = element.selectFirst("a.manga-cover")!!.absUrl("href")
|
|
||||||
val uri = Uri.parse(url)
|
|
||||||
val pathSegments = uri.pathSegments
|
|
||||||
val lastSegment = URLEncoder.encode(pathSegments.last(), "UTF-8")
|
|
||||||
val encodedUrl = uri.buildUpon()
|
|
||||||
.path(pathSegments.dropLast(1).joinToString("/") + "/$lastSegment")
|
|
||||||
.toString()
|
|
||||||
|
|
||||||
title = element.selectFirst("a.manga-title")!!.text()
|
|
||||||
thumbnail_url = element.selectFirst("a.manga-cover img")?.absUrl("data-src")
|
|
||||||
setUrlWithoutDomain(encodedUrl)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun latestUpdatesParse(response: Response): MangasPage {
|
|
||||||
val document = response.asJsoup()
|
|
||||||
val mangas = document.select(latestUpdatesSelector()).map(::latestUpdatesFromElement)
|
|
||||||
return MangasPage(mangas, document.hasNextPage())
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun Document.hasNextPage() =
|
|
||||||
selectFirst(latestUpdatesNextPageSelector())?.absUrl("href")?.let {
|
|
||||||
selectFirst("a.page-link.active")
|
|
||||||
?.absUrl("href")
|
|
||||||
.equals(it, ignoreCase = true).not()
|
|
||||||
} ?: false
|
|
||||||
|
|
||||||
// ============================== Search ===============================
|
|
||||||
|
|
||||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
|
||||||
val url = baseUrl.toHttpUrl().newBuilder()
|
|
||||||
.addQueryParameter("search", query)
|
|
||||||
return GET(url.build(), headers)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
|
|
||||||
if (query.startsWith(PREFIX_SEARCH)) {
|
|
||||||
val slug = query.substringAfter(PREFIX_SEARCH)
|
|
||||||
return client.newCall(GET("$baseUrl/manga/$slug", headers))
|
|
||||||
.asObservableSuccess()
|
|
||||||
.map {
|
|
||||||
val manga = mangaDetailsParse(it.asJsoup())
|
|
||||||
MangasPage(listOf(manga), false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return super.fetchSearchManga(page, query, filters)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun searchMangaSelector() = ".search-result-item"
|
|
||||||
|
|
||||||
override fun searchMangaFromElement(element: Element) = SManga.create().apply {
|
|
||||||
title = element.selectFirst(".search-result-title")!!.text()
|
|
||||||
thumbnail_url = element.selectFirst("img")?.absUrl("src")
|
|
||||||
setUrlWithoutDomain(
|
|
||||||
element.attr("onclick").let {
|
|
||||||
SEARCH_URL_REGEX.find(it)?.groups?.get(1)?.value!!
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun searchMangaNextPageSelector() = null
|
|
||||||
|
|
||||||
// ============================== Manga Details =========================
|
|
||||||
|
|
||||||
override fun mangaDetailsParse(document: Document) = SManga.create().apply {
|
|
||||||
val details = document.selectFirst(".manga-banner .container")!!
|
|
||||||
title = details.selectFirst("h1")!!.text()
|
|
||||||
thumbnail_url = details.selectFirst("img")?.absUrl("src")
|
|
||||||
genre = details.select(".genre-tag").joinToString { it.text() }
|
|
||||||
description = details.selectFirst(".sinopse p")?.text()
|
|
||||||
details.selectFirst(".manga-meta > div")?.ownText()?.let {
|
|
||||||
status = when (it.lowercase()) {
|
|
||||||
"em andamento" -> SManga.ONGOING
|
|
||||||
"completo" -> SManga.COMPLETED
|
|
||||||
"cancelado" -> SManga.CANCELLED
|
|
||||||
"hiato" -> SManga.ON_HIATUS
|
|
||||||
else -> SManga.UNKNOWN
|
|
||||||
}
|
|
||||||
}
|
|
||||||
setUrlWithoutDomain(document.location())
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun SManga.fetchMangaId(): String {
|
|
||||||
val document = client.newCall(mangaDetailsRequest(this)).execute().asJsoup()
|
|
||||||
return document.select("script")
|
|
||||||
.map(Element::data)
|
|
||||||
.firstOrNull(MANGA_ID_REGEX::containsMatchIn)
|
|
||||||
?.let { MANGA_ID_REGEX.find(it)?.groups?.get(1)?.value }
|
|
||||||
?: throw Exception("Manga ID não encontrado")
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================== Chapters ===============================
|
|
||||||
|
|
||||||
override fun chapterListSelector() = "a.chapter-item"
|
|
||||||
|
|
||||||
override fun chapterFromElement(element: Element) = SChapter.create().apply {
|
|
||||||
name = element.selectFirst(".capitulo-numero")!!.ownText()
|
|
||||||
setUrlWithoutDomain(element.absUrl("href"))
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
|
|
||||||
val mangaId = manga.fetchMangaId()
|
|
||||||
val chapters = mutableListOf<SChapter>()
|
|
||||||
var page = 1
|
|
||||||
do {
|
|
||||||
val dto = fetchChapterListPage(mangaId, page++).parseAs<ChaptersDto>()
|
|
||||||
val document = Jsoup.parseBodyFragment(dto.chapters, baseUrl)
|
|
||||||
chapters += document.select(chapterListSelector()).map(::chapterFromElement)
|
|
||||||
} while (dto.hasNext())
|
|
||||||
return Observable.just(chapters)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun fetchChapterListPage(mangaId: String, page: Int): Response {
|
|
||||||
val url = "$baseUrl/ajax/lzmvke.php?order=DESC".toHttpUrl().newBuilder()
|
|
||||||
.addQueryParameter("manga_id", mangaId)
|
|
||||||
.addQueryParameter("page", page.toString())
|
|
||||||
.build()
|
|
||||||
|
|
||||||
return client
|
|
||||||
.newCall(GET(url, headers))
|
|
||||||
.execute()
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================== Pages ===============================
|
|
||||||
|
|
||||||
override fun pageListParse(document: Document): List<Page> {
|
|
||||||
return document.select("picture img").mapIndexed { idx, element ->
|
|
||||||
Page(idx, imageUrl = element.absUrl("src"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun imageUrlParse(document: Document) = ""
|
|
||||||
|
|
||||||
// ============================== Utilities ===========================
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
class ChaptersDto(val chapters: String, private val remaining: Int) {
|
|
||||||
fun hasNext() = remaining > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val PREFIX_SEARCH = "id:"
|
|
||||||
val SEARCH_URL_REGEX = "'([^']+)".toRegex()
|
|
||||||
val MANGA_ID_REGEX = """obra_id:\s+(\d+)""".toRegex()
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,7 +1,7 @@
|
|||||||
ext {
|
ext {
|
||||||
extName = 'Akuma'
|
extName = 'Akuma'
|
||||||
extClass = '.AkumaFactory'
|
extClass = '.AkumaFactory'
|
||||||
extVersionCode = 7
|
extVersionCode = 5
|
||||||
isNsfw = true
|
isNsfw = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package eu.kanade.tachiyomi.extension.all.akuma
|
package eu.kanade.tachiyomi.extension.all.akuma
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import androidx.preference.PreferenceScreen
|
import androidx.preference.PreferenceScreen
|
||||||
import androidx.preference.SwitchPreferenceCompat
|
import androidx.preference.SwitchPreferenceCompat
|
||||||
@ -15,7 +16,6 @@ import eu.kanade.tachiyomi.source.model.SManga
|
|||||||
import eu.kanade.tachiyomi.source.model.UpdateStrategy
|
import eu.kanade.tachiyomi.source.model.UpdateStrategy
|
||||||
import eu.kanade.tachiyomi.source.online.ParsedHttpSource
|
import eu.kanade.tachiyomi.source.online.ParsedHttpSource
|
||||||
import eu.kanade.tachiyomi.util.asJsoup
|
import eu.kanade.tachiyomi.util.asJsoup
|
||||||
import keiyoushi.utils.getPreferencesLazy
|
|
||||||
import okhttp3.FormBody
|
import okhttp3.FormBody
|
||||||
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||||
import okhttp3.Interceptor
|
import okhttp3.Interceptor
|
||||||
@ -25,6 +25,8 @@ 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
|
||||||
@ -46,12 +48,12 @@ class Akuma(
|
|||||||
|
|
||||||
private var storedToken: String? = null
|
private var storedToken: String? = null
|
||||||
|
|
||||||
private val ddosGuardIntercept = DDosGuardInterceptor(network.cloudflareClient)
|
private val ddosGuardIntercept = DDosGuardInterceptor(network.client)
|
||||||
|
|
||||||
private val dateFormat = SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.ENGLISH).apply {
|
private val dateFormat = SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.ENGLISH).apply {
|
||||||
timeZone = TimeZone.getTimeZone("UTC")
|
timeZone = TimeZone.getTimeZone("UTC")
|
||||||
}
|
}
|
||||||
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
|
override val client: OkHttpClient = network.client.newBuilder()
|
||||||
.addInterceptor(ddosGuardIntercept)
|
.addInterceptor(ddosGuardIntercept)
|
||||||
.addInterceptor(::tokenInterceptor)
|
.addInterceptor(::tokenInterceptor)
|
||||||
.rateLimit(2)
|
.rateLimit(2)
|
||||||
@ -110,7 +112,9 @@ class Akuma(
|
|||||||
return storedToken!!
|
return storedToken!!
|
||||||
}
|
}
|
||||||
|
|
||||||
private val preferences: SharedPreferences by getPreferencesLazy()
|
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 displayFullTitle: Boolean get() = preferences.getBoolean(PREF_TITLE, false)
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
ext {
|
ext {
|
||||||
extName = 'Bato.to'
|
extName = 'Bato.to'
|
||||||
extClass = '.BatoToFactory'
|
extClass = '.BatoToFactory'
|
||||||
extVersionCode = 49
|
extVersionCode = 48
|
||||||
isNsfw = true
|
isNsfw = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,7 +20,6 @@ import eu.kanade.tachiyomi.source.model.SChapter
|
|||||||
import eu.kanade.tachiyomi.source.model.SManga
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
import eu.kanade.tachiyomi.source.online.ParsedHttpSource
|
import eu.kanade.tachiyomi.source.online.ParsedHttpSource
|
||||||
import eu.kanade.tachiyomi.util.asJsoup
|
import eu.kanade.tachiyomi.util.asJsoup
|
||||||
import keiyoushi.utils.getPreferencesLazy
|
|
||||||
import kotlinx.serialization.decodeFromString
|
import kotlinx.serialization.decodeFromString
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import kotlinx.serialization.json.JsonObject
|
import kotlinx.serialization.json.JsonObject
|
||||||
@ -49,7 +48,10 @@ open class BatoTo(
|
|||||||
private val siteLang: String,
|
private val siteLang: String,
|
||||||
) : ConfigurableSource, ParsedHttpSource() {
|
) : ConfigurableSource, ParsedHttpSource() {
|
||||||
|
|
||||||
private val preferences by getPreferencesLazy { migrateMirrorPref() }
|
private val preferences: SharedPreferences by lazy {
|
||||||
|
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
||||||
|
.migrateMirrorPref()
|
||||||
|
}
|
||||||
|
|
||||||
override val name: String = "Bato.to"
|
override val name: String = "Bato.to"
|
||||||
override val baseUrl: String get() = mirror
|
override val baseUrl: String get() = mirror
|
||||||
@ -123,12 +125,14 @@ open class BatoTo(
|
|||||||
return preferences.getBoolean("${REMOVE_TITLE_VERSION_PREF}_$lang", false)
|
return preferences.getBoolean("${REMOVE_TITLE_VERSION_PREF}_$lang", false)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun SharedPreferences.migrateMirrorPref() {
|
private fun SharedPreferences.migrateMirrorPref(): SharedPreferences {
|
||||||
val selectedMirror = getString("${MIRROR_PREF_KEY}_$lang", MIRROR_PREF_DEFAULT_VALUE)!!
|
val selectedMirror = getString("${MIRROR_PREF_KEY}_$lang", MIRROR_PREF_DEFAULT_VALUE)!!
|
||||||
|
|
||||||
if (selectedMirror in DEPRECATED_MIRRORS) {
|
if (selectedMirror in DEPRECATED_MIRRORS) {
|
||||||
edit().putString("${MIRROR_PREF_KEY}_$lang", MIRROR_PREF_DEFAULT_VALUE).commit()
|
edit().putString("${MIRROR_PREF_KEY}_$lang", MIRROR_PREF_DEFAULT_VALUE).commit()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
override val supportsLatest = true
|
override val supportsLatest = true
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
ext {
|
ext {
|
||||||
extName = 'Comic Fury'
|
extName = 'Comic Fury'
|
||||||
extClass = '.ComicFuryFactory'
|
extClass = '.ComicFuryFactory'
|
||||||
extVersionCode = 5
|
extVersionCode = 3
|
||||||
isNsfw = true
|
isNsfw = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package eu.kanade.tachiyomi.extension.all.comicfury
|
package eu.kanade.tachiyomi.extension.all.comicfury
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import androidx.preference.PreferenceScreen
|
import androidx.preference.PreferenceScreen
|
||||||
import androidx.preference.SwitchPreferenceCompat
|
import androidx.preference.SwitchPreferenceCompat
|
||||||
@ -15,12 +16,13 @@ import eu.kanade.tachiyomi.source.model.SChapter
|
|||||||
import eu.kanade.tachiyomi.source.model.SManga
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||||
import eu.kanade.tachiyomi.util.asJsoup
|
import eu.kanade.tachiyomi.util.asJsoup
|
||||||
import keiyoushi.utils.getPreferencesLazy
|
|
||||||
import okhttp3.HttpUrl
|
import okhttp3.HttpUrl
|
||||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
import org.jsoup.nodes.Element
|
import org.jsoup.nodes.Element
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
@ -33,7 +35,7 @@ class ComicFury(
|
|||||||
override val name: String = "Comic Fury$extraName" // Used for No Text
|
override val name: String = "Comic Fury$extraName" // Used for No Text
|
||||||
override val supportsLatest: Boolean = true
|
override val supportsLatest: Boolean = true
|
||||||
|
|
||||||
override val client = network.cloudflareClient.newBuilder().addInterceptor(TextInterceptor()).build()
|
override val client = super.client.newBuilder().addInterceptor(TextInterceptor()).build()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Archive is on a separate page from manga info
|
* Archive is on a separate page from manga info
|
||||||
@ -203,7 +205,9 @@ class ComicFury(
|
|||||||
private fun Boolean.toInt(): Int = if (this) { 0 } else { 1 }
|
private fun Boolean.toInt(): Int = if (this) { 0 } else { 1 }
|
||||||
|
|
||||||
// START OF AUTHOR NOTES //
|
// START OF AUTHOR NOTES //
|
||||||
private val preferences: SharedPreferences by getPreferencesLazy()
|
private val preferences: SharedPreferences by lazy {
|
||||||
|
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
||||||
|
}
|
||||||
companion object {
|
companion object {
|
||||||
private const val SHOW_AUTHORS_NOTES_KEY = "showAuthorsNotes"
|
private const val SHOW_AUTHORS_NOTES_KEY = "showAuthorsNotes"
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,6 @@ ext {
|
|||||||
themePkg = 'gigaviewer'
|
themePkg = 'gigaviewer'
|
||||||
baseUrl = 'https://comic-growl.com'
|
baseUrl = 'https://comic-growl.com'
|
||||||
overrideVersionCode = 0
|
overrideVersionCode = 0
|
||||||
isNsfw = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: "$rootDir/common.gradle"
|
apply from: "$rootDir/common.gradle"
|
||||||
|
@ -1,8 +1,5 @@
|
|||||||
ignored_groups_title=Ignored Groups
|
ignored_groups_title=Ignored Groups
|
||||||
ignored_groups_summary=Chapters from these groups won't be shown.\nOne group name per line (case-insensitive)
|
ignored_groups_summary=Chapters from these groups won't be shown.\nOne group name per line (case-insensitive)
|
||||||
show_alternative_titles_title=Show Alternative Titles
|
|
||||||
show_alternative_titles_on=Adds alternative titles to the description
|
|
||||||
show_alternative_titles_off=Does not show alternative titles to the description
|
|
||||||
include_tags_title=Include Tags
|
include_tags_title=Include Tags
|
||||||
include_tags_on=More specific, but might contain spoilers!
|
include_tags_on=More specific, but might contain spoilers!
|
||||||
include_tags_off=Only the broader genres
|
include_tags_off=Only the broader genres
|
||||||
@ -12,9 +9,6 @@ group_tags_off=List all tags together
|
|||||||
update_cover_title=Update Covers
|
update_cover_title=Update Covers
|
||||||
update_cover_on=Keep cover updated
|
update_cover_on=Keep cover updated
|
||||||
update_cover_off=Prefer first cover
|
update_cover_off=Prefer first cover
|
||||||
local_title_title=Translated Title
|
|
||||||
local_title_on=if available
|
|
||||||
local_title_off=Use the default title from the site
|
|
||||||
score_position_title=Score Position in the Description
|
score_position_title=Score Position in the Description
|
||||||
score_position_top=Top
|
score_position_top=Top
|
||||||
score_position_middle=Middle
|
score_position_middle=Middle
|
||||||
|
@ -1,8 +1,5 @@
|
|||||||
ignored_groups_title=Grupos Ignorados
|
ignored_groups_title=Grupos Ignorados
|
||||||
ignored_groups_summary=Capítulos desses grupos não aparecerão.\nUm grupo por linha
|
ignored_groups_summary=Capítulos desses grupos não aparecerão.\nUm grupo por linha
|
||||||
show_alternative_titles_title=Mostrar Títulos Alternativos
|
|
||||||
show_alternative_titles_on=Adiciona títulos alternativos à descrição
|
|
||||||
show_alternative_titles_off=Não mostra títulos alternativos na descrição
|
|
||||||
include_tags_title=Incluir Tags
|
include_tags_title=Incluir Tags
|
||||||
include_tags_on=Mais detalhadas, mas podem conter spoilers
|
include_tags_on=Mais detalhadas, mas podem conter spoilers
|
||||||
include_tags_off=Apenas os gêneros básicos
|
include_tags_off=Apenas os gêneros básicos
|
||||||
@ -12,9 +9,6 @@ group_tags_off=Listar todas as tags juntas
|
|||||||
update_cover_title=Atualizar Capas
|
update_cover_title=Atualizar Capas
|
||||||
update_cover_on=Manter capas atualizadas
|
update_cover_on=Manter capas atualizadas
|
||||||
update_cover_off=Usar apenas a primeira capa
|
update_cover_off=Usar apenas a primeira capa
|
||||||
local_title_title=Título Traduzido
|
|
||||||
local_title_on=se disponível
|
|
||||||
local_title_off=Usar o título padrão do site
|
|
||||||
score_position_title=Posição da Nota na Descrição
|
score_position_title=Posição da Nota na Descrição
|
||||||
score_position_top=Topo
|
score_position_top=Topo
|
||||||
score_position_middle=Meio
|
score_position_middle=Meio
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
ext {
|
ext {
|
||||||
extName = 'Comick'
|
extName = 'Comick'
|
||||||
extClass = '.ComickFactory'
|
extClass = '.ComickFactory'
|
||||||
extVersionCode = 55
|
extVersionCode = 52
|
||||||
isNsfw = true
|
isNsfw = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package eu.kanade.tachiyomi.extension.all.comickfun
|
package eu.kanade.tachiyomi.extension.all.comickfun
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import androidx.preference.EditTextPreference
|
import androidx.preference.EditTextPreference
|
||||||
import androidx.preference.ListPreference
|
import androidx.preference.ListPreference
|
||||||
@ -16,16 +17,16 @@ import eu.kanade.tachiyomi.source.model.Page
|
|||||||
import eu.kanade.tachiyomi.source.model.SChapter
|
import eu.kanade.tachiyomi.source.model.SChapter
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||||
import keiyoushi.utils.getPreferencesLazy
|
|
||||||
import kotlinx.serialization.decodeFromString
|
import kotlinx.serialization.decodeFromString
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import okhttp3.Headers
|
import okhttp3.Headers
|
||||||
import okhttp3.HttpUrl.Builder
|
|
||||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||||
import okhttp3.Interceptor
|
import okhttp3.Interceptor
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
import java.text.ParseException
|
import java.text.ParseException
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
@ -64,7 +65,10 @@ abstract class Comick(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val preferences by getPreferencesLazy { newLineIgnoredGroups() }
|
private val preferences by lazy {
|
||||||
|
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
||||||
|
.newLineIgnoredGroups()
|
||||||
|
}
|
||||||
|
|
||||||
override fun setupPreferenceScreen(screen: PreferenceScreen) {
|
override fun setupPreferenceScreen(screen: PreferenceScreen) {
|
||||||
EditTextPreference(screen.context).apply {
|
EditTextPreference(screen.context).apply {
|
||||||
@ -79,20 +83,6 @@ abstract class Comick(
|
|||||||
}
|
}
|
||||||
}.also(screen::addPreference)
|
}.also(screen::addPreference)
|
||||||
|
|
||||||
SwitchPreferenceCompat(screen.context).apply {
|
|
||||||
key = SHOW_ALTERNATIVE_TITLES_PREF
|
|
||||||
title = intl["show_alternative_titles_title"]
|
|
||||||
summaryOn = intl["show_alternative_titles_on"]
|
|
||||||
summaryOff = intl["show_alternative_titles_off"]
|
|
||||||
setDefaultValue(SHOW_ALTERNATIVE_TITLES_DEFAULT)
|
|
||||||
|
|
||||||
setOnPreferenceChangeListener { _, newValue ->
|
|
||||||
preferences.edit()
|
|
||||||
.putBoolean(SHOW_ALTERNATIVE_TITLES_PREF, newValue as Boolean)
|
|
||||||
.commit()
|
|
||||||
}
|
|
||||||
}.also(screen::addPreference)
|
|
||||||
|
|
||||||
SwitchPreferenceCompat(screen.context).apply {
|
SwitchPreferenceCompat(screen.context).apply {
|
||||||
key = INCLUDE_MU_TAGS_PREF
|
key = INCLUDE_MU_TAGS_PREF
|
||||||
title = intl["include_tags_title"]
|
title = intl["include_tags_title"]
|
||||||
@ -135,20 +125,6 @@ abstract class Comick(
|
|||||||
}
|
}
|
||||||
}.also(screen::addPreference)
|
}.also(screen::addPreference)
|
||||||
|
|
||||||
SwitchPreferenceCompat(screen.context).apply {
|
|
||||||
key = LOCAL_TITLE_PREF
|
|
||||||
title = intl["local_title_title"]
|
|
||||||
summaryOff = intl["local_title_off"]
|
|
||||||
summaryOn = intl["local_title_on"]
|
|
||||||
setDefaultValue(LOCAL_TITLE_DEFAULT)
|
|
||||||
|
|
||||||
setOnPreferenceChangeListener { _, newValue ->
|
|
||||||
preferences.edit()
|
|
||||||
.putBoolean(LOCAL_TITLE_PREF, newValue as Boolean)
|
|
||||||
.commit()
|
|
||||||
}
|
|
||||||
}.also(screen::addPreference)
|
|
||||||
|
|
||||||
ListPreference(screen.context).apply {
|
ListPreference(screen.context).apply {
|
||||||
key = SCORE_POSITION_PREF
|
key = SCORE_POSITION_PREF
|
||||||
title = intl["score_position_title"]
|
title = intl["score_position_title"]
|
||||||
@ -184,9 +160,6 @@ abstract class Comick(
|
|||||||
.orEmpty()
|
.orEmpty()
|
||||||
.toSet()
|
.toSet()
|
||||||
|
|
||||||
private val SharedPreferences.showAlternativeTitles: Boolean
|
|
||||||
get() = getBoolean(SHOW_ALTERNATIVE_TITLES_PREF, SHOW_ALTERNATIVE_TITLES_DEFAULT)
|
|
||||||
|
|
||||||
private val SharedPreferences.includeMuTags: Boolean
|
private val SharedPreferences.includeMuTags: Boolean
|
||||||
get() = getBoolean(INCLUDE_MU_TAGS_PREF, INCLUDE_MU_TAGS_DEFAULT)
|
get() = getBoolean(INCLUDE_MU_TAGS_PREF, INCLUDE_MU_TAGS_DEFAULT)
|
||||||
|
|
||||||
@ -196,17 +169,6 @@ abstract class Comick(
|
|||||||
private val SharedPreferences.updateCover: Boolean
|
private val SharedPreferences.updateCover: Boolean
|
||||||
get() = getBoolean(FIRST_COVER_PREF, FIRST_COVER_DEFAULT)
|
get() = getBoolean(FIRST_COVER_PREF, FIRST_COVER_DEFAULT)
|
||||||
|
|
||||||
private val SharedPreferences.localTitle: String
|
|
||||||
get() = if (getBoolean(
|
|
||||||
LOCAL_TITLE_PREF,
|
|
||||||
LOCAL_TITLE_DEFAULT,
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
comickLang.lowercase()
|
|
||||||
} else {
|
|
||||||
"all"
|
|
||||||
}
|
|
||||||
|
|
||||||
private val SharedPreferences.scorePosition: String
|
private val SharedPreferences.scorePosition: String
|
||||||
get() = getString(SCORE_POSITION_PREF, SCORE_POSITION_DEFAULT) ?: SCORE_POSITION_DEFAULT
|
get() = getString(SCORE_POSITION_PREF, SCORE_POSITION_DEFAULT) ?: SCORE_POSITION_DEFAULT
|
||||||
|
|
||||||
@ -315,16 +277,6 @@ abstract class Comick(
|
|||||||
return MangasPage(entries, end < searchResponse.size)
|
return MangasPage(entries, end < searchResponse.size)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun addTagQueryParameters(builder: Builder, tags: String, parameterName: String) {
|
|
||||||
tags.split(",").forEach {
|
|
||||||
builder.addQueryParameter(
|
|
||||||
parameterName,
|
|
||||||
it.trim().lowercase().replace(SPACE_AND_SLASH_REGEX, "-")
|
|
||||||
.replace("'-", "-and-039-").replace("'", "-and-039-"),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||||
val url = "$apiUrl/v1.0/search".toHttpUrl().newBuilder().apply {
|
val url = "$apiUrl/v1.0/search".toHttpUrl().newBuilder().apply {
|
||||||
filters.forEach { it ->
|
filters.forEach { it ->
|
||||||
@ -346,7 +298,7 @@ abstract class Comick(
|
|||||||
}
|
}
|
||||||
|
|
||||||
is DemographicFilter -> {
|
is DemographicFilter -> {
|
||||||
it.state.filter { it.state }.forEach {
|
it.state.filter { it.isIncluded() }.forEach {
|
||||||
addQueryParameter("demographic", it.value)
|
addQueryParameter("demographic", it.value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -367,12 +319,6 @@ abstract class Comick(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is ContentRatingFilter -> {
|
|
||||||
if (it.state > 0) {
|
|
||||||
addQueryParameter("content_rating", it.getValue())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
is CreatedAtFilter -> {
|
is CreatedAtFilter -> {
|
||||||
if (it.state > 0) {
|
if (it.state > 0) {
|
||||||
addQueryParameter("time", it.getValue())
|
addQueryParameter("time", it.getValue())
|
||||||
@ -399,13 +345,13 @@ abstract class Comick(
|
|||||||
|
|
||||||
is TagFilter -> {
|
is TagFilter -> {
|
||||||
if (it.state.isNotEmpty()) {
|
if (it.state.isNotEmpty()) {
|
||||||
addTagQueryParameters(this, it.state, "tags")
|
it.state.split(",").forEach {
|
||||||
}
|
addQueryParameter(
|
||||||
}
|
"tags",
|
||||||
|
it.trim().lowercase().replace(SPACE_AND_SLASH_REGEX, "-")
|
||||||
is ExcludedTagFilter -> {
|
.replace("'-", "-and-039-").replace("'", "-and-039-"),
|
||||||
if (it.state.isNotEmpty()) {
|
)
|
||||||
addTagQueryParameters(this, it.state, "excluded-tags")
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -459,18 +405,14 @@ abstract class Comick(
|
|||||||
return mangaData.toSManga(
|
return mangaData.toSManga(
|
||||||
includeMuTags = preferences.includeMuTags,
|
includeMuTags = preferences.includeMuTags,
|
||||||
scorePosition = preferences.scorePosition,
|
scorePosition = preferences.scorePosition,
|
||||||
showAlternativeTitles = preferences.showAlternativeTitles,
|
|
||||||
covers = localCovers.ifEmpty { originalCovers }.ifEmpty { firstVol },
|
covers = localCovers.ifEmpty { originalCovers }.ifEmpty { firstVol },
|
||||||
groupTags = preferences.groupTags,
|
groupTags = preferences.groupTags,
|
||||||
titleLang = preferences.localTitle,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return mangaData.toSManga(
|
return mangaData.toSManga(
|
||||||
includeMuTags = preferences.includeMuTags,
|
includeMuTags = preferences.includeMuTags,
|
||||||
scorePosition = preferences.scorePosition,
|
scorePosition = preferences.scorePosition,
|
||||||
showAlternativeTitles = preferences.showAlternativeTitles,
|
|
||||||
groupTags = preferences.groupTags,
|
groupTags = preferences.groupTags,
|
||||||
titleLang = preferences.localTitle,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -565,9 +507,8 @@ abstract class Comick(
|
|||||||
|
|
||||||
override fun getFilterList() = getFilters()
|
override fun getFilterList() = getFilters()
|
||||||
|
|
||||||
private fun SharedPreferences.newLineIgnoredGroups() {
|
private fun SharedPreferences.newLineIgnoredGroups(): SharedPreferences {
|
||||||
if (getBoolean(MIGRATED_IGNORED_GROUPS, false)) return
|
if (getBoolean(MIGRATED_IGNORED_GROUPS, false)) return this
|
||||||
|
|
||||||
val ignoredGroups = getString(IGNORED_GROUPS_PREF, "").orEmpty()
|
val ignoredGroups = getString(IGNORED_GROUPS_PREF, "").orEmpty()
|
||||||
|
|
||||||
edit()
|
edit()
|
||||||
@ -581,14 +522,14 @@ abstract class Comick(
|
|||||||
)
|
)
|
||||||
.putBoolean(MIGRATED_IGNORED_GROUPS, true)
|
.putBoolean(MIGRATED_IGNORED_GROUPS, true)
|
||||||
.apply()
|
.apply()
|
||||||
|
|
||||||
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val SLUG_SEARCH_PREFIX = "id:"
|
const val SLUG_SEARCH_PREFIX = "id:"
|
||||||
private val SPACE_AND_SLASH_REGEX = Regex("[ /]")
|
private val SPACE_AND_SLASH_REGEX = Regex("[ /]")
|
||||||
private const val IGNORED_GROUPS_PREF = "IgnoredGroups"
|
private const val IGNORED_GROUPS_PREF = "IgnoredGroups"
|
||||||
private const val SHOW_ALTERNATIVE_TITLES_PREF = "ShowAlternativeTitles"
|
|
||||||
const val SHOW_ALTERNATIVE_TITLES_DEFAULT = false
|
|
||||||
private const val INCLUDE_MU_TAGS_PREF = "IncludeMangaUpdatesTags"
|
private const val INCLUDE_MU_TAGS_PREF = "IncludeMangaUpdatesTags"
|
||||||
const val INCLUDE_MU_TAGS_DEFAULT = false
|
const val INCLUDE_MU_TAGS_DEFAULT = false
|
||||||
private const val GROUP_TAGS_PREF = "GroupTags"
|
private const val GROUP_TAGS_PREF = "GroupTags"
|
||||||
@ -598,8 +539,6 @@ abstract class Comick(
|
|||||||
private const val FIRST_COVER_DEFAULT = true
|
private const val FIRST_COVER_DEFAULT = true
|
||||||
private const val SCORE_POSITION_PREF = "ScorePosition"
|
private const val SCORE_POSITION_PREF = "ScorePosition"
|
||||||
const val SCORE_POSITION_DEFAULT = "top"
|
const val SCORE_POSITION_DEFAULT = "top"
|
||||||
private const val LOCAL_TITLE_PREF = "LocalTitle"
|
|
||||||
private const val LOCAL_TITLE_DEFAULT = false
|
|
||||||
private const val LIMIT = 20
|
private const val LIMIT = 20
|
||||||
private const val CHAPTERS_LIMIT = 99999
|
private const val CHAPTERS_LIMIT = 99999
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,6 @@ package eu.kanade.tachiyomi.extension.all.comickfun
|
|||||||
import eu.kanade.tachiyomi.extension.all.comickfun.Comick.Companion.GROUP_TAGS_DEFAULT
|
import eu.kanade.tachiyomi.extension.all.comickfun.Comick.Companion.GROUP_TAGS_DEFAULT
|
||||||
import eu.kanade.tachiyomi.extension.all.comickfun.Comick.Companion.INCLUDE_MU_TAGS_DEFAULT
|
import eu.kanade.tachiyomi.extension.all.comickfun.Comick.Companion.INCLUDE_MU_TAGS_DEFAULT
|
||||||
import eu.kanade.tachiyomi.extension.all.comickfun.Comick.Companion.SCORE_POSITION_DEFAULT
|
import eu.kanade.tachiyomi.extension.all.comickfun.Comick.Companion.SCORE_POSITION_DEFAULT
|
||||||
import eu.kanade.tachiyomi.extension.all.comickfun.Comick.Companion.SHOW_ALTERNATIVE_TITLES_DEFAULT
|
|
||||||
import eu.kanade.tachiyomi.source.model.SChapter
|
import eu.kanade.tachiyomi.source.model.SChapter
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
import kotlinx.serialization.SerialName
|
import kotlinx.serialization.SerialName
|
||||||
@ -37,20 +36,13 @@ class Manga(
|
|||||||
fun toSManga(
|
fun toSManga(
|
||||||
includeMuTags: Boolean = INCLUDE_MU_TAGS_DEFAULT,
|
includeMuTags: Boolean = INCLUDE_MU_TAGS_DEFAULT,
|
||||||
scorePosition: String = SCORE_POSITION_DEFAULT,
|
scorePosition: String = SCORE_POSITION_DEFAULT,
|
||||||
showAlternativeTitles: Boolean = SHOW_ALTERNATIVE_TITLES_DEFAULT,
|
|
||||||
covers: List<MDcovers>? = null,
|
covers: List<MDcovers>? = null,
|
||||||
groupTags: Boolean = GROUP_TAGS_DEFAULT,
|
groupTags: Boolean = GROUP_TAGS_DEFAULT,
|
||||||
titleLang: String,
|
) =
|
||||||
): SManga {
|
SManga.create().apply {
|
||||||
val entryTitle = comic.altTitles.firstOrNull {
|
|
||||||
titleLang != "all" && !it.lang.isNullOrBlank() && titleLang.startsWith(it.lang)
|
|
||||||
}?.title ?: comic.title
|
|
||||||
val titles = listOf(Title(title = comic.title)) + comic.altTitles
|
|
||||||
|
|
||||||
return SManga.create().apply {
|
|
||||||
// appennding # at end as part of migration from slug to hid
|
// appennding # at end as part of migration from slug to hid
|
||||||
url = "/comic/${comic.hid}#"
|
url = "/comic/${comic.hid}#"
|
||||||
title = entryTitle
|
title = comic.title
|
||||||
description = buildString {
|
description = buildString {
|
||||||
if (scorePosition == "top") append(comic.fancyScore)
|
if (scorePosition == "top") append(comic.fancyScore)
|
||||||
val desc = comic.desc?.beautifyDescription()
|
val desc = comic.desc?.beautifyDescription()
|
||||||
@ -62,14 +54,13 @@ class Manga(
|
|||||||
if (this.isNotEmpty()) append("\n\n")
|
if (this.isNotEmpty()) append("\n\n")
|
||||||
append(comic.fancyScore)
|
append(comic.fancyScore)
|
||||||
}
|
}
|
||||||
if (showAlternativeTitles && comic.altTitles.isNotEmpty()) {
|
if (comic.altTitles.isNotEmpty()) {
|
||||||
if (this.isNotEmpty()) append("\n\n")
|
if (this.isNotEmpty()) append("\n\n")
|
||||||
append("Alternative Titles:\n")
|
append("Alternative Titles:\n")
|
||||||
append(
|
append(
|
||||||
titles.distinctBy { it.title }.filter { it.title != entryTitle }
|
comic.altTitles.mapNotNull { title ->
|
||||||
.mapNotNull { title ->
|
title.title?.let { "• $it" }
|
||||||
title.title?.let { "• $it" }
|
}.joinToString("\n"),
|
||||||
}.joinToString("\n"),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (scorePosition == "bottom") {
|
if (scorePosition == "bottom") {
|
||||||
@ -104,7 +95,6 @@ class Manga(
|
|||||||
.filterNot { it.name.isNullOrBlank() || it.group.isNullOrBlank() }
|
.filterNot { it.name.isNullOrBlank() || it.group.isNullOrBlank() }
|
||||||
.joinToString { if (groupTags) "${it.group}:${it.name?.trim()}" else "${it.name?.trim()}" }
|
.joinToString { if (groupTags) "${it.group}:${it.name?.trim()}" else "${it.name?.trim()}" }
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
@ -179,7 +169,6 @@ class MDcovers(
|
|||||||
@Serializable
|
@Serializable
|
||||||
class Title(
|
class Title(
|
||||||
val title: String?,
|
val title: String?,
|
||||||
val lang: String? = null,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
|
@ -11,7 +11,6 @@ fun getFilters(): FilterList {
|
|||||||
TypeFilter("Type", getTypeList),
|
TypeFilter("Type", getTypeList),
|
||||||
SortFilter("Sort", getSortsList),
|
SortFilter("Sort", getSortsList),
|
||||||
StatusFilter("Status", getStatusList),
|
StatusFilter("Status", getStatusList),
|
||||||
ContentRatingFilter("Content Rating", getContentRatingList),
|
|
||||||
CompletedFilter("Completely Scanlated?"),
|
CompletedFilter("Completely Scanlated?"),
|
||||||
CreatedAtFilter("Created at", getCreatedAtList),
|
CreatedAtFilter("Created at", getCreatedAtList),
|
||||||
MinimumFilter("Minimum Chapters"),
|
MinimumFilter("Minimum Chapters"),
|
||||||
@ -21,7 +20,6 @@ fun getFilters(): FilterList {
|
|||||||
ToYearFilter("To"),
|
ToYearFilter("To"),
|
||||||
Filter.Header("Separate tags with commas"),
|
Filter.Header("Separate tags with commas"),
|
||||||
TagFilter("Tags"),
|
TagFilter("Tags"),
|
||||||
ExcludedTagFilter("Excluded Tags"),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -31,10 +29,8 @@ internal class GenreFilter(name: String, genreList: List<Pair<String, String>>)
|
|||||||
|
|
||||||
internal class TagFilter(name: String) : TextFilter(name)
|
internal class TagFilter(name: String) : TextFilter(name)
|
||||||
|
|
||||||
internal class ExcludedTagFilter(name: String) : TextFilter(name)
|
|
||||||
|
|
||||||
internal class DemographicFilter(name: String, demographicList: List<Pair<String, String>>) :
|
internal class DemographicFilter(name: String, demographicList: List<Pair<String, String>>) :
|
||||||
Filter.Group<CheckBoxFilter>(name, demographicList.map { CheckBoxFilter(it.first, it.second) })
|
Filter.Group<TriFilter>(name, demographicList.map { TriFilter(it.first, it.second) })
|
||||||
|
|
||||||
internal class TypeFilter(name: String, typeList: List<Pair<String, String>>) :
|
internal class TypeFilter(name: String, typeList: List<Pair<String, String>>) :
|
||||||
Filter.Group<CheckBoxFilter>(name, typeList.map { CheckBoxFilter(it.first, it.second) })
|
Filter.Group<CheckBoxFilter>(name, typeList.map { CheckBoxFilter(it.first, it.second) })
|
||||||
@ -56,9 +52,6 @@ internal class SortFilter(name: String, sortList: List<Pair<String, String>>, st
|
|||||||
internal class StatusFilter(name: String, statusList: List<Pair<String, String>>, state: Int = 0) :
|
internal class StatusFilter(name: String, statusList: List<Pair<String, String>>, state: Int = 0) :
|
||||||
SelectFilter(name, statusList, state)
|
SelectFilter(name, statusList, state)
|
||||||
|
|
||||||
internal class ContentRatingFilter(name: String, statusList: List<Pair<String, String>>, state: Int = 0) :
|
|
||||||
SelectFilter(name, statusList, state)
|
|
||||||
|
|
||||||
/** Generics **/
|
/** Generics **/
|
||||||
internal open class TriFilter(name: String, val value: String) : Filter.TriState(name)
|
internal open class TriFilter(name: String, val value: String) : Filter.TriState(name)
|
||||||
|
|
||||||
@ -163,14 +156,12 @@ private val getDemographicList: List<Pair<String, String>> = listOf(
|
|||||||
Pair("Shoujo", "2"),
|
Pair("Shoujo", "2"),
|
||||||
Pair("Seinen", "3"),
|
Pair("Seinen", "3"),
|
||||||
Pair("Josei", "4"),
|
Pair("Josei", "4"),
|
||||||
Pair("None", "5"),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
private val getTypeList: List<Pair<String, String>> = listOf(
|
private val getTypeList: List<Pair<String, String>> = listOf(
|
||||||
Pair("Manga", "jp"),
|
Pair("Manga", "jp"),
|
||||||
Pair("Manhwa", "kr"),
|
Pair("Manhwa", "kr"),
|
||||||
Pair("Manhua", "cn"),
|
Pair("Manhua", "cn"),
|
||||||
Pair("Others", "others"),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
private val getCreatedAtList: List<Pair<String, String>> = listOf(
|
private val getCreatedAtList: List<Pair<String, String>> = listOf(
|
||||||
@ -199,10 +190,3 @@ private val getStatusList: List<Pair<String, String>> = listOf(
|
|||||||
Pair("Cancelled", "3"),
|
Pair("Cancelled", "3"),
|
||||||
Pair("Hiatus", "4"),
|
Pair("Hiatus", "4"),
|
||||||
)
|
)
|
||||||
|
|
||||||
private val getContentRatingList: List<Pair<String, String>> = listOf(
|
|
||||||
Pair("All", ""),
|
|
||||||
Pair("Safe", "safe"),
|
|
||||||
Pair("Suggestive", "suggestive"),
|
|
||||||
Pair("Erotica", "erotica"),
|
|
||||||
)
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
ext {
|
ext {
|
||||||
extName = 'Comico'
|
extName = 'Comico'
|
||||||
extClass = '.ComicoFactory'
|
extClass = '.ComicoFactory'
|
||||||
extVersionCode = 7
|
extVersionCode = 6
|
||||||
isNsfw = true
|
isNsfw = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,7 +62,7 @@ open class Comico(
|
|||||||
this["Origin"] = baseUrl
|
this["Origin"] = baseUrl
|
||||||
}.build()
|
}.build()
|
||||||
|
|
||||||
override val client = network.cloudflareClient.newBuilder()
|
override val client = network.client.newBuilder()
|
||||||
.cookieJar(
|
.cookieJar(
|
||||||
object : CookieJar {
|
object : CookieJar {
|
||||||
override fun saveFromResponse(url: HttpUrl, cookies: List<Cookie>) =
|
override fun saveFromResponse(url: HttpUrl, cookies: List<Cookie>) =
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
ext {
|
ext {
|
||||||
extName = 'ComicsKingdom'
|
extName = 'ComicsKingdom'
|
||||||
extClass = '.ComicsKingdomFactory'
|
extClass = '.ComicsKingdomFactory'
|
||||||
extVersionCode = 2
|
extVersionCode = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: "$rootDir/common.gradle"
|
apply from: "$rootDir/common.gradle"
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package eu.kanade.tachiyomi.extension.all.comicskingdom
|
package eu.kanade.tachiyomi.extension.all.comicskingdom
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import androidx.preference.PreferenceScreen
|
import androidx.preference.PreferenceScreen
|
||||||
import eu.kanade.tachiyomi.network.GET
|
import eu.kanade.tachiyomi.network.GET
|
||||||
@ -11,7 +12,6 @@ import eu.kanade.tachiyomi.source.model.Page
|
|||||||
import eu.kanade.tachiyomi.source.model.SChapter
|
import eu.kanade.tachiyomi.source.model.SChapter
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||||
import keiyoushi.utils.getPreferencesLazy
|
|
||||||
import kotlinx.serialization.decodeFromString
|
import kotlinx.serialization.decodeFromString
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import okhttp3.HttpUrl
|
import okhttp3.HttpUrl
|
||||||
@ -19,6 +19,8 @@ import okhttp3.HttpUrl.Companion.toHttpUrl
|
|||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
import org.jsoup.Jsoup
|
import org.jsoup.Jsoup
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
@ -299,7 +301,9 @@ class ComicsKingdom(override val lang: String) : ConfigurableSource, HttpSource(
|
|||||||
screen.addPreference(compactpref)
|
screen.addPreference(compactpref)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val preferences: SharedPreferences by getPreferencesLazy()
|
private val preferences: SharedPreferences by lazy {
|
||||||
|
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
||||||
|
}
|
||||||
|
|
||||||
private fun shouldCompact() = preferences.getBoolean("compactPref", true)
|
private fun shouldCompact() = preferences.getBoolean("compactPref", true)
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
ext {
|
ext {
|
||||||
extName = "Comikey"
|
extName = "Comikey"
|
||||||
extClass = ".ComikeyFactory"
|
extClass = ".ComikeyFactory"
|
||||||
extVersionCode = 3
|
extVersionCode = 2
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: "$rootDir/common.gradle"
|
apply from: "$rootDir/common.gradle"
|
||||||
|
@ -24,7 +24,6 @@ import eu.kanade.tachiyomi.source.model.SChapter
|
|||||||
import eu.kanade.tachiyomi.source.model.SManga
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
import eu.kanade.tachiyomi.source.online.ParsedHttpSource
|
import eu.kanade.tachiyomi.source.online.ParsedHttpSource
|
||||||
import eu.kanade.tachiyomi.util.asJsoup
|
import eu.kanade.tachiyomi.util.asJsoup
|
||||||
import keiyoushi.utils.getPreferencesLazy
|
|
||||||
import kotlinx.serialization.decodeFromString
|
import kotlinx.serialization.decodeFromString
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import okhttp3.HttpUrl
|
import okhttp3.HttpUrl
|
||||||
@ -76,7 +75,9 @@ open class Comikey(
|
|||||||
classLoader = this::class.java.classLoader!!,
|
classLoader = this::class.java.classLoader!!,
|
||||||
)
|
)
|
||||||
|
|
||||||
private val preferences by getPreferencesLazy()
|
private val preferences by lazy {
|
||||||
|
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
||||||
|
}
|
||||||
|
|
||||||
override fun popularMangaRequest(page: Int) = GET("$baseUrl/comics/?order=-views&page=$page", headers)
|
override fun popularMangaRequest(page: Int) = GET("$baseUrl/comics/?order=-views&page=$page", headers)
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
ext {
|
ext {
|
||||||
extName = 'Danbooru'
|
extName = 'Danbooru'
|
||||||
extClass = '.Danbooru'
|
extClass = '.Danbooru'
|
||||||
extVersionCode = 3
|
extVersionCode = 2
|
||||||
isNsfw = true
|
isNsfw = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,46 +1,51 @@
|
|||||||
package eu.kanade.tachiyomi.extension.all.danbooru
|
package eu.kanade.tachiyomi.extension.all.danbooru
|
||||||
|
|
||||||
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.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.Page
|
import eu.kanade.tachiyomi.source.model.Page
|
||||||
import eu.kanade.tachiyomi.source.model.SChapter
|
import eu.kanade.tachiyomi.source.model.SChapter
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
import eu.kanade.tachiyomi.source.model.UpdateStrategy
|
import eu.kanade.tachiyomi.source.model.UpdateStrategy
|
||||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
import eu.kanade.tachiyomi.source.online.ParsedHttpSource
|
||||||
import eu.kanade.tachiyomi.util.asJsoup
|
import kotlinx.serialization.decodeFromString
|
||||||
import keiyoushi.utils.getPreferencesLazy
|
import kotlinx.serialization.json.Json
|
||||||
import keiyoushi.utils.parseAs
|
import kotlinx.serialization.json.JsonObject
|
||||||
import keiyoushi.utils.tryParse
|
import kotlinx.serialization.json.jsonArray
|
||||||
|
import kotlinx.serialization.json.jsonPrimitive
|
||||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
|
import org.jsoup.nodes.Document
|
||||||
import org.jsoup.nodes.Element
|
import org.jsoup.nodes.Element
|
||||||
|
import uy.kohesive.injekt.injectLazy
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
class Danbooru : HttpSource(), ConfigurableSource {
|
class Danbooru : ParsedHttpSource() {
|
||||||
override val name: String = "Danbooru"
|
override val name: String = "Danbooru"
|
||||||
override val baseUrl: String = "https://danbooru.donmai.us"
|
override val baseUrl: String = "https://danbooru.donmai.us"
|
||||||
override val lang: String = "all"
|
override val lang: String = "all"
|
||||||
override val supportsLatest: Boolean = true
|
override val supportsLatest: Boolean = true
|
||||||
|
|
||||||
override val client = network.cloudflareClient
|
override val client: OkHttpClient = network.cloudflareClient
|
||||||
|
private val json: Json by injectLazy()
|
||||||
|
|
||||||
private val dateFormat =
|
private val dateFormat: SimpleDateFormat by lazy {
|
||||||
SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.ENGLISH)
|
SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.ENGLISH)
|
||||||
|
}
|
||||||
private val preference by getPreferencesLazy()
|
|
||||||
|
|
||||||
override fun popularMangaRequest(page: Int): Request =
|
override fun popularMangaRequest(page: Int): Request =
|
||||||
searchMangaRequest(page, "", FilterList())
|
searchMangaRequest(page, "", FilterList())
|
||||||
|
|
||||||
override fun popularMangaParse(response: Response) =
|
override fun popularMangaFromElement(element: Element): SManga =
|
||||||
searchMangaParse(response)
|
searchMangaFromElement(element)
|
||||||
|
|
||||||
|
override fun popularMangaNextPageSelector(): String =
|
||||||
|
searchMangaNextPageSelector()
|
||||||
|
|
||||||
|
override fun popularMangaSelector(): String =
|
||||||
|
searchMangaSelector()
|
||||||
|
|
||||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||||
val url = "$baseUrl/pools/gallery".toHttpUrl().newBuilder()
|
val url = "$baseUrl/pools/gallery".toHttpUrl().newBuilder()
|
||||||
@ -82,100 +87,86 @@ class Danbooru : HttpSource(), ConfigurableSource {
|
|||||||
return GET(url.build(), headers)
|
return GET(url.build(), headers)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun searchMangaParse(response: Response): MangasPage {
|
override fun searchMangaSelector(): String =
|
||||||
val document = response.asJsoup()
|
"article.post-preview"
|
||||||
|
|
||||||
val entries = document.select("article.post-preview").map {
|
override fun searchMangaFromElement(element: Element) = SManga.create().apply {
|
||||||
searchMangaFromElement(it)
|
url = element.selectFirst(".post-preview-link")?.attr("href")!!
|
||||||
}
|
title = element.selectFirst("div.text-center")?.text() ?: ""
|
||||||
val hasNextPage = document.selectFirst("a.paginator-next") != null
|
|
||||||
|
|
||||||
return MangasPage(entries, hasNextPage)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun searchMangaFromElement(element: Element) = SManga.create().apply {
|
|
||||||
url = element.selectFirst(".post-preview-link")!!.attr("href")
|
|
||||||
title = element.selectFirst("div.text-center")!!.text()
|
|
||||||
|
|
||||||
thumbnail_url = element.selectFirst("source")?.attr("srcset")
|
thumbnail_url = element.selectFirst("source")?.attr("srcset")
|
||||||
?.substringAfterLast(',')?.trim()
|
?.substringAfterLast(',')?.trim()
|
||||||
?.substringBeforeLast(' ')?.trimStart()
|
?.substringBeforeLast(' ')?.trimStart()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun searchMangaNextPageSelector(): String =
|
||||||
|
"a.paginator-next"
|
||||||
|
|
||||||
override fun latestUpdatesRequest(page: Int): Request =
|
override fun latestUpdatesRequest(page: Int): Request =
|
||||||
searchMangaRequest(page, "", FilterList(FilterOrder("created_at")))
|
searchMangaRequest(page, "", FilterList(FilterOrder("created_at")))
|
||||||
|
|
||||||
override fun latestUpdatesParse(response: Response): MangasPage =
|
override fun latestUpdatesSelector(): String =
|
||||||
searchMangaParse(response)
|
searchMangaSelector()
|
||||||
|
|
||||||
override fun mangaDetailsParse(response: Response) = SManga.create().apply {
|
override fun latestUpdatesFromElement(element: Element): SManga =
|
||||||
val document = response.asJsoup()
|
searchMangaFromElement(element)
|
||||||
|
|
||||||
|
override fun latestUpdatesNextPageSelector(): String =
|
||||||
|
searchMangaNextPageSelector()
|
||||||
|
|
||||||
|
override fun mangaDetailsParse(document: Document) = SManga.create().apply {
|
||||||
setUrlWithoutDomain(document.location())
|
setUrlWithoutDomain(document.location())
|
||||||
title = document.selectFirst(".pool-category-series, .pool-category-collection")!!.text()
|
|
||||||
description = document.getElementById("description")?.wholeText()
|
title = document.selectFirst(".pool-category-series, .pool-category-collection")?.text() ?: ""
|
||||||
author = document.selectFirst("#description a[href*=artists]")?.ownText()
|
description = document.getElementById("description")?.wholeText() ?: ""
|
||||||
artist = author
|
update_strategy = UpdateStrategy.ONLY_FETCH_ONCE
|
||||||
update_strategy = if (!preference.splitChaptersPref) {
|
|
||||||
UpdateStrategy.ONLY_FETCH_ONCE
|
|
||||||
} else {
|
|
||||||
UpdateStrategy.ALWAYS_UPDATE
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun chapterListRequest(manga: SManga): Request =
|
override fun chapterListRequest(manga: SManga): Request =
|
||||||
GET("$baseUrl${manga.url}.json", headers)
|
GET("$baseUrl${manga.url}.json?only=id,created_at", headers)
|
||||||
|
|
||||||
override fun chapterListParse(response: Response): List<SChapter> {
|
override fun chapterListParse(response: Response): List<SChapter> = listOf(
|
||||||
val data = response.parseAs<Pool>()
|
SChapter.create().apply {
|
||||||
|
val data = json.decodeFromString<JsonObject>(response.body.string())
|
||||||
|
|
||||||
return if (preference.splitChaptersPref) {
|
val id = data["id"]!!.jsonPrimitive.content
|
||||||
data.postIds.mapIndexed { index, id ->
|
val createdAt = data["created_at"]?.jsonPrimitive?.content
|
||||||
SChapter.create().apply {
|
|
||||||
url = "/posts/$id"
|
url = "/pools/$id"
|
||||||
name = "Post ${index + 1}"
|
name = "Oneshot"
|
||||||
chapter_number = index + 1f
|
date_upload = createdAt?.let(::parseTimestamp) ?: 0
|
||||||
}
|
chapter_number = 0F
|
||||||
}.reversed().apply {
|
},
|
||||||
if (isNotEmpty()) {
|
)
|
||||||
this[0].date_upload = dateFormat.tryParse(data.updatedAt)
|
|
||||||
}
|
override fun chapterListSelector(): String =
|
||||||
}
|
throw IllegalStateException("Not used")
|
||||||
} else {
|
|
||||||
listOf(
|
override fun chapterFromElement(element: Element): SChapter =
|
||||||
SChapter.create().apply {
|
throw IllegalStateException("Not used")
|
||||||
url = "/pools/${data.id}"
|
|
||||||
name = "Oneshot"
|
|
||||||
date_upload = dateFormat.tryParse(data.updatedAt)
|
|
||||||
chapter_number = 0F
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun pageListRequest(chapter: SChapter): Request =
|
override fun pageListRequest(chapter: SChapter): Request =
|
||||||
GET("$baseUrl${chapter.url}.json", headers)
|
GET("$baseUrl${chapter.url}.json?only=post_ids", headers)
|
||||||
|
|
||||||
override fun pageListParse(response: Response): List<Page> =
|
override fun pageListParse(response: Response): List<Page> =
|
||||||
if (response.request.url.toString().contains("/posts/")) {
|
json.decodeFromString<JsonObject>(response.body.string())
|
||||||
val data = response.parseAs<Post>()
|
.get("post_ids")?.jsonArray
|
||||||
|
?.map { it.jsonPrimitive.content }
|
||||||
|
?.mapIndexed { i, id -> Page(index = i, url = "/posts/$id") }
|
||||||
|
?: emptyList()
|
||||||
|
|
||||||
listOf(
|
override fun pageListParse(document: Document): List<Page> =
|
||||||
Page(index = 0, imageUrl = data.fileUrl),
|
throw IllegalStateException("Not used")
|
||||||
)
|
|
||||||
} else {
|
|
||||||
val data = response.parseAs<Pool>()
|
|
||||||
|
|
||||||
data.postIds.mapIndexed { index, id ->
|
|
||||||
Page(index, url = "/posts/$id")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun imageUrlRequest(page: Page): Request =
|
override fun imageUrlRequest(page: Page): Request =
|
||||||
GET("$baseUrl${page.url}.json", headers)
|
GET("$baseUrl${page.url}.json?only=file_url", headers)
|
||||||
|
|
||||||
override fun imageUrlParse(response: Response): String =
|
override fun imageUrlParse(response: Response): String =
|
||||||
response.parseAs<Post>().fileUrl
|
json.decodeFromString<JsonObject>(response.body.string())
|
||||||
|
.get("file_url")!!.jsonPrimitive.content
|
||||||
|
|
||||||
|
override fun imageUrlParse(document: Document): String =
|
||||||
|
throw IllegalStateException("Not used")
|
||||||
|
|
||||||
override fun getChapterUrl(chapter: SChapter): String =
|
override fun getChapterUrl(chapter: SChapter): String =
|
||||||
baseUrl + chapter.url
|
baseUrl + chapter.url
|
||||||
@ -190,20 +181,6 @@ class Danbooru : HttpSource(), ConfigurableSource {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
override fun setupPreferenceScreen(screen: PreferenceScreen) {
|
private fun parseTimestamp(string: String): Long? =
|
||||||
SwitchPreferenceCompat(screen.context).apply {
|
runCatching { dateFormat.parse(string)?.time!! }.getOrNull()
|
||||||
key = CHAPTER_LIST_PREF
|
|
||||||
title = "Split posts into individual chapters"
|
|
||||||
summary = """
|
|
||||||
Instead of showing one 'OneShot' chapter,
|
|
||||||
each post will be it's own chapter
|
|
||||||
""".trimIndent()
|
|
||||||
setDefaultValue(false)
|
|
||||||
}.also(screen::addPreference)
|
|
||||||
}
|
|
||||||
|
|
||||||
private val SharedPreferences.splitChaptersPref: Boolean
|
|
||||||
get() = getBoolean(CHAPTER_LIST_PREF, false)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private const val CHAPTER_LIST_PREF = "prefChapterList"
|
|
||||||
|
@ -1,16 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.extension.all.danbooru
|
|
||||||
|
|
||||||
import kotlinx.serialization.SerialName
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
class Pool(
|
|
||||||
val id: Int,
|
|
||||||
@SerialName("updated_at") val updatedAt: String,
|
|
||||||
@SerialName("post_ids") val postIds: List<Int>,
|
|
||||||
)
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
class Post(
|
|
||||||
@SerialName("file_url") val fileUrl: String,
|
|
||||||
)
|
|
@ -1,5 +1,4 @@
|
|||||||
package eu.kanade.tachiyomi.extension.all.danbooru
|
package eu.kanade.tachiyomi.extension.all.danbooru
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.source.model.Filter
|
import eu.kanade.tachiyomi.source.model.Filter
|
||||||
|
|
||||||
internal class FilterTags : Filter.Text("Tags")
|
internal class FilterTags : Filter.Text("Tags")
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
ext {
|
ext {
|
||||||
extName = 'DeviantArt'
|
extName = 'DeviantArt'
|
||||||
extClass = '.DeviantArt'
|
extClass = '.DeviantArt'
|
||||||
extVersionCode = 7
|
extVersionCode = 6
|
||||||
isNsfw = true
|
isNsfw = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package eu.kanade.tachiyomi.extension.all.deviantart
|
package eu.kanade.tachiyomi.extension.all.deviantart
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import androidx.preference.ListPreference
|
import androidx.preference.ListPreference
|
||||||
import androidx.preference.PreferenceScreen
|
import androidx.preference.PreferenceScreen
|
||||||
@ -12,7 +13,6 @@ import eu.kanade.tachiyomi.source.model.SChapter
|
|||||||
import eu.kanade.tachiyomi.source.model.SManga
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||||
import eu.kanade.tachiyomi.util.asJsoup
|
import eu.kanade.tachiyomi.util.asJsoup
|
||||||
import keiyoushi.utils.getPreferencesLazy
|
|
||||||
import okhttp3.Headers
|
import okhttp3.Headers
|
||||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
@ -20,6 +20,8 @@ import okhttp3.Response
|
|||||||
import org.jsoup.Jsoup
|
import org.jsoup.Jsoup
|
||||||
import org.jsoup.nodes.Document
|
import org.jsoup.nodes.Document
|
||||||
import org.jsoup.parser.Parser
|
import org.jsoup.parser.Parser
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
import java.text.ParseException
|
import java.text.ParseException
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
@ -30,7 +32,9 @@ class DeviantArt : HttpSource(), ConfigurableSource {
|
|||||||
override val lang = "all"
|
override val lang = "all"
|
||||||
override val supportsLatest = false
|
override val supportsLatest = false
|
||||||
|
|
||||||
private val preferences: SharedPreferences by getPreferencesLazy()
|
private val preferences: SharedPreferences by lazy {
|
||||||
|
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
||||||
|
}
|
||||||
|
|
||||||
override fun headersBuilder() = Headers.Builder().apply {
|
override fun headersBuilder() = Headers.Builder().apply {
|
||||||
add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:133.0) Gecko/20100101 Firefox/133.0")
|
add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:133.0) Gecko/20100101 Firefox/133.0")
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
ext {
|
ext {
|
||||||
extName = 'E-Hentai'
|
extName = 'E-Hentai'
|
||||||
extClass = '.EHFactory'
|
extClass = '.EHFactory'
|
||||||
extVersionCode = 25
|
extVersionCode = 24
|
||||||
isNsfw = true
|
isNsfw = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package eu.kanade.tachiyomi.extension.all.ehentai
|
package eu.kanade.tachiyomi.extension.all.ehentai
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
|
import android.app.Application
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.webkit.CookieManager
|
import android.webkit.CookieManager
|
||||||
@ -22,7 +23,6 @@ import eu.kanade.tachiyomi.source.model.SManga
|
|||||||
import eu.kanade.tachiyomi.source.model.UpdateStrategy
|
import eu.kanade.tachiyomi.source.model.UpdateStrategy
|
||||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||||
import eu.kanade.tachiyomi.util.asJsoup
|
import eu.kanade.tachiyomi.util.asJsoup
|
||||||
import keiyoushi.utils.getPreferencesLazy
|
|
||||||
import okhttp3.CacheControl
|
import okhttp3.CacheControl
|
||||||
import okhttp3.CookieJar
|
import okhttp3.CookieJar
|
||||||
import okhttp3.Headers
|
import okhttp3.Headers
|
||||||
@ -30,6 +30,8 @@ import okhttp3.Request
|
|||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
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.net.URLEncoder
|
import java.net.URLEncoder
|
||||||
|
|
||||||
abstract class EHentai(
|
abstract class EHentai(
|
||||||
@ -39,7 +41,9 @@ abstract class EHentai(
|
|||||||
|
|
||||||
override val name = "E-Hentai"
|
override val name = "E-Hentai"
|
||||||
|
|
||||||
private val preferences: SharedPreferences by getPreferencesLazy()
|
private val preferences: SharedPreferences by lazy {
|
||||||
|
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
||||||
|
}
|
||||||
|
|
||||||
private val webViewCookieManager: CookieManager by lazy { CookieManager.getInstance() }
|
private val webViewCookieManager: CookieManager by lazy { CookieManager.getInstance() }
|
||||||
private val memberId: String by lazy { getMemberIdPref() }
|
private val memberId: String by lazy { getMemberIdPref() }
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
ext {
|
ext {
|
||||||
extName = 'Galaxy'
|
extName = 'Galaxy'
|
||||||
extClass = '.GalaxyFactory'
|
extClass = '.GalaxyFactory'
|
||||||
extVersionCode = 5
|
extVersionCode = 4
|
||||||
isNsfw = false
|
isNsfw = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
package eu.kanade.tachiyomi.extension.all.galaxy
|
package eu.kanade.tachiyomi.extension.all.galaxy
|
||||||
|
|
||||||
|
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.source.ConfigurableSource
|
import eu.kanade.tachiyomi.source.ConfigurableSource
|
||||||
import eu.kanade.tachiyomi.source.SourceFactory
|
import eu.kanade.tachiyomi.source.SourceFactory
|
||||||
import keiyoushi.utils.getPreferencesLazy
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
class GalaxyFactory : SourceFactory {
|
class GalaxyFactory : SourceFactory {
|
||||||
|
|
||||||
@ -20,7 +22,9 @@ class GalaxyFactory : SourceFactory {
|
|||||||
|
|
||||||
override val baseUrl by lazy { getPrefBaseUrl() }
|
override val baseUrl by lazy { getPrefBaseUrl() }
|
||||||
|
|
||||||
private val preferences: SharedPreferences by getPreferencesLazy()
|
private val preferences: SharedPreferences by lazy {
|
||||||
|
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val RESTART_APP = ".لتطبيق الإعدادات الجديدة أعد تشغيل التطبيق"
|
private const val RESTART_APP = ".لتطبيق الإعدادات الجديدة أعد تشغيل التطبيق"
|
||||||
|
@ -4,7 +4,6 @@ ext {
|
|||||||
themePkg = 'madara'
|
themePkg = 'madara'
|
||||||
baseUrl = 'https://grabber.zone'
|
baseUrl = 'https://grabber.zone'
|
||||||
overrideVersionCode = 0
|
overrideVersionCode = 0
|
||||||
isNsfw = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: "$rootDir/common.gradle"
|
apply from: "$rootDir/common.gradle"
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
ext {
|
ext {
|
||||||
extName = '3Hentai'
|
extName = '3Hentai'
|
||||||
extClass = '.Hentai3Factory'
|
extClass = '.Hentai3Factory'
|
||||||
extVersionCode = 1
|
extVersionCode = 1
|
||||||
isNsfw = true
|
isNsfw = true
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: "$rootDir/common.gradle"
|
apply from: "$rootDir/common.gradle"
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
package eu.kanade.tachiyomi.extension.all.hentai3
|
package eu.kanade.tachiyomi.extension.all.hentai3
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.network.GET
|
import eu.kanade.tachiyomi.network.GET
|
||||||
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
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
package eu.kanade.tachiyomi.extension.all.hentai3
|
package eu.kanade.tachiyomi.extension.all.hentai3
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
ext {
|
ext {
|
||||||
extName = 'HentaiEra'
|
extName = 'HentaiEra'
|
||||||
extClass = '.HentaiEraFactory'
|
extClass = '.HentaiEraFactory'
|
||||||
themePkg = 'galleryadults'
|
themePkg = 'galleryadults'
|
||||||
baseUrl = 'https://hentaiera.com'
|
baseUrl = 'https://hentaiera.com'
|
||||||
overrideVersionCode = 1
|
overrideVersionCode = 1
|
||||||
isNsfw = true
|
isNsfw = true
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: "$rootDir/common.gradle"
|
apply from: "$rootDir/common.gradle"
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user