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();
- }
- }
- }
}