174 lines
6.9 KiB
C#
174 lines
6.9 KiB
C#
using System;
|
|||
|
|
using System.Collections.Generic;
|
||
|
|
using System.Linq;
|
||
|
|
using System.Net.Http;
|
||
|
|
using System.Text;
|
||
|
|
using System.Text.RegularExpressions;
|
||
|
|
using System.Threading.Tasks;
|
||
|
|
using BarrageGrab.Modles;
|
||
|
|
using Newtonsoft.Json.Linq;
|
||
|
|
|
||
|
|
namespace BarrageGrab
|
||
|
|
{
|
||
|
|
internal class KsApiHelper
|
||
|
|
{
|
||
|
|
public static async Task<KsRoomInfo> GetRoomInfo(string userId, string cookie = null)
|
||
|
|
{
|
||
|
|
string url = $"https://live.kuaishou.com/u/{userId}";
|
||
|
|
|
||
|
|
var client = new HttpClient();
|
||
|
|
client.DefaultRequestHeaders.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 Edg/131.0.0.0");
|
||
|
|
client.DefaultRequestHeaders.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8");
|
||
|
|
client.DefaultRequestHeaders.Add("Accept-Language", "zh-CN,zh;q=0.9");
|
||
|
|
if (!string.IsNullOrWhiteSpace(cookie))
|
||
|
|
{
|
||
|
|
client.DefaultRequestHeaders.Add("Cookie", cookie);
|
||
|
|
}
|
||
|
|
|
||
|
|
try
|
||
|
|
{
|
||
|
|
var response = await client.GetStringAsync(url);
|
||
|
|
return ParseRoomInfoFromHtml(response, userId);
|
||
|
|
}
|
||
|
|
catch (Exception ex)
|
||
|
|
{
|
||
|
|
Logger.LogError($"获取快手房间信息失败: {ex.Message}");
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
public static KsRoomInfo ParseRoomInfoFromHtml(string html, string userId)
|
||
|
|
{
|
||
|
|
var info = new KsRoomInfo { UserId = userId };
|
||
|
|
|
||
|
|
try
|
||
|
|
{
|
||
|
|
// 2026-05 快手页面结构变更: __INITIAL_STATE__ JSON 中的字段名和旧版本不同
|
||
|
|
// liveStreamId → 嵌套在 "liveStream":{"id":"xxx"}
|
||
|
|
var liveStreamIdReg = new Regex(@"""liveStream""\s*:\s*\{[^}]*?""id""\s*:\s*""([^""]+)""");
|
||
|
|
var liveStreamMatch = liveStreamIdReg.Match(html);
|
||
|
|
if (liveStreamMatch.Success)
|
||
|
|
{
|
||
|
|
info.LiveStreamId = liveStreamMatch.Groups[1].Value;
|
||
|
|
}
|
||
|
|
|
||
|
|
// 直播封面 → liveStream.poster
|
||
|
|
var posterReg = new Regex(@"""liveStream""\s*:\s*\{[^}]*?""poster""\s*:\s*""([^""]+)""");
|
||
|
|
var posterMatch = posterReg.Match(html);
|
||
|
|
if (posterMatch.Success)
|
||
|
|
{
|
||
|
|
info.CoverUrl = UnescapeJson(posterMatch.Groups[1].Value);
|
||
|
|
}
|
||
|
|
|
||
|
|
// token
|
||
|
|
var tokenReg = new Regex(@"""token""\s*:\s*""([^""]*)""");
|
||
|
|
var tokenMatch = tokenReg.Match(html);
|
||
|
|
if (tokenMatch.Success && !string.IsNullOrWhiteSpace(tokenMatch.Groups[1].Value))
|
||
|
|
{
|
||
|
|
info.Token = tokenMatch.Groups[1].Value;
|
||
|
|
}
|
||
|
|
|
||
|
|
// 主播昵称 → 嵌套在 "author":{"...","name":"xxx"}
|
||
|
|
var nickReg = new Regex(@"""author""\s*:\s*\{[^}]*?""name""\s*:\s*""([^""]+)""");
|
||
|
|
var nickMatch = nickReg.Match(html);
|
||
|
|
if (nickMatch.Success)
|
||
|
|
{
|
||
|
|
info.Nickname = nickMatch.Groups[1].Value;
|
||
|
|
}
|
||
|
|
|
||
|
|
// 主播头像 → author.avatar
|
||
|
|
var avatarReg = new Regex(@"""author""\s*:\s*\{[^}]*?""avatar""\s*:\s*""([^""]+)""");
|
||
|
|
var avatarMatch = avatarReg.Match(html);
|
||
|
|
if (avatarMatch.Success)
|
||
|
|
{
|
||
|
|
info.HeadUrl = UnescapeJson(avatarMatch.Groups[1].Value);
|
||
|
|
}
|
||
|
|
|
||
|
|
// 观看人数 → 现在是字符串如 "1万+",不是纯数字
|
||
|
|
var watchingReg = new Regex(@"""watchingCount""\s*:\s*""([^""]+)""");
|
||
|
|
var watchingMatch = watchingReg.Match(html);
|
||
|
|
if (watchingMatch.Success)
|
||
|
|
{
|
||
|
|
info.WatchingCountStr = watchingMatch.Groups[1].Value;
|
||
|
|
info.WatchingCount = ParseCount(info.WatchingCountStr);
|
||
|
|
}
|
||
|
|
|
||
|
|
// 点赞数 → 字段名已变更为 liked
|
||
|
|
var likeReg = new Regex(@"""liked""\s*:\s*""([^""]+)""");
|
||
|
|
var likeMatch = likeReg.Match(html);
|
||
|
|
if (likeMatch.Success)
|
||
|
|
{
|
||
|
|
info.LikeCountStr = likeMatch.Groups[1].Value;
|
||
|
|
info.LikeCount = ParseCount(info.LikeCountStr);
|
||
|
|
}
|
||
|
|
|
||
|
|
// 直播状态 → 现在是 isLiving 布尔值
|
||
|
|
var liveReg = new Regex(@"""isLiving""\s*:\s*(true|false)");
|
||
|
|
var liveMatch = liveReg.Match(html);
|
||
|
|
if (liveMatch.Success)
|
||
|
|
{
|
||
|
|
info.IsLive = liveMatch.Groups[1].Value == "true";
|
||
|
|
}
|
||
|
|
}
|
||
|
|
catch (Exception ex)
|
||
|
|
{
|
||
|
|
Logger.LogWarn($"解析快手页面房间信息时出错: {ex.Message}");
|
||
|
|
}
|
||
|
|
|
||
|
|
return info;
|
||
|
|
}
|
||
|
|
|
||
|
|
// 将 "1万+" "2853" "6.7w" 等转为 long
|
||
|
|
private static long ParseCount(string str)
|
||
|
|
{
|
||
|
|
if (string.IsNullOrWhiteSpace(str)) return 0;
|
||
|
|
str = str.Trim();
|
||
|
|
// 含"万" → 乘以10000
|
||
|
|
if (str.Contains("万"))
|
||
|
|
{
|
||
|
|
var numStr = Regex.Replace(str, @"[^\d\.]", "");
|
||
|
|
if (double.TryParse(numStr, out var d)) return (long)(d * 10000);
|
||
|
|
}
|
||
|
|
// 含"w" → 乘以10000
|
||
|
|
if (str.ToLower().Contains("w"))
|
||
|
|
{
|
||
|
|
var numStr = Regex.Replace(str, @"[^\d\.]", "");
|
||
|
|
if (double.TryParse(numStr, out var d)) return (long)(d * 10000);
|
||
|
|
}
|
||
|
|
// 纯数字
|
||
|
|
if (long.TryParse(str, out var l)) return l;
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
// 反转义 JSON Unicode 转义序列 (\\u002F → /)
|
||
|
|
private static string UnescapeJson(string str)
|
||
|
|
{
|
||
|
|
if (string.IsNullOrWhiteSpace(str)) return str;
|
||
|
|
return Regex.Replace(str, @"\\u([0-9a-fA-F]{4})", m =>
|
||
|
|
{
|
||
|
|
int code = int.Parse(m.Groups[1].Value, System.Globalization.NumberStyles.HexNumber);
|
||
|
|
return ((char)code).ToString();
|
||
|
|
});
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
public class KsRoomInfo
|
||
|
|
{
|
||
|
|
public string UserId { get; set; }
|
||
|
|
public string LiveStreamId { get; set; }
|
||
|
|
public string Token { get; set; }
|
||
|
|
public string Nickname { get; set; }
|
||
|
|
public long WatchingCount { get; set; }
|
||
|
|
public long LikeCount { get; set; }
|
||
|
|
public bool IsLive { get; set; }
|
||
|
|
public string PageId { get; set; } = Guid.NewGuid().ToString("N").Substring(0, 16);
|
||
|
|
public string LiveUrl { get; set; }
|
||
|
|
// 字符串格式的原始值(用于展示 "1万+" 等)
|
||
|
|
public string WatchingCountStr { get; set; }
|
||
|
|
public string LikeCountStr { get; set; }
|
||
|
|
// 头像和封面
|
||
|
|
public string HeadUrl { get; set; }
|
||
|
|
public string CoverUrl { get; set; }
|
||
|
|
}
|
||
|
|
}
|