diff --git a/DownKyi.Core/BiliApi/Bangumi/Models/BangumiEpisode.cs b/DownKyi.Core/BiliApi/Bangumi/Models/BangumiEpisode.cs index 348f3ca..89bee8c 100644 --- a/DownKyi.Core/BiliApi/Bangumi/Models/BangumiEpisode.cs +++ b/DownKyi.Core/BiliApi/Bangumi/Models/BangumiEpisode.cs @@ -13,6 +13,7 @@ public class BangumiEpisode : BaseModel // badge_type [JsonProperty("bvid")] public string Bvid { get; set; } [JsonProperty("cid")] public long Cid { get; set; } + [JsonProperty("ep_id")] public long EpisodeId { get; set; } [JsonProperty("cover")] public string Cover { get; set; } [JsonProperty("dimension")] public Dimension Dimension { get; set; } [JsonProperty("duration")] public long Duration { get; set; } diff --git a/DownKyi.Core/BiliApi/VideoStream/Models/PlayView.cs b/DownKyi.Core/BiliApi/VideoStream/Models/PlayView.cs new file mode 100644 index 0000000..15e522c --- /dev/null +++ b/DownKyi.Core/BiliApi/VideoStream/Models/PlayView.cs @@ -0,0 +1,19 @@ +using Newtonsoft.Json; + +namespace DownKyi.Core.BiliApi.VideoStream.Models; + +public class PlayViewOrigin +{ + [JsonProperty("data")] public PlayView? Data { get; set; } +} + +public class PlayView +{ + [JsonProperty("video_info")] public PlayUrl VideoInfo { get; set; } = new(); + [JsonProperty("plugins")] public List Plugins { get; set; } = new(); +} + +public class PlayViewPlugin +{ + [JsonProperty("name")] public string Name { get; set; } = string.Empty; +} \ No newline at end of file diff --git a/DownKyi.Core/BiliApi/VideoStream/VideoStream.cs b/DownKyi.Core/BiliApi/VideoStream/VideoStream.cs index 6e1081e..9503d11 100644 --- a/DownKyi.Core/BiliApi/VideoStream/VideoStream.cs +++ b/DownKyi.Core/BiliApi/VideoStream/VideoStream.cs @@ -1,8 +1,10 @@ using System.Text.RegularExpressions; +using DownKyi.Core.BiliApi.Login; using DownKyi.Core.BiliApi.Models.Json; using DownKyi.Core.BiliApi.Sign; using DownKyi.Core.BiliApi.VideoStream.Models; using DownKyi.Core.Logging; +using DownKyi.Core.Storage; using Newtonsoft.Json; using Console = DownKyi.Core.Utils.Debugging.Console; @@ -116,7 +118,7 @@ public static class VideoStream /// /// /// - public static PlayUrl GetVideoPlayUrl(long avid, string bvid, long cid, int quality = 125) + public static PlayUrl? GetVideoPlayUrl(long avid, string bvid, long cid, int quality = 125) { var parameters = new Dictionary { @@ -153,7 +155,7 @@ public static class VideoStream /// /// /// - public static PlayUrl GetVideoPlayUrlWebPage(long avid, string bvid, long cid, int p) + public static PlayUrl? GetVideoPlayUrlWebPage(long avid, string bvid, long cid, int p) { var url = "https://www.bilibili.com/video"; if (bvid == string.Empty) @@ -174,32 +176,86 @@ public static class VideoStream return playUrl; } + // /// + // /// 获取番剧的视频流 + // /// + // /// + // /// + // /// + // /// + // /// + // public static PlayUrl GetBangumiPlayUrl(long avid, string bvid, long cid, int quality = 125) + // { + // var baseUrl = $"https://api.bilibili.com/pgc/player/web/playurl?cid={cid}&qn={quality}&fourk=1&fnver=0&fnval=4048"; + // string url; + // if (bvid != null) + // { + // url = $"{baseUrl}&bvid={bvid}"; + // } + // else if (avid > -1) + // { + // url = $"{baseUrl}&aid={avid}"; + // } + // else + // { + // return null; + // } + // + // return GetPlayUrl(url); + // } + /// /// 获取番剧的视频流 /// - /// - /// - /// + /// /// /// - public static PlayUrl GetBangumiPlayUrl(long avid, string bvid, long cid, int quality = 125) + public static PlayUrl? GetBangumiPlayUrl(long episodeId, int quality = 125) { - var baseUrl = $"https://api.bilibili.com/pgc/player/web/playurl?cid={cid}&qn={quality}&fourk=1&fnver=0&fnval=4048"; - string url; - if (bvid != null) + string? csrfToken = null; + if (File.Exists(StorageManager.GetLogin())) { - url = $"{baseUrl}&bvid={bvid}"; - } - else if (avid > -1) - { - url = $"{baseUrl}&aid={avid}"; - } - else - { - return null; + csrfToken = LoginHelper.GetLoginInfoCookies().First(cookie => cookie.Name == "bili_jct").Value; } - return GetPlayUrl(url); + var url = "https://api.bilibili.com/ogv/player/playview"; + if (csrfToken != null) url += $"?csrf={csrfToken}"; + var parameters = new Dictionary + { + { "scene", "normal" }, + { + "video_index", new Dictionary + { + { "bvid", null }, + { "cid", null }, + { "ogv_season_id", null }, + { "ogv_episode_id", episodeId }, + } + }, + { + "video_param", new Dictionary + { + { "qn", quality } + } + }, + { + "player_param", new Dictionary + { + { "fnver", 0 }, + { "fnval", 4048 }, + { "drm_tech_type", 2 }, + } + }, + { + "exp_info", new Dictionary + { + { "ogv_half_pay", true } + } + } + }; + + + return GetPlayUrlBangumi(url, parameters); } /// @@ -241,7 +297,7 @@ public static class VideoStream /// /// /// - private static PlayUrl GetPlayUrl(string url) + private static PlayUrl? GetPlayUrl(string url) { const string referer = "https://www.bilibili.com"; var response = WebClient.RequestWeb(url, referer); @@ -320,4 +376,26 @@ public static class VideoStream return null; } } + + private static PlayUrl? GetPlayUrlBangumi(string url, Dictionary parameters) + { + const string referer = "https://www.bilibili.com"; + var response = WebClient.RequestWeb(url, referer, "POST", parameters, json: true); + var playViewOrigin = JsonConvert.DeserializeObject(response); + var playViewOriginData = playViewOrigin?.Data; + if (playViewOriginData == null) + { + Console.PrintLine("GetPlayUrlBangumi()返回数据为空"); + LogManager.Error("GetPlayUrlBangumi()", new Exception("返回数据为空")); + return null; + } + + var notLoginPlugin = playViewOriginData.Plugins.Find(plugin => plugin.Name == "NoLoginDrmLimitPanel"); + if (notLoginPlugin != null) + { + throw new Exception("当前视频需要登录才能观看,请先登录B站账号。"); + } + + return playViewOriginData.VideoInfo; + } } \ No newline at end of file diff --git a/DownKyi.Core/BiliApi/WebClient.cs b/DownKyi.Core/BiliApi/WebClient.cs index 86ddfa6..3f2fc13 100644 --- a/DownKyi.Core/BiliApi/WebClient.cs +++ b/DownKyi.Core/BiliApi/WebClient.cs @@ -10,7 +10,7 @@ namespace DownKyi.Core.BiliApi; internal static class WebClient { - private static readonly HttpClient _httpClient; + private static readonly HttpClient HttpClient; private static string? _bvuid3 = string.Empty; private static string? _bvuid4 = string.Empty; @@ -49,9 +49,9 @@ internal static class WebClient break; } - _httpClient = new HttpClient(socketsHandler); - _httpClient.DefaultRequestHeaders.Add("User-Agent", SettingsManager.GetInstance().GetUserAgent()); - _httpClient.DefaultRequestHeaders.Add("accept-language", "zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7"); + HttpClient = new HttpClient(socketsHandler); + HttpClient.DefaultRequestHeaders.Add("User-Agent", SettingsManager.GetInstance().GetUserAgent()); + HttpClient.DefaultRequestHeaders.Add("accept-language", "zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7"); } internal class SpiOrigin @@ -76,7 +76,7 @@ internal static class WebClient _bvuid4 = spi?.Data?.Bvuid4; } - public static string RequestWeb(string url, string? referer = null, string method = "GET", Dictionary? parameters = null, int retry = 3) + public static string RequestWeb(string url, string? referer = null, string method = "GET", Dictionary? parameters = null, int retry = 3, bool json = false) { if (retry <= 0) { @@ -121,7 +121,14 @@ internal static class WebClient if (method == "POST" && parameters != null) { - request.Content = new FormUrlEncodedContent(parameters); + if (json) + { + request.Content = new StringContent(JsonSerializer.Serialize(parameters), System.Text.Encoding.UTF8, "application/json"); + } + else + { + request.Content = new FormUrlEncodedContent(parameters.Select(item => new KeyValuePair(item.Key, item.Value?.ToString() ?? ""))); + } } else if (parameters != null) { @@ -130,7 +137,7 @@ internal static class WebClient request.RequestUri = new Uri(url); } - var response = _httpClient.Send(request); + var response = HttpClient.Send(request); response.EnsureSuccessStatusCode(); using var reader = new StreamReader(response.Content.ReadAsStream()); @@ -151,21 +158,21 @@ internal static class WebClient } public static void DownloadFile(string url, string destFile, string? referer = null) - { + { using var fs = File.Create(destFile); using var stream = RequestStream(url, referer); - stream?.CopyTo(fs); + stream.CopyTo(fs); } - public static Stream? RequestStream(string url, string? referer = null, - string method = "GET") + public static Stream RequestStream(string url, string? referer = null, string method = "GET") { var request = new HttpRequestMessage(new HttpMethod(method), url); - + if (referer != null) { request.Headers.Referrer = new Uri(referer); } + if (!url.Contains("getLogin")) { request.Headers.Add("origin", "https://m.bilibili.com"); @@ -175,8 +182,8 @@ internal static class WebClient request.Headers.Add("cookie", cookies); } } - - var response = _httpClient.Send(request); + + var response = HttpClient.Send(request); response.EnsureSuccessStatusCode(); return response.Content.ReadAsStream(); } diff --git a/DownKyi/Services/BangumiInfoService.cs b/DownKyi/Services/BangumiInfoService.cs index ee99afb..2879692 100644 --- a/DownKyi/Services/BangumiInfoService.cs +++ b/DownKyi/Services/BangumiInfoService.cs @@ -105,7 +105,7 @@ public class BangumiInfoService : IInfoService Avid = episode.Aid, Bvid = episode.Bvid, Cid = episode.Cid, - EpisodeId = -1, + EpisodeId = episode.EpisodeId, FirstFrame = episode.Cover, Order = order, Name = name, @@ -199,7 +199,7 @@ public class BangumiInfoService : IInfoService Avid = episode.Aid, Bvid = episode.Bvid, Cid = episode.Cid, - EpisodeId = -1, + EpisodeId = episode.EpisodeId, FirstFrame = episode.Cover, Order = order, Name = name, @@ -255,7 +255,7 @@ public class BangumiInfoService : IInfoService /// public void GetVideoStream(VideoPage page) { - var playUrl = VideoStream.GetBangumiPlayUrl(page.Avid, page.Bvid, page.Cid); + var playUrl = VideoStream.GetBangumiPlayUrl(page.EpisodeId); Dispatcher.UIThread.Invoke(() => Utils.VideoPageInfo(playUrl, page)); } diff --git a/DownKyi/Services/Download/DownloadService.cs b/DownKyi/Services/Download/DownloadService.cs index acde4fb..966103e 100644 --- a/DownKyi/Services/Download/DownloadService.cs +++ b/DownKyi/Services/Download/DownloadService.cs @@ -418,8 +418,7 @@ public abstract class DownloadService }; break; case PlayStreamType.Bangumi: - downloading.PlayUrl ??= VideoStream.GetBangumiPlayUrl(downloading.DownloadBase.Avid, - downloading.DownloadBase.Bvid, downloading.DownloadBase.Cid); + downloading.PlayUrl ??= VideoStream.GetBangumiPlayUrl(downloading.DownloadBase.EpisodeId); break; case PlayStreamType.Cheese: downloading.PlayUrl ??= VideoStream.GetCheesePlayUrl(downloading.DownloadBase.Avid, diff --git a/DownKyi/ViewModels/Toolbox/ViewBiliHelperViewModel.cs b/DownKyi/ViewModels/Toolbox/ViewBiliHelperViewModel.cs index 1d38fd2..f7c6d2d 100644 --- a/DownKyi/ViewModels/Toolbox/ViewBiliHelperViewModel.cs +++ b/DownKyi/ViewModels/Toolbox/ViewBiliHelperViewModel.cs @@ -84,7 +84,7 @@ public class ViewBiliHelperViewModel : ViewModelBase return; } - await Task.Run(() => { Bvid = BvId.Av2Bv((ulong)avid); }); + await Task.Run(() => { Bvid = BvId.Av2Bv(avid); }); } // 输入bvid事件 diff --git a/DownKyi/ViewModels/ViewVideoDetailViewModel.cs b/DownKyi/ViewModels/ViewVideoDetailViewModel.cs index be35edc..01be6eb 100644 --- a/DownKyi/ViewModels/ViewVideoDetailViewModel.cs +++ b/DownKyi/ViewModels/ViewVideoDetailViewModel.cs @@ -190,7 +190,7 @@ public class ViewVideoDetailViewModel : ViewModelBase private DelegateCommand? _inputSearchCommand; - + public DelegateCommand InputSearchCommand => _inputSearchCommand ??= new DelegateCommand(ExecuteInputSearchCommand); @@ -260,6 +260,7 @@ public class ViewVideoDetailViewModel : ViewModelBase { Console.PrintLine("InputCommand()发生异常: {0}", e); LogManager.Error(Tag, e); + EventAggregator.GetEvent().Publish(e.Message); LoadingVisibility = false; ContentVisibility = false; @@ -334,13 +335,14 @@ public class ViewVideoDetailViewModel : ViewModelBase { return; } + var selectedSection = VideoSections.FirstOrDefault(x => x.IsSelected); - if (selectedSection?.VideoPages == null) + if (selectedSection?.VideoPages == null) { IsSelectAll = false; - return; + return; } - + var selectedPages = selectedSection.VideoPages .Where(x => x.IsSelected).ToList(); foreach (var page in selectedPages) @@ -348,7 +350,7 @@ public class ViewVideoDetailViewModel : ViewModelBase grid.SelectedItems.Add(page); } - IsSelectAll = selectedSection.VideoPages.Count > 0 && + IsSelectAll = selectedSection.VideoPages.Count > 0 && selectedPages.Count == selectedSection.VideoPages.Count; } @@ -369,7 +371,7 @@ public class ViewVideoDetailViewModel : ViewModelBase } var section = VideoSections.FirstOrDefault(item => item.IsSelected); - + if (section == null) { return; @@ -410,7 +412,7 @@ public class ViewVideoDetailViewModel : ViewModelBase // 解析视频流事件 private DelegateCommand? _parseCommand; - + public DelegateCommand ParseCommand => _parseCommand ??= new DelegateCommand(ExecuteParseCommand, CanExecuteParseCommand); /// @@ -439,6 +441,7 @@ public class ViewVideoDetailViewModel : ViewModelBase { Console.PrintLine("ParseCommand()发生异常: {0}", e); LogManager.Error(Tag, e); + EventAggregator.GetEvent().Publish(e.Message); LoadingVisibility = false; } @@ -558,6 +561,7 @@ public class ViewVideoDetailViewModel : ViewModelBase { Console.PrintLine("ParseCommand()发生异常: {0}", e); LogManager.Error(Tag, e); + EventAggregator.GetEvent().Publish(e.Message); LoadingVisibility = false; } @@ -609,8 +613,7 @@ public class ViewVideoDetailViewModel : ViewModelBase VideoSections.Clear(); CaCheVideoSections.Clear(); } - - + /// /// 更新页面的统一方法 @@ -624,8 +627,8 @@ public class ViewVideoDetailViewModel : ViewModelBase if (_infoService == null || refresh) { // 视频 - if (ParseEntrance.IsAvUrl(input) || ParseEntrance.IsBvUrl(input) - || ParseEntrance.IsAvId(input) || ParseEntrance.IsBvId(input)) + if (ParseEntrance.IsAvUrl(input) || ParseEntrance.IsBvUrl(input) + || ParseEntrance.IsAvId(input) || ParseEntrance.IsBvId(input)) { _infoService = new VideoInfoService(input); }