From 075d880f535dd044142c44715bc25725807d65e4 Mon Sep 17 00:00:00 2001 From: czp3009 Date: Thu, 21 Feb 2019 00:09:25 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 11 +- .../kotlin/com/hiczp/bilibili/api/BaseUrl.kt | 5 + .../com/hiczp/bilibili/api/BilibiliClient.kt | 75 +-- .../com/hiczp/bilibili/api/app/AppAPI.kt | 79 +++- .../app/model/{IndexPage.kt => HomePage.kt} | 2 +- .../hiczp/bilibili/api/app/model/PlayUrl.kt | 80 ++++ .../bilibili/api/app/model/PopularPage.kt | 88 ++++ .../com/hiczp/bilibili/api/app/model/View.kt | 446 ++++++++++++++++++ .../hiczp/bilibili/api/member/MemberAPI.kt | 18 + .../hiczp/bilibili/api/member/model/Pre.kt | 78 +++ 10 files changed, 838 insertions(+), 44 deletions(-) rename src/main/kotlin/com/hiczp/bilibili/api/app/model/{IndexPage.kt => HomePage.kt} (99%) create mode 100644 src/main/kotlin/com/hiczp/bilibili/api/app/model/PlayUrl.kt create mode 100644 src/main/kotlin/com/hiczp/bilibili/api/app/model/PopularPage.kt create mode 100644 src/main/kotlin/com/hiczp/bilibili/api/app/model/View.kt create mode 100644 src/main/kotlin/com/hiczp/bilibili/api/member/MemberAPI.kt create mode 100644 src/main/kotlin/com/hiczp/bilibili/api/member/model/Pre.kt diff --git a/README.md b/README.md index aedac95..d31acfd 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ https://app.bilibili.com BilibiliClient().appAPI -为 app 提供通用接口, 例如获取个人信息. 完整示例如下 +总站 API. 获取个人信息的完整示例如下: runBlocking { val bilibiliClient = BilibiliClient().apply { @@ -70,7 +70,14 @@ https://api.vc.bilibili.com BilibiliClient().vcAPI -小视频有关的接口. +小视频. + +# member +https://member.bilibili.com + + BilibiliClient().memberAPI + +创作中心. # License GPL V3 diff --git a/src/main/kotlin/com/hiczp/bilibili/api/BaseUrl.kt b/src/main/kotlin/com/hiczp/bilibili/api/BaseUrl.kt index f9badc7..c6f7ff8 100644 --- a/src/main/kotlin/com/hiczp/bilibili/api/BaseUrl.kt +++ b/src/main/kotlin/com/hiczp/bilibili/api/BaseUrl.kt @@ -24,6 +24,11 @@ object BaseUrl { */ const val vc = "https://api.vc.bilibili.com" + /** + * 创作中心 + */ + const val member = "https://member.bilibili.com" + /** * 直播 */ diff --git a/src/main/kotlin/com/hiczp/bilibili/api/BilibiliClient.kt b/src/main/kotlin/com/hiczp/bilibili/api/BilibiliClient.kt index 6d6afe0..2336576 100644 --- a/src/main/kotlin/com/hiczp/bilibili/api/BilibiliClient.kt +++ b/src/main/kotlin/com/hiczp/bilibili/api/BilibiliClient.kt @@ -1,6 +1,7 @@ package com.hiczp.bilibili.api import com.hiczp.bilibili.api.app.AppAPI +import com.hiczp.bilibili.api.member.MemberAPI import com.hiczp.bilibili.api.message.MessageAPI import com.hiczp.bilibili.api.passport.PassportAPI import com.hiczp.bilibili.api.passport.model.LoginResponse @@ -84,15 +85,15 @@ class BilibiliClient( "ts" to { Instant.now().epochSecond.toString() } ) + private val defaultCommonQueryParamInterceptor = CommonParamInterceptor(ParamType.QUERY, *defaultCommonParamArray) private val defaultQuerySignInterceptor = SortAndSignInterceptor(ParamType.QUERY, billingClientProperties.appSecret) - private val defaultFormSignInterceptor = SortAndSignInterceptor(ParamType.FORM_URL_ENCODED, billingClientProperties.appSecret) /** * 用户鉴权相关的接口 */ @Suppress("SpellCheckingInspection") val passportAPI by lazy { - createAPI(BaseUrl.passport, logLevel, + createAPI(BaseUrl.passport, defaultCommonHeaderInterceptor, CommonParamInterceptor(ParamType.FORM_URL_ENCODED, "appkey" to { billingClientProperties.appKey }, @@ -102,7 +103,7 @@ class BilibiliClient( "platform" to { billingClientProperties.platform }, "ts" to { Instant.now().epochSecond.toString() } ), - defaultFormSignInterceptor + SortAndSignInterceptor(ParamType.FORM_URL_ENCODED, billingClientProperties.appSecret) ) } @@ -111,7 +112,7 @@ class BilibiliClient( */ @Suppress("SpellCheckingInspection") val messageAPI by lazy { - createAPI(BaseUrl.message, logLevel, + createAPI(BaseUrl.message, defaultCommonHeaderInterceptor, CommonParamInterceptor(ParamType.QUERY, *defaultCommonParamArray, "actionKey" to { "appkey" }, @@ -122,13 +123,13 @@ class BilibiliClient( } /** - * 提供一些通用信息 + * 总站 API */ @Suppress("SpellCheckingInspection") val appAPI by lazy { - createAPI(BaseUrl.app, logLevel, + createAPI(BaseUrl.app, defaultCommonHeaderInterceptor, - CommonParamInterceptor(ParamType.QUERY, *defaultCommonParamArray), + defaultCommonQueryParamInterceptor, defaultQuerySignInterceptor ) } @@ -138,7 +139,7 @@ class BilibiliClient( */ @Suppress("SpellCheckingInspection") val vcAPI by lazy { - createAPI(BaseUrl.vc, logLevel, + createAPI(BaseUrl.vc, defaultCommonHeaderInterceptor, CommonParamInterceptor(ParamType.QUERY, *defaultCommonParamArray, "_device" to { billingClientProperties.platform }, @@ -152,6 +153,17 @@ class BilibiliClient( ) } + /** + * 创作中心 + */ + val memberAPI by lazy { + createAPI(BaseUrl.member, + defaultCommonHeaderInterceptor, + defaultCommonQueryParamInterceptor, + defaultQuerySignInterceptor + ) + } + /** * 登陆 * v3 登陆接口会同时返回 cookies 和 token @@ -207,32 +219,31 @@ class BilibiliClient( loginResponse = null } + private inline fun createAPI( + baseUrl: String, + vararg interceptors: Interceptor + ) = Retrofit.Builder() + .baseUrl(baseUrl) + .addConverterFactory(gsonConverterFactory) + .addCallAdapterFactory(coroutineCallAdapterFactory) + .client(OkHttpClient.Builder().apply { + interceptors.forEach { + addInterceptor(it) + } + addInterceptor(FailureResponseInterceptor) + //log + if (logLevel != HttpLoggingInterceptor.Level.NONE) { + addNetworkInterceptor(HttpLoggingInterceptor().setLevel(logLevel)) + } + }.build()) + .build() + .create(T::class.java) + companion object { + @Suppress("SpellCheckingInspection") + private val gsonConverterFactory = GsonConverterFactory.create() + private val coroutineCallAdapterFactory = CoroutineCallAdapterFactory() private val traceIdFormat = SimpleDateFormat("yyyyMMddHHmm000ss") private fun generateTraceId() = traceIdFormat.format(Date()) } } - -@Suppress("SpellCheckingInspection") -private val gsonConverterFactory = GsonConverterFactory.create() -private val coroutineCallAdapterFactory = CoroutineCallAdapterFactory() -private inline fun createAPI( - baseUrl: String, - logLevel: HttpLoggingInterceptor.Level = HttpLoggingInterceptor.Level.NONE, - vararg interceptors: Interceptor -) = Retrofit.Builder() - .baseUrl(baseUrl) - .addConverterFactory(gsonConverterFactory) - .addCallAdapterFactory(coroutineCallAdapterFactory) - .client(OkHttpClient.Builder().apply { - interceptors.forEach { - addInterceptor(it) - } - addInterceptor(FailureResponseInterceptor) - //log - if (logLevel != HttpLoggingInterceptor.Level.NONE) { - addNetworkInterceptor(HttpLoggingInterceptor().setLevel(logLevel)) - } - }.build()) - .build() - .create(T::class.java) diff --git a/src/main/kotlin/com/hiczp/bilibili/api/app/AppAPI.kt b/src/main/kotlin/com/hiczp/bilibili/api/app/AppAPI.kt index b73f449..01d6f07 100644 --- a/src/main/kotlin/com/hiczp/bilibili/api/app/AppAPI.kt +++ b/src/main/kotlin/com/hiczp/bilibili/api/app/AppAPI.kt @@ -1,16 +1,14 @@ package com.hiczp.bilibili.api.app -import com.hiczp.bilibili.api.app.model.IndexPage -import com.hiczp.bilibili.api.app.model.Mine -import com.hiczp.bilibili.api.app.model.MyInfo -import com.hiczp.bilibili.api.app.model.Sidebar +import com.hiczp.bilibili.api.app.model.* import kotlinx.coroutines.Deferred import retrofit2.http.GET import retrofit2.http.Query import java.time.Instant +import java.util.* /** - * 提供通用信息的接口 + * 总站 API */ @Suppress("DeferredIsResult") interface AppAPI { @@ -34,12 +32,12 @@ interface AppAPI { fun sidebar(): Deferred /** - * 首页内容 + * 首页内容(客户端通过解析返回的内容来生成页面内容, 下同) * 首页 -> 推荐 */ @Suppress("SpellCheckingInspection") @GET("/x/v2/feed/index") - fun index( + fun homePage( @Query("ad_extra") adExtra: String? = null, @Query("autoplay_card") autoplayCard: Int = 0, @Query("banner_hash") bannerHash: String? = null, @@ -50,11 +48,74 @@ interface AppAPI { @Query("fnver") fnVer: Int = 0, @Query("force_host") forceHost: Int = 0, @Query("idx") index: Long = Instant.now().epochSecond, - @Query("login_event") loginEvent: Int = 2, + @Query("login_event") loginEvent: Int = 0, @Query("network") network: String = "mobile", @Query("open_event") openEvent: String? = null, @Query("pull") pull: Boolean = true, @Query("qn") qn: Int = 32, @Query("recsys_mode") recsysMode: Int = 0 - ): Deferred + ): Deferred + + /** + * 热门页面 + * 首页 -> 热门 + */ + @GET("/x/v2/show/popular/index") + fun popularPage( + @Query("fnval") fnVal: Int = 16, + @Query("fnver") fnVer: Int = 0, + @Query("force_host") forceHost: Int = 0, + @Query("idx") index: Long = 0, + @Query("last_param") lastParam: String? = null, + @Query("login_event") loginEvent: Int = 0, + @Query("qn") qn: Int = 32, + @Query("ver") ver: Long? = null //ver 的值为上一次请求该接口时的 timestamp-1 + ): Deferred + + /** + * 视频页面 + * 包含视频基本信息, 推荐和广告 + * + * @param aid 视频的唯一标识 + */ + @Suppress("SpellCheckingInspection") + @GET("/x/v2/view") + fun view( + @Query("ad_extra") adExtra: String? = null, + @Query("aid") aid: Long, + @Query("autoplay") autoplay: Int = 0, + @Query("fnval") fnVal: Int = 16, + @Query("fnver") fnVer: Int = 0, + @Query("force_host") forceHost: Int = 0, + @Query("from") from: Int? = null, + @Query("plat") plat: Int = 0, + @Query("qn") qn: Int = 32, + @Query("trackid") trackId: String? = null //all_10.shylf-ai-recsys-120.1550674524909.237 + ): Deferred + + //TODO 这里的 appkey 变为 iVGUTjsxvpLeuDCf + /** + * 获得视频的播放地址 + * + * @param expire 默认为下个月的这一天的时间戳 + * @param mid 当前用户 ID + * @param cid 在 view() 接口的返回值里 + * @param aid 视频的唯一标识 + */ + @Suppress("SpellCheckingInspection") + @GET("/x/playurl") + fun playUrl( + @Query("device") device: String = "android", + @Query("expire") expire: Long = Calendar.getInstance().apply { add(Calendar.MONTH, 1) }.toInstant().epochSecond, + @Query("force_host") forceHost: Int = 0, + @Query("mid") mid: Long? = null, + @Query("fnval") fnVal: Int = 16, + @Query("qn") qn: Int = 32, + @Query("npcybs") npcybs: Int = 0, + @Query("cid") cid: Long? = null, + @Query("otype") otype: String = "json", + @Query("fnver") fnVer: Int = 0, + @Query("buvid") buildVersionId: String? = null, + @Query("aid") aid: Long + ): Deferred } diff --git a/src/main/kotlin/com/hiczp/bilibili/api/app/model/IndexPage.kt b/src/main/kotlin/com/hiczp/bilibili/api/app/model/HomePage.kt similarity index 99% rename from src/main/kotlin/com/hiczp/bilibili/api/app/model/IndexPage.kt rename to src/main/kotlin/com/hiczp/bilibili/api/app/model/HomePage.kt index 7bd8645..2c66e2d 100644 --- a/src/main/kotlin/com/hiczp/bilibili/api/app/model/IndexPage.kt +++ b/src/main/kotlin/com/hiczp/bilibili/api/app/model/HomePage.kt @@ -2,7 +2,7 @@ package com.hiczp.bilibili.api.app.model import com.google.gson.annotations.SerializedName -data class IndexPage( +data class HomePage( @SerializedName("code") var code: Int, // 0 @SerializedName("data") diff --git a/src/main/kotlin/com/hiczp/bilibili/api/app/model/PlayUrl.kt b/src/main/kotlin/com/hiczp/bilibili/api/app/model/PlayUrl.kt new file mode 100644 index 0000000..75364b5 --- /dev/null +++ b/src/main/kotlin/com/hiczp/bilibili/api/app/model/PlayUrl.kt @@ -0,0 +1,80 @@ +package com.hiczp.bilibili.api.app.model + +import com.google.gson.annotations.SerializedName + +data class PlayUrl( + @SerializedName("code") + var code: Int, // 0 + @SerializedName("data") + var `data`: Data, + @SerializedName("message") + var message: String, // 0 + @SerializedName("ttl") + var ttl: Int // 1 +) { + data class Data( + @SerializedName("accept_description") + var acceptDescription: List, + @SerializedName("accept_format") + var acceptFormat: String, // flv720,flv480,flv360 + @SerializedName("accept_quality") + var acceptQuality: List, + @SerializedName("dash") + var dash: Dash, + @SerializedName("fnval") + var fnval: Int, // 16 + @SerializedName("fnver") + var fnver: Int, // 0 + @SerializedName("format") + var format: String, // flv480 + @SerializedName("from") + var from: String, // local + @SerializedName("quality") + var quality: Int, // 32 + @SerializedName("result") + var result: String, // suee + @SerializedName("seek_param") + var seekParam: String, // start + @SerializedName("seek_type") + var seekType: String, // offset + @SerializedName("timelength") + var timelength: Int, // 443737 + @SerializedName("video_codecid") + var videoCodecid: Int, // 7 + @SerializedName("video_project") + var videoProject: Boolean // true + ) { + data class Dash( + @SerializedName("audio") + var audio: List