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 | ||||
|     | ||||
|         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) } | ||||
|     } | ||||
|  | ||||
| @ -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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Alessandro Jean
						Alessandro Jean