mirror of
https://github.com/czp3009/bilibili-api.git
synced 2025-02-19 20:50:28 +08:00
完成登录和登出
This commit is contained in:
parent
ca86c44995
commit
b0bff448f9
@ -1,6 +1,7 @@
|
||||
package com.hiczp.bilibili.api
|
||||
|
||||
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.interceptor.CommonHeaderInterceptor
|
||||
import com.hiczp.bilibili.api.retrofit.interceptor.CommonParamInterceptor
|
||||
@ -11,7 +12,11 @@ import okhttp3.OkHttpClient
|
||||
import okhttp3.logging.HttpLoggingInterceptor
|
||||
import retrofit2.Retrofit
|
||||
import retrofit2.converter.gson.GsonConverterFactory
|
||||
import java.security.KeyFactory
|
||||
import java.security.spec.X509EncodedKeySpec
|
||||
import java.time.Instant
|
||||
import java.util.*
|
||||
import javax.crypto.Cipher
|
||||
|
||||
private val logger = KotlinLogging.logger {}
|
||||
|
||||
@ -35,11 +40,17 @@ class BilibiliClient(
|
||||
*/
|
||||
private val initTime = Instant.now().epochSecond
|
||||
|
||||
/**
|
||||
* 登陆操作得到的 Response
|
||||
*/
|
||||
var loginResponse: LoginResponse? = null
|
||||
|
||||
/**
|
||||
* 是否已登录
|
||||
*/
|
||||
@Suppress("MemberVisibilityCanBePrivate")
|
||||
var isLogin = false
|
||||
val isLogin
|
||||
get() = loginResponse != null
|
||||
|
||||
@Suppress("SpellCheckingInspection")
|
||||
val passportAPI: PassportAPI by lazy {
|
||||
@ -84,17 +95,42 @@ class BilibiliClient(
|
||||
/**
|
||||
* 登陆
|
||||
*/
|
||||
suspend fun login(username: String, password: String) {
|
||||
val (hash, key) = passportAPI.getKey().await().data.let {
|
||||
it.hash to it.key
|
||||
suspend fun login(username: String, password: String): LoginResponse {
|
||||
//取得 hash 和 RSA 公钥
|
||||
val (hash, key) = passportAPI.getKey().await().data.let { data ->
|
||||
data.hash to data.key.split('\n').filterNot { it.startsWith('-') }.joinToString(separator = "")
|
||||
}
|
||||
|
||||
//解析 RSA 公钥
|
||||
val publicKey = X509EncodedKeySpec(Base64.getDecoder().decode(key)).let {
|
||||
KeyFactory.getInstance("RSA").generatePublic(it)
|
||||
}
|
||||
//加密密码
|
||||
//兼容 Android
|
||||
val cipheredPassword = Cipher.getInstance("RSA/ECB/PKCS1Padding").apply {
|
||||
init(Cipher.ENCRYPT_MODE, publicKey)
|
||||
}.doFinal((hash + password).toByteArray()).let {
|
||||
Base64.getEncoder().encode(it)
|
||||
}.let {
|
||||
String(it)
|
||||
}
|
||||
|
||||
return passportAPI.login(username, cipheredPassword).await().also {
|
||||
this.loginResponse = it
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 登出
|
||||
* 这个方法不一定是线程安全的, 登出的同时如果进行登陆操作可能引发错误
|
||||
*/
|
||||
fun logout() {
|
||||
TODO()
|
||||
suspend fun logout() {
|
||||
val data = loginResponse?.data ?: return
|
||||
val cookieMap = data.cookieInfo.cookies
|
||||
.associate {
|
||||
it.name to it.value
|
||||
}
|
||||
passportAPI.revoke(cookieMap, data.tokenInfo.accessToken).await()
|
||||
loginResponse = null
|
||||
}
|
||||
}
|
||||
|
12
src/main/kotlin/com/hiczp/bilibili/api/CommonResponse.kt
Normal file
12
src/main/kotlin/com/hiczp/bilibili/api/CommonResponse.kt
Normal file
@ -0,0 +1,12 @@
|
||||
package com.hiczp.bilibili.api
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class CommonResponse(
|
||||
@SerializedName("code")
|
||||
var code: Int, // 0
|
||||
@SerializedName("message")
|
||||
var message: String?,
|
||||
@SerializedName("ts")
|
||||
var ts: Int // 1550546539
|
||||
)
|
@ -1,9 +1,11 @@
|
||||
package com.hiczp.bilibili.api.passport
|
||||
|
||||
import com.hiczp.bilibili.api.CommonResponse
|
||||
import com.hiczp.bilibili.api.passport.model.GetKeyResponse
|
||||
import com.hiczp.bilibili.api.passport.model.LoginResponse
|
||||
import kotlinx.coroutines.Deferred
|
||||
import retrofit2.http.Field
|
||||
import retrofit2.http.FieldMap
|
||||
import retrofit2.http.FormUrlEncoded
|
||||
import retrofit2.http.POST
|
||||
|
||||
@ -15,4 +17,28 @@ interface PassportAPI {
|
||||
@POST("/api/v3/oauth2/login")
|
||||
@FormUrlEncoded
|
||||
fun login(@Field("username") username: String, @Field("password") password: String): Deferred<LoginResponse>
|
||||
|
||||
/**
|
||||
* 除了 accessToken, 其他全部都是 cookie 的值
|
||||
*/
|
||||
@POST("/api/v2/oauth2/revoke")
|
||||
@FormUrlEncoded
|
||||
fun revoke(
|
||||
@Field("DedeUserID") dedeUserId: String,
|
||||
@Field("DedeUserID__ckMd5") ckMd5: String,
|
||||
@Suppress("SpellCheckingInspection") @Field("SESSDATA") sessData: String,
|
||||
@Field("access_token") accessToken: String,
|
||||
@Field("bili_jct") biliJct: String,
|
||||
@Field("sid") sid: String
|
||||
): Deferred<CommonResponse>
|
||||
|
||||
/**
|
||||
* 将所有 cookie 以 Map 形式传入
|
||||
*/
|
||||
@POST("/api/v2/oauth2/revoke")
|
||||
@FormUrlEncoded
|
||||
fun revoke(
|
||||
@FieldMap cookieMap: Map<String, String>,
|
||||
@Field("access_token") accessToken: String
|
||||
): Deferred<CommonResponse>
|
||||
}
|
||||
|
@ -32,7 +32,7 @@ data class LoginResponse(
|
||||
@SerializedName("expires")
|
||||
var expires: Int, // 1552811689
|
||||
@SerializedName("http_only")
|
||||
var httpOnly: Boolean, // 1
|
||||
var httpOnly: Int, // 1
|
||||
@SerializedName("name")
|
||||
var name: String, // SESSDATA
|
||||
@SerializedName("value")
|
||||
|
@ -2,7 +2,6 @@ package com.hiczp.bilibili.api.retrofit.interceptor
|
||||
|
||||
import com.hiczp.bilibili.api.retrofit.Method
|
||||
import com.hiczp.bilibili.api.retrofit.ParamType
|
||||
import com.hiczp.bilibili.api.retrofit.addAll
|
||||
import com.hiczp.bilibili.api.retrofit.sortedRaw
|
||||
import mu.KotlinLogging
|
||||
import okhttp3.FormBody
|
||||
@ -39,9 +38,13 @@ class SortAndSignInterceptor(private val paramType: ParamType, private val appSe
|
||||
|
||||
val body = request.body()
|
||||
if (request.method() == Method.POST && body is FormBody) {
|
||||
val sign = calculateSign(body.sortedRaw(), appSecret)
|
||||
val sortedRaw = body.sortedRaw()
|
||||
val sign = calculateSign(sortedRaw, appSecret)
|
||||
val newFormBody = FormBody.Builder().apply {
|
||||
addAll(body)
|
||||
sortedRaw.split('&').forEach {
|
||||
val (name, value) = it.split('=')
|
||||
addEncoded(name, value)
|
||||
}
|
||||
addEncoded("sign", sign)
|
||||
}.build()
|
||||
return chain.proceed(request.newBuilder().post(newFormBody).build())
|
||||
|
@ -7,10 +7,13 @@ import org.junit.jupiter.api.Test
|
||||
|
||||
class LoginTest {
|
||||
@Test
|
||||
fun login() {
|
||||
fun loginAndLogout() {
|
||||
runBlocking {
|
||||
BilibiliClient(logLevel = HttpLoggingInterceptor.Level.BODY)
|
||||
.login(Config.username, Config.password)
|
||||
.run {
|
||||
login(Config.username, Config.password)
|
||||
logout()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user