2018-12-10 22:25:15 +08:00
|
|
|
using System;
|
2020-05-01 07:38:38 +08:00
|
|
|
using System.Collections.Generic;
|
2018-10-30 23:41:38 +08:00
|
|
|
using System.Linq;
|
2018-03-13 23:04:08 +08:00
|
|
|
using System.Net;
|
2019-08-22 01:26:18 +08:00
|
|
|
using System.Net.Http;
|
|
|
|
using System.Threading.Tasks;
|
2020-11-27 18:51:02 +08:00
|
|
|
using BililiveRecorder.Core.Config;
|
|
|
|
using Newtonsoft.Json.Linq;
|
|
|
|
using NLog;
|
2018-03-13 23:04:08 +08:00
|
|
|
|
|
|
|
namespace BililiveRecorder.Core
|
|
|
|
{
|
2020-11-27 18:51:02 +08:00
|
|
|
public class BililiveAPI
|
2018-03-13 23:04:08 +08:00
|
|
|
{
|
2020-05-01 07:38:38 +08:00
|
|
|
private const string HTTP_HEADER_ACCEPT = "application/json, text/javascript, */*; q=0.01";
|
|
|
|
private const string HTTP_HEADER_REFERER = "https://live.bilibili.com/";
|
|
|
|
private const string DEFAULT_SERVER_HOST = "broadcastlv.chat.bilibili.com";
|
|
|
|
private const int DEFAULT_SERVER_PORT = 2243;
|
2020-11-27 18:51:02 +08:00
|
|
|
|
2019-03-01 18:00:20 +08:00
|
|
|
private static readonly Logger logger = LogManager.GetCurrentClassLogger();
|
2019-08-22 01:26:18 +08:00
|
|
|
private static readonly Random random = new Random();
|
|
|
|
|
2020-11-27 18:51:02 +08:00
|
|
|
private readonly ConfigV1 Config;
|
|
|
|
private readonly HttpClient danmakuhttpclient;
|
|
|
|
private HttpClient httpclient;
|
|
|
|
|
|
|
|
public BililiveAPI(ConfigV1 config)
|
2019-08-22 01:26:18 +08:00
|
|
|
{
|
2020-11-27 18:51:02 +08:00
|
|
|
Config = config;
|
|
|
|
Config.PropertyChanged += (sender, e) =>
|
|
|
|
{
|
|
|
|
if (e.PropertyName == nameof(Config.Cookie))
|
|
|
|
{
|
|
|
|
ApplyCookieSettings(Config.Cookie);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2019-11-13 19:06:57 +08:00
|
|
|
httpclient = new HttpClient { Timeout = TimeSpan.FromSeconds(10) };
|
2020-05-01 07:38:38 +08:00
|
|
|
httpclient.DefaultRequestHeaders.Add("Accept", HTTP_HEADER_ACCEPT);
|
|
|
|
httpclient.DefaultRequestHeaders.Add("Referer", HTTP_HEADER_REFERER);
|
2019-08-22 01:26:18 +08:00
|
|
|
httpclient.DefaultRequestHeaders.Add("User-Agent", Utils.UserAgent);
|
2020-05-01 07:38:38 +08:00
|
|
|
|
|
|
|
danmakuhttpclient = new HttpClient { Timeout = TimeSpan.FromSeconds(10) };
|
|
|
|
danmakuhttpclient.DefaultRequestHeaders.Add("Accept", HTTP_HEADER_ACCEPT);
|
|
|
|
danmakuhttpclient.DefaultRequestHeaders.Add("Referer", HTTP_HEADER_REFERER);
|
|
|
|
danmakuhttpclient.DefaultRequestHeaders.Add("User-Agent", Utils.UserAgent);
|
2019-08-22 01:26:18 +08:00
|
|
|
}
|
|
|
|
|
2020-11-27 18:51:02 +08:00
|
|
|
public void ApplyCookieSettings(string cookie_string)
|
2019-10-31 22:02:19 +08:00
|
|
|
{
|
|
|
|
try
|
|
|
|
{
|
2020-11-27 18:51:02 +08:00
|
|
|
logger.Trace("设置 Cookie 信息...");
|
2019-10-31 22:02:19 +08:00
|
|
|
if (!string.IsNullOrWhiteSpace(cookie_string))
|
|
|
|
{
|
2020-05-01 07:38:11 +08:00
|
|
|
var pclient = new HttpClient(handler: new HttpClientHandler
|
2019-10-31 22:02:19 +08:00
|
|
|
{
|
2020-05-01 07:38:11 +08:00
|
|
|
UseCookies = false,
|
|
|
|
UseDefaultCredentials = false,
|
|
|
|
}, disposeHandler: true)
|
2019-10-31 22:02:19 +08:00
|
|
|
{
|
2020-05-01 07:38:11 +08:00
|
|
|
Timeout = TimeSpan.FromSeconds(5)
|
|
|
|
};
|
2020-05-01 07:38:38 +08:00
|
|
|
pclient.DefaultRequestHeaders.Add("Accept", HTTP_HEADER_ACCEPT);
|
|
|
|
pclient.DefaultRequestHeaders.Add("Referer", HTTP_HEADER_REFERER);
|
2020-05-01 07:38:11 +08:00
|
|
|
pclient.DefaultRequestHeaders.Add("User-Agent", Utils.UserAgent);
|
|
|
|
pclient.DefaultRequestHeaders.Add("Cookie", cookie_string);
|
|
|
|
httpclient = pclient;
|
2019-10-31 22:02:19 +08:00
|
|
|
}
|
2020-05-01 07:38:11 +08:00
|
|
|
else
|
|
|
|
{
|
|
|
|
var cleanclient = new HttpClient { Timeout = TimeSpan.FromSeconds(5) };
|
2020-05-01 07:38:38 +08:00
|
|
|
cleanclient.DefaultRequestHeaders.Add("Accept", HTTP_HEADER_ACCEPT);
|
|
|
|
cleanclient.DefaultRequestHeaders.Add("Referer", HTTP_HEADER_REFERER);
|
2020-05-01 07:38:11 +08:00
|
|
|
cleanclient.DefaultRequestHeaders.Add("User-Agent", Utils.UserAgent);
|
|
|
|
httpclient = cleanclient;
|
|
|
|
}
|
2020-11-27 18:51:02 +08:00
|
|
|
logger.Debug("设置 Cookie 成功");
|
2020-05-01 07:38:11 +08:00
|
|
|
}
|
|
|
|
catch (Exception ex)
|
|
|
|
{
|
|
|
|
logger.Error(ex, "设置 Cookie 时发生错误");
|
2019-10-31 22:02:19 +08:00
|
|
|
}
|
|
|
|
}
|
2019-03-01 18:00:20 +08:00
|
|
|
|
2018-03-15 21:55:01 +08:00
|
|
|
/// <summary>
|
|
|
|
/// 下载json并解析
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="url">下载路径</param>
|
|
|
|
/// <returns>数据</returns>
|
|
|
|
/// <exception cref="ArgumentNullException"/>
|
|
|
|
/// <exception cref="WebException"/>
|
2020-11-27 18:51:02 +08:00
|
|
|
private async Task<JObject> HttpGetJsonAsync(HttpClient client, string url)
|
2018-03-13 23:04:08 +08:00
|
|
|
{
|
2019-10-31 22:02:19 +08:00
|
|
|
try
|
|
|
|
{
|
2020-05-01 07:38:38 +08:00
|
|
|
var s = await client.GetStringAsync(url);
|
2019-10-31 22:02:19 +08:00
|
|
|
var j = JObject.Parse(s);
|
|
|
|
return j;
|
|
|
|
}
|
2019-11-13 19:06:57 +08:00
|
|
|
catch (TaskCanceledException)
|
|
|
|
{
|
|
|
|
return null;
|
|
|
|
}
|
2018-03-13 23:04:08 +08:00
|
|
|
}
|
|
|
|
|
2018-03-15 21:55:01 +08:00
|
|
|
/// <summary>
|
|
|
|
/// 获取直播间播放地址
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="roomid">原房间号</param>
|
|
|
|
/// <returns>FLV播放地址</returns>
|
|
|
|
/// <exception cref="WebException"/>
|
|
|
|
/// <exception cref="Exception"/>
|
2020-11-27 18:51:02 +08:00
|
|
|
public async Task<string> GetPlayUrlAsync(int roomid)
|
2018-03-13 23:04:08 +08:00
|
|
|
{
|
2020-11-27 18:51:02 +08:00
|
|
|
var url = $@"{Config.LiveApiHost}/room/v1/Room/playUrl?cid={roomid}&quality=4&platform=web";
|
|
|
|
// 随机选择一个 url
|
|
|
|
if ((await HttpGetJsonAsync(httpclient, url))?["data"]?["durl"] is JArray array)
|
2018-10-30 23:41:38 +08:00
|
|
|
{
|
2020-11-27 18:51:02 +08:00
|
|
|
var urls = array.Select(t => t?["url"]?.ToObject<string>());
|
|
|
|
var distinct = urls.Distinct().ToArray();
|
|
|
|
if (distinct.Length > 0)
|
2018-10-30 23:41:38 +08:00
|
|
|
{
|
2020-11-27 18:51:02 +08:00
|
|
|
return distinct[random.Next(distinct.Length)];
|
2018-10-30 23:41:38 +08:00
|
|
|
}
|
|
|
|
}
|
2020-11-27 18:51:02 +08:00
|
|
|
throw new Exception("没有直播播放地址");
|
2018-03-13 23:04:08 +08:00
|
|
|
}
|
|
|
|
|
2018-03-15 21:55:01 +08:00
|
|
|
/// <summary>
|
|
|
|
/// 获取直播间信息
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="roomid">房间号(允许短号)</param>
|
|
|
|
/// <returns>直播间信息</returns>
|
|
|
|
/// <exception cref="WebException"/>
|
|
|
|
/// <exception cref="Exception"/>
|
2020-11-27 18:51:02 +08:00
|
|
|
public async Task<RoomInfo> GetRoomInfoAsync(int roomid)
|
2018-03-13 23:04:08 +08:00
|
|
|
{
|
2019-03-01 18:00:20 +08:00
|
|
|
try
|
|
|
|
{
|
2020-05-01 07:38:38 +08:00
|
|
|
var room = await HttpGetJsonAsync(httpclient, $@"https://api.live.bilibili.com/room/v1/Room/get_info?id={roomid}");
|
2020-02-05 17:32:21 +08:00
|
|
|
if (room?["code"]?.ToObject<int>() != 0)
|
2019-03-01 18:00:20 +08:00
|
|
|
{
|
2020-02-05 17:32:21 +08:00
|
|
|
logger.Warn("不能获取 {roomid} 的信息1: {errormsg}", roomid, room?["message"]?.ToObject<string>() ?? "网络超时");
|
2019-08-22 01:26:18 +08:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2020-05-01 07:38:38 +08:00
|
|
|
var user = await HttpGetJsonAsync(httpclient, $@"https://api.live.bilibili.com/live_user/v1/UserInfo/get_anchor_in_room?roomid={roomid}");
|
2020-02-05 17:32:21 +08:00
|
|
|
if (user?["code"]?.ToObject<int>() != 0)
|
2019-08-22 01:26:18 +08:00
|
|
|
{
|
2020-02-05 17:32:21 +08:00
|
|
|
logger.Warn("不能获取 {roomid} 的信息2: {errormsg}", roomid, user?["message"]?.ToObject<string>() ?? "网络超时");
|
2019-03-01 18:00:20 +08:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
var i = new RoomInfo()
|
|
|
|
{
|
2019-08-22 01:26:18 +08:00
|
|
|
ShortRoomId = room?["data"]?["short_id"]?.ToObject<int>() ?? throw new Exception("未获取到直播间信息"),
|
|
|
|
RoomId = room?["data"]?["room_id"]?.ToObject<int>() ?? throw new Exception("未获取到直播间信息"),
|
|
|
|
IsStreaming = 1 == (room?["data"]?["live_status"]?.ToObject<int>() ?? throw new Exception("未获取到直播间信息")),
|
|
|
|
UserName = user?["data"]?["info"]?["uname"]?.ToObject<string>() ?? throw new Exception("未获取到直播间信息"),
|
2020-04-24 22:04:34 +08:00
|
|
|
Title = room?["data"]?["title"]?.ToObject<string>() ?? throw new Exception("未获取到直播间信息")
|
2019-03-01 18:00:20 +08:00
|
|
|
};
|
|
|
|
return i;
|
|
|
|
}
|
|
|
|
catch (Exception ex)
|
2018-03-13 23:04:08 +08:00
|
|
|
{
|
2019-03-01 18:00:20 +08:00
|
|
|
logger.Warn(ex, "获取直播间 {roomid} 的信息时出错", roomid);
|
|
|
|
throw;
|
|
|
|
}
|
2018-03-13 23:04:08 +08:00
|
|
|
}
|
2020-05-01 07:38:38 +08:00
|
|
|
|
2020-05-01 07:44:49 +08:00
|
|
|
/// <summary>
|
|
|
|
/// 获取弹幕连接信息
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="roomid"></param>
|
|
|
|
/// <returns></returns>
|
2020-11-27 18:51:02 +08:00
|
|
|
public async Task<(string token, string host, int port)> GetDanmuConf(int roomid)
|
2020-05-01 07:38:38 +08:00
|
|
|
{
|
|
|
|
try
|
|
|
|
{
|
|
|
|
var result = await HttpGetJsonAsync(danmakuhttpclient, $@"https://api.live.bilibili.com/room/v1/Danmu/getConf?room_id={roomid}&platform=pc&player=web");
|
|
|
|
|
|
|
|
if (result?["code"]?.ToObject<int>() == 0)
|
|
|
|
{
|
2020-05-01 07:44:49 +08:00
|
|
|
var token = result?["data"]?["token"]?.ToObject<string>() ?? string.Empty;
|
2020-05-01 07:38:38 +08:00
|
|
|
|
|
|
|
List<(string host, int port)> servers = new List<(string host, int port)>();
|
|
|
|
|
|
|
|
if (result?["data"]?["host_server_list"] is JArray host_server_list)
|
|
|
|
{
|
|
|
|
foreach (var host_server_jtoken in host_server_list)
|
|
|
|
if (host_server_jtoken is JObject host_server)
|
|
|
|
servers.Add((host_server["host"]?.ToObject<string>(), host_server["port"]?.ToObject<int>() ?? 0));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (result?["data"]?["server_list"] is JArray server_list)
|
|
|
|
{
|
|
|
|
foreach (var server_jtoken in server_list)
|
|
|
|
if (server_jtoken is JObject server)
|
|
|
|
servers.Add((server["host"]?.ToObject<string>(), server["port"]?.ToObject<int>() ?? 0));
|
|
|
|
}
|
|
|
|
|
|
|
|
servers.RemoveAll(x => string.IsNullOrWhiteSpace(x.host) || x.port <= 0 || x.host == DEFAULT_SERVER_HOST);
|
|
|
|
|
|
|
|
if (servers.Count > 0)
|
|
|
|
{
|
|
|
|
var (host, port) = servers[random.Next(servers.Count)];
|
|
|
|
return (token, host, port);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return (token, DEFAULT_SERVER_HOST, DEFAULT_SERVER_PORT);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
logger.Warn("获取直播间 {roomid} 的弹幕连接信息时返回了 {code}", roomid, result?["code"]?.ToObject<int>());
|
|
|
|
return (string.Empty, DEFAULT_SERVER_HOST, DEFAULT_SERVER_PORT);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
catch (Exception ex)
|
|
|
|
{
|
|
|
|
logger.Warn(ex, "获取直播间 {roomid} 的弹幕连接信息时出错", roomid);
|
|
|
|
return (string.Empty, DEFAULT_SERVER_HOST, DEFAULT_SERVER_PORT);
|
|
|
|
}
|
|
|
|
}
|
2018-03-13 23:04:08 +08:00
|
|
|
}
|
|
|
|
}
|