重构代码, BilibiliAPI 不再 static

This commit is contained in:
czp 2018-01-19 14:40:11 +08:00
parent 4db58a5889
commit 2058d6333d
40 changed files with 741 additions and 1326 deletions

2
.gitignore vendored
View File

@ -2,4 +2,6 @@
.idea
*.iml
target
out
build
src/test/resources/config.json

View File

@ -7,7 +7,7 @@
<groupId>com.hiczp</groupId>
<artifactId>bilibili-api</artifactId>
<name>bilibili-api</name>
<version>1.0</version>
<version>0.0.1</version>
<description>Bilibili API</description>
<url>https://github.com/czp3009/bilibili-api</url>

View File

@ -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/";
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -0,0 +1,13 @@
package com.hiczp.bilibili.api;
public interface BilibiliSecurityContext {
String getAccessToken();
String getRefreshToken();
Long getUserId();
Long getExpirationTime();
Long getLoginTime();
}

View File

@ -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());
}
}
}
}

View File

@ -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();
}

View File

@ -0,0 +1,7 @@
package com.hiczp.bilibili.api;
import com.hiczp.bilibili.api.live.socket.LiveClient;
public interface LiveClientProvider {
LiveClient getLiveClient(long showRoomId);
}

View File

@ -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;
//今天所有的宝箱已经领完

View File

@ -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<String> 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;
}
}

View File

@ -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);
}

View File

@ -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());
}

View File

@ -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<String>[] headerAndValues;
@SafeVarargs
public AddDynamicHeadersInterceptor(Supplier<String>... 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());
}
}

View File

@ -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<String>[] paramAndValues;
@SafeVarargs
public AddDynamicParamsInterceptor(Supplier<String>... 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());
}
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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 {

View File

@ -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<String> nameAndValues) {
return nameAndValues.stream().collect(Collectors.joining("&"));
}
//排序 params 并计算 sign
//传入值为 name1=value1 形式
private String calculateSign(List<String> 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);
}
}
}

View File

@ -71,8 +71,8 @@ public interface LiveService {
@POST("api/sendmsg")
@FormUrlEncoded
Call<SendBulletScreenResponseEntity> sendBulletScreen(@Field("cid") int cid,
@Field("mid") int mid,
Call<SendBulletScreenResponseEntity> sendBulletScreen(@Field("cid") long cid,
@Field("mid") long mid,
@Field("msg") String message,
@Field("rnd") long random,
@Field("mode") int mode,

View File

@ -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;
}

View File

@ -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<BulletScreenListener> 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<BulletScreenListener> 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;
}
}
}
}

View File

@ -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);
}

View File

@ -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) {
}
}

View File

@ -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<BulletScreenListener> 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<BulletScreenListener> getBulletScreenListeners() {
return bulletScreenListeners;
}
public void setBulletScreenListeners(Vector<BulletScreenListener> bulletScreenListeners) {
this.bulletScreenListeners = bulletScreenListeners;
}
public LiveRoomInfoEntity.LiveRoomEntity getLiveRoomEntity() {
return liveRoomEntity;
}
}

View File

@ -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<BulletScreenListener> bulletScreenListeners, @Nullable Consumer<BulletScreenListener> 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;

View File

@ -21,4 +21,8 @@ public interface PassportService {
@POST("api/oauth2/revoke")
Call<LogoutResponseEntity> logout(@Query("access_token") String accessToken);
//TODO sso 未测试
@GET("api/login/sso")
Call sso(@Query("access_token") String accessToken);
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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<SendBulletScreenResponseEntity>() {
private Gson gson = new Gson();
@Override
public void onResponse(Call<SendBulletScreenResponseEntity> call, Response<SendBulletScreenResponseEntity> response) {
gson.toJson(response.body(), System.out);
System.out.println();
}
@Override
public void onFailure(Call<SendBulletScreenResponseEntity> 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();
}
}

View File

@ -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();
}
}

View File

@ -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();
}
}

View File

@ -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();
}
}

View File

@ -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();
}
}
};
}

View File

@ -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));
}
}

View File

@ -1,5 +1,5 @@
{
"username": "xxxxx",
"password": "xxxxx",
"roomId": "23058"
"roomId": "1110317"
}