using Newtonsoft.Json.Linq;
using NLog;
using System;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
namespace BililiveRecorder.Core
{
internal static class BililiveAPI
{
private static readonly Logger logger = LogManager.GetCurrentClassLogger();
private static readonly Random random = new Random();
private static readonly SemaphoreSlim semaphoreSlim = new SemaphoreSlim(1, 1);
private static HttpClient httpclient;
internal static Config.ConfigV1 Config = null; // TODO: 以后有空把整个 class 改成非 static 的然后用 DI 获取 config
static BililiveAPI()
{
httpclient = new HttpClient { Timeout = TimeSpan.FromSeconds(10) };
httpclient.DefaultRequestHeaders.Add("Accept", "application/json, text/javascript, */*; q=0.01");
httpclient.DefaultRequestHeaders.Add("Referer", "https://live.bilibili.com/");
httpclient.DefaultRequestHeaders.Add("User-Agent", Utils.UserAgent);
}
public static async Task ApplyCookieSettings(string cookie_string)
{
await semaphoreSlim.WaitAsync();
try
{
if (!string.IsNullOrWhiteSpace(cookie_string))
{
try
{
CookieContainer cc = new CookieContainer();
cc.PerDomainCapacity = 300;
foreach (var t in cookie_string.Trim(' ', ';').Split(';').Select(x => x.Trim().Split(new[] { '=' }, 2)))
{
try
{
string v = string.Empty;
if (t.Length == 2)
{
v = System.Web.HttpUtility.UrlDecode(t[1]).Trim();
}
cc.Add(new Cookie(t[0].Trim(), v, "/", ".bilibili.com"));
}
catch (Exception) { }
}
var pclient = new HttpClient(handler: new HttpClientHandler
{
CookieContainer = cc
}, disposeHandler: true)
{
Timeout = TimeSpan.FromSeconds(5)
};
pclient.DefaultRequestHeaders.Add("Accept", "application/json, text/javascript, */*; q=0.01");
pclient.DefaultRequestHeaders.Add("Referer", "https://live.bilibili.com/");
pclient.DefaultRequestHeaders.Add("User-Agent", Utils.UserAgent);
httpclient = pclient;
return;
}
catch (Exception ex)
{
logger.Error(ex, "设置 Cookie 时发生错误");
}
}
var cleanclient = new HttpClient { Timeout = TimeSpan.FromSeconds(5) };
cleanclient.DefaultRequestHeaders.Add("Accept", "application/json, text/javascript, */*; q=0.01");
cleanclient.DefaultRequestHeaders.Add("Referer", "https://live.bilibili.com/");
cleanclient.DefaultRequestHeaders.Add("User-Agent", Utils.UserAgent);
httpclient = cleanclient;
}
finally
{
semaphoreSlim.Release();
}
}
///
/// 下载json并解析
///
/// 下载路径
/// 数据
///
///
public static async Task HttpGetJsonAsync(string url)
{
await semaphoreSlim.WaitAsync();
try
{
var s = await httpclient.GetStringAsync(url);
var j = JObject.Parse(s);
return j;
}
catch (TaskCanceledException)
{
return null;
}
finally
{
semaphoreSlim.Release();
}
}
///
/// 获取直播间播放地址
///
/// 原房间号
/// FLV播放地址
///
///
public static async Task GetPlayUrlAsync(int roomid)
{
string url = $@"https://api.live.bilibili.com/room/v1/Room/playUrl?cid={roomid}&quality=4&platform=web";
if (Config.AvoidTxy)
{
// 尽量避开腾讯云
int attempt_left = 3;
while (true)
{
attempt_left--;
if ((await HttpGetJsonAsync(url))?["data"]?["durl"] is JArray all_jtoken && all_jtoken.Count > 0)
{
var all = all_jtoken.Select(x => x["url"].ToObject()).ToArray();
var withoutTxy = all.Where(x => !x.Contains("txy.")).ToArray();
if (withoutTxy.Length > 0)
{
return withoutTxy[random.Next(withoutTxy.Length)];
}
else if (attempt_left <= 0)
{
return all[random.Next(all.Length)];
}
}
else
{
throw new Exception("没有直播播放地址");
}
}
}
else
{
// 随机选择一个 url
if ((await HttpGetJsonAsync(url))?["data"]?["durl"] is JArray array)
{
var urls = array.Select(t => t?["url"]?.ToObject());
var distinct = urls.Distinct().ToArray();
if (distinct.Length > 0)
{
return distinct[random.Next(distinct.Length)];
}
}
throw new Exception("没有直播播放地址");
}
}
///
/// 获取直播间信息
///
/// 房间号(允许短号)
/// 直播间信息
///
///
public static async Task GetRoomInfoAsync(int roomid)
{
try
{
var room = await HttpGetJsonAsync($@"https://api.live.bilibili.com/room/v1/Room/get_info?id={roomid}");
if (room?["code"]?.ToObject() != 0)
{
logger.Warn("不能获取 {roomid} 的信息1: {errormsg}", roomid, room?["message"]?.ToObject() ?? "网络超时");
return null;
}
var user = await HttpGetJsonAsync($@"https://api.live.bilibili.com/live_user/v1/UserInfo/get_anchor_in_room?roomid={roomid}");
if (user?["code"]?.ToObject() != 0)
{
logger.Warn("不能获取 {roomid} 的信息2: {errormsg}", roomid, user?["message"]?.ToObject() ?? "网络超时");
return null;
}
var i = new RoomInfo()
{
ShortRoomId = room?["data"]?["short_id"]?.ToObject() ?? throw new Exception("未获取到直播间信息"),
RoomId = room?["data"]?["room_id"]?.ToObject() ?? throw new Exception("未获取到直播间信息"),
IsStreaming = 1 == (room?["data"]?["live_status"]?.ToObject() ?? throw new Exception("未获取到直播间信息")),
UserName = user?["data"]?["info"]?["uname"]?.ToObject() ?? throw new Exception("未获取到直播间信息"),
};
return i;
}
catch (Exception ex)
{
logger.Warn(ex, "获取直播间 {roomid} 的信息时出错", roomid);
throw;
}
}
}
}