diff --git a/src/all/tachidesk/build.gradle b/src/all/tachidesk/build.gradle index 51e26357a..d5f4227da 100644 --- a/src/all/tachidesk/build.gradle +++ b/src/all/tachidesk/build.gradle @@ -6,7 +6,7 @@ ext { extName = 'Suwayomi' pkgNameSuffix = 'all.tachidesk' extClass = '.Tachidesk' - extVersionCode = 8 + extVersionCode = 9 } dependencies { diff --git a/src/all/tachidesk/src/eu/kanade/tachiyomi/extension/all/tachidesk/Tachidesk.kt b/src/all/tachidesk/src/eu/kanade/tachiyomi/extension/all/tachidesk/Tachidesk.kt index 73f15a232..fff191607 100644 --- a/src/all/tachidesk/src/eu/kanade/tachiyomi/extension/all/tachidesk/Tachidesk.kt +++ b/src/all/tachidesk/src/eu/kanade/tachiyomi/extension/all/tachidesk/Tachidesk.kt @@ -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(response.body.string()).let { it.toSManga() } + json.decodeFromString(response.body.string()).toSManga() // ------------- Chapter ------------- @@ -118,14 +122,24 @@ class Tachidesk : ConfigurableSource, UnmeteredSource, HttpSource() { // ------------- Filters & Search ------------- + private var categoryList: List = emptyList() + + private val defaultCategoryId: Int + get() = categoryList.firstOrNull()?.id ?: 0 + + class CategorySelect(categoryList: List) : + Filter.Select("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 = 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() + categoryUrlList.forEach { categoryUrl -> + val categoryMangaListRequest = + GET(categoryUrl, headers) + val categoryMangaListResponse = + client.newCall(categoryMangaListRequest).execute() + val categoryMangaListJson = + categoryMangaListResponse.body.string() + val categoryMangaList = + json.decodeFromString>(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) : - Filter.Select("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"))