diff --git a/.gitignore b/.gitignore
index bed64ff..0659de8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,4 +2,6 @@
.idea
*.iml
target
+out
+build
src/test/resources/config.json
diff --git a/pom.xml b/pom.xml
index 8a974a6..d615f9b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -7,7 +7,7 @@
com.hiczp
bilibili-api
bilibili-api
- 1.0
+ 0.0.1
Bilibili API
https://github.com/czp3009/bilibili-api
diff --git a/src/main/java/com/hiczp/bilibili/api/BaseUrlDefinition.java b/src/main/java/com/hiczp/bilibili/api/BaseUrlDefinition.java
new file mode 100644
index 0000000..42cd128
--- /dev/null
+++ b/src/main/java/com/hiczp/bilibili/api/BaseUrlDefinition.java
@@ -0,0 +1,6 @@
+package com.hiczp.bilibili.api;
+
+public class BaseUrlDefinition {
+ public static final String PASSPORT = "https://passport.bilibili.com/";
+ public static final String LIVE = "http://api.live.bilibili.com/";
+}
diff --git a/src/main/java/com/hiczp/bilibili/api/BilibiliAPI.java b/src/main/java/com/hiczp/bilibili/api/BilibiliAPI.java
new file mode 100644
index 0000000..14bb0a7
--- /dev/null
+++ b/src/main/java/com/hiczp/bilibili/api/BilibiliAPI.java
@@ -0,0 +1,169 @@
+package com.hiczp.bilibili.api;
+
+import com.hiczp.bilibili.api.interceptor.*;
+import com.hiczp.bilibili.api.live.LiveService;
+import com.hiczp.bilibili.api.live.socket.LiveClient;
+import com.hiczp.bilibili.api.passport.PassportService;
+import com.hiczp.bilibili.api.passport.entity.LoginResponseEntity;
+import com.hiczp.bilibili.api.passport.entity.LogoutResponseEntity;
+import com.hiczp.bilibili.api.passport.entity.RefreshTokenResponseEntity;
+import okhttp3.OkHttpClient;
+import okhttp3.logging.HttpLoggingInterceptor;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import retrofit2.Retrofit;
+import retrofit2.converter.gson.GsonConverterFactory;
+
+import javax.security.auth.login.LoginException;
+import java.io.IOException;
+import java.text.SimpleDateFormat;
+import java.time.Instant;
+import java.util.Date;
+
+//TODO 尚未实现自动 refreshToken 的拦截器
+public class BilibiliAPI implements BilibiliServiceProvider, LiveClientProvider {
+ private static final Logger LOGGER = LoggerFactory.getLogger(BilibiliAPI.class);
+
+ private final Long apiInitTime = Instant.now().getEpochSecond(); //记录当前类被实例化的时间
+ private final BilibiliClientProperties bilibiliClientProperties;
+ private final BilibiliAccount bilibiliAccount;
+
+ private PassportService passportService;
+ private LiveService liveService;
+
+ public BilibiliAPI() {
+ this.bilibiliClientProperties = BilibiliClientProperties.defaultSetting();
+ this.bilibiliAccount = BilibiliAccount.emptyInstance();
+ }
+
+ public BilibiliAPI(BilibiliClientProperties bilibiliClientProperties) {
+ this.bilibiliClientProperties = bilibiliClientProperties;
+ this.bilibiliAccount = BilibiliAccount.emptyInstance();
+ }
+
+ public BilibiliAPI(BilibiliAccount bilibiliAccount) {
+ this.bilibiliClientProperties = BilibiliClientProperties.defaultSetting();
+ this.bilibiliAccount = bilibiliAccount;
+ }
+
+ public BilibiliAPI(BilibiliClientProperties bilibiliClientProperties, BilibiliAccount bilibiliAccount) {
+ this.bilibiliClientProperties = bilibiliClientProperties;
+ this.bilibiliAccount = bilibiliAccount;
+ }
+
+ //TODO 不明确客户端访问 passport.bilibili.com 时使用的 UA
+ @Override
+ public PassportService getPassportService() {
+ if (passportService == null) {
+ LOGGER.debug("Init PassportService in BilibiliAPI instance {}", this.hashCode());
+ OkHttpClient okHttpClient = new OkHttpClient().newBuilder()
+ .addInterceptor(new AddFixedParamsInterceptor(
+ "build", bilibiliClientProperties.getBuild(),
+ "mobi_app", "android",
+ "platform", "android"
+ ))
+ .addInterceptor(new AddDynamicParamsInterceptor(
+ () -> "ts", () -> Long.toString(Instant.now().getEpochSecond())
+ ))
+ .addInterceptor(new AddAppKeyInterceptor(bilibiliClientProperties))
+ .addInterceptor(new SortParamsAndSignInterceptor(bilibiliClientProperties))
+ .addInterceptor(new ErrorResponseConverterInterceptor())
+ .addNetworkInterceptor(new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY))
+ .build();
+
+ passportService = new Retrofit.Builder()
+ .baseUrl(BaseUrlDefinition.PASSPORT)
+ .addConverterFactory(GsonConverterFactory.create())
+ .client(okHttpClient)
+ .build()
+ .create(PassportService.class);
+ }
+ return passportService;
+ }
+
+ @Override
+ public LiveService getLiveService() {
+ if (liveService == null) {
+ LOGGER.debug("Init LiveService in BilibiliAPI instance {}", this.hashCode());
+ OkHttpClient okHttpClient = new OkHttpClient.Builder()
+ .addInterceptor(new AddFixedHeadersInterceptor(
+ "Buvid", bilibiliClientProperties.getBuvId(),
+ "User-Agent", "Mozilla/5.0 BiliDroid/5.15.0 (bbcallen@gmail.com)",
+ "Device-ID", bilibiliClientProperties.getHardwareId()
+ )).addInterceptor(new AddDynamicHeadersInterceptor(
+ //Display-ID 的值在未登录前为 Buvid-客户端启动时间, 在登录后为 mid-客户端启动时间
+ () -> "Display-ID", () -> String.format("%s-%d", bilibiliAccount.getUserId() == null ? bilibiliClientProperties.getBuvId() : bilibiliAccount.getUserId(), apiInitTime)
+ )).addInterceptor(new AddFixedParamsInterceptor(
+ "_device", "android",
+ "_hwid", bilibiliClientProperties.getHardwareId(),
+ "build", bilibiliClientProperties.getBuild(),
+ "mobi_app", "android",
+ "platform", "android",
+ "scale", bilibiliClientProperties.getScale(),
+ "src", "google",
+ "version", bilibiliClientProperties.getVersion()
+ )).addInterceptor(new AddDynamicParamsInterceptor(
+ () -> "ts", () -> Long.toString(Instant.now().getEpochSecond()),
+ () -> "trace_id", () -> new SimpleDateFormat("yyyyMMddHHmm000ss").format(new Date())
+ ))
+ .addInterceptor(new AddAccessKeyInterceptor(bilibiliAccount))
+ .addInterceptor(new AddAppKeyInterceptor(bilibiliClientProperties))
+ .addInterceptor(new SortParamsAndSignInterceptor(bilibiliClientProperties))
+ .addInterceptor(new ErrorResponseConverterInterceptor())
+ .addNetworkInterceptor(new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY))
+ .build();
+
+ liveService = new Retrofit.Builder()
+ .baseUrl(BaseUrlDefinition.LIVE)
+ .addConverterFactory(GsonConverterFactory.create())
+ .client(okHttpClient)
+ .build()
+ .create(LiveService.class);
+ }
+ return liveService;
+ }
+
+ public LoginResponseEntity login(String username, String password) throws IOException, LoginException {
+ LOGGER.debug("Login attempting with username '{}'", username);
+ LoginResponseEntity loginResponseEntity = BilibiliSecurityHelper.login(
+ this,
+ username,
+ password
+ );
+ bilibiliAccount.copyFrom(loginResponseEntity.toBilibiliAccount());
+ return loginResponseEntity;
+ }
+
+ public RefreshTokenResponseEntity refreshToken() throws IOException, LoginException {
+ LOGGER.debug("RefreshToken attempting with userId '{}'", bilibiliAccount.getUserId());
+ RefreshTokenResponseEntity refreshTokenResponseEntity = BilibiliSecurityHelper.refreshToken(
+ this,
+ bilibiliAccount.getAccessToken(),
+ bilibiliAccount.getRefreshToken()
+ );
+ bilibiliAccount.copyFrom(refreshTokenResponseEntity.toBilibiliAccount());
+ return refreshTokenResponseEntity;
+ }
+
+ public LogoutResponseEntity logout() throws IOException, LoginException {
+ LOGGER.debug("Logout attempting with userId '{}'", bilibiliAccount.getUserId());
+ Long userId = bilibiliAccount.getUserId();
+ LogoutResponseEntity logoutResponseEntity = BilibiliSecurityHelper.logout(this, bilibiliAccount.getAccessToken());
+ bilibiliAccount.reset();
+ LOGGER.debug("Logout succeed with userId: {}", userId);
+ return logoutResponseEntity;
+ }
+
+ @Override
+ public LiveClient getLiveClient(long showRoomId) {
+ return bilibiliAccount.getUserId() == null ? new LiveClient(showRoomId) : new LiveClient(showRoomId, bilibiliAccount.getUserId());
+ }
+
+ public BilibiliClientProperties getBilibiliClientProperties() {
+ return bilibiliClientProperties;
+ }
+
+ public BilibiliAccount getBilibiliAccount() {
+ return bilibiliAccount;
+ }
+}
diff --git a/src/main/java/com/hiczp/bilibili/api/BilibiliAccount.java b/src/main/java/com/hiczp/bilibili/api/BilibiliAccount.java
new file mode 100644
index 0000000..c4cf278
--- /dev/null
+++ b/src/main/java/com/hiczp/bilibili/api/BilibiliAccount.java
@@ -0,0 +1,97 @@
+package com.hiczp.bilibili.api;
+
+public class BilibiliAccount implements BilibiliSecurityContext {
+ private String accessToken;
+ private String refreshToken;
+ private Long userId;
+ private Long expirationTime;
+ private Long loginTime;
+
+ private BilibiliAccount() {
+
+ }
+
+ public BilibiliAccount(String accessToken, String refreshToken, long userId, long expirationTime, long loginTime) {
+ this.accessToken = accessToken;
+ this.refreshToken = refreshToken;
+ this.userId = userId;
+ this.expirationTime = expirationTime;
+ this.loginTime = loginTime;
+ }
+
+ public BilibiliAccount(BilibiliAccount bilibiliAccount) {
+ copyFrom(bilibiliAccount);
+ }
+
+ public static BilibiliAccount emptyInstance() {
+ return new BilibiliAccount();
+ }
+
+ public BilibiliAccount copyFrom(BilibiliAccount bilibiliAccount) {
+ this.accessToken = bilibiliAccount.accessToken;
+ this.refreshToken = bilibiliAccount.refreshToken;
+ this.userId = bilibiliAccount.userId;
+ this.expirationTime = bilibiliAccount.expirationTime;
+ this.loginTime = bilibiliAccount.loginTime;
+ return this;
+ }
+
+ public BilibiliAccount reset() {
+ this.accessToken = null;
+ this.refreshToken = null;
+ this.userId = null;
+ this.expirationTime = null;
+ this.loginTime = null;
+ return this;
+ }
+
+ @Override
+ public String getAccessToken() {
+ return accessToken;
+ }
+
+ public BilibiliAccount setAccessToken(String accessToken) {
+ this.accessToken = accessToken;
+ return this;
+ }
+
+ @Override
+ public String getRefreshToken() {
+ return refreshToken;
+ }
+
+ public BilibiliAccount setRefreshToken(String refreshToken) {
+ this.refreshToken = refreshToken;
+ return this;
+ }
+
+ @Override
+ public Long getUserId() {
+ return userId;
+ }
+
+ public BilibiliAccount setUserId(Long userId) {
+ this.userId = userId;
+ return this;
+ }
+
+ @Override
+ public Long getExpirationTime() {
+ return expirationTime;
+ }
+
+ public BilibiliAccount setExpirationTime(Long expirationTime) {
+ this.expirationTime = expirationTime;
+ return this;
+ }
+
+ @Override
+ public Long getLoginTime() {
+ return loginTime;
+ }
+
+ public BilibiliAccount setLoginTime(Long loginTime) {
+ this.loginTime = loginTime;
+ return this;
+ }
+}
diff --git a/src/main/java/com/hiczp/bilibili/api/BilibiliClientProperties.java b/src/main/java/com/hiczp/bilibili/api/BilibiliClientProperties.java
new file mode 100644
index 0000000..bee2974
--- /dev/null
+++ b/src/main/java/com/hiczp/bilibili/api/BilibiliClientProperties.java
@@ -0,0 +1,85 @@
+package com.hiczp.bilibili.api;
+
+import java.util.Objects;
+
+public class BilibiliClientProperties {
+ private String appKey = "1d8b6e7d45233436";
+ private String appSecret = "560c52ccd288fed045859ed18bffd973";
+ private String hardwareId = "JxdyESFAJkcjEicQbBBsCTlbal5uX2Y";
+ private String scale = "xxhdpi";
+ private String version = "5.15.0.515000";
+ private String build;
+ private String buvId = "JxdyESFAJkcjEicQbBBsCTlbal5uX2Yinfoc";
+
+ private BilibiliClientProperties() {
+ generateBuildProperty();
+ }
+
+ public static BilibiliClientProperties defaultSetting() {
+ return new BilibiliClientProperties();
+ }
+
+ private void generateBuildProperty() {
+ this.build = version.substring(version.lastIndexOf(".") + 1);
+ }
+
+ public String getAppKey() {
+ return appKey;
+ }
+
+ public BilibiliClientProperties setAppKey(String appKey) {
+ this.appKey = appKey;
+ return this;
+ }
+
+ public String getAppSecret() {
+ return appSecret;
+ }
+
+ public BilibiliClientProperties setAppSecret(String appSecret) {
+ this.appSecret = appSecret;
+ return this;
+ }
+
+ public String getHardwareId() {
+ return hardwareId;
+ }
+
+ public BilibiliClientProperties setHardwareId(String hardwareId) {
+ this.hardwareId = hardwareId;
+ return this;
+ }
+
+ public String getScale() {
+ return scale;
+ }
+
+ public BilibiliClientProperties setScale(String scale) {
+ this.scale = scale;
+ return this;
+ }
+
+ public String getVersion() {
+ return version;
+ }
+
+ public BilibiliClientProperties setVersion(String version) {
+ Objects.requireNonNull(version);
+ this.version = version;
+ generateBuildProperty();
+ return this;
+ }
+
+ public String getBuild() {
+ return build;
+ }
+
+ public String getBuvId() {
+ return buvId;
+ }
+
+ public BilibiliClientProperties setBuvId(String buvId) {
+ this.buvId = buvId;
+ return this;
+ }
+}
diff --git a/src/main/java/com/hiczp/bilibili/api/BilibiliRESTAPI.java b/src/main/java/com/hiczp/bilibili/api/BilibiliRESTAPI.java
deleted file mode 100644
index 847213b..0000000
--- a/src/main/java/com/hiczp/bilibili/api/BilibiliRESTAPI.java
+++ /dev/null
@@ -1,255 +0,0 @@
-package com.hiczp.bilibili.api;
-
-import com.hiczp.bilibili.api.interceptor.*;
-import com.hiczp.bilibili.api.live.LiveService;
-import com.hiczp.bilibili.api.passport.PassportService;
-import com.hiczp.bilibili.api.passport.entity.*;
-import okhttp3.OkHttpClient;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import retrofit2.Retrofit;
-import retrofit2.converter.gson.GsonConverterFactory;
-import sun.security.rsa.RSAPublicKeyImpl;
-
-import javax.security.auth.login.LoginException;
-import java.io.IOException;
-import java.security.InvalidKeyException;
-import java.security.interfaces.RSAPublicKey;
-import java.time.Instant;
-import java.util.Base64;
-
-//TODO 尚未实现自动 refreshToken 拦截器
-public class BilibiliRESTAPI {
- private static final Logger LOGGER = LoggerFactory.getLogger(BilibiliRESTAPI.class);
- private static String accessToken;
- private static String refreshToken;
- private static int mid;
-
- private static LiveService liveService;
- private static PassportService passportService;
-
- public static LiveService getLiveService() {
- if (liveService == null) {
- LOGGER.debug("Init LiveService");
- OkHttpClient okHttpClient = new OkHttpClient.Builder()
- .addInterceptor(new AddFixedHeadersInterceptor(
- "Display-ID", Utils.calculateDisplayId(),
- "Buvid", Utils.getBUVID(),
- "User-Agent", "Mozilla/5.0 BiliDroid/5.15.0 (bbcallen@gmail.com)",
- "Device-ID", Utils.getHardwareId()
- )).addInterceptor(new AddFixedParamsInterceptor(
- "_device", "android",
- "_hwid", Utils.getHardwareId(),
- "build", Utils.getBUILD(),
- "mobi_app", "android",
- "platform", "android",
- "scale", Utils.getScale(),
- "src", "google",
- "trace_id", Utils.calculateTraceId(),
- "ts", Long.toString(Instant.now().getEpochSecond()),
- "version", Utils.getVERSION()
- )).addInterceptor(AddAccessKeyInterceptor.getInstance())
- .addInterceptor(AddAppKeyInterceptor.getInstance())
- .addInterceptor(SortParamsAndSignInterceptor.getInstance())
- .addInterceptor(ErrorResponseConverterInterceptor.getInstance())
- .addNetworkInterceptor(BodyHttpLoggingInterceptor.getInstance())
- .build();
-
- liveService = new Retrofit.Builder()
- .baseUrl("http://api.live.bilibili.com/")
- .addConverterFactory(GsonConverterFactory.create())
- .client(okHttpClient)
- .build()
- .create(LiveService.class);
- }
- return liveService;
- }
-
- //TODO 不明确客户端访问 passport.bilibili.com 时使用的 UA
- public static PassportService getPassportService() {
- if (passportService == null) {
- LOGGER.debug("Init PassportService");
- OkHttpClient okHttpClient = new OkHttpClient().newBuilder()
- .addInterceptor(AddAppKeyInterceptor.getInstance())
- .addInterceptor(SortParamsAndSignInterceptor.getInstance())
- .addInterceptor(ErrorResponseConverterInterceptor.getInstance())
- .addNetworkInterceptor(BodyHttpLoggingInterceptor.getInstance())
- .build();
-
- passportService = new Retrofit.Builder()
- .baseUrl("https://passport.bilibili.com/")
- .addConverterFactory(GsonConverterFactory.create())
- .client(okHttpClient)
- .build()
- .create(PassportService.class);
- }
- return passportService;
- }
-
- public static LoginResponseEntity login(String username, String password) throws IOException, LoginException {
- LOGGER.debug("Login attempt with username '{}'", username);
- PassportService passportService = getPassportService();
- KeyEntity keyEntity = passportService.getKey().execute().body();
- //服务器返回异常错误码
- if (keyEntity.getCode() != 0) {
- throw new IOException(keyEntity.getMessage());
- }
- RSAPublicKey rsaPublicKey;
- try {
- rsaPublicKey = new RSAPublicKeyImpl(
- Base64.getDecoder().decode(
- keyEntity.getData().getKey()
- .replace("-----BEGIN PUBLIC KEY-----", "")
- .replace("-----END PUBLIC KEY-----", "")
- .replaceAll("\n", "")
- .getBytes()
- )
- );
- } catch (InvalidKeyException e) {
- throw new IOException("get broken RSA public key");
- }
- String cipheredPassword;
- try {
- cipheredPassword = Utils.cipherPassword(
- password,
- keyEntity.getData().getHash(),
- rsaPublicKey
- );
- } catch (InvalidKeyException e) {
- throw new IOException("get broken RSA public key");
- }
- LoginResponseEntity loginResponseEntity = passportService.login(
- username, cipheredPassword
- ).execute().body();
- int code = loginResponseEntity.getCode();
- switch (code) {
- case ServerErrorCode.Passport.BAD_REQUEST: {
- throw new IOException("request error");
- }
- case ServerErrorCode.Passport.USERNAME_OR_PASSWORD_INVALID: {
- throw new LoginException("username or password invalid");
- }
- case ServerErrorCode.Passport.CANT_DECRYPT_RSA_PASSWORD: {
- throw new LoginException("password error or hash expired");
- }
- default: {
- //其他错误码
- if (code != 0) {
- throw new IOException(loginResponseEntity.getMessage());
- }
- }
- }
- BilibiliRESTAPI.accessToken = loginResponseEntity.getData().getAccessToken();
- BilibiliRESTAPI.refreshToken = loginResponseEntity.getData().getRefreshToken();
- BilibiliRESTAPI.mid = loginResponseEntity.getData().getMid();
- LOGGER.info("Login success with username '{}'", username);
- LOGGER.debug("mid: {}", BilibiliRESTAPI.mid);
- return loginResponseEntity;
- }
-
- public static InfoEntity getAccountInfo() throws IOException, LoginException {
- InfoEntity infoEntity = getPassportService().getInfo(accessToken).execute().body();
- int code = infoEntity.getCode();
- switch (code) {
- case ServerErrorCode.Passport.NO_LOGIN: {
- throw new LoginException("please try after login");
- }
- default: {
- //其他错误码
- if (code != 0) {
- throw new IOException(infoEntity.getMessage());
- }
- }
- }
- BilibiliRESTAPI.mid = infoEntity.getData().getMid();
- return infoEntity;
- }
-
- public static RefreshTokenResponseEntity refreshToken() throws IOException, LoginException {
- return refreshToken(accessToken, refreshToken);
- }
-
- public static RefreshTokenResponseEntity refreshToken(String accessToken, String refreshToken) throws IOException, LoginException {
- RefreshTokenResponseEntity refreshTokenResponseEntity = getPassportService().refreshToken(accessToken, refreshToken).execute().body();
- int code = refreshTokenResponseEntity.getCode();
- switch (code) {
- case ServerErrorCode.Passport.NO_LOGIN: {
- throw new LoginException("access token can't be empty");
- }
- case ServerErrorCode.Passport.ACCESS_TOKEN_NOT_FOUND: {
- throw new LoginException("access token invalid");
- }
- case ServerErrorCode.Passport.REFRESH_TOKEN_NOT_MATCH: {
- throw new LoginException("access token and refresh token mismatch");
- }
- default: {
- //其他错误码
- if (code != 0) {
- throw new IOException(refreshTokenResponseEntity.getMessage());
- }
- }
- }
- BilibiliRESTAPI.accessToken = refreshTokenResponseEntity.getData().getAccessToken();
- BilibiliRESTAPI.refreshToken = refreshTokenResponseEntity.getData().getRefreshToken();
- BilibiliRESTAPI.mid = refreshTokenResponseEntity.getData().getMid();
- LOGGER.info("Access token refreshed");
- LOGGER.debug("Expires in {} seconds later", refreshTokenResponseEntity.getData().getExpiresIn());
- return refreshTokenResponseEntity;
- }
-
- public static LogoutResponseEntity logout() throws IOException, LoginException {
- return logout(accessToken);
- }
-
- public static LogoutResponseEntity logout(String accessToken) throws IOException, LoginException {
- LogoutResponseEntity logoutResponseEntity = getPassportService().logout(accessToken).execute().body();
- int code = logoutResponseEntity.getCode();
- switch (code) {
- case ServerErrorCode.Passport.NO_LOGIN: {
- throw new LoginException("access token can't be empty");
- }
- case ServerErrorCode.Passport.ACCESS_TOKEN_NOT_FOUND: {
- throw new LoginException("access token invalid");
- }
- default: {
- //其他错误码
- if (code != 0) {
- throw new IOException(logoutResponseEntity.getMessage());
- }
- }
- }
- if (logoutResponseEntity.getCode() != 0) {
- throw new LoginException("access token invalid");
- }
- LOGGER.info("Logout success");
- LOGGER.debug("Old mid: {}", BilibiliRESTAPI.mid);
- BilibiliRESTAPI.accessToken = null;
- BilibiliRESTAPI.refreshToken = null;
- BilibiliRESTAPI.mid = 0;
- return logoutResponseEntity;
- }
-
- public static String getAccessToken() {
- return accessToken;
- }
-
- public static void setAccessToken(String accessToken) {
- BilibiliRESTAPI.accessToken = accessToken;
- }
-
- public static String getRefreshToken() {
- return refreshToken;
- }
-
- public static void setRefreshToken(String refreshToken) {
- BilibiliRESTAPI.refreshToken = refreshToken;
- }
-
- public static int getMid() {
- return mid;
- }
-
- public static void setMid(int mid) {
- BilibiliRESTAPI.mid = mid;
- }
-}
diff --git a/src/main/java/com/hiczp/bilibili/api/BilibiliSecurityContext.java b/src/main/java/com/hiczp/bilibili/api/BilibiliSecurityContext.java
new file mode 100644
index 0000000..d672f98
--- /dev/null
+++ b/src/main/java/com/hiczp/bilibili/api/BilibiliSecurityContext.java
@@ -0,0 +1,13 @@
+package com.hiczp.bilibili.api;
+
+public interface BilibiliSecurityContext {
+ String getAccessToken();
+
+ String getRefreshToken();
+
+ Long getUserId();
+
+ Long getExpirationTime();
+
+ Long getLoginTime();
+}
diff --git a/src/main/java/com/hiczp/bilibili/api/BilibiliSecurityHelper.java b/src/main/java/com/hiczp/bilibili/api/BilibiliSecurityHelper.java
new file mode 100644
index 0000000..5278ffe
--- /dev/null
+++ b/src/main/java/com/hiczp/bilibili/api/BilibiliSecurityHelper.java
@@ -0,0 +1,137 @@
+package com.hiczp.bilibili.api;
+
+import com.hiczp.bilibili.api.passport.PassportService;
+import com.hiczp.bilibili.api.passport.entity.KeyEntity;
+import com.hiczp.bilibili.api.passport.entity.LoginResponseEntity;
+import com.hiczp.bilibili.api.passport.entity.LogoutResponseEntity;
+import com.hiczp.bilibili.api.passport.entity.RefreshTokenResponseEntity;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import sun.security.rsa.RSAPublicKeyImpl;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.NoSuchPaddingException;
+import javax.security.auth.login.LoginException;
+import java.io.IOException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.interfaces.RSAPublicKey;
+import java.util.Base64;
+
+public class BilibiliSecurityHelper {
+ private static final Logger LOGGER = LoggerFactory.getLogger(BilibiliSecurityHelper.class);
+
+ public static LoginResponseEntity login(BilibiliServiceProvider bilibiliServiceProvider,
+ String username,
+ String password) throws IOException, LoginException {
+ PassportService passportService = bilibiliServiceProvider.getPassportService();
+ KeyEntity keyEntity = passportService.getKey().execute().body();
+ //服务器返回异常错误码
+ if (keyEntity.getCode() != 0) {
+ throw new IOException(keyEntity.getMessage());
+ }
+ //解析 RSA 公钥
+ RSAPublicKey rsaPublicKey;
+ try {
+ rsaPublicKey = new RSAPublicKeyImpl(
+ Base64.getDecoder().decode(
+ keyEntity.getData().getKey()
+ .replace("-----BEGIN PUBLIC KEY-----", "")
+ .replace("-----END PUBLIC KEY-----", "")
+ .replaceAll("\n", "")
+ .getBytes()
+ )
+ );
+ } catch (InvalidKeyException e) {
+ throw new IOException("get broken RSA public key");
+ }
+ //加密密码
+ String cipheredPassword;
+ try {
+ Cipher cipher = Cipher.getInstance("RSA");
+ cipher.init(Cipher.ENCRYPT_MODE, rsaPublicKey);
+ cipheredPassword = new String(
+ Base64.getEncoder().encode(
+ cipher.doFinal((keyEntity.getData().getHash() + password).getBytes())
+ )
+ );
+ } catch (NoSuchAlgorithmException | NoSuchPaddingException | IllegalBlockSizeException | BadPaddingException e) {
+ throw new Error(e);
+ } catch (InvalidKeyException e) {
+ throw new IOException("get broken RSA public key");
+ }
+ //发起登录请求
+ LoginResponseEntity loginResponseEntity = passportService.login(
+ username, cipheredPassword
+ ).execute().body();
+ //判断返回值
+ switch (loginResponseEntity.getCode()) {
+ case ServerErrorCode.Common.OK: {
+ LOGGER.info("Login succeed with username '{}', userId: {}", username, loginResponseEntity.getData().getMid());
+ return loginResponseEntity;
+ }
+ case ServerErrorCode.Passport.USERNAME_OR_PASSWORD_INVALID: {
+ throw new LoginException("username or password invalid");
+ }
+ case ServerErrorCode.Passport.CANT_DECRYPT_RSA_PASSWORD: {
+ throw new LoginException("password error or hash expired");
+ }
+ default: {
+ //其他错误码
+ throw new IOException(loginResponseEntity.getMessage());
+ }
+ }
+ }
+
+ public static RefreshTokenResponseEntity refreshToken(BilibiliServiceProvider bilibiliServiceProvider,
+ String accessToken,
+ String refreshToken) throws IOException, LoginException {
+ RefreshTokenResponseEntity refreshTokenResponseEntity = bilibiliServiceProvider.getPassportService()
+ .refreshToken(
+ accessToken,
+ refreshToken
+ ).execute()
+ .body();
+ switch (refreshTokenResponseEntity.getCode()) {
+ case ServerErrorCode.Common.OK: {
+ LOGGER.info("Access token refreshed, Expires in {} seconds later", refreshTokenResponseEntity.getData().getExpiresIn());
+ return refreshTokenResponseEntity;
+ }
+ case ServerErrorCode.Common.NO_LOGIN: {
+ throw new LoginException("access token can't be empty");
+ }
+ case ServerErrorCode.Passport.ACCESS_TOKEN_NOT_FOUND: {
+ throw new LoginException("access token invalid");
+ }
+ case ServerErrorCode.Passport.REFRESH_TOKEN_NOT_MATCH: {
+ throw new LoginException("access token and refresh token mismatch");
+ }
+ default: {
+ //其他错误码
+ throw new IOException(refreshTokenResponseEntity.getMessage());
+ }
+ }
+ }
+
+ public static LogoutResponseEntity logout(BilibiliServiceProvider bilibiliServiceProvider,
+ String accessToken) throws IOException, LoginException {
+ LogoutResponseEntity logoutResponseEntity = bilibiliServiceProvider.getPassportService().logout(accessToken).execute().body();
+ switch (logoutResponseEntity.getCode()) {
+ case ServerErrorCode.Common.OK: {
+ return logoutResponseEntity;
+ }
+ case ServerErrorCode.Common.NO_LOGIN: {
+ throw new LoginException("access token can't be empty or invalid");
+ }
+ case ServerErrorCode.Passport.ACCESS_TOKEN_NOT_FOUND: {
+ throw new LoginException("access token invalid");
+ }
+ default: {
+ //其他错误码
+ throw new IOException(logoutResponseEntity.getMessage());
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/hiczp/bilibili/api/BilibiliServiceProvider.java b/src/main/java/com/hiczp/bilibili/api/BilibiliServiceProvider.java
new file mode 100644
index 0000000..432e79e
--- /dev/null
+++ b/src/main/java/com/hiczp/bilibili/api/BilibiliServiceProvider.java
@@ -0,0 +1,10 @@
+package com.hiczp.bilibili.api;
+
+import com.hiczp.bilibili.api.live.LiveService;
+import com.hiczp.bilibili.api.passport.PassportService;
+
+public interface BilibiliServiceProvider {
+ PassportService getPassportService();
+
+ LiveService getLiveService();
+}
diff --git a/src/main/java/com/hiczp/bilibili/api/LiveClientProvider.java b/src/main/java/com/hiczp/bilibili/api/LiveClientProvider.java
new file mode 100644
index 0000000..d623873
--- /dev/null
+++ b/src/main/java/com/hiczp/bilibili/api/LiveClientProvider.java
@@ -0,0 +1,7 @@
+package com.hiczp.bilibili.api;
+
+import com.hiczp.bilibili.api.live.socket.LiveClient;
+
+public interface LiveClientProvider {
+ LiveClient getLiveClient(long showRoomId);
+}
diff --git a/src/main/java/com/hiczp/bilibili/api/ServerErrorCode.java b/src/main/java/com/hiczp/bilibili/api/ServerErrorCode.java
index 3547822..44a077a 100644
--- a/src/main/java/com/hiczp/bilibili/api/ServerErrorCode.java
+++ b/src/main/java/com/hiczp/bilibili/api/ServerErrorCode.java
@@ -3,15 +3,16 @@ package com.hiczp.bilibili.api;
//不知道为什么错误码都要加负号
public class ServerErrorCode {
public static class Common {
+ public static final int OK = 0;
public static final int NO_LOGIN = -101;
public static final int BAD_REQUEST = -400;
+ //message 可能为 Illegal request
+ public static final int FORBIDDEN = -403;
public static final int NOT_FOUND = -404;
public static final int INTERNAL_SERVER_ERROR = -500;
}
public static class Passport {
- public static final int NO_LOGIN = -101;
- public static final int BAD_REQUEST = -400;
//用户名不存在
public static final int USERNAME_OR_PASSWORD_INVALID = -629;
//密码不可解密或者密码错误
@@ -24,11 +25,7 @@ public class ServerErrorCode {
//访问 isFollowed 时如果未登录, 返回的是 3, message 是 "user no login"
public static final int USER_NO_LOGIN = 3;
//正常的 API 如果未登录, 返回的是 -101
- public static final int NO_LOGIN = -101;
- public static final int BAD_REQUEST = -400;
public static final int NOT_FOUND = -404;
- //弹幕长度超出限制时也是 -500
- public static final int INTERNAL_SERVER_ERROR = -500;
//已经领取过这个宝箱
public static final int THIS_SILVER_TASK_ALREADY_TOOK = -903;
//今天所有的宝箱已经领完
diff --git a/src/main/java/com/hiczp/bilibili/api/Utils.java b/src/main/java/com/hiczp/bilibili/api/Utils.java
deleted file mode 100644
index 7fdc251..0000000
--- a/src/main/java/com/hiczp/bilibili/api/Utils.java
+++ /dev/null
@@ -1,103 +0,0 @@
-package com.hiczp.bilibili.api;
-
-import javax.crypto.BadPaddingException;
-import javax.crypto.Cipher;
-import javax.crypto.IllegalBlockSizeException;
-import javax.crypto.NoSuchPaddingException;
-import java.math.BigInteger;
-import java.security.InvalidKeyException;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.security.interfaces.RSAPublicKey;
-import java.text.SimpleDateFormat;
-import java.time.Instant;
-import java.util.Base64;
-import java.util.Collections;
-import java.util.Date;
-import java.util.List;
-import java.util.stream.Collectors;
-
-public final class Utils {
- private static final String APP_KEY = "1d8b6e7d45233436";
- private static final String APP_SECRET = "560c52ccd288fed045859ed18bffd973";
- private static final String HARDWARE_ID = "JxdyESFAJkcjEicQbBBsCTlbal5uX2Y";
- private static final String SCALE = "xxhdpi";
- private static final String VERSION = "5.15.0.515000";
- private static final String BUILD = VERSION.substring(VERSION.lastIndexOf(".") + 1);
- private static final String BUVID = "JxdyESFAJkcjEicQbBBsCTlbal5uX2Yinfoc";
-
- private static final SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMddHHmm000ss");
-
- //Display-ID 的值在未登录前为 Buvid-客户端启动时间, 在登录后为 mid-客户端启动时间
- public static String calculateDisplayId() {
- int mid = BilibiliRESTAPI.getMid();
- return mid != 0 ? calculateDisplayId(mid) : String.format("%s-%d", BUVID, Instant.now().getEpochSecond());
- }
-
- public static String calculateDisplayId(int mid) {
- return String.format("%d-%d", mid, Instant.now().getEpochSecond());
- }
-
- public static String calculateTraceId() {
- return simpleDateFormat.format(new Date());
- }
-
- //排序 params 并计算 sign
- //传入值为 name1=value1 形式
- public static String calculateSign(List nameAndValues) {
- Collections.sort(nameAndValues);
- String encodedQuery = nameAndValues.stream().collect(Collectors.joining("&"));
- try {
- MessageDigest messageDigest = MessageDigest.getInstance("MD5");
- messageDigest.update((encodedQuery + APP_SECRET).getBytes());
- String md5 = new BigInteger(1, messageDigest.digest()).toString(16);
- //md5 不满 32 位时左边加 0
- return ("00000000000000000000000000000000" + md5).substring(md5.length());
- } catch (NoSuchAlgorithmException e) {
- throw new Error(e);
- }
- }
-
- //加密密码
- public static String cipherPassword(String password, String hash, RSAPublicKey rsaPublicKey) throws InvalidKeyException {
- try {
- Cipher cipher = Cipher.getInstance("RSA");
- cipher.init(Cipher.ENCRYPT_MODE, rsaPublicKey);
- return new String(
- Base64.getEncoder().encode(
- cipher.doFinal((hash + password).getBytes())
- )
- );
- } catch (NoSuchAlgorithmException | NoSuchPaddingException | IllegalBlockSizeException | BadPaddingException e) {
- throw new Error(e);
- }
- }
-
- public static String getAppKey() {
- return APP_KEY;
- }
-
- public static String getAppSecret() {
- return APP_SECRET;
- }
-
- public static String getHardwareId() {
- return HARDWARE_ID;
- }
-
- public static String getScale() {
- return SCALE;
- }
-
- public static String getVERSION() {
- return VERSION;
- }
-
- public static String getBUILD() {
- return BUILD;
- }
-
- public static String getBUVID() {
- return BUVID;
- }
-}
diff --git a/src/main/java/com/hiczp/bilibili/api/interceptor/AddAccessKeyInterceptor.java b/src/main/java/com/hiczp/bilibili/api/interceptor/AddAccessKeyInterceptor.java
index f5cfc83..9d4eb4c 100644
--- a/src/main/java/com/hiczp/bilibili/api/interceptor/AddAccessKeyInterceptor.java
+++ b/src/main/java/com/hiczp/bilibili/api/interceptor/AddAccessKeyInterceptor.java
@@ -1,6 +1,6 @@
package com.hiczp.bilibili.api.interceptor;
-import com.hiczp.bilibili.api.BilibiliRESTAPI;
+import com.hiczp.bilibili.api.BilibiliSecurityContext;
import okhttp3.HttpUrl;
import okhttp3.Interceptor;
import okhttp3.Request;
@@ -9,20 +9,17 @@ import okhttp3.Response;
import java.io.IOException;
public class AddAccessKeyInterceptor implements Interceptor {
- private static AddAccessKeyInterceptor addAccessKeyInterceptor;
+ private BilibiliSecurityContext bilibiliSecurityContext;
- public static AddAccessKeyInterceptor getInstance() {
- if (addAccessKeyInterceptor == null) {
- addAccessKeyInterceptor = new AddAccessKeyInterceptor();
- }
- return addAccessKeyInterceptor;
+ public AddAccessKeyInterceptor(BilibiliSecurityContext bilibiliSecurityContext) {
+ this.bilibiliSecurityContext = bilibiliSecurityContext;
}
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
HttpUrl.Builder httpUrlBuilder = request.url().newBuilder();
- String accessKey = BilibiliRESTAPI.getAccessToken();
+ String accessKey = bilibiliSecurityContext.getAccessToken();
if (accessKey != null && accessKey.length() != 0) {
httpUrlBuilder.addQueryParameter("access_key", accessKey);
}
diff --git a/src/main/java/com/hiczp/bilibili/api/interceptor/AddAppKeyInterceptor.java b/src/main/java/com/hiczp/bilibili/api/interceptor/AddAppKeyInterceptor.java
index 56e6154..1256de8 100644
--- a/src/main/java/com/hiczp/bilibili/api/interceptor/AddAppKeyInterceptor.java
+++ b/src/main/java/com/hiczp/bilibili/api/interceptor/AddAppKeyInterceptor.java
@@ -1,6 +1,6 @@
package com.hiczp.bilibili.api.interceptor;
-import com.hiczp.bilibili.api.Utils;
+import com.hiczp.bilibili.api.BilibiliClientProperties;
import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;
@@ -8,13 +8,10 @@ import okhttp3.Response;
import java.io.IOException;
public class AddAppKeyInterceptor implements Interceptor {
- private static AddAppKeyInterceptor addAppKeyInterceptor;
+ private BilibiliClientProperties bilibiliClientDefinition;
- public static AddAppKeyInterceptor getInstance() {
- if (addAppKeyInterceptor == null) {
- addAppKeyInterceptor = new AddAppKeyInterceptor();
- }
- return addAppKeyInterceptor;
+ public AddAppKeyInterceptor(BilibiliClientProperties bilibiliClientDefinition) {
+ this.bilibiliClientDefinition = bilibiliClientDefinition;
}
@Override
@@ -22,7 +19,7 @@ public class AddAppKeyInterceptor implements Interceptor {
Request request = chain.request();
return chain.proceed(request.newBuilder().url(
request.url().newBuilder()
- .addQueryParameter("appkey", Utils.getAppKey())
+ .addQueryParameter("appkey", bilibiliClientDefinition.getAppKey())
.build()
).build());
}
diff --git a/src/main/java/com/hiczp/bilibili/api/interceptor/AddDynamicHeadersInterceptor.java b/src/main/java/com/hiczp/bilibili/api/interceptor/AddDynamicHeadersInterceptor.java
new file mode 100644
index 0000000..d5ee178
--- /dev/null
+++ b/src/main/java/com/hiczp/bilibili/api/interceptor/AddDynamicHeadersInterceptor.java
@@ -0,0 +1,29 @@
+package com.hiczp.bilibili.api.interceptor;
+
+import okhttp3.Interceptor;
+import okhttp3.Request;
+import okhttp3.Response;
+
+import java.io.IOException;
+import java.util.function.Supplier;
+
+public class AddDynamicHeadersInterceptor implements Interceptor {
+ private Supplier[] headerAndValues;
+
+ @SafeVarargs
+ public AddDynamicHeadersInterceptor(Supplier... headerAndValues) {
+ if (headerAndValues.length % 2 != 0) {
+ throw new IllegalArgumentException("Header must have value");
+ }
+ this.headerAndValues = headerAndValues;
+ }
+
+ @Override
+ public Response intercept(Chain chain) throws IOException {
+ Request.Builder requestBuilder = chain.request().newBuilder();
+ for (int i = 0; i < headerAndValues.length; i += 2) {
+ requestBuilder.addHeader(headerAndValues[i].get(), headerAndValues[i + 1].get());
+ }
+ return chain.proceed(requestBuilder.build());
+ }
+}
diff --git a/src/main/java/com/hiczp/bilibili/api/interceptor/AddDynamicParamsInterceptor.java b/src/main/java/com/hiczp/bilibili/api/interceptor/AddDynamicParamsInterceptor.java
new file mode 100644
index 0000000..968c4c5
--- /dev/null
+++ b/src/main/java/com/hiczp/bilibili/api/interceptor/AddDynamicParamsInterceptor.java
@@ -0,0 +1,31 @@
+package com.hiczp.bilibili.api.interceptor;
+
+import okhttp3.HttpUrl;
+import okhttp3.Interceptor;
+import okhttp3.Request;
+import okhttp3.Response;
+
+import java.io.IOException;
+import java.util.function.Supplier;
+
+public class AddDynamicParamsInterceptor implements Interceptor {
+ private Supplier[] paramAndValues;
+
+ @SafeVarargs
+ public AddDynamicParamsInterceptor(Supplier... paramAndValues) {
+ if (paramAndValues.length % 2 != 0) {
+ throw new IllegalArgumentException("Parameter must have value");
+ }
+ this.paramAndValues = paramAndValues;
+ }
+
+ @Override
+ public Response intercept(Chain chain) throws IOException {
+ Request request = chain.request();
+ HttpUrl.Builder httpUrlBuilder = request.url().newBuilder();
+ for (int i = 0; i < paramAndValues.length; i += 2) {
+ httpUrlBuilder.addQueryParameter(paramAndValues[i].get(), paramAndValues[i + 1].get());
+ }
+ return chain.proceed(request.newBuilder().url(httpUrlBuilder.build()).build());
+ }
+}
diff --git a/src/main/java/com/hiczp/bilibili/api/interceptor/AddFixedHeadersInterceptor.java b/src/main/java/com/hiczp/bilibili/api/interceptor/AddFixedHeadersInterceptor.java
index 531702d..92fb7c3 100644
--- a/src/main/java/com/hiczp/bilibili/api/interceptor/AddFixedHeadersInterceptor.java
+++ b/src/main/java/com/hiczp/bilibili/api/interceptor/AddFixedHeadersInterceptor.java
@@ -11,7 +11,7 @@ public class AddFixedHeadersInterceptor implements Interceptor {
public AddFixedHeadersInterceptor(String... headerAndValues) {
if (headerAndValues.length % 2 != 0) {
- throw new IllegalArgumentException("header must have value");
+ throw new IllegalArgumentException("Header must have value");
}
this.headerAndValues = headerAndValues;
}
diff --git a/src/main/java/com/hiczp/bilibili/api/interceptor/AddFixedParamsInterceptor.java b/src/main/java/com/hiczp/bilibili/api/interceptor/AddFixedParamsInterceptor.java
index 83b3e7b..230b3e2 100644
--- a/src/main/java/com/hiczp/bilibili/api/interceptor/AddFixedParamsInterceptor.java
+++ b/src/main/java/com/hiczp/bilibili/api/interceptor/AddFixedParamsInterceptor.java
@@ -12,7 +12,7 @@ public class AddFixedParamsInterceptor implements Interceptor {
public AddFixedParamsInterceptor(String... paramAndValues) {
if (paramAndValues.length % 2 != 0) {
- throw new IllegalArgumentException("param must have value");
+ throw new IllegalArgumentException("Parameter must have value");
}
this.paramAndValues = paramAndValues;
}
diff --git a/src/main/java/com/hiczp/bilibili/api/interceptor/BodyHttpLoggingInterceptor.java b/src/main/java/com/hiczp/bilibili/api/interceptor/BodyHttpLoggingInterceptor.java
deleted file mode 100644
index 917bce8..0000000
--- a/src/main/java/com/hiczp/bilibili/api/interceptor/BodyHttpLoggingInterceptor.java
+++ /dev/null
@@ -1,14 +0,0 @@
-package com.hiczp.bilibili.api.interceptor;
-
-import okhttp3.logging.HttpLoggingInterceptor;
-
-public class BodyHttpLoggingInterceptor {
- private static HttpLoggingInterceptor httpLoggingInterceptor;
-
- public static HttpLoggingInterceptor getInstance() {
- if (httpLoggingInterceptor == null) {
- httpLoggingInterceptor = new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY);
- }
- return httpLoggingInterceptor;
- }
-}
diff --git a/src/main/java/com/hiczp/bilibili/api/interceptor/ErrorResponseConverterInterceptor.java b/src/main/java/com/hiczp/bilibili/api/interceptor/ErrorResponseConverterInterceptor.java
index 1621c3c..31b0311 100644
--- a/src/main/java/com/hiczp/bilibili/api/interceptor/ErrorResponseConverterInterceptor.java
+++ b/src/main/java/com/hiczp/bilibili/api/interceptor/ErrorResponseConverterInterceptor.java
@@ -6,8 +6,6 @@ import okhttp3.Response;
import okhttp3.ResponseBody;
import okio.Buffer;
import okio.BufferedSource;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
@@ -15,17 +13,8 @@ import java.nio.charset.StandardCharsets;
//由于服务器返回错误时的 data 字段类型不固定, 会导致 json 反序列化出错.
//该拦截器将在返回的 code 不为 0 时, 将 response 转换为包含一个空 data 的 json 字符串.
public class ErrorResponseConverterInterceptor implements Interceptor {
- private static final Logger LOGGER = LoggerFactory.getLogger(ErrorResponseConverterInterceptor.class);
private static final JsonParser JSON_PARSER = new JsonParser();
private static final Gson GSON = new Gson();
- private static ErrorResponseConverterInterceptor errorResponseConverterInterceptor;
-
- public static ErrorResponseConverterInterceptor getInstance() {
- if (errorResponseConverterInterceptor == null) {
- errorResponseConverterInterceptor = new ErrorResponseConverterInterceptor();
- }
- return errorResponseConverterInterceptor;
- }
@Override
public Response intercept(Chain chain) throws IOException {
diff --git a/src/main/java/com/hiczp/bilibili/api/interceptor/SortParamsAndSignInterceptor.java b/src/main/java/com/hiczp/bilibili/api/interceptor/SortParamsAndSignInterceptor.java
index 06497a3..03c4732 100644
--- a/src/main/java/com/hiczp/bilibili/api/interceptor/SortParamsAndSignInterceptor.java
+++ b/src/main/java/com/hiczp/bilibili/api/interceptor/SortParamsAndSignInterceptor.java
@@ -1,6 +1,6 @@
package com.hiczp.bilibili.api.interceptor;
-import com.hiczp.bilibili.api.Utils;
+import com.hiczp.bilibili.api.BilibiliClientProperties;
import okhttp3.HttpUrl;
import okhttp3.Interceptor;
import okhttp3.Request;
@@ -8,20 +8,21 @@ import okhttp3.Response;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
+import java.math.BigInteger;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
public class SortParamsAndSignInterceptor implements Interceptor {
- private static SortParamsAndSignInterceptor sortParamsAndSignInterceptor;
+ private BilibiliClientProperties bilibiliClientDefinition;
- public static SortParamsAndSignInterceptor getInstance() {
- if (sortParamsAndSignInterceptor == null) {
- sortParamsAndSignInterceptor = new SortParamsAndSignInterceptor();
- }
- return sortParamsAndSignInterceptor;
+ public SortParamsAndSignInterceptor(BilibiliClientProperties bilibiliClientDefinition) {
+ this.bilibiliClientDefinition = bilibiliClientDefinition;
}
@Override
@@ -39,11 +40,30 @@ public class SortParamsAndSignInterceptor implements Interceptor {
}
)
);
- nameAndValues.add(String.format("%s=%s", "sign", Utils.calculateSign(nameAndValues)));
+ Collections.sort(nameAndValues);
+ nameAndValues.add(String.format("%s=%s", "sign", calculateSign(nameAndValues)));
return chain.proceed(
request.newBuilder()
- .url(httpUrl.newBuilder().encodedQuery(nameAndValues.stream().collect(Collectors.joining("&"))).build())
+ .url(httpUrl.newBuilder().encodedQuery(generateQuery(nameAndValues)).build())
.build()
);
}
+
+ private String generateQuery(List nameAndValues) {
+ return nameAndValues.stream().collect(Collectors.joining("&"));
+ }
+
+ //排序 params 并计算 sign
+ //传入值为 name1=value1 形式
+ private String calculateSign(List nameAndValues) {
+ try {
+ MessageDigest messageDigest = MessageDigest.getInstance("MD5");
+ messageDigest.update((generateQuery(nameAndValues) + bilibiliClientDefinition.getAppSecret()).getBytes());
+ String md5 = new BigInteger(1, messageDigest.digest()).toString(16);
+ //md5 不满 32 位时左边加 0
+ return ("00000000000000000000000000000000" + md5).substring(md5.length());
+ } catch (NoSuchAlgorithmException e) {
+ throw new Error(e);
+ }
+ }
}
diff --git a/src/main/java/com/hiczp/bilibili/api/live/LiveService.java b/src/main/java/com/hiczp/bilibili/api/live/LiveService.java
index b467a22..0886703 100644
--- a/src/main/java/com/hiczp/bilibili/api/live/LiveService.java
+++ b/src/main/java/com/hiczp/bilibili/api/live/LiveService.java
@@ -71,8 +71,8 @@ public interface LiveService {
@POST("api/sendmsg")
@FormUrlEncoded
- Call sendBulletScreen(@Field("cid") int cid,
- @Field("mid") int mid,
+ Call sendBulletScreen(@Field("cid") long cid,
+ @Field("mid") long mid,
@Field("msg") String message,
@Field("rnd") long random,
@Field("mode") int mode,
diff --git a/src/main/java/com/hiczp/bilibili/api/live/entity/BulletScreenEntity.java b/src/main/java/com/hiczp/bilibili/api/live/entity/BulletScreenEntity.java
index 7382ccd..d0bf8ed 100644
--- a/src/main/java/com/hiczp/bilibili/api/live/entity/BulletScreenEntity.java
+++ b/src/main/java/com/hiczp/bilibili/api/live/entity/BulletScreenEntity.java
@@ -1,12 +1,11 @@
package com.hiczp.bilibili.api.live.entity;
import com.google.gson.annotations.SerializedName;
-import com.hiczp.bilibili.api.BilibiliRESTAPI;
public class BulletScreenEntity {
- private int cid;
+ private long cid;
- private int mid = BilibiliRESTAPI.getMid();
+ private long mid;
//弹幕长度限制为 LiveRoomInfoEntity.getData().getMsgLength(), 但是实际上所有房间的弹幕长度限制都是 20
@SerializedName("msg")
@@ -38,25 +37,26 @@ public class BulletScreenEntity {
private String playTime = "0.0";
- public BulletScreenEntity(int cid, String message) {
+ public BulletScreenEntity(long cid, long mid, String message) {
this.cid = cid;
+ this.mid = mid;
this.message = message;
}
- public int getCid() {
+ public long getCid() {
return cid;
}
- public BulletScreenEntity setCid(int cid) {
+ public BulletScreenEntity setCid(long cid) {
this.cid = cid;
return this;
}
- public int getMid() {
+ public long getMid() {
return mid;
}
- public BulletScreenEntity setMid(int mid) {
+ public BulletScreenEntity setMid(long mid) {
this.mid = mid;
return this;
}
diff --git a/src/main/java/com/hiczp/bilibili/api/live/socket/BulletScreenDispatcherRunnable.java b/src/main/java/com/hiczp/bilibili/api/live/socket/BulletScreenDispatcherRunnable.java
deleted file mode 100644
index fcf2296..0000000
--- a/src/main/java/com/hiczp/bilibili/api/live/socket/BulletScreenDispatcherRunnable.java
+++ /dev/null
@@ -1,116 +0,0 @@
-package com.hiczp.bilibili.api.live.socket;
-
-import com.google.gson.Gson;
-import com.google.gson.GsonBuilder;
-import com.google.gson.JsonObject;
-import com.google.gson.JsonParser;
-import com.hiczp.bilibili.api.live.socket.entity.*;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.nio.channels.SocketChannel;
-import java.util.Arrays;
-import java.util.function.Consumer;
-
-public class BulletScreenDispatcherRunnable implements Runnable {
- private static final Logger LOGGER = LoggerFactory.getLogger(BulletScreenDispatcherRunnable.class);
- private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create();
- private static final JsonParser JSON_PARSER = new JsonParser();
- private SocketChannel socketChannel;
- private LiveClient liveClient;
-
- public BulletScreenDispatcherRunnable(SocketChannel socketChannel, LiveClient liveClient) {
- this.socketChannel = socketChannel;
- this.liveClient = liveClient;
- }
-
- private void invokeCallback(Consumer consumer) {
- Utils.invokeCallback(liveClient.getBulletScreenListeners(), consumer);
- }
-
- @Override
- public void run() {
- while (true) {
- try {
- ByteBuffer[] byteBuffers = PackageRepository.readNextPackageSplit(socketChannel);
- //如果没有回调则不解析数据包, 直接开始接收下一个数据包
- if (liveClient.getBulletScreenListeners().size() == 0) {
- continue;
- }
-
- //判断数据包类型
- byte[] packageTypeBytes = byteBuffers[3].array();
- Consumer consumer = null;
- if (Arrays.equals(packageTypeBytes, PackageRepository.DATA_PACKAGE_TYPE_BYTES)) { //弹幕
- String json = new String(byteBuffers[5].array());
- JsonObject jsonObject = JSON_PARSER.parse(json).getAsJsonObject();
- String cmd = jsonObject.get("cmd").getAsString();
- //判断 cmd
- switch (cmd) {
- case "DANMU_MSG": {
- consumer = bulletScreenListener -> bulletScreenListener.onDanMuMSGPackage(GSON.fromJson(json, DanMuMSGEntity.class));
- }
- break;
- case "SEND_GIFT": {
- consumer = bulletScreenListener -> bulletScreenListener.onSendGiftPackage(GSON.fromJson(json, SendGiftEntity.class));
- }
- break;
- case "SYS_MSG": {
- consumer = bulletScreenListener -> bulletScreenListener.onSysMSGPackage(GSON.fromJson(json, SysMSGEntity.class));
- }
- break;
- case "SYS_GIFT": {
- consumer = bulletScreenListener -> bulletScreenListener.onSysGiftPackage(GSON.fromJson(json, SysGiftEntity.class));
- }
- break;
- case "WELCOME": {
- consumer = bulletScreenListener -> bulletScreenListener.onWelcomePackage(GSON.fromJson(json, WelcomeEntity.class));
- }
- break;
- case "WELCOME_GUARD": {
- consumer = bulletScreenListener -> bulletScreenListener.onWelcomeGuardPackage(GSON.fromJson(json, WelcomeGuardEntity.class));
- }
- break;
- case "LIVE": {
- consumer = bulletScreenListener -> bulletScreenListener.onLivePackage(GSON.fromJson(json, LiveEntity.class));
- }
- break;
- case "PREPARING": {
- consumer = bulletScreenListener -> bulletScreenListener.onPreparingPackage(GSON.fromJson(json, PreparingEntity.class));
- }
- break;
- default: { //未知的 cmd
- LOGGER.error("Unknown json below: ");
- GSON.toJson(jsonObject, System.out);
- System.out.println();
- }
- }
- } else if (Arrays.equals(packageTypeBytes, PackageRepository.VIEWER_COUNT_PACKAGE_TYPE_BYTES)) { //在线人数
- consumer = bulletScreenListener -> bulletScreenListener.onViewerCountPackage(byteBuffers[5].getInt());
- } else { //未知类型
- ByteBuffer byteBuffer = ByteBuffer.allocate(Arrays.stream(byteBuffers).mapToInt(ByteBuffer::limit).sum());
- Arrays.stream(byteBuffers).forEach(byteBuffer::put);
- byte[] bytes = byteBuffer.array();
- LOGGER.error("Unknown package below: ");
- Utils.printBytes(bytes);
- consumer = bulletScreenListener -> bulletScreenListener.onUnknownPackage(bytes);
- }
-
- //执行回调
- invokeCallback(consumer);
- } catch (IOException e) {
- LOGGER.debug("Connection closed, BulletScreenDispatcherThread prepare to exit");
- try {
- liveClient.close();
- } catch (IOException e1) {
- e1.printStackTrace();
- }
- //调用 onDisconnect 回调
- invokeCallback(BulletScreenListener::onDisconnect);
- break;
- }
- }
- }
-}
diff --git a/src/main/java/com/hiczp/bilibili/api/live/socket/BulletScreenListener.java b/src/main/java/com/hiczp/bilibili/api/live/socket/BulletScreenListener.java
deleted file mode 100644
index 8b21056..0000000
--- a/src/main/java/com/hiczp/bilibili/api/live/socket/BulletScreenListener.java
+++ /dev/null
@@ -1,29 +0,0 @@
-package com.hiczp.bilibili.api.live.socket;
-
-import com.hiczp.bilibili.api.live.socket.entity.*;
-
-public interface BulletScreenListener {
- void onConnect();
-
- void onDisconnect();
-
- void onViewerCountPackage(int viewerCount);
-
- void onDanMuMSGPackage(DanMuMSGEntity danMuMSGEntity);
-
- void onSendGiftPackage(SendGiftEntity sendGiftEntity);
-
- void onSysMSGPackage(SysMSGEntity sysMSGEntity);
-
- void onSysGiftPackage(SysGiftEntity sysGiftEntity);
-
- void onWelcomePackage(WelcomeEntity welcomeEntity);
-
- void onWelcomeGuardPackage(WelcomeGuardEntity welcomeGuardEntity);
-
- void onLivePackage(LiveEntity liveEntity);
-
- void onPreparingPackage(PreparingEntity preparingEntity);
-
- void onUnknownPackage(byte[] raw);
-}
diff --git a/src/main/java/com/hiczp/bilibili/api/live/socket/BulletScreenListenerAdaptor.java b/src/main/java/com/hiczp/bilibili/api/live/socket/BulletScreenListenerAdaptor.java
deleted file mode 100644
index 9ce77d0..0000000
--- a/src/main/java/com/hiczp/bilibili/api/live/socket/BulletScreenListenerAdaptor.java
+++ /dev/null
@@ -1,65 +0,0 @@
-package com.hiczp.bilibili.api.live.socket;
-
-import com.hiczp.bilibili.api.live.socket.entity.*;
-
-public class BulletScreenListenerAdaptor implements BulletScreenListener {
- @Override
- public void onConnect() {
-
- }
-
- @Override
- public void onDisconnect() {
-
- }
-
- @Override
- public void onViewerCountPackage(int viewerCount) {
-
- }
-
- @Override
- public void onDanMuMSGPackage(DanMuMSGEntity danMuMSGEntity) {
-
- }
-
- @Override
- public void onSendGiftPackage(SendGiftEntity sendGiftEntity) {
-
- }
-
- @Override
- public void onSysMSGPackage(SysMSGEntity sysMSGEntity) {
-
- }
-
- @Override
- public void onSysGiftPackage(SysGiftEntity sysGiftEntity) {
-
- }
-
- @Override
- public void onWelcomePackage(WelcomeEntity welcomeEntity) {
-
- }
-
- @Override
- public void onWelcomeGuardPackage(WelcomeGuardEntity welcomeGuardEntity) {
-
- }
-
- @Override
- public void onLivePackage(LiveEntity liveEntity) {
-
- }
-
- @Override
- public void onPreparingPackage(PreparingEntity preparingEntity) {
-
- }
-
- @Override
- public void onUnknownPackage(byte[] raw) {
-
- }
-}
diff --git a/src/main/java/com/hiczp/bilibili/api/live/socket/LiveClient.java b/src/main/java/com/hiczp/bilibili/api/live/socket/LiveClient.java
index 48f9f0d..090068f 100644
--- a/src/main/java/com/hiczp/bilibili/api/live/socket/LiveClient.java
+++ b/src/main/java/com/hiczp/bilibili/api/live/socket/LiveClient.java
@@ -1,138 +1,25 @@
package com.hiczp.bilibili.api.live.socket;
-import com.hiczp.bilibili.api.BilibiliRESTAPI;
-import com.hiczp.bilibili.api.live.LiveService;
-import com.hiczp.bilibili.api.live.entity.LiveRoomInfoEntity;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import java.io.Closeable;
-import java.io.IOException;
-import java.net.InetSocketAddress;
-import java.net.SocketException;
-import java.nio.channels.SocketChannel;
-import java.util.Timer;
-import java.util.TimerTask;
-import java.util.Vector;
-
-public class LiveClient implements Closeable {
+public class LiveClient {
private static final Logger LOGGER = LoggerFactory.getLogger(LiveClient.class);
- private int showRoomId;
- private int roomId;
- private int userId = BilibiliRESTAPI.getMid();
- private LiveService liveService = BilibiliRESTAPI.getLiveService();
- private Vector bulletScreenListeners = new Vector<>();
- private LiveRoomInfoEntity.LiveRoomEntity liveRoomEntity;
- private SocketChannel socketChannel;
- private Thread bulletScreenDispatcherThread;
- private Timer heartBeatTimer;
+ private final long showRoomId;
+ private final long userId;
- public LiveClient(int showRoomId) {
+ public LiveClient(long showRoomId) {
this.showRoomId = showRoomId;
+ this.userId = 0;
}
- //如果不传入 userId, 将使用默认值 0
- public LiveClient(int showRoomId, int userId) {
+ public LiveClient(long showRoomId, long userId) {
this.showRoomId = showRoomId;
this.userId = userId;
}
- public LiveClient addListener(BulletScreenListener bulletScreenListener) {
- bulletScreenListeners.add(bulletScreenListener);
+ public LiveClient connect() {
return this;
}
-
- public LiveClient removeListener(BulletScreenListener bulletScreenListener) {
- bulletScreenListeners.remove(bulletScreenListener);
- return this;
- }
-
- public LiveClient connect() throws IOException {
- LOGGER.info("Entering live room {} with uid {}", showRoomId, userId);
- //获取直播间信息
- LiveRoomInfoEntity liveRoomInfoEntity = liveService.getRoomInfo(showRoomId).execute().body();
- if (liveRoomInfoEntity.getCode() != 0) {
- LOGGER.error("Can't get live room info");
- throw new IOException(liveRoomInfoEntity.getMessage());
- }
- liveRoomEntity = liveRoomInfoEntity.getData();
- roomId = liveRoomEntity.getRoomId();
- LOGGER.debug("Real room id: {}", roomId);
-
- //socket 连接
- String address = liveRoomEntity.getCmt();
- int port = liveRoomEntity.getCmtPortGoim();
- LOGGER.info("Connect to {}:{}", address, port);
- socketChannel = SocketChannel.open(new InetSocketAddress(address, port));
- //发送进房包
- socketChannel.write(PackageRepository.createEnterRoomPackage(roomId, userId));
- //验证下一个数据包是否是进房成功数据包
- if (PackageRepository.isNextPackageIsEnterRoomSuccessPackage(socketChannel)) {
- LOGGER.info("Socket connection success");
- //调用 onConnect 回调
- Utils.invokeCallback(bulletScreenListeners, BulletScreenListener::onConnect);
- } else {
- LOGGER.error("Socket connection failed");
- socketChannel.close();
- throw new SocketException("Can't connection to Bullet Screen server");
- }
-
- //启动回调分发线程
- bulletScreenDispatcherThread = new Thread(new BulletScreenDispatcherRunnable(socketChannel, this));
- bulletScreenDispatcherThread.setName("BulletScreenDispatcherThread");
- bulletScreenDispatcherThread.start();
- LOGGER.debug("BulletScreenDispatcherThread started");
-
- //启动心跳包线程
- heartBeatTimer = new Timer("LiveHeartBeatThread");
- heartBeatTimer.schedule(new TimerTask() {
- private final Logger logger = LoggerFactory.getLogger(TimerTask.class);
-
- @Override
- public void run() {
- try {
- socketChannel.socket().getOutputStream().write(PackageRepository.createHeartBeatPackage(roomId, userId).array());
- logger.debug("Send heart beat package success");
- } catch (IOException e) {
- logger.debug("Connection closed, cancel HeartBeatTimerTask");
- cancel();
- try {
- close();
- } catch (IOException e1) {
- e1.printStackTrace();
- }
- }
- }
- }, 0, 30 * 1000);
- LOGGER.debug("HeatBeatTimer started");
-
- return this;
- }
-
- @Override
- public synchronized void close() throws IOException {
- if (heartBeatTimer != null) {
- heartBeatTimer.cancel();
- LOGGER.debug("HeartBeatTimer canceled");
- heartBeatTimer = null;
- }
- if (socketChannel != null && socketChannel.isConnected()) {
- socketChannel.close();
- LOGGER.debug("Socket closed");
- socketChannel = null;
- }
- }
-
- public Vector getBulletScreenListeners() {
- return bulletScreenListeners;
- }
-
- public void setBulletScreenListeners(Vector bulletScreenListeners) {
- this.bulletScreenListeners = bulletScreenListeners;
- }
-
- public LiveRoomInfoEntity.LiveRoomEntity getLiveRoomEntity() {
- return liveRoomEntity;
- }
}
diff --git a/src/main/java/com/hiczp/bilibili/api/live/socket/Utils.java b/src/main/java/com/hiczp/bilibili/api/live/socket/Utils.java
index 30a2d72..f45bb34 100644
--- a/src/main/java/com/hiczp/bilibili/api/live/socket/Utils.java
+++ b/src/main/java/com/hiczp/bilibili/api/live/socket/Utils.java
@@ -1,10 +1,6 @@
package com.hiczp.bilibili.api.live.socket;
-import com.sun.istack.internal.Nullable;
-
import java.util.Arrays;
-import java.util.List;
-import java.util.function.Consumer;
public class Utils {
private static byte[][] splitBytes(byte[] bytes, int n) {
@@ -22,18 +18,6 @@ public class Utils {
return result;
}
- static void invokeCallback(List bulletScreenListeners, @Nullable Consumer consumer) {
- if (consumer != null) {
- for (int i = bulletScreenListeners.size() - 1; i >= 0; i--) {
- try {
- consumer.accept(bulletScreenListeners.get(i));
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
- }
-
public static void printBytes(byte[] bytes) {
byte[][] data = splitBytes(bytes, 16);
byte[] currentRow;
diff --git a/src/main/java/com/hiczp/bilibili/api/passport/PassportService.java b/src/main/java/com/hiczp/bilibili/api/passport/PassportService.java
index ea3450a..8ef0912 100644
--- a/src/main/java/com/hiczp/bilibili/api/passport/PassportService.java
+++ b/src/main/java/com/hiczp/bilibili/api/passport/PassportService.java
@@ -21,4 +21,8 @@ public interface PassportService {
@POST("api/oauth2/revoke")
Call logout(@Query("access_token") String accessToken);
+
+ //TODO sso 未测试
+ @GET("api/login/sso")
+ Call sso(@Query("access_token") String accessToken);
}
diff --git a/src/main/java/com/hiczp/bilibili/api/passport/entity/LoginResponseEntity.java b/src/main/java/com/hiczp/bilibili/api/passport/entity/LoginResponseEntity.java
index be69e19..0a80988 100644
--- a/src/main/java/com/hiczp/bilibili/api/passport/entity/LoginResponseEntity.java
+++ b/src/main/java/com/hiczp/bilibili/api/passport/entity/LoginResponseEntity.java
@@ -1,6 +1,7 @@
package com.hiczp.bilibili.api.passport.entity;
import com.google.gson.annotations.SerializedName;
+import com.hiczp.bilibili.api.BilibiliAccount;
public class LoginResponseEntity {
/**
@@ -16,7 +17,7 @@ public class LoginResponseEntity {
@SerializedName("data")
private DataEntity data;
@SerializedName("ts")
- private int ts;
+ private long ts;
public int getCode() {
return code;
@@ -42,14 +43,24 @@ public class LoginResponseEntity {
this.data = data;
}
- public int getTs() {
+ public long getTs() {
return ts;
}
- public void setTs(int ts) {
+ public void setTs(long ts) {
this.ts = ts;
}
+ public BilibiliAccount toBilibiliAccount() {
+ return new BilibiliAccount(
+ this.data.accessToken,
+ this.data.refreshToken,
+ this.data.mid,
+ this.data.expiresIn,
+ this.ts
+ );
+ }
+
public static class DataEntity {
/**
* access_token : 8501735069b043dd62c3bb88810444fd
@@ -63,9 +74,9 @@ public class LoginResponseEntity {
@SerializedName("refresh_token")
private String refreshToken;
@SerializedName("mid")
- private int mid;
+ private long mid;
@SerializedName("expires_in")
- private int expiresIn;
+ private long expiresIn;
public String getAccessToken() {
return accessToken;
@@ -83,19 +94,19 @@ public class LoginResponseEntity {
this.refreshToken = refreshToken;
}
- public int getMid() {
+ public long getMid() {
return mid;
}
- public void setMid(int mid) {
+ public void setMid(long mid) {
this.mid = mid;
}
- public int getExpiresIn() {
+ public long getExpiresIn() {
return expiresIn;
}
- public void setExpiresIn(int expiresIn) {
+ public void setExpiresIn(long expiresIn) {
this.expiresIn = expiresIn;
}
}
diff --git a/src/main/java/com/hiczp/bilibili/api/passport/entity/RefreshTokenResponseEntity.java b/src/main/java/com/hiczp/bilibili/api/passport/entity/RefreshTokenResponseEntity.java
index 7e72230..18fae0b 100644
--- a/src/main/java/com/hiczp/bilibili/api/passport/entity/RefreshTokenResponseEntity.java
+++ b/src/main/java/com/hiczp/bilibili/api/passport/entity/RefreshTokenResponseEntity.java
@@ -1,6 +1,7 @@
package com.hiczp.bilibili.api.passport.entity;
import com.google.gson.annotations.SerializedName;
+import com.hiczp.bilibili.api.BilibiliAccount;
public class RefreshTokenResponseEntity {
/**
@@ -10,7 +11,7 @@ public class RefreshTokenResponseEntity {
*/
@SerializedName("ts")
- private int ts;
+ private long ts;
@SerializedName("code")
private int code;
@SerializedName("message")
@@ -18,11 +19,11 @@ public class RefreshTokenResponseEntity {
@SerializedName("data")
private DataEntity data;
- public int getTs() {
+ public long getTs() {
return ts;
}
- public void setTs(int ts) {
+ public void setTs(long ts) {
this.ts = ts;
}
@@ -50,6 +51,16 @@ public class RefreshTokenResponseEntity {
this.data = data;
}
+ public BilibiliAccount toBilibiliAccount() {
+ return new BilibiliAccount(
+ this.data.accessToken,
+ this.data.refreshToken,
+ this.data.mid,
+ this.data.expiresIn,
+ this.ts
+ );
+ }
+
public static class DataEntity {
/**
* mid : 20293030
@@ -59,19 +70,19 @@ public class RefreshTokenResponseEntity {
*/
@SerializedName("mid")
- private int mid;
+ private long mid;
@SerializedName("refresh_token")
private String refreshToken;
@SerializedName("access_token")
private String accessToken;
@SerializedName("expires_in")
- private int expiresIn;
+ private long expiresIn;
- public int getMid() {
+ public long getMid() {
return mid;
}
- public void setMid(int mid) {
+ public void setMid(long mid) {
this.mid = mid;
}
@@ -91,11 +102,11 @@ public class RefreshTokenResponseEntity {
this.accessToken = accessToken;
}
- public int getExpiresIn() {
+ public long getExpiresIn() {
return expiresIn;
}
- public void setExpiresIn(int expiresIn) {
+ public void setExpiresIn(long expiresIn) {
this.expiresIn = expiresIn;
}
}
diff --git a/src/test/java/com/hiczp/bilibili/api/test/Config.java b/src/test/java/com/hiczp/bilibili/api/test/Config.java
index ba2f7a4..eea2a12 100644
--- a/src/test/java/com/hiczp/bilibili/api/test/Config.java
+++ b/src/test/java/com/hiczp/bilibili/api/test/Config.java
@@ -1,7 +1,10 @@
package com.hiczp.bilibili.api.test;
+import com.hiczp.bilibili.api.BilibiliAPI;
+
public class Config {
private static Config config;
+ private static final BilibiliAPI bilibiliAPI = new BilibiliAPI();
private String username;
private String password;
@@ -38,4 +41,8 @@ public class Config {
public void setRoomId(int roomId) {
this.roomId = roomId;
}
+
+ public static BilibiliAPI getBilibiliAPI() {
+ return bilibiliAPI;
+ }
}
diff --git a/src/test/java/com/hiczp/bilibili/api/test/LiveRoomTest.java b/src/test/java/com/hiczp/bilibili/api/test/LiveRoomTest.java
deleted file mode 100644
index a10a4ae..0000000
--- a/src/test/java/com/hiczp/bilibili/api/test/LiveRoomTest.java
+++ /dev/null
@@ -1,163 +0,0 @@
-package com.hiczp.bilibili.api.test;
-
-import com.google.gson.Gson;
-import com.hiczp.bilibili.api.BilibiliRESTAPI;
-import com.hiczp.bilibili.api.live.entity.BulletScreenEntity;
-import com.hiczp.bilibili.api.live.entity.LiveRoomInfoEntity;
-import com.hiczp.bilibili.api.live.entity.SendBulletScreenResponseEntity;
-import com.hiczp.bilibili.api.live.socket.BulletScreenListenerAdaptor;
-import com.hiczp.bilibili.api.live.socket.LiveClient;
-import com.hiczp.bilibili.api.live.socket.PackageRepository;
-import com.hiczp.bilibili.api.live.socket.Utils;
-import com.hiczp.bilibili.api.live.socket.entity.DanMuMSGEntity;
-import com.hiczp.bilibili.api.live.socket.entity.LiveEntity;
-import com.hiczp.bilibili.api.live.socket.entity.PreparingEntity;
-import com.hiczp.bilibili.api.live.socket.entity.SendGiftEntity;
-import com.hiczp.bilibili.api.live.socket.entity.SysGiftEntity;
-import com.hiczp.bilibili.api.live.socket.entity.SysMSGEntity;
-import com.hiczp.bilibili.api.live.socket.entity.WelcomeEntity;
-import com.hiczp.bilibili.api.live.socket.entity.WelcomeGuardEntity;
-import java.io.IOException;
-import java.net.InetSocketAddress;
-import java.nio.channels.SocketChannel;
-import org.junit.FixMethodOrder;
-import org.junit.Ignore;
-import org.junit.Test;
-import org.junit.runners.MethodSorters;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import retrofit2.Call;
-import retrofit2.Callback;
-import retrofit2.Response;
-
-@Ignore
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-public class LiveRoomTest {
- private static final Logger LOGGER = LoggerFactory.getLogger(LiveRoomTest.class);
- private static final Config CONFIG = Config.getInstance();
- private static final int USER_ID = BilibiliRESTAPI.getMid();
- private static final int STOP_AFTER_N_HEART_BEATS = 3;
- private static final int STOP_AFTER_SECOND = 90;
-
- @Ignore
- @Test
- public void _0socketTest() throws IOException {
- LOGGER.info("Start socket connection to live Bullet Screen stream server, test continue for {} heart beat", STOP_AFTER_N_HEART_BEATS);
- LiveRoomInfoEntity.LiveRoomEntity liveRoomEntity = BilibiliRESTAPI.getLiveService().getRoomInfo(CONFIG.getRoomId()).execute().body().getData();
- int roomId = liveRoomEntity.getRoomId();
-
- SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress(liveRoomEntity.getCmt(), liveRoomEntity.getCmtPortGoim()));
- socketChannel.write(PackageRepository.createEnterRoomPackage(roomId, USER_ID));
- Thread thread = new Thread(() -> {
- while (true) {
- try {
- Utils.printBytes(PackageRepository.readNextPackage(socketChannel).array());
- System.out.println();
- } catch (IOException e) {
- break;
- }
- }
- });
- thread.start();
-
- for (int i = STOP_AFTER_N_HEART_BEATS; i > 0; i--) {
- socketChannel.write(PackageRepository.createHeartBeatPackage(roomId, USER_ID));
- LOGGER.debug("Send heart beat package");
- BilibiliRESTAPI.getLiveService().sendBulletScreen(new BulletScreenEntity(roomId, "send heart beat")).enqueue(new Callback() {
- private Gson gson = new Gson();
-
- @Override
- public void onResponse(Call call, Response response) {
- gson.toJson(response.body(), System.out);
- System.out.println();
- }
-
- @Override
- public void onFailure(Call call, Throwable throwable) {
- throwable.printStackTrace();
- }
- });
- try {
- Thread.sleep(30 * 1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- break;
- }
- }
-
- socketChannel.close();
- }
-
- @Ignore
- @Test
- public void _1liveClientTest() throws IOException {
- int roomId = CONFIG.getRoomId();
- LOGGER.info("Start LiveClientTest for room {}", roomId);
- LiveClient liveClient = new LiveClient(roomId, USER_ID)
- .addListener(new BulletScreenListenerAdaptor() {
- @Override
- public void onConnect() {
- LOGGER.info("Connected");
- }
-
- @Override
- public void onDisconnect() {
- LOGGER.info("Disconnected");
- }
-
- @Override
- public void onViewerCountPackage(int viewerCount) {
- LOGGER.info("Current viewers: {}", viewerCount);
- }
-
- @Override
- public void onDanMuMSGPackage(DanMuMSGEntity danMuMSGEntity) {
- LOGGER.info("[{}]{}", danMuMSGEntity.getUsername(), danMuMSGEntity.getMessage());
- }
-
- @Override
- public void onSendGiftPackage(SendGiftEntity sendGiftEntity) {
- SendGiftEntity.DataEntity dataEntity = sendGiftEntity.getData();
- LOGGER.info("{} send {} * {}", dataEntity.getUserName(), dataEntity.getGiftName(), dataEntity.getNum());
- }
-
- @Override
- public void onSysMSGPackage(SysMSGEntity sysMSGEntity) {
- LOGGER.info("System message: {} {}", sysMSGEntity.getMsg(), sysMSGEntity.getUrl());
- }
-
- @Override
- public void onSysGiftPackage(SysGiftEntity sysGiftEntity) {
- LOGGER.info("System gift: {} {}", sysGiftEntity.getMsg(), sysGiftEntity.getUrl());
- }
-
- @Override
- public void onWelcomePackage(WelcomeEntity welcomeEntity) {
- LOGGER.info("Welcome {}", welcomeEntity.getData().getUserName());
- }
-
- @Override
- public void onWelcomeGuardPackage(WelcomeGuardEntity welcomeGuardEntity) {
- WelcomeGuardEntity.DataEntity dataEntity = welcomeGuardEntity.getData();
- LOGGER.info("Welcome guard [Lv{}]{}", dataEntity.getGuardLevel(), dataEntity.getUsername());
- }
-
- @Override
- public void onLivePackage(LiveEntity liveEntity) {
- LOGGER.info("Room {} start live", liveEntity.getRoomId());
- }
-
- @Override
- public void onPreparingPackage(PreparingEntity preparingEntity) {
- LOGGER.info("Room {} stop live", preparingEntity.getRoomId());
- }
- })
- .connect();
- try {
- Thread.sleep(STOP_AFTER_SECOND * 1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- liveClient.close();
- }
-}
diff --git a/src/test/java/com/hiczp/bilibili/api/test/LiveServiceTest.java b/src/test/java/com/hiczp/bilibili/api/test/LiveServiceTest.java
deleted file mode 100644
index 2a487ed..0000000
--- a/src/test/java/com/hiczp/bilibili/api/test/LiveServiceTest.java
+++ /dev/null
@@ -1,287 +0,0 @@
-package com.hiczp.bilibili.api.test;
-
-import com.google.gson.Gson;
-import com.google.gson.GsonBuilder;
-import com.hiczp.bilibili.api.BilibiliRESTAPI;
-import com.hiczp.bilibili.api.Utils;
-import com.hiczp.bilibili.api.live.entity.BulletScreenEntity;
-import com.hiczp.bilibili.api.live.entity.GiftEntity;
-import com.hiczp.bilibili.api.live.entity.LiveRoomInfoEntity;
-import com.hiczp.bilibili.api.live.entity.PlayerBagEntity;
-import org.junit.After;
-import org.junit.FixMethodOrder;
-import org.junit.Test;
-import org.junit.runners.MethodSorters;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.IOException;
-import java.text.SimpleDateFormat;
-import java.util.Date;
-
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-public class LiveServiceTest {
- private static final Logger LOGGER = LoggerFactory.getLogger(LiveServiceTest.class);
- private static final Gson GSON = new GsonBuilder().disableHtmlEscaping().setPrettyPrinting().create();
- private static int roomId = Config.getInstance().getRoomId();
- private static LiveRoomInfoEntity.LiveRoomEntity liveRoomEntity;
- private static PlayerBagEntity.BagGiftEntity firstGiftInPlayerBag;
-
- @Test
- public void _00getBulletScreenConfig() throws IOException {
- LOGGER.info("Getting Bullet Screen config");
- GSON.toJson(
- BilibiliRESTAPI.getLiveService()
- .getBulletScreenConfig("all")
- .execute()
- .body(),
- System.out
- );
- }
-
- @Test
- public void _01getHistoryBulletScreens() throws Exception {
- LOGGER.info("Getting history Bullet Screens of room " + roomId);
- GSON.toJson(
- BilibiliRESTAPI.getLiveService()
- .getHistoryBulletScreens(roomId)
- .execute()
- .body(),
- System.out
- );
- }
-
- @Test
- public void _02getRoomInfo() throws Exception {
- LOGGER.info("Getting info of live room " + roomId);
- LiveRoomInfoEntity liveRoomInfoEntity = BilibiliRESTAPI.getLiveService()
- .getRoomInfo(roomId)
- .execute()
- .body();
- liveRoomEntity = liveRoomInfoEntity.getData();
- roomId = liveRoomEntity.getRoomId();
- GSON.toJson(
- liveRoomInfoEntity,
- System.out);
- }
-
- @Test
- public void _03isFollowed() throws Exception {
- LOGGER.info("Getting is followed user " + liveRoomEntity.getMid());
- GSON.toJson(
- BilibiliRESTAPI.getLiveService()
- .isFollowed(liveRoomEntity.getMid())
- .execute()
- .body(),
- System.out
- );
- }
-
- @Test
- public void _04sendDaily() throws Exception {
- LOGGER.info("Sending daily");
- GSON.toJson(
- BilibiliRESTAPI.getLiveService()
- .sendDaily()
- .execute()
- .body(),
- System.out
- );
- }
-
- @Test
- public void _05getAllItem() throws Exception {
- LOGGER.info("Getting all items");
- GSON.toJson(
- BilibiliRESTAPI.getLiveService()
- .getAllItem()
- .execute()
- .body(),
- System.out
- );
- }
-
- @Test
- public void _06getAppSmallTV() throws Exception {
- LOGGER.info("Getting App Small TV info");
- GSON.toJson(
- BilibiliRESTAPI.getLiveService()
- .getAppSmallTV()
- .execute()
- .body(),
- System.out
- );
- }
-
- @Test
- public void _07getTitle() throws Exception {
- LOGGER.info("Getting titles");
- GSON.toJson(
- BilibiliRESTAPI.getLiveService()
- .getTitle()
- .execute()
- .body(),
- System.out
- );
- }
-
- @Test
- public void _08getSpecialGift() throws Exception {
- LOGGER.info("Getting special gift");
- GSON.toJson(
- BilibiliRESTAPI.getLiveService()
- .getSpecialGift(roomId)
- .execute()
- .body(),
- System.out
- );
- }
-
- @Test
- public void _09getUserInfo() throws Exception {
- LOGGER.info("Getting user info");
- GSON.toJson(
- BilibiliRESTAPI.getLiveService()
- .getUserInfo()
- .execute()
- .body(),
- System.out
- );
- }
-
- @Test
- public void _10getPlayUrl() throws Exception {
- LOGGER.info("Getting play url of room " + roomId);
- GSON.toJson(
- BilibiliRESTAPI.getLiveService()
- .getPlayUrl(roomId, "json")
- .execute()
- .body(),
- System.out
- );
- }
-
- @Test
- public void _11sendOnlineHeart() throws Exception {
- LOGGER.info("Sending online heart to room " + roomId);
- GSON.toJson(
- BilibiliRESTAPI.getLiveService()
- .sendOnlineHeart(roomId, Utils.getScale())
- .execute()
- .body(),
- System.out
- );
- }
-
- @Test
- public void _12sendBulletScreen() throws Exception {
- LOGGER.info("Sending Bullet Screen to room " + roomId);
- GSON.toJson(
- BilibiliRESTAPI.getLiveService()
- .sendBulletScreen(new BulletScreenEntity(roomId, new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())))
- .execute()
- .body(),
- System.out
- );
- }
-
- @Test
- public void _13getFreeSilverCurrentTask() throws Exception {
- LOGGER.info("Getting free silver current task");
- GSON.toJson(
- BilibiliRESTAPI.getLiveService()
- .getFreeSilverCurrentTask()
- .execute()
- .body(),
- System.out
- );
- }
-
- @Test
- public void _14getFreeSilverAward() throws Exception {
- LOGGER.info("Getting free silver award");
- GSON.toJson(
- BilibiliRESTAPI.getLiveService()
- .getFreeSilverAward()
- .execute()
- .body(),
- System.out
- );
- }
-
- @Test
- public void _15getPlayerBag() throws Exception {
- LOGGER.info("Getting player bag");
- PlayerBagEntity playerBagEntity = BilibiliRESTAPI.getLiveService()
- .getPlayerBag()
- .execute()
- .body();
- try {
- firstGiftInPlayerBag = playerBagEntity.getData().get(0);
- } catch (IndexOutOfBoundsException e) {
- LOGGER.error("Current user don't have any gift");
- }
- GSON.toJson(
- playerBagEntity,
- System.out
- );
- }
-
- @Test
- public void _16getActivityGifts() throws Exception {
- LOGGER.info("Getting activity gifts");
- GSON.toJson(
- BilibiliRESTAPI.getLiveService()
- .getActivityGifts(roomId)
- .execute()
- .body(),
- System.out
- );
- }
-
- @Test
- public void _17sendGift() throws Exception {
- if (firstGiftInPlayerBag != null) {
- int number = 1;
- LOGGER.info("Sending {} {} to room of user '{}'", number, firstGiftInPlayerBag.getGiftName(), liveRoomEntity.getUname());
- GSON.toJson(
- BilibiliRESTAPI.getLiveService()
- .sendGift(new GiftEntity(firstGiftInPlayerBag, number, liveRoomEntity))
- .execute()
- .body(),
- System.out
- );
- } else {
- LOGGER.error("No gift available in player bag, ignore sending gift test");
- }
- }
-
- @Test
- public void _18getGiftTop() throws Exception {
- LOGGER.info("Getting gift top");
- GSON.toJson(
- BilibiliRESTAPI.getLiveService()
- .getGiftTop(roomId)
- .execute()
- .body(),
- System.out
- );
- }
-
- @Test
- public void _19getSignInfo() throws Exception {
- LOGGER.info("Getting sign info");
- GSON.toJson(
- BilibiliRESTAPI.getLiveService()
- .getSignInfo()
- .execute()
- .body(),
- System.out
- );
- }
-
- @After
- public void endLine() {
- System.out.println();
- }
-}
diff --git a/src/test/java/com/hiczp/bilibili/api/test/LoginTest.java b/src/test/java/com/hiczp/bilibili/api/test/LoginTest.java
deleted file mode 100644
index 619836a..0000000
--- a/src/test/java/com/hiczp/bilibili/api/test/LoginTest.java
+++ /dev/null
@@ -1,52 +0,0 @@
-package com.hiczp.bilibili.api.test;
-
-import com.google.gson.Gson;
-import com.google.gson.GsonBuilder;
-import com.hiczp.bilibili.api.BilibiliRESTAPI;
-import org.junit.After;
-import org.junit.FixMethodOrder;
-import org.junit.Ignore;
-import org.junit.Test;
-import org.junit.runners.MethodSorters;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-public class LoginTest {
- private static final Logger LOGGER = LoggerFactory.getLogger(LoginTest.class);
- private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create();
-
- @Test
- public void _0login() throws Exception {
- LOGGER.info("Start login test");
- Config config = Config.getInstance();
- GSON.toJson(
- BilibiliRESTAPI.login(config.getUsername(), config.getPassword()),
- System.out
- );
- }
-
- @Test
- public void _1info() throws Exception {
- LOGGER.info("Getting user info");
- GSON.toJson(
- BilibiliRESTAPI.getAccountInfo(),
- System.out
- );
- }
-
- @Ignore
- @Test
- public void _2refreshToken() throws Exception {
- LOGGER.info("Refreshing token");
- GSON.toJson(
- BilibiliRESTAPI.refreshToken(),
- System.out
- );
- }
-
- @After
- public void endLine() {
- System.out.println();
- }
-}
diff --git a/src/test/java/com/hiczp/bilibili/api/test/LogoutTest.java b/src/test/java/com/hiczp/bilibili/api/test/LogoutTest.java
deleted file mode 100644
index b4b87bf..0000000
--- a/src/test/java/com/hiczp/bilibili/api/test/LogoutTest.java
+++ /dev/null
@@ -1,28 +0,0 @@
-package com.hiczp.bilibili.api.test;
-
-import com.google.gson.Gson;
-import com.google.gson.GsonBuilder;
-import com.hiczp.bilibili.api.BilibiliRESTAPI;
-import org.junit.After;
-import org.junit.Test;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-public class LogoutTest {
- private static final Logger LOGGER = LoggerFactory.getLogger(LogoutTest.class);
- private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create();
-
- @Test
- public void logout() throws Exception {
- LOGGER.info("Logout");
- GSON.toJson(
- BilibiliRESTAPI.logout(),
- System.out
- );
- }
-
- @After
- public void endLine() {
- System.out.println();
- }
-}
diff --git a/src/test/java/com/hiczp/bilibili/api/test/RuleSuite.java b/src/test/java/com/hiczp/bilibili/api/test/RuleSuite.java
index 55374c0..de12508 100644
--- a/src/test/java/com/hiczp/bilibili/api/test/RuleSuite.java
+++ b/src/test/java/com/hiczp/bilibili/api/test/RuleSuite.java
@@ -7,15 +7,14 @@ import org.junit.rules.ExternalResource;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
+import javax.security.auth.login.LoginException;
import java.io.BufferedReader;
+import java.io.IOException;
import java.io.InputStreamReader;
@RunWith(Suite.class)
@Suite.SuiteClasses({
- LoginTest.class,
- LiveServiceTest.class,
- LiveRoomTest.class,
- LogoutTest.class
+ UserInfoTest.class
})
public class RuleSuite {
@ClassRule
@@ -36,6 +35,19 @@ public class RuleSuite {
//抛出异常就可以取消测试
throw new RuntimeException("Please create config file before tests");
}
+
+ Config config = Config.getInstance();
+ //登录
+ Config.getBilibiliAPI().login(config.getUsername(), config.getPassword());
+ }
+
+ @Override
+ protected void after() {
+ try {
+ Config.getBilibiliAPI().logout();
+ } catch (IOException | LoginException e) {
+ e.printStackTrace();
+ }
}
};
}
diff --git a/src/test/java/com/hiczp/bilibili/api/test/UserInfoTest.java b/src/test/java/com/hiczp/bilibili/api/test/UserInfoTest.java
new file mode 100644
index 0000000..300a276
--- /dev/null
+++ b/src/test/java/com/hiczp/bilibili/api/test/UserInfoTest.java
@@ -0,0 +1,25 @@
+package com.hiczp.bilibili.api.test;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.hiczp.bilibili.api.BilibiliAPI;
+import com.hiczp.bilibili.api.passport.entity.InfoEntity;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class UserInfoTest {
+ private static final Logger LOGGER = LoggerFactory.getLogger(UserInfoTest.class);
+ private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create();
+
+ private final BilibiliAPI bilibiliAPI = Config.getBilibiliAPI();
+
+ @Test
+ public void getUserInfo() throws Exception {
+ InfoEntity infoEntity = bilibiliAPI.getPassportService()
+ .getInfo(bilibiliAPI.getBilibiliAccount().getAccessToken())
+ .execute()
+ .body();
+ LOGGER.debug("UserInfo below: \n{}", GSON.toJson(infoEntity));
+ }
+}
diff --git a/src/test/resources/config-template.json b/src/test/resources/config-template.json
index 6f499b6..12086c0 100644
--- a/src/test/resources/config-template.json
+++ b/src/test/resources/config-template.json
@@ -1,5 +1,5 @@
{
"username": "xxxxx",
"password": "xxxxx",
- "roomId": "23058"
+ "roomId": "1110317"
}