diff --git a/.github/release.info b/.github/release.info index 2d665ff..436d23e 100644 --- a/.github/release.info +++ b/.github/release.info @@ -1,3 +1 @@ -* 优化:淘宝:兼容可能的json解析错误。 -* 优化:虎牙:通过java代码来计算md5,而不是js。 -* 修复:修改虎牙`ctype`、`t`参数,使之不在2分钟时就断开。 #132 \ No newline at end of file +* 修复:虎牙:参考[biliup/biliup](https://github.com/biliup/biliup/blob/7c703b936b7a79c134a7af45331d71c32de976a7/biliup/plugins/huya.py#L169),使用微信小程序的参数。 #134 \ No newline at end of file diff --git a/.github/release.json b/.github/release.json index 4ccb020..3879827 100644 --- a/.github/release.json +++ b/.github/release.json @@ -1,4 +1,4 @@ { - "tag_main": "2.28.0", - "tag_latest": "2.28.0" + "tag_main": "2.29.0", + "tag_latest": "2.29.0" } \ No newline at end of file diff --git a/README.md b/README.md index c733f0e..c485313 100644 --- a/README.md +++ b/README.md @@ -78,8 +78,8 @@ Go go go, Bilibili Pikachu! | kuaishou | 2024/08/17 | `flv`清晰度可多选,必须要cookie(可以不登录,只需要过了拖拽验证即可) | | douyin | 2024/08/17 | `flv`清晰度可多选,可不需要cookie。id为`https://live.douyin.com/1234567`后面的那串数字 | | douyin2 | 2024/08/17 | 抖音的另一种解析方式,前者失败后可以尝试。`flv`清晰度可多选,必须要cookie(可以不登录,只需要过了拖拽验证即可)。id为`https://live.douyin.com/1234567`后面的那串数字,也可以直接输入短网址类型`https://v.douyin.com/xxxx` | -| huya | 2024/08/17 | `flv`清晰度可多选,可不需要cookie。开播后要过一阵才能检测到。 | -| huya2 | 2024/08/17 | 虎牙的另一种解析方式,只接受数字id,非数字的需要打开网页寻找(热度值左边)。`flv`清晰度可多选,可不需要cookie。 | +| huya | 2024/08/22 | `flv`清晰度可多选,可不需要cookie。只接受数字id,非数字的需要打开网页寻找(热度值左边)。 | +| huya2 | 2024/08/22 | 虎牙的另一种解析方式,只接受数字id,非数字的需要打开网页寻找(热度值左边)。`flv`清晰度可多选,可不需要cookie。 | | taobao | 2023/09/17 | `flv`清晰度可多选,必须要cookie(先试一试不登录)。id建议首次使用类似`https://m.tb.cn/xxxx`的直播或者回访的分享链接,这时会输出回放m3u8链接和可用id,之后建议一直使用该id。当然,建议是使用相关m3u8下载器下载回放,而不是直接录制。 | | acfun | 2023/02/22 | `flv`清晰度可多选,可不需要cookie | | yy | 2022/10/09 | `flv`清晰度可多选,必须要cookie(可以不登录,只需要过了拖拽验证即可) | @@ -319,6 +319,7 @@ or传入参数: qnPri=蓝光4M>蓝光 + 使用[JSON.org](https://github.com/stleary/JSON-java)库做简单的Json解析[![](https://img.shields.io/badge/license-MIT-green.svg)](https://github.com/stleary/JSON-java/blob/master/LICENSE) + 使用[Crypto-js](https://github.com/brix/crypto-js)仿浏览器生成斗鱼直播录制token[![](https://img.shields.io/badge/license-MIT-green.svg)](https://github.com/brix/crypto-js/blob/develop/LICENSE) + 参考[HuyaSender](https://github.com/xyxyxiaoyan/HuyaSender)解析虎牙[TarsTup](https://github.com/TarsCloud/TarsTup)协议[![](https://img.shields.io/badge/license-Tencent%20Binary%20License-green.svg)](https://tencentdingdang.github.io/dmsdk/licenses/android-wup-sdk/LICENSE.txt) ++ 参考[biliup/biliup](https://github.com/biliup/biliup/blob/7c703b936b7a79c134a7af45331d71c32de976a7/biliup/plugins/huya.py#L169)修改query参数[![](https://img.shields.io/badge/license-MIT-green.svg)](https://github.com/biliup/biliup/blob/7c703b936b7a79c134a7af45331d71c32de976a7/LICENSE) ## :smile:LICENSE ``` diff --git a/UPDATE.md b/UPDATE.md index 688fcbe..d629cce 100644 --- a/UPDATE.md +++ b/UPDATE.md @@ -1,4 +1,6 @@ ## 更新 ++ V2.29.0 + * 修复:虎牙:参考[biliup/biliup](https://github.com/biliup/biliup/blob/7c703b936b7a79c134a7af45331d71c32de976a7/biliup/plugins/huya.py#L169),使用微信小程序的参数。 #134 + V2.28.0 * 优化:淘宝:兼容可能的json解析错误。 * 优化:虎牙:通过java代码来计算md5,而不是js。 diff --git a/pom.xml b/pom.xml index 248212b..8a24450 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 top.nicelee live-record - 2.28.0 + 2.29.0 iLiveRecord B站、Acfun、斗鱼、虎牙、快手、抖音、淘宝直播录制 diff --git a/src/main/java/nicelee/bilibili/Main.java b/src/main/java/nicelee/bilibili/Main.java index 0a73e47..62b2b45 100644 --- a/src/main/java/nicelee/bilibili/Main.java +++ b/src/main/java/nicelee/bilibili/Main.java @@ -14,7 +14,7 @@ public class Main { - final static String version = "v2.28.0"; + final static String version = "v2.29.0"; public static Thread thRecord; public static Thread thMonitor; public static Thread thCommand; diff --git a/src/main/java/nicelee/bilibili/live/impl/RoomDealerHuya.java b/src/main/java/nicelee/bilibili/live/impl/RoomDealerHuya.java index 467c207..345e761 100644 --- a/src/main/java/nicelee/bilibili/live/impl/RoomDealerHuya.java +++ b/src/main/java/nicelee/bilibili/live/impl/RoomDealerHuya.java @@ -1,17 +1,11 @@ package nicelee.bilibili.live.impl; -import java.io.InputStream; import java.math.BigInteger; -import java.net.HttpURLConnection; -import java.net.URL; import java.net.URLDecoder; import java.security.MessageDigest; import java.util.Base64; import java.util.HashMap; -import java.util.Map; import java.util.Random; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import org.json.JSONArray; import org.json.JSONObject; @@ -42,54 +36,28 @@ public RoomInfo getRoomInfo(String shortId) { roomInfo.setShortId(shortId); try { // 获取基础信息 - String basicInfoUrl = String.format("https://www.huya.com/%s", shortId); - String html = util.getContent(basicInfoUrl, headers.getCommonHeaders("www.huya.com"), null); - - Pattern pJson = Pattern.compile("var TT_ROOM_DATA =(.*?); *var +TT"); - Matcher matcher = pJson.matcher(html); - matcher.find(); - JSONObject room = new JSONObject(matcher.group(1)); + JSONObject all = getLiveObj(shortId); + JSONObject liveData = all.getJSONObject("liveData"); + JSONObject profileInfo = all.getJSONObject("profileInfo"); // 直播状态 ON REPLAY - if ("ON".equals(room.getString("state"))) { + if ("ON".equals(all.getString("liveStatus"))) { roomInfo.setLiveStatus(1); } else { roomInfo.setLiveStatus(0); } - // 真实房间id roomInfo.setRoomId(shortId); - - roomInfo.setDescription(room.getString("introduction")); - - // 房间主id - roomInfo.setUserId(room.optLong("profileRoom")); - - pJson = Pattern.compile("var hyPlayerConfig = (.*?});"); - matcher = pJson.matcher(html); - matcher.find(); - // System.out.println(matcher.group(1)); - JSONObject obj = new JSONObject(matcher.group(1)); + roomInfo.setDescription(liveData.getString("introduction")); + roomInfo.setUserId(profileInfo.optLong("profileRoom")); + roomInfo.setUserName(profileInfo.getString("nick")); + roomInfo.setTitle(liveData.getString("roomName")); + + if (roomInfo.getLiveStatus() == 1) { - JSONObject streamDetail = obj.getJSONObject("stream").getJSONArray("data").getJSONObject(0) - .getJSONArray("gameStreamInfoList").getJSONObject(0); - String url = getFlvUrlFromStreamDetail(streamDetail, ""); - boolean urlIsValid = test(url, headers.getHeaders()); - Logger.println("当前url可用性: " + urlIsValid); - if (!urlIsValid) - roomInfo.setLiveStatus(0); - } - if (roomInfo.getLiveStatus() == 1) { -// String stream = obj.getString("stream"); -// stream = new String(Base64.getDecoder().decode(stream), "UTF-8"); -// obj = new JSONObject(stream); - obj = obj.getJSONObject("stream"); - // 房间主名称 - JSONObject liveInfo = obj.getJSONArray("data").getJSONObject(0).getJSONObject("gameLiveInfo"); - roomInfo.setUserName(liveInfo.getString("nick")); - roomInfo.setTitle(liveInfo.getString("roomName")); + JSONObject obj = all.getJSONObject("stream").getJSONObject("flv"); // 清晰度 - JSONArray jArray = obj.getJSONArray("vMultiStreamInfo"); + JSONArray jArray = obj.getJSONArray("rateArray"); String[] qn = new String[jArray.length()]; String[] qnDesc = new String[jArray.length()]; for (int i = 0; i < jArray.length(); i++) { @@ -128,6 +96,14 @@ public RoomInfo getRoomInfo(String shortId) { return roomInfo; } + public JSONObject getLiveObj(String roomId) { + String basicInfoUrl = "https://mp.huya.com/cache.php?m=Live&do=profileRoom&roomid=" + roomId; + HashMap map = headers.getCommonHeaders("www.huya.com"); + String j = util.getContent(basicInfoUrl, map, null); + Logger.println(j); + return new JSONObject(j).getJSONObject("data"); + } + /** * 获取直播地址的下载链接 * @@ -139,16 +115,8 @@ public RoomInfo getRoomInfo(String shortId) { public String getLiveUrl(String roomId, String qn, Object... params) { try { // 获取基础信息 - String basicInfoUrl = String.format("https://www.huya.com/%s", roomId); - HashMap map = headers.getCommonHeaders("www.huya.com"); - String html = util.getContent(basicInfoUrl, map, null); - - Pattern pJson = Pattern.compile("var hyPlayerConfig *= *(.*?});"); - Matcher matcher = pJson.matcher(html); - matcher.find(); - JSONObject obj = new JSONObject(matcher.group(1)).getJSONObject("stream"); JSONObject streamDetail = null; - JSONArray cdns = obj.getJSONArray("data").getJSONObject(0).getJSONArray("gameStreamInfoList"); + JSONArray cdns = getLiveObj(roomId).getJSONObject("stream").getJSONArray("baseSteamInfoList"); for (int i = 0; i < cdns.length(); i++) { JSONObject cdn = cdns.getJSONObject(i); // ali CDN 似乎坚持不到5min就会断掉 @@ -180,39 +148,42 @@ String getFlvUrlFromStreamDetail(JSONObject streamDetail, String qn) { return url; } - static String sv, platform, ctype, t, uaSuffix, ua, cdnType; +// static String sv, platform, ctype, t, uaSuffix, ua, cdnType; + static String sv, ctype, t, cdnType; static { cdnType = System.getProperty("huya.cdn", "TX"); sv = System.getProperty("huya.sv", "2408161057"); - platform = System.getProperty("huya.platform", "adr"); - uaSuffix = System.getProperty("huya.uaSuffix", "&huya"); // websocket minigame signalsd - switch (platform) { - case "addr": - t = "2"; - break; - case "ios": - t = "3"; - break; - case "mini_app": - t = "102"; - break; - case "wap": - t = "103"; - sv = "1.0.0"; - break; - case "huya_liveshareh5": - platform = "liveshareh5"; - t = "104"; - break; - case "web": - t = "100"; - break; - default: - t = System.getProperty("huya.plType", "2"); - break; - } - ctype = "huya_" + platform; - ua = platform + "&" + sv + uaSuffix; +// platform = System.getProperty("huya.platform", "adr"); +// uaSuffix = System.getProperty("huya.uaSuffix", "&huya"); // websocket minigame signalsd +// switch (platform) { +// case "adr": +// t = "2"; +// break; +// case "ios": +// t = "3"; +// break; +// case "mini_app": +// t = "102"; +// break; +// case "wap": +// t = "103"; +// sv = "1.0.0"; +// break; +// case "huya_liveshareh5": +// platform = "liveshareh5"; +// t = "104"; +// break; +// case "web": +// t = "100"; +// break; +// default: +// t = System.getProperty("huya.plType", "2"); +// break; +// } +// ctype = "huya_" + platform; +// ua = platform + "&" + sv + uaSuffix; + ctype = System.getProperty("huya.force_ctype", "tars_mp");; + t = System.getProperty("huya.force_t", "102"); } String genFlvAntiCode(String sStreamName, String sFlvAntiCode, String qn) { @@ -228,6 +199,7 @@ String genFlvAntiCode(String sStreamName, String sFlvAntiCode, String qn) { } // 随机生成uid long uid = 1462220000000L + new Random().nextInt(1145142333); + long convertUid = (uid << 8 | uid >> (32 - 8)) & 0xFFFFFFFF; long currentTime = System.currentTimeMillis(); String wsTime = Long.toHexString(currentTime / 1000); long seqid = uid + currentTime + 216000000L; // 216000000 = 30*1000*60*60 = 30h @@ -239,16 +211,26 @@ String genFlvAntiCode(String sStreamName, String sFlvAntiCode, String qn) { Logger.println(ctype); // String oe = JSEngine.huyaTrans(String.join("|", "" + seqid, ctype, t)); String oe = md5(String.join("|", "" + seqid, ctype, t)); - String r = fm.replace("$0", "" + uid).replace("$1", sStreamName).replace("$2", oe).replace("$3", wsTime); + String r = fm.replace("$0", "" + convertUid).replace("$1", sStreamName).replace("$2", oe).replace("$3", + wsTime); String wsSecret = md5(r); StringBuilder sb = new StringBuilder(); - sb.append("wsSecret=").append(wsSecret).append("&wsTime=").append(wsTime).append("&seqid=").append(seqid) - .append("&ctype=").append(ctype).append("&ver=1&fs=").append(n.getOrDefault("fs", "")).append("&t=") - .append(t).append("&sphdcdn=").append(n.getOrDefault("sphdcdn", "")).append("&sphdDC=") - .append(n.getOrDefault("sphdDC", "")).append("&sphd=").append(n.getOrDefault("sphd", "")) - .append("&exsphd=").append(n.getOrDefault("exsphd", "")).append("&t=").append(t).append("&sv=") - .append(sv) // .append("&ratio=").append(qn) - .append("&dMod=mseh-32&sdkPcdn=1_1&u=").append(uid).append("&sdk_sid=").append(currentTime); + sb.append("wsSecret=").append(wsSecret) + .append("&wsTime=").append(wsTime) + .append("&seqid=").append(seqid) + .append("&ctype=").append(ctype) + .append("&t=").append(t) + .append("&ver=1&fs=").append(n.getOrDefault("fs", "")) +// .append("&sphdcdn=").append(n.getOrDefault("sphdcdn", "")).append("&sphdDC=") +// .append(n.getOrDefault("sphdDC", "")).append("&sphd=").append(n.getOrDefault("sphd", "")) +// .append("&exsphd=").append(n.getOrDefault("exsphd", "")) +// .append("&t=").append(t) +// .append("&dMod=mseh-32&sdkPcdn=1_1") + .append("&sv=").append(sv) // .append("&ratio=").append(qn) + .append("&uuid=").append(uid / 100) // 随便填的 + .append("&codec=264") + .append("&u=").append(convertUid) + .append("&sdk_sid=").append(currentTime); if (!"".equals(qn) && !"0".equals(qn)) { sb.append("&ratio=").append(qn); } @@ -283,41 +265,4 @@ public void startRecord(String url, String fileName, String shortId) { util.download(url, fileName + ".flv", mobile); } - public boolean test(String url, HashMap headers) { - InputStream inn = null; - try { - URL realUrl = new URL(url); - HttpURLConnection conn = (HttpURLConnection) realUrl.openConnection(); - conn.setConnectTimeout(20000); - conn.setReadTimeout(120000); - for (Map.Entry entry : headers.entrySet()) { - conn.setRequestProperty(entry.getKey(), entry.getValue()); - // System.out.println(entry.getKey() + ":" + entry.getValue()); - } - conn.connect(); - // 获取所有响应头字段 -// Map> map = conn.getHeaderFields(); -// // 遍历所有的响应头字段 -// for (String key : map.keySet()) { -// System.out.println(key + "--->" + map.get(key)); -// } - inn = conn.getInputStream(); - int rspCode = conn.getResponseCode(); - if (rspCode >= 200 && rspCode < 300) - return true; - else - return false; - } catch (Exception e) { - System.out.println("发送GET请求出现异常!" + e); - return false; - } finally { - try { - if (inn != null) { - inn.close(); - } - } catch (Exception e2) { - e2.printStackTrace(); - } - } - } }