Compare commits

..

No commits in common. "07eba2f8f2890c1aa39196ce16d4fb771c5745c6" and "43bd7342e7b6da152c3ea3e0ccb72fcceed0c358" have entirely different histories.

1500 changed files with 8044 additions and 9478 deletions

View File

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

View File

@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.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

Binary file not shown.

View File

@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.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
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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\\":\\"([^"']+)\\"""")

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +0,0 @@
plugins {
id("lib-multisrc")
}
baseVersionCode = 1

View File

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

View File

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

View File

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

View File

@ -1,7 +1,7 @@
ext { ext {
extName = 'Bato.to' extName = 'Bato.to'
extClass = '.BatoToFactory' extClass = '.BatoToFactory'
extVersionCode = 49 extVersionCode = 48
isNsfw = true isNsfw = true
} }

View File

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

View File

@ -1,7 +1,7 @@
ext { ext {
extName = 'Comic Fury' extName = 'Comic Fury'
extClass = '.ComicFuryFactory' extClass = '.ComicFuryFactory'
extVersionCode = 5 extVersionCode = 3
isNsfw = true isNsfw = true
} }

View File

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

View File

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

View File

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

View File

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

View File

@ -1,7 +1,7 @@
ext { ext {
extName = 'Comick' extName = 'Comick'
extClass = '.ComickFactory' extClass = '.ComickFactory'
extVersionCode = 55 extVersionCode = 52
isNsfw = true isNsfw = true
} }

View File

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

View File

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

View File

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

View File

@ -1,7 +1,7 @@
ext { ext {
extName = 'Comico' extName = 'Comico'
extClass = '.ComicoFactory' extClass = '.ComicoFactory'
extVersionCode = 7 extVersionCode = 6
isNsfw = true isNsfw = true
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,7 +1,7 @@
ext { ext {
extName = 'E-Hentai' extName = 'E-Hentai'
extClass = '.EHFactory' extClass = '.EHFactory'
extVersionCode = 25 extVersionCode = 24
isNsfw = true isNsfw = true
} }

View File

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

View File

@ -1,7 +1,7 @@
ext { ext {
extName = 'Galaxy' extName = 'Galaxy'
extClass = '.GalaxyFactory' extClass = '.GalaxyFactory'
extVersionCode = 5 extVersionCode = 4
isNsfw = false isNsfw = false
} }

View File

@ -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 = ".لتطبيق الإعدادات الجديدة أعد تشغيل التطبيق"

View File

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

View File

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

View File

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

View File

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

View File

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