2021-02-23 18:03:37 +08:00
|
|
|
using System;
|
|
|
|
using System.ComponentModel;
|
2021-03-01 21:38:13 +08:00
|
|
|
using System.Net;
|
2021-02-23 18:03:37 +08:00
|
|
|
using System.Net.Http;
|
2023-07-04 08:34:11 +08:00
|
|
|
using System.Text.RegularExpressions;
|
2021-02-23 18:03:37 +08:00
|
|
|
using System.Threading;
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
using BililiveRecorder.Core.Api.Model;
|
2021-12-19 21:10:34 +08:00
|
|
|
using BililiveRecorder.Core.Config.V3;
|
2021-02-23 18:03:37 +08:00
|
|
|
using Newtonsoft.Json;
|
2022-05-06 19:36:40 +08:00
|
|
|
using Newtonsoft.Json.Linq;
|
2021-02-23 18:03:37 +08:00
|
|
|
|
|
|
|
namespace BililiveRecorder.Core.Api.Http
|
|
|
|
{
|
2023-07-16 16:01:50 +08:00
|
|
|
internal class HttpApiClient : IApiClient, IDanmakuServerApiClient, ICookieTester
|
2021-02-23 18:03:37 +08:00
|
|
|
{
|
2022-08-25 18:42:36 +08:00
|
|
|
internal const string HttpHeaderAccept = "application/json, text/javascript, */*; q=0.01";
|
2023-08-19 19:00:48 +08:00
|
|
|
internal const string HttpHeaderAcceptLanguage = "zh-CN";
|
2022-08-25 18:42:36 +08:00
|
|
|
internal const string HttpHeaderReferer = "https://live.bilibili.com/";
|
|
|
|
internal const string HttpHeaderOrigin = "https://live.bilibili.com";
|
2023-11-05 17:58:41 +08:00
|
|
|
internal const string HttpHeaderUserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36";
|
2023-07-16 16:01:50 +08:00
|
|
|
private static readonly Regex matchCookieUidRegex = new Regex(@"DedeUserID=(\d+?);", RegexOptions.Compiled);
|
|
|
|
private static readonly Regex matchCookieBuvid3Regex = new Regex(@"buvid3=(.+?);", RegexOptions.Compiled);
|
2023-07-04 08:34:11 +08:00
|
|
|
private long uid;
|
2023-07-16 16:01:50 +08:00
|
|
|
private string? buvid3;
|
2021-02-23 18:03:37 +08:00
|
|
|
|
|
|
|
private readonly GlobalConfig config;
|
2023-07-16 16:01:50 +08:00
|
|
|
private HttpClient client;
|
2021-02-23 18:03:37 +08:00
|
|
|
private bool disposedValue;
|
|
|
|
|
|
|
|
public HttpApiClient(GlobalConfig config)
|
|
|
|
{
|
|
|
|
this.config = config ?? throw new ArgumentNullException(nameof(config));
|
|
|
|
|
|
|
|
config.PropertyChanged += this.Config_PropertyChanged;
|
|
|
|
|
2023-07-16 16:01:50 +08:00
|
|
|
this.client = null!;
|
2022-08-25 19:23:18 +08:00
|
|
|
this.UpdateHttpClient();
|
2021-02-23 18:03:37 +08:00
|
|
|
}
|
|
|
|
|
2022-08-25 19:23:18 +08:00
|
|
|
private void UpdateHttpClient()
|
2021-02-23 18:03:37 +08:00
|
|
|
{
|
|
|
|
var client = new HttpClient(new HttpClientHandler
|
|
|
|
{
|
|
|
|
UseCookies = false,
|
|
|
|
UseDefaultCredentials = false,
|
|
|
|
})
|
|
|
|
{
|
2022-08-25 19:23:18 +08:00
|
|
|
Timeout = TimeSpan.FromMilliseconds(this.config.TimingApiTimeout)
|
2021-02-23 18:03:37 +08:00
|
|
|
};
|
|
|
|
var headers = client.DefaultRequestHeaders;
|
|
|
|
headers.Add("Accept", HttpHeaderAccept);
|
2023-08-19 19:00:48 +08:00
|
|
|
headers.Add("Accept-Language", HttpHeaderAcceptLanguage);
|
2021-02-23 18:03:37 +08:00
|
|
|
headers.Add("Origin", HttpHeaderOrigin);
|
|
|
|
headers.Add("Referer", HttpHeaderReferer);
|
|
|
|
headers.Add("User-Agent", HttpHeaderUserAgent);
|
|
|
|
|
|
|
|
var cookie_string = this.config.Cookie;
|
|
|
|
if (!string.IsNullOrWhiteSpace(cookie_string))
|
2023-07-15 14:55:17 +08:00
|
|
|
{
|
2021-02-23 18:03:37 +08:00
|
|
|
headers.Add("Cookie", cookie_string);
|
2023-08-24 23:48:11 +08:00
|
|
|
_ = long.TryParse(matchCookieUidRegex.Match(cookie_string).Groups[1].Value, out var uid);
|
2023-07-15 14:55:17 +08:00
|
|
|
this.uid = uid;
|
2023-08-19 19:00:48 +08:00
|
|
|
var buvid3 = matchCookieBuvid3Regex.Match(cookie_string).Groups[1].Value;
|
2023-07-16 16:01:50 +08:00
|
|
|
if (!string.IsNullOrWhiteSpace(buvid3))
|
|
|
|
this.buvid3 = buvid3;
|
|
|
|
else
|
|
|
|
this.buvid3 = null;
|
2023-07-15 14:55:17 +08:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
this.uid = 0;
|
2023-07-16 17:28:16 +08:00
|
|
|
this.buvid3 = null;
|
2023-07-15 14:55:17 +08:00
|
|
|
}
|
|
|
|
|
2023-07-16 16:01:50 +08:00
|
|
|
var old = Interlocked.Exchange(ref this.client, client);
|
2021-02-23 18:03:37 +08:00
|
|
|
old?.Dispose();
|
|
|
|
}
|
|
|
|
|
2023-08-24 23:08:56 +08:00
|
|
|
private void Config_PropertyChanged(object? sender, PropertyChangedEventArgs e)
|
2021-02-23 18:03:37 +08:00
|
|
|
{
|
2022-12-10 07:59:54 +08:00
|
|
|
if (e.PropertyName is (nameof(this.config.Cookie)) or (nameof(this.config.TimingApiTimeout)))
|
2022-08-25 19:23:18 +08:00
|
|
|
this.UpdateHttpClient();
|
2021-02-23 18:03:37 +08:00
|
|
|
}
|
|
|
|
|
2023-07-16 16:01:50 +08:00
|
|
|
private async Task<string> FetchAsTextAsync(string url)
|
2021-02-23 18:03:37 +08:00
|
|
|
{
|
2023-07-16 16:01:50 +08:00
|
|
|
var resp = await this.client.GetAsync(url).ConfigureAwait(false);
|
2021-03-01 21:38:13 +08:00
|
|
|
|
|
|
|
if (resp.StatusCode == (HttpStatusCode)412)
|
|
|
|
throw new Http412Exception("Got HTTP Status 412 when requesting " + url);
|
|
|
|
|
|
|
|
resp.EnsureSuccessStatusCode();
|
|
|
|
|
2023-07-16 16:01:50 +08:00
|
|
|
return await resp.Content.ReadAsStringAsync().ConfigureAwait(false);
|
|
|
|
}
|
2021-03-01 21:38:13 +08:00
|
|
|
|
2023-07-16 16:01:50 +08:00
|
|
|
private async Task<BilibiliApiResponse<T>> FetchAsync<T>(string url) where T : class
|
|
|
|
{
|
|
|
|
var text = await this.FetchAsTextAsync(url).ConfigureAwait(false);
|
2021-02-23 18:03:37 +08:00
|
|
|
var obj = JsonConvert.DeserializeObject<BilibiliApiResponse<T>>(text);
|
2022-12-10 07:59:54 +08:00
|
|
|
return obj?.Code != 0 ? throw new BilibiliApiResponseCodeNotZeroException(obj?.Code, text) : obj;
|
2021-02-23 18:03:37 +08:00
|
|
|
}
|
|
|
|
|
2022-05-06 19:36:40 +08:00
|
|
|
public async Task<BilibiliApiResponse<RoomInfo>> GetRoomInfoAsync(int roomid)
|
2021-02-23 18:03:37 +08:00
|
|
|
{
|
|
|
|
if (this.disposedValue)
|
|
|
|
throw new ObjectDisposedException(nameof(HttpApiClient));
|
|
|
|
|
2022-05-06 19:36:40 +08:00
|
|
|
var url = $@"{this.config.LiveApiHost}/xlive/web-room/v1/index/getInfoByRoom?room_id={roomid}";
|
2021-02-23 18:03:37 +08:00
|
|
|
|
2023-07-16 16:01:50 +08:00
|
|
|
var text = await this.FetchAsTextAsync(url).ConfigureAwait(false);
|
2022-05-06 19:36:40 +08:00
|
|
|
|
|
|
|
var jobject = JObject.Parse(text);
|
|
|
|
|
|
|
|
var obj = jobject.ToObject<BilibiliApiResponse<RoomInfo>>();
|
|
|
|
if (obj?.Code != 0)
|
2022-12-10 07:59:54 +08:00
|
|
|
throw new BilibiliApiResponseCodeNotZeroException(obj?.Code, text);
|
2022-05-06 19:36:40 +08:00
|
|
|
|
|
|
|
obj.Data!.RawBilibiliApiJsonData = jobject["data"] as JObject;
|
|
|
|
|
|
|
|
return obj;
|
2021-02-23 18:03:37 +08:00
|
|
|
}
|
|
|
|
|
2021-07-09 19:48:39 +08:00
|
|
|
public Task<BilibiliApiResponse<RoomPlayInfo>> GetStreamUrlAsync(int roomid, int qn)
|
2021-02-23 18:03:37 +08:00
|
|
|
{
|
|
|
|
if (this.disposedValue)
|
|
|
|
throw new ObjectDisposedException(nameof(HttpApiClient));
|
|
|
|
|
2022-06-30 21:23:05 +08:00
|
|
|
var url = $@"{this.config.LiveApiHost}/xlive/web-room/v2/index/getRoomPlayInfo?room_id={roomid}&protocol=0,1&format=0,1,2&codec=0,1&qn={qn}&platform=web&ptype=8&dolby=5&panorama=1";
|
2023-07-16 16:01:50 +08:00
|
|
|
return this.FetchAsync<RoomPlayInfo>(url);
|
|
|
|
}
|
|
|
|
|
|
|
|
public async Task<(bool, string)> TestCookieAsync()
|
|
|
|
{
|
|
|
|
// 需要测试 cookie 的情况不需要风控和失败检测
|
|
|
|
var resp = await this.client.GetStringAsync("https://api.live.bilibili.com/xlive/web-ucenter/user/get_user_info").ConfigureAwait(false);
|
|
|
|
var jo = JObject.Parse(resp);
|
|
|
|
if (jo["code"]?.ToObject<int>() != 0)
|
|
|
|
return (false, $"Response:\n{resp}");
|
|
|
|
|
2023-08-19 19:00:48 +08:00
|
|
|
var message = $@"User: {jo["data"]?["uname"]?.ToObject<string>()}
|
2023-07-16 16:01:50 +08:00
|
|
|
UID (from API response): {jo["data"]?["uid"]?.ToObject<string>()}
|
|
|
|
UID (from Cookie): {this.GetUid()}
|
|
|
|
BUVID3 (from Cookie): {this.GetBuvid3()}";
|
|
|
|
return (true, message);
|
2021-02-23 18:03:37 +08:00
|
|
|
}
|
|
|
|
|
2023-07-04 08:34:11 +08:00
|
|
|
public long GetUid() => this.uid;
|
|
|
|
|
2023-07-16 16:01:50 +08:00
|
|
|
public string? GetBuvid3() => this.buvid3;
|
|
|
|
|
2021-02-23 18:03:37 +08:00
|
|
|
public Task<BilibiliApiResponse<DanmuInfo>> GetDanmakuServerAsync(int roomid)
|
|
|
|
{
|
|
|
|
if (this.disposedValue)
|
|
|
|
throw new ObjectDisposedException(nameof(HttpApiClient));
|
|
|
|
|
2021-04-15 19:13:19 +08:00
|
|
|
var url = $@"{this.config.LiveApiHost}/xlive/web-room/v1/index/getDanmuInfo?id={roomid}&type=0";
|
2023-07-16 16:01:50 +08:00
|
|
|
return this.FetchAsync<DanmuInfo>(url);
|
2021-02-23 18:03:37 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
protected virtual void Dispose(bool disposing)
|
|
|
|
{
|
|
|
|
if (!this.disposedValue)
|
|
|
|
{
|
|
|
|
if (disposing)
|
|
|
|
{
|
|
|
|
// dispose managed state (managed objects)
|
|
|
|
this.config.PropertyChanged -= this.Config_PropertyChanged;
|
2023-07-16 16:01:50 +08:00
|
|
|
this.client.Dispose();
|
2021-02-23 18:03:37 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// free unmanaged resources (unmanaged objects) and override finalizer
|
|
|
|
// set large fields to null
|
|
|
|
this.disposedValue = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources
|
|
|
|
// ~HttpApiClient()
|
|
|
|
// {
|
|
|
|
// // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
|
|
|
// Dispose(disposing: false);
|
|
|
|
// }
|
|
|
|
|
|
|
|
public void Dispose()
|
|
|
|
{
|
|
|
|
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
|
|
|
this.Dispose(disposing: true);
|
|
|
|
GC.SuppressFinalize(this);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|