Tachidesk (Suwayomi): Implement search (#15466)
* Tachidesk (Suwayomi): Implement search * Update extVersionCode * Tachidesk: Move search logic * Move retrieval and filtering from searchMangaRequest to searchMangaParse * Remove fetchSearchManga override * Tachidesk: Implement PR suggestions * Import and use proper json encode method for search results * Removed redundant gzip handling * Moved query from header to fragment * Switched to extension-lib GET instead of Request.Builder * Improved and reduced null/empty checks * Tachidesk: Toggle global search * Adds an option to search only the current category * Default behaviour is to search whole catalog * Switched from URL fragment to query params for search info embed * Minor cleanup * Tachidesk: Clean up * Removed redundant code path in `searchMangaRequest` * Moved search/filter stuff to the "Filters & Search" section
This commit is contained in:
parent
e5bcf9190f
commit
0c01024f76
|
@ -6,7 +6,7 @@ ext {
|
|||
extName = 'Suwayomi'
|
||||
pkgNameSuffix = 'all.tachidesk'
|
||||
extClass = '.Tachidesk'
|
||||
extVersionCode = 8
|
||||
extVersionCode = 9
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
|
|
@ -19,13 +19,17 @@ import eu.kanade.tachiyomi.source.model.SChapter
|
|||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||
import kotlinx.serialization.decodeFromString
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import okhttp3.Credentials
|
||||
import okhttp3.Dns
|
||||
import okhttp3.Headers
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import okhttp3.MediaType.Companion.toMediaType
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import okhttp3.ResponseBody.Companion.toResponseBody
|
||||
import rx.Observable
|
||||
import rx.Single
|
||||
import rx.android.schedulers.AndroidSchedulers
|
||||
|
@ -76,7 +80,7 @@ class Tachidesk : ConfigurableSource, UnmeteredSource, HttpSource() {
|
|||
GET("$checkedBaseUrl/api/v1/manga/${manga.url}/?onlineFetch=true", headers)
|
||||
|
||||
override fun mangaDetailsParse(response: Response): SManga =
|
||||
json.decodeFromString<MangaDataClass>(response.body.string()).let { it.toSManga() }
|
||||
json.decodeFromString<MangaDataClass>(response.body.string()).toSManga()
|
||||
|
||||
// ------------- Chapter -------------
|
||||
|
||||
|
@ -118,14 +122,24 @@ class Tachidesk : ConfigurableSource, UnmeteredSource, HttpSource() {
|
|||
|
||||
// ------------- Filters & Search -------------
|
||||
|
||||
private var categoryList: List<CategoryDataClass> = emptyList()
|
||||
|
||||
private val defaultCategoryId: Int
|
||||
get() = categoryList.firstOrNull()?.id ?: 0
|
||||
|
||||
class CategorySelect(categoryList: List<CategoryDataClass>) :
|
||||
Filter.Select<String>("Category", categoryList.map { it.name }.toTypedArray())
|
||||
|
||||
class DisableGlobalSearch() :
|
||||
Filter.CheckBox("Search only current category", false)
|
||||
|
||||
override fun getFilterList(): FilterList =
|
||||
FilterList(
|
||||
CategorySelect(refreshCategoryList(baseUrl).let { categoryList }),
|
||||
Filter.Header("Press reset to attempt to fetch categories"),
|
||||
DisableGlobalSearch(),
|
||||
)
|
||||
|
||||
private var categoryList: List<CategoryDataClass> = emptyList()
|
||||
|
||||
private fun refreshCategoryList(baseUrl: String) {
|
||||
Single.fromCallable {
|
||||
client.newCall(GET("$baseUrl/api/v1/category", headers)).execute()
|
||||
|
@ -144,6 +158,97 @@ class Tachidesk : ConfigurableSource, UnmeteredSource, HttpSource() {
|
|||
)
|
||||
}
|
||||
|
||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||
// Embed search query and scope into URL params for processing in searchMangaParse
|
||||
var currentCategoryId = defaultCategoryId
|
||||
var disableGlobalSearch = false
|
||||
filters.forEach { filter ->
|
||||
when (filter) {
|
||||
is CategorySelect -> currentCategoryId = categoryList[filter.state].id
|
||||
is DisableGlobalSearch -> disableGlobalSearch = filter.state
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
val url = "$checkedBaseUrl/api/v1/$currentCategoryId"
|
||||
.toHttpUrl()
|
||||
.newBuilder()
|
||||
.addQueryParameter("searchQuery", query)
|
||||
.addQueryParameter("currentCategoryId", currentCategoryId.toString())
|
||||
.addQueryParameter("disableGlobalSearch", disableGlobalSearch.toString())
|
||||
.addQueryParameter("page", page.toString())
|
||||
.build()
|
||||
return GET(url, headers)
|
||||
}
|
||||
|
||||
override fun searchMangaParse(response: Response): MangasPage {
|
||||
val request = response.request
|
||||
val newResponse: Response
|
||||
var searchQuery: String? = ""
|
||||
var currentCategoryId: Int? = defaultCategoryId
|
||||
var disableGlobalSearch = false
|
||||
// Check if URL has query params and parse them
|
||||
if (!request.url.query.isNullOrEmpty()) {
|
||||
searchQuery = request.url.queryParameter("searchQuery")
|
||||
currentCategoryId = request.url.queryParameter("currentCategoryId")?.toIntOrNull()
|
||||
disableGlobalSearch = request.url.queryParameter("disableGlobalSearch").toBoolean()
|
||||
}
|
||||
newResponse = if (!searchQuery.isNullOrEmpty()) {
|
||||
// Get URLs of categories to search
|
||||
val categoryUrlList = if (!disableGlobalSearch) {
|
||||
categoryList.map { category ->
|
||||
val categoryId = category.id
|
||||
"$checkedBaseUrl/api/v1/category/$categoryId"
|
||||
}
|
||||
} else {
|
||||
listOfNotNull("$checkedBaseUrl/api/v1/category/$currentCategoryId")
|
||||
}
|
||||
|
||||
// Construct a list of all manga in the required categories by querying each one
|
||||
val mangaList = mutableListOf<MangaDataClass>()
|
||||
categoryUrlList.forEach { categoryUrl ->
|
||||
val categoryMangaListRequest =
|
||||
GET(categoryUrl, headers)
|
||||
val categoryMangaListResponse =
|
||||
client.newCall(categoryMangaListRequest).execute()
|
||||
val categoryMangaListJson =
|
||||
categoryMangaListResponse.body.string()
|
||||
val categoryMangaList =
|
||||
json.decodeFromString<List<MangaDataClass>>(categoryMangaListJson)
|
||||
mangaList.addAll(categoryMangaList)
|
||||
}
|
||||
|
||||
// Filter according to search terms.
|
||||
// Basic substring search, room for improvement.
|
||||
val searchResults = mangaList.filter { mangaData ->
|
||||
val fieldsToCheck = listOfNotNull(
|
||||
mangaData.title,
|
||||
mangaData.url,
|
||||
mangaData.artist,
|
||||
mangaData.author,
|
||||
mangaData.description,
|
||||
)
|
||||
fieldsToCheck.any { field ->
|
||||
field.contains(searchQuery, ignoreCase = true)
|
||||
}
|
||||
}.distinct()
|
||||
|
||||
// Construct new response with search results
|
||||
val jsonString = json.encodeToString(searchResults)
|
||||
val mediaType = "application/json".toMediaType()
|
||||
val responseBody = jsonString.toResponseBody(mediaType)
|
||||
Response.Builder()
|
||||
.request(request)
|
||||
.protocol(response.protocol)
|
||||
.code(200)
|
||||
.body(responseBody)
|
||||
.message("OK")
|
||||
.build()
|
||||
} else {
|
||||
response
|
||||
}
|
||||
return popularMangaParse(newResponse)
|
||||
}
|
||||
|
||||
// ------------- Images -------------
|
||||
override fun imageRequest(page: Page) = GET(page.imageUrl!!, headers)
|
||||
|
||||
|
@ -169,34 +274,6 @@ class Tachidesk : ConfigurableSource, UnmeteredSource, HttpSource() {
|
|||
}
|
||||
}
|
||||
|
||||
private val defaultCategoryId: Int
|
||||
get() = categoryList.firstOrNull()?.id ?: 0
|
||||
|
||||
class CategorySelect(categoryList: List<CategoryDataClass>) :
|
||||
Filter.Select<String>("Category", categoryList.map { it.name }.toTypedArray())
|
||||
|
||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||
if (query.isNotEmpty()) {
|
||||
throw RuntimeException("Only Empty search is supported!")
|
||||
} else {
|
||||
var selectedFilter = defaultCategoryId
|
||||
|
||||
filters.forEach { filter ->
|
||||
when (filter) {
|
||||
is CategorySelect -> {
|
||||
selectedFilter = categoryList[filter.state].id
|
||||
}
|
||||
else -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return GET("$checkedBaseUrl/api/v1/category/$selectedFilter", headers)
|
||||
}
|
||||
}
|
||||
|
||||
override fun searchMangaParse(response: Response): MangasPage = popularMangaParse(response)
|
||||
|
||||
// ------------- Preferences -------------
|
||||
override fun setupPreferenceScreen(screen: PreferenceScreen) {
|
||||
screen.addPreference(screen.editTextPreference(ADDRESS_TITLE, ADDRESS_DEFAULT, baseUrl, false, "i.e. http://192.168.1.115:4567"))
|
||||
|
|
Loading…
Reference in New Issue