feat(all/googledriveindex): Add new features, fix stuff, code refactor (#1723)

This commit is contained in:
Secozzi
2023-06-13 21:53:04 +02:00
committed by GitHub
parent bab58afbc0
commit 9beb40f9ef
3 changed files with 270 additions and 163 deletions

View File

@ -6,7 +6,7 @@ ext {
extName = 'GoogleDriveIndex' extName = 'GoogleDriveIndex'
pkgNameSuffix = 'all.googledriveindex' pkgNameSuffix = 'all.googledriveindex'
extClass = '.GoogleDriveIndex' extClass = '.GoogleDriveIndex'
extVersionCode = 5 extVersionCode = 6
libVersion = '13' libVersion = '13'
} }

View File

@ -2,7 +2,11 @@ package eu.kanade.tachiyomi.animeextension.all.googledriveindex
import android.app.Application import android.app.Application
import android.content.SharedPreferences import android.content.SharedPreferences
import android.text.Editable
import android.text.TextWatcher
import android.util.Base64 import android.util.Base64
import android.widget.Button
import android.widget.EditText
import android.widget.Toast import android.widget.Toast
import androidx.preference.EditTextPreference import androidx.preference.EditTextPreference
import androidx.preference.PreferenceScreen import androidx.preference.PreferenceScreen
@ -40,7 +44,7 @@ class GoogleDriveIndex : ConfigurableAnimeSource, AnimeHttpSource() {
override val name = "GoogleDriveIndex" override val name = "GoogleDriveIndex"
override val baseUrl by lazy { override val baseUrl by lazy {
preferences.getString("domain_list", "")!!.split(",").first() preferences.domainList.split(",").first().removeName()
} }
override val lang = "all" override val lang = "all"
@ -83,12 +87,9 @@ class GoogleDriveIndex : ConfigurableAnimeSource, AnimeHttpSource() {
// ============================== Popular =============================== // ============================== Popular ===============================
override fun popularAnimeRequest(page: Int): Request { override fun popularAnimeRequest(page: Int): Request {
if (baseUrl.isEmpty()) { require(baseUrl.isNotEmpty()) { "Enter drive path(s) in extension settings." }
throw Exception("Enter drive path(s) in extension settings.") require(baseUrl.toHttpUrl().host != "drive.google.com") {
} "This extension is only for Google Drive Index sites, not drive.google.com folders."
if (baseUrl.toHttpUrl().host == "drive.google.com") {
throw Exception("This extension is only for Google Drive Index sites, not drive.google.com folders.")
} }
if (page == 1) pageToken = "" if (page == 1) pageToken = ""
@ -97,7 +98,7 @@ class GoogleDriveIndex : ConfigurableAnimeSource, AnimeHttpSource() {
.add("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8") .add("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8")
.add("Host", baseUrl.toHttpUrl().host) .add("Host", baseUrl.toHttpUrl().host)
.add("Origin", "https://${baseUrl.toHttpUrl().host}") .add("Origin", "https://${baseUrl.toHttpUrl().host}")
.add("Referer", URLEncoder.encode(baseUrl, "UTF-8")) .add("Referer", baseUrl.asReferer())
.add("X-Requested-With", "XMLHttpRequest") .add("X-Requested-With", "XMLHttpRequest")
.build() .build()
@ -106,9 +107,7 @@ class GoogleDriveIndex : ConfigurableAnimeSource, AnimeHttpSource() {
return POST(baseUrl, body = popBody, headers = popHeaders) return POST(baseUrl, body = popBody, headers = popHeaders)
} }
override fun popularAnimeParse(response: Response): AnimesPage { override fun popularAnimeParse(response: Response): AnimesPage = parsePage(response, baseUrl)
return parsePage(response, baseUrl)
}
// =============================== Latest =============================== // =============================== Latest ===============================
@ -125,34 +124,51 @@ class GoogleDriveIndex : ConfigurableAnimeSource, AnimeHttpSource() {
query: String, query: String,
filters: AnimeFilterList, filters: AnimeFilterList,
): Observable<AnimesPage> { ): Observable<AnimesPage> {
val req = searchAnimeRequest(page, query, filters) val filterList = if (filters.isEmpty()) getFilterList() else filters
return Observable.defer { val urlFilter = filterList.find { it is URLFilter } as URLFilter
try {
client.newCall(req).asObservableSuccess() return if (urlFilter.state.isEmpty()) {
} catch (e: NoClassDefFoundError) { val req = searchAnimeRequest(page, query, filters)
// RxJava doesn't handle Errors, which tends to happen during global searches Observable.defer {
// if an old extension using non-existent classes is still around try {
throw RuntimeException(e) client.newCall(req).asObservableSuccess()
} } catch (e: NoClassDefFoundError) {
} // RxJava doesn't handle Errors, which tends to happen during global searches
.map { response -> // if an old extension using non-existent classes is still around
throw RuntimeException(e)
}
}.map { response ->
searchAnimeParse(response, req.url.toString()) searchAnimeParse(response, req.url.toString())
} }
} else {
Observable.just(addSinglePage(urlFilter.state))
}
}
private fun addSinglePage(inputUrl: String): AnimesPage {
val match = URL_REGEX.matchEntire(inputUrl) ?: throw Exception("Invalid url")
val anime = SAnime.create().apply {
title = match.groups["name"]?.value?.substringAfter("[")?.substringBeforeLast("]") ?: "Folder"
url = LinkData(
type = "multi",
url = match.groups["url"]!!.value,
fragment = inputUrl.removeName().toHttpUrl().encodedFragment,
).toJsonString()
thumbnail_url = ""
}
return AnimesPage(listOf(anime), false)
} }
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request { override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
if (baseUrl.isEmpty()) { require(baseUrl.isNotEmpty()) { "Enter drive path(s) in extension settings." }
throw Exception("Enter drive path(s) in extension settings.") require(baseUrl.toHttpUrl().host != "drive.google.com") {
"This extension is only for Google Drive Index sites, not drive.google.com folders."
} }
val filterList = if (filters.isEmpty()) getFilterList() else filters val filterList = if (filters.isEmpty()) getFilterList() else filters
val serverFilter = filterList.find { it is ServerFilter } as ServerFilter val serverFilter = filterList.find { it is ServerFilter } as ServerFilter
val serverUrl = serverFilter.toUriPart() val serverUrl = serverFilter.toUriPart()
if (serverUrl.toHttpUrl().host == "drive.google.com") {
throw Exception("This extension is only for Google Drive Index sites, not drive.google.com folders.")
}
if (page == 1) pageToken = "" if (page == 1) pageToken = ""
val searchHeaders = headers.newBuilder() val searchHeaders = headers.newBuilder()
.add("Accept", "*/*") .add("Accept", "*/*")
@ -161,37 +177,39 @@ class GoogleDriveIndex : ConfigurableAnimeSource, AnimeHttpSource() {
.add("Origin", "https://${serverUrl.toHttpUrl().host}") .add("Origin", "https://${serverUrl.toHttpUrl().host}")
.add("X-Requested-With", "XMLHttpRequest") .add("X-Requested-With", "XMLHttpRequest")
return if (query.isBlank()) { return when {
val popBody = "password=&page_token=$pageToken&page_index=${page - 1}".toRequestBody("application/x-www-form-urlencoded".toMediaType()) query.isBlank() -> {
POST( val popBody = "password=&page_token=$pageToken&page_index=${page - 1}".toRequestBody("application/x-www-form-urlencoded".toMediaType())
serverUrl,
body = popBody,
headers = searchHeaders.add("Referer", URLEncoder.encode(serverUrl, "UTF-8")).build(),
)
} else {
val cleanQuery = query.replace(" ", "+")
val searchUrl = "https://${serverUrl.toHttpUrl().hostAndCred()}/${serverUrl.toHttpUrl().pathSegments[0]}search" POST(
serverUrl,
body = popBody,
headers = searchHeaders.add("Referer", serverUrl.asReferer()).build(),
)
}
else -> {
val cleanQuery = query.replace(" ", "+")
val searchUrl = "https://${serverUrl.toHttpUrl().hostAndCred()}/${serverUrl.toHttpUrl().pathSegments[0]}search"
val popBody = "q=$cleanQuery&page_token=$pageToken&page_index=${page - 1}".toRequestBody("application/x-www-form-urlencoded".toMediaType())
val popBody = "q=$cleanQuery&page_token=$pageToken&page_index=${page - 1}".toRequestBody("application/x-www-form-urlencoded".toMediaType()) POST(
searchUrl,
POST( body = popBody,
searchUrl, headers = searchHeaders.add("Referer", "$searchUrl?q=$cleanQuery").build(),
body = popBody, )
headers = searchHeaders.add("Referer", "$searchUrl?q=$cleanQuery").build(), }
)
} }
} }
private fun searchAnimeParse(response: Response, url: String): AnimesPage { private fun searchAnimeParse(response: Response, url: String): AnimesPage = parsePage(response, url)
return parsePage(response, url)
}
// ============================== FILTERS =============================== // ============================== FILTERS ===============================
override fun getFilterList(): AnimeFilterList = AnimeFilterList( override fun getFilterList(): AnimeFilterList = AnimeFilterList(
AnimeFilter.Header("Text search will only search inside selected server"), AnimeFilter.Header("Text search will only search inside selected server"),
ServerFilter(getDomains()), ServerFilter(getDomains()),
AnimeFilter.Header("Add single folder"),
URLFilter(),
) )
private class ServerFilter(domains: Array<Pair<String, String>>) : UriPartFilter( private class ServerFilter(domains: Array<Pair<String, String>>) : UriPartFilter(
@ -200,8 +218,13 @@ class GoogleDriveIndex : ConfigurableAnimeSource, AnimeHttpSource() {
) )
private fun getDomains(): Array<Pair<String, String>> { private fun getDomains(): Array<Pair<String, String>> {
return preferences.getString("domain_list", "")!!.split(",").map { if (preferences.domainList.isBlank()) return emptyArray()
Pair(it.substringAfter("https://"), it) return preferences.domainList.split(",").map {
val match = URL_REGEX.matchEntire(it)!!
val name = match.groups["name"]?.let {
it.value.substringAfter("[").substringBeforeLast("]")
}
Pair(name ?: it.toHttpUrl().encodedPath, it.removeName())
}.toTypedArray() }.toTypedArray()
} }
@ -210,6 +233,8 @@ class GoogleDriveIndex : ConfigurableAnimeSource, AnimeHttpSource() {
fun toUriPart() = vals[state].second fun toUriPart() = vals[state].second
} }
private class URLFilter : AnimeFilter.Text("Url")
// =========================== Anime Details ============================ // =========================== Anime Details ============================
override fun fetchAnimeDetails(anime: SAnime): Observable<SAnime> { override fun fetchAnimeDetails(anime: SAnime): Observable<SAnime> {
@ -296,6 +321,12 @@ class GoogleDriveIndex : ConfigurableAnimeSource, AnimeHttpSource() {
val episodeList = mutableListOf<SEpisode>() val episodeList = mutableListOf<SEpisode>()
val parsed = json.decodeFromString<LinkData>(anime.url) val parsed = json.decodeFromString<LinkData>(anime.url)
var counter = 1 var counter = 1
val maxRecursionDepth = parsed.fragment?.substringBefore(",")?.toInt() ?: 2
val (start, stop) = if (parsed.fragment?.contains(",") == true) {
parsed.fragment.substringAfter(",").split(",").map { it.toInt() }
} else {
listOf(null, null)
}
val newParsed = if (parsed.type != "search") { val newParsed = if (parsed.type != "search") {
parsed parsed
@ -323,22 +354,23 @@ class GoogleDriveIndex : ConfigurableAnimeSource, AnimeHttpSource() {
} }
if (newParsed.type == "single") { if (newParsed.type == "single") {
val episode = SEpisode.create() val titleName = newParsed.url.toHttpUrl().pathSegments.last()
val size = if (newParsed.info == null) { episodeList.add(
"" SEpisode.create().apply {
} else { name = if (preferences.trimEpisodeName) titleName.trimInfo() else titleName
" - ${newParsed.info}" url = newParsed.url
} episode_number = 1F
episode.name = "${newParsed.url.toHttpUrl().pathSegments.last()}$size" date_upload = -1L
episode.url = newParsed.url scanlator = newParsed.info
episode.episode_number = 1F },
episodeList.add(episode) )
} }
if (newParsed.type == "multi") { if (newParsed.type == "multi") {
val basePathCounter = newParsed.url.toHttpUrl().pathSize val basePathCounter = newParsed.url.toHttpUrl().pathSize
fun traverseDirectory(url: String) { fun traverseDirectory(url: String, recursionDepth: Int = 0) {
if (recursionDepth == maxRecursionDepth) return
var newToken: String? = "" var newToken: String? = ""
var newPageIndex = 0 var newPageIndex = 0
@ -361,18 +393,16 @@ class GoogleDriveIndex : ConfigurableAnimeSource, AnimeHttpSource() {
parsed.data.files.forEach { item -> parsed.data.files.forEach { item ->
if (item.mimeType.endsWith("folder")) { if (item.mimeType.endsWith("folder")) {
if (
preferences.getString("blacklist_folders", "")!!.split("/")
.any { it.equals(item.name, ignoreCase = true) }
) {
return@forEach
}
val newUrl = joinUrl(url, item.name).addSuffix("/") val newUrl = joinUrl(url, item.name).addSuffix("/")
traverseDirectory(newUrl) traverseDirectory(newUrl, recursionDepth + 1)
} }
if (item.mimeType.startsWith("video/")) { if (item.mimeType.startsWith("video/")) {
val episode = SEpisode.create() if (start != null && maxRecursionDepth == 1 && counter < start) {
counter++
return@forEach
}
if (stop != null && maxRecursionDepth == 1 && counter > stop) return
val epUrl = joinUrl(url, item.name) val epUrl = joinUrl(url, item.name)
val paths = epUrl.toHttpUrl().pathSegments val paths = epUrl.toHttpUrl().pathSegments
@ -393,17 +423,20 @@ class GoogleDriveIndex : ConfigurableAnimeSource, AnimeHttpSource() {
val extraInfo = if (paths.size > basePathCounter) { val extraInfo = if (paths.size > basePathCounter) {
"/" + paths.subList(basePathCounter - 1, paths.size - 1).joinToString("/") { it.trimInfo() } "/" + paths.subList(basePathCounter - 1, paths.size - 1).joinToString("/") { it.trimInfo() }
} else { } else {
"" "/"
} }
val size = item.size?.toLongOrNull()?.let { formatFileSize(it) } val size = item.size?.toLongOrNull()?.let { formatFileSize(it) }
episode.name = "${item.name.trimInfo()}${if (size == null) "" else " - $size"}" episodeList.add(
episode.url = epUrl SEpisode.create().apply {
episode.scanlator = seasonInfo + extraInfo name = if (preferences.trimEpisodeName) item.name.trimInfo() else item.name
episode.episode_number = counter.toFloat() this.url = epUrl
scanlator = "${if (size == null) "" else "$size"}$seasonInfo$extraInfo"
date_upload = -1L
episode_number = counter.toFloat()
},
)
counter++ counter++
episodeList.add(episode)
} }
} }
@ -485,7 +518,7 @@ class GoogleDriveIndex : ConfigurableAnimeSource, AnimeHttpSource() {
} }
private fun String.trimInfo(): String { private fun String.trimInfo(): String {
var newString = this.replaceFirst("""^\[\w+\] ?""".toRegex(), "") var newString = this.replaceFirst("""^\[[\w-]+\] ?""".toRegex(), "")
val regex = """( ?\[[\s\w-]+\]| ?\([\s\w-]+\))(\.mkv|\.mp4|\.avi)?${'$'}""".toRegex() val regex = """( ?\[[\s\w-]+\]| ?\([\s\w-]+\))(\.mkv|\.mp4|\.avi)?${'$'}""".toRegex()
while (regex.containsMatchIn(newString)) { while (regex.containsMatchIn(newString)) {
@ -508,6 +541,17 @@ class GoogleDriveIndex : ConfigurableAnimeSource, AnimeHttpSource() {
} }
} }
private fun String.asReferer(): String {
return URLEncoder.encode(
this.toHttpUrl().let {
"https://${it.host}${it.encodedPath}"
},
"UTF-8",
)
}
private fun String.removeName(): String = Regex("""^(\[[^\[\];]+\])""").replace(this, "")
private fun LinkData.toJsonString(): String { private fun LinkData.toJsonString(): String {
return json.encodeToString(this) return json.encodeToString(this)
} }
@ -523,63 +567,59 @@ class GoogleDriveIndex : ConfigurableAnimeSource, AnimeHttpSource() {
parsed.data.files.forEach { item -> parsed.data.files.forEach { item ->
if (item.mimeType.endsWith("folder")) { if (item.mimeType.endsWith("folder")) {
val anime = SAnime.create() animeList.add(
anime.title = item.name.trimInfo() SAnime.create().apply {
anime.thumbnail_url = "" title = if (preferences.trimAnimeName) item.name.trimInfo() else item.name
thumbnail_url = ""
if (isSearch) { this.url = if (isSearch) {
anime.setUrlWithoutDomain( LinkData(
LinkData( "search",
"search", IdUrl(
IdUrl( item.id,
item.id, url.substringBeforeLast("search"),
url.substringBeforeLast("search"), response.request.header("Referer")!!,
response.request.header("Referer")!!, "multi",
).toJsonString(),
).toJsonString()
} else {
LinkData(
"multi", "multi",
).toJsonString(), joinUrl(URL_REGEX.matchEntire(url)!!.groups["url"]!!.value, item.name).addSuffix("/"),
).toJsonString(), fragment = url.toHttpUrl().encodedFragment,
) ).toJsonString()
} else { }
anime.setUrlWithoutDomain( },
LinkData( )
"multi",
joinUrl(url, item.name).addSuffix("/"),
).toJsonString(),
)
}
animeList.add(anime)
} }
if ( if (
item.mimeType.startsWith("video/") && item.mimeType.startsWith("video/") &&
!(preferences.getBoolean("ignore_non_folder", true) && isSearch) !(preferences.ignoreFolder && isSearch)
) { ) {
val anime = SAnime.create() animeList.add(
anime.title = item.name.trimInfo() SAnime.create().apply {
anime.thumbnail_url = "" title = if (preferences.trimAnimeName) item.name.trimInfo() else item.name
thumbnail_url = ""
if (isSearch) { this.url = if (isSearch) {
anime.setUrlWithoutDomain( LinkData(
LinkData( "search",
"search", IdUrl(
IdUrl( item.id,
item.id, url.substringBeforeLast("search"),
url.substringBeforeLast("search"), response.request.header("Referer")!!,
response.request.header("Referer")!!, "single",
).toJsonString(),
item.size?.toLongOrNull()?.let { formatFileSize(it) },
).toJsonString()
} else {
LinkData(
"single", "single",
).toJsonString(), joinUrl(URL_REGEX.matchEntire(url)!!.groups["url"]!!.value, item.name),
item.size?.toLongOrNull()?.let { formatFileSize(it) }, item.size?.toLongOrNull()?.let { formatFileSize(it) },
).toJsonString(), fragment = url.toHttpUrl().encodedFragment,
) ).toJsonString()
} else { }
anime.setUrlWithoutDomain( },
LinkData( )
"single",
joinUrl(url, item.name),
item.size?.toLongOrNull()?.let { formatFileSize(it) },
).toJsonString(),
)
}
animeList.add(anime)
} }
} }
@ -588,22 +628,95 @@ class GoogleDriveIndex : ConfigurableAnimeSource, AnimeHttpSource() {
return AnimesPage(animeList, parsed.nextPageToken != null) return AnimesPage(animeList, parsed.nextPageToken != null)
} }
private fun isUrl(text: String) = URL_REGEX matches text
/*
* Stolen from the MangaDex manga extension
*
* This will likely need to be removed or revisited when the app migrates the
* extension preferences screen to Compose.
*/
private fun setupEditTextUrlValidator(editText: EditText) {
editText.addTextChangedListener(
object : TextWatcher {
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
// Do nothing.
}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
// Do nothing.
}
override fun afterTextChanged(editable: Editable?) {
requireNotNull(editable)
val text = editable.toString()
val isValid = text.isBlank() || text
.split(",")
.map(String::trim)
.all(::isUrl)
editText.error = if (!isValid) "${text.split(",").first { !isUrl(it) }} is not a valid url" else null
editText.rootView.findViewById<Button>(android.R.id.button1)
?.isEnabled = editText.error == null
}
},
)
}
companion object {
private const val DOMAIN_PREF_KEY = "domain_list"
private const val DOMAIN_PREF_DEFAULT = ""
private const val SEARCH_FOLDER_IGNORE_KEY = "ignore_non_folder"
private const val SEARCH_FOLDER_IGNORE_DEFAULT = true
private const val TRIM_EPISODE_NAME_KEY = "trim_episode_name"
private const val TRIM_EPISODE_NAME_DEFAULT = true
private const val TRIM_ANIME_NAME_KEY = "trim_anime_name"
private const val TRIM_ANIME_NAME_DEFAULT = true
private val URL_REGEX = Regex("""(?<name>\[[^\[\];]+\])?(?<url>https(?:[^,#]+))(?<depth>#\d+(?<range>,\d+,\d+)?)?${'$'}""")
}
private val SharedPreferences.domainList
get() = getString(DOMAIN_PREF_KEY, DOMAIN_PREF_DEFAULT)!!
private val SharedPreferences.ignoreFolder
get() = getBoolean(SEARCH_FOLDER_IGNORE_KEY, SEARCH_FOLDER_IGNORE_DEFAULT)
private val SharedPreferences.trimEpisodeName
get() = getBoolean(TRIM_EPISODE_NAME_KEY, TRIM_EPISODE_NAME_DEFAULT)
private val SharedPreferences.trimAnimeName
get() = getBoolean(TRIM_ANIME_NAME_KEY, TRIM_ANIME_NAME_DEFAULT)
// ============================== Settings ==============================
override fun setupPreferenceScreen(screen: PreferenceScreen) { override fun setupPreferenceScreen(screen: PreferenceScreen) {
val domainListPref = EditTextPreference(screen.context).apply { EditTextPreference(screen.context).apply {
key = "domain_list" key = DOMAIN_PREF_KEY
title = "Enter drive paths to be shown in extension" title = "Enter drive paths to be shown in extension"
summary = """Enter drive paths to be shown in extension summary = """Enter drive paths to be shown in extension
|Enter as comma separated list |Enter as comma separated list
""".trimMargin() """.trimMargin()
this.setDefaultValue("") this.setDefaultValue(DOMAIN_PREF_DEFAULT)
dialogTitle = "Path list" dialogTitle = "Path list"
dialogMessage = """Separate paths with a comma. For password protected sites, dialogMessage = """Separate paths with a comma. For password protected sites,
|format as: "https://username:password@example.worker.dev/0:/" |format as: "https://username:password@example.worker.dev/0:/"
|- (optional) Add [] before url to customize name. For example: [drive 5]https://site.workers.dev/0:
|- (optional) add #<integer> to limit the depth of recursion when loading episodes, defaults is 2. For example: https://site.workers.dev/0:#5
|- (optional) add #depth,start,stop (all integers) to specify range when loading episodes. Only works if depth is 1. For example: https://site.workers.dev/0:#1,2,6
""".trimMargin() """.trimMargin()
setOnBindEditTextListener(::setupEditTextUrlValidator)
setOnPreferenceChangeListener { _, newValue -> setOnPreferenceChangeListener { _, newValue ->
try { try {
val res = preferences.edit().putString("domain_list", newValue as String).commit() val res = preferences.edit().putString(DOMAIN_PREF_KEY, newValue as String).commit()
Toast.makeText(screen.context, "Restart Aniyomi to apply changes", Toast.LENGTH_LONG).show() Toast.makeText(screen.context, "Restart Aniyomi to apply changes", Toast.LENGTH_LONG).show()
res res
} catch (e: java.lang.Exception) { } catch (e: java.lang.Exception) {
@ -611,40 +724,33 @@ class GoogleDriveIndex : ConfigurableAnimeSource, AnimeHttpSource() {
false false
} }
} }
} }.also(screen::addPreference)
val blacklistFolders = EditTextPreference(screen.context).apply {
key = "blacklist_folders"
title = "Blacklist folder names"
summary = """Enter names of folders to skip over
|Enter as slash / separated list
""".trimMargin()
this.setDefaultValue("NC/Extras")
dialogTitle = "Blacklisted folders"
dialogMessage = "Separate folders with a slash (case insensitive)"
setOnPreferenceChangeListener { _, newValue -> SwitchPreferenceCompat(screen.context).apply {
try { key = SEARCH_FOLDER_IGNORE_KEY
val res = preferences.edit().putString("blacklist_folders", newValue as String).commit()
Toast.makeText(screen.context, "Restart Aniyomi to apply changes", Toast.LENGTH_LONG).show()
res
} catch (e: java.lang.Exception) {
e.printStackTrace()
false
}
}
}
val ignoreNonFolder = SwitchPreferenceCompat(screen.context).apply {
key = "ignore_non_folder"
title = "Only include folders on search" title = "Only include folders on search"
setDefaultValue(true) setDefaultValue(SEARCH_FOLDER_IGNORE_DEFAULT)
setOnPreferenceChangeListener { _, newValue -> setOnPreferenceChangeListener { _, newValue ->
preferences.edit().putBoolean(key, newValue as Boolean).commit() preferences.edit().putBoolean(key, newValue as Boolean).commit()
} }
} }.also(screen::addPreference)
screen.addPreference(domainListPref) SwitchPreferenceCompat(screen.context).apply {
screen.addPreference(blacklistFolders) key = TRIM_EPISODE_NAME_KEY
screen.addPreference(ignoreNonFolder) title = "Trim info from episode name"
setDefaultValue(TRIM_EPISODE_NAME_DEFAULT)
setOnPreferenceChangeListener { _, newValue ->
preferences.edit().putBoolean(key, newValue as Boolean).commit()
}
}.also(screen::addPreference)
SwitchPreferenceCompat(screen.context).apply {
key = TRIM_ANIME_NAME_KEY
title = "Trim info from anime name"
setDefaultValue(TRIM_ANIME_NAME_DEFAULT)
setOnPreferenceChangeListener { _, newValue ->
preferences.edit().putBoolean(key, newValue as Boolean).commit()
}
}.also(screen::addPreference)
} }
} }

View File

@ -27,6 +27,7 @@ data class LinkData(
val type: String, val type: String,
val url: String, val url: String,
val info: String? = null, val info: String? = null,
val fragment: String? = null,
) )
@Serializable @Serializable