完成登陆过程中会访问的 API

This commit is contained in:
czp3009 2019-02-20 18:24:47 +08:00
parent e083859b1a
commit eb588f5798
14 changed files with 783 additions and 35 deletions

View File

@ -43,10 +43,17 @@ https://passport.bilibili.com
登陆后, 可以访问全部 API.
# message
https://message.bilibili.com
BilibiliClient().messageAPI
消息通知有关的接口.
# app
https://app.bilibili.com
BilibiliClient().appAPI()
BilibiliClient().appAPI
为 app 提供通用接口, 例如获取个人信息. 完整示例如下
@ -54,15 +61,16 @@ https://app.bilibili.com
val bilibiliClient = BilibiliClient().apply {
login(username, password)
}
val myInfo = bilibiliClient.appAPI().myInfo().await()
val myInfo = bilibiliClient.appAPI.myInfo().await()
println(myInfo)
}
# 主站
//TODO
# av
https://api.vc.bilibili.com
# 直播站
//TODO
BilibiliClient().vcAPI
小视频有关的接口.
# License
GPL V3

View File

@ -5,17 +5,27 @@ package com.hiczp.bilibili.api
*/
object BaseUrl {
/**
* passport , 用于登录
* 用户鉴权
*/
const val passport = "https://passport.bilibili.com"
/**
* 消息
*/
const val message = "https://message.bilibili.com"
/**
* 提供通用功能, 例如获取用户信息
*/
const val app = "https://app.bilibili.com"
/**
* 直播站
* 小视频
*/
const val vc = "https://api.vc.bilibili.com"
/**
* 直播
*/
const val live = "https://api.live.bilibili.com"
}

View File

@ -1,6 +1,7 @@
package com.hiczp.bilibili.api
import com.hiczp.bilibili.api.app.AppAPI
import com.hiczp.bilibili.api.message.MessageAPI
import com.hiczp.bilibili.api.passport.PassportAPI
import com.hiczp.bilibili.api.passport.model.LoginResponse
import com.hiczp.bilibili.api.retrofit.ParamType
@ -9,8 +10,8 @@ import com.hiczp.bilibili.api.retrofit.interceptor.CommonHeaderInterceptor
import com.hiczp.bilibili.api.retrofit.interceptor.CommonParamInterceptor
import com.hiczp.bilibili.api.retrofit.interceptor.FailureResponseInterceptor
import com.hiczp.bilibili.api.retrofit.interceptor.SortAndSignInterceptor
import com.hiczp.bilibili.api.vc.VcAPI
import com.jakewharton.retrofit2.adapter.kotlin.coroutines.CoroutineCallAdapterFactory
import mu.KotlinLogging
import okhttp3.Interceptor
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
@ -18,12 +19,11 @@ import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import java.security.KeyFactory
import java.security.spec.X509EncodedKeySpec
import java.text.SimpleDateFormat
import java.time.Instant
import java.util.*
import javax.crypto.Cipher
private val logger = KotlinLogging.logger {}
/**
* 此类表示一个模拟的 Bilibili 客户端(Android), 所有调用由此开始.
* 多个 BilibiliClient 实例之间不共享登陆状态.
@ -58,8 +58,34 @@ class BilibiliClient(
get() = loginResponse != null
//快捷方式
val userId get() = loginResponse?.userId
val token get() = loginResponse?.token
@Suppress("MemberVisibilityCanBePrivate")
val userId
get() = loginResponse?.userId
@Suppress("MemberVisibilityCanBePrivate")
val token
get() = loginResponse?.token
@Suppress("SpellCheckingInspection")
private val defaultCommonHeaderInterceptor = CommonHeaderInterceptor(
"Display-ID" to { "${billingClientProperties.buildVersionId}-$initTime" },
"Buvid" to { billingClientProperties.buildVersionId },
"User-Agent" to { "Mozilla/5.0 BiliDroid/5.37.0 (bbcallen@gmail.com)" },
"Device-ID" to { billingClientProperties.hardwareId }
)
@Suppress("SpellCheckingInspection")
private val defaultCommonParamArray = arrayOf(
"access_key" to { token },
"appkey" to { billingClientProperties.appKey },
"build" to { billingClientProperties.build },
"channel" to { billingClientProperties.channel },
"mobi_app" to { billingClientProperties.platform },
"platform" to { billingClientProperties.platform },
"ts" to { Instant.now().epochSecond.toString() }
)
private val defaultQuerySignInterceptor = SortAndSignInterceptor(ParamType.QUERY, billingClientProperties.appSecret)
private val defaultFormSignInterceptor = SortAndSignInterceptor(ParamType.FORM_URL_ENCODED, billingClientProperties.appSecret)
/**
* 用户鉴权相关的接口
@ -67,12 +93,7 @@ class BilibiliClient(
@Suppress("SpellCheckingInspection")
val passportAPI by lazy {
createAPI<PassportAPI>(BaseUrl.passport, logLevel,
CommonHeaderInterceptor(
"Display-ID" to { "${billingClientProperties.buildVersionId}-$initTime" },
"Buvid" to { billingClientProperties.buildVersionId },
"User-Agent" to { "Mozilla/5.0 BiliDroid/5.37.0 (bbcallen@gmail.com)" },
"Device-ID" to { billingClientProperties.hardwareId }
),
defaultCommonHeaderInterceptor,
CommonParamInterceptor(ParamType.FORM_URL_ENCODED,
"appkey" to { billingClientProperties.appKey },
"build" to { billingClientProperties.build },
@ -81,7 +102,22 @@ class BilibiliClient(
"platform" to { billingClientProperties.platform },
"ts" to { Instant.now().epochSecond.toString() }
),
SortAndSignInterceptor(ParamType.FORM_URL_ENCODED, billingClientProperties.appSecret)
defaultFormSignInterceptor
)
}
/**
* 消息通知有关的接口
*/
@Suppress("SpellCheckingInspection")
val messageAPI by lazy {
createAPI<MessageAPI>(BaseUrl.message, logLevel,
defaultCommonHeaderInterceptor,
CommonParamInterceptor(ParamType.QUERY, *defaultCommonParamArray,
"actionKey" to { "appkey" },
"has_up" to { "1" }
),
defaultQuerySignInterceptor
)
}
@ -91,22 +127,28 @@ class BilibiliClient(
@Suppress("SpellCheckingInspection")
val appAPI by lazy {
createAPI<AppAPI>(BaseUrl.app, logLevel,
CommonHeaderInterceptor(
"Display-ID" to { "${billingClientProperties.buildVersionId}-$initTime" },
"Buvid" to { billingClientProperties.buildVersionId },
"User-Agent" to { "Mozilla/5.0 BiliDroid/5.37.0 (bbcallen@gmail.com)" },
"Device-ID" to { billingClientProperties.hardwareId }
defaultCommonHeaderInterceptor,
CommonParamInterceptor(ParamType.QUERY, *defaultCommonParamArray),
defaultQuerySignInterceptor
)
}
/**
* 小视频相关接口
*/
@Suppress("SpellCheckingInspection")
val vcAPI by lazy {
createAPI<VcAPI>(BaseUrl.vc, logLevel,
defaultCommonHeaderInterceptor,
CommonParamInterceptor(ParamType.QUERY, *defaultCommonParamArray,
"_device" to { billingClientProperties.platform },
"_hwid" to { billingClientProperties.hardwareId },
"src" to { billingClientProperties.channel },
"trace_id" to { generateTraceId() },
"uid" to { userId?.toString() },
"version" to { billingClientProperties.version }
),
CommonParamInterceptor(ParamType.QUERY,
"access_key" to { token },
"appkey" to { billingClientProperties.appKey },
"build" to { billingClientProperties.build },
"channel" to { billingClientProperties.channel },
"mobi_app" to { billingClientProperties.platform },
"platform" to { billingClientProperties.platform },
"ts" to { Instant.now().epochSecond.toString() }
),
SortAndSignInterceptor(ParamType.QUERY, billingClientProperties.appSecret)
defaultQuerySignInterceptor
)
}
@ -164,6 +206,11 @@ class BilibiliClient(
passportAPI.revoke(cookieMap, response.token).await()
loginResponse = null
}
companion object {
private val traceIdFormat = SimpleDateFormat("yyyyMMddHHmm000ss")
private fun generateTraceId() = traceIdFormat.format(Date())
}
}
@Suppress("SpellCheckingInspection")

View File

@ -1,12 +1,60 @@
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 kotlinx.coroutines.Deferred
import retrofit2.http.GET
import retrofit2.http.Query
import java.time.Instant
/**
* 提供通用信息的接口
*/
@Suppress("DeferredIsResult")
interface AppAPI {
/**
* 登陆完成后将请求一次此接口以获得个人资料
*/
@Suppress("SpellCheckingInspection")
@GET("/x/v2/account/myinfo")
fun myInfo(): Deferred<MyInfo>
/**
* 登陆后也会访问此接口, 返回内容大致与 myInfo() 相同
*/
@GET("/x/v2/account/mine")
fun mine(): Deferred<Mine>
/**
* 侧边栏中动态增加的按钮, 返回信息包含 URI 地址(到对应的 activity)
*/
@GET("/x/resource/sidebar")
fun sidebar(): Deferred<Sidebar>
/**
* 首页内容
* 首页 -> 推荐
*/
@Suppress("SpellCheckingInspection")
@GET("/x/v2/feed/index")
fun index(
@Query("ad_extra") adExtra: String? = null,
@Query("autoplay_card") autoplayCard: Int = 0,
@Query("banner_hash") bannerHash: String? = null,
@Query("column") column: Int = 2,
@Query("device_type") deviceType: Int = 0,
@Query("flush") flush: Int = 0,
@Query("fnval") fnVal: Int = 16,
@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("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<IndexPage>
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,60 @@
package com.hiczp.bilibili.api.app.model
import com.google.gson.annotations.SerializedName
data class Mine(
@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("audio_type")
var audioType: Int, // 0
@SerializedName("bcoin")
var bcoin: Int, // 5
@SerializedName("coin")
var coin: Double, // 892.7
@SerializedName("dynamic")
var `dynamic`: Int, // 8
@SerializedName("face")
var face: String, // http://i0.hdslb.com/bfs/face/0434dccc0ec4de223e8ca374dea06a6e1e8eb471.jpg
@SerializedName("follower")
var follower: Int, // 512
@SerializedName("following")
var following: Int, // 106
@SerializedName("level")
var level: Int, // 5
@SerializedName("mid")
var mid: Int, // 2866663
@SerializedName("name")
var name: String, // hyx5020
@SerializedName("new_followers")
var newFollowers: Int, // 0
@SerializedName("official_verify")
var officialVerify: OfficialVerify,
@SerializedName("rank")
var rank: Int, // 10000
@SerializedName("sex")
var sex: Int, // 0
@SerializedName("show_creative")
var showCreative: Int, // 1
@SerializedName("show_videoup")
var showVideoup: Int, // 1
@SerializedName("silence")
var silence: Int, // 0
@SerializedName("vip_type")
var vipType: Int // 2
) {
data class OfficialVerify(
@SerializedName("desc")
var desc: String,
@SerializedName("type")
var type: Int // -1
)
}
}

View File

@ -0,0 +1,33 @@
package com.hiczp.bilibili.api.app.model
import com.google.gson.annotations.SerializedName
data class Sidebar(
@SerializedName("code")
var code: Int, // 0
@SerializedName("data")
var `data`: List<SidebarElement>,
@SerializedName("message")
var message: String, // 0
@SerializedName("ttl")
var ttl: Int // 1
) {
data class SidebarElement(
@SerializedName("id")
var id: Int, // 13
@SerializedName("logo")
var logo: String, // http://i0.hdslb.com/bfs/archive/91f7ba40e54502f7479c8d355e4298989bb8ebce.png
@SerializedName("module")
var module: Int, // 1
@SerializedName("name")
var name: String, // 会员购中心
@SerializedName("online_time")
var onlineTime: Int, // 0
@SerializedName("param")
var `param`: String, // bilibili://mall/mine?msource=mine
@SerializedName("rank")
var rank: Int, // 300
@SerializedName("tip")
var tip: Int // 1
)
}

View File

@ -0,0 +1,26 @@
package com.hiczp.bilibili.api.message
import com.hiczp.bilibili.api.message.model.NotifyCount
import com.hiczp.bilibili.api.message.model.UplmList
import kotlinx.coroutines.Deferred
import retrofit2.http.GET
/**
* 消息推送有关的接口
*/
@Suppress("DeferredIsResult")
interface MessageAPI {
/**
* 获取消息数量
* 首页 -> 右上角 talk 图标
*/
@GET("/api/notify/query.notify.count.do")
fun queryNotifyCount(): Deferred<NotifyCount>
/**
* 荣誉周报
*/
@Suppress("SpellCheckingInspection")
@GET("/api/notify/get.uplm.list.do")
fun getUplmList(): Deferred<UplmList>
}

View File

@ -0,0 +1,38 @@
package com.hiczp.bilibili.api.message.model
import com.google.gson.annotations.SerializedName
data class NotifyCount(
@SerializedName("code")
var code: Int, // 0
@SerializedName("data")
var `data`: Data,
@SerializedName("message")
var message: String,
@SerializedName("msg")
var msg: String
) {
data class Data(
@SerializedName("_gt_")
var gt: Int, // 0
/**
* \@我
*/
@SerializedName("at_me")
var atMe: Int, // 0
@SerializedName("notify_me")
var notifyMe: Int, // 9
/**
* 收到的赞
*/
@SerializedName("praise_me")
var praiseMe: Int, // 0
/**
* 回复我的
*/
@SerializedName("reply_me")
var replyMe: Int, // 0
@SerializedName("up")
var up: Int // 0
)
}

View File

@ -0,0 +1,27 @@
package com.hiczp.bilibili.api.message.model
import com.google.gson.annotations.SerializedName
data class UplmList(
@SerializedName("code")
var code: Int, // 0
@SerializedName("data")
var `data`: Data,
@SerializedName("message")
var message: String,
@SerializedName("msg")
var msg: String
) {
data class Data(
@SerializedName("_gt_")
var gt: Int, // 0
@SerializedName("id")
var id: Int, // 2429173
@SerializedName("time")
var time: String, // 2018-11-25 19:29:22
@SerializedName("title")
var title: String, // 叮!你有一份荣誉周报待查收!
@SerializedName("unread")
var unread: Int // 0
)
}

View File

@ -9,6 +9,9 @@ import retrofit2.http.FieldMap
import retrofit2.http.FormUrlEncoded
import retrofit2.http.POST
/**
* 用户鉴权相关的接口
*/
@Suppress("DeferredIsResult")
interface PassportAPI {
@POST("/api/oauth2/getKey")

View File

@ -0,0 +1,30 @@
package com.hiczp.bilibili.api.vc
import com.hiczp.bilibili.api.vc.model.AttentionList
import com.hiczp.bilibili.api.vc.model.DynamicNumber
import kotlinx.coroutines.Deferred
import retrofit2.http.GET
import retrofit2.http.Query
/**
* 小视频
*/
@Suppress("DeferredIsResult")
interface VcAPI {
/**
* //TODO 接口意义不明
* 可能是一个通知接口
*/
@GET("/dynamic_svr/v1/dynamic_svr/dynamic_num")
fun dynamicNumber(
@Query("rsp_type") rspType: Int,
@Query("type_list") typeList: Long? = null,
@Query("update_num_dy_id") updateNumberDynamicId: Int = 0
): Deferred<DynamicNumber>
/**
* 关注列表
*/
@GET("/feed/v1/feed/get_attention_list")
fun getAttentionList(): Deferred<AttentionList>
}

View File

@ -0,0 +1,19 @@
package com.hiczp.bilibili.api.vc.model
import com.google.gson.annotations.SerializedName
data class AttentionList(
@SerializedName("code")
var code: Int, // 0
@SerializedName("data")
var `data`: Data,
@SerializedName("message")
var message: String, // success
@SerializedName("msg")
var msg: String // success
) {
data class Data(
@SerializedName("list")
var list: List<String>
)
}

View File

@ -0,0 +1,25 @@
package com.hiczp.bilibili.api.vc.model
import com.google.gson.annotations.SerializedName
data class DynamicNumber(
@SerializedName("code")
var code: Int, // 0
@SerializedName("data")
var `data`: Data,
@SerializedName("message")
var message: String,
@SerializedName("msg")
var msg: String
) {
data class Data(
@SerializedName("_gt_")
var gt: Int, // 0
@SerializedName("exist_gap")
var existGap: Int, // 1
@SerializedName("new_num")
var newNum: Int, // 30
@SerializedName("update_num")
var updateNum: Int // 100
)
}