mirror of
https://github.com/czp3009/bilibili-api.git
synced 2024-12-21 20:30:28 +08:00
重构代码, BilibiliAPI 不再 static
This commit is contained in:
parent
4db58a5889
commit
2058d6333d
2
.gitignore
vendored
2
.gitignore
vendored
@ -2,4 +2,6 @@
|
||||
.idea
|
||||
*.iml
|
||||
target
|
||||
out
|
||||
build
|
||||
src/test/resources/config.json
|
||||
|
2
pom.xml
2
pom.xml
@ -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>
|
||||
|
||||
|
@ -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/";
|
||||
}
|
169
src/main/java/com/hiczp/bilibili/api/BilibiliAPI.java
Normal file
169
src/main/java/com/hiczp/bilibili/api/BilibiliAPI.java
Normal 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;
|
||||
}
|
||||
}
|
97
src/main/java/com/hiczp/bilibili/api/BilibiliAccount.java
Normal file
97
src/main/java/com/hiczp/bilibili/api/BilibiliAccount.java
Normal 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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
package com.hiczp.bilibili.api;
|
||||
|
||||
public interface BilibiliSecurityContext {
|
||||
String getAccessToken();
|
||||
|
||||
String getRefreshToken();
|
||||
|
||||
Long getUserId();
|
||||
|
||||
Long getExpirationTime();
|
||||
|
||||
Long getLoginTime();
|
||||
}
|
137
src/main/java/com/hiczp/bilibili/api/BilibiliSecurityHelper.java
Normal file
137
src/main/java/com/hiczp/bilibili/api/BilibiliSecurityHelper.java
Normal 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
package com.hiczp.bilibili.api;
|
||||
|
||||
import com.hiczp.bilibili.api.live.socket.LiveClient;
|
||||
|
||||
public interface LiveClientProvider {
|
||||
LiveClient getLiveClient(long showRoomId);
|
||||
}
|
@ -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;
|
||||
//今天所有的宝箱已经领完
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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 {
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
@ -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) {
|
||||
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
25
src/test/java/com/hiczp/bilibili/api/test/UserInfoTest.java
Normal file
25
src/test/java/com/hiczp/bilibili/api/test/UserInfoTest.java
Normal 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));
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"username": "xxxxx",
|
||||
"password": "xxxxx",
|
||||
"roomId": "23058"
|
||||
"roomId": "1110317"
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user