diff --git a/src/BiliLite.UWP/Assets/GeeTest/bili_gt.cshtml b/src/BiliLite.UWP/Assets/GeeTest/bili_gt.cshtml new file mode 100644 index 000000000..e40048d1c --- /dev/null +++ b/src/BiliLite.UWP/Assets/GeeTest/bili_gt.cshtml @@ -0,0 +1,421 @@ + + + + + 极验 + + + + + +
+

完成验证后继续登录

+
+ + + + + diff --git a/src/BiliLite.UWP/BiliLite.UWP.csproj b/src/BiliLite.UWP/BiliLite.UWP.csproj index 766c06eba..cd3bc778b 100644 --- a/src/BiliLite.UWP/BiliLite.UWP.csproj +++ b/src/BiliLite.UWP/BiliLite.UWP.csproj @@ -136,6 +136,12 @@ PackageReference + + AttentionButton.xaml + + + UserFollowingTagsFlyout.xaml + @@ -187,6 +193,9 @@ + + + @@ -196,6 +205,16 @@ + + + + + + + + + + @@ -628,7 +647,7 @@ - + @@ -876,6 +895,7 @@ + Designer @@ -895,6 +915,10 @@ MSBuild:Compile Designer + + Designer + MSBuild:Compile + MSBuild:Compile Designer @@ -903,6 +927,10 @@ Designer MSBuild:Compile + + Designer + MSBuild:Compile + Designer MSBuild:Compile diff --git a/src/BiliLite.UWP/Controls/AttentionButton.xaml b/src/BiliLite.UWP/Controls/AttentionButton.xaml new file mode 100644 index 000000000..371a056b2 --- /dev/null +++ b/src/BiliLite.UWP/Controls/AttentionButton.xaml @@ -0,0 +1,35 @@ + + + + 设置分组 + 取消关注 + + + + + + + + diff --git a/src/BiliLite.UWP/Controls/AttentionButton.xaml.cs b/src/BiliLite.UWP/Controls/AttentionButton.xaml.cs new file mode 100644 index 000000000..df1f09e0a --- /dev/null +++ b/src/BiliLite.UWP/Controls/AttentionButton.xaml.cs @@ -0,0 +1,61 @@ +using System.Threading.Tasks; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Controls.Primitives; +using BiliLite.ViewModels.User; +using Microsoft.Extensions.DependencyInjection; + +//https://go.microsoft.com/fwlink/?LinkId=234236 上介绍了“用户控件”项模板 + +namespace BiliLite.Controls +{ + public sealed partial class AttentionButton : UserControl + { + private readonly UserAttentionButtonViewModel m_viewModel; + + public AttentionButton() + { + m_viewModel = App.ServiceProvider.GetRequiredService(); + this.InitializeComponent(); + } + + public static readonly DependencyProperty AttentionProperty = + DependencyProperty.Register(nameof(Attention), typeof(int), typeof(AttentionButton), new PropertyMetadata(0)); + + public int Attention + { + get => m_viewModel.Attention; + set => m_viewModel.Attention = value; + } + + public static readonly DependencyProperty UserIdProperty = + DependencyProperty.Register(nameof(Attention), typeof(string), typeof(AttentionButton), new PropertyMetadata(default(string))); + + public string UserId + { + get => m_viewModel.UserId; + set => m_viewModel.UserId = value; + } + + public async Task AttentionUp() + { + await m_viewModel.AttentionUP(m_viewModel.UserId, 1); + } + + private void AttendedBtn_OnClick(object sender, RoutedEventArgs e) + { + var flyoutShowOptions = new FlyoutShowOptions() + { + Placement = FlyoutPlacementMode.Bottom + }; + AttentionFlyout.ShowAt(sender as DependencyObject, flyoutShowOptions); + } + + private async void SetFollowingTag_OnClick(object sender, RoutedEventArgs e) + { + if(!UserFollowingTagsFlyout.HasInit) + await UserFollowingTagsFlyout.Init(m_viewModel.UserId); + UserFollowingTagsFlyout.ShowAt(AttendedBtn); + } + } +} diff --git a/src/BiliLite.UWP/Controls/UserFollowingTagsFlyout.xaml b/src/BiliLite.UWP/Controls/UserFollowingTagsFlyout.xaml new file mode 100644 index 000000000..ef4cfc932 --- /dev/null +++ b/src/BiliLite.UWP/Controls/UserFollowingTagsFlyout.xaml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + diff --git a/src/BiliLite.UWP/Controls/UserFollowingTagsFlyout.xaml.cs b/src/BiliLite.UWP/Controls/UserFollowingTagsFlyout.xaml.cs new file mode 100644 index 000000000..bb8d29fab --- /dev/null +++ b/src/BiliLite.UWP/Controls/UserFollowingTagsFlyout.xaml.cs @@ -0,0 +1,49 @@ +using System.Threading.Tasks; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Controls.Primitives; +using BiliLite.ViewModels.User; +using Microsoft.Extensions.DependencyInjection; + +//https://go.microsoft.com/fwlink/?LinkId=234236 上介绍了“用户控件”项模板 + +namespace BiliLite.Controls +{ + public sealed partial class UserFollowingTagsFlyout : UserControl + { + private readonly UserFollowingTagsFlyoutViewModel m_viewModel; + + public UserFollowingTagsFlyout() + { + m_viewModel = App.ServiceProvider.GetRequiredService(); + this.InitializeComponent(); + } + + public bool HasInit { get; set; } + + private void FollowingTagFlyout_OnClosed(object sender, object e) + { + m_viewModel.CancelSaveFollowingTagUser(); + } + + private async void SaveFollowingTagUser_OnClick(object sender, RoutedEventArgs e) + { + await m_viewModel.SaveFollowingTagUser(); + FollowingTagFlyout.Hide(); + } + + public async Task Init(string userId) + { + await m_viewModel.Init(userId); + HasInit = true; + } + + public void ShowAt(DependencyObject target) + { + ContextFlyout.ShowAt(target, new FlyoutShowOptions() + { + Placement = FlyoutPlacementMode.Bottom + }); + } + } +} diff --git a/src/BiliLite.UWP/Dialogs/LoginDialog.xaml b/src/BiliLite.UWP/Dialogs/LoginDialog.xaml index 704b7fd99..3ec5d79fe 100644 --- a/src/BiliLite.UWP/Dialogs/LoginDialog.xaml +++ b/src/BiliLite.UWP/Dialogs/LoginDialog.xaml @@ -10,6 +10,7 @@ PrimaryButtonText="登录" SecondaryButtonText="取消" xmlns:user="using:BiliLite.Modules.User" + xmlns:controls="using:Microsoft.UI.Xaml.Controls" PrimaryButtonClick="ContentDialog_PrimaryButtonClick" IsPrimaryButtonEnabled="{x:Bind Path=loginVM.PrimaryButtonEnable,Mode=OneWay}" SecondaryButtonClick="ContentDialog_SecondaryButtonClick"> @@ -142,7 +143,7 @@ - + diff --git a/src/BiliLite.UWP/Dialogs/LoginDialog.xaml.cs b/src/BiliLite.UWP/Dialogs/LoginDialog.xaml.cs index db76c2887..be3192d60 100644 --- a/src/BiliLite.UWP/Dialogs/LoginDialog.xaml.cs +++ b/src/BiliLite.UWP/Dialogs/LoginDialog.xaml.cs @@ -5,8 +5,12 @@ using Newtonsoft.Json.Linq; using System; using System.Text.RegularExpressions; +using System.Threading.Tasks; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Controls; +using Microsoft.Web.WebView2.Core; +using Windows.Storage; // https://go.microsoft.com/fwlink/?LinkId=234238 上介绍了“内容对话框”项模板 @@ -50,9 +54,17 @@ private void _biliapp_ValidateLoginEvent(object sender, string e) loginVM.ValidateLogin(JObject.Parse(e)); } - private void LoginVM_OpenWebView(object sender, Uri e) + + private async void LoginVM_OpenWebView(object sender, Uri e) { - webView.Source = e; + await InitWebView2(); + var templateText = await FileIO.ReadTextAsync( + await StorageFile.GetFileFromApplicationUriAsync(new Uri("ms-appx:///Assets/GeeTest/bili_gt.cshtml"))); + + var result = templateText.Replace("@Model.Url", e.AbsoluteUri); + + //webView.Source = e; + webView.NavigateToString(result); } private void SMSLoginDialog_Loaded(object sender, RoutedEventArgs e) @@ -77,6 +89,11 @@ private void ContentDialog_SecondaryButtonClick(ContentDialog sender, ContentDia } + private async Task InitWebView2() + { + await webView.EnsureCoreWebView2Async(); + } + private void txt_Password_GotFocus(object sender, RoutedEventArgs e) { @@ -88,20 +105,20 @@ private void txt_Password_LostFocus(object sender, RoutedEventArgs e) hide.Visibility = Visibility.Collapsed; } - private async void webView_NavigationStarting(WebView sender, WebViewNavigationStartingEventArgs args) + private async void WebView_OnNavigationStarting(WebView2 sender, CoreWebView2NavigationStartingEventArgs args) { - if (args.Uri.AbsoluteUri.Contains("access_key=")) + if (args.Uri.Contains("access_key=")) { - var access = Regex.Match(args.Uri.AbsoluteUri, "access_key=(.*?)&").Groups[1].Value; - var mid = Regex.Match(args.Uri.AbsoluteUri, "mid=(.*?)&").Groups[1].Value; + var access = Regex.Match(args.Uri, "access_key=(.*?)&").Groups[1].Value; + var mid = Regex.Match(args.Uri, "mid=(.*?)&").Groups[1].Value; var appKey = SettingConstants.Account.DefaultLoginAppKeySecret; await loginVM.account.SaveLogin(access, "", 0, long.Parse(mid), null, null, appKey); this.Hide(); return; } - if (args.Uri.AbsoluteUri.Contains("geetest.result")) + if (args.Uri.Contains("geetest.result")) { - var success = (Regex.Match(args.Uri.AbsoluteUri, @"success=(\d)&").Groups[1].Value).ToInt32(); + var success = (Regex.Match(args.Uri, @"success=(\d)&").Groups[1].Value).ToInt32(); if (success == 0) { //验证失败 @@ -112,10 +129,10 @@ private async void webView_NavigationStarting(WebView sender, WebViewNavigationS { webView.Visibility = Visibility.Collapsed; //验证成功 - var challenge = Regex.Match(args.Uri.AbsoluteUri, "geetest_challenge=(.*?)&").Groups[1].Value; - var validate = Regex.Match(args.Uri.AbsoluteUri, "geetest_validate=(.*?)&").Groups[1].Value; - var seccode = Regex.Match(args.Uri.AbsoluteUri, "geetest_seccode=(.*?)&").Groups[1].Value; - var recaptcha_token = Regex.Match(args.Uri.AbsoluteUri, "recaptcha_token=(.*?)&").Groups[1].Value; + var challenge = Regex.Match(args.Uri, "geetest_challenge=(.*?)&").Groups[1].Value; + var validate = Regex.Match(args.Uri, "geetest_validate=(.*?)&").Groups[1].Value; + var seccode = Regex.Match(args.Uri, "geetest_seccode=(.*?)&").Groups[1].Value; + var recaptcha_token = Regex.Match(args.Uri, "recaptcha_token=(.*?)&").Groups[1].Value; loginVM.HandleGeetestSuccess(seccode, validate, challenge, recaptcha_token); } else if (success == 2) @@ -129,8 +146,8 @@ private async void webView_NavigationStarting(WebView sender, WebViewNavigationS } try { - this.webView.AddWebAllowedObject("biliapp", _biliapp); - this.webView.AddWebAllowedObject("secure", _secure); + //this.webView.AddWebAllowedObject("biliapp", _biliapp); + //this.webView.AddWebAllowedObject("secure", _secure); } catch (Exception ex) { @@ -138,15 +155,15 @@ private async void webView_NavigationStarting(WebView sender, WebViewNavigationS } } - private async void WebView_NavigationCompleted(WebView sender, WebViewNavigationCompletedEventArgs args) + private async void WebView_OnNavigationCompleted(WebView2 sender, CoreWebView2NavigationCompletedEventArgs args) { - if (args.Uri.AbsoluteUri == "https://passport.bilibili.com/ajax/miniLogin/redirect" || args.Uri.AbsoluteUri == "https://www.bilibili.com/") + if (sender.Source.AbsoluteUri == "https://passport.bilibili.com/ajax/miniLogin/redirect" || sender.Source.AbsoluteUri == "https://www.bilibili.com/") { var results = await $"https://passport.bilibili.com/login/app/third?appkey=&api=http%3A%2F%2Flink.acg.tv%2Fforum.php&sign=67ec798004373253d60114caaad89a8c".GetString(); var obj = JObject.Parse(results); if (obj["code"].ToInt32() == 0) { - webView.Navigate(new Uri(obj["data"]["confirm_uri"].ToString())); + webView.Source = new Uri(obj["data"]["confirm_uri"].ToString()); } else { diff --git a/src/BiliLite.UWP/Extensions/MapperExtensions.cs b/src/BiliLite.UWP/Extensions/MapperExtensions.cs index f447c710e..117495c1f 100644 --- a/src/BiliLite.UWP/Extensions/MapperExtensions.cs +++ b/src/BiliLite.UWP/Extensions/MapperExtensions.cs @@ -8,19 +8,23 @@ using BiliLite.Models.Common; using BiliLite.Models.Common.Anime; using BiliLite.Models.Common.Comment; +using BiliLite.Models.Common.Download; using BiliLite.Models.Common.Dynamic; using BiliLite.Models.Common.Home; using BiliLite.Models.Common.Season; using BiliLite.Models.Common.User; +using BiliLite.Models.Common.User.UserDetails; using BiliLite.Models.Common.UserDynamic; using BiliLite.Models.Common.Video.Detail; using BiliLite.Models.Download; using BiliLite.Models.Dynamic; +using BiliLite.Modules.User.UserDetail; using BiliLite.Services; using BiliLite.ViewModels.Comment; using BiliLite.ViewModels.Download; using BiliLite.ViewModels.Home; using BiliLite.ViewModels.Season; +using BiliLite.ViewModels.User; using BiliLite.ViewModels.UserDynamic; using BiliLite.ViewModels.Video; using Microsoft.Extensions.DependencyInjection; @@ -48,6 +52,13 @@ public static IServiceCollection AddMapper(this IServiceCollection services) expression.CreateMap(); expression.CreateMap(); expression.CreateMap(); + expression.CreateMap(); + expression.CreateMap(); + + expression.CreateMap() + .ForMember(dest => dest.Paths, opt => opt.MapFrom(src => new List())) + .ForMember(dest => dest.Title, opt => opt.MapFrom(src => src.EpisodeTitle)) + .ForMember(dest => dest.SubtitlePath, opt => opt.MapFrom(src => new List())); expression.CreateMap() .ForMember(dest => dest.Play, opt => opt.MapFrom(src => src.Archive.Stat.View)) @@ -141,6 +152,14 @@ public static List MapToDynamicItemModels(this IEnumerable x.ModuleType == DynModuleType.ModuleAuthor); var moduleDynamic = src.Modules.FirstOrDefault(x => x.ModuleType == DynModuleType.ModuleDynamic); + + // 处理特殊情况:类型为番剧但是数据为普通视频 + if (moduleDynamic != null && type == 512 && moduleDynamic.ModuleDynamic.DynArchive != null && + moduleDynamic.ModuleDynamic.DynPgc == null) + { + type = 8; + } + var dynDesc = new DynamicDescModel() { Type = type, @@ -151,6 +170,7 @@ public static List MapToDynamicItemModels(this IEnumerable(this T obj) return default; } } + + public static T ObjectCloneWithoutSerializable(this T obj) + { + try + { + var serializedObj = JsonConvert.SerializeObject(obj); + return JsonConvert.DeserializeObject(serializedObj); + } + catch (Exception e) + { + return default; + } + } } } diff --git a/src/BiliLite.UWP/Extensions/StringExtensions.cs b/src/BiliLite.UWP/Extensions/StringExtensions.cs index 67c4db81b..5bff2a1ab 100644 --- a/src/BiliLite.UWP/Extensions/StringExtensions.cs +++ b/src/BiliLite.UWP/Extensions/StringExtensions.cs @@ -225,7 +225,7 @@ public static string ParseArea(this string title, long mid) public static string ParseArea(this string title, string mid) { - return title.ParseArea(mid.ToInt32()); + return title.ParseArea(mid.ToInt64()); } /// diff --git a/src/BiliLite.UWP/Extensions/ViewModelExtensions.cs b/src/BiliLite.UWP/Extensions/ViewModelExtensions.cs index df3ecb03a..a188a8a16 100644 --- a/src/BiliLite.UWP/Extensions/ViewModelExtensions.cs +++ b/src/BiliLite.UWP/Extensions/ViewModelExtensions.cs @@ -22,6 +22,9 @@ public static IServiceCollection AddViewModels(this IServiceCollection services) services.AddTransient(); services.AddTransient(); services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); return services; } } diff --git a/src/BiliLite.UWP/Models/Common/User/UserDetails/UserCenterInfoLiveRoomModel.cs b/src/BiliLite.UWP/Models/Common/User/UserDetails/UserCenterInfoLiveRoomModel.cs new file mode 100644 index 000000000..d860fd365 --- /dev/null +++ b/src/BiliLite.UWP/Models/Common/User/UserDetails/UserCenterInfoLiveRoomModel.cs @@ -0,0 +1,27 @@ +using Newtonsoft.Json; + +namespace BiliLite.Models.Common.User.UserDetails +{ + public class UserCenterInfoLiveRoomModel + { + public int RoomStatus { get; set; } + + public int LiveStatus { get; set; } + + public string Url { get; set; } + + public string Title { get; set; } + + public string Cover { get; set; } + + public int Online { get; set; } + + [JsonProperty("roomid")] + public int RoomId { get; set; } + + public int RoundStatus { get; set; } + + [JsonProperty("broadcast_type")] + public int BroadcastType { get; set; } + } +} \ No newline at end of file diff --git a/src/BiliLite.UWP/Models/Common/User/UserDetails/UserCenterInfoModel.cs b/src/BiliLite.UWP/Models/Common/User/UserDetails/UserCenterInfoModel.cs new file mode 100644 index 000000000..31970658a --- /dev/null +++ b/src/BiliLite.UWP/Models/Common/User/UserDetails/UserCenterInfoModel.cs @@ -0,0 +1,63 @@ +using System; +using BiliLite.Modules.User; +using Newtonsoft.Json; + +namespace BiliLite.Models.Common.User.UserDetails +{ + public class UserCenterInfoModel + { + public long Mid { get; set; } + + public string Name { get; set; } + + public string Sex { get; set; } + + public string Face { get; set; } + + public string Sign { get; set; } + + public int Rank { get; set; } + + public int Level { get; set; } + + [JsonProperty("jointime")] + public int JoinTime { get; set; } + + public int Moral { get; set; } + + public int Silence { get; set; } + + public string Birthday { get; set; } + + public double Coins { get; set; } + + [JsonProperty("fans_badge")] + public bool FansBadge { get; set; } + + public UserCenterInfoOfficialModel Official { get; set; } + + public UserCenterInfoVipModel Vip { get; set; } + + public UserCenterInfoPendantModel Pendant { get; set; } + + [JsonProperty("nameplate")] + public UserCenterInfoNameplateModel NamePlate { get; set; } + + [JsonProperty("is_followed")] + public bool IsFollowed { get; set; } + + [JsonProperty("top_photo")] + public string TopPhoto { get; set; } + + [JsonProperty("live_room")] + public UserCenterInfoLiveRoomModel LiveRoom { get; set; } + + public UserCenterSpaceStatModel Stat { get; set; } + + [Obsolete] + public string PendantStr => ""; + + [Obsolete] + public string Verify => ""; + } +} \ No newline at end of file diff --git a/src/BiliLite.UWP/Models/Common/User/UserDetails/UserCenterInfoNameplateModel.cs b/src/BiliLite.UWP/Models/Common/User/UserDetails/UserCenterInfoNameplateModel.cs new file mode 100644 index 000000000..78bd38ea4 --- /dev/null +++ b/src/BiliLite.UWP/Models/Common/User/UserDetails/UserCenterInfoNameplateModel.cs @@ -0,0 +1,20 @@ +using Newtonsoft.Json; + +namespace BiliLite.Models.Common.User.UserDetails +{ + public class UserCenterInfoNameplateModel + { + public int Nid { get; set; } + + public string Name { get; set; } + + public string Image { get; set; } + + [JsonProperty("image_small")] + public string ImageSmall { get; set; } + + public string Level { get; set; } + + public string Condition { get; set; } + } +} \ No newline at end of file diff --git a/src/BiliLite.UWP/Models/Common/User/UserDetails/UserCenterInfoOfficialModel.cs b/src/BiliLite.UWP/Models/Common/User/UserDetails/UserCenterInfoOfficialModel.cs new file mode 100644 index 000000000..d592cb7df --- /dev/null +++ b/src/BiliLite.UWP/Models/Common/User/UserDetails/UserCenterInfoOfficialModel.cs @@ -0,0 +1,15 @@ +namespace BiliLite.Models.Common.User.UserDetails +{ + public class UserCenterInfoOfficialModel + { + public int Role { get; set; } + + public string Title { get; set; } + + public string Desc { get; set; } + + public int Type { get; set; } + + public bool ShowOfficial => Type != -1; + } +} \ No newline at end of file diff --git a/src/BiliLite.UWP/Models/Common/User/UserDetails/UserCenterInfoPendantModel.cs b/src/BiliLite.UWP/Models/Common/User/UserDetails/UserCenterInfoPendantModel.cs new file mode 100644 index 000000000..911afcbd0 --- /dev/null +++ b/src/BiliLite.UWP/Models/Common/User/UserDetails/UserCenterInfoPendantModel.cs @@ -0,0 +1,18 @@ +using Newtonsoft.Json; + +namespace BiliLite.Models.Common.User.UserDetails +{ + public class UserCenterInfoPendantModel + { + public int Pid { get; set; } + + public string Name { get; set; } + + public string Image { get; set; } + + public int Expire { get; set; } + + [JsonProperty("image_enhance")] + public string ImageEnhance { get; set; } + } +} \ No newline at end of file diff --git a/src/BiliLite.UWP/Models/Common/User/UserDetails/UserCenterInfoStatModel.cs b/src/BiliLite.UWP/Models/Common/User/UserDetails/UserCenterInfoStatModel.cs new file mode 100644 index 000000000..243fe29b0 --- /dev/null +++ b/src/BiliLite.UWP/Models/Common/User/UserDetails/UserCenterInfoStatModel.cs @@ -0,0 +1,15 @@ +namespace BiliLite.Models.Common.User.UserDetails +{ + public class UserCenterInfoStatModel + { + public long Mid { get; set; } + + public int Following { get; set; } + + public int Whisper { get; set; } + + public int Black { get; set; } + + public int Follower { get; set; } + } +} \ No newline at end of file diff --git a/src/BiliLite.UWP/Models/Common/User/UserDetails/UserCenterInfoVipLabelModel.cs b/src/BiliLite.UWP/Models/Common/User/UserDetails/UserCenterInfoVipLabelModel.cs new file mode 100644 index 000000000..601ebd9db --- /dev/null +++ b/src/BiliLite.UWP/Models/Common/User/UserDetails/UserCenterInfoVipLabelModel.cs @@ -0,0 +1,14 @@ +using Newtonsoft.Json; + +namespace BiliLite.Models.Common.User.UserDetails +{ + public class UserCenterInfoVipLabelModel + { + public string Path { get; set; } + + public string Text { get; set; } + + [JsonProperty("label_theme")] + public string LabelTheme { get; set; } + } +} \ No newline at end of file diff --git a/src/BiliLite.UWP/Models/Common/User/UserDetails/UserCenterInfoVipModel.cs b/src/BiliLite.UWP/Models/Common/User/UserDetails/UserCenterInfoVipModel.cs new file mode 100644 index 000000000..320a6dc57 --- /dev/null +++ b/src/BiliLite.UWP/Models/Common/User/UserDetails/UserCenterInfoVipModel.cs @@ -0,0 +1,22 @@ +using Newtonsoft.Json; + +namespace BiliLite.Models.Common.User.UserDetails +{ + public class UserCenterInfoVipModel + { + public int Type { get; set; } + + public int Status { get; set; } + + [JsonProperty("theme_type")] + public int ThemeType { get; set; } + + public UserCenterInfoVipLabelModel Label { get; set; } + + [JsonProperty("avatar_subscript")] + public int AvatarSubscript { get; set; } + + [JsonProperty("nickname_color")] + public string NicknameColor { get; set; } + } +} \ No newline at end of file diff --git a/src/BiliLite.UWP/Models/Common/User/UserDetails/UserCenterSpaceStatModel.cs b/src/BiliLite.UWP/Models/Common/User/UserDetails/UserCenterSpaceStatModel.cs new file mode 100644 index 000000000..55eb7a5d1 --- /dev/null +++ b/src/BiliLite.UWP/Models/Common/User/UserDetails/UserCenterSpaceStatModel.cs @@ -0,0 +1,31 @@ +using BiliLite.Extensions; + +namespace BiliLite.Models.Common.User.UserDetails +{ + public class UserCenterSpaceStatModel + { + public int Following { get; set; } + + public string Attention => Following > 0 ? " " + Following.ToCountString() : ""; + + public int Follower { get; set; } + + public string Fans => Follower > 0 ? " " + Follower.ToCountString() : ""; + + public int VideoCount { get; set; } + + public string Video => VideoCount > 0 ? " " + VideoCount.ToCountString() : ""; + + public int ArticleCount { get; set; } + + public string Article => ArticleCount > 0 ? " " + ArticleCount.ToCountString() : ""; + + public int FavouriteCount { get; set; } + + public string Favourite => FavouriteCount > 0 ? " " + FavouriteCount.ToCountString() : ""; + + public int CollectionCount { get; set; } + + public string Collection => CollectionCount > 0 ? " " + CollectionCount.ToCountString() : ""; + } +} \ No newline at end of file diff --git a/src/BiliLite.UWP/Models/Requests/Api/AccountApi.cs b/src/BiliLite.UWP/Models/Requests/Api/AccountApi.cs index 0ebdc7b36..63963b223 100644 --- a/src/BiliLite.UWP/Models/Requests/Api/AccountApi.cs +++ b/src/BiliLite.UWP/Models/Requests/Api/AccountApi.cs @@ -94,6 +94,10 @@ public ApiModel SendSMS(string cid, string phone, string sessionId, ApiKeyInfo a public ApiModel SendSMSWithCaptcha(string cid, string phone, string session_id, string seccode = "", string validate = "", string challenge = "", string recaptchaToken = "", ApiKeyInfo appKey = null) { + if (seccode.Contains("|")) + { + seccode = seccode.UrlEncode(); + } var buvid = ApiHelper.GetBuvid(); var api = new ApiModel() { diff --git a/src/BiliLite.UWP/Models/Requests/Api/User/DynamicAPI.cs b/src/BiliLite.UWP/Models/Requests/Api/User/DynamicAPI.cs index c23f84b98..7890502be 100644 --- a/src/BiliLite.UWP/Models/Requests/Api/User/DynamicAPI.cs +++ b/src/BiliLite.UWP/Models/Requests/Api/User/DynamicAPI.cs @@ -154,8 +154,6 @@ public ApiModel RecommendTopic() /// /// 发表图片动态 /// - /// 用户ID - /// 1为关注,2为取消关注 /// public ApiModel CreateDynamicPhoto(string imgs, string content, string at_uids, string at_control) { @@ -173,8 +171,6 @@ public ApiModel CreateDynamicPhoto(string imgs, string content, string at_uids, /// /// 发表文本动态 /// - /// 用户ID - /// 1为关注,2为取消关注 /// public ApiModel CreateDynamicText(string content, string at_uids, string at_control) { @@ -192,8 +188,6 @@ public ApiModel CreateDynamicText(string content, string at_uids, string at_cont /// /// 转发动态 /// - /// 用户ID - /// 1为关注,2为取消关注 /// public ApiModel RepostDynamic(string dynamic_id, string content, string at_uids, string at_control) { diff --git a/src/BiliLite.UWP/Models/Requests/Api/User/FavoriteAPI.cs b/src/BiliLite.UWP/Models/Requests/Api/User/FavoriteAPI.cs index 363047b46..354f01180 100644 --- a/src/BiliLite.UWP/Models/Requests/Api/User/FavoriteAPI.cs +++ b/src/BiliLite.UWP/Models/Requests/Api/User/FavoriteAPI.cs @@ -55,10 +55,10 @@ public ApiModel MyCreatedFavorite(string aid) /// 添加到收藏夹 /// /// - public ApiModel AddFavorite(List fav_ids, string avid) + public ApiModel AddFavorite(List favIds, string avid) { var ids = ""; - foreach (var item in fav_ids) + foreach (var item in favIds) { ids += item + ","; } diff --git a/src/BiliLite.UWP/Models/Requests/Api/User/UserDetailAPI.cs b/src/BiliLite.UWP/Models/Requests/Api/User/UserDetailAPI.cs index 80488fc0b..617888c8b 100644 --- a/src/BiliLite.UWP/Models/Requests/Api/User/UserDetailAPI.cs +++ b/src/BiliLite.UWP/Models/Requests/Api/User/UserDetailAPI.cs @@ -1,10 +1,20 @@ -using BiliLite.Services; +using System.Collections.Generic; +using System.Linq; +using BiliLite.Services; using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; namespace BiliLite.Models.Requests.Api.User { public class UserDetailAPI : BaseApi { + private readonly CookieService m_cookieService; + + public UserDetailAPI() + { + m_cookieService = App.ServiceProvider.GetRequiredService(); + } + /// /// 用户信息 /// @@ -132,6 +142,66 @@ public ApiModel FollowingsTag() api.parameter += ApiHelper.GetSign(api.parameter, AppKey); return api; } + + /// + /// 查询目标用户所在的分组 + /// + /// + public ApiModel FollowingTagUser(long targetUserId) + { + var api = new ApiModel() + { + method = RestSharp.Method.Get, + baseUrl = $"{ApiHelper.API_BASE_URL}/x/relation/tag/user", + parameter = $"fid={targetUserId}&" + ApiHelper.MustParameter(AppKey, true) + }; + api.parameter += ApiHelper.GetSign(api.parameter, AppKey); + return api; + } + + /// + /// 创建分组 + /// + /// + public ApiModel CreateFollowingTag(long tag) + { + var api = new ApiModel() + { + method = RestSharp.Method.Post, + baseUrl = $"{ApiHelper.API_BASE_URL}/x/relation/tag/create", + body = $"tag={tag}&" + ApiHelper.MustParameter(AppKey, true) + }; + api.body += ApiHelper.GetSign(api.parameter, AppKey); + return api; + } + + /// + /// 修改分组成员 + /// + /// + public ApiModel AddFollowingTagUsers(List targetUserIdList,List tagIdList) + { + var csrf = m_cookieService.GetCSRFToken(); + var fids = targetUserIdList.Aggregate("", (current, id) => current + $"{id},"); + fids = fids.TrimEnd(','); + var tagids = "0"; + if (tagIdList.Any()) + { + tagids = tagIdList.Aggregate("", (current, id) => current + $"{id},"); + tagids = tagids.TrimEnd(','); + } + + var api = new ApiModel() + { + method = RestSharp.Method.Post, + baseUrl = $"{ApiHelper.API_BASE_URL}/x/relation/tags/addUsers", + body = $"fids={fids}&tagids={tagids}&csrf={csrf}",// + ApiHelper.MustParameter(AppKey, true) + need_cookie = true, + }; + //api.body += ApiHelper.GetSign(api.body, AppKey); + return api; + } + /// /// 关注的人 /// diff --git a/src/BiliLite.UWP/Modules/Live/LiveMessage.cs b/src/BiliLite.UWP/Modules/Live/LiveMessage.cs index 96c501637..0e7f62638 100644 --- a/src/BiliLite.UWP/Modules/Live/LiveMessage.cs +++ b/src/BiliLite.UWP/Modules/Live/LiveMessage.cs @@ -37,7 +37,7 @@ public LiveMessage() ws = new ClientWebSocket(); } private static System.Timers.Timer heartBeatTimer; - public async Task Connect(int roomID, int uid, string token, string buvid, string host, CancellationToken cancellationToken) + public async Task Connect(int roomID, long uid, string token, string buvid, string host, CancellationToken cancellationToken) { ws.Options.SetRequestHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 Edg/116.0.1938.69"); //连接 @@ -89,7 +89,7 @@ private async void HeartBeatTimer_Elapsed(object sender, System.Timers.ElapsedEv /// /// /// - private async Task JoinRoomAsync(int roomId, string buvid, string token, int uid = 0) + private async Task JoinRoomAsync(int roomId, string buvid, string token, long uid = 0) { if (ws.State == WebSocketState.Open) { diff --git a/src/BiliLite.UWP/Modules/Player/Playurl/BiliPlayUrlRequest.cs b/src/BiliLite.UWP/Modules/Player/Playurl/BiliPlayUrlRequest.cs index eb7e646c1..76fd09304 100644 --- a/src/BiliLite.UWP/Modules/Player/Playurl/BiliPlayUrlRequest.cs +++ b/src/BiliLite.UWP/Modules/Player/Playurl/BiliPlayUrlRequest.cs @@ -15,6 +15,7 @@ using BiliLite.Models.Common.Video; using BiliLite.Models.Common.Video.PlayUrlInfos; using PlayURL = BiliLite.gRPC.Api.PlayURL; +using BiliLite.gRPC.Api; namespace BiliLite.Modules.Player.Playurl { @@ -686,7 +687,11 @@ private async Task GetPlayUrlUseGrpc(PlayInfo playInfo, { Bilibili.App.Playurl.V1.CodeType codec = CodecMode == PlayUrlCodecMode.DASH_H265 ? Bilibili.App.Playurl.V1.CodeType.Code265 : Bilibili.App.Playurl.V1.CodeType.Code264; - var playViewReply = await playUrlApi.VideoPlayView(Convert.ToInt64(playInfo.avid), Convert.ToInt64(playInfo.cid), qualityID, 16, codec, SettingService.Account.AccessKey); + var requestUserInfo = new GrpcBiliUserInfo( + SettingService.Account.AccessKey, + SettingService.Account.UserID, + SettingService.Account.GetLoginAppKeySecret().Appkey); + var playViewReply = await playUrlApi.VideoPlayView(Convert.ToInt64(playInfo.avid), Convert.ToInt64(playInfo.cid), qualityID, 16, codec, requestUserInfo); var grpcResult = await ParseGrpc(qualityID, playViewReply, AndroidUserAgent, ""); return grpcResult; @@ -784,7 +789,11 @@ private async Task GetPlayUrlUseGrpc(PlayInfo playInfo, CodeType codec = CodecMode == PlayUrlCodecMode.DASH_H265 ? CodeType.Code265 : CodeType.Code264; - var playViewReply = await playUrlApi.BangumiPlayView(Convert.ToInt64(playInfo.ep_id), Convert.ToInt64(playInfo.cid), qualityID, 0, codec, SettingService.Account.AccessKey); + var requestUserInfo = new GrpcBiliUserInfo( + SettingService.Account.AccessKey, + SettingService.Account.UserID, + SettingService.Account.GetLoginAppKeySecret().Appkey); + var playViewReply = await playUrlApi.BangumiPlayView(Convert.ToInt64(playInfo.ep_id), Convert.ToInt64(playInfo.cid), qualityID, 0, codec, requestUserInfo); var grpcResult = await ParseGrpc(qualityID, playViewReply, AndroidUserAgent, ""); return grpcResult; diff --git a/src/BiliLite.UWP/Modules/User/LoginVM.cs b/src/BiliLite.UWP/Modules/User/LoginVM.cs index ef7352e5c..64112b0f7 100644 --- a/src/BiliLite.UWP/Modules/User/LoginVM.cs +++ b/src/BiliLite.UWP/Modules/User/LoginVM.cs @@ -340,8 +340,9 @@ public async void SendSMSCodeWithCaptcha(string seccode = "", string validate = try { var appKey = SettingConstants.Account.DefaultLoginAppKeySecret; - var results = await accountApi.SendSMSWithCaptcha(CurrentCountry.country_code, Phone, sessionId, - seccode, validate, challenge, recaptcha_token, appKey).Request(); + var request = accountApi.SendSMSWithCaptcha(CurrentCountry.country_code, Phone, sessionId, + seccode, validate, challenge, recaptcha_token, appKey); + var results = await request.Request(); if (!results.status) { throw new CustomizedErrorWithDataException(results.message, results); @@ -409,7 +410,7 @@ private async void DoSMSLogin() SettingService.SetValue(SettingConstants.Account.IS_WEB_LOGIN, false); var data = await results.GetData(); var result = await HandelLoginResult(data.code, data.message, data.data, appKey); - HnadelResult(result); + HandleResult(result); } else { @@ -476,7 +477,7 @@ private async void DoPasswordLogin() var data = await results.GetData(); var result = await HandelLoginResult(data.code, data.message, data.data, appKey); - HnadelResult(result); + HandleResult(result); } else { @@ -523,7 +524,7 @@ private async void CompletePasswordLoginCheck() var code = obj["data"]["code"].ToString(); var result = await PasswordLoginFetchCookie(code); - HnadelResult(result); + HandleResult(result); } catch (CustomizedErrorWithDataException ex) { @@ -694,11 +695,12 @@ public void HandleGeetestSuccess(string seccode, string validate, string challen { if (gee_req != null) { + // 验证完成后的 gee_challenge 值可能与之前获取的不同,此处只做提示 if (gee_req.gee_challenge != challenge) { Notify.ShowMessageToast("验证码失效"); - return; } + gee_req.gee_challenge = challenge; gee_req.gee_validate = validate; gee_req.gee_seccode = seccode; } @@ -832,7 +834,7 @@ private async Task HandelLoginResult(int code, string messag } } - private void HnadelResult(LoginCallbackModel result) + private void HandleResult(LoginCallbackModel result) { switch (result.status) { @@ -847,6 +849,15 @@ private void HnadelResult(LoginCallbackModel result) case LoginStatus.NeedCaptcha: var uri = new Uri(result.url); SetWebViewVisibility?.Invoke(this, true); + var query = HttpUtility.ParseQueryString(uri.Query); + gee_req = new GeetestRequestModel() + { + gee_challenge = query.Get("gee_challenge"), + gee_gt = query.Get("gee_gt"), + recaptcha_token = query.Get("recaptcha_token"), + }; + // TODO: 密码登录验证需要获取tmp_code + gee_tmp_token = ""; //验证码重定向 //源码:https://github.com/xiaoyaocz/some_web OpenWebView?.Invoke(this, new Uri("ms-appx-web:///Assets/GeeTest/bili_gt.html" + uri.Query + "&app=uwp")); diff --git a/src/BiliLite.UWP/Modules/User/SendDynamic/AtVM.cs b/src/BiliLite.UWP/Modules/User/SendDynamic/AtVM.cs index fe0764f05..5ed9ac291 100644 --- a/src/BiliLite.UWP/Modules/User/SendDynamic/AtVM.cs +++ b/src/BiliLite.UWP/Modules/User/SendDynamic/AtVM.cs @@ -69,7 +69,7 @@ public async Task GetUser() { Face = item["face"].ToString(), UserName = item["name"].ToString(), - ID = item["mid"].ToInt32(), + ID = item["mid"].ToInt64(), }); } } @@ -83,7 +83,7 @@ public async Task GetUser() { Face = item["face"].ToString(), UserName = item["uname"].ToString(), - ID = item["uid"].ToInt32(), + ID = item["uid"].ToInt64(), }); } } @@ -135,7 +135,7 @@ public async void Search(string keyword) } public class AtUserModel { - public int ID { get; set; } + public long ID { get; set; } public string UserName { get; set; } public string Face { get; set; } public string Display { get { return "@" + UserName; } } diff --git a/src/BiliLite.UWP/Modules/User/UserDetail/UserDetailVM.cs b/src/BiliLite.UWP/Modules/User/UserDetail/UserDetailVM.cs deleted file mode 100644 index 231af0e76..000000000 --- a/src/BiliLite.UWP/Modules/User/UserDetail/UserDetailVM.cs +++ /dev/null @@ -1,406 +0,0 @@ -using BiliLite.Models; -using BiliLite.Models.Requests.Api.User; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.Windows.Input; -using Windows.Storage.Streams; -using Windows.UI; -using Windows.UI.Xaml.Media; -using BiliLite.Extensions; -using BiliLite.Models.Common; -using BiliLite.Services; - -namespace BiliLite.Modules.User -{ - public class UserDetailVM : IModules - { - private static readonly ILogger logger = GlobalLogger.FromCurrentType(); - - public string mid { get; set; } - private readonly UserDetailAPI userDetailAPI; - private readonly FollowAPI followAPI; - public UserDetailVM() - { - userDetailAPI = new UserDetailAPI(); - followAPI = new FollowAPI(); - AttentionCommand = new RelayCommand(DoAttentionUP); - } - private UserCenterInfoModel _userInfo; - public UserCenterInfoModel UserInfo - { - get { return _userInfo; } - set { _userInfo = value; DoPropertyChanged("UserInfo"); } - } - - public ICommand AttentionCommand { get; private set; } - - public async void GetUserInfo() - { - try - { - var api = await userDetailAPI.UserInfo(mid); - var result = await api.Request(); - - if (result.status) - { - var data = await result.GetData(); - if (data.success) - { - data.data.stat = await GetSpeceStat(); - UserInfo = data.data; - } - else - { - Notify.ShowMessageToast(data.message); - } - - } - else - { - Notify.ShowMessageToast(result.message); - } - } - catch (Exception ex) - { - logger.Log("读取个人资料失败", LogType.Error, ex); - Notify.ShowMessageToast("读取个人资料失败"); - } - } - public async Task GetStat() - { - try - { - var result = await userDetailAPI.UserStat(mid).Request(); - - if (result.status) - { - var data = await result.GetData(); - if (data.success) - { - return data.data; - } - else - { - return null; - } - - } - else - { - return null; - } - } - catch (Exception ex) - { - logger.Log("读取个人资料失败", LogType.Error, ex); - return null; - } - } - - public async Task GetSpeceStat() - { - try - { - var result = await userDetailAPI.Space(mid).Request(); - - if (result.status) - { - var data = await result.GetData(); - if (data.success) - { - UserCenterSpaceStatModel stat = new UserCenterSpaceStatModel(); - stat.article_count = (data.data["article"]?["count"] ?? 0).ToInt32(); - stat.video_count = (data.data["archive"]?["count"] ?? 0).ToInt32(); - stat.favourite_count = (data.data["favourite2"]?["count"] ?? 0).ToInt32(); - stat.follower = data.data["card"]["fans"].ToInt32(); - stat.following = data.data["card"]["attention"].ToInt32(); - stat.CollectionCount = (data.data?["ugc_season"]?["count"] ?? 0).ToInt32() + (data.data?["series"]?["item"].ToArray().Length ?? 0); - return stat; - } - else - { - return null; - } - - } - else - { - return null; - } - } - catch (Exception ex) - { - logger.Log("读取个人资料失败", LogType.Error, ex); - return null; - } - } - public async void DoAttentionUP() - { - var result = await AttentionUP(UserInfo.mid.ToString(), UserInfo.is_followed ? 2 : 1); - if (result) - { - UserInfo.is_followed = !UserInfo.is_followed; - } - } - public async Task AttentionUP(string mid, int mode) - { - if (!SettingService.Account.Logined && !await Notify.ShowLoginDialog()) - { - Notify.ShowMessageToast("请先登录后再操作"); - return false; - } - - try - { - var results = await followAPI.Attention(mid, mode.ToString()).Request(); - if (results.status) - { - var data = await results.GetJson>(); - if (data.success) - { - Notify.ShowMessageToast("操作成功"); - return true; - } - else - { - Notify.ShowMessageToast(data.message); - return false; - } - } - else - { - Notify.ShowMessageToast(results.message); - return false; - } - } - catch (Exception ex) - { - var handel = HandelError(ex); - Notify.ShowMessageToast(handel.message); - return false; - } - - - - } - - - } - - - - public class UserCenterInfoOfficialModel - { - public int role { get; set; } - public string title { get; set; } - public string desc { get; set; } - public int type { get; set; } - public bool showOfficial { get { return type != -1; } } - } - - public class UserCenterInfoVipLabelModel - { - public string path { get; set; } - public string text { get; set; } - public string label_theme { get; set; } - } - - public class UserCenterInfoVipModel - { - public int type { get; set; } - public int status { get; set; } - public int theme_type { get; set; } - public UserCenterInfoVipLabelModel label { get; set; } - public int avatar_subscript { get; set; } - public string nickname_color { get; set; } - } - - public class UserCenterInfoPendantModel - { - public int pid { get; set; } - public string name { get; set; } - public string image { get; set; } - public int expire { get; set; } - public string image_enhance { get; set; } - } - - public class UserCenterInfoNameplateModel - { - public int nid { get; set; } - public string name { get; set; } - public string image { get; set; } - public string image_small { get; set; } - public string level { get; set; } - public string condition { get; set; } - } - - - - public class UserCenterInfoLiveRoomModel - { - public int roomStatus { get; set; } - public int liveStatus { get; set; } - public string url { get; set; } - public string title { get; set; } - public string cover { get; set; } - public int online { get; set; } - public int roomid { get; set; } - public int roundStatus { get; set; } - public int broadcast_type { get; set; } - } - public class UserCenterInfoStatModel - { - public long mid { get; set; } - public int following { get; set; } - public int whisper { get; set; } - public int black { get; set; } - public int follower { get; set; } - } - public class UserCenterSpaceStatModel - { - public int following { get; set; } - public string attention - { - get - { - return following > 0 ? " " + following.ToCountString() : ""; - } - } - public int follower { get; set; } - public string fans - { - get - { - return follower > 0 ? " " + follower.ToCountString() : ""; - } - } - public int video_count { get; set; } - public string video - { - get - { - return video_count > 0 ? " " + video_count.ToCountString() : ""; - } - } - public int article_count { get; set; } - public string article - { - get - { - return article_count > 0 ? " " + article_count.ToCountString() : ""; - } - } - public int favourite_count { get; set; } - public string favourite - { - get - { - return favourite_count > 0 ? " " + favourite_count.ToCountString() : ""; - } - } - - public int CollectionCount { get; set; } - public string Collection { get => CollectionCount > 0 ? " " + CollectionCount.ToCountString() : ""; } - } - public class UserCenterInfoModel : IModules - { - public long mid { get; set; } - public string name { get; set; } - public string sex { get; set; } - public string face { get; set; } - public string sign { get; set; } - public int rank { get; set; } - public int level { get; set; } - public SolidColorBrush level_color - { - get - { - switch (level) - { - - case 2: - return new SolidColorBrush(Colors.LightGreen); - case 3: - return new SolidColorBrush(Colors.LightBlue); - case 4: - return new SolidColorBrush(Colors.Yellow); - case 5: - return new SolidColorBrush(Colors.Orange); - case 6: - return new SolidColorBrush(Colors.Red); - case 7: - return new SolidColorBrush(Colors.HotPink); - case 8: - return new SolidColorBrush(Colors.Purple); - default: - return new SolidColorBrush(Colors.Gray); - } - } - } - public int jointime { get; set; } - public int moral { get; set; } - public int silence { get; set; } - public string birthday { get; set; } - public double coins { get; set; } - public bool fans_badge { get; set; } - public UserCenterInfoOfficialModel official { get; set; } - public UserCenterInfoVipModel vip { get; set; } - public UserCenterInfoPendantModel pendant { get; set; } - public UserCenterInfoNameplateModel nameplate { get; set; } - private bool _is_followed; - - public bool is_followed - { - get { return _is_followed; } - set { _is_followed = value; DoPropertyChanged("is_followed"); } - } - - public string top_photo { get; set; } - - public UserCenterInfoLiveRoomModel live_room { get; set; } - public UserCenterSpaceStatModel stat { get; set; } - public string pendant_str - { - get - { - if (pendant != null) - { - if (pendant.image == "") - { - return Constants.App.TRANSPARENT_IMAGE; - } - return pendant.image; - } - else - { - return Constants.App.TRANSPARENT_IMAGE; - } - } - } - public string Verify - { - get - { - if (official == null) - { - return ""; - } - switch (official.type) - { - case 0: - return Constants.App.VERIFY_PERSONAL_IMAGE; - case 1: - return Constants.App.VERIFY_OGANIZATION_IMAGE; - default: - return Constants.App.TRANSPARENT_IMAGE; - } - } - } - } -} diff --git a/src/BiliLite.UWP/Modules/User/UserDetail/UserFollowVM.cs b/src/BiliLite.UWP/Modules/User/UserDetail/UserFollowVM.cs index 5670dd19b..3b55e6f45 100644 --- a/src/BiliLite.UWP/Modules/User/UserDetail/UserFollowVM.cs +++ b/src/BiliLite.UWP/Modules/User/UserDetail/UserFollowVM.cs @@ -1,17 +1,11 @@ -using BiliLite.Models; -using BiliLite.Models.Requests.Api.User; +using BiliLite.Models.Requests.Api.User; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using System; -using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; -using System.Text; using System.Threading.Tasks; using System.Windows.Input; -using Windows.Storage.Streams; -using Windows.UI; -using Windows.UI.Xaml.Media; using BiliLite.Extensions; using BiliLite.Models.Common; @@ -33,8 +27,8 @@ public UserFollowVM(bool isfans) Tlist = new ObservableCollection() { new FollowTlistItemModel() { - name="全部关注", - tagid=-1 + Name="全部关注", + TagId=-1 } }; SelectTid = Tlist.First(); @@ -124,7 +118,7 @@ public async Task Get() Nothing = false; CanLoadMore = false; Loading = true; - CurrentTid = SelectTid.tagid; + CurrentTid = SelectTid.TagId; var api = await userDetailAPI.Followings(mid, Page, 30, tid: CurrentTid, keyword: Keyword, (FollowingsOrder)SelectOrder); if (IsFans) { @@ -259,9 +253,13 @@ public class UserFollowOfficialVerifyItem } public class FollowTlistItemModel { - public int tagid { get; set; } - public string name { get; set; } - public int count { get; set; } - public string tip { get; set; } + [JsonProperty("tagid")] + public int TagId { get; set; } + + public string Name { get; set; } + + public int Count { get; set; } + + public string Tip { get; set; } } } diff --git a/src/BiliLite.UWP/Pages/DownloadPage.xaml b/src/BiliLite.UWP/Pages/DownloadPage.xaml index d827f2482..f7e9c6d59 100644 --- a/src/BiliLite.UWP/Pages/DownloadPage.xaml +++ b/src/BiliLite.UWP/Pages/DownloadPage.xaml @@ -27,6 +27,13 @@ + + 加载中 + + % + @@ -108,16 +111,16 @@ - + - + - + @@ -127,29 +130,29 @@ - + - - + + - - - - LV + + + + LV - - + + - - 关注   粉丝 + + 关注   粉丝 - + @@ -157,14 +160,30 @@ - - + + + + + + + - + @@ -189,7 +208,7 @@ --> - 视频 + 视频 - 专栏 + 专栏 - 合集 + 合集 - 收藏 + 收藏 - 关注 + 关注 - + @@ -578,7 +597,7 @@ - 粉丝 + 粉丝 (); this.InitializeComponent(); Title = "用户中心"; - userDetailVM = new Modules.User.UserDetailVM(); m_userSubmitVideoViewModel = App.ServiceProvider.GetService(); m_userSubmitCollectionViewModel = App.ServiceProvider.GetService(); userSubmitArticleVM = new UserSubmitArticleVM(); @@ -116,7 +115,7 @@ private void UserDynamicViewModelOpenCommentEvent(object sender, UserDynamicItem // Oid = id //}); } - protected override void OnNavigatedTo(NavigationEventArgs e) + protected override async void OnNavigatedTo(NavigationEventArgs e) { base.OnNavigatedTo(e); SetStaggered(); @@ -134,14 +133,14 @@ protected override void OnNavigatedTo(NavigationEventArgs e) { mid = e.Parameter.ToString(); } - userDetailVM.mid = mid; + m_viewModel.Mid = mid; m_userSubmitVideoViewModel.Mid = mid; m_userSubmitCollectionViewModel.Mid = mid; userSubmitArticleVM.mid = mid; userFavlistVM.mid = mid; fansVM.mid = mid; followVM.mid = mid; - if (userDetailVM.mid == SettingService.Account.UserID.ToString()) + if (m_viewModel.Mid == SettingService.Account.UserID.ToString()) { isSelf = true; appBar.Visibility = Visibility.Collapsed; @@ -154,13 +153,12 @@ protected override void OnNavigatedTo(NavigationEventArgs e) } m_userDynamicViewModel.DynamicType = DynamicType.Space; m_userDynamicViewModel.Uid = mid; - userDetailVM.GetUserInfo(); - + m_viewModel.GetUserInfo(); + await UserFollowingTagsFlyout.Init(mid); if (tabIndex != 0) { pivot.SelectedIndex = tabIndex; } - } } @@ -190,13 +188,13 @@ private async void SubmitVideo_ItemClick(object sender, ItemClickEventArgs e) private void btnLiveRoom_Click(object sender, RoutedEventArgs e) { - if (userDetailVM.UserInfo == null) return; + if (m_viewModel.UserInfo == null) return; MessageCenter.NavigateToPage(this, new NavigationInfo() { icon = Symbol.Video, page = typeof(LiveDetailPage), - title = userDetailVM.UserInfo.name + "的直播间", - parameters = userDetailVM.UserInfo.live_room.roomid + title = m_viewModel.UserInfo.Name + "的直播间", + parameters = m_viewModel.UserInfo.LiveRoom.RoomId }); } @@ -207,7 +205,7 @@ private void btnChat_Click(object sender, RoutedEventArgs e) icon = Symbol.Message, title = "消息中心", page = typeof(WebPage), - parameters = $"https://message.bilibili.com/#whisper/mid{userDetailVM.mid}" + parameters = $"https://message.bilibili.com/#whisper/mid{m_viewModel.Mid}" }); } @@ -383,9 +381,9 @@ private void comFollowOrder_SelectionChanged(object sender, SelectionChangedEven private void ComboBox_SelectionChanged_1(object sender, SelectionChangedEventArgs e) { - if (followVM != null && followVM.CurrentTid != followVM.SelectTid.tagid) + if (followVM != null && followVM.CurrentTid != followVM.SelectTid.TagId) { - if (followVM.SelectTid.tagid == -1) + if (followVM.SelectTid.TagId == -1) { searchFollow.Visibility = Visibility.Visible; } @@ -395,12 +393,16 @@ private void ComboBox_SelectionChanged_1(object sender, SelectionChangedEventArg } followVM.Refresh(); } - } private void searchFollow_QuerySubmitted(AutoSuggestBox sender, AutoSuggestBoxQuerySubmittedEventArgs args) { followVM.Refresh(); } + + private void BtnFollowingTag_OnClick(object sender, RoutedEventArgs e) + { + UserFollowingTagsFlyout.ShowAt(sender as DependencyObject); + } } } diff --git a/src/BiliLite.UWP/Pages/VideoDetailPage.xaml b/src/BiliLite.UWP/Pages/VideoDetailPage.xaml index e41f9352b..ebf285139 100644 --- a/src/BiliLite.UWP/Pages/VideoDetailPage.xaml +++ b/src/BiliLite.UWP/Pages/VideoDetailPage.xaml @@ -313,14 +313,10 @@ 粉丝 - - + @@ -344,12 +340,10 @@ - - + diff --git a/src/BiliLite.UWP/Pages/VideoDetailPage.xaml.cs b/src/BiliLite.UWP/Pages/VideoDetailPage.xaml.cs index 2ae553cb3..9a2828915 100644 --- a/src/BiliLite.UWP/Pages/VideoDetailPage.xaml.cs +++ b/src/BiliLite.UWP/Pages/VideoDetailPage.xaml.cs @@ -353,25 +353,6 @@ private void txtDesc_Holding(object sender, HoldingRoutedEventArgs e) } } - private async void btnAttention_Click(object sender, RoutedEventArgs e) - { - var data = (sender as Button).DataContext as VideoDetailStaffViewModel; - var result = await m_viewModel.AttentionUP(data.Mid, data.Attention == 1 ? 2 : 1); - if (result) - { - if (data.Attention == 1) - { - data.Attention = 0; - } - else - { - data.Attention = 1; - } - } - - } - - private void listRelates_ItemClick(object sender, ItemClickEventArgs e) { var data = e.ClickedItem as VideoDetailRelatesViewModel; @@ -595,7 +576,7 @@ private async void btnDownload_Click(object sender, RoutedEventArgs e) Subtitle = m_viewModel.VideoInfo.Bvid, Title = m_viewModel.VideoInfo.Title, Type = DownloadType.Video, - UpMid = m_viewModel.VideoInfo.Owner.Mid.ToInt32(), + UpMid = m_viewModel.VideoInfo.Owner.Mid.ToInt64(), }; int i = 0; foreach (var item in m_viewModel.VideoInfo.Pages) diff --git a/src/BiliLite.UWP/Services/GrpcService.cs b/src/BiliLite.UWP/Services/GrpcService.cs index 4c0d6fcb7..68080131e 100644 --- a/src/BiliLite.UWP/Services/GrpcService.cs +++ b/src/BiliLite.UWP/Services/GrpcService.cs @@ -18,9 +18,12 @@ public async Task SearchSpaceArchive(string mid, int page = Pn = page, Ps = pageSize, }; - var accessKey = SettingService.Account.AccessKey; + var requestUserInfo = new GrpcBiliUserInfo( + SettingService.Account.AccessKey, + SettingService.Account.UserID, + SettingService.Account.GetLoginAppKeySecret().Appkey); - var result = await GrpcRequest.Instance.SendMessage("https://grpc.biliapi.net:443/bilibili.app.interface.v1.Space/SearchArchive", message, accessKey); + var result = await GrpcRequest.Instance.SendMessage("https://grpc.biliapi.net:443/bilibili.app.interface.v1.Space/SearchArchive", message, requestUserInfo); if (result.status) { var reply = SearchArchiveReply.Parser.ParseFrom(result.results); @@ -43,9 +46,12 @@ public async Task GetDynAll(int page = 1) { Page = page }; - var accessKey = SettingService.Account.AccessKey; + var requestUserInfo = new GrpcBiliUserInfo( + SettingService.Account.AccessKey, + SettingService.Account.UserID, + SettingService.Account.GetLoginAppKeySecret().Appkey); - var result = await GrpcRequest.Instance.SendMessage("https://grpc.biliapi.net:443/bilibili.app.dynamic.v2.Dynamic/DynAll", message, accessKey); + var result = await GrpcRequest.Instance.SendMessage("https://grpc.biliapi.net:443/bilibili.app.dynamic.v2.Dynamic/DynAll", message, requestUserInfo); if (result.status) { var reply = DynAllReply.Parser.ParseFrom(result.results); @@ -70,9 +76,12 @@ public async Task GetDynVideo(int page, string historyOffset, str message.Page = page; message.RefreshType = Refresh.History; } - var accessKey = SettingService.Account.AccessKey; + var requestUserInfo = new GrpcBiliUserInfo( + SettingService.Account.AccessKey, + SettingService.Account.UserID, + SettingService.Account.GetLoginAppKeySecret().Appkey); - var result = await GrpcRequest.Instance.SendMessage("https://grpc.biliapi.net:443/bilibili.app.dynamic.v2.Dynamic/DynVideo", message, accessKey); + var result = await GrpcRequest.Instance.SendMessage("https://grpc.biliapi.net:443/bilibili.app.dynamic.v2.Dynamic/DynVideo", message, requestUserInfo); if (result.status) { var reply = DynVideoReply.Parser.ParseFrom(result.results); diff --git a/src/BiliLite.UWP/Services/SettingService.cs b/src/BiliLite.UWP/Services/SettingService.cs index 84eb68502..17c599d76 100644 --- a/src/BiliLite.UWP/Services/SettingService.cs +++ b/src/BiliLite.UWP/Services/SettingService.cs @@ -53,7 +53,7 @@ public class Account public static string AccessKey => GetValue(SettingConstants.Account.ACCESS_KEY, ""); - public static int UserID => GetValue(SettingConstants.Account.USER_ID, 0); + public static long UserID => GetValue(SettingConstants.Account.USER_ID, 0); public static ApiKeyInfo GetLoginAppKeySecret() { diff --git a/src/BiliLite.UWP/ViewModels/Download/DownloadPageViewModel.cs b/src/BiliLite.UWP/ViewModels/Download/DownloadPageViewModel.cs index ebd713942..2ad0736cf 100644 --- a/src/BiliLite.UWP/ViewModels/Download/DownloadPageViewModel.cs +++ b/src/BiliLite.UWP/ViewModels/Download/DownloadPageViewModel.cs @@ -20,6 +20,7 @@ using PropertyChanged; using System.Threading; using Windows.Networking.BackgroundTransfer; +using AutoMapper; using BiliLite.Extensions; namespace BiliLite.ViewModels.Download @@ -34,14 +35,16 @@ public class DownloadPageViewModel : BaseViewModel private List m_handelList; private const string DOWNLOAD_FOLDER_NAME = "哔哩哔哩下载"; private const string OLD_DOWNLOAD_FOLDER_NAME = "BiliBiliDownload"; + private readonly IMapper m_mapper; #endregion #region Constructors - public DownloadPageViewModel() + public DownloadPageViewModel(IMapper mapper) { + m_mapper = mapper; DownloadedViewModels = new ObservableCollection(); Downloadings = new ObservableCollection(); Downloadeds = new List(); @@ -95,6 +98,13 @@ public DownloadPageViewModel() public double DiskFree { get; set; } + public int TotalDownloadedCount { get; set; } + + public int LoadedDownloadedCount { get; set; } + + [DependsOn(nameof(TotalDownloadedCount), nameof(LoadedDownloadedCount))] + public int LoadingDownloadedPercent => (int)((LoadedDownloadedCount * 1f / TotalDownloadedCount * 1f) * 100); + #endregion #region Events @@ -424,31 +434,26 @@ public void SearchDownloaded(string keyword) /// public async void LoadDownloaded() { - LoadingDownloaded = true; DownloadedViewModels.Clear(); Downloadeds.Clear(); var folder = await GetDownloadFolder(); await LoadDiskSize(folder); - // var list = new List(); - foreach (var item in await folder.GetFoldersAsync()) + var folders = await folder.GetFoldersAsync(); + LoadedDownloadedCount = 0; + TotalDownloadedCount = folders.Count; + foreach (var item in folders) { try { //检查是否存在info.json - var infoFile = await item.TryGetItemAsync("info.json") as StorageFile; - if (infoFile == null) - { - continue; - } + if (!(await item.TryGetItemAsync("info.json") is StorageFile infoFile)) continue; + var info = JsonConvert.DeserializeObject(await FileIO.ReadTextAsync(infoFile)); //旧版无Cover字段,跳过 - if (string.IsNullOrEmpty(info.Cover)) - { - continue; - } - List lsEpisodes = new List(); - DownloadedItem downloadedItem = new DownloadedItem() + if (string.IsNullOrEmpty(info.Cover)) continue; + var lsEpisodes = new List(); + var downloadedItem = new DownloadedItem() { CoverPath = Path.Combine(item.Path, "cover.jpg"), Epsidoes = new ObservableCollection(), @@ -458,44 +463,33 @@ public async void LoadDownloaded() IsSeason = info.Type == DownloadType.Season, Path = item.Path }; - var coverFile = await item.TryGetItemAsync("cover.jpg") as StorageFile; - if (coverFile != null) + if (await item.TryGetItemAsync("cover.jpg") is StorageFile coverFile) { - BitmapImage bitmapImage = new BitmapImage(); + var bitmapImage = new BitmapImage(); var buffer = await FileIO.ReadBufferAsync(coverFile); - using (IRandomAccessStream stream = new InMemoryRandomAccessStream()) - { - await stream.WriteAsync(buffer); - stream.Seek(0); - bitmapImage.SetSource(stream); - downloadedItem.Cover = bitmapImage; - - } - + using IRandomAccessStream stream = new InMemoryRandomAccessStream(); + await stream.WriteAsync(buffer); + stream.Seek(0); + bitmapImage.SetSource(stream); + downloadedItem.Cover = bitmapImage; } foreach (var episodeItem in await item.GetFoldersAsync()) { //检查是否存在info.json - var episodeInfoFile = await episodeItem.TryGetItemAsync("info.json") as StorageFile; - if (episodeInfoFile == null) - { - continue; - } + if (!(await episodeItem.TryGetItemAsync("info.json") is StorageFile episodeInfoFile)) continue; var files = (await episodeItem.GetFilesAsync()).Where(x => x.FileType == ".blv" || x.FileType == ".mp4" || x.FileType == ".m4s"); - if (files.Count() == 0) + if (!files.Any()) { continue; } - bool flag = false; + var flag = false; foreach (var subfile in files) { var size = (await subfile.GetBasicPropertiesAsync()).Size; - if (size == 0) - { - flag = true; - break; - } + if (size != 0) continue; + flag = true; + break; } files = null; if (flag) @@ -504,18 +498,7 @@ public async void LoadDownloaded() } var episodeInfo = JsonConvert.DeserializeObject(await FileIO.ReadTextAsync(episodeInfoFile)); - var downloadedSubItem = new DownloadedSubItem() - { - AVID = episodeInfo.AVID, - CID = episodeInfo.CID, - Index = episodeInfo.Index, - EpisodeID = episodeInfo.EpisodeID, - IsDash = episodeInfo.IsDash, - Paths = new List(), - Title = episodeInfo.EpisodeTitle, - SubtitlePath = new List(), - Path = episodeItem.Path - }; + var downloadedSubItem = m_mapper.Map(episodeInfo); //设置视频 foreach (var path in episodeInfo.VideoPath) { @@ -546,26 +529,22 @@ public async void LoadDownloaded() } Downloadeds.Add(downloadedItem); + DownloadedViewModels.Add(downloadedItem); + LoadedDownloadedCount++; } catch (Exception ex) { Debug.WriteLine(ex.Message); + _logger.Warn(ex.Message); continue; } } - // list = list.OrderByDescending(x => x.UpdateTime).ToList(); if (SettingService.GetValue(SettingConstants.Download.LOAD_OLD_DOWNLOAD, false)) { await LoadDownloadedOld(); } - - //foreach (var item in list) - //{ - // Downloadeds.Add(item); - //} - DownloadedViewModels.AddRange(Downloadeds); LoadingDownloaded = false; } @@ -580,16 +559,14 @@ public async Task LoadDownloadedOld() var folder = await GetDownloadOldFolder(); //var list = new List(); - foreach (var item in await folder.GetFoldersAsync()) + var folders = await folder.GetFoldersAsync(); + TotalDownloadedCount += folders.Count; + foreach (var item in folders) { try { //检查是否存在info.json - var infoFile = await item.TryGetItemAsync("info.json") as StorageFile; - if (infoFile == null) - { - continue; - } + if (!(await item.TryGetItemAsync("info.json") is StorageFile infoFile)) continue; var info = JObject.Parse(await FileIO.ReadTextAsync(infoFile)); //新版下载无thumb字段 @@ -629,7 +606,7 @@ public async Task LoadDownloadedOld() { continue; } - bool flag = false; + var flag = false; foreach (var subfile in files) { var size = (await subfile.GetBasicPropertiesAsync()).Size; @@ -677,19 +654,16 @@ public async Task LoadDownloadedOld() } Downloadeds.Add(downloadedItem); + DownloadedViewModels.Add(downloadedItem); + LoadedDownloadedCount++; } catch (Exception ex) { Debug.WriteLine(ex.Message); + _logger.Warn(ex.Message); continue; } } - - // list = list.OrderByDescending(x => x.UpdateTime).ToList(); - - //return list; - - DownloadedViewModels.AddRange(Downloadeds); } /// diff --git a/src/BiliLite.UWP/ViewModels/Live/LiveRoomViewModel.cs b/src/BiliLite.UWP/ViewModels/Live/LiveRoomViewModel.cs index 45d68b38f..7c4298583 100644 --- a/src/BiliLite.UWP/ViewModels/Live/LiveRoomViewModel.cs +++ b/src/BiliLite.UWP/ViewModels/Live/LiveRoomViewModel.cs @@ -329,7 +329,7 @@ private async void ReceiveMessage(int roomId) { try { - var uid = 0; + long uid = 0; if (SettingService.Account.Logined) { uid = SettingService.Account.UserID; @@ -636,6 +636,7 @@ public async Task LoadSuperChat() SuperChats.Clear(); var ls = JsonConvert.DeserializeObject>( data.data["list"]?.ToString() ?? "[]"); + if (ls == null) return; foreach (var item in ls) { SuperChats.Add(new SuperChatMsgViewModel() diff --git a/src/BiliLite.UWP/ViewModels/User/UserAttentionButtonViewModel.cs b/src/BiliLite.UWP/ViewModels/User/UserAttentionButtonViewModel.cs new file mode 100644 index 000000000..48e8e4ffc --- /dev/null +++ b/src/BiliLite.UWP/ViewModels/User/UserAttentionButtonViewModel.cs @@ -0,0 +1,93 @@ +using System; +using System.Threading.Tasks; +using System.Windows.Input; +using BiliLite.Extensions; +using BiliLite.Models; +using BiliLite.Models.Requests.Api; +using BiliLite.Modules; +using BiliLite.Services; +using BiliLite.ViewModels.Common; +using Newtonsoft.Json.Linq; +using PropertyChanged; + +namespace BiliLite.ViewModels.User +{ + public class UserAttentionButtonViewModel : BaseViewModel + { + private readonly VideoAPI m_videoApi; + + public UserAttentionButtonViewModel() + { + m_videoApi = new VideoAPI(); + AttentionCommand = new RelayCommand(DoAttentionUP); + } + + public event EventHandler AttentionDone; + + public event EventHandler CancelAttention; + + public ICommand AttentionCommand { get; private set; } + + public int Attention { get; set; } + + [DoNotNotify] + public string UserId { get; set; } + + public async void DoAttentionUP() + { + var result = await AttentionUP(UserId, Attention == 1 ? 2 : 1); + if (!result) return; + if (Attention == 1) + { + Attention = -999; + CancelAttention?.Invoke(this, EventArgs.Empty); + } + else + { + Attention = 1; + AttentionDone?.Invoke(this, EventArgs.Empty); + } + } + + public async Task AttentionUP(string mid, int mode) + { + if (!SettingService.Account.Logined && !await Notify.ShowLoginDialog()) + { + Notify.ShowMessageToast("请先登录后再操作"); + return false; + } + + try + { + var results = await m_videoApi.Attention(mid, mode.ToString()).Request(); + if (results.status) + { + var data = await results.GetJson>(); + + if (data.success) + { + + Notify.ShowMessageToast("操作成功"); + return true; + } + else + { + Notify.ShowMessageToast(data.message); + return false; + } + } + else + { + Notify.ShowMessageToast(results.message); + return false; + } + } + catch (Exception ex) + { + var handel = HandelError(ex); + Notify.ShowMessageToast(handel.message); + return false; + } + } + } +} diff --git a/src/BiliLite.UWP/ViewModels/User/UserCenterInfoViewModel.cs b/src/BiliLite.UWP/ViewModels/User/UserCenterInfoViewModel.cs new file mode 100644 index 000000000..b0f16896c --- /dev/null +++ b/src/BiliLite.UWP/ViewModels/User/UserCenterInfoViewModel.cs @@ -0,0 +1,125 @@ +using Windows.UI; +using Windows.UI.Xaml.Media; +using BiliLite.Models.Common; +using BiliLite.Models.Common.User.UserDetails; +using BiliLite.ViewModels.Common; +using PropertyChanged; + +namespace BiliLite.ViewModels.User +{ + public class UserCenterInfoViewModel : BaseViewModel + { + [DoNotNotify] + public long Mid { get; set; } + + [DoNotNotify] + public string Name { get; set; } + + [DoNotNotify] + public string Sex { get; set; } + + [DoNotNotify] + public string Face { get; set; } + + [DoNotNotify] + public string Sign { get; set; } + + [DoNotNotify] + public int Rank { get; set; } + + [DoNotNotify] + public int Level { get; set; } + + [DoNotNotify] + public SolidColorBrush LevelColor => + Level switch + { + 2 => new SolidColorBrush(Colors.LightGreen), + 3 => new SolidColorBrush(Colors.LightBlue), + 4 => new SolidColorBrush(Colors.Yellow), + 5 => new SolidColorBrush(Colors.Orange), + 6 => new SolidColorBrush(Colors.Red), + 7 => new SolidColorBrush(Colors.HotPink), + 8 => new SolidColorBrush(Colors.Purple), + _ => new SolidColorBrush(Colors.Gray) + }; + + [DoNotNotify] + public int JoinTime { get; set; } + + [DoNotNotify] + public int Moral { get; set; } + + [DoNotNotify] + public int Silence { get; set; } + + [DoNotNotify] + public string Birthday { get; set; } + + [DoNotNotify] + public double Coins { get; set; } + + [DoNotNotify] + public bool FansBadge { get; set; } + + [DoNotNotify] + public UserCenterInfoOfficialModel Official { get; set; } + + [DoNotNotify] + public UserCenterInfoVipModel Vip { get; set; } + + [DoNotNotify] + public UserCenterInfoPendantModel Pendant { get; set; } + + [DoNotNotify] + public UserCenterInfoNameplateModel NamePlate { get; set; } + + public bool IsFollowed { get; set; } + + [DoNotNotify] + public string TopPhoto { get; set; } + + [DoNotNotify] + public UserCenterInfoLiveRoomModel LiveRoom { get; set; } + + [DoNotNotify] + public UserCenterSpaceStatModel Stat { get; set; } + + [DoNotNotify] + public string PendantStr + { + get + { + if (Pendant != null) + { + return Pendant.Image == "" ? Constants.App.TRANSPARENT_IMAGE : Pendant.Image; + } + else + { + return Constants.App.TRANSPARENT_IMAGE; + } + } + } + + [DoNotNotify] + public string Verify + { + get + { + if (Official == null) + { + return ""; + } + switch (Official.Type) + { + case 0: + return Constants.App.VERIFY_PERSONAL_IMAGE; + case 1: + return Constants.App.VERIFY_OGANIZATION_IMAGE; + default: + return Constants.App.TRANSPARENT_IMAGE; + } + } + } + } +} \ No newline at end of file diff --git a/src/BiliLite.UWP/ViewModels/User/UserDetailViewModel.cs b/src/BiliLite.UWP/ViewModels/User/UserDetailViewModel.cs new file mode 100644 index 000000000..57c0a89c1 --- /dev/null +++ b/src/BiliLite.UWP/ViewModels/User/UserDetailViewModel.cs @@ -0,0 +1,196 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using System.Windows.Input; +using AutoMapper; +using BiliLite.Extensions; +using BiliLite.Models; +using BiliLite.Models.Common; +using BiliLite.Models.Common.User.UserDetails; +using BiliLite.Models.Exceptions; +using BiliLite.Models.Requests.Api.User; +using BiliLite.Modules; +using BiliLite.Services; +using BiliLite.ViewModels.Common; +using Newtonsoft.Json.Linq; +using PropertyChanged; + +namespace BiliLite.ViewModels.User +{ + public class UserDetailViewModel : BaseViewModel + { + #region Fields + + private static readonly ILogger _logger = GlobalLogger.FromCurrentType(); + private readonly UserDetailAPI m_userDetailApi; + private readonly FollowAPI m_followApi; + private readonly IMapper m_mapper; + + #endregion + + #region Constructors + + public UserDetailViewModel(IMapper mapper) + { + m_mapper = mapper; + m_userDetailApi = new UserDetailAPI(); + m_followApi = new FollowAPI(); + AttentionCommand = new RelayCommand(DoAttentionUP); + } + + #endregion + + #region Properties + + public ICommand AttentionCommand { get; private set; } + + [DoNotNotify] + public string Mid { get; set; } + + public UserCenterInfoViewModel UserInfo { get; set; } + + #endregion + + #region Private Methods + + private async Task GetUserInfoCore() + { + var api = await m_userDetailApi.UserInfo(Mid); + var result = await api.Request(); + + if (!result.status) throw new CustomizedErrorException(result.message); + + var data = await result.GetData(); + if (!data.success) throw new CustomizedErrorException(data.message); + data.data.Stat = await GetSpaceStat(); + UserInfo = m_mapper.Map(data.data); + } + + private async Task GetStatCore() + { + var result = await m_userDetailApi.UserStat(Mid).Request(); + + if (!result.status) throw new CustomizedErrorException(result.message); + var data = await result.GetData(); + if (!data.success) throw new CustomizedErrorException(data.message); + return data.data; + } + + private async Task GetSpaceStatCore() + { + var result = await m_userDetailApi.Space(Mid).Request(); + + if (!result.status) + throw new CustomizedErrorException(result.message); + + var data = await result.GetData(); + if (!data.success) + throw new CustomizedErrorException(data.message); + + var stat = new UserCenterSpaceStatModel + { + ArticleCount = (data.data["article"]?["count"] ?? 0).ToInt32(), + VideoCount = (data.data["archive"]?["count"] ?? 0).ToInt32(), + FavouriteCount = (data.data["favourite2"]?["count"] ?? 0).ToInt32(), + Follower = data.data["card"]["fans"].ToInt32(), + Following = data.data["card"]["attention"].ToInt32(), + CollectionCount = (data.data?["ugc_season"]?["count"] ?? 0).ToInt32() + + (data.data?["series"]?["item"].ToArray().Length ?? 0) + }; + return stat; + } + + private async Task AttentionUPCore(string mid, int mode) + { + var results = await m_followApi.Attention(mid, mode.ToString()).Request(); + if (!results.status) + throw new CustomizedErrorException(results.message); + + var data = await results.GetJson>(); + if (!data.success) + throw new CustomizedErrorException(data.message); + + Notify.ShowMessageToast("操作成功"); + } + + #endregion + + #region Public Methods + + public async void GetUserInfo() + { + try + { + await GetUserInfoCore(); + } + catch (Exception ex) + { + _logger.Log("读取个人资料失败", LogType.Error, ex); + Notify.ShowMessageToast("读取个人资料失败"); + } + } + + public async Task GetStat() + { + try + { + return await GetStatCore(); + } + catch (Exception ex) + { + _logger.Log("读取个人资料失败", LogType.Error, ex); + return null; + } + } + + public async Task GetSpaceStat() + { + try + { + return await GetSpaceStatCore(); + } + catch (Exception ex) + { + _logger.Log("读取个人资料失败", LogType.Error, ex); + return null; + } + } + + public async void DoAttentionUP() + { + var result = await AttentionUP(UserInfo.Mid.ToString(), UserInfo.IsFollowed ? 2 : 1); + if (result) + { + UserInfo.IsFollowed = !UserInfo.IsFollowed; + } + } + + public async Task AttentionUP(string mid, int mode) + { + if (!SettingService.Account.Logined && !await Notify.ShowLoginDialog()) + { + Notify.ShowMessageToast("请先登录后再操作"); + return false; + } + + try + { + await AttentionUPCore(mid, mode); + return true; + } + catch (CustomizedErrorException ex) + { + Notify.ShowMessageToast(ex.Message); + return false; + } + catch (Exception ex) + { + var handel = HandelError(ex); + Notify.ShowMessageToast(handel.message); + return false; + } + } + + #endregion + } +} diff --git a/src/BiliLite.UWP/ViewModels/User/UserFollowingTagsFlyoutViewModel.cs b/src/BiliLite.UWP/ViewModels/User/UserFollowingTagsFlyoutViewModel.cs new file mode 100644 index 000000000..3b5dcaa5d --- /dev/null +++ b/src/BiliLite.UWP/ViewModels/User/UserFollowingTagsFlyoutViewModel.cs @@ -0,0 +1,124 @@ +using AutoMapper; +using BiliLite.ViewModels.Common; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using BiliLite.Extensions; +using BiliLite.Models.Exceptions; +using BiliLite.Models.Requests.Api.User; +using BiliLite.Modules.User.UserDetail; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using BiliLite.Models.Common; +using System; +using BiliLite.Services; + +namespace BiliLite.ViewModels.User +{ + public class UserFollowingTagsFlyoutViewModel : BaseViewModel + { + #region Fields + + private readonly IMapper m_mapper; + private readonly UserDetailAPI m_userDetailApi; + private List m_followingTags; + private string m_userId; + private static readonly ILogger _logger = GlobalLogger.FromCurrentType(); + + #endregion + + #region Constructors + + public UserFollowingTagsFlyoutViewModel(IMapper mapper) + { + m_mapper = mapper; + m_userDetailApi = new UserDetailAPI(); + } + + #endregion + + #region Properties + + public List FollowingTags { get; set; } + + #endregion + + #region Private Methods + + private async Task UpdateFollowingTagUserCore() + { + var followingTags = FollowingTags + .Where(x => x.UserInThisTag) + .Select(x => x.TagId) + .ToList(); + var api = m_userDetailApi.AddFollowingTagUsers(new List() { m_userId.ToInt64() }, followingTags); + var results = await api.Request(); + + if (!results.status) + throw new CustomizedErrorException(results.message); + var data = await results.GetData(); + if (!data.success) + throw new CustomizedErrorException(data.message); + m_followingTags = FollowingTags.ObjectCloneWithoutSerializable(); + } + + + private async Task GetFollowingTagsCore() + { + var api = m_userDetailApi.FollowingsTag(); + var results = await api.Request(); + if (!results.status) throw new CustomizedErrorException(results.message); + + var data = await results.GetData(); + var items = JsonConvert.DeserializeObject>(data.data.ToString()); + FollowingTags = m_mapper.Map>(items); + + m_followingTags = FollowingTags.ObjectCloneWithoutSerializable(); + } + + private async Task GetFollowingTagUser() + { + var api = m_userDetailApi.FollowingTagUser(m_userId.ToInt64()); + var results = await api.Request(); + if (!results.status) throw new CustomizedErrorException(results.message); + var data = await results.GetData>(); + foreach (var tag in FollowingTags) + { + tag.UserInThisTag = data.data.ContainsKey(tag.TagId.ToString()); + } + + m_followingTags = FollowingTags.ObjectCloneWithoutSerializable(); + } + + #endregion + + #region Public Methods + + public async Task Init(string userId) + { + m_userId = userId; + await GetFollowingTagsCore(); + await GetFollowingTagUser(); + } + + public async Task SaveFollowingTagUser() + { + try + { + await UpdateFollowingTagUserCore(); + } + catch (Exception ex) + { + _logger.Log("设置关注分组失败", LogType.Error, ex); + Notify.ShowMessageToast("设置关注分组失败"); + } + } + + public void CancelSaveFollowingTagUser() + { + FollowingTags = m_followingTags.ObjectCloneWithoutSerializable(); + } + + #endregion + } +} diff --git a/src/BiliLite.UWP/ViewModels/User/UserRelationFollowingTagViewModel.cs b/src/BiliLite.UWP/ViewModels/User/UserRelationFollowingTagViewModel.cs new file mode 100644 index 000000000..a4e31c7e1 --- /dev/null +++ b/src/BiliLite.UWP/ViewModels/User/UserRelationFollowingTagViewModel.cs @@ -0,0 +1,22 @@ +using BiliLite.ViewModels.Common; +using PropertyChanged; + +namespace BiliLite.ViewModels.User +{ + public class UserRelationFollowingTagViewModel : BaseViewModel + { + [DoNotNotify] + public long TagId { get; set; } + + [DoNotNotify] + public string Name { get; set; } + + [DoNotNotify] + public int Count { get; set; } + + [DoNotNotify] + public string Tip { get; set; } + + public bool UserInThisTag { get; set; } + } +} diff --git a/src/BiliLite.UWP/ViewModels/Video/VideoDetailPageViewModel.cs b/src/BiliLite.UWP/ViewModels/Video/VideoDetailPageViewModel.cs index 88399df16..956327d8a 100644 --- a/src/BiliLite.UWP/ViewModels/Video/VideoDetailPageViewModel.cs +++ b/src/BiliLite.UWP/ViewModels/Video/VideoDetailPageViewModel.cs @@ -50,7 +50,6 @@ public VideoDetailPageViewModel() DislikeCommand = new RelayCommand(DoDislike); LaunchUrlCommand = new RelayCommand(LaunchUrl); CoinCommand = new RelayCommand(DoCoin); - AttentionCommand = new RelayCommand(DoAttentionUP); SetStaffHeightCommand = new RelayCommand(SetStaffHeight); OpenRightInfoCommand = new RelayCommand(OpenRightInfo); } @@ -67,8 +66,6 @@ public VideoDetailPageViewModel() public ICommand CoinCommand { get; private set; } - public ICommand AttentionCommand { get; private set; } - public ICommand LaunchUrlCommand { get; private set; } public ICommand SetStaffHeightCommand { get; private set; } @@ -610,23 +607,6 @@ public async Task GetAttentionUp() } } - public async void DoAttentionUP() - { - var result = await AttentionUP(VideoInfo.Owner.Mid, VideoInfo.ReqUser.Attention == 1 ? 2 : 1); - if (result) - { - if (VideoInfo.ReqUser.Attention == 1) - { - VideoInfo.ReqUser.Attention = -999; - VideoInfo.OwnerExt.Fans -= 1; - } - else - { - VideoInfo.ReqUser.Attention = 1; - VideoInfo.OwnerExt.Fans += 1; - } - } - } public async Task AttentionUP(string mid, int mode) { if (!SettingService.Account.Logined && !await Notify.ShowLoginDialog()) diff --git a/src/BiliLite.gRPC/Api/AndroidGrpcRequestHeader.cs b/src/BiliLite.gRPC/Api/AndroidGrpcRequestHeader.cs new file mode 100644 index 000000000..c2f42c05a --- /dev/null +++ b/src/BiliLite.gRPC/Api/AndroidGrpcRequestHeader.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; + +namespace BiliLite.gRPC.Api +{ + internal class AndroidGrpcRequestHeader : IGrpcRequestHeader + { + public Dictionary GetHeaders(GrpcBiliUserInfo userInfo) + { + var config = new AndroidGrpcRequestHeaderConfig(userInfo); + var ua = $"Dalvik/{AndroidGrpcRequestHeaderConfig.dalvik_ver} " + + $"(Linux; U; Android {AndroidGrpcRequestHeaderConfig.os_ver}; {AndroidGrpcRequestHeaderConfig.brand} {AndroidGrpcRequestHeaderConfig.model}) {AndroidGrpcRequestHeaderConfig.app_ver} " + + $"os/android model/{AndroidGrpcRequestHeaderConfig.model} mobi_app/android build/{AndroidGrpcRequestHeaderConfig.build} " + + $"channel/{AndroidGrpcRequestHeaderConfig.channel} innerVer/{AndroidGrpcRequestHeaderConfig.build} osVer/{AndroidGrpcRequestHeaderConfig.os_ver} " + + $"network/{AndroidGrpcRequestHeaderConfig.network_type}"; + return new Dictionary + { + {"User-Agent",ua}, + {"APP-KEY","android"}, + {"x-bili-metadata-bin",config.GetMetadataBin()}, + {"authorization","identify_v1 " + userInfo.AccessKey}, + {"x-bili-device-bin",config.GetDeviceBin()}, + {"x-bili-network-bi",config.GetNetworkBin()}, + {"x-bili-restriction-bin",""}, + {"x-bili-locale-bin",config.GetLocaleBin()}, + {"x-bili-fawkes-req-bin",config.GetFawkesreqBin()}, + {"grpc-accept-encoding","identity"}, + {"grpc-timeout","17985446u"}, + {"env","prod"}, + {"Transfer-Encoding","chunked"}, + {"TE","trailers"}, + }; + } + } +} diff --git a/src/BiliLite.gRPC/Api/GrpcRequestHedaer.cs b/src/BiliLite.gRPC/Api/AndroidGrpcRequestHeaderConfig.cs similarity index 95% rename from src/BiliLite.gRPC/Api/GrpcRequestHedaer.cs rename to src/BiliLite.gRPC/Api/AndroidGrpcRequestHeaderConfig.cs index 1f223fffc..33aee208e 100644 --- a/src/BiliLite.gRPC/Api/GrpcRequestHedaer.cs +++ b/src/BiliLite.gRPC/Api/AndroidGrpcRequestHeaderConfig.cs @@ -9,11 +9,11 @@ namespace BiliLite.gRPC.Api { - public class GrpcRequestHeaderConfig + public class AndroidGrpcRequestHeaderConfig { - public GrpcRequestHeaderConfig(string accessKey) + public AndroidGrpcRequestHeaderConfig(GrpcBiliUserInfo userInfo) { - AccessKey = accessKey; + AccessKey = userInfo.AccessKey; } public string AccessKey { get; set; } diff --git a/src/BiliLite.gRPC/Api/Constants.cs b/src/BiliLite.gRPC/Api/Constants.cs new file mode 100644 index 000000000..685218118 --- /dev/null +++ b/src/BiliLite.gRPC/Api/Constants.cs @@ -0,0 +1,9 @@ +namespace BiliLite.gRPC.Api +{ + internal class Constants + { + public const string IOS_APP_KEY = "27eb53fc9058f8c3"; + + public const string ANDROID_APP_KEY = "1d8b6e7d45233436"; + } +} diff --git a/src/BiliLite.gRPC/Api/GrpcBiliUserInfo.cs b/src/BiliLite.gRPC/Api/GrpcBiliUserInfo.cs new file mode 100644 index 000000000..1ecf7c52f --- /dev/null +++ b/src/BiliLite.gRPC/Api/GrpcBiliUserInfo.cs @@ -0,0 +1,34 @@ +namespace BiliLite.gRPC.Api +{ + public class GrpcBiliUserInfo + { + public GrpcBiliUserInfo(string accessKey, long userMid, string cookies, string appKey) + { + AccessKey = accessKey; + UserMid = userMid; + Cookies = cookies; + AppKey = appKey; + } + + public GrpcBiliUserInfo(string accessKey, long userMid, string appKey) + { + AccessKey = accessKey; + UserMid = userMid; + AppKey = appKey; + } + + public void GrpcBiliUserInfo1() + { + } + + public string AccessKey { get; set; } + + public long UserMid { get; set; } + + public string Cookies { get; set; } + + public string AppKey { get; set; } + + public string Build { get; set; } + } +} diff --git a/src/BiliLite.gRPC/Api/GrpcRequest.cs b/src/BiliLite.gRPC/Api/GrpcRequest.cs index dae768185..d2f45ce71 100644 --- a/src/BiliLite.gRPC/Api/GrpcRequest.cs +++ b/src/BiliLite.gRPC/Api/GrpcRequest.cs @@ -29,33 +29,19 @@ public static GrpcRequest Instance /// 请求头 /// access_key /// 处理后的byte[] - public async Task SendMessage(string url, IMessage message, string access_key = "") + public async Task SendMessage(string url, IMessage message, GrpcBiliUserInfo userInfo) { try { - HttpClient httpClient = new HttpClient(); - var config = new GrpcRequestHeaderConfig(access_key); - //httpClient.DefaultRequestHeaders.Add("User-Agent", "Dalvik/2.1.0 (Linux; U; Android 5.1.1; ELE-AL00 Build/LMY48Z) 6.12.0 os/android model/ELE-AL00 mobi_app/android build/6120400 channel/bili innerVer/6120400 osVer/5.1.1 network/2"); - var ua = $"Dalvik/{GrpcRequestHeaderConfig.dalvik_ver} " - + $"(Linux; U; Android {GrpcRequestHeaderConfig.os_ver}; {GrpcRequestHeaderConfig.brand} {GrpcRequestHeaderConfig.model}) {GrpcRequestHeaderConfig.app_ver} " - + $"os/android model/{GrpcRequestHeaderConfig.model} mobi_app/android build/{GrpcRequestHeaderConfig.build} " - + $"channel/{GrpcRequestHeaderConfig.channel} innerVer/{GrpcRequestHeaderConfig.build} osVer/{GrpcRequestHeaderConfig.os_ver} " - + $"network/{GrpcRequestHeaderConfig.network_type}"; - httpClient.DefaultRequestHeaders.Add("User-Agent", ua); - httpClient.DefaultRequestHeaders.Add("APP-KEY", "android"); - httpClient.DefaultRequestHeaders.Add("x-bili-metadata-bin", config.GetMetadataBin()); - httpClient.DefaultRequestHeaders.Add("authorization", "identify_v1 " + access_key); - httpClient.DefaultRequestHeaders.Add("x-bili-device-bin", config.GetDeviceBin()); - httpClient.DefaultRequestHeaders.Add("x-bili-network-bin", config.GetNetworkBin()); - httpClient.DefaultRequestHeaders.Add("x-bili-restriction-bin", ""); - httpClient.DefaultRequestHeaders.Add("x-bili-locale-bin", config.GetLocaleBin()); - httpClient.DefaultRequestHeaders.Add("x-bili-fawkes-req-bin", config.GetFawkesreqBin()); - //httpClient.DefaultRequestHeaders.Add("grpc-encoding", "gzip"); - httpClient.DefaultRequestHeaders.Add("grpc-accept-encoding", "identity"); - httpClient.DefaultRequestHeaders.Add("grpc-timeout", "17985446u"); - httpClient.DefaultRequestHeaders.Add("env", "prod"); - httpClient.DefaultRequestHeaders.Add("Transfer-Encoding", "chunked"); - httpClient.DefaultRequestHeaders.Add("TE", "trailers"); + var httpClient = new HttpClient(); + + var grpcHeader = GrpcRequestHeaderFactory.GetGrpcRequestHeader(userInfo.AppKey); + + foreach (var headerItem in grpcHeader.GetHeaders(userInfo)) + { + httpClient.DefaultRequestHeaders.Add(headerItem.Key, headerItem.Value); + } + //httpClient.DefaultRequestVersion = new Version(1, 1); var messageBytes = message.ToByteArray(); diff --git a/src/BiliLite.gRPC/Api/GrpcRequestHeaderFactory.cs b/src/BiliLite.gRPC/Api/GrpcRequestHeaderFactory.cs new file mode 100644 index 000000000..67882f705 --- /dev/null +++ b/src/BiliLite.gRPC/Api/GrpcRequestHeaderFactory.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; + +namespace BiliLite.gRPC.Api +{ + internal static class GrpcRequestHeaderFactory + { + private static readonly Dictionary _grpcRequestHeaders + = new Dictionary() + { + { Constants.ANDROID_APP_KEY, new AndroidGrpcRequestHeader() }, + { Constants.IOS_APP_KEY, new IosGrpcRequestHeader() } + }; + + public static IGrpcRequestHeader GetGrpcRequestHeader(string appKey) + { + return !_grpcRequestHeaders.TryGetValue(appKey, out var header) ? null : header; + } + } +} diff --git a/src/BiliLite.gRPC/Api/IGrpcRequestHeader.cs b/src/BiliLite.gRPC/Api/IGrpcRequestHeader.cs new file mode 100644 index 000000000..f03b27da2 --- /dev/null +++ b/src/BiliLite.gRPC/Api/IGrpcRequestHeader.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; + +namespace BiliLite.gRPC.Api +{ + internal interface IGrpcRequestHeader + { + Dictionary GetHeaders(GrpcBiliUserInfo userInfo); + } +} diff --git a/src/BiliLite.gRPC/Api/IosGrpcRequestHeader.cs b/src/BiliLite.gRPC/Api/IosGrpcRequestHeader.cs new file mode 100644 index 000000000..26b11d4cb --- /dev/null +++ b/src/BiliLite.gRPC/Api/IosGrpcRequestHeader.cs @@ -0,0 +1,32 @@ +using System.Collections.Generic; + +namespace BiliLite.gRPC.Api +{ + internal class IosGrpcRequestHeader : IGrpcRequestHeader + { + public Dictionary GetHeaders(GrpcBiliUserInfo userInfo) + { + var config = new IosGrpcRequestHeaderConfig(userInfo); + var ua = $"bili-universal/{config.Build} os/ios model/{config.Model} mobi_app/{config.MobiApp} osVer/{config.OsVer} network/2"; + return new Dictionary + { + { "User-Agent", ua }, + {"x-bili-gaia-vtoken",""}, + { "x-bili-aurora-eid", config.GetAuroraEid() }, + { "x-bili-mid", userInfo.UserMid.ToString() }, + { "x-bili-aurora-zone", "" }, + { "x-bili-trace-id", config.GenTraceId() }, + {"authorization",config.GetAuthorization()}, + { "buvid", config.Buvid }, + { "x-bili-fawkes-req-bin", config.GetFawkesreqBin() }, + { "x-bili-metadata-bin", config.GetMetadataBin() }, + { "x-bili-device-bin", config.GetDeviceBin() }, + { "x-bili-network-bin", config.GetNetworkBin() }, + { "x-bili-restriction-bin", "" }, + { "x-bili-locale-bin", config.GetLocaleBin() }, + { "x-bili-exps-bin", "" }, + { "te", "trailers" }, + }; + } + } +} diff --git a/src/BiliLite.gRPC/Api/IosGrpcRequestHeaderConfig.cs b/src/BiliLite.gRPC/Api/IosGrpcRequestHeaderConfig.cs new file mode 100644 index 000000000..b20e93736 --- /dev/null +++ b/src/BiliLite.gRPC/Api/IosGrpcRequestHeaderConfig.cs @@ -0,0 +1,172 @@ +using System.Text; +using System; +using System.Collections.Generic; +using System.Linq; +using Bilibili.Metadata; +using Bilibili.Metadata.Device; +using Bilibili.Metadata.Fawkes; +using Bilibili.Metadata.Locale; +using Google.Protobuf; +using Bilibili.Metadata.Network; + +namespace BiliLite.gRPC.Api +{ + internal class IosGrpcRequestHeaderConfig + { + private readonly GrpcBiliUserInfo m_userinfo; + private const string MOBI_APP = "iphone"; + private const string PLATFORM = "ios"; + private const string BUVID = "Y3436F391696935149CCA03C1537118D2080"; + private const string ENV = "prod"; + private const string BRAND = "Apple"; + private const string MODEL = "iPad 9G"; + private const string OS_VER = "15.5"; + private const string VERSION_NAME = "7.67.0"; + private const string CHANNEL = "apple"; + private const int BUILD = 76700100; + private const string DEVICE = "pad"; + private const int APP_ID = 1; + private const string REGION = "CN"; + private const string LANGUAGE = "zh"; + + public IosGrpcRequestHeaderConfig(GrpcBiliUserInfo userInfo) + { + m_userinfo = userInfo; + } + + public string Buvid => BUVID; + + public int Build => BUILD; + + public string Model => MODEL; + + public string MobiApp => MOBI_APP; + + public string OsVer => OS_VER; + + public string GetLocaleBin() + { + var msg = new Locale + { + CLocale = new LocaleIds + { + Language = LANGUAGE, + Region = REGION + }, + SLocale = new LocaleIds + { + Language = LANGUAGE, + Region = REGION + } + }; + return ToBase64(msg.ToByteArray()); + } + + public string GetNetworkBin() + { + var msg = new Network + { + Type = NetworkType.Wifi + }; + return ToBase64(msg.ToByteArray()); + } + + public string GetDeviceBin() + { + var msg = new Device + { + AppId = APP_ID, + Build = BUILD, + Buvid = BUVID, + MobiApp = MOBI_APP, + Platform = PLATFORM, + Device_ = DEVICE, + Channel = CHANNEL, + Brand = BRAND, + Model = MODEL, + Osver = OS_VER + }; + return ToBase64(msg.ToByteArray()); + } + + public string GetMetadataBin() + { + var msg = new Metadata + { + AccessKey = m_userinfo.AccessKey, + MobiApp = MOBI_APP, + Device = DEVICE, + Build = BUILD, + Channel = CHANNEL, + Buvid = BUVID, + Platform = PLATFORM + }; + return ToBase64(msg.ToByteArray()); + } + + public string GetFawkesreqBin() + { + var msg = new FawkesReq(); + msg.Appkey = MOBI_APP; + msg.Env = ENV; + return ToBase64(msg.ToByteArray()); + } + + public string GetAuroraEid() + { + if (m_userinfo.UserMid == 0) return ""; + var resultByte = new List(64); + var midByte = Encoding.UTF8.GetBytes(m_userinfo.UserMid.ToString()); + resultByte.AddRange(midByte.Select((t, i) => (byte)(t ^ (new byte[] { 97, 100, 49, 118, 97, 52, 54, 97, 55, 108, 122, 97 }[i % 12])))); + + return ToBase64(resultByte.ToArray()).TrimEnd('='); + } + + public string GetAuthorization() + { + return string.IsNullOrEmpty(m_userinfo.AccessKey) ? "" : $"identify_v1 {m_userinfo.AccessKey}"; + } + + public string GenTraceId() + { + var randomId = GenRandomString(32); + var randomTraceId = new StringBuilder(40); + randomTraceId.Append(randomId.Substring(0, 24)); + var bArr = new byte[3]; + var ts = DateTimeOffset.Now.ToUnixTimeSeconds(); + for (var i = bArr.Length - 1; i >= 0; i--) + { + ts >>= 8; + bArr[i] = (byte)((ts / 128) % 2 == 0 ? ts % 256 : ts % 256 - 256); + } + foreach (var b in bArr) + { + randomTraceId.Append(b.ToString("x2")); + } + randomTraceId.Append(randomId.Substring(30, 2)); + var randomTraceIdFinal = new StringBuilder(64); + randomTraceIdFinal.Append(randomTraceId); + randomTraceIdFinal.Append(":"); + randomTraceIdFinal.Append(randomTraceId.ToString().Substring(16, 16)); + randomTraceIdFinal.Append(":0:0"); + return randomTraceIdFinal.ToString(); + } + + private string GenRandomString(int length) + { + var chars = "0123456789abcdefghijklmnopqrstuvwxyz"; + var sb = new StringBuilder(length); + var rnd = new Random(); + for (var i = 0; i < length; i++) + { + sb.Append(chars[rnd.Next(chars.Length)]); + } + return sb.ToString(); + } + + private string ToBase64(byte[] data) + { + return Convert.ToBase64String(data).TrimEnd('='); + } + } +} diff --git a/src/BiliLite.gRPC/Api/PlayURL.cs b/src/BiliLite.gRPC/Api/PlayURL.cs index 442bcbf3a..76f6be616 100644 --- a/src/BiliLite.gRPC/Api/PlayURL.cs +++ b/src/BiliLite.gRPC/Api/PlayURL.cs @@ -16,7 +16,7 @@ public class PlayURL /// 编码,支持h264及h265 /// 登录后access_key /// - public async Task VideoPlayView(long aid, long cid,int qn,int fnval , CodeType codeType, string access_key="") + public async Task VideoPlayView(long aid, long cid,int qn,int fnval , CodeType codeType, GrpcBiliUserInfo userInfo) { var message = new PlayViewReq() { Aid= aid, @@ -26,7 +26,7 @@ public async Task VideoPlayView(long aid, long cid,int qn,int fnv Fourk=true, PreferCodecType= codeType }; - var result=await GrpcRequest.Instance.SendMessage("https://app.bilibili.com/bilibili.app.playurl.v1.PlayURL/PlayView", message, access_key); + var result=await GrpcRequest.Instance.SendMessage("https://app.bilibili.com/bilibili.app.playurl.v1.PlayURL/PlayView", message, userInfo); if (result.status) { return PlayViewReply.Parser.ParseFrom(result.results); @@ -46,7 +46,7 @@ public async Task VideoPlayView(long aid, long cid,int qn,int fnv /// 编码,支持h264及h265 /// 登录后access_key /// - public async Task BangumiPlayView(long epid, long cid, int qn, int fnval, CodeType codeType, string access_key = "") + public async Task BangumiPlayView(long epid, long cid, int qn, int fnval, CodeType codeType, GrpcBiliUserInfo userInfo) { var message = new PlayViewReq() { @@ -57,7 +57,7 @@ public async Task BangumiPlayView(long epid, long cid, int qn, in Fourk = true, PreferCodecType = codeType }; - var result = await GrpcRequest.Instance.SendMessage("https://app.bilibili.com/bilibili.pgc.gateway.player.v1.PlayURL/PlayView", message, access_key); + var result = await GrpcRequest.Instance.SendMessage("https://app.bilibili.com/bilibili.pgc.gateway.player.v1.PlayURL/PlayView", message, userInfo); if (result.status) { return PlayViewReply.Parser.ParseFrom(result.results);