Fix URL intent crash and add filters to Bilibili. (#8423)
This commit is contained in:
parent
47e0b4eee8
commit
c4c10cdb32
|
@ -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>
|
||||
|
|
|
@ -6,7 +6,7 @@ ext {
|
|||
extName = 'Bilibili Comics'
|
||||
pkgNameSuffix = 'en.bilibilicomics'
|
||||
extClass = '.BilibiliComics'
|
||||
extVersionCode = 5
|
||||
extVersionCode = 6
|
||||
libVersion = '1.2'
|
||||
containsNsfw = true
|
||||
}
|
||||
|
|
|
@ -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
|
||||
private const val POPULAR_PER_PAGE = 18
|
||||
private const val SEARCH_PER_PAGE = 9
|
||||
|
||||
const val prefixIdSearch = "id:"
|
||||
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) }
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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 = ""
|
||||
|
|
Loading…
Reference in New Issue