From 8a10573fbfaabaa9cc09ab111b8baa0aec43064c Mon Sep 17 00:00:00 2001 From: Enrico Ludwig Date: Wed, 4 Sep 2024 00:10:18 +0200 Subject: [PATCH] A 'little' code cleanup --- App.axaml.cs | 168 +-------------- Controls/RaidButton.axaml | 95 --------- Controls/RaidButton.axaml.cs | 13 -- Extensions/DataContextExtensions.cs | 5 +- Misc/Constants.cs | 30 +++ Misc/Tools.cs | 15 +- Models/TwitchChannel.cs | 8 +- Services/ITwitchDataService.cs | 16 +- Services/Implementations/TwitchDataService.cs | 132 +++++++++++- .../Implementations/TwitchPubSubService.cs | 37 +++- ViewModels/MainWindowViewModel.cs | 9 +- ViewModels/RaidButtonViewModel.cs | 195 ------------------ Views/AboutWindow.axaml.cs | 2 - Views/AddChannelWindow.axaml.cs | 2 - Views/MainWindow.axaml | 5 +- Views/MainWindow.axaml.cs | 11 - 16 files changed, 232 insertions(+), 511 deletions(-) delete mode 100644 Controls/RaidButton.axaml delete mode 100644 Controls/RaidButton.axaml.cs create mode 100644 Misc/Constants.cs delete mode 100644 ViewModels/RaidButtonViewModel.cs diff --git a/App.axaml.cs b/App.axaml.cs index e66e68b..9f0e640 100644 --- a/App.axaml.cs +++ b/App.axaml.cs @@ -1,6 +1,4 @@ using System; -using System.IO; -using System.Linq; using Avalonia; using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Data.Core.Plugins; @@ -11,180 +9,30 @@ using BetterRaid.Services.Implementations; using BetterRaid.ViewModels; using BetterRaid.Views; using Microsoft.Extensions.DependencyInjection; -using TwitchLib.Api; -using TwitchLib.PubSub; namespace BetterRaid; public partial class App : Application { - private readonly ServiceCollection _services = []; - private ServiceProvider? _provider; + private static readonly ServiceCollection Services = []; + private static ServiceProvider? _serviceProvider; - private static TwitchAPI? _twitchApi; - private static bool _hasUserZnSubbed; - private static string _betterRaidDataPath = ""; - private static string _twitchBroadcasterId = ""; - private static string _twitchOAuthAccessToken = ""; - private static string _twitchOAuthAccessTokenFilePath = ""; - private const string TokenClientId = "kkxu4jorjrrc5jch1ito5i61hbev2o"; - private const string TwitchOAuthRedirectUrl = "http://localhost:9900"; - private const string TwitchOAuthResponseType = "token"; - - private static readonly string[] TwitchOAuthScopes = [ - "channel:manage:raids", - "user:read:subscriptions" - ]; - - internal static readonly string TwitchOAuthUrl = $"https://id.twitch.tv/oauth2/authorize" - + $"?client_id={TokenClientId}" - + $"&redirect_uri={TwitchOAuthRedirectUrl}" - + $"&response_type={TwitchOAuthResponseType}" - + $"&scope={string.Join("+", TwitchOAuthScopes)}"; - - public const string ChannelPlaceholderImageUrl = "https://cdn.pixabay.com/photo/2018/11/13/22/01/avatar-3814081_1280.png"; - - public static TwitchAPI? TwitchApi => _twitchApi; - public static bool HasUserZnSubbed => _hasUserZnSubbed; - public static string BetterRaidDataPath => _betterRaidDataPath; - - public IServiceProvider? Provider => _provider; - public static string? TwitchBroadcasterId => _twitchBroadcasterId; - - public static string TwitchOAuthAccessToken - { - get => _twitchOAuthAccessToken; - set - { - _twitchOAuthAccessToken = value; - InitTwitchClient(true); - } - } + public static IServiceProvider? ServiceProvider => _serviceProvider; public override void Initialize() { - LoadTwitchToken(); InitializeServices(); - AvaloniaXamlLoader.Load(_provider, this); - } - - private void LoadTwitchToken() - { - var userHomeDir = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); - - _betterRaidDataPath = Environment.OSVersion.Platform switch - { - PlatformID.Win32NT => Path.Combine(userHomeDir, "AppData", "Roaming", "BetterRaid"), - PlatformID.Unix => Path.Combine(userHomeDir, ".config", "BetterRaid"), - PlatformID.MacOSX => Path.Combine(userHomeDir, "Library", "Application Support", "BetterRaid"), - _ => throw new PlatformNotSupportedException($"Your platform '{Environment.OSVersion.Platform}' is not supported. Please report this issue here: https://www.github.com/zion-networks/BetterRaid/issues") - }; - - if (!Directory.Exists(_betterRaidDataPath)) - { - var di = Directory.CreateDirectory(_betterRaidDataPath); - if (di.Exists == false) - { - throw new Exception($"Failed to create directory '{_betterRaidDataPath}'"); - } - } - - _twitchOAuthAccessTokenFilePath = Path.Combine(_betterRaidDataPath, ".access_token"); - - if (!File.Exists(_twitchOAuthAccessTokenFilePath)) - return; - - _twitchOAuthAccessToken = File.ReadAllText(_twitchOAuthAccessTokenFilePath); - InitTwitchClient(); + AvaloniaXamlLoader.Load(_serviceProvider, this); } private void InitializeServices() { - _services.AddSingleton(); - _services.AddSingleton(); - _services.AddTransient(); + Services.AddSingleton(); + Services.AddSingleton(); + Services.AddTransient(); - _provider = _services.BuildServiceProvider(); - } - - public static void InitTwitchClient(bool overrideToken = false) - { - Console.WriteLine("[INFO] Initializing Twitch Client..."); - - if (string.IsNullOrEmpty(_twitchOAuthAccessToken)) - { - Console.WriteLine("[ERROR] Failed to initialize Twitch Client: Access Token is empty!"); - return; - } - - _twitchApi = new TwitchAPI - { - Settings = - { - ClientId = TokenClientId, - AccessToken = _twitchOAuthAccessToken - } - }; - - Console.WriteLine("[INFO] Testing Twitch API connection..."); - - var user = _twitchApi.Helix.Users.GetUsersAsync().Result.Users.FirstOrDefault(); - if (user == null) - { - _twitchApi = null; - Console.WriteLine("[ERROR] Failed to connect to Twitch API!"); - return; - } - - var channel = _twitchApi.Helix.Search - .SearchChannelsAsync(user.Login).Result.Channels - .FirstOrDefault(c => c.BroadcasterLogin == user.Login); - - var userSubs = _twitchApi.Helix.Subscriptions.CheckUserSubscriptionAsync( - userId: user.Id, - broadcasterId: "1120558409" - ).Result.Data; - - if (userSubs is { Length: > 0 } && userSubs.Any(s => s.BroadcasterId == "1120558409")) - { - _hasUserZnSubbed = true; - } - - if (channel == null) - { - Console.WriteLine($"[ERROR] Failed to get channel information for '{user.Login}'!"); - return; - } - - _twitchBroadcasterId = channel.Id; - - Console.WriteLine("[INFO] Connected to Twitch API as '{0}'!", user.DisplayName); - - if (!overrideToken) - return; - - File.WriteAllText(_twitchOAuthAccessTokenFilePath, _twitchOAuthAccessToken); - - switch (Environment.OSVersion.Platform) - { - case PlatformID.Win32NT: - File.SetAttributes(_twitchOAuthAccessTokenFilePath, File.GetAttributes(_twitchOAuthAccessTokenFilePath) | FileAttributes.Hidden); - break; - - case PlatformID.Unix: -#pragma warning disable CA1416 // Validate platform compatibility - File.SetUnixFileMode(_twitchOAuthAccessTokenFilePath, UnixFileMode.UserRead); -#pragma warning restore CA1416 // Validate platform compatibility - break; - - case PlatformID.MacOSX: - File.SetAttributes(_twitchOAuthAccessTokenFilePath, File.GetAttributes(_twitchOAuthAccessTokenFilePath) | FileAttributes.Hidden); - break; - - default: - throw new PlatformNotSupportedException($"Your platform '{Environment.OSVersion.Platform}' is not supported. Please report this issue here: https://www.github.com/zion-networks/BetterRaid/issues"); - } + _serviceProvider = Services.BuildServiceProvider(); } public override void OnFrameworkInitializationCompleted() diff --git a/Controls/RaidButton.axaml b/Controls/RaidButton.axaml deleted file mode 100644 index 106dfa0..0000000 --- a/Controls/RaidButton.axaml +++ /dev/null @@ -1,95 +0,0 @@ - - - - - - - - - - - - - - diff --git a/Controls/RaidButton.axaml.cs b/Controls/RaidButton.axaml.cs deleted file mode 100644 index 023c2c6..0000000 --- a/Controls/RaidButton.axaml.cs +++ /dev/null @@ -1,13 +0,0 @@ -using Avalonia; -using Avalonia.Controls; -using Avalonia.Markup.Xaml; - -namespace BetterRaid; - -public partial class RaidButton : UserControl -{ - public RaidButton() - { - InitializeComponent(); - } -} \ No newline at end of file diff --git a/Extensions/DataContextExtensions.cs b/Extensions/DataContextExtensions.cs index 0237d51..527528d 100644 --- a/Extensions/DataContextExtensions.cs +++ b/Extensions/DataContextExtensions.cs @@ -1,5 +1,4 @@ using Avalonia; -using Avalonia.Controls; using Microsoft.Extensions.DependencyInjection; namespace BetterRaid.Extensions; @@ -13,10 +12,10 @@ public static class DataContextExtensions public static void InjectDataContext(this StyledElement e) where T : class { - if (Application.Current is not App { Provider: not null } app) + if (App.ServiceProvider == null) return; - var vm = app.Provider.GetRequiredService(); + var vm = App.ServiceProvider.GetRequiredService(); e.DataContext = vm; } } \ No newline at end of file diff --git a/Misc/Constants.cs b/Misc/Constants.cs new file mode 100644 index 0000000..87b0952 --- /dev/null +++ b/Misc/Constants.cs @@ -0,0 +1,30 @@ +using System; +using System.IO; + +namespace BetterRaid.Misc; + +public class Constants +{ + // General + public const string ChannelPlaceholderImageUrl = "https://cdn.pixabay.com/photo/2018/11/13/22/01/avatar-3814081_1280.png"; + + // Paths + public static string BetterRaidDataPath => Environment.OSVersion.Platform switch + { + PlatformID.Win32NT => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "AppData", "Roaming", "BetterRaid"), + PlatformID.Unix => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".config", "BetterRaid"), + PlatformID.MacOSX => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "Library", "Application Support", "BetterRaid"), + _ => throw new PlatformNotSupportedException($"Your platform '{Environment.OSVersion.Platform}' is not supported. Please report this issue here: https://www.github.com/zion-networks/BetterRaid/issues") + }; + public static string TwitchOAuthAccessTokenFilePath => Path.Combine(BetterRaidDataPath, ".access_token"); + public static string DatabaseFilePath => Path.Combine(BetterRaidDataPath, "db.json"); + + // Twitch API + public const string TwitchClientId = "kkxu4jorjrrc5jch1ito5i61hbev2o"; + public const string TwitchOAuthRedirectUrl = "http://localhost:9900"; + public const string TwitchOAuthResponseType = "token"; + public static readonly string[] TwitchOAuthScopes = [ + "channel:manage:raids", // Allows the application to start and cancel raids on the broadcaster's channel + "user:read:subscriptions" // Allows the application to check, if the user has subscribed to the developer's channel + ]; +} \ No newline at end of file diff --git a/Misc/Tools.cs b/Misc/Tools.cs index de2a89e..069d953 100644 --- a/Misc/Tools.cs +++ b/Misc/Tools.cs @@ -6,13 +6,13 @@ using System.Text; using System.Text.Json.Nodes; using System.Threading; using System.Threading.Tasks; +using BetterRaid.Services; namespace BetterRaid.Misc; public static class Tools { private static HttpListener? _oauthListener; - private static Task? _oauthWaiterTask; // Source: https://stackoverflow.com/a/43232486 public static void StartOAuthLogin(string url, Action? callback = null, CancellationToken token = default) @@ -20,10 +20,10 @@ public static class Tools if (_oauthListener == null) { _oauthListener = new HttpListener(); - _oauthListener.Prefixes.Add("http://localhost:9900/"); + _oauthListener.Prefixes.Add(Constants.TwitchOAuthRedirectUrl); _oauthListener.Start(); - _oauthWaiterTask = WaitForCallback(callback, token); + Task.Run(() => WaitForCallback(callback, token), token); } OpenUrl(url); @@ -84,7 +84,7 @@ public static class Tools req.InputStream.Close(); var json = data.ToString(); - var jsonData = JsonObject.Parse(json); + var jsonData = JsonNode.Parse(json); if (jsonData == null) { @@ -106,7 +106,12 @@ public static class Tools } var accessToken = jsonData["access_token"]?.ToString(); - App.TwitchOAuthAccessToken = accessToken!; + + var dataService = App.ServiceProvider?.GetService(typeof(ITwitchDataService)); + if (dataService is ITwitchDataService twitchDataService) + { + twitchDataService.ConnectApi(Constants.TwitchClientId, accessToken!); + } res.StatusCode = 200; res.Close(); diff --git a/Models/TwitchChannel.cs b/Models/TwitchChannel.cs index 91c2f42..1d134cc 100644 --- a/Models/TwitchChannel.cs +++ b/Models/TwitchChannel.cs @@ -1,9 +1,9 @@ using System; -using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Runtime.CompilerServices; using BetterRaid.Attributes; +using BetterRaid.Services; namespace BetterRaid.Models; @@ -142,15 +142,15 @@ public class TwitchChannel : INotifyPropertyChanged Name = channelName; } - public void InitChannel() + public void UpdateChannelData(ITwitchDataService dataService) { - var channel = App.TwitchApi?.Helix.Search.SearchChannelsAsync(Name).Result.Channels + var channel = dataService.TwitchApi.Helix.Search.SearchChannelsAsync(Name).Result.Channels .FirstOrDefault(c => c.BroadcasterLogin.Equals(Name, StringComparison.CurrentCultureIgnoreCase)); if (channel == null) return; - var stream = App.TwitchApi?.Helix.Streams.GetStreamsAsync(userLogins: [ Name ]).Result.Streams + var stream = dataService.TwitchApi.Helix.Streams.GetStreamsAsync(userLogins: [ Name ]).Result.Streams .FirstOrDefault(s => s.UserLogin.Equals(Name, StringComparison.CurrentCultureIgnoreCase)); BroadcasterId = channel.Id; diff --git a/Services/ITwitchDataService.cs b/Services/ITwitchDataService.cs index e41a728..664de42 100644 --- a/Services/ITwitchDataService.cs +++ b/Services/ITwitchDataService.cs @@ -1,12 +1,26 @@ +using System.ComponentModel; +using BetterRaid.Models; +using TwitchLib.Api; + namespace BetterRaid.Services; public interface ITwitchDataService { + public string? AccessToken { get; set; } + public TwitchChannel? UserChannel { get; set; } + public TwitchAPI TwitchApi { get; } public bool IsRaidStarted { get; set; } - + + public void ConnectApi(string clientId, string accessToken); + public void SaveAccessToken(string token); + public bool TryGetUserChannel(out TwitchChannel? userChannel); + public string GetOAuthUrl(); public void StartRaid(string from, string to); public void StartRaidCommand(object? arg); public void StopRaid(); public void StopRaidCommand(); public void OpenChannelCommand(object? arg); + + public event PropertyChangingEventHandler? PropertyChanging; + public event PropertyChangedEventHandler? PropertyChanged; } \ No newline at end of file diff --git a/Services/Implementations/TwitchDataService.cs b/Services/Implementations/TwitchDataService.cs index e329db4..2f9350c 100644 --- a/Services/Implementations/TwitchDataService.cs +++ b/Services/Implementations/TwitchDataService.cs @@ -1,36 +1,142 @@ +using System; using System.Collections.Generic; using System.ComponentModel; +using System.IO; using System.Runtime.CompilerServices; using BetterRaid.Misc; +using BetterRaid.Models; +using TwitchLib.Api; namespace BetterRaid.Services.Implementations; -public class TwitchDataService : ITwitchDataService, INotifyPropertyChanged +public class TwitchDataService : ITwitchDataService, INotifyPropertyChanged, INotifyPropertyChanging { private bool _isRaidStarted; + private TwitchChannel? _userChannel; + public string AccessToken { get; set; } = string.Empty; + public bool IsRaidStarted { get => _isRaidStarted; set => SetField(ref _isRaidStarted, value); } + + public TwitchChannel? UserChannel + { + get => _userChannel; + set + { + if (_userChannel != null && _userChannel.Name?.Equals(value?.Name) == true) + return; + + SetField(ref _userChannel, value); + + _userChannel?.UpdateChannelData(this); + } + } + + public TwitchAPI TwitchApi { get; } + + public TwitchDataService() + { + TwitchApi = new TwitchAPI(); + + if (TryLoadAccessToken(out var token)) + { + Console.WriteLine($"[INFO][{nameof(TwitchDataService)}] Found access token."); + ConnectApi(Constants.TwitchClientId, token); + } + else + { + Console.WriteLine($"[INFO][{nameof(TwitchDataService)}] No access token found."); + } + } + + public void ConnectApi(string clientId, string accessToken) + { + Console.WriteLine($"[INFO][{nameof(TwitchDataService)}] Connecting to Twitch API ..."); + + AccessToken = accessToken; + + TwitchApi.Settings.ClientId = clientId; + TwitchApi.Settings.AccessToken = accessToken; + + if (TryGetUserChannel(out var channel)) + { + UserChannel = channel; + Console.WriteLine($"[INFO][{nameof(TwitchDataService)}] Connected to Twitch API as {channel?.Name}."); + } + else + { + UserChannel = null; + + Console.WriteLine($"[ERROR][{nameof(TwitchDataService)}] Could not get user channel."); + Console.WriteLine($"[ERROR][{nameof(TwitchDataService)}] Failed to connect to Twitch API."); + } + } + + private bool TryLoadAccessToken(out string token) + { + token = string.Empty; + + if (!File.Exists(Constants.TwitchOAuthAccessTokenFilePath)) + return false; + + token = File.ReadAllText(Constants.TwitchOAuthAccessTokenFilePath); + return true; + } + + public void SaveAccessToken(string token) + { + File.WriteAllText(Constants.TwitchOAuthAccessTokenFilePath, token); + } + + public bool TryGetUserChannel(out TwitchChannel? userChannel) + { + userChannel = null; + + try + { + var user = TwitchApi.Helix.Users.GetUsersAsync().Result.Users[0]; + userChannel = new TwitchChannel(user.Login); + + return true; + } + catch (Exception e) + { + Console.WriteLine($"[ERROR][{nameof(TwitchDataService)}] {e.Message}"); + return false; + } + } + + public string GetOAuthUrl() + { + var scopes = string.Join("+", Constants.TwitchOAuthScopes); + + return $"https://id.twitch.tv/oauth2/authorize" + + $"?client_id={Constants.TwitchClientId}" + + $"&redirect_uri={Constants.TwitchOAuthRedirectUrl}" + + $"&response_type={Constants.TwitchOAuthResponseType}" + + $"&scope={scopes}"; + } public void StartRaid(string from, string to) { // TODO: Also check, if the logged in user is live - App.TwitchApi?.Helix.Raids.StartRaidAsync(from, to); + TwitchApi.Helix.Raids.StartRaidAsync(from, to); IsRaidStarted = true; } public void StartRaidCommand(object? arg) { - if (arg == null || App.TwitchBroadcasterId == null) + if (arg == null || UserChannel?.BroadcasterId == null) { return; } - var from = App.TwitchBroadcasterId; + var from = UserChannel.BroadcasterId!; var to = arg.ToString()!; StartRaid(from, to); @@ -38,10 +144,13 @@ public class TwitchDataService : ITwitchDataService, INotifyPropertyChanged public void StopRaid() { + if (UserChannel?.BroadcasterId == null) + return; + if (IsRaidStarted == false) return; - App.TwitchApi?.Helix.Raids.CancelRaidAsync(App.TwitchBroadcasterId); + TwitchApi.Helix.Raids.CancelRaidAsync(UserChannel.BroadcasterId); IsRaidStarted = false; } @@ -61,18 +170,29 @@ public class TwitchDataService : ITwitchDataService, INotifyPropertyChanged Tools.OpenUrl(url); } + public event PropertyChangingEventHandler? PropertyChanging; public event PropertyChangedEventHandler? PropertyChanged; protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } + + protected virtual void OnPropertyChanging([CallerMemberName] string? propertyName = null) + { + PropertyChanging?.Invoke(this, new PropertyChangingEventArgs(propertyName)); + } protected bool SetField(ref T field, T value, [CallerMemberName] string? propertyName = null) { - if (EqualityComparer.Default.Equals(field, value)) return false; + if (EqualityComparer.Default.Equals(field, value)) + return false; + + OnPropertyChanging(propertyName); field = value; OnPropertyChanged(propertyName); + return true; } + } \ No newline at end of file diff --git a/Services/Implementations/TwitchPubSubService.cs b/Services/Implementations/TwitchPubSubService.cs index 21e02b6..1bd441e 100644 --- a/Services/Implementations/TwitchPubSubService.cs +++ b/Services/Implementations/TwitchPubSubService.cs @@ -14,9 +14,12 @@ public class TwitchPubSubService : ITwitchPubSubService { private readonly Dictionary> _targets = new(); private readonly TwitchPubSub _sub; + private readonly ITwitchDataService _dataService; - public TwitchPubSubService() + public TwitchPubSubService(ITwitchDataService dataService) { + _dataService = dataService; + _sub = new TwitchPubSub(); _sub.OnPubSubServiceConnected += OnSubOnOnPubSubServiceConnected; @@ -26,6 +29,29 @@ public class TwitchPubSubService : ITwitchPubSubService _sub.OnViewCount += OnSubOnOnViewCount; _sub.Connect(); + + if (_dataService.UserChannel != null) + { + RegisterReceiver(_dataService.UserChannel); + } + + _dataService.PropertyChanging += (_, args) => + { + if (args.PropertyName != nameof(_dataService.UserChannel)) + return; + + if (_dataService.UserChannel != null) + UnregisterReceiver(_dataService.UserChannel); + }; + + _dataService.PropertyChanged += (_, args) => + { + if (args.PropertyName != nameof(_dataService.UserChannel)) + return; + + if (_dataService.UserChannel != null) + RegisterReceiver(_dataService.UserChannel); + }; } private void OnSubOnOnViewCount(object? sender, OnViewCountArgs args) @@ -145,7 +171,7 @@ public class TwitchPubSubService : ITwitchPubSubService } _sub.ListenToVideoPlayback(channelId); - _sub.SendTopics(App.TwitchOAuthAccessToken); + _sub.SendTopics(_dataService.AccessToken); break; } @@ -156,10 +182,9 @@ public class TwitchPubSubService : ITwitchPubSubService { ArgumentNullException.ThrowIfNull(receiver, nameof(receiver)); - foreach (var target in _targets) + foreach (var (topic, listeners) in _targets) { - var topic = target.Key; - var listener = target.Value.Where(x => x.Instance == receiver).ToList(); + var listener = listeners.Where(x => x.Instance == receiver).ToList(); foreach (var l in listener) { @@ -177,7 +202,7 @@ public class TwitchPubSubService : ITwitchPubSubService break; case PubSubType.VideoPlayback: _sub.ListenToVideoPlayback(l.ChannelId); - _sub.SendTopics(App.TwitchOAuthAccessToken, true); + _sub.SendTopics(_dataService.AccessToken, true); break; } } diff --git a/ViewModels/MainWindowViewModel.cs b/ViewModels/MainWindowViewModel.cs index ab6f4a2..00a62fe 100644 --- a/ViewModels/MainWindowViewModel.cs +++ b/ViewModels/MainWindowViewModel.cs @@ -1,6 +1,5 @@ using System; using System.Collections.ObjectModel; -using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -46,14 +45,14 @@ public partial class MainWindowViewModel : ViewModelBase set => SetProperty(ref _filter, value); } - public bool IsLoggedIn => App.TwitchApi != null; + public bool IsLoggedIn => DataService.UserChannel != null; public MainWindowViewModel(ITwitchPubSubService pubSub, ITwitchDataService dataService) { _pubSub = pubSub; DataService = dataService; - Database = BetterRaidDatabase.LoadFromFile(Path.Combine(App.BetterRaidDataPath, "db.json")); + Database = BetterRaidDatabase.LoadFromFile(Constants.DatabaseFilePath); } public void ExitApplication() @@ -71,7 +70,7 @@ public partial class MainWindowViewModel : ViewModelBase public void LoginWithTwitch() { - Tools.StartOAuthLogin(App.TwitchOAuthUrl, OnTwitchLoginCallback, CancellationToken.None); + Tools.StartOAuthLogin(DataService.GetOAuthUrl(), OnTwitchLoginCallback, CancellationToken.None); } private void OnTwitchLoginCallback() @@ -101,7 +100,7 @@ public partial class MainWindowViewModel : ViewModelBase { Task.Run(() => { - channel.InitChannel(); + channel.UpdateChannelData(DataService); _pubSub.RegisterReceiver(channel); }); diff --git a/ViewModels/RaidButtonViewModel.cs b/ViewModels/RaidButtonViewModel.cs deleted file mode 100644 index 2189b62..0000000 --- a/ViewModels/RaidButtonViewModel.cs +++ /dev/null @@ -1,195 +0,0 @@ -using System; -using System.ComponentModel; -using System.Linq; -using System.Threading.Tasks; -using Avalonia.Media; -using Avalonia.Threading; -using BetterRaid.Events; -using BetterRaid.Models; -using TwitchLib.Api.Helix.Models.Raids.StartRaid; -using TwitchLib.Api.Helix.Models.Search; -using TwitchLib.Api.Helix.Models.Streams.GetStreams; - -namespace BetterRaid.ViewModels; - -public class RaidButtonViewModel : ViewModelBase -{ - private TwitchChannel? _channel; - private SolidColorBrush _viewerCountColor = new SolidColorBrush(Color.FromRgb(byte.MaxValue, byte.MaxValue, byte.MaxValue)); - private bool _hideDeleteButton; - private bool _isAd; - - public string ChannelName - { - get; - set; - } - - public bool HideDeleteButton - { - get => _hideDeleteButton; - set => SetProperty(ref _hideDeleteButton, value); - } - - public bool IsAd - { - get => _isAd; - set => SetProperty(ref _isAd, value); - } - - public TwitchChannel? Channel => _channel ?? new TwitchChannel(ChannelName); - - public SolidColorBrush ViewerCountColor - { - get => _viewerCountColor; - set => SetProperty(ref _viewerCountColor, value); - } - - public MainWindowViewModel? MainVm { get; set; } - - public DateTime? LastRaided => MainVm?.Database?.GetLastRaided(ChannelName); - - public event EventHandler? ChannelDataChanged; - - public RaidButtonViewModel(string channelName) - { - ChannelName = channelName; - } - - public async Task GetOrUpdateChannelAsync() - { - Console.WriteLine("[DEBUG] Updating channel '{0}' ...", ChannelName); - - var currentChannelData = await GetChannelAsync(ChannelName); - - if (currentChannelData == null) - return false; - - var currentStreamData = await GetStreamAsync(currentChannelData); - - var swapChannel = new TwitchChannel(ChannelName) - { - BroadcasterId = currentChannelData.Id, - Name = ChannelName, - DisplayName = currentChannelData.DisplayName, - IsLive = currentChannelData.IsLive, - ThumbnailUrl = currentChannelData.ThumbnailUrl, - ViewerCount = currentStreamData?.ViewerCount == null - ? "(Offline)" - : $"{currentStreamData?.ViewerCount} Viewers", - Category = currentStreamData?.GameName - }; - - if (_channel != null) - { - _channel.PropertyChanged -= OnChannelDataChanged; - } - - Dispatcher.UIThread.Invoke(() => { - ViewerCountColor = new SolidColorBrush(Color.FromRgb( - r: swapChannel.IsLive ? (byte) 0 : byte.MaxValue, - g: swapChannel.IsLive ? byte.MaxValue : (byte) 0, - b: 0) - ); - - _channel = swapChannel; - OnPropertyChanged(nameof(Channel)); - }); - - if (_channel != null) - { - _channel.PropertyChanged += OnChannelDataChanged; - } - - Console.WriteLine("[DEBUG] DONE Updating channel '{0}'", ChannelName); - - return true; - } - - private async Task GetChannelAsync(string channelName) - { - if (App.TwitchApi == null) - return null; - - if (string.IsNullOrEmpty(channelName)) - return null; - - var channels = await App.TwitchApi.Helix.Search.SearchChannelsAsync(channelName); - var exactChannel = channels.Channels.FirstOrDefault(c => c.BroadcasterLogin.Equals(channelName, StringComparison.CurrentCultureIgnoreCase)); - - return exactChannel; - } - - private async Task GetStreamAsync(Channel currentChannelData) - { - if (App.TwitchApi == null) - return null; - - if (currentChannelData == null) - return null; - - var streams = await App.TwitchApi.Helix.Streams.GetStreamsAsync(userLogins: [currentChannelData.BroadcasterLogin]); - var exactStream = streams.Streams.FirstOrDefault(s => s.UserLogin == currentChannelData.BroadcasterLogin); - - return exactStream; - } - - public async Task RaidChannel() - { - if (App.TwitchApi == null) - return; - - if (Channel == null) - return; - - if (string.IsNullOrWhiteSpace(App.TwitchBroadcasterId)) - return; - - if (App.TwitchBroadcasterId == Channel.BroadcasterId) - return; - - try - { - await App.TwitchApi.Helix.Raids.StartRaidAsync(App.TwitchBroadcasterId, Channel.BroadcasterId); - } - catch (Exception e) - { - Console.WriteLine(e.Message); - Console.WriteLine(e.StackTrace); - - return; - } - - if (MainVm?.Database != null) - { - MainVm.Database.SetRaided(ChannelName, DateTime.Now); - } - } - - public void RemoveChannel() - { - if (MainVm?.Database == null) - return; - - MainVm.Database.RemoveChannel(ChannelName); - } - - private void OnChannelDataChanged(object? sender, PropertyChangedEventArgs e) - { - switch (e.PropertyName) - { - case "IsLive": - OnChannelDataChanged(ChannelDataChangedEventArgs.FromIsLive(false, true)); - break; - - case "ViewerCount": - OnChannelDataChanged(ChannelDataChangedEventArgs.FromViewerCount(0, 10)); - break; - } - } - - private void OnChannelDataChanged(ChannelDataChangedEventArgs args) - { - ChannelDataChanged?.Invoke(this, args); - } -} \ No newline at end of file diff --git a/Views/AboutWindow.axaml.cs b/Views/AboutWindow.axaml.cs index edd21de..ebb3ecd 100644 --- a/Views/AboutWindow.axaml.cs +++ b/Views/AboutWindow.axaml.cs @@ -1,6 +1,4 @@ -using Avalonia; using Avalonia.Controls; -using Avalonia.Markup.Xaml; namespace BetterRaid.Views; diff --git a/Views/AddChannelWindow.axaml.cs b/Views/AddChannelWindow.axaml.cs index 966667e..0261f95 100644 --- a/Views/AddChannelWindow.axaml.cs +++ b/Views/AddChannelWindow.axaml.cs @@ -1,6 +1,4 @@ -using Avalonia; using Avalonia.Controls; -using Avalonia.Markup.Xaml; namespace BetterRaid.Views; diff --git a/Views/MainWindow.axaml b/Views/MainWindow.axaml index f0dbc2a..6f2a0cd 100644 --- a/Views/MainWindow.axaml +++ b/Views/MainWindow.axaml @@ -1,12 +1,11 @@ + Source="{Binding ThumbnailUrl, TargetNullValue={x:Static misc:Constants.ChannelPlaceholderImageUrl}}" />