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>
This commit is contained in:
Alessandro Jean 2022-10-07 10:35:45 -03:00 committed by GitHub
parent 09c587a0b4
commit db71211623
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 164 additions and 108 deletions

View File

@ -36,8 +36,12 @@ class BilibiliManga : Bilibili(
override val defaultLatestSort: Int = 1
override fun getAllSortOptions(): Array<String> =
arrayOf(intl.sortPopular, intl.sortUpdated, intl.sortFollowers, intl.sortAdded)
override fun getAllSortOptions(): Array<BilibiliTag> = arrayOf(
BilibiliTag(intl.sortPopular, 0),
BilibiliTag(intl.sortUpdated, 1),
BilibiliTag(intl.sortFollowers, 2),
BilibiliTag(intl.sortAdded, 3)
)
override fun getAllPrices(): Array<String> =
arrayOf(intl.priceAll, intl.priceFree, intl.pricePaid, intl.priceWaitForFree)

View File

@ -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<Application>().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<SortFilter>()
.firstOrNull()?.state ?: 0
val status = filters.filterIsInstance<StatusFilter>()
.firstOrNull()?.state?.minus(1) ?: -1
val price = filters.filterIsInstance<PriceFilter>()
.firstOrNull()?.state ?: 0
val styleId = filters.filterIsInstance<GenreFilter>()
.firstOrNull()?.selected?.id ?: -1
val areaId = filters.filterIsInstance<AreaFilter>()
.firstOrNull()?.selected?.id ?: -1
val pageSize = if (query.isBlank()) POPULAR_PER_PAGE else SEARCH_PER_PAGE
val price = filters.firstInstanceOrNull<PriceFilter>()?.state ?: 0
val jsonPayload = buildJsonObject {
put("area_id", areaId)
put("is_finish", status)
put("area_id", filters.firstInstanceOrNull<AreaFilter>()?.selected?.id ?: -1)
put("is_finish", filters.firstInstanceOrNull<StatusFilter>()?.state?.minus(1) ?: -1)
put("is_free", if (price == 0) -1 else price)
put("order", order)
put("order", filters.firstInstanceOrNull<SortFilter>()?.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<GenreFilter>()?.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<List<BilibiliPageDto>>()
@ -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<BilibiliTag> = emptyArray()
protected open fun getAllSortOptions(): Array<String> =
arrayOf(intl.sortInterest, intl.sortPopular, intl.sortUpdated)
protected open fun getAllSortOptions(): Array<BilibiliTag> = arrayOf(
BilibiliTag(intl.sortInterest, 0),
BilibiliTag(intl.sortUpdated, 4),
)
protected open fun getAllStatus(): Array<String> =
arrayOf(intl.statusAll, intl.statusOngoing, intl.statusComplete)
protected open fun getAllPrices(): Array<String> =
arrayOf(intl.priceAll, intl.priceFree, intl.pricePaid)
protected open fun getAllPrices(): Array<String> = 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 <reified R> List<*>.firstInstanceOrNull(): R? =
filterIsInstance<R>().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)

View File

@ -23,13 +23,18 @@ data class BilibiliComicDto(
@SerialName("ep_list") val episodeList: List<BilibiliEpisodeDto> = 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<String> = emptyList(),
val title: String,
@SerialName("update_weekday") val updateWeekdays: List<Int> = 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<String> =
(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(

View File

@ -6,20 +6,21 @@ data class BilibiliTag(val name: String, val id: Int) {
override fun toString(): String = name
}
class GenreFilter(label: String, genres: Array<BilibiliTag>) :
Filter.Select<BilibiliTag>(label, genres) {
val selected: BilibiliTag
get() = values[state]
open class EnhancedSelect<T>(name: String, values: Array<T>, state: Int = 0) :
Filter.Select<T>(name, values, state) {
val selected: T?
get() = values.getOrNull(state)
}
class GenreFilter(label: String, genres: Array<BilibiliTag>) :
EnhancedSelect<BilibiliTag>(label, genres)
class AreaFilter(label: String, genres: Array<BilibiliTag>) :
Filter.Select<BilibiliTag>(label, genres) {
val selected: BilibiliTag
get() = values[state]
}
EnhancedSelect<BilibiliTag>(label, genres)
class SortFilter(label: String, options: Array<String>, state: Int = 0) :
Filter.Select<String>(label, options, state)
class SortFilter(label: String, options: Array<BilibiliTag>, state: Int = 0) :
EnhancedSelect<BilibiliTag>(label, options, state)
class StatusFilter(label: String, statuses: Array<String>) :
Filter.Select<String>(label, statuses)

View File

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

View File

@ -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<String> = 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<Int>): 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"