From db71211623b7b2b707d13bf861068fdb2bc7ee1f Mon Sep 17 00:00:00 2001 From: Alessandro Jean <14254807+alessandrojean@users.noreply.github.com> Date: Fri, 7 Oct 2022 10:35:45 -0300 Subject: [PATCH] Add original quality as option in Bilibili (#13719) * Add original quality as option in Bilibili. Co-authored-by: stevenyomi <95685115+stevenyomi@users.noreply.github.com> Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com> * Always show the locked chapters count. * Show extra information in the series details. Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com> * Add missing Chinese translations. Co-authored-by: stevenyomi <95685115+stevenyomi@users.noreply.github.com> Co-authored-by: stevenyomi <95685115+stevenyomi@users.noreply.github.com> Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com> --- .../bilibilimanga/src/BilibiliManga.kt | 8 +- .../tachiyomi/multisrc/bilibili/Bilibili.kt | 154 +++++++++--------- .../multisrc/bilibili/BilibiliDto.kt | 20 ++- .../multisrc/bilibili/BilibiliFilters.kt | 21 +-- .../multisrc/bilibili/BilibiliGenerator.kt | 2 +- .../multisrc/bilibili/BilibiliIntl.kt | 67 ++++++-- 6 files changed, 164 insertions(+), 108 deletions(-) diff --git a/multisrc/overrides/bilibili/bilibilimanga/src/BilibiliManga.kt b/multisrc/overrides/bilibili/bilibilimanga/src/BilibiliManga.kt index 6f5c8783b..3f6779243 100644 --- a/multisrc/overrides/bilibili/bilibilimanga/src/BilibiliManga.kt +++ b/multisrc/overrides/bilibili/bilibilimanga/src/BilibiliManga.kt @@ -36,8 +36,12 @@ class BilibiliManga : Bilibili( override val defaultLatestSort: Int = 1 - override fun getAllSortOptions(): Array = - arrayOf(intl.sortPopular, intl.sortUpdated, intl.sortFollowers, intl.sortAdded) + override fun getAllSortOptions(): Array = arrayOf( + BilibiliTag(intl.sortPopular, 0), + BilibiliTag(intl.sortUpdated, 1), + BilibiliTag(intl.sortFollowers, 2), + BilibiliTag(intl.sortAdded, 3) + ) override fun getAllPrices(): Array = arrayOf(intl.priceAll, intl.priceFree, intl.pricePaid, intl.priceWaitForFree) diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/bilibili/Bilibili.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/bilibili/Bilibili.kt index 5b95263bb..c7fc19971 100644 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/bilibili/Bilibili.kt +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/bilibili/Bilibili.kt @@ -63,9 +63,9 @@ abstract class Bilibili( else -> lang } - protected open val defaultPopularSort: Int = 1 + protected open val defaultPopularSort: Int = 0 - protected open val defaultLatestSort: Int = 2 + protected open val defaultLatestSort: Int = 1 private val preferences: SharedPreferences by lazy { Injekt.get().getSharedPreferences("source_$id", 0x0000) @@ -75,19 +75,23 @@ abstract class Bilibili( protected open val signedIn: Boolean = false - private val chapterImageQuality: String - get() = preferences.getString("${IMAGE_QUALITY_PREF_KEY}_$lang", IMAGE_QUALITY_PREF_DEFAULT_VALUE)!! - - private val chapterImageFormat: String - get() = preferences.getString("${IMAGE_FORMAT_PREF_KEY}_$lang", IMAGE_FORMAT_PREF_DEFAULT_VALUE)!! - - override fun popularMangaRequest(page: Int): Request = - searchMangaRequest(page, "", FilterList(SortFilter("", emptyArray(), defaultPopularSort))) + override fun popularMangaRequest(page: Int): Request = searchMangaRequest( + page = page, + query = "", + filters = FilterList( + SortFilter("", getAllSortOptions(), defaultPopularSort) + ) + ) override fun popularMangaParse(response: Response): MangasPage = searchMangaParse(response) - override fun latestUpdatesRequest(page: Int): Request = - searchMangaRequest(page, "", FilterList(SortFilter("", emptyArray(), defaultLatestSort))) + override fun latestUpdatesRequest(page: Int): Request = searchMangaRequest( + page = page, + query = "", + filters = FilterList( + SortFilter("", getAllSortOptions(), defaultLatestSort) + ) + ) override fun latestUpdatesParse(response: Response): MangasPage = searchMangaParse(response) @@ -99,31 +103,16 @@ abstract class Bilibili( return mangaDetailsApiRequest("/detail/mc$comicId") } - val order = filters.filterIsInstance() - .firstOrNull()?.state ?: 0 - - val status = filters.filterIsInstance() - .firstOrNull()?.state?.minus(1) ?: -1 - - val price = filters.filterIsInstance() - .firstOrNull()?.state ?: 0 - - val styleId = filters.filterIsInstance() - .firstOrNull()?.selected?.id ?: -1 - - val areaId = filters.filterIsInstance() - .firstOrNull()?.selected?.id ?: -1 - - val pageSize = if (query.isBlank()) POPULAR_PER_PAGE else SEARCH_PER_PAGE + val price = filters.firstInstanceOrNull()?.state ?: 0 val jsonPayload = buildJsonObject { - put("area_id", areaId) - put("is_finish", status) + put("area_id", filters.firstInstanceOrNull()?.selected?.id ?: -1) + put("is_finish", filters.firstInstanceOrNull()?.state?.minus(1) ?: -1) put("is_free", if (price == 0) -1 else price) - put("order", order) + put("order", filters.firstInstanceOrNull()?.selected?.id ?: 0) put("page_num", page) - put("page_size", pageSize) - put("style_id", styleId) + put("page_size", if (query.isBlank()) POPULAR_PER_PAGE else SEARCH_PER_PAGE) + put("style_id", filters.firstInstanceOrNull()?.selected?.id ?: -1) put("style_prefer", "[]") if (query.isNotBlank()) { @@ -222,15 +211,27 @@ abstract class Bilibili( title = comic.title author = comic.authorName.joinToString() - status = if (comic.isFinish == 1) SManga.COMPLETED else SManga.ONGOING genre = comic.genres(intl.pricePaid, EMOJI_LOCKED).joinToString() - description = comic.classicLines - thumbnail_url = comic.verticalCover + THUMBNAIL_RESOLUTION - url = "/detail/mc" + comic.id - - if (comic.hasPaidChapters && !signedIn) { - description = "${intl.hasPaidChaptersWarning}\n\n$description" + status = when { + comic.isFinish == 1 -> SManga.COMPLETED + comic.isOnHiatus -> SManga.ON_HIATUS + else -> SManga.ONGOING } + description = buildString { + if (comic.hasPaidChapters && !signedIn) { + append("${intl.hasPaidChaptersWarning(comic.paidChaptersCount)}\n\n") + } + + append("${comic.classicLines}\n\n") + append("${intl.informationTitle}:") + append("\n• ${intl.totalChapterCount}: ${intl.localize(comic.episodeList.size)}") + + if (comic.updateWeekdays.isNotEmpty() && status == SManga.ONGOING) { + append("\n• ${intl.updatedEvery}: ${intl.getWeekdays(comic.updateWeekdays)}") + } + } + thumbnail_url = comic.verticalCover + url = "/detail/mc" + comic.id } // Chapters are available in the same url of the manga details. @@ -288,10 +289,10 @@ abstract class Bilibili( return emptyList() } - val imageQuality = chapterImageQuality - val imageFormat = chapterImageFormat + val imageQuality = preferences.chapterImageQuality + val imageFormat = preferences.chapterImageFormat - val imageUrls = result.data!!.images.map { "${it.path}@$imageQuality.$imageFormat" } + val imageUrls = result.data!!.images.map { it.url(imageQuality, imageFormat) } val imageTokenRequest = imageTokenRequest(imageUrls) val imageTokenResponse = client.newCall(imageTokenRequest).execute() val imageTokenResult = imageTokenResponse.parseAs>() @@ -324,16 +325,6 @@ abstract class Bilibili( entryValues = IMAGE_QUALITY_PREF_ENTRY_VALUES setDefaultValue(IMAGE_QUALITY_PREF_DEFAULT_VALUE) summary = "%s" - - setOnPreferenceChangeListener { _, newValue -> - val selected = newValue as String - val index = findIndexOfValue(selected) - val entry = entryValues[index] as String - - preferences.edit() - .putString("${IMAGE_QUALITY_PREF_KEY}_$lang", entry) - .commit() - } } val imageFormatPref = ListPreference(screen.context).apply { @@ -343,16 +334,6 @@ abstract class Bilibili( entryValues = IMAGE_FORMAT_PREF_ENTRY_VALUES setDefaultValue(IMAGE_FORMAT_PREF_DEFAULT_VALUE) summary = "%s" - - setOnPreferenceChangeListener { _, newValue -> - val selected = newValue as String - val index = findIndexOfValue(selected) - val entry = entryValues[index] as String - - preferences.edit() - .putString("${IMAGE_FORMAT_PREF_KEY}_$lang", entry) - .commit() - } } screen.addPreference(imageQualityPref) @@ -363,29 +344,28 @@ abstract class Bilibili( protected open fun getAllAreas(): Array = emptyArray() - protected open fun getAllSortOptions(): Array = - arrayOf(intl.sortInterest, intl.sortPopular, intl.sortUpdated) + protected open fun getAllSortOptions(): Array = arrayOf( + BilibiliTag(intl.sortInterest, 0), + BilibiliTag(intl.sortUpdated, 4), + ) protected open fun getAllStatus(): Array = arrayOf(intl.statusAll, intl.statusOngoing, intl.statusComplete) - protected open fun getAllPrices(): Array = - arrayOf(intl.priceAll, intl.priceFree, intl.pricePaid) + protected open fun getAllPrices(): Array = emptyArray() override fun getFilterList(): FilterList { - val filters = mutableListOf( + val allAreas = getAllAreas() + val allPrices = getAllPrices() + + val filters = listOfNotNull( StatusFilter(intl.statusLabel, getAllStatus()), SortFilter(intl.sortLabel, getAllSortOptions(), defaultPopularSort), - PriceFilter(intl.priceLabel, getAllPrices()), - GenreFilter(intl.genreLabel, getAllGenres()) + PriceFilter(intl.priceLabel, getAllPrices()).takeIf { allPrices.isNotEmpty() }, + GenreFilter(intl.genreLabel, getAllGenres()), + AreaFilter(intl.areaLabel, allAreas).takeIf { allAreas.isNotEmpty() } ) - val allAreas = getAllAreas() - - if (allAreas.isNotEmpty()) { - filters += AreaFilter(intl.areaLabel, allAreas) - } - return FilterList(filters) } @@ -414,6 +394,20 @@ abstract class Bilibili( return response } + protected val SharedPreferences.chapterImageQuality + get() = when (getString("${IMAGE_QUALITY_PREF_KEY}_$lang", IMAGE_QUALITY_PREF_DEFAULT_VALUE)!!) { + "raw" -> "1600w" + "hd" -> "1000w" + "sd" -> "800w_50q" + else -> "raw+" + } + + protected val SharedPreferences.chapterImageFormat + get() = getString("${IMAGE_FORMAT_PREF_KEY}_$lang", IMAGE_FORMAT_PREF_DEFAULT_VALUE)!! + + private inline fun List<*>.firstInstanceOrNull(): R? = + filterIsInstance().firstOrNull() + protected open fun HttpUrl.Builder.addCommonParameters(): HttpUrl.Builder = let { if (name == "BILIBILI COMICS") { addQueryParameter("lang", apiLang) @@ -451,16 +445,16 @@ abstract class Bilibili( const val PREFIX_ID_SEARCH = "id:" private val ID_SEARCH_PATTERN = "^id:(mc)?(\\d+)$".toRegex() - private const val IMAGE_QUALITY_PREF_KEY = "chapterImageResolution" - private val IMAGE_QUALITY_PREF_ENTRY_VALUES = arrayOf("1200w", "800w", "600w_50q") - private val IMAGE_QUALITY_PREF_DEFAULT_VALUE = IMAGE_QUALITY_PREF_ENTRY_VALUES[0] + private const val IMAGE_QUALITY_PREF_KEY = "chapterImageQuality" + private val IMAGE_QUALITY_PREF_ENTRY_VALUES = arrayOf("raw+", "raw", "hd", "sd") + private val IMAGE_QUALITY_PREF_DEFAULT_VALUE = IMAGE_QUALITY_PREF_ENTRY_VALUES[1] private const val IMAGE_FORMAT_PREF_KEY = "chapterImageFormat" private val IMAGE_FORMAT_PREF_ENTRIES = arrayOf("JPG", "WEBP", "PNG") private val IMAGE_FORMAT_PREF_ENTRY_VALUES = arrayOf("jpg", "webp", "png") private val IMAGE_FORMAT_PREF_DEFAULT_VALUE = IMAGE_FORMAT_PREF_ENTRY_VALUES[0] - private const val THUMBNAIL_RESOLUTION = "@512w.jpg" + const val THUMBNAIL_RESOLUTION = "@512w.jpg" private val DATE_FORMATTER by lazy { SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH) diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/bilibili/BilibiliDto.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/bilibili/BilibiliDto.kt index 35062ea08..970ba7372 100644 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/bilibili/BilibiliDto.kt +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/bilibili/BilibiliDto.kt @@ -23,13 +23,18 @@ data class BilibiliComicDto( @SerialName("ep_list") val episodeList: List = emptyList(), val id: Int = 0, @SerialName("is_finish") val isFinish: Int = 0, + @SerialName("temp_stop_update") val isOnHiatus: Boolean = false, @SerialName("season_id") val seasonId: Int = 0, val styles: List = emptyList(), val title: String, + @SerialName("update_weekday") val updateWeekdays: List = emptyList(), @SerialName("vertical_cover") val verticalCover: String = "" ) { val hasPaidChapters: Boolean - get() = episodeList.any { episode -> episode.payMode == 1 && episode.payGold > 0 } + get() = paidChaptersCount > 0 + + val paidChaptersCount: Int + get() = episodeList.filter { episode -> episode.payMode == 1 && episode.payGold > 0 }.size fun genres(paidLabel: String, emoji: String): List = (if (hasPaidChapters) listOf("$emoji $paidLabel") else emptyList()) + styles @@ -54,8 +59,17 @@ data class BilibiliReader( @Serializable data class BilibiliImageDto( - val path: String -) + val path: String, + @SerialName("x") val width: Int, + @SerialName("y") val height: Int +) { + + fun url(quality: String, format: String): String { + val imageWidth = if (quality == "raw+") "${width}w" else quality + + return "$path@$imageWidth.$format" + } +} @Serializable data class BilibiliPageDto( diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/bilibili/BilibiliFilters.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/bilibili/BilibiliFilters.kt index 74ffc874c..fc38d581d 100644 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/bilibili/BilibiliFilters.kt +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/bilibili/BilibiliFilters.kt @@ -6,20 +6,21 @@ data class BilibiliTag(val name: String, val id: Int) { override fun toString(): String = name } -class GenreFilter(label: String, genres: Array) : - Filter.Select(label, genres) { - val selected: BilibiliTag - get() = values[state] +open class EnhancedSelect(name: String, values: Array, state: Int = 0) : + Filter.Select(name, values, state) { + + val selected: T? + get() = values.getOrNull(state) } +class GenreFilter(label: String, genres: Array) : + EnhancedSelect(label, genres) + class AreaFilter(label: String, genres: Array) : - Filter.Select(label, genres) { - val selected: BilibiliTag - get() = values[state] -} + EnhancedSelect(label, genres) -class SortFilter(label: String, options: Array, state: Int = 0) : - Filter.Select(label, options, state) +class SortFilter(label: String, options: Array, state: Int = 0) : + EnhancedSelect(label, options, state) class StatusFilter(label: String, statuses: Array) : Filter.Select(label, statuses) diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/bilibili/BilibiliGenerator.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/bilibili/BilibiliGenerator.kt index 3c7ee0318..a11b45da2 100644 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/bilibili/BilibiliGenerator.kt +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/bilibili/BilibiliGenerator.kt @@ -10,7 +10,7 @@ class BilibiliGenerator : ThemeSourceGenerator { override val themeClass = "Bilibili" - override val baseVersionCode: Int = 4 + override val baseVersionCode: Int = 5 override val sources = listOf( MultiLang( diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/bilibili/BilibiliIntl.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/bilibili/BilibiliIntl.kt index c30aa6d22..09892ac58 100644 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/bilibili/BilibiliIntl.kt +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/bilibili/BilibiliIntl.kt @@ -1,6 +1,16 @@ package eu.kanade.tachiyomi.multisrc.bilibili -class BilibiliIntl(lang: String) { +import java.text.DateFormatSymbols +import java.text.NumberFormat +import java.util.Locale + +class BilibiliIntl(private val lang: String) { + + private val locale by lazy { Locale.forLanguageTag(lang) } + + private val dateFormatSymbols by lazy { DateFormatSymbols(locale) } + + private val numberFormat by lazy { NumberFormat.getInstance(locale) } val statusLabel: String = when (lang) { CHINESE, SIMPLIFIED_CHINESE -> "进度" @@ -39,18 +49,20 @@ class BilibiliIntl(lang: String) { else -> "Ep. " } - val hasPaidChaptersWarning: String = when (lang) { + fun hasPaidChaptersWarning(chapterCount: Int): String = when (lang) { CHINESE, SIMPLIFIED_CHINESE -> - "${Bilibili.EMOJI_WARNING} 此漫画的付费章节已从章节列表中过滤。如果您已购买章节,请在 WebView " + - "登录并刷新章节列表以阅读已购章节。" + "${Bilibili.EMOJI_WARNING} 此漫画有 ${chapterCount.localized} 个付费章节,已在目录中隐藏。" + + "如果你已购买,请在 WebView 登录并刷新目录,即可阅读已购章节。" SPANISH -> - "${Bilibili.EMOJI_WARNING} ADVERTENCIA: Esta serie tiene capítulos pagos que fueron " + - "filtrados de la lista de capítulos. Si ya compró y tiene alguno en su cuenta, " + - "inicie sesión en WebView y actualice la lista de capítulos para leerlos." + "${Bilibili.EMOJI_WARNING} ADVERTENCIA: Esta serie tiene ${chapterCount.localized} " + + "capítulos pagos que fueron filtrados de la lista de capítulos. Si ya has " + + "desbloqueado y tiene alguno en su cuenta, inicie sesión en WebView y " + + "actualice la lista de capítulos para leerlos." else -> - "${Bilibili.EMOJI_WARNING} WARNING: This series has paid chapters that were filtered " + - "out from the chapter list. If you have already bought and have any in your " + - "account, sign in through WebView and refresh the chapter list to read them." + "${Bilibili.EMOJI_WARNING} WARNING: This series has ${chapterCount.localized} paid " + + "chapters that were filtered out from the chapter list. If you have already " + + "unlocked and have any in your account, sign in through WebView and refresh " + + "the chapter list to read them." } val imageQualityPrefTitle: String = when (lang) { @@ -61,8 +73,8 @@ class BilibiliIntl(lang: String) { } val imageQualityPrefEntries: Array = when (lang) { - CHINESE, SIMPLIFIED_CHINESE -> arrayOf("原图", "高", "低") - else -> arrayOf("Raw", "HD", "SD") + CHINESE, SIMPLIFIED_CHINESE -> arrayOf("原图+", "原图 (1600w)", "高 (1000w)", "低 (800w)") + else -> arrayOf("Raw+", "Raw (1600w)", "HD (1000w)", "SD (800w)") } val imageFormatPrefTitle: String = when (lang) { @@ -167,6 +179,37 @@ class BilibiliIntl(lang: String) { else -> "Failed to get the credential to read the chapter." } + val informationTitle: String = when (lang) { + CHINESE, SIMPLIFIED_CHINESE -> "信息" + SPANISH -> "Información" + else -> "Information" + } + + val totalChapterCount: String = when (lang) { + CHINESE, SIMPLIFIED_CHINESE -> "章节总数" + SPANISH -> "Número total de capítulos" + else -> "Total chapter count" + } + + val updatedEvery: String = when (lang) { + CHINESE, SIMPLIFIED_CHINESE -> "每周更新时间" + SPANISH -> "Actualizado en" + else -> "Updated every" + } + + fun getWeekdays(dayIndexes: List): String { + val weekdays = dateFormatSymbols.weekdays + .filter(String::isNotBlank) + .map { dayName -> dayName.replaceFirstChar { it.uppercase(locale) } } + + return dayIndexes.joinToString { weekdays[it] } + } + + fun localize(value: Int) = value.localized + + private val Int.localized: String + get() = numberFormat.format(this) + companion object { const val CHINESE = "zh" const val ENGLISH = "en"