diff --git a/src/main/kotlin/com/hiczp/bilibili/api/BilibiliClient.kt b/src/main/kotlin/com/hiczp/bilibili/api/BilibiliClient.kt index d487687..d837075 100644 --- a/src/main/kotlin/com/hiczp/bilibili/api/BilibiliClient.kt +++ b/src/main/kotlin/com/hiczp/bilibili/api/BilibiliClient.kt @@ -1,10 +1,12 @@ package com.hiczp.bilibili.api +import com.hiczp.bilibili.api.exception.BilibiliApiException 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 +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 @@ -76,6 +78,7 @@ class BilibiliClient( "ts" to { Instant.now().epochSecond.toString() } )) addInterceptor(SortAndSignInterceptor(ParamType.FORM_URL_ENCODED, billingClientProperties.appSecret)) + addInterceptor(FailureResponseInterceptor) //log if (logLevel != HttpLoggingInterceptor.Level.NONE) { @@ -92,9 +95,14 @@ class BilibiliClient( .create(PassportAPI::class.java) } + //TODO 验证码 /** * 登陆 + * v3 登陆接口会同时返回 cookies 和 token + * + * @throws BilibiliApiException 用户名与密码不匹配或者需要验证码 */ + @Throws(BilibiliApiException::class) suspend fun login(username: String, password: String): LoginResponse { //取得 hash 和 RSA 公钥 val (hash, key) = passportAPI.getKey().await().data.let { data -> diff --git a/src/main/kotlin/com/hiczp/bilibili/api/exception/BilibiliApiException.kt b/src/main/kotlin/com/hiczp/bilibili/api/exception/BilibiliApiException.kt new file mode 100644 index 0000000..d6c58cd --- /dev/null +++ b/src/main/kotlin/com/hiczp/bilibili/api/exception/BilibiliApiException.kt @@ -0,0 +1,11 @@ +package com.hiczp.bilibili.api.exception + +import com.hiczp.bilibili.api.retrofit.CommonResponse +import java.io.IOException + +/** + * 当服务器返回的 code 不等于 0 时抛出 + */ +class BilibiliApiException( + commonResponse: CommonResponse +) : IOException(commonResponse.msg ?: commonResponse.message) diff --git a/src/main/kotlin/com/hiczp/bilibili/api/passport/model/GetKeyResponse.kt b/src/main/kotlin/com/hiczp/bilibili/api/passport/model/GetKeyResponse.kt index a82262b..e5cc97a 100644 --- a/src/main/kotlin/com/hiczp/bilibili/api/passport/model/GetKeyResponse.kt +++ b/src/main/kotlin/com/hiczp/bilibili/api/passport/model/GetKeyResponse.kt @@ -10,7 +10,7 @@ data class GetKeyResponse( @SerializedName("data") var `data`: Data, @SerializedName("ts") - var ts: Int // 1550219688 + var ts: Long // 1550219688 ) { data class Data( @SerializedName("hash") diff --git a/src/main/kotlin/com/hiczp/bilibili/api/passport/model/LoginResponse.kt b/src/main/kotlin/com/hiczp/bilibili/api/passport/model/LoginResponse.kt index 90edd75..9b210c3 100644 --- a/src/main/kotlin/com/hiczp/bilibili/api/passport/model/LoginResponse.kt +++ b/src/main/kotlin/com/hiczp/bilibili/api/passport/model/LoginResponse.kt @@ -10,7 +10,7 @@ data class LoginResponse( @SerializedName("data") var `data`: Data, @SerializedName("ts") - var ts: Int // 1550219689 + var ts: Long // 1550219689 ) { data class Data( @SerializedName("cookie_info") diff --git a/src/main/kotlin/com/hiczp/bilibili/api/retrofit/CommonResponse.kt b/src/main/kotlin/com/hiczp/bilibili/api/retrofit/CommonResponse.kt index e75eab7..eefaac4 100644 --- a/src/main/kotlin/com/hiczp/bilibili/api/retrofit/CommonResponse.kt +++ b/src/main/kotlin/com/hiczp/bilibili/api/retrofit/CommonResponse.kt @@ -2,11 +2,19 @@ package com.hiczp.bilibili.api.retrofit import com.google.gson.annotations.SerializedName +/** + * 通用实体, 可表示无 data 的响应 或 错误响应 + * code 为 0 表示正常响应, 此时 message 为 null + * code 不为 0 表示错误响应, 此时 data 可能是各种类型 + * 一些 API 同时有 msg 和 message + */ data class CommonResponse( @SerializedName("code") var code: Int, // 0 + @SerializedName("msg") + var msg: String?, @SerializedName("message") var message: String?, @SerializedName("ts") - var ts: Int // 1550546539 + var timestamp: Long // 1550546539 ) diff --git a/src/main/kotlin/com/hiczp/bilibili/api/retrofit/interceptor/FailureResponseInterceptor.kt b/src/main/kotlin/com/hiczp/bilibili/api/retrofit/interceptor/FailureResponseInterceptor.kt new file mode 100644 index 0000000..de9a211 --- /dev/null +++ b/src/main/kotlin/com/hiczp/bilibili/api/retrofit/interceptor/FailureResponseInterceptor.kt @@ -0,0 +1,45 @@ +package com.hiczp.bilibili.api.retrofit.interceptor + +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 okhttp3.Interceptor +import okhttp3.Response + +/** + * 如果服务器返回的 code 不为 0 则抛出异常 + */ +object FailureResponseInterceptor : Interceptor { + private val jsonParser = JsonParser() + @Suppress("SpellCheckingInspection") + private val gson = Gson() + + override fun intercept(chain: Interceptor.Chain): Response { + val response = chain.proceed(chain.request()) + val body = response.body() + if (!response.isSuccessful || body == null || body.contentLength() == 0L) return response + + //获取字符集 + val contentType = body.contentType() + val charset = if (contentType == null) { + Charsets.UTF_8 + } else { + contentType.charset(Charsets.UTF_8)!! + } + + //拷贝流并读取其内容 + val jsonObject = body.source().also { + it.request(Long.MAX_VALUE) + }.buffer.clone().inputStream().reader(charset).let { + jsonParser.parse(it).asJsonObject + } + + //判断 code 是否为 0 + if (jsonObject["code"].asInt != 0) { + throw BilibiliApiException(gson.fromJson(jsonObject, CommonResponse::class.java)) + } + + return response + } +}