diff --git a/lib-multisrc/bilibili/build.gradle.kts b/lib-multisrc/bilibili/build.gradle.kts deleted file mode 100644 index d1d9afd6e..000000000 --- a/lib-multisrc/bilibili/build.gradle.kts +++ /dev/null @@ -1,5 +0,0 @@ -plugins { - id("lib-multisrc") -} - -baseVersionCode = 9 diff --git a/src/all/bilibilicomics/AndroidManifest.xml b/src/all/bilibilicomics/AndroidManifest.xml deleted file mode 100644 index b12ae7a39..000000000 --- a/src/all/bilibilicomics/AndroidManifest.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/all/bilibilicomics/build.gradle b/src/all/bilibilicomics/build.gradle deleted file mode 100644 index 362a0d24a..000000000 --- a/src/all/bilibilicomics/build.gradle +++ /dev/null @@ -1,9 +0,0 @@ -ext { - extName = 'BILIBILI COMICS' - extClass = '.BilibiliComicsFactory' - themePkg = 'bilibili' - baseUrl = 'https://www.bilibilicomics.com' - overrideVersionCode = 3 -} - -apply from: "$rootDir/common.gradle" diff --git a/src/all/bilibilicomics/src/eu/kanade/tachiyomi/extension/all/bilibilicomics/BilibiliComicsFactory.kt b/src/all/bilibilicomics/src/eu/kanade/tachiyomi/extension/all/bilibilicomics/BilibiliComicsFactory.kt deleted file mode 100644 index c7b2234e8..000000000 --- a/src/all/bilibilicomics/src/eu/kanade/tachiyomi/extension/all/bilibilicomics/BilibiliComicsFactory.kt +++ /dev/null @@ -1,411 +0,0 @@ -package eu.kanade.tachiyomi.extension.all.bilibilicomics - -import eu.kanade.tachiyomi.multisrc.bilibili.Bilibili -import eu.kanade.tachiyomi.multisrc.bilibili.BilibiliAccessToken -import eu.kanade.tachiyomi.multisrc.bilibili.BilibiliAccessTokenCookie -import eu.kanade.tachiyomi.multisrc.bilibili.BilibiliComicDto -import eu.kanade.tachiyomi.multisrc.bilibili.BilibiliCredential -import eu.kanade.tachiyomi.multisrc.bilibili.BilibiliGetCredential -import eu.kanade.tachiyomi.multisrc.bilibili.BilibiliIntl -import eu.kanade.tachiyomi.multisrc.bilibili.BilibiliSearchDto -import eu.kanade.tachiyomi.multisrc.bilibili.BilibiliTag -import eu.kanade.tachiyomi.multisrc.bilibili.BilibiliUnlockedEpisode -import eu.kanade.tachiyomi.multisrc.bilibili.BilibiliUserEpisodes -import eu.kanade.tachiyomi.network.POST -import eu.kanade.tachiyomi.source.SourceFactory -import eu.kanade.tachiyomi.source.model.MangasPage -import eu.kanade.tachiyomi.source.model.Page -import eu.kanade.tachiyomi.source.model.SChapter -import eu.kanade.tachiyomi.source.model.SManga -import kotlinx.serialization.decodeFromString -import kotlinx.serialization.encodeToString -import kotlinx.serialization.json.buildJsonObject -import kotlinx.serialization.json.put -import okhttp3.HttpUrl -import okhttp3.HttpUrl.Companion.toHttpUrl -import okhttp3.Interceptor -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.RequestBody.Companion.toRequestBody -import okhttp3.Response -import okio.Buffer -import java.io.IOException -import java.net.URLDecoder -import java.util.Calendar - -class BilibiliComicsFactory : SourceFactory { - override fun createSources() = listOf( - BilibiliComicsEn(), - BilibiliComicsCn(), - BilibiliComicsId(), - BilibiliComicsEs(), - BilibiliComicsFr(), - ) -} - -abstract class BilibiliComics(lang: String) : Bilibili( - "BILIBILI COMICS", - "https://www.bilibilicomics.com", - lang, -) { - - override val client: OkHttpClient = super.client.newBuilder() - .apply { interceptors().add(0, Interceptor { chain -> signedInIntercept(chain) }) } - .build() - - init { - setAccessTokenCookie(baseUrl.toHttpUrl()) - } - - override val signedIn: Boolean - get() = accessTokenCookie != null - - private val globalApiSubDomain: String - get() = GLOBAL_API_SUBDOMAINS[(accessTokenCookie?.area?.toIntOrNull() ?: 1) - 1] - - private val globalApiBaseUrl: String - get() = "https://$globalApiSubDomain.bilibilicomics.com" - - private var accessTokenCookie: BilibiliAccessTokenCookie? = null - - private val dayOfWeek: Int - get() = Calendar.getInstance().get(Calendar.DAY_OF_WEEK) - 1 - - override fun latestUpdatesRequest(page: Int): Request { - val jsonPayload = buildJsonObject { put("day", dayOfWeek) } - 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/schedule") - .build() - - val apiUrl = "$baseUrl/$API_COMIC_V1_COMIC_ENDPOINT/GetSchedule".toHttpUrl().newBuilder() - .addCommonParameters() - .toString() - - return POST(apiUrl, newHeaders, requestBody) - } - - override fun latestUpdatesParse(response: Response): MangasPage { - val result = response.parseAs() - - if (result.code != 0) { - return MangasPage(emptyList(), hasNextPage = false) - } - - val comicList = result.data!!.list.map(::latestMangaFromObject) - - return MangasPage(comicList, hasNextPage = false) - } - - protected open fun latestMangaFromObject(comic: BilibiliComicDto): SManga = SManga.create().apply { - title = comic.title - thumbnail_url = comic.verticalCover + THUMBNAIL_RESOLUTION - url = "/detail/mc${comic.comicId}" - } - - override fun chapterListParse(response: Response): List { - if (!signedIn) { - return super.chapterListParse(response) - } - - val result = response.parseAs() - - if (result.code != 0) { - return emptyList() - } - - val comic = result.data!! - - val userEpisodesRequest = userEpisodesRequest(comic.id) - val userEpisodesResponse = client.newCall(userEpisodesRequest).execute() - val unlockedEpisodes = userEpisodesParse(userEpisodesResponse) - - return comic.episodeList.map { ep -> chapterFromObject(ep, comic.id, isUnlocked = ep.id in unlockedEpisodes) } - } - - private fun userEpisodesRequest(comicId: Int): Request { - val jsonPayload = buildJsonObject { put("comic_id", comicId) } - val requestBody = jsonPayload.toString().toRequestBody(JSON_MEDIA_TYPE) - - val newHeaders = headersBuilder() - .set("Referer", baseUrl) - .build() - - val apiUrl = "$globalApiBaseUrl/$API_COMIC_V1_USER_ENDPOINT/GetUserEpisodes".toHttpUrl() - .newBuilder() - .addCommonParameters() - .toString() - - return POST(apiUrl, newHeaders, requestBody) - } - - private fun userEpisodesParse(response: Response): List { - if (!response.isSuccessful) { - return emptyList() - } - - val result = response.parseAs() - - if (result.code != 0) { - return emptyList() - } - - return result.data!!.unlockedEpisodes.orEmpty() - .map(BilibiliUnlockedEpisode::id) - } - - override fun pageListRequest(chapter: SChapter): Request { - if (!signedIn) { - return super.pageListRequest(chapter) - } - - val chapterPaths = (baseUrl + chapter.url).toHttpUrl().pathSegments - val comicId = chapterPaths[0].removePrefix("mc").toInt() - val episodeId = chapterPaths[1].toInt() - - val jsonPayload = BilibiliGetCredential(comicId, episodeId, 1) - val requestBody = json.encodeToString(jsonPayload).toRequestBody(JSON_MEDIA_TYPE) - - val newHeaders = headersBuilder() - .set("Referer", baseUrl + chapter.url) - .build() - - val apiUrl = "$globalApiBaseUrl/$API_GLOBAL_V1_USER_ENDPOINT/GetCredential".toHttpUrl() - .newBuilder() - .addCommonParameters() - .toString() - - return POST(apiUrl, newHeaders, requestBody) - } - - override fun pageListParse(response: Response): List { - if (!signedIn) { - return super.pageListParse(response) - } - - if (!response.isSuccessful) { - throw Exception(intl.failedToGetCredential) - } - - val result = response.parseAs() - val credential = result.data?.credential ?: "" - - val requestPayload = response.request.bodyString - val credentialInfo = json.decodeFromString(requestPayload) - val chapterUrl = "/mc${credentialInfo.comicId}/${credentialInfo.episodeId}" - - val imageIndexRequest = imageIndexRequest(chapterUrl, credential) - val imageIndexResponse = client.newCall(imageIndexRequest).execute() - - return super.pageListParse(imageIndexResponse) - } - - private fun setAccessTokenCookie(url: HttpUrl) { - val authCookie = client.cookieJar.loadForRequest(url) - .firstOrNull { cookie -> cookie.name == ACCESS_TOKEN_COOKIE_NAME } - ?.let { cookie -> URLDecoder.decode(cookie.value, "UTF-8") } - ?.let { jsonString -> json.decodeFromString(jsonString) } - - if (accessTokenCookie == null) { - accessTokenCookie = authCookie - } else if (authCookie == null) { - accessTokenCookie = null - } - } - - private fun signedInIntercept(chain: Interceptor.Chain): Response { - var request = chain.request() - val requestUrl = request.url.toString() - - if (!requestUrl.contains("bilibilicomics.com")) { - return chain.proceed(request) - } - - setAccessTokenCookie(request.url) - - if (!accessTokenCookie?.accessToken.isNullOrEmpty()) { - request = request.newBuilder() - .addHeader("Authorization", "Bearer ${accessTokenCookie!!.accessToken}") - .build() - } - - val response = chain.proceed(request) - - // Try to refresh the token if it expired. - if (response.code == 401 && !accessTokenCookie?.refreshToken.isNullOrEmpty()) { - response.close() - - val refreshTokenRequest = refreshTokenRequest( - accessTokenCookie!!.accessToken, - accessTokenCookie!!.refreshToken, - ) - val refreshTokenResponse = chain.proceed(refreshTokenRequest) - - accessTokenCookie = refreshTokenParse(refreshTokenResponse) - refreshTokenResponse.close() - - request = request.newBuilder() - .header("Authorization", "Bearer ${accessTokenCookie!!.accessToken}") - .build() - return chain.proceed(request) - } - - return response - } - - private fun refreshTokenRequest(accessToken: String, refreshToken: String): Request { - val jsonPayload = buildJsonObject { put("refresh_token", refreshToken) } - val requestBody = jsonPayload.toString().toRequestBody(JSON_MEDIA_TYPE) - - val newHeaders = headersBuilder() - .add("Authorization", "Bearer $accessToken") - .set("Referer", baseUrl) - .build() - - val apiUrl = "$globalApiBaseUrl/$API_GLOBAL_V1_USER_ENDPOINT/RefreshToken".toHttpUrl() - .newBuilder() - .addCommonParameters() - .toString() - - return POST(apiUrl, newHeaders, requestBody) - } - - private fun refreshTokenParse(response: Response): BilibiliAccessTokenCookie { - if (!response.isSuccessful) { - throw IOException(intl.failedToRefreshToken) - } - - val result = response.parseAs() - - if (result.code != 0) { - throw IOException(intl.failedToRefreshToken) - } - - val accessToken = result.data!! - - return BilibiliAccessTokenCookie( - accessToken.accessToken, - accessToken.refreshToken, - accessTokenCookie!!.area, - ) - } - - private val Request.bodyString: String - get() { - val requestCopy = newBuilder().build() - val buffer = Buffer() - - return runCatching { buffer.apply { requestCopy.body!!.writeTo(this) }.readUtf8() } - .getOrNull() ?: "" - } - - companion object { - private const val ACCESS_TOKEN_COOKIE_NAME = "access_token" - - private val GLOBAL_API_SUBDOMAINS = arrayOf("us-user", "sg-user") - private const val API_GLOBAL_V1_USER_ENDPOINT = "twirp/global.v1.User" - private const val API_COMIC_V1_USER_ENDPOINT = "twirp/comic.v1.User" - } -} - -class BilibiliComicsEn : BilibiliComics(BilibiliIntl.ENGLISH) { - - override fun getAllGenres(): Array = arrayOf( - BilibiliTag("All", -1), - BilibiliTag("Action", 19), - BilibiliTag("Adventure", 22), - BilibiliTag("BL", 3), - BilibiliTag("Comedy", 14), - BilibiliTag("Eastern", 30), - BilibiliTag("Fantasy", 11), - BilibiliTag("GL", 16), - BilibiliTag("Harem", 15), - BilibiliTag("Historical", 12), - BilibiliTag("Horror", 23), - BilibiliTag("Mistery", 17), - BilibiliTag("Romance", 13), - BilibiliTag("Slice of Life", 21), - BilibiliTag("Suspense", 41), - BilibiliTag("Teen", 20), - ) -} - -class BilibiliComicsCn : BilibiliComics(BilibiliIntl.SIMPLIFIED_CHINESE) { - - override fun getAllGenres(): Array = arrayOf( - BilibiliTag("全部", -1), - BilibiliTag("校园", 18), - BilibiliTag("都市", 9), - BilibiliTag("耽美", 3), - BilibiliTag("少女", 20), - BilibiliTag("恋爱", 13), - BilibiliTag("奇幻", 11), - BilibiliTag("热血", 19), - BilibiliTag("冒险", 22), - BilibiliTag("古风", 12), - BilibiliTag("百合", 16), - BilibiliTag("玄幻", 30), - BilibiliTag("悬疑", 41), - BilibiliTag("科幻", 8), - ) -} - -class BilibiliComicsId : BilibiliComics(BilibiliIntl.INDONESIAN) { - - override fun getAllGenres(): Array = arrayOf( - BilibiliTag("Semua", -1), - BilibiliTag("Aksi", 19), - BilibiliTag("Fantasi Timur", 30), - BilibiliTag("Fantasi", 11), - BilibiliTag("Historis", 12), - BilibiliTag("Horror", 23), - BilibiliTag("Kampus", 18), - BilibiliTag("Komedi", 14), - BilibiliTag("Menegangkan", 41), - BilibiliTag("Remaja", 20), - BilibiliTag("Romantis", 13), - ) -} - -class BilibiliComicsEs : BilibiliComics(BilibiliIntl.SPANISH) { - - override fun getAllGenres(): Array = arrayOf( - BilibiliTag("Todos", -1), - BilibiliTag("Adolescencia", 105), - BilibiliTag("BL", 3), - BilibiliTag("Ciberdeportes", 104), - BilibiliTag("Ciencia ficción", 8), - BilibiliTag("Comedia", 14), - BilibiliTag("Fantasía occidental", 106), - BilibiliTag("Fantasía", 11), - BilibiliTag("Ficción Realista", 116), - BilibiliTag("GL", 16), - BilibiliTag("Histórico", 12), - BilibiliTag("Horror", 23), - BilibiliTag("Juvenil", 20), - BilibiliTag("Moderno", 111), - BilibiliTag("Oriental", 30), - BilibiliTag("Romance", 13), - BilibiliTag("Suspenso", 41), - BilibiliTag("Urbano", 9), - BilibiliTag("Wuxia", 103), - ) -} - -class BilibiliComicsFr : BilibiliComics(BilibiliIntl.FRENCH) { - - override fun getAllGenres(): Array = arrayOf( - BilibiliTag("Tout", -1), - BilibiliTag("BL", 3), - BilibiliTag("Science Fiction", 8), - BilibiliTag("Historique", 12), - BilibiliTag("Romance", 13), - BilibiliTag("GL", 16), - BilibiliTag("Fantasy Orientale", 30), - BilibiliTag("Suspense", 41), - BilibiliTag("Moderne", 111), - ) -} diff --git a/lib-multisrc/bilibili/README.md b/src/zh/bilibilimanga/README.md similarity index 100% rename from lib-multisrc/bilibili/README.md rename to src/zh/bilibilimanga/README.md diff --git a/src/zh/bilibilimanga/build.gradle b/src/zh/bilibilimanga/build.gradle index 85f7bb308..11e71b2e6 100644 --- a/src/zh/bilibilimanga/build.gradle +++ b/src/zh/bilibilimanga/build.gradle @@ -1,9 +1,7 @@ ext { extName = 'BILIBILI MANGA' extClass = '.BilibiliManga' - themePkg = 'bilibili' - baseUrl = 'https://manga.bilibili.com' - overrideVersionCode = 2 + extVersionCode = 11 } apply from: "$rootDir/common.gradle" diff --git a/lib-multisrc/bilibili/src/eu/kanade/tachiyomi/multisrc/bilibili/Bilibili.kt b/src/zh/bilibilimanga/src/eu/kanade/tachiyomi/multisrc/bilibili/Bilibili.kt similarity index 100% rename from lib-multisrc/bilibili/src/eu/kanade/tachiyomi/multisrc/bilibili/Bilibili.kt rename to src/zh/bilibilimanga/src/eu/kanade/tachiyomi/multisrc/bilibili/Bilibili.kt diff --git a/lib-multisrc/bilibili/src/eu/kanade/tachiyomi/multisrc/bilibili/BilibiliDto.kt b/src/zh/bilibilimanga/src/eu/kanade/tachiyomi/multisrc/bilibili/BilibiliDto.kt similarity index 100% rename from lib-multisrc/bilibili/src/eu/kanade/tachiyomi/multisrc/bilibili/BilibiliDto.kt rename to src/zh/bilibilimanga/src/eu/kanade/tachiyomi/multisrc/bilibili/BilibiliDto.kt diff --git a/lib-multisrc/bilibili/src/eu/kanade/tachiyomi/multisrc/bilibili/BilibiliFilters.kt b/src/zh/bilibilimanga/src/eu/kanade/tachiyomi/multisrc/bilibili/BilibiliFilters.kt similarity index 100% rename from lib-multisrc/bilibili/src/eu/kanade/tachiyomi/multisrc/bilibili/BilibiliFilters.kt rename to src/zh/bilibilimanga/src/eu/kanade/tachiyomi/multisrc/bilibili/BilibiliFilters.kt diff --git a/lib-multisrc/bilibili/src/eu/kanade/tachiyomi/multisrc/bilibili/BilibiliIntl.kt b/src/zh/bilibilimanga/src/eu/kanade/tachiyomi/multisrc/bilibili/BilibiliIntl.kt similarity index 100% rename from lib-multisrc/bilibili/src/eu/kanade/tachiyomi/multisrc/bilibili/BilibiliIntl.kt rename to src/zh/bilibilimanga/src/eu/kanade/tachiyomi/multisrc/bilibili/BilibiliIntl.kt diff --git a/lib-multisrc/bilibili/src/eu/kanade/tachiyomi/multisrc/bilibili/BilibiliUrlActivity.kt b/src/zh/bilibilimanga/src/eu/kanade/tachiyomi/multisrc/bilibili/BilibiliUrlActivity.kt similarity index 100% rename from lib-multisrc/bilibili/src/eu/kanade/tachiyomi/multisrc/bilibili/BilibiliUrlActivity.kt rename to src/zh/bilibilimanga/src/eu/kanade/tachiyomi/multisrc/bilibili/BilibiliUrlActivity.kt