diff --git a/src/en/bilibilicomics/AndroidManifest.xml b/src/en/bilibilicomics/AndroidManifest.xml
index afba42cfd..000affda9 100644
--- a/src/en/bilibilicomics/AndroidManifest.xml
+++ b/src/en/bilibilicomics/AndroidManifest.xml
@@ -15,12 +15,22 @@
+
+
+
+
diff --git a/src/en/bilibilicomics/build.gradle b/src/en/bilibilicomics/build.gradle
index 1f8bec484..3fb086edc 100644
--- a/src/en/bilibilicomics/build.gradle
+++ b/src/en/bilibilicomics/build.gradle
@@ -6,7 +6,7 @@ ext {
extName = 'Bilibili Comics'
pkgNameSuffix = 'en.bilibilicomics'
extClass = '.BilibiliComics'
- extVersionCode = 5
+ extVersionCode = 6
libVersion = '1.2'
containsNsfw = true
}
diff --git a/src/en/bilibilicomics/src/eu/kanade/tachiyomi/extension/en/bilibilicomics/BilibiliComics.kt b/src/en/bilibilicomics/src/eu/kanade/tachiyomi/extension/en/bilibilicomics/BilibiliComics.kt
index 1bdcea896..516be044a 100644
--- a/src/en/bilibilicomics/src/eu/kanade/tachiyomi/extension/en/bilibilicomics/BilibiliComics.kt
+++ b/src/en/bilibilicomics/src/eu/kanade/tachiyomi/extension/en/bilibilicomics/BilibiliComics.kt
@@ -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>(response.body!!.string())
+ val result = json.decodeFromString>>(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 {
- 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()
+ .firstOrNull()?.state ?: 0
+
+ val status = filters.filterIsInstance()
+ .firstOrNull()?.state?.minus(1) ?: -1
+
+ val styleId = filters.filterIsInstance()
+ .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>>(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>(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 {
- 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 {
val result = json.decodeFromString>(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) : Filter.Select("Genre", genres) {
+ val selected: Genre
+ get() = values[state]
+ }
+
+ private class SortFilter(options: Array) : Filter.Select("Sort by", options)
+ private class StatusFilter(statuses: Array) : Filter.Select("Status", statuses)
+
+ private fun getAllGenres(): Array = 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 = arrayOf("Popular", "Updated")
+
+ private fun getAllStatus(): Array = 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) }
}
diff --git a/src/en/bilibilicomics/src/eu/kanade/tachiyomi/extension/en/bilibilicomics/BilibiliComicsUrlActivity.kt b/src/en/bilibilicomics/src/eu/kanade/tachiyomi/extension/en/bilibilicomics/BilibiliComicsUrlActivity.kt
index d57d5db71..03e07c713 100644
--- a/src/en/bilibilicomics/src/eu/kanade/tachiyomi/extension/en/bilibilicomics/BilibiliComicsUrlActivity.kt
+++ b/src/en/bilibilicomics/src/eu/kanade/tachiyomi/extension/en/bilibilicomics/BilibiliComicsUrlActivity.kt
@@ -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()
diff --git a/src/en/bilibilicomics/src/eu/kanade/tachiyomi/extension/en/bilibilicomics/BilibiliDto.kt b/src/en/bilibilicomics/src/eu/kanade/tachiyomi/extension/en/bilibilicomics/BilibiliDto.kt
index f8e6d934d..11bf5cb6f 100644
--- a/src/en/bilibilicomics/src/eu/kanade/tachiyomi/extension/en/bilibilicomics/BilibiliDto.kt
+++ b/src/en/bilibilicomics/src/eu/kanade/tachiyomi/extension/en/bilibilicomics/BilibiliDto.kt
@@ -33,6 +33,7 @@ data class BilibiliComicDto(
@SerialName("ep_list") val episodeList: List = emptyList(),
val id: Int = 0,
@SerialName("is_finish") val isFinish: Int = 0,
+ @SerialName("season_id") val seasonId: Int = 0,
val styles: List = emptyList(),
val title: String,
@SerialName("vertical_cover") val verticalCover: String = ""