A 'little' code cleanup

This commit is contained in:
Enrico Ludwig 2024-09-04 00:10:18 +02:00
parent a8a481d4f9
commit 8a10573fbf
16 changed files with 232 additions and 511 deletions

View File

@ -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<ITwitchDataService, TwitchDataService>();
_services.AddSingleton<ITwitchPubSubService, TwitchPubSubService>();
_services.AddTransient<MainWindowViewModel>();
Services.AddSingleton<ITwitchDataService, TwitchDataService>();
Services.AddSingleton<ITwitchPubSubService, TwitchPubSubService>();
Services.AddTransient<MainWindowViewModel>();
_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()

View File

@ -1,95 +0,0 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ai="using:AsyncImageLoader"
xmlns:vm="using:BetterRaid.ViewModels"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="BetterRaid.RaidButton"
x:DataType="vm:RaidButtonViewModel"
Margin="5"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<Design.DataContext>
<vm:RaidButtonViewModel />
</Design.DataContext>
<Button HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Command="{Binding RaidChannel}">
<Grid HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="75*" />
<RowDefinition Height="25*" />
</Grid.RowDefinitions>
<ai:AdvancedImage Grid.Column="0"
Grid.Row="0"
Source="{Binding Channel.ThumbnailUrl}" />
<Border IsVisible="{Binding IsAd}"
BorderThickness="1"
BorderBrush="DarkGoldenrod"
CornerRadius="4"
Width="24"
Height="16"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Margin="5">
<TextBlock Text="Ad"
Margin="2"
FontSize="12"
HorizontalAlignment="Center"
VerticalAlignment="Center"
TextAlignment="Center"
Foreground="DarkGoldenrod" />
</Border>
<Button Width="32"
Height="32"
Background="DarkRed"
CornerRadius="16"
Padding="0"
BorderThickness="0"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
HorizontalAlignment="Right"
VerticalAlignment="Top"
Margin="5"
IsVisible="{Binding !HideDeleteButton}"
Command="{Binding RemoveChannel}">
<Image Source="avares://BetterRaid/Assets/icons8-close-32.png"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" />
</Button>
<StackPanel Grid.Column="0"
Grid.Row="1"
Orientation="Vertical">
<Label HorizontalAlignment="Stretch"
HorizontalContentAlignment="Center"
Content="{Binding Channel.DisplayName, FallbackValue=...}" />
<Label HorizontalAlignment="Stretch"
HorizontalContentAlignment="Center"
Content="{Binding Channel.Category, TargetNullValue=-, FallbackValue=...}" />
<Label HorizontalAlignment="Stretch"
HorizontalContentAlignment="Center"
Foreground="{Binding ViewerCountColor}"
Content="{Binding Channel.ViewerCount, TargetNullValue=(Offline), FallbackValue=...}" />
<Label HorizontalAlignment="Stretch"
HorizontalContentAlignment="Center"
Content="{Binding LastRaided, TargetNullValue=Never Raided, FallbackValue=...}" />
</StackPanel>
</Grid>
</Button>
</UserControl>

View File

@ -1,13 +0,0 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace BetterRaid;
public partial class RaidButton : UserControl
{
public RaidButton()
{
InitializeComponent();
}
}

View File

@ -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<T>(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<T>();
var vm = App.ServiceProvider.GetRequiredService<T>();
e.DataContext = vm;
}
}

30
Misc/Constants.cs Normal file
View File

@ -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
];
}

View File

@ -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();

View File

@ -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;

View File

@ -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;
}

View File

@ -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<T>(ref T field, T value, [CallerMemberName] string? propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
if (EqualityComparer<T>.Default.Equals(field, value))
return false;
OnPropertyChanging(propertyName);
field = value;
OnPropertyChanged(propertyName);
return true;
}
}

View File

@ -14,9 +14,12 @@ public class TwitchPubSubService : ITwitchPubSubService
{
private readonly Dictionary<PubSubType, List<PubSubListener>> _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;
}
}

View File

@ -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);
});

View File

@ -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<ChannelDataChangedEventArgs>? ChannelDataChanged;
public RaidButtonViewModel(string channelName)
{
ChannelName = channelName;
}
public async Task<bool> 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<Channel?> 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<Stream?> 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);
}
}

View File

@ -1,6 +1,4 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace BetterRaid.Views;

View File

@ -1,6 +1,4 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace BetterRaid.Views;

View File

@ -1,12 +1,11 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:BetterRaid.ViewModels"
xmlns:br="using:BetterRaid"
xmlns:con="using:BetterRaid.Converters"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ai="using:AsyncImageLoader"
xmlns:misc="clr-namespace:BetterRaid.Misc"
xmlns:misc="using:BetterRaid.Misc"
mc:Ignorable="d"
d:DesignWidth="600"
d:DesignHeight="800"
@ -122,7 +121,7 @@
<ai:AdvancedImage Grid.Column="0"
Grid.Row="0"
Source="{Binding ThumbnailUrl, TargetNullValue={x:Static br:App.ChannelPlaceholderImageUrl}}" />
Source="{Binding ThumbnailUrl, TargetNullValue={x:Static misc:Constants.ChannelPlaceholderImageUrl}}" />
<Border Grid.Column="0"
Grid.Row="0"

View File

@ -1,15 +1,4 @@
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Threading;
using BetterRaid.Extensions;
using BetterRaid.Models;
using BetterRaid.ViewModels;
namespace BetterRaid.Views;