From 54ac47c8b42f7789bf02077479242c0d30110ded Mon Sep 17 00:00:00 2001 From: zlzw <583819556@qq.com> Date: Tue, 2 Jul 2024 10:01:10 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E7=89=88=E6=94=B9=E7=89=88=EF=BC=9A?= =?UTF-8?q?=20=E6=96=B0=E5=A2=9EB=E7=AB=99=E6=8E=A5=E5=8F=A3=20=E6=B5=8B?= =?UTF-8?q?=E8=AF=95=E7=9B=B4=E6=92=ADWebSocket?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- biliapi/src/main/java/com/yutou/Main.java | 184 ++++++++++++++ .../main/java/com/yutou/bili/api/LiveApi.java | 56 +++++ .../java/com/yutou/bili/api/LoginApi.java | 13 + .../yutou/bili/bean/live/LiveDanmuInfo.java | 46 ++++ .../yutou/bili/bean/live/LiveRoomConfig.java | 13 + .../yutou/bili/bean/live/LiveRoomInfo.java | 234 ++++++++++++++++++ .../bili/bean/live/LiveRoomPlayInfo.java | 231 +++++++++++++++++ .../yutou/bili/bean/live/LiveRoomStatus.java | 37 +++ .../yutou/bili/bean/live/MasterInfoBean.java | 99 ++++++++ .../bili/bean/login/CheckCookieBean.java | 15 ++ .../bili/databases/BiliBiliLoginDatabase.java | 10 +- .../com/yutou/bili/enums/LiveProtocol.java | 23 ++ .../com/yutou/bili/enums/LiveVideoCodec.java | 22 ++ .../yutou/bili/enums/LiveVideoDefinition.java | 28 +++ .../com/yutou/bili/enums/LiveVideoFormat.java | 23 ++ .../com/yutou/bili/net/BiliCookieManager.java | 90 +++++++ .../yutou/bili/net/BiliLiveNetApiManager.java | 39 +++ .../bili/net/BiliLoginNetApiManager.java | 21 +- .../yutou/bili/net/BiliUserNetApiManager.java | 7 +- .../com/yutou/bili/net/WebSocketManager.java | 230 +++++++++++++++++ .../com/yutou/bili/utils/LiveHeartBeat.java | 69 ++++++ .../main/java/com/yutou/okhttp/FileBody.java | 11 + .../java/com/yutou/okhttp/FileCallback.java | 103 ++++++++ .../java/com/yutou/okhttp/ParamsContext.java | 1 + .../java/com/yutou/okhttp/api/BaseApi.java | 31 ++- .../converter/JsonResponseBodyConverter.java | 32 ++- .../main/java/com/yutou/utils/RSAUtils.java | 136 ++++++++++ 27 files changed, 1768 insertions(+), 36 deletions(-) create mode 100644 biliapi/src/main/java/com/yutou/Main.java create mode 100644 biliapi/src/main/java/com/yutou/bili/api/LiveApi.java create mode 100644 biliapi/src/main/java/com/yutou/bili/bean/live/LiveDanmuInfo.java create mode 100644 biliapi/src/main/java/com/yutou/bili/bean/live/LiveRoomConfig.java create mode 100644 biliapi/src/main/java/com/yutou/bili/bean/live/LiveRoomInfo.java create mode 100644 biliapi/src/main/java/com/yutou/bili/bean/live/LiveRoomPlayInfo.java create mode 100644 biliapi/src/main/java/com/yutou/bili/bean/live/LiveRoomStatus.java create mode 100644 biliapi/src/main/java/com/yutou/bili/bean/live/MasterInfoBean.java create mode 100644 biliapi/src/main/java/com/yutou/bili/bean/login/CheckCookieBean.java create mode 100644 biliapi/src/main/java/com/yutou/bili/enums/LiveProtocol.java create mode 100644 biliapi/src/main/java/com/yutou/bili/enums/LiveVideoCodec.java create mode 100644 biliapi/src/main/java/com/yutou/bili/enums/LiveVideoDefinition.java create mode 100644 biliapi/src/main/java/com/yutou/bili/enums/LiveVideoFormat.java create mode 100644 biliapi/src/main/java/com/yutou/bili/net/BiliCookieManager.java create mode 100644 biliapi/src/main/java/com/yutou/bili/net/BiliLiveNetApiManager.java create mode 100644 biliapi/src/main/java/com/yutou/bili/net/WebSocketManager.java create mode 100644 biliapi/src/main/java/com/yutou/bili/utils/LiveHeartBeat.java create mode 100644 common/src/main/java/com/yutou/okhttp/FileBody.java create mode 100644 common/src/main/java/com/yutou/okhttp/FileCallback.java create mode 100644 common/src/main/java/com/yutou/utils/RSAUtils.java diff --git a/biliapi/src/main/java/com/yutou/Main.java b/biliapi/src/main/java/com/yutou/Main.java new file mode 100644 index 0000000..452021b --- /dev/null +++ b/biliapi/src/main/java/com/yutou/Main.java @@ -0,0 +1,184 @@ +package com.yutou; + +import com.alibaba.fastjson2.JSONObject; +import com.yutou.bili.api.LiveApi; +import com.yutou.bili.api.UserApi; +import com.yutou.bili.bean.live.LiveRoomConfig; +import com.yutou.bili.bean.live.LiveRoomPlayInfo; +import com.yutou.bili.bean.login.LoginCookie; +import com.yutou.bili.bean.login.UserInfoBean; +import com.yutou.bili.enums.LiveProtocol; +import com.yutou.bili.enums.LiveVideoCodec; +import com.yutou.bili.enums.LiveVideoDefinition; +import com.yutou.bili.enums.LiveVideoFormat; +import com.yutou.bili.net.BiliLiveNetApiManager; +import com.yutou.bili.net.BiliLoginNetApiManager; +import com.yutou.bili.databases.BiliBiliLoginDatabase; +import com.yutou.bili.net.BiliUserNetApiManager; +import com.yutou.bili.net.WebSocketManager; +import com.yutou.inter.IHttpApiCheckCallback; +import com.yutou.okhttp.BaseBean; +import com.yutou.okhttp.FileCallback; +import com.yutou.okhttp.HttpCallback; +import com.yutou.okhttp.HttpLoggingInterceptor; +import jakarta.xml.bind.DatatypeConverter; +import okhttp3.Headers; + +import java.io.ByteArrayOutputStream; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; + +public class Main { + public static void main(String[] args) { + HttpLoggingInterceptor.setLog(false); + HttpLoggingInterceptor.setLog(true); + getPlayUrl(); + //testSocket(); + + } + + + public static void testSocket() { + try { + JSONObject json = new JSONObject(); + json.put("roomid", "13246789"); + json.put("protover", "3"); + json.put("platform", "web"); + json.put("type", 2); + json.put("key", "aaaabbb"); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + // outputStream.write(toLH(json.toString().length() + 16)); + outputStream.write(new byte[]{0, 0, 1, 68, 0, 16, 0, 1, 0, 0, 0, 7, 0, 0, 0, 1}); + outputStream.write(json.toJSONString().getBytes(StandardCharsets.UTF_8)); + outputStream.flush(); + System.out.println("\n\n\n"); + String str = DatatypeConverter.printHexBinary(outputStream.toByteArray()); + for (int i = 0; i < str.length(); i = i + 4) { + if (i % 32 == 0 && i != 0) { + System.out.println(); + } + if (str.length() - i > 4) { + System.out.print(str.substring(i, i + 4) + " "); + } else { + System.out.println(str.substring(i)); + } + } + System.out.println("\n\n\n"); + + } catch (Exception e) { + e.printStackTrace(); + } + } + + public static void getPlayUrl() { + BiliLiveNetApiManager + .getInstance() + .getApi(new IHttpApiCheckCallback() { + @Override + public void onSuccess(LiveApi api) { + String roomId = "22689676"; + String mid = "68057278"; + + // roomId="42062"; + + api.getLiveRoomPlayInfo( + roomId, + LiveProtocol.getAll(), + LiveVideoFormat.getAll(), + LiveVideoCodec.getAll(), + LiveVideoDefinition.ORIGINAL.getValue() + ).enqueue(new HttpCallback<>() { + @Override + public void onResponse(Headers headers, int code, String status, LiveRoomPlayInfo response, String rawResponse) { + LiveRoomPlayInfo.Codec codec = response.getPlayurlInfo().getPlayurl().getStream().get(0).getFormat().get(0).getCodec().get(0); + + LiveRoomConfig config = new LiveRoomConfig(); + config.setUid("0"); + config.setRoomId(roomId); + config.setMid(mid); + config.setLogin(false); + WebSocketManager.getInstance().addRoom(config); + + /*String url = codec.getUrlInfo().get(0).getHost() + codec.getBaseUrl() + codec.getUrlInfo().get(0).getExtra(); + System.out.println("下载url = " + url); + api.downloadLive(url).enqueue(new FileCallback<>(response) { + @Override + public void onStart(LiveRoomPlayInfo bean) { + System.out.println("开始下载"); + } + + @Override + public boolean onDownload(Headers headers, LiveRoomPlayInfo bean, long len, long total) { + System.out.println("下载中:"+len+"|"+total); + return true; + } + + @Override + public void onFinish(LiveRoomPlayInfo bean) { + System.out.println("下载结束"); + } + + @Override + public void onFailure(LiveRoomPlayInfo bean, Throwable throwable) { + System.out.println("下载失败"); + } + });*/ + + } + + @Override + public void onFailure(Throwable throwable) { + + } + }); + } + + @Override + public void onError(int code, String error) { + + } + }); + } + + public static void login(String username, String password) { + BiliLoginNetApiManager.getInstance().login(new HttpCallback() { + @Override + public void onResponse(Headers headers, int code, String status, LoginCookie response, String rawResponse) { + System.out.println("headers = " + headers + ", code = " + code + ", status = " + status + ", response = " + response + ", rawResponse = " + rawResponse); + if (code == BiliLoginNetApiManager.LOGIN_SUCCESS) { + BiliBiliLoginDatabase.getInstance().initData(response).close(); + } + } + + @Override + public void onFailure(Throwable throwable) { + + } + }); + } + + public static void getUserInfo() { + BiliUserNetApiManager.getInstance().getUserApi(new IHttpApiCheckCallback() { + @Override + public void onSuccess(UserApi api) { + api.getUserInfo().enqueue(new HttpCallback() { + @Override + public void onResponse(Headers headers, int code, String status, UserInfoBean response, String rawResponse) { + System.out.println("response = " + rawResponse); + System.out.println(response); + } + + @Override + public void onFailure(Throwable throwable) { + + } + }); + } + + @Override + public void onError(int code, String error) { + System.out.println("code = " + code + ", error = " + error); + } + }); + } +} \ No newline at end of file diff --git a/biliapi/src/main/java/com/yutou/bili/api/LiveApi.java b/biliapi/src/main/java/com/yutou/bili/api/LiveApi.java new file mode 100644 index 0000000..565d2b4 --- /dev/null +++ b/biliapi/src/main/java/com/yutou/bili/api/LiveApi.java @@ -0,0 +1,56 @@ +package com.yutou.bili.api; + +import com.yutou.bili.bean.live.*; +import com.yutou.okhttp.BaseBean; +import com.yutou.okhttp.FileBody; +import com.yutou.okhttp.HttpBody; +import retrofit2.Call; +import retrofit2.http.GET; +import retrofit2.http.Query; +import retrofit2.http.Streaming; +import retrofit2.http.Url; + +/** + * 直播间相关API + * 文档地址 + */ +public interface LiveApi { + /** + * 获取直播间信息 + * @param roomId 直播间号 必要 可以为短号 + */ + @GET("/room/v1/Room/get_info") + Call> getRoomInfo(@Query("room_id")String roomId); + @GET("/room/v1/Room/getRoomInfoOld") + Call> getRoomStatus(@Query("mid")String mid); + + /** + * 获取主播信息 + */ + @GET("/live_user/v1/Master/info") + Call> getMasterInfo(@Query("uid")String uid); + + /** + * 获取直播间信息 + * @param id 直播间id + * @param protocol 直播协议 {@link com.yutou.bili.enums.LiveProtocol} + * @param format 格式 {@link com.yutou.bili.enums.LiveVideoFormat} + * @param codec 编码 {@link com.yutou.bili.enums.LiveVideoCodec} + * @param qn 清晰度 {@link com.yutou.bili.enums.LiveVideoDefinition} + * @return + */ + @GET("/xlive/web-room/v2/index/getRoomPlayInfo") + Call> getLiveRoomPlayInfo( + @Query("room_id")String id, + @Query("protocol")String protocol, + @Query("format")String format, + @Query("codec")String codec, + @Query("qn")int qn + ); + @Streaming + @GET() + Call> downloadLive(@Url String url); + + @GET("/xlive/web-room/v1/index/getDanmuInfo") + Call> getLiveRoomDanmuInfo(@Query("id")String id); +} diff --git a/biliapi/src/main/java/com/yutou/bili/api/LoginApi.java b/biliapi/src/main/java/com/yutou/bili/api/LoginApi.java index 54ecfa2..93ed0a7 100644 --- a/biliapi/src/main/java/com/yutou/bili/api/LoginApi.java +++ b/biliapi/src/main/java/com/yutou/bili/api/LoginApi.java @@ -1,5 +1,6 @@ package com.yutou.bili.api; +import com.yutou.bili.bean.login.CheckCookieBean; import com.yutou.bili.bean.login.LoginInfoBean; import com.yutou.bili.bean.login.QRCodeGenerateBean; import com.yutou.okhttp.HttpBody; @@ -8,11 +9,23 @@ import retrofit2.http.GET; import retrofit2.http.Query; public interface LoginApi { + /** + * 获取登陆二维码 + */ @GET("/x/passport-login/web/qrcode/generate") Call> getQRCodeGenerate(); + /** + * 通过二维码登录 + */ @GET("/x/passport-login/web/qrcode/poll") Call> loginQRCode(@Query("qrcode_key") String qrcode_key); + /** + * 检查cookie是否有效,太麻烦了以后做 + */ + @GET("x/passport-login/web/cookie/info") + Call> checkCookie(); + } diff --git a/biliapi/src/main/java/com/yutou/bili/bean/live/LiveDanmuInfo.java b/biliapi/src/main/java/com/yutou/bili/bean/live/LiveDanmuInfo.java new file mode 100644 index 0000000..74d50b6 --- /dev/null +++ b/biliapi/src/main/java/com/yutou/bili/bean/live/LiveDanmuInfo.java @@ -0,0 +1,46 @@ +package com.yutou.bili.bean.live; + +import com.alibaba.fastjson2.annotation.JSONField; +import lombok.Data; + +import java.util.List; + +@Data +public class LiveDanmuInfo { + @JSONField(name = "group") + private String group; + + @JSONField(name = "business_id") + private int businessId; + + @JSONField(name = "refresh_row_factor") + private double refreshRowFactor; + + @JSONField(name = "refresh_rate") + private int refreshRate; + + @JSONField(name = "max_delay") + private int maxDelay; + + @JSONField(name = "token") + private String token; + + @JSONField(name = "host_list") + private List hostList; + + @Data + public static class Host { + + @JSONField(name = "host") + private String host; + + @JSONField(name = "port") + private int port; + + @JSONField(name = "wss_port") + private int wssPort; + + @JSONField(name = "ws_port") + private int wsPort; + } +} diff --git a/biliapi/src/main/java/com/yutou/bili/bean/live/LiveRoomConfig.java b/biliapi/src/main/java/com/yutou/bili/bean/live/LiveRoomConfig.java new file mode 100644 index 0000000..1a52288 --- /dev/null +++ b/biliapi/src/main/java/com/yutou/bili/bean/live/LiveRoomConfig.java @@ -0,0 +1,13 @@ +package com.yutou.bili.bean.live; + +import lombok.Data; + +@Data +public class LiveRoomConfig { + String uid; + String roomId; + String mid;//真实房间id + boolean isLogin; + LiveDanmuInfo liveInfo; + +} diff --git a/biliapi/src/main/java/com/yutou/bili/bean/live/LiveRoomInfo.java b/biliapi/src/main/java/com/yutou/bili/bean/live/LiveRoomInfo.java new file mode 100644 index 0000000..ca4bc3b --- /dev/null +++ b/biliapi/src/main/java/com/yutou/bili/bean/live/LiveRoomInfo.java @@ -0,0 +1,234 @@ +package com.yutou.bili.bean.live; + +import com.alibaba.fastjson2.annotation.JSONField; +import lombok.Data; + +import java.util.List; + +@Data +public class LiveRoomInfo { + @JSONField(name = "uid") + private int uid; + + @JSONField(name = "room_id") + private int roomId; + + @JSONField(name = "short_id") + private int shortId; + + @JSONField(name = "attention") + private int attention; + + @JSONField(name = "online") + private int online; + + @JSONField(name = "is_portrait") + private boolean isPortrait; + + @JSONField(name = "description") + private String description; + + @JSONField(name = "live_status") + private int liveStatus; + + @JSONField(name = "area_id") + private int areaId; + + @JSONField(name = "parent_area_id") + private int parentAreaId; + + @JSONField(name = "parent_area_name") + private String parentAreaName; + + @JSONField(name = "old_area_id") + private int oldAreaId; + + @JSONField(name = "background") + private String background; + + @JSONField(name = "title") + private String title; + + @JSONField(name = "user_cover") + private String userCover; + + @JSONField(name = "keyframe") + private String keyframe; + + @JSONField(name = "is_strict_room") + private boolean isStrictRoom; + + @JSONField(name = "live_time") + private String liveTime; + + @JSONField(name = "tags") + private String tags; + + @JSONField(name = "is_anchor") + private int isAnchor; + + @JSONField(name = "room_silent_type") + private String roomSilentType; + + @JSONField(name = "room_silent_level") + private int roomSilentLevel; + + @JSONField(name = "room_silent_second") + private int roomSilentSecond; + + @JSONField(name = "area_name") + private String areaName; + + @JSONField(name = "pendants") + private String pendants; + + @JSONField(name = "area_pendants") + private String areaPendants; + + @JSONField(name = "hot_words") + private List hotWords; + + @JSONField(name = "hot_words_status") + private int hotWordsStatus; + + @JSONField(name = "verify") + private String verify; + + @JSONField(name = "new_pendants") + private NewPendants newPendants; + + @JSONField(name = "up_session") + private String upSession; + + @JSONField(name = "pk_status") + private int pkStatus; + + @JSONField(name = "pk_id") + private int pkId; + + @JSONField(name = "battle_id") + private int battleId; + + @JSONField(name = "allow_change_area_time") + private int allowChangeAreaTime; + + @JSONField(name = "allow_upload_cover_time") + private int allowUploadCoverTime; + + @JSONField(name = "studio_info") + private StudioInfo studioInfo; + + @Data + public static class NewPendants { + @JSONField(name = "frame") + private Frame frame; + + @JSONField(name = "badge") + private Badge badge; + + @JSONField(name = "mobile_frame") + private MobileFrame mobileFrame; + @JSONField(name = "mobile_badge") + private MobileBadge mobileBadge; + } + + @Data + public static class Frame { + @JSONField(name = "name") + private String name; + + @JSONField(name = "value") + private String value; + + @JSONField(name = "position") + private int position; + + @JSONField(name = "desc") + private String desc; + + @JSONField(name = "area") + private int area; + + @JSONField(name = "area_old") + private int areaOld; + + @JSONField(name = "bg_color") + private String bgColor; + + @JSONField(name = "bg_pic") + private String bgPic; + + @JSONField(name = "use_old_area") + private boolean useOldArea; + } + + @Data + public static class Badge { + @JSONField(name = "name") + private String name; + + @JSONField(name = "position") + private int position; + + @JSONField(name = "value") + private String value; + + @JSONField(name = "desc") + private String desc; + } + + @Data + public static class MobileFrame { + @JSONField(name = "name") + private String name; + + @JSONField(name = "value") + private String value; + + @JSONField(name = "position") + private int position; + + @JSONField(name = "desc") + private String desc; + + @JSONField(name = "area") + private int area; + + @JSONField(name = "area_old") + private int areaOld; + + @JSONField(name = "bg_color") + private String bgColor; + + @JSONField(name = "bg_pic") + private String bgPic; + + @JSONField(name = "use_old_area") + private boolean useOldArea; + } + + @Data + public static class MobileBadge { + @JSONField(name = "name") + private String name; + + @JSONField(name = "position") + private int position; + + @JSONField(name = "value") + private String value; + + @JSONField(name = "desc") + private String desc; + } + + @Data + public static class StudioInfo { + @JSONField(name = "status") + private int status; + + @JSONField(name = "master_list") + private List masterList; + } + +} diff --git a/biliapi/src/main/java/com/yutou/bili/bean/live/LiveRoomPlayInfo.java b/biliapi/src/main/java/com/yutou/bili/bean/live/LiveRoomPlayInfo.java new file mode 100644 index 0000000..41dd3c4 --- /dev/null +++ b/biliapi/src/main/java/com/yutou/bili/bean/live/LiveRoomPlayInfo.java @@ -0,0 +1,231 @@ +package com.yutou.bili.bean.live; + +import com.alibaba.fastjson2.annotation.JSONField; +import com.yutou.okhttp.BaseBean; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.List; + +@EqualsAndHashCode(callSuper = true) +@Data +public class LiveRoomPlayInfo extends BaseBean { + @JSONField(name = "room_id") + private int roomId; + + @JSONField(name = "short_id") + private int shortId; + + @JSONField(name = "uid") + private int uid; + + @JSONField(name = "is_hidden") + private boolean isHidden; + + @JSONField(name = "is_locked") + private boolean isLocked; + + @JSONField(name = "is_portrait") + private boolean isPortrait; + + @JSONField(name = "live_status") + private int liveStatus; + + @JSONField(name = "hidden_till") + private int hiddenTill; + + @JSONField(name = "lock_till") + private int lockTill; + + @JSONField(name = "encrypted") + private boolean encrypted; + + @JSONField(name = "pwd_verified") + private boolean pwdVerified; + + @JSONField(name = "live_time") + private int liveTime; + + @JSONField(name = "room_shield") + private int roomShield; + + @JSONField(name = "all_special_types") + private List allSpecialTypes; + + @JSONField(name = "playurl_info") + private PlayurlInfo playurlInfo; + + @JSONField(name = "official_type") + private int officialType; + + @JSONField(name = "official_room_id") + private int officialRoomId; + + @JSONField(name = "risk_with_delay") + private int riskWithDelay; + + @Data + public static class PlayurlInfo { + @JSONField(name = "conf_json") + private String confJson; + + @JSONField(name = "playurl") + private Playurl playurl; + + @JSONField(name = "dolby_qn") + private Integer dolbyQn; + } + + @Data + public static class Playurl { + @JSONField(name = "cid") + private int cid; + + @JSONField(name = "g_qn_desc") + private List gQnDesc; + + @JSONField(name = "stream") + private List stream; + + @JSONField(name = "p2p_data") + private P2pData p2pData; + } + + @Data + public static class QnDesc { + @JSONField(name = "qn") + private int qn; + + @JSONField(name = "desc") + private String desc; + + @JSONField(name = "hdr_desc") + private String hdrDesc; + + @JSONField(name = "attr_desc") + private Object attrDesc; + } + + @Data + public static class Stream { + @JSONField(name = "protocol_name") + private String protocolName; + + @JSONField(name = "format") + private List format; + + @JSONField(name = "p2p_type") + private int p2pType; + + @JSONField(name = "free_type") + private int freeType; + + @JSONField(name = "mid") + private int mid; + + @JSONField(name = "sid") + private String sid; + + @JSONField(name = "chash") + private int chash; + + @JSONField(name = "bmt") + private int bmt; + + @JSONField(name = "sche") + private String sche; + + @JSONField(name = "score") + private int score; + + @JSONField(name = "pp") + private String pp; + + @JSONField(name = "source") + private String source; + + @JSONField(name = "trace") + private int trace; + + @JSONField(name = "site") + private String site; + @JSONField(name = "zoneid_l") + private int zoneidL; + + @JSONField(name = "sid_l") + private String sidL; + + @JSONField(name = "order") + private int order; + } + + @Data + public static class Format { + @JSONField(name = "format_name") + private String formatName; + + @JSONField(name = "codec") + private List codec; + + @JSONField(name = "hdr_qn") + private Integer hdrQn; + + @JSONField(name = "dolby_type") + private int dolbyType; + + @JSONField(name = "attr_name") + private String attrName; + } + + @Data + public static class Codec { + @JSONField(name = "codec_name") + private String codecName; + + @JSONField(name = "current_qn") + private int currentQn; + + @JSONField(name = "accept_qn") + private List acceptQn; + + @JSONField(name = "base_url") + private String baseUrl; + + @JSONField(name = "url_info") + private List urlInfo; + + @JSONField(name = "hdr_desc") + private String hdrDesc; + + @JSONField(name = "attr_desc") + private Object attrDesc; + } + + @Data + public static class UrlInfo { + @JSONField(name = "host") + private String host; + + @JSONField(name = "extra") + private String extra; + + @JSONField(name = "stream_ttl") + private int streamTtl; + } + + @Data + public static class P2pData { + @JSONField(name = "p2p") + private boolean p2p; + + @JSONField(name = "p2p_type") + private int p2pType; + + @JSONField(name = "m_p2p") + private boolean mP2p; + + @JSONField(name = "m_servers") + private List mServers; + } + +} diff --git a/biliapi/src/main/java/com/yutou/bili/bean/live/LiveRoomStatus.java b/biliapi/src/main/java/com/yutou/bili/bean/live/LiveRoomStatus.java new file mode 100644 index 0000000..3f84e2b --- /dev/null +++ b/biliapi/src/main/java/com/yutou/bili/bean/live/LiveRoomStatus.java @@ -0,0 +1,37 @@ +package com.yutou.bili.bean.live; + +import com.alibaba.fastjson2.annotation.JSONField; +import lombok.Data; + +@Data +public class LiveRoomStatus { + @JSONField(name = "roomStatus") + private int roomStatus; + + @JSONField(name = "roundStatus") + private int roundStatus; + + @JSONField(name = "liveStatus") + private int liveStatus; + + @JSONField(name = "url") + private String url; + + @JSONField(name = "title") + private String title; + + @JSONField(name = "cover") + private String cover; + + @JSONField(name = "online") + private int online; + + @JSONField(name = "roomid") + private int roomid; + + @JSONField(name = "broadcast_type") + private int broadcastType; + + @JSONField(name = "online_hidden") + private int onlineHidden; +} \ No newline at end of file diff --git a/biliapi/src/main/java/com/yutou/bili/bean/live/MasterInfoBean.java b/biliapi/src/main/java/com/yutou/bili/bean/live/MasterInfoBean.java new file mode 100644 index 0000000..4cfa426 --- /dev/null +++ b/biliapi/src/main/java/com/yutou/bili/bean/live/MasterInfoBean.java @@ -0,0 +1,99 @@ +package com.yutou.bili.bean.live; + +import com.alibaba.fastjson2.annotation.JSONField; +import com.yutou.okhttp.BaseBean; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.List; + +@EqualsAndHashCode(callSuper = true) +@Data +public class MasterInfoBean extends BaseBean { + @JSONField(name = "info") + private Info info; + + @JSONField(name = "exp") + private Exp exp; + + @JSONField(name = "follower_num") + private int followerNum; + + @JSONField(name = "room_id") + private int roomId; + + @JSONField(name = "medal_name") + private String medalName; + + @JSONField(name = "glory_count") + private int gloryCount; + + @JSONField(name = "pendant") + private String pendant; + + @JSONField(name = "link_group_num") + private int linkGroupNum; + + @JSONField(name = "room_news") + private RoomNews roomNews; + + @Data + public static class Info { + @JSONField(name = "uid") + private int uid; + + @JSONField(name = "uname") + private String uname; + + @JSONField(name = "face") + private String face; + + @JSONField(name = "official_verify") + private OfficialVerify officialVerify; + + @JSONField(name = "gender") + private int gender; + } + + @Data + public static class Exp { + @JSONField(name = "master_level") + private MasterLevel masterLevel; + } + + @Data + public static class MasterLevel { + @JSONField(name = "level") + private int level; + + @JSONField(name = "color") + private int color; + + @JSONField(name = "current") + private List current; + + @JSONField(name = "next") + private List next; + } + + @Data + public static class OfficialVerify { + @JSONField(name = "type") + private int type; + + @JSONField(name = "desc") + private String desc; + } + + @Data + public static class RoomNews { + @JSONField(name = "content") + private String content; + + @JSONField(name = "ctime") + private String ctime; + + @JSONField(name = "ctime_text") + private String ctimeText; + } +} diff --git a/biliapi/src/main/java/com/yutou/bili/bean/login/CheckCookieBean.java b/biliapi/src/main/java/com/yutou/bili/bean/login/CheckCookieBean.java new file mode 100644 index 0000000..03f6dae --- /dev/null +++ b/biliapi/src/main/java/com/yutou/bili/bean/login/CheckCookieBean.java @@ -0,0 +1,15 @@ +package com.yutou.bili.bean.login; + +import com.alibaba.fastjson2.annotation.JSONField; +import com.yutou.okhttp.BaseBean; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@EqualsAndHashCode(callSuper = true) +@Data +public class CheckCookieBean extends BaseBean { + @JSONField(name = "refresh") + boolean refresh; + @JSONField(name = "timestamp") + long timestamp; +} diff --git a/biliapi/src/main/java/com/yutou/bili/databases/BiliBiliLoginDatabase.java b/biliapi/src/main/java/com/yutou/bili/databases/BiliBiliLoginDatabase.java index 983c0df..a56bcea 100644 --- a/biliapi/src/main/java/com/yutou/bili/databases/BiliBiliLoginDatabase.java +++ b/biliapi/src/main/java/com/yutou/bili/databases/BiliBiliLoginDatabase.java @@ -35,12 +35,16 @@ public class BiliBiliLoginDatabase extends SQLiteManager { return this; } - public List get() { - return super.get(cookie.getTableName(), LoginCookie.class); + public LoginCookie get() { + List list = super.get(cookie.getTableName(), LoginCookie.class); + if (!list.isEmpty()) { + return list.get(0); + } + return null; } @Override - protected LoginCookie getDataBean() { + protected LoginCookie getDataBean() { return new LoginCookie(); } } diff --git a/biliapi/src/main/java/com/yutou/bili/enums/LiveProtocol.java b/biliapi/src/main/java/com/yutou/bili/enums/LiveProtocol.java new file mode 100644 index 0000000..307c742 --- /dev/null +++ b/biliapi/src/main/java/com/yutou/bili/enums/LiveProtocol.java @@ -0,0 +1,23 @@ +package com.yutou.bili.enums; + +import lombok.Getter; + +@Getter +public enum LiveProtocol { + stream(0), + hls(1); + + private final int value; + + private LiveProtocol(int value) { + this.value = value; + } + + public static String getAll() { + StringBuilder sb = new StringBuilder(); + for (LiveProtocol value : values()) { + sb.append(String.valueOf(value.value)).append(","); + } + return sb.substring(0, sb.length() - 1); + } +} diff --git a/biliapi/src/main/java/com/yutou/bili/enums/LiveVideoCodec.java b/biliapi/src/main/java/com/yutou/bili/enums/LiveVideoCodec.java new file mode 100644 index 0000000..de36623 --- /dev/null +++ b/biliapi/src/main/java/com/yutou/bili/enums/LiveVideoCodec.java @@ -0,0 +1,22 @@ +package com.yutou.bili.enums; + +import lombok.Getter; + +@Getter +public enum LiveVideoCodec { + AVC(0), + HEVC(1); + + private final int value; + + private LiveVideoCodec(int value) { + this.value=value; + } + public static String getAll() { + StringBuilder sb = new StringBuilder(); + for (LiveVideoCodec value : values()) { + sb.append(String.valueOf(value.value)).append(","); + } + return sb.substring(0, sb.length() - 1); + } +} diff --git a/biliapi/src/main/java/com/yutou/bili/enums/LiveVideoDefinition.java b/biliapi/src/main/java/com/yutou/bili/enums/LiveVideoDefinition.java new file mode 100644 index 0000000..aca15de --- /dev/null +++ b/biliapi/src/main/java/com/yutou/bili/enums/LiveVideoDefinition.java @@ -0,0 +1,28 @@ +package com.yutou.bili.enums; + +import lombok.Getter; + +@Getter +public enum LiveVideoDefinition { + + LOW(80), // 流畅 + HIGH(150), // 高清 + SUPER(250), // 超清 + BLU_RAY(400), // 蓝光 + ORIGINAL(10000), // 原画 + V4K(20000), + DOLBY(30000); + + private final int value; + + private LiveVideoDefinition(int value) { + this.value = value; + } + public static String getAll() { + StringBuilder sb = new StringBuilder(); + for (LiveVideoDefinition value : values()) { + sb.append(String.valueOf(value.value)).append(","); + } + return sb.substring(0, sb.length() - 1); + } +} diff --git a/biliapi/src/main/java/com/yutou/bili/enums/LiveVideoFormat.java b/biliapi/src/main/java/com/yutou/bili/enums/LiveVideoFormat.java new file mode 100644 index 0000000..fd47ff8 --- /dev/null +++ b/biliapi/src/main/java/com/yutou/bili/enums/LiveVideoFormat.java @@ -0,0 +1,23 @@ +package com.yutou.bili.enums; + +import lombok.Getter; + +@Getter +public enum LiveVideoFormat { + FLV(0), + TS(1), + FMP4(2); + + private final int value; + + private LiveVideoFormat(int value) { + this.value=value; + } + public static String getAll() { + StringBuilder sb = new StringBuilder(); + for (LiveVideoFormat value : values()) { + sb.append(String.valueOf(value.value)).append(","); + } + return sb.substring(0, sb.length() - 1); + } +} diff --git a/biliapi/src/main/java/com/yutou/bili/net/BiliCookieManager.java b/biliapi/src/main/java/com/yutou/bili/net/BiliCookieManager.java new file mode 100644 index 0000000..c07658e --- /dev/null +++ b/biliapi/src/main/java/com/yutou/bili/net/BiliCookieManager.java @@ -0,0 +1,90 @@ +package com.yutou.bili.net; + +import com.yutou.bili.bean.login.CheckCookieBean; +import com.yutou.bili.bean.login.LoginInfoBean; +import com.yutou.inter.IHttpApiCheckCallback; +import com.yutou.okhttp.HttpCallback; +import com.yutou.utils.RSAUtils; +import okhttp3.Headers; + +import javax.crypto.Cipher; +import javax.crypto.spec.OAEPParameterSpec; +import javax.crypto.spec.PSource; +import java.math.BigInteger; +import java.nio.charset.StandardCharsets; +import java.security.KeyFactory; +import java.security.PublicKey; +import java.security.spec.MGF1ParameterSpec; +import java.security.spec.X509EncodedKeySpec; +import java.util.Base64; + +public class BiliCookieManager { + public static final int COOKIE_INVALID = -101; + public static final int COOKIE_SUCCESS = 0; + + private static final String PUBLIC_KEY = "-----BEGIN PUBLIC KEY-----\n" + + "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDLgd2OAkcGVtoE3ThUREbio0Eg\n" + + "Uc/prcajMKXvkCKFCWhJYJcLkcM2DKKcSeFpD/j6Boy538YXnR6VhcuUJOhH2x71\n" + + "nzPjfdTcqMz7djHum0qSZA0AyCBDABUqCrfNgCiJ00Ra7GmRj+YCK1NJEuewlb40\n" + + "JNrRuoEUXpabUzGB8QIDAQAB\n" + + "-----END PUBLIC KEY-----"; + + public void checkCookie(IHttpApiCheckCallback callback){ + BiliLoginNetApiManager.getInstance().getLoginApi(true) + .checkCookie().enqueue(new HttpCallback() { + @Override + public void onResponse(Headers headers, int code, String status, CheckCookieBean response, String rawResponse) { + if(code==-101){ + // TODO cookie失效,需要重新登录 + callback.onError(COOKIE_INVALID,"cookie失效,需要重新登录"); + return; + } + if(response.isRefresh()){ + refreshCookie(); + } + callback.onSuccess(COOKIE_SUCCESS); + } + + @Override + public void onFailure(Throwable throwable) { + + } + }); + } + + /** + * 文档地址 + */ + private void refreshCookie(){ + try { + String refreshTime = String.format("refresh_%d", System.currentTimeMillis()); + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + String publicKeyStr = PUBLIC_KEY + .replace("-----BEGIN PUBLIC KEY-----", "") + .replace("-----END PUBLIC KEY-----", "") + .replace("\n", "") + .trim(); + byte[] publicBytes = Base64.getDecoder().decode(publicKeyStr); + X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(publicBytes); + PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec); + + String algorithm = "RSA/ECB/OAEPPadding"; + Cipher cipher = Cipher.getInstance(algorithm); + cipher.init(Cipher.ENCRYPT_MODE, publicKey); + + // Encode the plaintext to bytes + byte[] plaintextBytes = refreshTime.getBytes(StandardCharsets.UTF_8); + + // Add OAEP padding to the plaintext bytes + OAEPParameterSpec oaepParams = new OAEPParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA256, PSource.PSpecified.DEFAULT); + cipher.init(Cipher.ENCRYPT_MODE, publicKey, oaepParams); + // Encrypt the padded plaintext bytes + byte[] encryptedBytes = cipher.doFinal(plaintextBytes); + // Convert the encrypted bytes to a Base64-encoded string + String encrypted = new BigInteger(1, encryptedBytes).toString(16); + }catch (Exception e){ + e.printStackTrace(); + } + + } +} diff --git a/biliapi/src/main/java/com/yutou/bili/net/BiliLiveNetApiManager.java b/biliapi/src/main/java/com/yutou/bili/net/BiliLiveNetApiManager.java new file mode 100644 index 0000000..851c275 --- /dev/null +++ b/biliapi/src/main/java/com/yutou/bili/net/BiliLiveNetApiManager.java @@ -0,0 +1,39 @@ +package com.yutou.bili.net; + +import com.alibaba.fastjson2.JSONObject; +import com.yutou.bili.api.LiveApi; +import com.yutou.bili.bean.login.LoginCookie; +import com.yutou.bili.databases.BiliBiliLoginDatabase; +import com.yutou.inter.IHttpApiCheckCallback; +import com.yutou.okhttp.api.BaseApi; + +import java.util.HashMap; + +public class BiliLiveNetApiManager extends BaseApi { + private static BiliLiveNetApiManager instance; + + public static BiliLiveNetApiManager getInstance() { + if (instance == null) { + instance = new BiliLiveNetApiManager("https://api.live.bilibili.com"); + } + return instance; + } + + private BiliLiveNetApiManager(String URL) { + super(URL); + } + + public void getApi(IHttpApiCheckCallback callback) { + LoginCookie cookie = BiliBiliLoginDatabase.getInstance().get(); + if (cookie != null) { + useCookie(JSONObject.parseObject(JSONObject.toJSONString(cookie))); + } + HashMap header=new HashMap<>(); + header.put("Accept-Language", "zh-CN,zh;q=0.8"); + header.put("Referer", "https://live.bilibili.com"); + header.put("Connection", "keep-alive"); + header.put("Upgrade-Insecure-Requests", "1"); + setHeaders(header); + callback.onSuccess(createApi(LiveApi.class)); + } +} diff --git a/biliapi/src/main/java/com/yutou/bili/net/BiliLoginNetApiManager.java b/biliapi/src/main/java/com/yutou/bili/net/BiliLoginNetApiManager.java index dbc8b90..81ba523 100644 --- a/biliapi/src/main/java/com/yutou/bili/net/BiliLoginNetApiManager.java +++ b/biliapi/src/main/java/com/yutou/bili/net/BiliLoginNetApiManager.java @@ -6,7 +6,6 @@ import com.yutou.bili.bean.login.LoginCookie; import com.yutou.bili.bean.login.LoginInfoBean; import com.yutou.bili.bean.login.QRCodeGenerateBean; import com.yutou.bili.databases.BiliBiliLoginDatabase; -import com.yutou.databases.AbsDatabasesBean; import com.yutou.okhttp.HttpBody; import com.yutou.okhttp.HttpCallback; import com.yutou.okhttp.api.BaseApi; @@ -38,10 +37,24 @@ public class BiliLoginNetApiManager extends BaseApi { return instance; } + public LoginApi getLoginApi() { + return getLoginApi(false); + } + + public LoginApi getLoginApi(boolean isCookie) { + if (isCookie) { + LoginCookie cookie = BiliBiliLoginDatabase.getInstance().get(); + if (cookie != null) { + useCookie(JSONObject.parseObject(JSONObject.toJSONString(cookie))); + } + } + return loginApi; + } + public void login(HttpCallback callback) { - List cookie = BiliBiliLoginDatabase.getInstance().get(); - if (!cookie.isEmpty()) { - callback.onResponse(null, LOGIN_SUCCESS, null, cookie.get(0), null); + LoginCookie cookie = BiliBiliLoginDatabase.getInstance().get(); + if (cookie != null) { + callback.onResponse(null, LOGIN_SUCCESS, null, cookie, null); return; } loginApi.getQRCodeGenerate().enqueue(new HttpCallback() { diff --git a/biliapi/src/main/java/com/yutou/bili/net/BiliUserNetApiManager.java b/biliapi/src/main/java/com/yutou/bili/net/BiliUserNetApiManager.java index c4a9688..c41eb42 100644 --- a/biliapi/src/main/java/com/yutou/bili/net/BiliUserNetApiManager.java +++ b/biliapi/src/main/java/com/yutou/bili/net/BiliUserNetApiManager.java @@ -27,12 +27,11 @@ public class BiliUserNetApiManager extends BaseApi { } public void getUserApi(IHttpApiCheckCallback callback) { - List list = BiliBiliLoginDatabase.getInstance().get(); - if (!list.isEmpty()) { + LoginCookie cookie = BiliBiliLoginDatabase.getInstance().get(); + if (cookie != null) { HashMap headers = new HashMap<>(); - LoginCookie cookie = list.get(0); JSONObject json = JSONObject.parseObject(JSONObject.toJSONString(cookie)); - StringBuilder ck=new StringBuilder(); + StringBuilder ck = new StringBuilder(); for (String key : json.keySet()) { ck.append(key).append("=").append(json.getString(key)).append("; "); } diff --git a/biliapi/src/main/java/com/yutou/bili/net/WebSocketManager.java b/biliapi/src/main/java/com/yutou/bili/net/WebSocketManager.java new file mode 100644 index 0000000..5073c15 --- /dev/null +++ b/biliapi/src/main/java/com/yutou/bili/net/WebSocketManager.java @@ -0,0 +1,230 @@ +package com.yutou.bili.net; + +import com.alibaba.fastjson2.JSONObject; +import com.yutou.bili.api.LiveApi; +import com.yutou.bili.bean.live.LiveDanmuInfo; +import com.yutou.bili.bean.live.LiveRoomConfig; +import com.yutou.inter.IHttpApiCheckCallback; +import com.yutou.okhttp.HttpCallback; +import jakarta.xml.bind.DatatypeConverter; +import okhttp3.Headers; +import org.java_websocket.client.WebSocketClient; +import org.java_websocket.handshake.ServerHandshake; + +import java.io.ByteArrayOutputStream; +import java.net.Socket; +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; +import java.util.Timer; +import java.util.TimerTask; +import java.util.zip.Inflater; + +public class WebSocketManager { + private static WebSocketManager instance; + Map roomMap; + + private WebSocketManager() { + roomMap = new HashMap<>(); + } + + public static WebSocketManager getInstance() { + if (instance == null) { + instance = new WebSocketManager(); + } + return instance; + } + + public void addRoom(LiveRoomConfig roomConfig) { + BiliLiveNetApiManager.getInstance().getApi(new IHttpApiCheckCallback() { + @Override + public void onSuccess(LiveApi api) { + api.getLiveRoomDanmuInfo(roomConfig.getRoomId()).enqueue(new HttpCallback() { + @Override + public void onResponse(Headers headers, int code, String status, LiveDanmuInfo response, String rawResponse) { + if (!response.getHostList().isEmpty()) { + LiveDanmuInfo.Host host = response.getHostList().get(0); + String url = "wss://" + host.getHost() + ":" + host.getWssPort() + "/sub"; + // url="ws://127.0.0.1:8765"; + try { + roomConfig.setLiveInfo(response); + new WebSocketClientTh(new URI(url), roomConfig); + } catch (URISyntaxException e) { + throw new RuntimeException(e); + } + } + } + + @Override + public void onFailure(Throwable throwable) { + throwable.printStackTrace(); + } + }); + } + + @Override + public void onError(int code, String error) { + + } + }); + } + + private static class WebSocketClientTh extends WebSocketClient { + private LiveRoomConfig roomConfig; + private HeartbeatTask heartbeatTask; + + + public WebSocketClientTh(URI serverUri, LiveRoomConfig roomId) { + super(serverUri); + this.roomConfig = roomId; + heartbeatTask = new HeartbeatTask(); + HashMap header = new HashMap<>(); + header.put("Sec-WebSocket-Extensions", "permessage-deflate; client_max_window_bits"); + header.put("Sec-WebSocket-Key", "f1kFoce72Pn+wbjdPyONLw=="); + // header.put("Sec-WebSocket-Key", roomId.getLiveInfo().getToken()); + header.put("Sec-WebSocket-Version", "13"); + header.put("Cache-Control", "no-cache"); + header.put("Connection", "Upgrade"); + header.put("Accept-Encoding:", "gzip, deflate, br, zstd"); + header.put("Host", roomId.getLiveInfo().getHostList().get(0).getHost() + ":" + roomId.getLiveInfo().getHostList().get(0).getWssPort()); + header.put("Origin", "https://live.bilibili.com"); + header.put("Pragma", "no-cache"); + header.put("Upgrade", "websocket"); + header.put("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36"); + for (String key : header.keySet()) { + addHeader(key, header.get(key)); + } + System.out.println(); + System.out.println(); + System.out.println("header = " + header); + connect(); + } + + @Override + public void onOpen(ServerHandshake serverHandshake) { + WebSocketManager.getInstance().roomMap.put(roomConfig, this); + heartbeatTask.setSocket(this); + heartbeatTask.sendInitAuthData(); + new Timer().schedule(heartbeatTask, 0, 30000); + System.out.println("WebSocketClientTh.onOpen"); + } + + @Override + public void onMessage(String s) { + System.out.println("s = " + s); + } + + @Override + public void onMessage(ByteBuffer bytes) { + System.out.println("WebSocketClientTh.onMessage"); + super.onMessage(bytes); + decompress(bytes.array()); + } + + @Override + public void onClose(int i, String s, boolean b) { + System.out.println("WebSocketClientTh.onClose"); + System.out.println("i = " + i + ", s = " + s + ", b = " + b); + WebSocketManager.getInstance().roomMap.remove(roomConfig); + heartbeatTask.cancel(); + } + + @Override + public void onError(Exception e) { + System.out.println("WebSocketClientTh.onError"); + e.printStackTrace(); + WebSocketManager.getInstance().roomMap.remove(roomConfig); + heartbeatTask.cancel(); + } + + /** + * 解压缩 + * + * @param data 待压缩的数据 + */ + public void decompress(byte[] data) { + try { + Inflater inflater = new Inflater(); + inflater.reset(); + inflater.setInput(data); + ByteArrayOutputStream out = new ByteArrayOutputStream(data.length); + byte[] buf = new byte[8192]; + while (!inflater.finished()) { + int i = inflater.inflate(buf); + out.write(buf, 0, i); + } + System.out.println("接收值:" + new String(out.toByteArray())); + } catch (Exception e) { + e.printStackTrace(); + } + } + + private class HeartbeatTask extends TimerTask { + WebSocketClientTh socket; + + public void setSocket(WebSocketClientTh socket) { + this.socket = socket; + } + + @Override + public void run() { + + } + + public void sendInitAuthData() { + JSONObject json = new JSONObject(); + if (roomConfig.isLogin()) { + json.put("uid", roomConfig.getUid()); + } else { + json.put("uid", "0"); + } + try { + json.put("roomid", roomConfig.getRoomId()); + json.put("protover", "3"); + json.put("platform", "web"); + json.put("buvid", "F56FA00C-B043-DDB7-B7D2-D1A2AEC3034E24804infoc"); + json.put("type", 2); + json.put("key", roomConfig.getLiveInfo().getToken()); + byte[] bytes = {0, 16, 0, 1, 0, 0, 0, 7, 0, 0, 0, 1}; + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + outputStream.write(toLH(json.toString().length() + bytes.length)); + outputStream.write(bytes); + outputStream.write(json.toJSONString().getBytes(StandardCharsets.UTF_8)); + outputStream.flush(); + System.out.println("\n\n\n"); + String str = DatatypeConverter.printHexBinary(outputStream.toByteArray()); + for (int i = 0; i < str.length(); i = i + 4) { + if (i % 32 == 0 && i != 0) { + System.out.println(); + } + if (str.length() - i > 4) { + System.out.print(str.substring(i, i + 4) + " "); + } else { + System.out.println(str.substring(i)); + } + } + System.out.println("\n\n\n"); + System.out.println(socket.isOpen()); + socket.send(outputStream.toByteArray()); + + } catch (Exception e) { + e.printStackTrace(); + } + + } + + public byte[] toLH(int n) { + byte[] b = new byte[4]; + b[3] = (byte) (n & 0xff); + b[2] = (byte) (n >> 8 & 0xff); + b[1] = (byte) (n >> 16 & 0xff); + b[0] = (byte) (n >> 24 & 0xff); + return b; + } + + } + } +} diff --git a/biliapi/src/main/java/com/yutou/bili/utils/LiveHeartBeat.java b/biliapi/src/main/java/com/yutou/bili/utils/LiveHeartBeat.java new file mode 100644 index 0000000..6a77a18 --- /dev/null +++ b/biliapi/src/main/java/com/yutou/bili/utils/LiveHeartBeat.java @@ -0,0 +1,69 @@ +package com.yutou.bili.utils; + +import org.java_websocket.client.WebSocketClient; +import org.java_websocket.handshake.ServerHandshake; + +import java.net.URI; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class LiveHeartBeat { + private static LiveHeartBeat instance; + private HeartBeatThread thread; + Map heartBeatMap=new HashMap<>(); + + public static LiveHeartBeat getInstance() { + if (instance == null) { + instance = new LiveHeartBeat(); + } + return instance; + } + + private LiveHeartBeat() { + } + + public void addHeartBeat(String roomId) { + if(heartBeatMap.containsKey(roomId)){ + return; + } + + } + + public void removeHeartBeat(String roomId) { + } + + public void endAllHeartBeat() { + + } + + public class HeartBeatThread extends WebSocketClient { + + + public HeartBeatThread(URI serverUri) { + super(serverUri); + } + + @Override + public void onOpen(ServerHandshake serverHandshake) { + + } + + @Override + public void onMessage(String s) { + + } + + @Override + public void onClose(int i, String s, boolean b) { + + } + + @Override + public void onError(Exception e) { + + } + } + +} diff --git a/common/src/main/java/com/yutou/okhttp/FileBody.java b/common/src/main/java/com/yutou/okhttp/FileBody.java new file mode 100644 index 0000000..f492250 --- /dev/null +++ b/common/src/main/java/com/yutou/okhttp/FileBody.java @@ -0,0 +1,11 @@ +package com.yutou.okhttp; + +import lombok.Data; + +import java.io.InputStream; + +@Data +public class FileBody { + InputStream inputStream; + T t; +} diff --git a/common/src/main/java/com/yutou/okhttp/FileCallback.java b/common/src/main/java/com/yutou/okhttp/FileCallback.java new file mode 100644 index 0000000..edf0842 --- /dev/null +++ b/common/src/main/java/com/yutou/okhttp/FileCallback.java @@ -0,0 +1,103 @@ +package com.yutou.okhttp; + +import okhttp3.Headers; +import okhttp3.HttpUrl; +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +public abstract class FileCallback implements Callback> { + + private static ThreadPoolExecutor executor; + private final T bean; + + + public FileCallback(T bean) { + this.bean = bean; + if (executor == null) { + executor = new ThreadPoolExecutor(2, 4, Long.MAX_VALUE, TimeUnit.SECONDS, new ArrayBlockingQueue(100)); + } + } + + private class DownloadTask implements Runnable { + private T bean; + private Headers headers; + private HttpUrl url; + private InputStream inputStream; + + public DownloadTask(T bean, Headers headers, HttpUrl url, InputStream inputStream) { + this.bean = bean; + + this.headers = headers; + this.url = url; + this.inputStream = inputStream; + } + + @Override + public void run() { + try { + System.out.println("开始下载"); + File file = new File("download" + File.separator + System.currentTimeMillis() + ".flv"); + onStart(bean); + if (!file.exists()) { + boolean mkdirs = file.getParentFile().mkdirs(); + System.out.println("mkdirs = " + mkdirs); + } + FileOutputStream outputStream = new FileOutputStream(file); + int len; + long total = 0; + byte[] bytes = new byte[4096]; + boolean isDownload = true; + long available = inputStream.available(); + while ((len = inputStream.read(bytes)) != -1 && isDownload) { + total += len; + outputStream.write(bytes, 0, len); + outputStream.flush(); + isDownload = onDownload(headers, bean, total, available); + } + System.out.println("下载完成"); + outputStream.close(); + } catch (Exception e) { + e.printStackTrace(); + } finally { + onFinish(bean); + try { + inputStream.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + + } + + } + + public abstract void onStart(T bean); + + public abstract boolean onDownload(Headers headers, T bean, long len, long total); + + public abstract void onFinish(T bean); + + public abstract void onFailure(T bean, Throwable throwable); + + @Override + public void onResponse(Call> call, Response> response) { + executor.execute(new DownloadTask(bean, response.headers(), call.request().url(), response.body().getInputStream())); + } + + @Override + public void onFailure(Call> call, Throwable throwable) { + onFailure(bean, throwable); + call.cancel(); + } + +} diff --git a/common/src/main/java/com/yutou/okhttp/ParamsContext.java b/common/src/main/java/com/yutou/okhttp/ParamsContext.java index b54f919..f964114 100644 --- a/common/src/main/java/com/yutou/okhttp/ParamsContext.java +++ b/common/src/main/java/com/yutou/okhttp/ParamsContext.java @@ -38,6 +38,7 @@ public class ParamsContext { break; } headerMap.remove("tableName"); + headerMap.put("User-Agent","Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36"); return iRequestParam.getRequest(headerMap, map, request); } } \ No newline at end of file diff --git a/common/src/main/java/com/yutou/okhttp/api/BaseApi.java b/common/src/main/java/com/yutou/okhttp/api/BaseApi.java index 8fb2f7e..f606f8c 100644 --- a/common/src/main/java/com/yutou/okhttp/api/BaseApi.java +++ b/common/src/main/java/com/yutou/okhttp/api/BaseApi.java @@ -1,5 +1,6 @@ package com.yutou.okhttp.api; +import com.alibaba.fastjson2.JSONObject; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.internal.bind.DateTypeAdapter; @@ -7,10 +8,7 @@ import com.yutou.okhttp.HttpLoggingInterceptor; import com.yutou.okhttp.ParamsContext; import com.yutou.okhttp.converter.JsonCallAdapter; import com.yutou.okhttp.converter.JsonConverterFactory; -import okhttp3.Interceptor; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.Response; +import okhttp3.*; import retrofit2.CallAdapter; import retrofit2.Converter; import retrofit2.Retrofit; @@ -21,8 +19,8 @@ import java.util.HashMap; public class BaseApi { private String URL; - private HashMap params; - private HashMap headers; + private HashMap params = new HashMap<>(); + private HashMap headers = new HashMap<>(); public BaseApi(String URL) { this.URL = URL; @@ -42,16 +40,24 @@ public class BaseApi { this.params = params; return this; } + public void useCookie(JSONObject json){ + StringBuilder ck = new StringBuilder(); + for (String key : json.keySet()) { + ck.append(key).append("=").append(json.getString(key)).append("; "); + } + headers.put("Cookie", ck.toString()); + setHeaders(headers); + } /** * 创建一个接口方法 * - * @param okHttpClient okhttp客户端 - * @param converterFactory 处理工厂类 + * @param okHttpClient okhttp客户端 + * @param converterFactory 处理工厂类 * @param callAdapterFactory 请求适配器工厂 - * @param baseUrl 基础地质 - * @param service 接口 - * @param 接口泛型 + * @param baseUrl 基础地质 + * @param service 接口 + * @param 接口泛型 * @return 接口 */ public T create(OkHttpClient okHttpClient, Converter.Factory converterFactory, CallAdapter.Factory callAdapterFactory, String baseUrl, Class service) { @@ -92,13 +98,14 @@ public class BaseApi { URL, apiClass); } + public Interceptor initQuery() { Interceptor addQueryParameterInterceptor = new Interceptor() { @Override public Response intercept(Chain chain) throws IOException { Request request = chain.request(); //配置公共参数 - request = new ParamsContext(headers,params,request).getInRequest(); + request = new ParamsContext(headers, params, request).getInRequest(); return chain.proceed(request); } }; diff --git a/common/src/main/java/com/yutou/okhttp/converter/JsonResponseBodyConverter.java b/common/src/main/java/com/yutou/okhttp/converter/JsonResponseBodyConverter.java index 69c06d1..7469e22 100644 --- a/common/src/main/java/com/yutou/okhttp/converter/JsonResponseBodyConverter.java +++ b/common/src/main/java/com/yutou/okhttp/converter/JsonResponseBodyConverter.java @@ -2,9 +2,9 @@ package com.yutou.okhttp.converter; import com.alibaba.fastjson2.JSONObject; import com.alibaba.fastjson2.JSONReader; -import com.alibaba.fastjson2.JSONWriter; import com.google.gson.Gson; import com.google.gson.TypeAdapter; +import com.yutou.okhttp.FileBody; import com.yutou.okhttp.HttpBody; import okhttp3.ResponseBody; import org.jetbrains.annotations.Nullable; @@ -27,19 +27,25 @@ public class JsonResponseBodyConverter implements Converter @Nullable @Override public T convert(ResponseBody responseBody) throws IOException { - String string = new String(responseBody.bytes()); - responseBody.close(); - HttpBody body; - try { - body = JSONObject.parseObject(string, type, JSONReader.Feature.FieldBased); - body.setSrc(string); + if (type.getTypeName().contains(HttpBody.class.getSimpleName())) { + String string = new String(responseBody.bytes()); + responseBody.close(); + HttpBody body; + try { + body = JSONObject.parseObject(string, type, JSONReader.Feature.FieldBased); + body.setSrc(string); + return (T) body; + } catch (Exception e) { + e.printStackTrace(); + body = new HttpBody(); + body.setSrc(string); + } return (T) body; - } catch (Exception e) { - e.printStackTrace(); - body = new HttpBody(); - body.setSrc(string); - } - return (T) body; + } else { + FileBody body=new FileBody(); + body.setInputStream(responseBody.byteStream()); + return (T) body; + } } } \ No newline at end of file diff --git a/common/src/main/java/com/yutou/utils/RSAUtils.java b/common/src/main/java/com/yutou/utils/RSAUtils.java new file mode 100644 index 0000000..7ff6826 --- /dev/null +++ b/common/src/main/java/com/yutou/utils/RSAUtils.java @@ -0,0 +1,136 @@ +package com.yutou.utils; + +import org.apache.commons.codec.binary.Base64; + +import javax.crypto.Cipher; +import java.io.ByteArrayOutputStream; +import java.security.*; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; +import java.util.HashMap; +import java.util.Map; + +public class RSAUtils { + /** + * 加密算法RSA + */ + public static final String KEY_ALGORITHM = "RSA"; + + /** + * 签名算法 + */ + public static final String SIGNATURE_ALGORITHM = "MD5withRSA"; + + /** + * 获取公钥的key + */ + private static final String PUBLIC_KEY = "RSAPublicKey"; + + /** + * 获取私钥的key + */ + private static final String PRIVATE_KEY = "RSAPrivateKey"; + + /** + * RSA 密钥位数 + */ + private static final int KEY_SIZE = 1024; + + /** + * RSA最大解密密文大小 + */ + private static final int MAX_DECRYPT_BLOCK = KEY_SIZE / 8; + + /** + * RSA最大加密明文大小 + */ + private static final int MAX_ENCRYPT_BLOCK = MAX_DECRYPT_BLOCK - 11; + private static Map keyMap=new HashMap<>(); + + /** + * 随机生成密钥对 + * @throws NoSuchAlgorithmException + */ + public static void getKeyPair() throws Exception { + //KeyPairGenerator类用于生成公钥和密钥对,基于RSA算法生成对象 + KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA"); + //初始化密钥对生成器,密钥大小为96-1024位 + keyPairGen.initialize(1024,new SecureRandom()); + //生成一个密钥对,保存在keyPair中 + KeyPair keyPair = keyPairGen.generateKeyPair(); + PrivateKey privateKey = keyPair.getPrivate();//得到私钥 + PublicKey publicKey = keyPair.getPublic();//得到公钥 + //得到公钥字符串 + String publicKeyString=new String(Base64.encodeBase64(publicKey.getEncoded())); + //得到私钥字符串 + String privateKeyString=new String(Base64.encodeBase64(privateKey.getEncoded())); + //将公钥和私钥保存到Map + keyMap.put(0,publicKeyString);//0表示公钥 + keyMap.put(1,privateKeyString);//1表示私钥 + } + /** + *

+ * 公钥加密 + *

+ * + * @param str 源数据 + * @param publicKey 公钥(BASE64编码) + * @return + * @throws Exception + */ + public static String encryptByPublicKey(String str, String publicKey) throws Exception { + publicKey=publicKey.replace("-----BEGIN PUBLIC KEY-----","").replace("-----END PUBLIC KEY-----",""); + byte[] data=Base64.decodeBase64(str); + byte[] keyBytes = Base64.decodeBase64(publicKey); + X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes); + KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); + Key publicK = keyFactory.generatePublic(x509KeySpec); + // 对数据加密 + Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm()); + cipher.init(Cipher.ENCRYPT_MODE, publicK); + int inputLen = data.length; + ByteArrayOutputStream out = new ByteArrayOutputStream(); + int offSet = 0; + byte[] cache; + int i = 0; + // 对数据分段加密 + while (inputLen - offSet > 0) { + if (inputLen - offSet > MAX_ENCRYPT_BLOCK) { + cache = cipher.doFinal(data, offSet, MAX_ENCRYPT_BLOCK); + } else { + cache = cipher.doFinal(data, offSet, inputLen - offSet); + } + out.write(cache, 0, cache.length); + i++; + offSet = i * MAX_ENCRYPT_BLOCK; + } + byte[] encryptedData = out.toByteArray(); + out.close(); + return new String(Base64.encodeBase64(encryptedData)); + } + + /** + * RSA私钥解密 + * + * @param str + * 加密字符串 + * @param privateKey + * 私钥 + * @return 铭文 + * @throws Exception + * 解密过程中的异常信息 + */ + public static String decrypt(String str,String privateKey) throws Exception { + //Base64解码加密后的字符串 + byte[] inputByte = Base64.decodeBase64(str.getBytes("UTF-8")); + //Base64编码的私钥 + byte[] decoded = Base64.decodeBase64(privateKey); + PrivateKey priKey = KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(decoded)); + //RSA解密 + Cipher cipher = Cipher.getInstance("RSA"); + cipher.init(Cipher.DECRYPT_MODE,priKey); + String outStr=new String(cipher.doFinal(inputByte)); + return outStr; + + } +}