Fix URL intent crash and add filters to Bilibili. (#8423)

This commit is contained in:
Alessandro Jean 2021-08-07 09:48:20 -03:00 committed by GitHub
parent 47e0b4eee8
commit c4c10cdb32
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 149 additions and 92 deletions

View File

@ -15,12 +15,22 @@
<data
android:host="www.bilibilicomics.com"
android:pathPattern="/detail/..*"
android:pathPattern="/detail/mc..*"
android:scheme="https" />
<data
android:host="bilibilicomics.com"
android:pathPattern="/detail/mc..*"
android:scheme="https" />
<data
android:host="www.m.bilibilicomics.com"
android:pathPattern="/detail/mc..*"
android:scheme="https" />
<data
android:host="m.bilibilicomics.com"
android:pathPattern="/detail/..*"
android:pathPattern="/detail/mc..*"
android:scheme="https" />
</intent-filter>
</activity>

View File

@ -6,7 +6,7 @@ ext {
extName = 'Bilibili Comics'
pkgNameSuffix = 'en.bilibilicomics'
extClass = '.BilibiliComics'
extVersionCode = 5
extVersionCode = 6
libVersion = '1.2'
containsNsfw = true
}

View File

@ -4,6 +4,7 @@ import eu.kanade.tachiyomi.annotations.Nsfw
import eu.kanade.tachiyomi.lib.ratelimit.RateLimitInterceptor
import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.network.asObservableSuccess
import eu.kanade.tachiyomi.source.model.Filter
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.MangasPage
import eu.kanade.tachiyomi.source.model.Page
@ -54,12 +55,19 @@ class BilibiliComics : HttpSource() {
private val json: Json by injectLazy()
private val day: Int
get() = Calendar.getInstance().get(Calendar.DAY_OF_WEEK) - 1
override fun popularMangaRequest(page: Int): Request {
val requestPayload = buildJsonObject {
put("id", FEATURED_ID)
put("isAll", 0)
put("page_num", 1)
put("page_size", 6)
put("area_id", -1)
put("is_finish", -1)
put("is_free", 1)
put("order", 0)
put("page_num", page)
put("page_size", POPULAR_PER_PAGE)
put("style_id", -1)
put("style_prefer", "[]")
}
val requestBody = requestPayload.toString().toRequestBody(JSON_MEDIA_TYPE)
@ -69,35 +77,33 @@ class BilibiliComics : HttpSource() {
.build()
return POST(
"$baseUrl/$BASE_API_ENDPOINT/GetClassPageSixComics?device=pc&platform=web",
"$baseUrl/$BASE_API_ENDPOINT/ClassPage?device=pc&platform=web",
headers = newHeaders,
body = requestBody
)
}
override fun popularMangaParse(response: Response): MangasPage {
val result = json.decodeFromString<BilibiliResultDto<BilibiliFeaturedDto>>(response.body!!.string())
val result = json.decodeFromString<BilibiliResultDto<List<BilibiliComicDto>>>(response.body!!.string())
if (result.code != 0) {
return MangasPage(emptyList(), hasNextPage = false)
}
val comicList = result.data!!.rollSixComics
.map(::popularMangaFromObject)
val comicList = result.data!!.map(::popularMangaFromObject)
val hasNextPage = comicList.size == POPULAR_PER_PAGE
return MangasPage(comicList, hasNextPage = false)
return MangasPage(comicList, hasNextPage)
}
private fun popularMangaFromObject(comic: BilibiliComicDto): SManga = SManga.create().apply {
title = comic.title
thumbnail_url = comic.verticalCover
url = "/detail/mc${comic.comicId}"
url = "/detail/mc${comic.seasonId}"
}
override fun latestUpdatesRequest(page: Int): Request {
val requestPayload = buildJsonObject {
put("day", day)
}
val requestPayload = buildJsonObject { put("day", day) }
val requestBody = requestPayload.toString().toRequestBody(JSON_MEDIA_TYPE)
val newHeaders = headersBuilder()
@ -131,85 +137,111 @@ class BilibiliComics : HttpSource() {
url = "/detail/mc${comic.comicId}"
}
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
return when {
query.startsWith(prefixIdSearch) -> {
val id = query.removePrefix(prefixIdSearch)
client.newCall(mangaDetailsApiRequestById(id)).asObservableSuccess()
.map { response ->
mangaDetailsParse(response).let { MangasPage(listOf(it), false) }
}
}
else -> {
client.newCall(searchMangaRequest(page, query, filters)).asObservableSuccess()
.map { response ->
searchMangaParse(response)
}
}
}
}
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
if (query.startsWith(PREFIX_ID_SEARCH) && query.matches(ID_SEARCH_PATTERN)) {
val comicId = query
.removePrefix(PREFIX_ID_SEARCH)
.removePrefix("mc")
return mangaDetailsApiRequest("/detail/mc$comicId")
}
val order = filters.filterIsInstance<SortFilter>()
.firstOrNull()?.state ?: 0
val status = filters.filterIsInstance<StatusFilter>()
.firstOrNull()?.state?.minus(1) ?: -1
val styleId = filters.filterIsInstance<GenreFilter>()
.firstOrNull()?.selected?.id ?: -1
val pageSize = if (query.isBlank()) POPULAR_PER_PAGE else SEARCH_PER_PAGE
val jsonPayload = buildJsonObject {
put("area_id", -1)
put("is_finish", -1)
put("is_finish", status)
put("is_free", 1)
put("key_word", query)
put("order", 0)
put("order", order)
put("page_num", page)
put("page_size", 9)
put("style_id", -1)
put("page_size", pageSize)
put("style_id", styleId)
put("style_prefer", "[]")
if (query.isNotBlank()) {
put("need_shield_prefer", true)
put("key_word", query)
}
}
val requestBody = jsonPayload.toString().toRequestBody(JSON_MEDIA_TYPE)
val refererUrl = "$baseUrl/search".toHttpUrl().newBuilder()
.addQueryParameter("keyword", query)
.toString()
val refererUrl = if (query.isBlank()) "$baseUrl/genre" else
"$baseUrl/search".toHttpUrl().newBuilder()
.addQueryParameter("keyword", query)
.toString()
val newHeaders = headersBuilder()
.add("Content-Length", requestBody.contentLength().toString())
.add("Content-Type", requestBody.contentType().toString())
.add("X-Page", page.toString())
.set("Referer", refererUrl)
.build()
val apiPath = if (query.isBlank()) "ClassPage" else "Search"
return POST(
"$baseUrl/$BASE_API_ENDPOINT/Search?device=pc&platform=web",
"$baseUrl/$BASE_API_ENDPOINT/$apiPath?device=pc&platform=web",
headers = newHeaders,
body = requestBody
)
}
override fun searchMangaParse(response: Response): MangasPage {
if (response.request.url.toString().contains("ComicDetail")) {
val comic = mangaDetailsParse(response)
return MangasPage(listOf(comic), hasNextPage = false)
}
if (response.request.url.toString().contains("ClassPage")) {
val result = json.decodeFromString<BilibiliResultDto<List<BilibiliComicDto>>>(response.body!!.string())
if (result.code != 0) {
return MangasPage(emptyList(), hasNextPage = false)
}
val comicList = result.data!!.map(::searchMangaFromObject)
val hasNextPage = comicList.size == POPULAR_PER_PAGE
return MangasPage(comicList, hasNextPage)
}
val result = json.decodeFromString<BilibiliResultDto<BilibiliSearchDto>>(response.body!!.string())
if (result.code != 0) {
return MangasPage(emptyList(), hasNextPage = false)
}
val comicList = result.data!!.list
.map(::searchMangaFromObject)
val comicList = result.data!!.list.map(::searchMangaFromObject)
val hasNextPage = comicList.size == SEARCH_PER_PAGE
return MangasPage(comicList, hasNextPage = false)
return MangasPage(comicList, hasNextPage)
}
private fun searchMangaFromObject(comic: BilibiliComicDto): SManga = SManga.create().apply {
title = Jsoup.parse(comic.title).text()
thumbnail_url = comic.verticalCover
url = "/detail/mc${comic.id}"
val comicId = if (comic.id == 0) comic.seasonId else comic.id
url = "/detail/mc$comicId"
}
// Workaround to allow "Open in browser" use the real URL.
override fun fetchMangaDetails(manga: SManga): Observable<SManga> {
return client.newCall(mangaDetailsApiRequest(manga))
return client.newCall(mangaDetailsApiRequest(manga.url))
.asObservableSuccess()
.map { response ->
mangaDetailsParse(response).apply { initialized = true }
}
}
private fun mangaDetailsApiRequest(manga: SManga): Request {
val comicId = manga.url.substringAfterLast("/mc").toInt()
private fun mangaDetailsApiRequest(mangaUrl: String): Request {
val comicId = mangaUrl.substringAfterLast("/mc").toInt()
val jsonPayload = buildJsonObject { put("comic_id", comicId) }
val requestBody = jsonPayload.toString().toRequestBody(JSON_MEDIA_TYPE)
@ -217,26 +249,7 @@ class BilibiliComics : HttpSource() {
val newHeaders = headersBuilder()
.add("Content-Length", requestBody.contentLength().toString())
.add("Content-Type", requestBody.contentType().toString())
.set("Referer", baseUrl + manga.url)
.build()
return POST(
"$baseUrl/$BASE_API_ENDPOINT/ComicDetail?device=pc&platform=web",
headers = newHeaders,
body = requestBody
)
}
private fun mangaDetailsApiRequestById(id: String): Request {
val comicId = id.toInt()
val jsonPayload = buildJsonObject { put("comic_id", comicId) }
val requestBody = jsonPayload.toString().toRequestBody(JSON_MEDIA_TYPE)
val newHeaders = headersBuilder()
.add("Content-Length", requestBody.contentLength().toString())
.add("Content-Type", requestBody.contentType().toString())
.set("Referer", "$baseUrl/detail/mc$id")
.set("Referer", baseUrl + mangaUrl)
.build()
return POST(
@ -256,10 +269,11 @@ class BilibiliComics : HttpSource() {
genre = comic.styles.joinToString()
description = comic.classicLines
thumbnail_url = comic.verticalCover
url = "/detail/mc" + comic.id
}
// Chapters are available in the same url of the manga details.
override fun chapterListRequest(manga: SManga): Request = mangaDetailsApiRequest(manga)
override fun chapterListRequest(manga: SManga): Request = mangaDetailsApiRequest(manga.url)
override fun chapterListParse(response: Response): List<SChapter> {
val result = json.decodeFromString<BilibiliResultDto<BilibiliComicDto>>(response.body!!.string())
@ -336,6 +350,47 @@ class BilibiliComics : HttpSource() {
return "${page.url}?token=${page.token}"
}
private data class Genre(val name: String, val id: Int) {
override fun toString(): String = name
}
private class GenreFilter(genres: Array<Genre>) : Filter.Select<Genre>("Genre", genres) {
val selected: Genre
get() = values[state]
}
private class SortFilter(options: Array<String>) : Filter.Select<String>("Sort by", options)
private class StatusFilter(statuses: Array<String>) : Filter.Select<String>("Status", statuses)
private fun getAllGenres(): Array<Genre> = arrayOf(
Genre("All", -1),
Genre("Action", 19),
Genre("Adventure", 22),
Genre("BL", 3),
Genre("Comedy", 14),
Genre("Eastern", 30),
Genre("Fantasy", 11),
Genre("GL", 16),
Genre("Harem", 15),
Genre("Historical", 12),
Genre("Horror", 23),
Genre("Mistery", 17),
Genre("Romance", 13),
Genre("Slice of Life", 21),
Genre("Suspense", 41),
Genre("Teen", 20)
)
private fun getAllSortOptions(): Array<String> = arrayOf("Popular", "Updated")
private fun getAllStatus(): Array<String> = arrayOf("All", "Ongoing", "Completed")
override fun getFilterList(): FilterList = FilterList(
StatusFilter(getAllStatus()),
SortFilter(getAllSortOptions()),
GenreFilter(getAllGenres())
)
private fun String.toDate(): Long {
return try {
DATE_FORMATTER.parse(this)?.time ?: 0L
@ -344,19 +399,6 @@ class BilibiliComics : HttpSource() {
}
}
private val day: Int
get() {
return when (Calendar.getInstance().get(Calendar.DAY_OF_WEEK)) {
Calendar.SUNDAY -> 0
Calendar.MONDAY -> 1
Calendar.TUESDAY -> 2
Calendar.WEDNESDAY -> 3
Calendar.THURSDAY -> 4
Calendar.FRIDAY -> 5
else -> 6
}
}
companion object {
private const val BASE_API_ENDPOINT = "twirp/comic.v1.Comic"
@ -364,9 +406,11 @@ class BilibiliComics : HttpSource() {
private val JSON_MEDIA_TYPE = "application/json;charset=UTF-8".toMediaType()
private const val FEATURED_ID = 3
const val prefixIdSearch = "id:"
private const val POPULAR_PER_PAGE = 18
private const val SEARCH_PER_PAGE = 9
const val PREFIX_ID_SEARCH = "id:"
private val ID_SEARCH_PATTERN = "^id:(mc)?(\\d+)$".toRegex()
private val DATE_FORMATTER by lazy { SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH) }
}

View File

@ -20,12 +20,14 @@ class BilibiliComicsUrlActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val pathSegments = intent?.data?.pathSegments
if (pathSegments != null && pathSegments.size > 1) {
val titleid = pathSegments[1]
val titleId = pathSegments[1]
val mainIntent = Intent().apply {
action = "eu.kanade.tachiyomi.SEARCH"
putExtra("query", "${BilibiliComics.prefixIdSearch}${titleid.removePrefix("mc")}")
putExtra("query", BilibiliComics.PREFIX_ID_SEARCH + titleId)
putExtra("filter", packageName)
}
@ -35,7 +37,7 @@ class BilibiliComicsUrlActivity : Activity() {
Log.e("BilibiliUrlActivity", e.toString())
}
} else {
Log.e("BilibiliUrlActivity", "could not parse uri from intent $intent")
Log.e("BilibiliUrlActivity", "Could not parse URI from intent $intent")
}
finish()

View File

@ -33,6 +33,7 @@ data class BilibiliComicDto(
@SerialName("ep_list") val episodeList: List<BilibiliEpisodeDto> = emptyList(),
val id: Int = 0,
@SerialName("is_finish") val isFinish: Int = 0,
@SerialName("season_id") val seasonId: Int = 0,
val styles: List<String> = emptyList(),
val title: String,
@SerialName("vertical_cover") val verticalCover: String = ""