Bilibili (Multisrc): Refactor some codes (#14164)

* Bilibili (Multisrc): Refactor some codes

* Lint

* Fix import
This commit is contained in:
AntsyLich 2022-11-07 21:14:17 +06:00 committed by GitHub
parent a55da72c76
commit a3b612a940
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 64 additions and 81 deletions

View File

@ -14,25 +14,13 @@
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" /> <category android:name="android.intent.category.BROWSABLE" />
<data <data android:scheme="https" />
android:host="www.bilibilicomics.com"
android:pathPattern="/detail/mc..*"
android:scheme="https" />
<data <data android:host="bilibilicomics.com" />
android:host="bilibilicomics.com" <data android:host="m.bilibilicomics.com" />
android:pathPattern="/detail/mc..*" <data android:host="www.bilibilicomics.com" />
android:scheme="https" />
<data <data android:pathPattern="/detail/mc..*" />
android:host="www.m.bilibilicomics.com"
android:pathPattern="/detail/mc..*"
android:scheme="https" />
<data
android:host="m.bilibilicomics.com"
android:pathPattern="/detail/mc..*"
android:scheme="https" />
</intent-filter> </intent-filter>
</activity> </activity>
</application> </application>

View File

@ -31,6 +31,7 @@ import okhttp3.Response
import okio.Buffer import okio.Buffer
import java.io.IOException import java.io.IOException
import java.net.URLDecoder import java.net.URLDecoder
import java.util.Calendar
class BilibiliComicsFactory : SourceFactory { class BilibiliComicsFactory : SourceFactory {
override fun createSources() = listOf( override fun createSources() = listOf(
@ -67,6 +68,9 @@ abstract class BilibiliComics(lang: String) : Bilibili(
private var accessTokenCookie: BilibiliAccessTokenCookie? = null private var accessTokenCookie: BilibiliAccessTokenCookie? = null
private val dayOfWeek: Int
get() = Calendar.getInstance().get(Calendar.DAY_OF_WEEK) - 1
override fun latestUpdatesRequest(page: Int): Request { override fun latestUpdatesRequest(page: Int): Request {
val jsonPayload = buildJsonObject { put("day", dayOfWeek) } val jsonPayload = buildJsonObject { put("day", dayOfWeek) }
val requestBody = jsonPayload.toString().toRequestBody(JSON_MEDIA_TYPE) val requestBody = jsonPayload.toString().toRequestBody(JSON_MEDIA_TYPE)

View File

@ -14,15 +14,12 @@
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" /> <category android:name="android.intent.category.BROWSABLE" />
<data <data android:scheme="https" />
android:host="manga.bilibili.com"
android:pathPattern="/detail/mc..*"
android:scheme="https" />
<data <data android:host="manga.bilibili.com" />
android:host="manga.bilibili.com"
android:pathPattern="/m/detail/mc..*" <data android:pathPattern="/detail/mc..*" />
android:scheme="https" /> <data android:pathPattern="/m/detail/mc..*" />
</intent-filter> </intent-filter>
</activity> </activity>
</application> </application>

View File

@ -34,7 +34,6 @@ import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Locale import java.util.Locale
abstract class Bilibili( abstract class Bilibili(
@ -46,7 +45,7 @@ abstract class Bilibili(
override val supportsLatest = true override val supportsLatest = true
override val client: OkHttpClient = network.cloudflareClient.newBuilder() override val client: OkHttpClient = network.cloudflareClient.newBuilder()
.addInterceptor(::expiredTokenIntercept) .addInterceptor(::expiredImageTokenIntercept)
.rateLimitHost(baseUrl.toHttpUrl(), 1) .rateLimitHost(baseUrl.toHttpUrl(), 1)
.rateLimitHost(CDN_URL.toHttpUrl(), 2) .rateLimitHost(CDN_URL.toHttpUrl(), 2)
.rateLimitHost(COVER_CDN_URL.toHttpUrl(), 2) .rateLimitHost(COVER_CDN_URL.toHttpUrl(), 2)
@ -76,9 +75,6 @@ abstract class Bilibili(
protected open val signedIn: Boolean = false protected open val signedIn: Boolean = false
protected val dayOfWeek: Int
get() = Calendar.getInstance().get(Calendar.DAY_OF_WEEK) - 1
override fun popularMangaRequest(page: Int): Request = searchMangaRequest( override fun popularMangaRequest(page: Int): Request = searchMangaRequest(
page = page, page = page,
query = "", query = "",
@ -100,11 +96,9 @@ abstract class Bilibili(
override fun latestUpdatesParse(response: Response): MangasPage = searchMangaParse(response) override fun latestUpdatesParse(response: Response): MangasPage = searchMangaParse(response)
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
if (query.startsWith(PREFIX_ID_SEARCH) && query.matches(ID_SEARCH_PATTERN)) { ID_SEARCH_PATTERN.matchEntire(query)?.let {
val comicId = query val (id) = it.destructured
.removePrefix(PREFIX_ID_SEARCH) return mangaDetailsApiRequest("/detail/mc$id")
.removePrefix("mc")
return mangaDetailsApiRequest("/detail/mc$comicId")
} }
val price = filters.firstInstanceOrNull<PriceFilter>()?.state ?: 0 val price = filters.firstInstanceOrNull<PriceFilter>()?.state ?: 0
@ -144,12 +138,13 @@ abstract class Bilibili(
} }
override fun searchMangaParse(response: Response): MangasPage { override fun searchMangaParse(response: Response): MangasPage {
if (response.request.url.toString().contains("ComicDetail")) { val requestUrl = response.request.url.toString()
if (requestUrl.contains("ComicDetail")) {
val comic = mangaDetailsParse(response) val comic = mangaDetailsParse(response)
return MangasPage(listOf(comic), hasNextPage = false) return MangasPage(listOf(comic), hasNextPage = false)
} }
if (response.request.url.toString().contains("ClassPage")) { if (requestUrl.contains("ClassPage")) {
val result = response.parseAs<List<BilibiliComicDto>>() val result = response.parseAs<List<BilibiliComicDto>>()
if (result.code != 0) { if (result.code != 0) {
@ -210,8 +205,7 @@ abstract class Bilibili(
} }
override fun mangaDetailsParse(response: Response): SManga = SManga.create().apply { override fun mangaDetailsParse(response: Response): SManga = SManga.create().apply {
val result = response.parseAs<BilibiliComicDto>() val comic = response.parseAs<BilibiliComicDto>().data!!
val comic = result.data!!
title = comic.title title = comic.title
author = comic.authorName.joinToString() author = comic.authorName.joinToString()
@ -231,7 +225,7 @@ abstract class Bilibili(
append("\n${intl.totalChapterCount}: ${intl.localize(comic.episodeList.size)}") append("\n${intl.totalChapterCount}: ${intl.localize(comic.episodeList.size)}")
if (comic.updateWeekdays.isNotEmpty() && status == SManga.ONGOING) { if (comic.updateWeekdays.isNotEmpty() && status == SManga.ONGOING) {
append("\n${intl.updatedEvery}: ${intl.getWeekdays(comic.updateWeekdays)}") append("\n${intl.getUpdateDays(comic.updateWeekdays)}")
} }
} }
thumbnail_url = comic.verticalCover thumbnail_url = comic.verticalCover
@ -254,9 +248,8 @@ abstract class Bilibili(
} }
protected open fun chapterFromObject(episode: BilibiliEpisodeDto, comicId: Int): SChapter = SChapter.create().apply { protected open fun chapterFromObject(episode: BilibiliEpisodeDto, comicId: Int): SChapter = SChapter.create().apply {
name = intl.episodePrefix + episode.shortTitle + name = episode.shortTitle.plus(if (episode.title.isNotBlank()) " - ${episode.title}" else "")
(if (episode.title.isNotBlank()) " - " + episode.title else "") date_upload = episode.publicationTime.toDate()
date_upload = episode.publicationTime.substringBefore("T").toDate()
url = "/mc$comicId/${episode.id}" url = "/mc$comicId/${episode.id}"
} }
@ -373,46 +366,45 @@ abstract class Bilibili(
return FilterList(filters) return FilterList(filters)
} }
private fun expiredTokenIntercept(chain: Interceptor.Chain): Response { private fun expiredImageTokenIntercept(chain: Interceptor.Chain): Response {
val response = chain.proceed(chain.request()) val response = chain.proceed(chain.request())
// Get a new image token if the current one expired. // Get a new image token if the current one expired.
if (response.code == 403 && chain.request().url.toString().contains(CDN_URL)) { if (response.code == 403 && chain.request().url.toString().contains(CDN_URL)) {
response.close()
val imagePath = chain.request().url.toString() val imagePath = chain.request().url.toString()
.substringAfter(CDN_URL) .substringAfter(CDN_URL)
.substringBefore("?token=") .substringBefore("?token=")
val imageTokenRequest = imageTokenRequest(listOf(imagePath)) val imageTokenRequest = imageTokenRequest(listOf(imagePath))
val imageTokenResponse = chain.proceed(imageTokenRequest) val imageTokenResponse = chain.proceed(imageTokenRequest)
val imageTokenResult = imageTokenResponse.parseAs<List<BilibiliPageDto>>() val imageTokenResult = imageTokenResponse.parseAs<List<BilibiliPageDto>>()
imageTokenResponse.close()
val newPage = imageTokenResult.data!![0] val newPage = imageTokenResult.data!!.first()
val newPageUrl = "${newPage.url}?token=${newPage.token}" val newPageUrl = "${newPage.url}?token=${newPage.token}"
val newRequest = imageRequest(Page(0, "", newPageUrl)) val newRequest = imageRequest(Page(0, "", newPageUrl))
imageTokenResponse.close()
response.close()
return chain.proceed(newRequest) return chain.proceed(newRequest)
} }
return response return response
} }
protected val SharedPreferences.chapterImageQuality private val SharedPreferences.chapterImageQuality
get() = when (getString("${IMAGE_QUALITY_PREF_KEY}_$lang", IMAGE_QUALITY_PREF_DEFAULT_VALUE)!!) { get() = when (getString("${IMAGE_QUALITY_PREF_KEY}_$lang", IMAGE_QUALITY_PREF_DEFAULT_VALUE)!!) {
"raw" -> "1600w" "hd" -> "1600w"
"hd" -> "1000w" "sd" -> "1000w"
"sd" -> "800w_50q" "low" -> "800w_50q"
else -> "raw+" else -> "raw"
} }
protected val SharedPreferences.chapterImageFormat private val SharedPreferences.chapterImageFormat
get() = getString("${IMAGE_FORMAT_PREF_KEY}_$lang", IMAGE_FORMAT_PREF_DEFAULT_VALUE)!! get() = getString("${IMAGE_FORMAT_PREF_KEY}_$lang", IMAGE_FORMAT_PREF_DEFAULT_VALUE)!!
private inline fun <reified R> List<*>.firstInstanceOrNull(): R? = private inline fun <reified R> List<*>.firstInstanceOrNull(): R? = firstOrNull { it is R } as? R
filterIsInstance<R>().firstOrNull()
protected open fun HttpUrl.Builder.addCommonParameters(): HttpUrl.Builder = let { protected open fun HttpUrl.Builder.addCommonParameters(): HttpUrl.Builder = apply {
if (name == "BILIBILI COMICS") { if (name == "BILIBILI COMICS") {
addQueryParameter("lang", apiLang) addQueryParameter("lang", apiLang)
addQueryParameter("sys_lang", apiLang) addQueryParameter("sys_lang", apiLang)
@ -420,8 +412,6 @@ abstract class Bilibili(
addQueryParameter("device", "pc") addQueryParameter("device", "pc")
addQueryParameter("platform", "web") addQueryParameter("platform", "web")
return@let it
} }
protected inline fun <reified T> Response.parseAs(): BilibiliResultDto<T> = use { protected inline fun <reified T> Response.parseAs(): BilibiliResultDto<T> = use {
@ -447,10 +437,10 @@ abstract class Bilibili(
private const val SEARCH_PER_PAGE = 9 private const val SEARCH_PER_PAGE = 9
const val PREFIX_ID_SEARCH = "id:" const val PREFIX_ID_SEARCH = "id:"
private val ID_SEARCH_PATTERN = "^id:(mc)?(\\d+)$".toRegex() private val ID_SEARCH_PATTERN = "^${PREFIX_ID_SEARCH}mc(\\d+)$".toRegex()
private const val IMAGE_QUALITY_PREF_KEY = "chapterImageQuality" 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_ENTRY_VALUES = arrayOf("raw", "hd", "sd", "low")
private val IMAGE_QUALITY_PREF_DEFAULT_VALUE = IMAGE_QUALITY_PREF_ENTRY_VALUES[1] private val IMAGE_QUALITY_PREF_DEFAULT_VALUE = IMAGE_QUALITY_PREF_ENTRY_VALUES[1]
private const val IMAGE_FORMAT_PREF_KEY = "chapterImageFormat" private const val IMAGE_FORMAT_PREF_KEY = "chapterImageFormat"
@ -461,7 +451,7 @@ abstract class Bilibili(
const val THUMBNAIL_RESOLUTION = "@512w.jpg" const val THUMBNAIL_RESOLUTION = "@512w.jpg"
private val DATE_FORMATTER by lazy { private val DATE_FORMATTER by lazy {
SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH) SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.ENGLISH)
} }
private const val EMOJI_LOCKED = "\uD83D\uDD12" private const val EMOJI_LOCKED = "\uD83D\uDD12"

View File

@ -65,7 +65,7 @@ data class BilibiliImageDto(
) { ) {
fun url(quality: String, format: String): String { fun url(quality: String, format: String): String {
val imageWidth = if (quality == "raw+") "${width}w" else quality val imageWidth = if (quality == "raw") "${width}w" else quality
return "$path@$imageWidth.$format" return "$path@$imageWidth.$format"
} }

View File

@ -10,7 +10,7 @@ class BilibiliGenerator : ThemeSourceGenerator {
override val themeClass = "Bilibili" override val themeClass = "Bilibili"
override val baseVersionCode: Int = 7 override val baseVersionCode: Int = 8
override val sources = listOf( override val sources = listOf(
MultiLang( MultiLang(

View File

@ -43,12 +43,6 @@ class BilibiliIntl(private val lang: String) {
else -> "Price" else -> "Price"
} }
val episodePrefix: String = when (lang) {
CHINESE, SIMPLIFIED_CHINESE -> ""
SPANISH -> "Cap. "
else -> "Ep. "
}
fun hasPaidChaptersWarning(chapterCount: Int): String = when (lang) { fun hasPaidChaptersWarning(chapterCount: Int): String = when (lang) {
CHINESE, SIMPLIFIED_CHINESE -> CHINESE, SIMPLIFIED_CHINESE ->
"${Bilibili.EMOJI_WARNING} 此漫画有 ${chapterCount.localized} 个付费章节,已在目录中隐藏。" + "${Bilibili.EMOJI_WARNING} 此漫画有 ${chapterCount.localized} 个付费章节,已在目录中隐藏。" +
@ -73,8 +67,8 @@ class BilibiliIntl(private val lang: String) {
} }
val imageQualityPrefEntries: Array<String> = when (lang) { val imageQualityPrefEntries: Array<String> = when (lang) {
CHINESE, SIMPLIFIED_CHINESE -> arrayOf("原图+", "原图 (1600w)", " (1000w)", " (800w)") CHINESE, SIMPLIFIED_CHINESE -> arrayOf("原图", "高清 (1600w)", "标清 (1000w)", " (800w)")
else -> arrayOf("Raw+", "Raw (1600w)", "HD (1000w)", "SD (800w)") else -> arrayOf("Raw", "HD (1600w)", "SD (1000w)", "Low (800w)")
} }
val imageFormatPrefTitle: String = when (lang) { val imageFormatPrefTitle: String = when (lang) {
@ -191,18 +185,27 @@ class BilibiliIntl(private val lang: String) {
else -> "Total chapter count" else -> "Total chapter count"
} }
val updatedEvery: String = when (lang) { private val updatesDaily: String = when (lang) {
CHINESE, SIMPLIFIED_CHINESE -> "周更新时间" CHINESE, SIMPLIFIED_CHINESE -> "日更新"
SPANISH -> "Actualizado en" SPANISH -> "Actualizaciones diarias"
else -> "Updated every" else -> "Updates daily"
} }
fun getWeekdays(dayIndexes: List<Int>): String { private fun updatesEvery(days: String): String = when (lang) {
val weekdays = dateFormatSymbols.weekdays CHINESE, SIMPLIFIED_CHINESE -> "${days}更新"
.filter(String::isNotBlank) SPANISH -> "Actualizaciones todos los $days"
.map { dayName -> dayName.replaceFirstChar { it.uppercase(locale) } } else -> "Updates every $days"
}
return dayIndexes.joinToString { weekdays[it] } fun getUpdateDays(dayIndexes: List<Int>): String {
val shortWeekDays = dateFormatSymbols.shortWeekdays.filterNot(String::isBlank)
if (dayIndexes.size == shortWeekDays.size) return updatesDaily
val shortWeekDaysUpperCased = shortWeekDays.map {
it.replaceFirstChar { char -> char.uppercase(locale) }
}
val days = dayIndexes.joinToString { shortWeekDaysUpperCased[it] }
return updatesEvery(days)
} }
fun localize(value: Int) = value.localized fun localize(value: Int) = value.localized

View File

@ -23,7 +23,8 @@ class BilibiliUrlActivity : Activity() {
val pathSegments = intent?.data?.pathSegments val pathSegments = intent?.data?.pathSegments
if (pathSegments != null && pathSegments.size > 1) { if (pathSegments != null && pathSegments.size > 1) {
val titleId = if (pathSegments.size == 3) pathSegments[2] else pathSegments[1] // Mobile site of https://manga.bilibili.com starts with path "m"
val titleId = if (pathSegments[0] == "m") pathSegments[2] else pathSegments[1]
val mainIntent = Intent().apply { val mainIntent = Intent().apply {
action = "eu.kanade.tachiyomi.SEARCH" action = "eu.kanade.tachiyomi.SEARCH"