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