diff --git a/biliapi/pom.xml b/biliapi/pom.xml index 6c77166..f2bc687 100644 --- a/biliapi/pom.xml +++ b/biliapi/pom.xml @@ -22,5 +22,11 @@ rxjava 3.1.8 + + com.aayushatharva.brotli4j + brotli4j + 1.16.0 + + \ No newline at end of file diff --git a/biliapi/src/main/java/com/yutou/Main.java b/biliapi/src/main/java/com/yutou/Main.java index 452021b..90107b7 100644 --- a/biliapi/src/main/java/com/yutou/Main.java +++ b/biliapi/src/main/java/com/yutou/Main.java @@ -5,6 +5,7 @@ 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.live.SpiBean; import com.yutou.bili.bean.login.LoginCookie; import com.yutou.bili.bean.login.UserInfoBean; import com.yutou.bili.enums.LiveProtocol; @@ -17,35 +18,42 @@ 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 com.yutou.utils.Log; 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(); - + // getPlayUrl(); + LiveRoomConfig config=new LiveRoomConfig(); + LoginCookie cookie = BiliBiliLoginDatabase.getInstance().get(); + config.setLogin(true); + config.setUid(cookie.getDedeUserID()); + config.setRoomId(String.valueOf(855204)); + config.setRoomId(String.valueOf(22642754)); + config.setRoomId(String.valueOf(81004)); + WebSocketManager.getInstance().addRoom(config); } - public static void testSocket() { + public static void testSocket(SpiBean spi) { try { JSONObject json = new JSONObject(); - json.put("roomid", "13246789"); + // json.put("roomid", "32805602"); + json.put("roomid", "855204"); json.put("protover", "3"); json.put("platform", "web"); json.put("type", 2); + json.put("buvid",spi.getB_3()); json.put("key", "aaaabbb"); + Log.i(json); 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}); @@ -76,7 +84,7 @@ public class Main { .getApi(new IHttpApiCheckCallback() { @Override public void onSuccess(LiveApi api) { - String roomId = "22689676"; + String roomId = "32805602"; String mid = "68057278"; // roomId="42062"; diff --git a/biliapi/src/main/java/com/yutou/bili/api/LiveApi.java b/biliapi/src/main/java/com/yutou/bili/api/LiveApi.java index 565d2b4..f39356b 100644 --- a/biliapi/src/main/java/com/yutou/bili/api/LiveApi.java +++ b/biliapi/src/main/java/com/yutou/bili/api/LiveApi.java @@ -5,10 +5,7 @@ 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; +import retrofit2.http.*; /** * 直播间相关API diff --git a/biliapi/src/main/java/com/yutou/bili/api/UserApi.java b/biliapi/src/main/java/com/yutou/bili/api/UserApi.java index 20440e4..6af1ba5 100644 --- a/biliapi/src/main/java/com/yutou/bili/api/UserApi.java +++ b/biliapi/src/main/java/com/yutou/bili/api/UserApi.java @@ -1,5 +1,6 @@ package com.yutou.bili.api; +import com.yutou.bili.bean.live.SpiBean; import com.yutou.bili.bean.login.UserInfoBean; import com.yutou.okhttp.HttpBody; import retrofit2.Call; @@ -8,4 +9,8 @@ import retrofit2.http.GET; public interface UserApi { @GET("/x/web-interface/nav") Call> getUserInfo(); + + + @GET("/x/frontend/finger/spi") + Call> getFingerSpi(); } diff --git a/biliapi/src/main/java/com/yutou/bili/bean/live/SpiBean.java b/biliapi/src/main/java/com/yutou/bili/bean/live/SpiBean.java new file mode 100644 index 0000000..5daee56 --- /dev/null +++ b/biliapi/src/main/java/com/yutou/bili/bean/live/SpiBean.java @@ -0,0 +1,12 @@ +package com.yutou.bili.bean.live; + +import com.yutou.okhttp.BaseBean; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@EqualsAndHashCode(callSuper = true) +@Data +public class SpiBean extends BaseBean { + private String b_3; + private String b_4; +} diff --git a/biliapi/src/main/java/com/yutou/bili/bean/websocket/WebSocketBody.java b/biliapi/src/main/java/com/yutou/bili/bean/websocket/WebSocketBody.java new file mode 100644 index 0000000..2956644 --- /dev/null +++ b/biliapi/src/main/java/com/yutou/bili/bean/websocket/WebSocketBody.java @@ -0,0 +1,34 @@ +package com.yutou.bili.bean.websocket; + +import com.alibaba.fastjson2.JSONObject; +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +@Data +public class WebSocketBody { + List bodyList; + + public WebSocketBody(byte[] bytes) { + bodyList = new ArrayList<>(); + addBody(bytes, 0); + } + + private void addBody(byte[] bytes, int offset) { + if (offset >= bytes.length) { + return; + } + byte[] headerByte = new byte[16]; + System.arraycopy(bytes, offset, headerByte, 0, headerByte.length); + WebSocketHeader header = new WebSocketHeader(headerByte); + byte[] data = new byte[header.getDataSize() - header.getHeaderSize()]; + System.arraycopy(bytes, offset + header.getHeaderSize(), data, 0, data.length); + try { + bodyList.add(JSONObject.parseObject(new String(data))); + } catch (Exception e) { + System.out.println(header + "|" + new String(data)); + } + addBody(bytes, offset + header.dataSize); + } +} diff --git a/biliapi/src/main/java/com/yutou/bili/bean/websocket/WebSocketHeader.java b/biliapi/src/main/java/com/yutou/bili/bean/websocket/WebSocketHeader.java new file mode 100644 index 0000000..12bd2da --- /dev/null +++ b/biliapi/src/main/java/com/yutou/bili/bean/websocket/WebSocketHeader.java @@ -0,0 +1,27 @@ +package com.yutou.bili.bean.websocket; + +import com.yutou.bili.utils.BytesUtils; +import lombok.Data; + +@Data +public class WebSocketHeader { + int dataSize; + int agree; + int headerSize; + int cmdData; + + public WebSocketHeader(byte[] bytes) { + byte[] size = new byte[4]; + byte[] header = new byte[4]; + byte[] cmd = new byte[4]; + byte[] agreement = new byte[4]; + System.arraycopy(bytes, 0, size, 0, 4); + System.arraycopy(bytes, 8, cmd, 0, 4); + System.arraycopy(bytes, 6, agreement, 2, 2); + System.arraycopy(bytes, 4, header, 2, 2); + dataSize = BytesUtils.bytesToInt2(size, 0); + agree = BytesUtils.bytesToInt2(agreement, 0); + headerSize = BytesUtils.bytesToInt2(header, 0); + cmdData = BytesUtils.bytesToInt2(cmd, 0); + } +} diff --git a/biliapi/src/main/java/com/yutou/bili/bean/websocket/live/WSDanmuData.java b/biliapi/src/main/java/com/yutou/bili/bean/websocket/live/WSDanmuData.java new file mode 100644 index 0000000..f418e0f --- /dev/null +++ b/biliapi/src/main/java/com/yutou/bili/bean/websocket/live/WSDanmuData.java @@ -0,0 +1,50 @@ +package com.yutou.bili.bean.websocket.live; + +import com.alibaba.fastjson2.JSONArray; +import com.alibaba.fastjson2.JSONObject; +import com.yutou.utils.Log; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 弹幕信息 + * 弹幕 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class WSDanmuData extends WSData { + private String dm_v2; + private int id; + private int model;// 1~3 滚动弹幕 4 底端弹幕 5 顶端弹幕 6 逆向弹幕 7 精准定位 8 高级弹幕 + private int fontSize; + private String fontColor; + private long time; + private String uCode; + private String danmu; + private long uid; + private String uname; + private WSUserMedal medal; + + public WSDanmuData(JSONObject json) { + super(json); + JSONArray infoData = json.getJSONArray("info"); + setModel(infoData.getJSONArray(0).getInteger(1)); + setFontSize(infoData.getJSONArray(0).getInteger(2)); + setFontColor(Integer.toHexString(infoData.getJSONArray(0).getInteger(3))); + setTime(infoData.getJSONArray(0).getLong(4)); + setUCode(infoData.getJSONArray(0).getString(7)); + setDanmu(infoData.getString(1)); + setUid(infoData.getJSONArray(2).getInteger(0)); + setUname(infoData.getJSONArray(2).getString(1)); + try { + medal = WSUserMedal.create(infoData.getJSONArray(3)); + } catch (Exception e) { + Log.i("弹幕信息解析失败:" + json); + } + } + + @Override + public String toString() { + return "弹幕 = " + "用户:" + getUname() + " 发送了: " + getDanmu() +" | json = "+jsonSrc; + } +} diff --git a/biliapi/src/main/java/com/yutou/bili/bean/websocket/live/WSData.java b/biliapi/src/main/java/com/yutou/bili/bean/websocket/live/WSData.java new file mode 100644 index 0000000..e331efd --- /dev/null +++ b/biliapi/src/main/java/com/yutou/bili/bean/websocket/live/WSData.java @@ -0,0 +1,42 @@ +package com.yutou.bili.bean.websocket.live; + +import com.alibaba.fastjson2.JSONObject; + +import java.io.Serializable; + +public class WSData implements Serializable { + public String cmd; + public String jsonSrc; + public long ws_timer; + + @Deprecated + public WSData() { + throw new NullPointerException("需要传入json"); + } + + @Override + public String toString() { + return "WSData{" + + "cmd='" + cmd + '\'' + + ", jsonSrc='" + jsonSrc + '\'' + + '}'; + } + + public WSData(JSONObject json) { + this.cmd = json.getString("cmd"); + ws_timer = System.currentTimeMillis(); + this.jsonSrc = json.toString(); + } + + public static WSData parse(JSONObject json) { + String cmd = json.getString("cmd"); + return switch (cmd) { + case "DANMU_MSG" -> new WSDanmuData(json); + case "DM_INTERACTION" -> new WSDmInteraction(json); + case "SEND_GIFT" -> new WSSendGift(json); + case "INTERACT_WORD" -> new WSInteractWord(json); + case "GUARD_BUY" -> new WSGuardBuy(json); + default -> new WSData(json); + }; + } +} diff --git a/biliapi/src/main/java/com/yutou/bili/bean/websocket/live/WSDmInteraction.java b/biliapi/src/main/java/com/yutou/bili/bean/websocket/live/WSDmInteraction.java new file mode 100644 index 0000000..d73e5c3 --- /dev/null +++ b/biliapi/src/main/java/com/yutou/bili/bean/websocket/live/WSDmInteraction.java @@ -0,0 +1,54 @@ +package com.yutou.bili.bean.websocket.live; + +import com.alibaba.fastjson2.JSONObject; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; + +/** + * 连续弹幕消息 + * 连续弹幕消息 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class WSDmInteraction extends WSData{ + public final static int TYPE_ZAN=106;//点赞 + public final static int TYPE_SHARE=105;//分享 + public final static int TYPE_DANMU=102;//弹幕 + private ComboData combo; + private int type; + + public static void main(String[] args) { + JSONObject json=JSONObject.parseObject("{\"cmd\":\"DM_INTERACTION\",\"data\":{\"data\":\"{\\\"fade_duration\\\":10000,\\\"cnt\\\":5,\\\"card_appear_interval\\\":0,\\\"suffix_text\\\":\\\"人正在点赞\\\",\\\"reset_cnt\\\":1,\\\"display_flag\\\":1}\",\"dmscore\":36,\"id\":53793047788032,\"status\":4,\"type\":106}}"); + WSDmInteraction wsDmInteraction=new WSDmInteraction(json); + System.out.println(wsDmInteraction); + } + public WSDmInteraction(JSONObject json) { + super(json); + JSONObject data=json.getJSONObject("data"); + JSONObject comboJson=JSONObject.parseObject(data.getString("data")); + combo=JSONObject.parseObject(data.getString("data"), ComboData.class); + type=data.getIntValue("type"); + if(type==106){ + combo.setContent(comboJson.getString("suffix_text")); + } + } + + @Data + public static class ComboData implements Serializable { + String content; + String guide; + int cnt; + } + + @Override + public String toString() { + return "WSDmInteraction{" + + "combo=" + combo + + ", cmd='" + cmd + '\'' + + ", jsonSrc='" + jsonSrc + '\'' + + ", ws_timer=" + ws_timer + + '}'; + } +} diff --git a/biliapi/src/main/java/com/yutou/bili/bean/websocket/live/WSGuardBuy.java b/biliapi/src/main/java/com/yutou/bili/bean/websocket/live/WSGuardBuy.java new file mode 100644 index 0000000..06b10fa --- /dev/null +++ b/biliapi/src/main/java/com/yutou/bili/bean/websocket/live/WSGuardBuy.java @@ -0,0 +1,42 @@ +package com.yutou.bili.bean.websocket.live; + +import com.alibaba.fastjson2.JSONObject; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 大航海购买 + * 上舰通知 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class WSGuardBuy extends WSData{ + private long uid; + private String username; + private long guardLevel; + private long num; + private long price; + private long giftID; + private String giftName; + private long startTime; + private long endTime; + + public static void main(String[] args) { + JSONObject json=JSONObject.parseObject("{\"cmd\":\"GUARD_BUY\",\"data\":{\"uid\":5427372,\"username\":\"李湜渰\",\"guard_level\":3,\"num\":1,\"price\":198000,\"gift_id\":10003,\"gift_name\":\"舰长\",\"start_time\":1724985039,\"end_time\":1724985039}}"); + WSGuardBuy wsguardBuy = new WSGuardBuy(json); + System.out.println(wsguardBuy); + } + public WSGuardBuy(JSONObject json) { + super(json); + JSONObject data = json.getJSONObject("data"); + uid = data.getLong("uid"); + username = data.getString("username"); + guardLevel = data.getLong("guard_level"); + num = data.getLong("num"); + price = data.getLong("price"); + giftID = data.getLong("gift_id"); + giftName = data.getString("gift_name"); + startTime = data.getLong("start_time"); + endTime = data.getLong("end_time"); + } +} diff --git a/biliapi/src/main/java/com/yutou/bili/bean/websocket/live/WSInteractWord.java b/biliapi/src/main/java/com/yutou/bili/bean/websocket/live/WSInteractWord.java new file mode 100644 index 0000000..f9635cb --- /dev/null +++ b/biliapi/src/main/java/com/yutou/bili/bean/websocket/live/WSInteractWord.java @@ -0,0 +1,70 @@ +package com.yutou.bili.bean.websocket.live; + +import com.alibaba.fastjson2.JSONObject; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.springframework.util.StringUtils; + +/** + * 进场信息 + * 进场 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class WSInteractWord extends WSData { + public final static int TYPE_ENTER = 1; + public final static int TYPE_FOLLOW = 2; + private int type; //1为进场,2为关注 + private long roomId; + private long timer; + private WSUserMedal medal; + private long uid; + private String uname; + private String uname_color; + private String face; + + + public WSInteractWord(JSONObject json) { + super(json); + JSONObject data = json.getJSONObject("data"); + JSONObject medalJson = data.containsKey("fans_medal") ? data.getJSONObject("fans_medal"):null; + type = data.getIntValue("msg_type"); + roomId = data.getLong("roomid"); + timer = data.getLong("score"); + uid = data.getLong("uid"); + uname = data.getString("uname"); + uname_color = data.getString("uname_color"); + face = data.getJSONObject("uinfo").getJSONObject("base").getString("face"); + if (medalJson != null) { + medal = new WSUserMedal(); + medal.setUid(medalJson.getLong("anchor_roomid")); + medal.setMedal_name(medalJson.getString("medal_name")); + medal.setMedal_color(Integer.toHexString(medalJson.getIntValue("medal_color"))); + medal.setMedal_level(medalJson.getIntValue("medal_level")); + } + } + + public String getUname_color() { + if (StringUtils.hasLength(uname_color)) { + uname_color = "FFFFFF"; + } + return uname_color; + } + + @Override + public String toString() { + return "WSInteractWord{" + + "type=" + type + + ", roomId=" + roomId + + ", timer=" + timer + + ", medal=" + medal + + ", uid=" + uid + + ", uname='" + uname + '\'' + + ", uname_color='" + uname_color + '\'' + + ", face='" + face + '\'' + + ", cmd='" + cmd + '\'' + + ", jsonSrc='" + jsonSrc + '\'' + + ", ws_timer=" + ws_timer + + '}'; + } +} diff --git a/biliapi/src/main/java/com/yutou/bili/bean/websocket/live/WSMedalInfo.java b/biliapi/src/main/java/com/yutou/bili/bean/websocket/live/WSMedalInfo.java new file mode 100644 index 0000000..d93cc5c --- /dev/null +++ b/biliapi/src/main/java/com/yutou/bili/bean/websocket/live/WSMedalInfo.java @@ -0,0 +1,37 @@ +package com.yutou.bili.bean.websocket.live; + +import com.alibaba.fastjson2.annotation.JSONField; +import com.yutou.okhttp.BaseBean; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@EqualsAndHashCode(callSuper = true) +@Data +public class WSMedalInfo extends BaseBean { + @JSONField(name = "anchor_roomid") + private long anchorRoomid; + @JSONField(name = "anchor_uname") + private String anchorUname; + @JSONField(name = "guard_level") + private long guardLevel; + @JSONField(name = "icon_id") + private long iconID; + @JSONField(name = "is_lighted") + private long isLighted; + @JSONField(name = "medal_color") + private long medalColor; + @JSONField(name = "medal_color_border") + private long medalColorBorder; + @JSONField(name = "medal_color_end") + private long medalColorEnd; + @JSONField(name = "medal_color_start") + private long medalColorStart; + @JSONField(name = "medal_level") + private long medalLevel; + @JSONField(name = "medal_name") + private String medalName; + @JSONField(name = "special") + private String special; + @JSONField(name = "target_id") + private long targetID; +} diff --git a/biliapi/src/main/java/com/yutou/bili/bean/websocket/live/WSSendGift.java b/biliapi/src/main/java/com/yutou/bili/bean/websocket/live/WSSendGift.java new file mode 100644 index 0000000..13d2c44 --- /dev/null +++ b/biliapi/src/main/java/com/yutou/bili/bean/websocket/live/WSSendGift.java @@ -0,0 +1,246 @@ +package com.yutou.bili.bean.websocket.live; + +import com.alibaba.fastjson2.JSONObject; +import com.alibaba.fastjson2.annotation.JSONField; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; +import java.util.List; + +@EqualsAndHashCode(callSuper = true) +@lombok.Data +public class WSSendGift extends WSData { + private Data data; + + public WSSendGift(JSONObject json) { + super(json); + data = JSONObject.parseObject(json.getJSONObject("data").toString(), Data.class); + } + + @Override + public String toString() { + return "WSSendGift{" + + "data=" + data + + ", cmd='" + cmd + '\'' + + ", jsonSrc='" + jsonSrc + '\'' + + ", ws_timer=" + ws_timer + + '}'; + } + + @lombok.Data + public static class Data implements Serializable { + @JSONField(name = "action") + private String action; + @JSONField(name = "batch_combo_id") + private String batchComboID; + @JSONField(name = "batch_combo_send") + private ComboSend batchComboSend; + @JSONField(name = "combo_send") + private ComboSend comboSend; + @JSONField(name = "beatId") + private String beatID; + @JSONField(name = "biz_source") + private String bizSource; + @JSONField(name = "broadcast_id") + private long broadcastID; + @JSONField(name = "coin_type") + private String coinType; + @JSONField(name = "combo_resources_id") + private long comboResourcesID; + @JSONField(name = "combo_stay_time") + private long comboStayTime; + @JSONField(name = "combo_total_coin") + private long comboTotalCoin; + @JSONField(name = "crit_prob") + private long critProb; + @JSONField(name = "demarcation") + private long demarcation; + @JSONField(name = "discount_price") + private long discountPrice; + @JSONField(name = "dmscore") + private long dmscore; + @JSONField(name = "draw") + private long draw; + @JSONField(name = "effect") + private long effect; + @JSONField(name = "effect_block") + private long effectBlock; + @JSONField(name = "face") + private String face; + @JSONField(name = "face_effect_id") + private long faceEffectID; + @JSONField(name = "face_effect_type") + private long faceEffectType; + @JSONField(name = "face_effect_v2") + private FaceEffectV2 faceEffectV2; + @JSONField(name = "float_sc_resource_id") + private long floatScResourceID; + @JSONField(name = "giftId") + private long giftID; + @JSONField(name = "giftName") + private String giftName; + @JSONField(name = "giftType") + private long giftType; + @JSONField(name = "gift_info") + private GiftInfo giftInfo; + @JSONField(name = "gift_tag") + private List giftTag; + @JSONField(name = "gold") + private long gold; + @JSONField(name = "guard_level") + private long guardLevel; + @JSONField(name = "is_first") + private boolean isFirst; + @JSONField(name = "is_join_receiver") + private boolean isJoinReceiver; + @JSONField(name = "is_naming") + private boolean isNaming; + @JSONField(name = "is_special_batch") + private long isSpecialBatch; + @JSONField(name = "magnification") + private long magnification; + @JSONField(name = "medal_info") + private WSMedalInfo medalInfo; + @JSONField(name = "name_color") + private String nameColor; + @JSONField(name = "num") + private long num; + @JSONField(name = "original_gift_name") + private String originalGiftName; + @JSONField(name = "price") + private long price; + @JSONField(name = "rcost") + private long rcost; + @JSONField(name = "receive_user_info") + private ReceiveUserInfo receiveUserInfo; + @JSONField(name = "receiver_uinfo") + private ErUinfo receiverUinfo; + @JSONField(name = "remain") + private long remain; + @JSONField(name = "rnd") + private String rnd; + @JSONField(name = "sender_uinfo") + private ErUinfo senderUinfo; + @JSONField(name = "silver") + private long silver; + @JSONField(name = "super") + private long dataSuper; + @JSONField(name = "super_batch_gift_num") + private long superBatchGiftNum; + @JSONField(name = "super_gift_num") + private long superGiftNum; + @JSONField(name = "svga_block") + private long svgaBlock; + @JSONField(name = "switch") + private boolean dataSwitch; + @JSONField(name = "tag_image") + private String tagImage; + @JSONField(name = "tid") + private String tid; + @JSONField(name = "timestamp") + private long timestamp; + @JSONField(name = "total_coin") + private long totalCoin; + @JSONField(name = "uid") + private long uid; + @JSONField(name = "uname") + private String uname; + @JSONField(name = "wealth_level") + private long wealthLevel; + } + + @lombok.Data + public static class FaceEffectV2 implements Serializable{ + @JSONField(name = "id") + private long id; + @JSONField(name = "type") + private long type; + } + + @lombok.Data + public static class GiftInfo implements Serializable{ + @JSONField(name = "effect_id") + private long effectID; + @JSONField(name = "has_imaged_gift") + private long hasImagedGift; + @JSONField(name = "img_basic") + private String imgBasic; + @JSONField(name = "webp") + private String webp; + } + + @lombok.Data + public static class ReceiveUserInfo implements Serializable { + @JSONField(name = "uid") + private long uid; + @JSONField(name = "uname") + private String uname; + } + + @lombok.Data + public static class ErUinfo implements Serializable { + @JSONField(name = "base") + private Base base; + @JSONField(name = "uid") + private long uid; + } + + @lombok.Data + public static class Base implements Serializable { + @JSONField(name = "face") + private String face; + @JSONField(name = "is_mystery") + private boolean isMystery; + @JSONField(name = "name") + private String name; + @JSONField(name = "name_color") + private long nameColor; + @JSONField(name = "name_color_str") + private String nameColorStr; + @JSONField(name = "official_info") + private OfficialInfo officialInfo; + @JSONField(name = "origin_info") + private Info originInfo; + @JSONField(name = "risk_ctrl_info") + private Info riskCtrlInfo; + } + + @lombok.Data + public static class OfficialInfo implements Serializable { + @JSONField(name = "desc") + private String desc; + @JSONField(name = "role") + private long role; + @JSONField(name = "title") + private String title; + @JSONField(name = "type") + private long type; + } + + @lombok.Data + public static class Info implements Serializable { + @JSONField(name = "face") + private String face; + @JSONField(name = "name") + private String name; + } + @lombok.Data + public static class ComboSend implements Serializable { + @JSONField(name = "action") + private String action; + @JSONField(name = "combo_id") + private String comboID; + @JSONField(name = "combo_num") + private long comboNum; + @JSONField(name = "gift_id") + private long giftID; + @JSONField(name = "gift_name") + private String giftName; + @JSONField(name = "gift_num") + private long giftNum; + @JSONField(name = "uid") + private long uid; + @JSONField(name = "uname") + private String uname; + } +} diff --git a/biliapi/src/main/java/com/yutou/bili/bean/websocket/live/WSSuperChatMessage.java b/biliapi/src/main/java/com/yutou/bili/bean/websocket/live/WSSuperChatMessage.java new file mode 100644 index 0000000..9344686 --- /dev/null +++ b/biliapi/src/main/java/com/yutou/bili/bean/websocket/live/WSSuperChatMessage.java @@ -0,0 +1,33 @@ +package com.yutou.bili.bean.websocket.live; + +import com.alibaba.fastjson2.JSONObject; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 超级留言 + * 醒目留言 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class WSSuperChatMessage extends WSData{ + private long price; + private long rate; + private long uid; + private long start_time; + private long end_time; + private String message; + private String message_trans; + private String message_font_color; + private WSMedalInfo medal_info; + + + public static void main(String[] args) { + JSONObject json=JSONObject.parseObject("{\"cmd\":\"SUPER_CHAT_MESSAGE\",\"data\":{\"background_bottom_color\":\"#2A60B2\",\"background_color\":\"#EDF5FF\",\"background_color_end\":\"#405D85\",\"background_color_start\":\"#3171D2\",\"background_icon\":\"\",\"background_image\":\"\",\"background_price_color\":\"#7497CD\",\"color_point\":0.7,\"dmscore\":616,\"end_time\":1724997230,\"gift\":{\"gift_id\":12000,\"gift_name\":\"醒目留言\",\"num\":1},\"group_medal\":{\"is_lighted\":0,\"medal_id\":0,\"name\":\"\"},\"id\":10427329,\"is_mystery\":false,\"is_ranked\":0,\"is_send_audit\":1,\"medal_info\":{\"anchor_roomid\":81004,\"anchor_uname\":\"艾尔莎_Channel\",\"guard_level\":0,\"icon_id\":0,\"is_lighted\":1,\"medal_color\":\"#1a544b\",\"medal_color_border\":1725515,\"medal_color_end\":5414290,\"medal_color_start\":1725515,\"medal_level\":21,\"medal_name\":\"艾薯条\",\"special\":\"\",\"target_id\":1521415},\"message\":\"莎莎,想安利你个植物大战僵尸的改版叫植物大战僵尸:肉鸽,具体情况私信你了,辛苦了\",\"message_font_color\":\"#A3F6FF\",\"message_trans\":\"サーシャ、あなたのPlantsvs.Zombiesの改版をPlantsvs.Zombiesと言いたい:肉鳩、具体的な状況は私的にあなたを信じて、お疲れ様でした\",\"price\":30,\"rate\":1000,\"start_time\":1724997170,\"time\":60,\"token\":\"9925C118\",\"trans_mark\":0,\"ts\":1724997170,\"uid\":100002175,\"uinfo\":{\"base\":{\"face\":\"https://i1.hdslb.com/bfs/face/b5ec3b1f7025b5546225ae0f36941d55ddef405b.jpg\",\"is_mystery\":false,\"name\":\"中吴同学\",\"name_color\":0,\"name_color_str\":\"#666666\",\"official_info\":{\"desc\":\"\",\"role\":0,\"title\":\"\",\"type\":-1},\"origin_info\":{\"face\":\"https://i1.hdslb.com/bfs/face/b5ec3b1f7025b5546225ae0f36941d55ddef405b.jpg\",\"name\":\"中吴同学\"}},\"guard\":{\"expired_str\":\"\",\"level\":0},\"medal\":{\"color\":1725515,\"color_border\":1725515,\"color_end\":5414290,\"color_start\":1725515,\"guard_icon\":\"\",\"guard_level\":0,\"honor_icon\":\"\",\"id\":0,\"is_light\":1,\"level\":21,\"name\":\"艾薯条\",\"ruid\":1521415,\"score\":50001980,\"typ\":0,\"user_receive_count\":0,\"v2_medal_color_border\":\"#5FC7F4FF\",\"v2_medal_color_end\":\"#43B3E3CC\",\"v2_medal_color_level\":\"#00308C99\",\"v2_medal_color_start\":\"#43B3E3CC\",\"v2_medal_color_text\":\"#FFFFFFFF\"},\"title\":{\"old_title_css_id\":\"\",\"title_css_id\":\"\"},\"uid\":100002175},\"user_info\":{\"face\":\"https://i1.hdslb.com/bfs/face/b5ec3b1f7025b5546225ae0f36941d55ddef405b.jpg\",\"face_frame\":\"\",\"guard_level\":0,\"is_main_vip\":1,\"is_svip\":0,\"is_vip\":0,\"level_color\":\"#5896de\",\"manager\":0,\"name_color\":\"#666666\",\"title\":\"\",\"uname\":\"中吴同学\",\"user_level\":25}},\"is_report\":true,\"msg_id\":\"19106780029655552:1000:1000\",\"p_is_ack\":true,\"p_msg_type\":1,\"send_time\":1724997170767}"); + WSSuperChatMessage message=new WSSuperChatMessage(json); + System.out.println(message); + } + public WSSuperChatMessage(JSONObject json) { + super(json); + } +} diff --git a/biliapi/src/main/java/com/yutou/bili/bean/websocket/live/WSUserMedal.java b/biliapi/src/main/java/com/yutou/bili/bean/websocket/live/WSUserMedal.java new file mode 100644 index 0000000..3114046 --- /dev/null +++ b/biliapi/src/main/java/com/yutou/bili/bean/websocket/live/WSUserMedal.java @@ -0,0 +1,25 @@ +package com.yutou.bili.bean.websocket.live; + +import com.alibaba.fastjson2.JSONArray; +import lombok.Data; + +@Data +public class WSUserMedal { + private long uid; + private String medal_name; + private String medal_color = "FFFFFF"; + private String medal_anchor; + private int medal_level; + + public static WSUserMedal create(JSONArray array) { + if (array.isEmpty()) { + return null; + } + WSUserMedal medal = new WSUserMedal(); + medal.setUid(array.getIntValue(3)); + medal.setMedal_name(array.getString(1)); + medal.setMedal_anchor(array.getString(2)); + medal.setMedal_level(array.getIntValue(0)); + return medal; + } +} diff --git a/biliapi/src/main/java/com/yutou/bili/net/BiliLiveNetApiManager.java b/biliapi/src/main/java/com/yutou/bili/net/BiliLiveNetApiManager.java index 851c275..3a7707d 100644 --- a/biliapi/src/main/java/com/yutou/bili/net/BiliLiveNetApiManager.java +++ b/biliapi/src/main/java/com/yutou/bili/net/BiliLiveNetApiManager.java @@ -33,7 +33,7 @@ public class BiliLiveNetApiManager extends BaseApi { header.put("Referer", "https://live.bilibili.com"); header.put("Connection", "keep-alive"); header.put("Upgrade-Insecure-Requests", "1"); - setHeaders(header); + addHeader(header); callback.onSuccess(createApi(LiveApi.class)); } } diff --git a/biliapi/src/main/java/com/yutou/bili/net/WebSocketManager.java b/biliapi/src/main/java/com/yutou/bili/net/WebSocketManager.java index 5073c15..203ca57 100644 --- a/biliapi/src/main/java/com/yutou/bili/net/WebSocketManager.java +++ b/biliapi/src/main/java/com/yutou/bili/net/WebSocketManager.java @@ -1,18 +1,27 @@ package com.yutou.bili.net; +import com.aayushatharva.brotli4j.Brotli4jLoader; +import com.aayushatharva.brotli4j.decoder.Decoder; +import com.aayushatharva.brotli4j.decoder.DecoderJNI; +import com.aayushatharva.brotli4j.decoder.DirectDecompress; 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.bili.bean.websocket.WebSocketBody; +import com.yutou.bili.bean.websocket.WebSocketHeader; +import com.yutou.bili.bean.websocket.live.WSData; +import com.yutou.bili.databases.BiliBiliLoginDatabase; +import com.yutou.bili.utils.BiliUserUtils; +import com.yutou.bili.utils.BytesUtils; import com.yutou.inter.IHttpApiCheckCallback; import com.yutou.okhttp.HttpCallback; -import jakarta.xml.bind.DatatypeConverter; +import com.yutou.utils.Log; 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; @@ -21,7 +30,6 @@ 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; @@ -81,25 +89,6 @@ public class WebSocketManager { 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(); } @@ -108,7 +97,7 @@ public class WebSocketManager { WebSocketManager.getInstance().roomMap.put(roomConfig, this); heartbeatTask.setSocket(this); heartbeatTask.sendInitAuthData(); - new Timer().schedule(heartbeatTask, 0, 30000); + new Timer().schedule(heartbeatTask, 1000, 30000); System.out.println("WebSocketClientTh.onOpen"); } @@ -146,17 +135,40 @@ public class WebSocketManager { * @param data 待压缩的数据 */ public void decompress(byte[] data) { + byte[] bytes = new byte[data.length - 16]; + WebSocketHeader header = new WebSocketHeader(data); + System.arraycopy(data, header.getHeaderSize(), bytes, 0, data.length - header.getHeaderSize()); + System.out.println("数据大小:" + header.getDataSize() + " 协议:" + header.getAgree() + " 头部大小:" + header.getHeaderSize() + " 命令:" + header.getCmdData()); + switch (header.getAgree()) { + case 0: + case 1: + danmu(bytes); + break; + default: + unzipDanmu(bytes, header.getAgree() == 3); + } + + } + + private void danmu(byte[] bytes) { + Log.i("未压缩:" + new String(bytes)); + } + + private void unzipDanmu(byte[] bytes, boolean useHeader) { 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); + Brotli4jLoader.ensureAvailability(); + DirectDecompress directDecompress = Decoder.decompress(bytes); + if (directDecompress.getResultStatus() == DecoderJNI.Status.DONE) { + WebSocketBody body = new WebSocketBody(directDecompress.getDecompressedData()); + Log.i("3协议:" + useHeader + " 命令数:" + body.getBodyList().size()); + for (JSONObject json : body.getBodyList()) { + Log.i("解压:" + WSData.parse(json)); + } + System.out.println(); + System.out.println(); + } else { + Log.e(new RuntimeException("解压失败")); } - System.out.println("接收值:" + new String(out.toByteArray())); } catch (Exception e) { e.printStackTrace(); } @@ -171,60 +183,66 @@ public class WebSocketManager { @Override public void run() { - + try { + // com.yutou.bilibili.Tools.Log.i("-------发送心跳--------"); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + outputStream.write(BytesUtils.toLH("[object Object]".length() + 16)); + outputStream.write(new byte[]{0, 16, 0, 1, 0, 0, 0, 2, 0, 0, 0, 1}); + outputStream.write("[object Object]".getBytes(StandardCharsets.UTF_8)); + outputStream.flush(); + socket.send(outputStream.toByteArray()); + } catch (Exception e) { + e.printStackTrace(); + } } public void sendInitAuthData() { JSONObject json = new JSONObject(); if (roomConfig.isLogin()) { - json.put("uid", roomConfig.getUid()); + json.put("uid", Long.parseLong(roomConfig.getUid())); + } else { - json.put("uid", "0"); + 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)); + BiliUserUtils.getBuvid(new IHttpApiCheckCallback() { + @Override + public void onSuccess(String api) { + try { + json.put("roomid", Long.parseLong(roomConfig.getRoomId())); + json.put("protover", 3); + json.put("buvid", api); + json.put("platform", "web"); + 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(); + System.out.println("bytes.length = " + bytes.length); + Log.i(json); + outputStream.write(BytesUtils.toLH(json.toString().length() + 16)); + outputStream.write(bytes); + outputStream.write(json.toJSONString().getBytes(StandardCharsets.UTF_8)); + outputStream.flush(); + BytesUtils.printHex(outputStream.toByteArray()); + System.out.println(socket.isOpen()); + socket.send(outputStream.toByteArray()); + + } catch (Exception e) { + e.printStackTrace(); } } - System.out.println("\n\n\n"); - System.out.println(socket.isOpen()); - socket.send(outputStream.toByteArray()); - } catch (Exception e) { - e.printStackTrace(); - } + @Override + public void onError(int code, String error) { + + } + }); + } - 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/BiliUserUtils.java b/biliapi/src/main/java/com/yutou/bili/utils/BiliUserUtils.java new file mode 100644 index 0000000..4e63b2c --- /dev/null +++ b/biliapi/src/main/java/com/yutou/bili/utils/BiliUserUtils.java @@ -0,0 +1,41 @@ +package com.yutou.bili.utils; + +import com.yutou.bili.api.UserApi; +import com.yutou.bili.bean.live.SpiBean; +import com.yutou.bili.net.BiliUserNetApiManager; +import com.yutou.inter.IHttpApiCheckCallback; +import com.yutou.okhttp.HttpCallback; +import com.yutou.utils.RedisTools; +import okhttp3.Headers; + +public class BiliUserUtils { + public static void getBuvid(IHttpApiCheckCallback callback) { + String buvid= RedisTools.get(RedisTools.BILI_USER_BUVID); + if(buvid!=null){ + callback.onSuccess(buvid); + return; + } + BiliUserNetApiManager.getInstance().getUserApi(new IHttpApiCheckCallback() { + @Override + public void onSuccess(UserApi api) { + api.getFingerSpi().enqueue(new HttpCallback() { + @Override + public void onResponse(Headers headers, int code, String status, SpiBean response, String rawResponse) { + RedisTools.set(RedisTools.BILI_USER_BUVID,response.getB_3()); + callback.onSuccess(response.getB_3()); + } + + @Override + public void onFailure(Throwable throwable) { + + } + }); + } + + @Override + public void onError(int code, String error) { + + } + }); + } +} diff --git a/biliapi/src/main/java/com/yutou/bili/utils/BytesUtils.java b/biliapi/src/main/java/com/yutou/bili/utils/BytesUtils.java new file mode 100644 index 0000000..6b27cd2 --- /dev/null +++ b/biliapi/src/main/java/com/yutou/bili/utils/BytesUtils.java @@ -0,0 +1,41 @@ +package com.yutou.bili.utils; + +import com.yutou.utils.Log; +import jakarta.xml.bind.DatatypeConverter; + +public class BytesUtils { + + public static int bytesToInt2(byte[] src, int offset) { + int value; + value = (int) (((src[offset] & 0xFF) << 24) + | ((src[offset + 1] & 0xFF) << 16) + | ((src[offset + 2] & 0xFF) << 8) + | (src[offset + 3] & 0xFF)); + return value; + } + + public static void printHex(byte[] byteArray) { + System.out.println("\n\n\n"); + String str = DatatypeConverter.printHexBinary(byteArray); + Log.i(str); + 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"); + } + public static 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/common/src/main/java/com/yutou/okhttp/api/BaseApi.java b/common/src/main/java/com/yutou/okhttp/api/BaseApi.java index f606f8c..a52f0ba 100644 --- a/common/src/main/java/com/yutou/okhttp/api/BaseApi.java +++ b/common/src/main/java/com/yutou/okhttp/api/BaseApi.java @@ -35,6 +35,10 @@ public class BaseApi { this.headers = headers; return this; } + public void addHeader(HashMap headers){ + this.headers.putAll(headers); + } + public BaseApi setParams(HashMap params) { this.params = params; diff --git a/common/src/main/java/com/yutou/utils/Log.java b/common/src/main/java/com/yutou/utils/Log.java index 6fe4ce1..f0becbf 100644 --- a/common/src/main/java/com/yutou/utils/Log.java +++ b/common/src/main/java/com/yutou/utils/Log.java @@ -8,7 +8,7 @@ import java.util.logging.Level; import java.util.logging.Logger; public class Log { - private static Logger logger; + private static Logger logger=Logger.getLogger("Biliob"); public static void i(String tag, Object log) { i('[' + tag + ']' + log); @@ -18,6 +18,7 @@ public class Log { if (!((boolean) ConfigTools.load(ConfigTools.CONFIG, "logcat"))) { return; } + // logger.log(Level.INFO, log.toString()); System.out.printf("[%s]%s%n", AppTools.getToDayNowTimeToString(), log diff --git a/common/src/main/java/com/yutou/utils/RedisTools.java b/common/src/main/java/com/yutou/utils/RedisTools.java index 1ff6853..16eeb9f 100644 --- a/common/src/main/java/com/yutou/utils/RedisTools.java +++ b/common/src/main/java/com/yutou/utils/RedisTools.java @@ -14,6 +14,7 @@ import java.util.Set; public class RedisTools { public static final int QQBOT_USER = 3; + public static final String BILI_USER_BUVID = "bili_user_buvid"; private static boolean isNotInstallRedis = false; private static String host; private static int port; @@ -28,7 +29,7 @@ public class RedisTools { //Properties properties = PropertyUtil.loadProperties("jedis.properties"); //host = properties.getProperty("redis.host"); //port = Integer.valueOf(properties.getProperty("redis.port")); - host = "127.0.0.1"; + host = "172.22.81.254"; port = 6379; } @@ -74,7 +75,7 @@ public class RedisTools { } public static String get(String key, int dbIndex) { - String value = "-999"; + String value = null; if (isNotInstallRedis) { return value; }