mirror of
https://github.com/czp3009/bilibili-api.git
synced 2025-02-19 20:50:28 +08:00
获取用户个人信息
This commit is contained in:
parent
2d6e249c05
commit
e083859b1a
19
README.md
19
README.md
@ -2,6 +2,8 @@
|
||||
该项目提供 Bilibili API 的 JVM 调用, 协议来自 Bilibili Android APP 的逆向工程以及截包分析.
|
||||
|
||||
# 登录和登出
|
||||
https://passport.bilibili.com
|
||||
|
||||
登陆和登出均为异步方法, 需要在协程上下文中执行.
|
||||
|
||||
runBlocking {
|
||||
@ -27,7 +29,7 @@
|
||||
|
||||
{"ts":1550569982,"code":-105,"data":{"url":"https://passport.bilibili.com/register/verification.html?success=1>=b6e5b7fad7ecd37f465838689732e788&challenge=9a67afa4d42ede71a93aeaaa54a4b6fe&ct=1&hash=105af2e7cc6ea829c4a95205f2371dc5"},"message":"验证码错误!"}
|
||||
|
||||
自行访问 `data.url` 将打开一个极验弹窗, 通过验证码后再次调用登陆接口:
|
||||
自行访问 `commonResponse.data.obj.url.string` 将打开一个极验弹窗, 通过验证码后再次调用登陆接口:
|
||||
|
||||
login(username, password, challenge, secCode, validate)
|
||||
|
||||
@ -41,6 +43,21 @@
|
||||
|
||||
登陆后, 可以访问全部 API.
|
||||
|
||||
# app
|
||||
https://app.bilibili.com
|
||||
|
||||
BilibiliClient().appAPI()
|
||||
|
||||
为 app 提供通用接口, 例如获取个人信息. 完整示例如下
|
||||
|
||||
runBlocking {
|
||||
val bilibiliClient = BilibiliClient().apply {
|
||||
login(username, password)
|
||||
}
|
||||
val myInfo = bilibiliClient.appAPI().myInfo().await()
|
||||
println(myInfo)
|
||||
}
|
||||
|
||||
# 主站
|
||||
//TODO
|
||||
|
||||
|
@ -54,6 +54,8 @@ dependencies {
|
||||
compile group: 'com.squareup.retrofit2', name: 'retrofit', version: '2.5.0'
|
||||
// https://mvnrepository.com/artifact/com.squareup.retrofit2/converter-gson
|
||||
compile group: 'com.squareup.retrofit2', name: 'converter-gson', version: '2.5.0'
|
||||
// https://mvnrepository.com/artifact/com.github.salomonbrys.kotson/kotson
|
||||
compile group: 'com.github.salomonbrys.kotson', name: 'kotson', version: '2.5.0'
|
||||
// https://mvnrepository.com/artifact/com.jakewharton.retrofit/retrofit2-kotlin-coroutines-adapter
|
||||
compile group: 'com.jakewharton.retrofit', name: 'retrofit2-kotlin-coroutines-adapter', version: '0.9.2'
|
||||
// https://mvnrepository.com/artifact/com.squareup.okhttp3/logging-interceptor
|
||||
|
@ -9,6 +9,11 @@ object BaseUrl {
|
||||
*/
|
||||
const val passport = "https://passport.bilibili.com"
|
||||
|
||||
/**
|
||||
* 提供通用功能, 例如获取用户信息
|
||||
*/
|
||||
const val app = "https://app.bilibili.com"
|
||||
|
||||
/**
|
||||
* 直播站
|
||||
*/
|
||||
|
@ -1,15 +1,17 @@
|
||||
package com.hiczp.bilibili.api
|
||||
|
||||
import com.hiczp.bilibili.api.exception.BilibiliApiException
|
||||
import com.hiczp.bilibili.api.app.AppAPI
|
||||
import com.hiczp.bilibili.api.passport.PassportAPI
|
||||
import com.hiczp.bilibili.api.passport.model.LoginResponse
|
||||
import com.hiczp.bilibili.api.retrofit.ParamType
|
||||
import com.hiczp.bilibili.api.retrofit.exception.BilibiliApiException
|
||||
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.jakewharton.retrofit2.adapter.kotlin.coroutines.CoroutineCallAdapterFactory
|
||||
import mu.KotlinLogging
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.logging.HttpLoggingInterceptor
|
||||
import retrofit2.Retrofit
|
||||
@ -55,39 +57,57 @@ class BilibiliClient(
|
||||
val isLogin
|
||||
get() = loginResponse != null
|
||||
|
||||
//快捷方式
|
||||
val userId get() = loginResponse?.userId
|
||||
val token get() = loginResponse?.token
|
||||
|
||||
/**
|
||||
* 用户鉴权相关的接口
|
||||
*/
|
||||
@Suppress("SpellCheckingInspection")
|
||||
val passportAPI: PassportAPI by lazy {
|
||||
val okHttpClient = OkHttpClient.Builder().apply {
|
||||
addInterceptor(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 }
|
||||
))
|
||||
addInterceptor(CommonParamInterceptor(ParamType.FORM_URL_ENCODED,
|
||||
"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() }
|
||||
))
|
||||
addInterceptor(SortAndSignInterceptor(ParamType.FORM_URL_ENCODED, billingClientProperties.appSecret))
|
||||
addInterceptor(FailureResponseInterceptor)
|
||||
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 }
|
||||
),
|
||||
CommonParamInterceptor(ParamType.FORM_URL_ENCODED,
|
||||
"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.FORM_URL_ENCODED, billingClientProperties.appSecret)
|
||||
)
|
||||
}
|
||||
|
||||
//log
|
||||
if (logLevel != HttpLoggingInterceptor.Level.NONE) {
|
||||
addNetworkInterceptor(HttpLoggingInterceptor().setLevel(logLevel))
|
||||
}
|
||||
}.build()
|
||||
|
||||
Retrofit.Builder()
|
||||
.baseUrl(BaseUrl.passport)
|
||||
.addConverterFactory(GsonConverterFactory.create())
|
||||
.addCallAdapterFactory(CoroutineCallAdapterFactory())
|
||||
.client(okHttpClient)
|
||||
.build()
|
||||
.create(PassportAPI::class.java)
|
||||
/**
|
||||
* 提供一些通用信息
|
||||
*/
|
||||
@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 }
|
||||
),
|
||||
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)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -145,3 +165,27 @@ class BilibiliClient(
|
||||
loginResponse = null
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("SpellCheckingInspection")
|
||||
private val gsonConverterFactory = GsonConverterFactory.create()
|
||||
private val coroutineCallAdapterFactory = CoroutineCallAdapterFactory()
|
||||
private inline fun <reified T : Any> 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)
|
||||
|
12
src/main/kotlin/com/hiczp/bilibili/api/app/AppAPI.kt
Normal file
12
src/main/kotlin/com/hiczp/bilibili/api/app/AppAPI.kt
Normal file
@ -0,0 +1,12 @@
|
||||
package com.hiczp.bilibili.api.app
|
||||
|
||||
import com.hiczp.bilibili.api.app.model.MyInfo
|
||||
import kotlinx.coroutines.Deferred
|
||||
import retrofit2.http.GET
|
||||
|
||||
@Suppress("DeferredIsResult")
|
||||
interface AppAPI {
|
||||
@Suppress("SpellCheckingInspection")
|
||||
@GET("/x/v2/account/myinfo")
|
||||
fun myInfo(): Deferred<MyInfo>
|
||||
}
|
67
src/main/kotlin/com/hiczp/bilibili/api/app/model/MyInfo.kt
Normal file
67
src/main/kotlin/com/hiczp/bilibili/api/app/model/MyInfo.kt
Normal file
@ -0,0 +1,67 @@
|
||||
package com.hiczp.bilibili.api.app.model
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class MyInfo(
|
||||
@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("birthday")
|
||||
var birthday: String, // 1995-11-18
|
||||
@SerializedName("coins")
|
||||
var coins: Int, // 1025
|
||||
@SerializedName("email_status")
|
||||
var emailStatus: Int, // 0
|
||||
@SerializedName("face")
|
||||
var face: String, // http://i1.hdslb.com/bfs/face/4f65e79399ad5a1bf3f877851b2f819d5870b494.jpg
|
||||
@SerializedName("identification")
|
||||
var identification: Int, // 1
|
||||
@SerializedName("level")
|
||||
var level: Int, // 4
|
||||
@SerializedName("mid")
|
||||
var mid: Int, // 20293030
|
||||
@SerializedName("name")
|
||||
var name: String, // czp3009
|
||||
@SerializedName("official")
|
||||
var official: Official,
|
||||
@SerializedName("rank")
|
||||
var rank: Int, // 10000
|
||||
@SerializedName("sex")
|
||||
var sex: Int, // 0
|
||||
@SerializedName("sign")
|
||||
var sign: String,
|
||||
@SerializedName("silence")
|
||||
var silence: Int, // 0
|
||||
@SerializedName("tel_status")
|
||||
var telStatus: Int, // 1
|
||||
@SerializedName("vip")
|
||||
var vip: Vip
|
||||
) {
|
||||
data class Official(
|
||||
@SerializedName("desc")
|
||||
var desc: String,
|
||||
@SerializedName("role")
|
||||
var role: Int, // 0
|
||||
@SerializedName("title")
|
||||
var title: String
|
||||
)
|
||||
|
||||
data class Vip(
|
||||
@SerializedName("due_date")
|
||||
var dueDate: Int, // 0
|
||||
@SerializedName("status")
|
||||
var status: Int, // 0
|
||||
@SerializedName("type")
|
||||
var type: Int, // 0
|
||||
@SerializedName("vip_pay_type")
|
||||
var vipPayType: Int // 0
|
||||
)
|
||||
}
|
||||
}
|
@ -26,5 +26,11 @@ data class CommonResponse(
|
||||
* data 可能是各种类型, 例如 array, object, string
|
||||
*/
|
||||
@SerializedName("data")
|
||||
var data: JsonElement?
|
||||
var data: JsonElement?,
|
||||
|
||||
/**
|
||||
* ttl, 不明确含义, 如果存在则值总为 1
|
||||
*/
|
||||
@SerializedName("ttl")
|
||||
var ttl: Int?
|
||||
)
|
||||
|
@ -1,4 +1,4 @@
|
||||
package com.hiczp.bilibili.api.exception
|
||||
package com.hiczp.bilibili.api.retrofit.exception
|
||||
|
||||
import com.hiczp.bilibili.api.retrofit.CommonResponse
|
||||
import java.io.IOException
|
@ -1,9 +1,11 @@
|
||||
package com.hiczp.bilibili.api.retrofit.interceptor
|
||||
|
||||
import com.github.salomonbrys.kotson.fromJson
|
||||
import com.github.salomonbrys.kotson.int
|
||||
import com.github.salomonbrys.kotson.obj
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.JsonParser
|
||||
import com.hiczp.bilibili.api.exception.BilibiliApiException
|
||||
import com.hiczp.bilibili.api.retrofit.CommonResponse
|
||||
import com.hiczp.bilibili.api.retrofit.exception.BilibiliApiException
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.Response
|
||||
|
||||
@ -32,12 +34,12 @@ object FailureResponseInterceptor : Interceptor {
|
||||
val jsonObject = body.source().also {
|
||||
it.request(Long.MAX_VALUE)
|
||||
}.buffer.clone().inputStream().reader(charset).let {
|
||||
jsonParser.parse(it).asJsonObject
|
||||
jsonParser.parse(it).obj
|
||||
}
|
||||
|
||||
//判断 code 是否为 0
|
||||
if (jsonObject["code"].asInt != 0) {
|
||||
throw BilibiliApiException(gson.fromJson(jsonObject, CommonResponse::class.java))
|
||||
if (jsonObject["code"].int != 0) {
|
||||
throw BilibiliApiException(gson.fromJson(jsonObject))
|
||||
}
|
||||
|
||||
return response
|
||||
|
@ -1,15 +1,27 @@
|
||||
package com.hiczp.bilibili.api.test
|
||||
|
||||
import com.github.salomonbrys.kotson.byString
|
||||
import com.github.salomonbrys.kotson.fromJson
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.JsonObject
|
||||
import com.hiczp.bilibili.api.BilibiliClient
|
||||
import okhttp3.logging.HttpLoggingInterceptor
|
||||
|
||||
object Config {
|
||||
private val config = Gson().fromJson(
|
||||
Config::class.java.getResourceAsStream("/config.json").reader(),
|
||||
JsonObject::class.java
|
||||
@Suppress("SpellCheckingInspection")
|
||||
private val gson = Gson()
|
||||
|
||||
private val config = gson.fromJson<JsonObject>(
|
||||
Config::class.java.getResourceAsStream("/config.json").reader()
|
||||
)
|
||||
|
||||
val username = config["username"].asString!!
|
||||
val username by config.byString
|
||||
|
||||
val password = config["password"].asString!!
|
||||
val password by config.byString
|
||||
|
||||
val presetBilibiliClient by lazy {
|
||||
BilibiliClient(logLevel = HttpLoggingInterceptor.Level.BODY).apply {
|
||||
loginResponse = config["loginResponse"]?.let { gson.fromJson(it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
13
src/test/kotlin/com/hiczp/bilibili/api/test/UserInfoTest.kt
Normal file
13
src/test/kotlin/com/hiczp/bilibili/api/test/UserInfoTest.kt
Normal file
@ -0,0 +1,13 @@
|
||||
package com.hiczp.bilibili.api.test
|
||||
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class UserInfoTest {
|
||||
@Test
|
||||
fun info() {
|
||||
runBlocking {
|
||||
Config.presetBilibiliClient.appAPI.myInfo().await()
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +1,64 @@
|
||||
{
|
||||
"username": "123456789",
|
||||
"password": "123456",
|
||||
"roomId": "3"
|
||||
"roomId": "3",
|
||||
"loginResponse": {
|
||||
"ts": 1550629285,
|
||||
"code": 0,
|
||||
"data": {
|
||||
"status": 0,
|
||||
"token_info": {
|
||||
"mid": 20293030,
|
||||
"access_token": "b0b214e97b7bf388769eb727da27f951",
|
||||
"refresh_token": "54c56bda9bde64cacb44a14e92007d51",
|
||||
"expires_in": 2592000
|
||||
},
|
||||
"cookie_info": {
|
||||
"cookies": [
|
||||
{
|
||||
"name": "bili_jct",
|
||||
"value": "578b271b308c760cbd281c37afc420c4",
|
||||
"http_only": 0,
|
||||
"expires": 1553221285
|
||||
},
|
||||
{
|
||||
"name": "DedeUserID",
|
||||
"value": "20293035",
|
||||
"http_only": 0,
|
||||
"expires": 1553221285
|
||||
},
|
||||
{
|
||||
"name": "DedeUserID__ckMd5",
|
||||
"value": "cdff5c8e58b793cd",
|
||||
"http_only": 0,
|
||||
"expires": 1553221285
|
||||
},
|
||||
{
|
||||
"name": "sid",
|
||||
"value": "ja724hee",
|
||||
"http_only": 0,
|
||||
"expires": 1553221285
|
||||
},
|
||||
{
|
||||
"name": "SESSDATA",
|
||||
"value": "eaa5b420%2C1553221285%2C59590a31",
|
||||
"http_only": 1,
|
||||
"expires": 1553221285
|
||||
}
|
||||
],
|
||||
"domains": [
|
||||
".bilibili.com",
|
||||
".biligame.com",
|
||||
".im9.com",
|
||||
".bigfunapp.cn"
|
||||
]
|
||||
},
|
||||
"sso": [
|
||||
"https://passport.bilibili.com/api/v2/sso",
|
||||
"https://passport.biligame.com/api/v2/sso",
|
||||
"https://passport.im9.com/api/v2/sso",
|
||||
"https://passport.bigfunapp.cn/api/v2/sso"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user