mirror of
https://github.com/BililiveRecorder/BililiveRecorder.git
synced 2024-11-15 19:22:19 +08:00
feat(core): connect to danmaku server with buvid3 from cookie & refactor HttpApiClient (#507)
* feat(core): connect to danmaku server with buvid3 * make buvid3 nullable & add json serializer setting * fix test * fix regex & make regex static * remove anonHttpClient * remove MainHttpClient * split FetchAsTextAsync * GetAnonymousCookieAsync * use template string in TestCookieAsync * fix test * background get anonymous cookie * make jsonSerializerSettings static * fix uid parse logic * fix buvid match * remove GetAnonCookie * restore merge typo * fix comment * rename ICookieTester
This commit is contained in:
parent
a073e5fe4d
commit
adc91cc4f3
|
@ -33,6 +33,8 @@ namespace BililiveRecorder.Core.Api.Danmaku
|
|||
|
||||
public Func<string, string?>? BeforeHandshake { get; set; } = null;
|
||||
|
||||
private static readonly JsonSerializerSettings jsonSerializerSettings = new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore };
|
||||
|
||||
public DanmakuClient(IDanmakuServerApiClient apiClient, ILogger logger)
|
||||
{
|
||||
this.apiClient = apiClient ?? throw new ArgumentNullException(nameof(apiClient));
|
||||
|
@ -98,7 +100,7 @@ namespace BililiveRecorder.Core.Api.Danmaku
|
|||
|
||||
this.danmakuTransport = transport;
|
||||
|
||||
await this.SendHelloAsync(roomid, this.apiClient.GetUid(), danmakuServerInfo.Token ?? string.Empty).ConfigureAwait(false);
|
||||
await this.SendHelloAsync(roomid, this.apiClient.GetUid(), this.apiClient.GetBuvid3(), danmakuServerInfo.Token ?? string.Empty).ConfigureAwait(false);
|
||||
await this.SendPingAsync().ConfigureAwait(false);
|
||||
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
|
@ -213,17 +215,18 @@ namespace BililiveRecorder.Core.Api.Danmaku
|
|||
|
||||
#region Send
|
||||
|
||||
private Task SendHelloAsync(int roomid, long uid, string token)
|
||||
private Task SendHelloAsync(int roomid, long uid, string? buvid, string token)
|
||||
{
|
||||
var body = JsonConvert.SerializeObject(new
|
||||
{
|
||||
uid = uid,
|
||||
roomid = roomid,
|
||||
uid,
|
||||
roomid,
|
||||
protover = 0,
|
||||
buvid,
|
||||
platform = "web",
|
||||
type = 2,
|
||||
key = token,
|
||||
}, Formatting.None);
|
||||
}, Formatting.None, jsonSerializerSettings);
|
||||
|
||||
if (this.BeforeHandshake is { } func)
|
||||
{
|
||||
|
|
|
@ -12,40 +12,29 @@ using Newtonsoft.Json.Linq;
|
|||
|
||||
namespace BililiveRecorder.Core.Api.Http
|
||||
{
|
||||
internal class HttpApiClient : IApiClient, IDanmakuServerApiClient, IHttpClientAccessor
|
||||
internal class HttpApiClient : IApiClient, IDanmakuServerApiClient, ICookieTester
|
||||
{
|
||||
internal const string HttpHeaderAccept = "application/json, text/javascript, */*; q=0.01";
|
||||
internal const string HttpHeaderReferer = "https://live.bilibili.com/";
|
||||
internal const string HttpHeaderOrigin = "https://live.bilibili.com";
|
||||
internal const string HttpHeaderUserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36";
|
||||
private readonly Regex matchCookieUidRegex = new Regex(@"DedeUserID=(\d+?);", RegexOptions.Compiled);
|
||||
private static readonly Regex matchCookieUidRegex = new Regex(@"DedeUserID=(\d+?);", RegexOptions.Compiled);
|
||||
private static readonly Regex matchCookieBuvid3Regex = new Regex(@"buvid3=(.+?);", RegexOptions.Compiled);
|
||||
private long uid;
|
||||
private string? buvid3;
|
||||
|
||||
private readonly GlobalConfig config;
|
||||
private readonly HttpClient anonClient;
|
||||
private HttpClient mainClient;
|
||||
private HttpClient client;
|
||||
private bool disposedValue;
|
||||
|
||||
public HttpClient MainHttpClient => this.mainClient;
|
||||
|
||||
public HttpApiClient(GlobalConfig config)
|
||||
{
|
||||
this.config = config ?? throw new ArgumentNullException(nameof(config));
|
||||
|
||||
config.PropertyChanged += this.Config_PropertyChanged;
|
||||
|
||||
this.mainClient = null!;
|
||||
this.client = null!;
|
||||
this.UpdateHttpClient();
|
||||
|
||||
this.anonClient = new HttpClient
|
||||
{
|
||||
Timeout = TimeSpan.FromMilliseconds(config.TimingApiTimeout)
|
||||
};
|
||||
var headers = this.anonClient.DefaultRequestHeaders;
|
||||
headers.Add("Accept", HttpHeaderAccept);
|
||||
headers.Add("Origin", HttpHeaderOrigin);
|
||||
headers.Add("Referer", HttpHeaderReferer);
|
||||
headers.Add("User-Agent", HttpHeaderUserAgent);
|
||||
}
|
||||
|
||||
private void UpdateHttpClient()
|
||||
|
@ -68,16 +57,20 @@ namespace BililiveRecorder.Core.Api.Http
|
|||
if (!string.IsNullOrWhiteSpace(cookie_string))
|
||||
{
|
||||
headers.Add("Cookie", cookie_string);
|
||||
|
||||
long.TryParse(this.matchCookieUidRegex.Match(cookie_string).Groups[1].Value, out var uid);
|
||||
long.TryParse(matchCookieUidRegex.Match(cookie_string).Groups[1].Value, out var uid);
|
||||
this.uid = uid;
|
||||
string buvid3 = matchCookieBuvid3Regex.Match(cookie_string).Groups[1].Value;
|
||||
if (!string.IsNullOrWhiteSpace(buvid3))
|
||||
this.buvid3 = buvid3;
|
||||
else
|
||||
this.buvid3 = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.uid = 0;
|
||||
}
|
||||
|
||||
var old = Interlocked.Exchange(ref this.mainClient, client);
|
||||
var old = Interlocked.Exchange(ref this.client, client);
|
||||
old?.Dispose();
|
||||
}
|
||||
|
||||
|
@ -87,19 +80,21 @@ namespace BililiveRecorder.Core.Api.Http
|
|||
this.UpdateHttpClient();
|
||||
}
|
||||
|
||||
private static async Task<BilibiliApiResponse<T>> FetchAsync<T>(HttpClient client, string url) where T : class
|
||||
private async Task<string> FetchAsTextAsync(string url)
|
||||
{
|
||||
// 记得 GetRoomInfoAsync 里复制了一份这里的代码,以后修改记得一起改了
|
||||
|
||||
var resp = await client.GetAsync(url).ConfigureAwait(false);
|
||||
var resp = await this.client.GetAsync(url).ConfigureAwait(false);
|
||||
|
||||
if (resp.StatusCode == (HttpStatusCode)412)
|
||||
throw new Http412Exception("Got HTTP Status 412 when requesting " + url);
|
||||
|
||||
resp.EnsureSuccessStatusCode();
|
||||
|
||||
var text = await resp.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||
return await resp.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async Task<BilibiliApiResponse<T>> FetchAsync<T>(string url) where T : class
|
||||
{
|
||||
var text = await this.FetchAsTextAsync(url).ConfigureAwait(false);
|
||||
var obj = JsonConvert.DeserializeObject<BilibiliApiResponse<T>>(text);
|
||||
return obj?.Code != 0 ? throw new BilibiliApiResponseCodeNotZeroException(obj?.Code, text) : obj;
|
||||
}
|
||||
|
@ -111,18 +106,7 @@ namespace BililiveRecorder.Core.Api.Http
|
|||
|
||||
var url = $@"{this.config.LiveApiHost}/xlive/web-room/v1/index/getInfoByRoom?room_id={roomid}";
|
||||
|
||||
// return FetchAsync<RoomInfo>(this.mainClient, url);
|
||||
// 下面的代码是从 FetchAsync 里复制修改的
|
||||
// 以后如果修改 FetchAsync 记得把这里也跟着改了
|
||||
|
||||
var resp = await this.mainClient.GetAsync(url).ConfigureAwait(false);
|
||||
|
||||
if (resp.StatusCode == (HttpStatusCode)412)
|
||||
throw new Http412Exception("Got HTTP Status 412 when requesting " + url);
|
||||
|
||||
resp.EnsureSuccessStatusCode();
|
||||
|
||||
var text = await resp.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||
var text = await this.FetchAsTextAsync(url).ConfigureAwait(false);
|
||||
|
||||
var jobject = JObject.Parse(text);
|
||||
|
||||
|
@ -141,18 +125,35 @@ namespace BililiveRecorder.Core.Api.Http
|
|||
throw new ObjectDisposedException(nameof(HttpApiClient));
|
||||
|
||||
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";
|
||||
return FetchAsync<RoomPlayInfo>(this.mainClient, url);
|
||||
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}");
|
||||
|
||||
string message = $@"User: {jo["data"]?["uname"]?.ToObject<string>()}
|
||||
UID (from API response): {jo["data"]?["uid"]?.ToObject<string>()}
|
||||
UID (from Cookie): {this.GetUid()}
|
||||
BUVID3 (from Cookie): {this.GetBuvid3()}";
|
||||
return (true, message);
|
||||
}
|
||||
|
||||
public long GetUid() => this.uid;
|
||||
|
||||
public string? GetBuvid3() => this.buvid3;
|
||||
|
||||
public Task<BilibiliApiResponse<DanmuInfo>> GetDanmakuServerAsync(int roomid)
|
||||
{
|
||||
if (this.disposedValue)
|
||||
throw new ObjectDisposedException(nameof(HttpApiClient));
|
||||
|
||||
var url = $@"{this.config.LiveApiHost}/xlive/web-room/v1/index/getDanmuInfo?id={roomid}&type=0";
|
||||
return FetchAsync<DanmuInfo>(this.mainClient, url);
|
||||
return this.FetchAsync<DanmuInfo>(url);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
|
@ -163,8 +164,7 @@ namespace BililiveRecorder.Core.Api.Http
|
|||
{
|
||||
// dispose managed state (managed objects)
|
||||
this.config.PropertyChanged -= this.Config_PropertyChanged;
|
||||
this.mainClient.Dispose();
|
||||
this.anonClient.Dispose();
|
||||
this.client.Dispose();
|
||||
}
|
||||
|
||||
// free unmanaged resources (unmanaged objects) and override finalizer
|
||||
|
|
|
@ -7,6 +7,7 @@ namespace BililiveRecorder.Core.Api
|
|||
internal interface IDanmakuServerApiClient : IDisposable
|
||||
{
|
||||
long GetUid();
|
||||
string? GetBuvid3();
|
||||
Task<BilibiliApiResponse<DanmuInfo>> GetDanmakuServerAsync(int roomid);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BililiveRecorder.Core.Api
|
||||
{
|
||||
public interface IHttpClientAccessor
|
||||
public interface ICookieTester
|
||||
{
|
||||
HttpClient MainHttpClient { get; }
|
||||
long GetUid();
|
||||
Task<(bool, string)> TestCookieAsync();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ namespace BililiveRecorder.Core.Api
|
|||
}
|
||||
|
||||
public long GetUid() => this.client.GetUid();
|
||||
public string? GetBuvid3() => this.client.GetBuvid3();
|
||||
|
||||
public async Task<BilibiliApiResponse<DanmuInfo>> GetDanmakuServerAsync(int roomid) => await this.policies
|
||||
.Get<IAsyncPolicy>(PolicyNames.PolicyDanmakuApiRequestAsync)
|
||||
|
|
|
@ -37,7 +37,7 @@ namespace BililiveRecorder.DependencyInjection
|
|||
|
||||
public static IServiceCollection AddRecorderApiClients(this IServiceCollection services) => services
|
||||
.AddSingleton<HttpApiClient>()
|
||||
.AddSingleton<IHttpClientAccessor>(sp => sp.GetRequiredService<HttpApiClient>())
|
||||
.AddSingleton<ICookieTester>(sp => sp.GetRequiredService<HttpApiClient>())
|
||||
.AddSingleton<PolicyWrappedApiClient<HttpApiClient>>()
|
||||
.AddSingleton<IApiClient>(sp => sp.GetRequiredService<PolicyWrappedApiClient<HttpApiClient>>())
|
||||
.AddSingleton<IDanmakuServerApiClient>(sp => sp.GetRequiredService<PolicyWrappedApiClient<HttpApiClient>>())
|
||||
|
|
|
@ -40,10 +40,10 @@
|
|||
<StackPanel Orientation="Vertical">
|
||||
<TextBlock TextWrapping="Wrap">
|
||||
建议使用小号;软件开发者不对账号发生的任何事情负责。<LineBreak/>
|
||||
账号 UID 是直接从 Cookie 中的 DedeUserID 读取的,连接弹幕服务器时会用。<LineBreak/>
|
||||
账号 UID 和 BUVID3 是直接从 Cookie 中的 DedeUserID 和 buvid3 读取的,连接弹幕服务器时会用。<LineBreak/>
|
||||
录播姬没有主动断开重连弹幕服务器的功能,如果你设置 Cookie 的目的是以登录状态连接弹幕服务器,建议修改设置后重启录播姬。<LineBreak/>
|
||||
Alt account highly recommended; developers are not responsible for anything happened to your account.<LineBreak/>
|
||||
Account UID is read directly from DedeUserID in Cookie, and will be used when connecting to the danmaku server.<LineBreak/>
|
||||
Account UID and BUVID3 is read directly from DedeUserID and buvid3 in Cookie, and will be used when connecting to the danmaku server.<LineBreak/>
|
||||
BililiveRecorder does not have the ability to reconnect to the danmaku server. If you set Cookie to connect to the danmaku server in logged-in state, it is recommended to restart BililiveRecorder after changing the setting.
|
||||
</TextBlock>
|
||||
<c:SettingWithDefault IsSettingNotUsingDefault="{Binding HasCookie}">
|
||||
|
|
|
@ -17,10 +17,10 @@ namespace BililiveRecorder.WPF.Pages
|
|||
public partial class AdvancedSettingsPage
|
||||
{
|
||||
private static readonly ILogger logger = Log.ForContext<AdvancedSettingsPage>();
|
||||
private readonly IHttpClientAccessor? httpApiClient;
|
||||
private readonly ICookieTester? httpApiClient;
|
||||
private readonly UserScriptRunner? userScriptRunner;
|
||||
|
||||
public AdvancedSettingsPage(IHttpClientAccessor? httpApiClient, UserScriptRunner? userScriptRunner)
|
||||
public AdvancedSettingsPage(ICookieTester? httpApiClient, UserScriptRunner? userScriptRunner)
|
||||
{
|
||||
this.InitializeComponent();
|
||||
this.httpApiClient = httpApiClient;
|
||||
|
@ -29,7 +29,7 @@ namespace BililiveRecorder.WPF.Pages
|
|||
|
||||
public AdvancedSettingsPage()
|
||||
: this(
|
||||
(IHttpClientAccessor?)(RootPage.ServiceProvider?.GetService(typeof(IHttpClientAccessor))),
|
||||
(ICookieTester?)(RootPage.ServiceProvider?.GetService(typeof(ICookieTester))),
|
||||
(UserScriptRunner?)(RootPage.ServiceProvider?.GetService(typeof(UserScriptRunner)))
|
||||
)
|
||||
{ }
|
||||
|
@ -66,29 +66,18 @@ namespace BililiveRecorder.WPF.Pages
|
|||
|
||||
private async Task TestCookieAsync()
|
||||
{
|
||||
bool succeed;
|
||||
string message;
|
||||
|
||||
if (this.httpApiClient is null)
|
||||
{
|
||||
MessageBox.Show("No Http Client Available", "Cookie Test - Failed", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
return;
|
||||
}
|
||||
(succeed, message) = (false, "No Http Client Available");
|
||||
else
|
||||
(succeed, message) = await this.httpApiClient.TestCookieAsync().ConfigureAwait(false);
|
||||
|
||||
var resp = await this.httpApiClient.MainHttpClient.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)
|
||||
{
|
||||
MessageBox.Show("Response:\n" + resp, "Cookie Test - Failed", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
var b = new StringBuilder();
|
||||
b.Append("User: ");
|
||||
b.Append(jo["data"]?["uname"]?.ToObject<string>());
|
||||
b.Append("\nUID (from API response): ");
|
||||
b.Append(jo["data"]?["uid"]?.ToObject<string>());
|
||||
b.Append("\nUID (from Cookie): ");
|
||||
b.Append(this.httpApiClient.GetUid());
|
||||
|
||||
MessageBox.Show(b.ToString(), "Cookie Test - Successed", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
if (succeed)
|
||||
MessageBox.Show(message, "Cookie Test - Succeed", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
else
|
||||
MessageBox.Show(message, "Cookie Test - Failed", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
}
|
||||
|
||||
private void TestScript_Click(object sender, RoutedEventArgs e)
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("BililiveRecorder.Core.UnitTests")]
|
||||
namespace BililiveRecorder.Core.Api
|
||||
{
|
||||
public interface IHttpClientAccessor
|
||||
public interface ICookieTester
|
||||
{
|
||||
System.Net.Http.HttpClient MainHttpClient { get; }
|
||||
long GetUid();
|
||||
System.Threading.Tasks.Task<System.ValueTuple<bool, string>> TestCookieAsync();
|
||||
}
|
||||
}
|
||||
namespace BililiveRecorder.Core.Config
|
||||
|
|
Loading…
Reference in New Issue
Block a user