Big update. Replaced Newtonsoft with System.Text.Json where possible, removed Spectre, tried to suppress the pile of compiler warnings I get on the GUI project, and tried to correct an issue where sometimes the session token retrieved is not usable.

This commit is contained in:
barelyprofessional
2024-06-14 23:03:05 +08:00
parent 98f7b2b27e
commit cdad1d6549
21 changed files with 281 additions and 251 deletions

View File

@@ -1,13 +1,13 @@
using System;
using System.Collections.ObjectModel;
using System.IO;
using System.Text.Json;
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
using KfChatDotNetGui.Models;
using KfChatDotNetGui.ViewModels;
using KfChatDotNetGui.Views;
using Newtonsoft.Json;
using NLog;
namespace KfChatDotNetGui
@@ -27,7 +27,7 @@ namespace KfChatDotNetGui
var dataContext = new MainWindowViewModel();
if (File.Exists("rooms.json"))
{
var rooms = JsonConvert.DeserializeObject<RoomSettingsModel>(File.ReadAllText("rooms.json"));
var rooms = JsonSerializer.Deserialize<RoomSettingsModel>(File.ReadAllText("rooms.json"));
dataContext.RoomList = rooms!.Rooms;
}

View File

@@ -2,10 +2,10 @@ using System;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text.Json;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using KfChatDotNetGui.Models;
using Newtonsoft.Json;
namespace KfChatDotNetGui.Helpers;
@@ -36,7 +36,7 @@ public static class ForumIdentity
}
var accountJs = match.Groups[1].Value;
return JsonConvert.DeserializeObject<ForumIdentityModel>(accountJs);
return JsonSerializer.Deserialize<ForumIdentityModel>(accountJs);
}
}
}

View File

@@ -28,7 +28,6 @@
<PackageReference Include="Avalonia.ReactiveUI" Version="11.0.10" />
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.0.10" />
<PackageReference Include="HtmlAgilityPack" Version="1.11.59" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="ReactiveUI" Version="19.5.72" />
</ItemGroup>
<ItemGroup>

View File

@@ -1,20 +1,20 @@
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using System.Text.Json.Serialization;
namespace KfChatDotNetGui.Models;
public class ForumIdentityModel
{
[JsonProperty("id")]
[JsonPropertyName("id")]
public int Id { get; set; }
[JsonProperty("username")]
[JsonPropertyName("username")]
public string Username { get; set; }
[JsonProperty("avatar_url")]
[JsonPropertyName("avatar_url")]
public Uri AvatarUrl { get; set; }
// Guessing it'll be the user ID as an int but no idea as this list is empty for me
[JsonProperty("ignored_users")]
[JsonPropertyName("ignored_users")]
public List<string> IgnoredUsers { get; set; }
[JsonProperty("is_staff")]
[JsonPropertyName("is_staff")]
public bool IsStaff { get; set; }
}

View File

@@ -1,5 +1,6 @@
using System;
using System.IO;
using System.Text.Json;
using System.Threading.Tasks;
using Avalonia;
using Avalonia.Controls;
@@ -10,7 +11,6 @@ using Avalonia.Threading;
using KfChatDotNetGui.Helpers;
using KfChatDotNetGui.Models;
using KfChatDotNetGui.ViewModels;
using Newtonsoft.Json;
using NLog;
namespace KfChatDotNetGui.Views;
@@ -44,7 +44,8 @@ public partial class IdentitySettingsWindow : Window
AntiDdosPow = (DataContext as IdentitySettingsWindowViewModel).AntiDdosPow,
Username = (DataContext as IdentitySettingsWindowViewModel).Username
};
File.WriteAllText("settings.json", JsonConvert.SerializeObject(settings, Formatting.Indented));
var options = new JsonSerializerOptions { WriteIndented = true };
File.WriteAllText("settings.json", JsonSerializer.Serialize(settings, options));
}
catch (Exception ex)
{

View File

@@ -4,14 +4,12 @@ using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Net;
using System.Text.Json;
using System.Threading.Tasks;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.LogicalTree;
using Avalonia.Markup.Xaml;
using Avalonia.Media.Imaging;
using Avalonia.Threading;
using KfChatDotNetGui.Models;
using KfChatDotNetGui.ViewModels;
@@ -19,7 +17,6 @@ using KfChatDotNetWsClient;
using KfChatDotNetWsClient.Models;
using KfChatDotNetWsClient.Models.Events;
using KfChatDotNetWsClient.Models.Json;
using Newtonsoft.Json;
using NLog;
using Websocket.Client;
@@ -31,9 +28,9 @@ namespace KfChatDotNetGui.Views
// Using an empty config as we can update it later through the UpdateConfig method
// Having this instance created early is handy for wiring up the events
private ChatClient _chatClient = new(new ChatClientConfigModel());
private SettingsModel _settings;
private SettingsModel _settings = null!;
private int _currentRoom;
private ForumIdentityModel _forumIdentity;
private ForumIdentityModel? _forumIdentity = null!;
public MainWindow()
{
@@ -62,7 +59,7 @@ namespace KfChatDotNetGui.Views
{
_logger.Info($"Received delete event for following message IDs: {string.Join(',', messageIds)}");
// Gotta make a copy of all the messages (annoyingly) as we'll be deleting stuff and .NET has a very obvious limitation there
var messages = (DataContext as MainWindowViewModel).Messages.ToList();
var messages = ((DataContext as MainWindowViewModel)!).Messages.ToList();
foreach (var message in messages)
{
foreach (var innerMessage in message.Messages.Where(m => messageIds.Contains(m.MessageId)))
@@ -71,13 +68,13 @@ namespace KfChatDotNetGui.Views
if (message.Messages.Count == 1)
{
_logger.Info("Removing parent message box");
(DataContext as MainWindowViewModel).Messages.Remove(message);
((DataContext as MainWindowViewModel)!).Messages.Remove(message);
}
// Go scavenging if there are multiple messages and we don't want to lose the lot
else
{
(DataContext as MainWindowViewModel)
.Messages[(DataContext as MainWindowViewModel).Messages.IndexOf(message)].Messages
((DataContext as MainWindowViewModel)!)
.Messages[((DataContext as MainWindowViewModel)!).Messages.IndexOf(message)].Messages
.Remove(innerMessage);
}
@@ -100,8 +97,8 @@ namespace KfChatDotNetGui.Views
Dispatcher.UIThread.InvokeAsync(() =>
{
UpdateStatus("Reconnected to SneedChat. Reason was " + reconnectionInfo.Type);
(DataContext as MainWindowViewModel).Messages.Clear();
(DataContext as MainWindowViewModel).UserList.Clear();
((DataContext as MainWindowViewModel)!).Messages.Clear();
((DataContext as MainWindowViewModel)!).UserList.Clear();
});
_chatClient.JoinRoom(_currentRoom);
@@ -112,8 +109,8 @@ namespace KfChatDotNetGui.Views
var context = new IdentitySettingsWindowViewModel();
if (File.Exists("settings.json"))
{
var settings = JsonConvert.DeserializeObject<SettingsModel>(File.ReadAllText("settings.json"));
context.WsUri = settings.WsUri;
var settings = JsonSerializer.Deserialize<SettingsModel>(File.ReadAllText("settings.json"));
context.WsUri = settings!.WsUri;
context.XfSessionToken = settings.XfSessionToken;
context.ReconnectTimeout = settings.ReconnectTimeout;
context.AntiDdosPow = settings.AntiDdosPow;
@@ -146,13 +143,13 @@ namespace KfChatDotNetGui.Views
}
ReloadSettings();
UpdateStatus("Testing XenForo token validity");
ForumIdentityModel forumIdentity;
ForumIdentityModel? forumIdentity;
if (string.IsNullOrEmpty(_settings.Username))
{
try
{
forumIdentity = await Helpers.ForumIdentity.GetForumIdentity(_settings.XfSessionToken,
new Uri($"https://{_settings.WsUri.Host}/test-chat"), _settings.AntiDdosPow);
forumIdentity = (await Helpers.ForumIdentity.GetForumIdentity(_settings.XfSessionToken,
new Uri($"https://{_settings.WsUri.Host}/test-chat"), _settings.AntiDdosPow));
}
catch (Exception ex)
{
@@ -184,12 +181,12 @@ namespace KfChatDotNetGui.Views
UpdateStatus("Token works! It belongs to " + forumIdentity.Username);
_forumIdentity = forumIdentity;
(DataContext as MainWindowViewModel).UserId = _forumIdentity.Id;
((DataContext as MainWindowViewModel)!).UserId = _forumIdentity.Id;
var roomListControl = this.FindControl<ListBox>("RoomList");
RoomSettingsModel.RoomList initialRoom;
if (roomListControl.SelectedItem == null)
if (roomListControl!.SelectedItem == null)
{
initialRoom = (DataContext as MainWindowViewModel).RoomList.First();
initialRoom = (DataContext as MainWindowViewModel)?.RoomList.First()!;
}
else
{
@@ -205,7 +202,7 @@ namespace KfChatDotNetGui.Views
});
await _chatClient.StartWsClient();
_chatClient.JoinRoom(initialRoom.Id);
_chatClient.JoinRoom(initialRoom!.Id);
_currentRoom = initialRoom.Id;
UpdateStatus("Connected!");
}
@@ -216,12 +213,12 @@ namespace KfChatDotNetGui.Views
{
foreach (var user in users)
{
if ((DataContext as MainWindowViewModel).UserList.FirstOrDefault(x => x.Id == user.Id) != null)
if (((DataContext as MainWindowViewModel)!).UserList.FirstOrDefault(x => x.Id == user.Id) != null)
{
_logger.Info($"{user.Username} ({user.Id}) is already in the list but has joined again. New tab? Ignoring!");
continue;
}
(DataContext as MainWindowViewModel).UserList.Add(new MainWindowViewModel.UserListViewModel
((DataContext as MainWindowViewModel)!).UserList.Add(new MainWindowViewModel.UserListViewModel
{
Id = user.Id,
Name = user.Username
@@ -237,13 +234,13 @@ namespace KfChatDotNetGui.Views
{
foreach (var id in userIds)
{
var row = (DataContext as MainWindowViewModel).UserList.FirstOrDefault(x => x.Id == id);
var row = ((DataContext as MainWindowViewModel)!).UserList.FirstOrDefault(x => x.Id == id);
if (row == null)
{
_logger.Info($"A user ({id}) who isn't in the list has parted, ignoring!");
continue;
}
(DataContext as MainWindowViewModel).UserList.Remove(row);
((DataContext as MainWindowViewModel)!).UserList.Remove(row);
}
UpdateUserTotalStatus();
});
@@ -253,7 +250,7 @@ namespace KfChatDotNetGui.Views
{
Dispatcher.UIThread.InvokeAsync(() =>
{
var previousMessage = (DataContext as MainWindowViewModel).Messages.LastOrDefault();
var previousMessage = ((DataContext as MainWindowViewModel)!).Messages.LastOrDefault();
if (previousMessage == null)
{
previousMessage = new MainWindowViewModel.MessageViewModel {AuthorId = -1};
@@ -261,7 +258,6 @@ namespace KfChatDotNetGui.Views
foreach (var message in messages)
{
_logger.Info("Received message, data payload next");
_logger.Info(JsonConvert.SerializeObject(message, Formatting.Indented));
if (message.RoomId != _currentRoom)
{
_logger.Info($"Message {message.MessageId} belongs to another room (we're in {_currentRoom}, this one was for {message.RoomId}), ignoring.");
@@ -272,7 +268,7 @@ namespace KfChatDotNetGui.Views
{
_logger.Info("Received an edit. Going to rewrite message if it already exists, " +
"if it doesn't, nothing will happen as this would occur when loading historically modified messages.");
foreach (var msg in (DataContext as MainWindowViewModel).Messages)
foreach (var msg in ((DataContext as MainWindowViewModel)!).Messages)
{
foreach (var innerMsg in msg.Messages.Where(m => m.MessageId == message.MessageId))
{
@@ -287,7 +283,7 @@ namespace KfChatDotNetGui.Views
if (previousMessage.AuthorId == message.Author.Id)
{
_logger.Info("Found a message from the same author, merging");
var lastMessage = (DataContext as MainWindowViewModel).Messages.Last();
var lastMessage = ((DataContext as MainWindowViewModel)!).Messages.Last();
lastMessage.Messages.Add(new MainWindowViewModel.InnerMessageViewModel
{
Message = WebUtility.HtmlDecode(message.MessageRaw),
@@ -312,11 +308,11 @@ namespace KfChatDotNetGui.Views
PostedAt = message.MessageDate.LocalDateTime,
AuthorId = message.Author.Id
};
(DataContext as MainWindowViewModel).Messages.Add(viewMessage);
((DataContext as MainWindowViewModel)!).Messages.Add(viewMessage);
previousMessage = viewMessage;
}
var messagesControl = this.FindControl<ListBox>("ChatMessageList");
messagesControl.ScrollIntoView((DataContext as MainWindowViewModel).Messages.Last());
messagesControl!.ScrollIntoView(((DataContext as MainWindowViewModel)!).Messages.Last());
});
}
@@ -335,7 +331,7 @@ namespace KfChatDotNetGui.Views
var context = new RoomSettingsWindowViewModel();
if (File.Exists("rooms.json"))
{
var settings = JsonConvert.DeserializeObject<RoomSettingsModel>(File.ReadAllText("rooms.json"));
var settings = JsonSerializer.Deserialize<RoomSettingsModel>(File.ReadAllText("rooms.json"));
context.RoomList.Clear();
foreach (var room in settings.Rooms)
{
@@ -364,7 +360,7 @@ namespace KfChatDotNetGui.Views
_logger.Error("Was asked to reload the settings but settings.json doesn't exist so I won't bother");
return;
}
var settings = JsonConvert.DeserializeObject<SettingsModel>(File.ReadAllText("settings.json"));
var settings = JsonSerializer.Deserialize<SettingsModel>(File.ReadAllText("settings.json"));
_settings = settings;
}
@@ -375,7 +371,7 @@ namespace KfChatDotNetGui.Views
_logger.Error("Was asked to reload the room list but rooms.json doesn't exist so I won't bother");
return;
}
var rooms = JsonConvert.DeserializeObject<RoomSettingsModel>(File.ReadAllText("rooms.json"));
var rooms = JsonSerializer.Deserialize<RoomSettingsModel>(File.ReadAllText("rooms.json"));
(DataContext as MainWindowViewModel)!.RoomList = rooms!.Rooms;
}
@@ -395,8 +391,8 @@ namespace KfChatDotNetGui.Views
return;
}
UpdateStatus($"Connected! Changing to {room.Name}");
(DataContext as MainWindowViewModel).Messages.Clear();
(DataContext as MainWindowViewModel).UserList.Clear();
((DataContext as MainWindowViewModel)!).Messages.Clear();
(DataContext as MainWindowViewModel)?.UserList.Clear();
_chatClient.JoinRoom(room.Id);
_currentRoom = room.Id;
}
@@ -468,7 +464,7 @@ namespace KfChatDotNetGui.Views
private void OuterMessageRow_OnPointerEnter(object? sender, PointerEventArgs e)
{
var children = (e.Source as ListBox).GetLogicalChildren().Cast<ListBoxItem>();
var children = ((e.Source as ListBox)!).GetLogicalChildren().Cast<ListBoxItem>();
foreach (var child in children)
{
// Bit of a ghetto hack but it ensures that there's only ever one subscriber

View File

@@ -6,6 +6,7 @@ using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text.Json;
using System.Threading.Tasks;
using Avalonia;
using Avalonia.Controls;
@@ -16,7 +17,6 @@ using Avalonia.Threading;
using HtmlAgilityPack;
using KfChatDotNetGui.Models;
using KfChatDotNetGui.ViewModels;
using Newtonsoft.Json;
using NLog;
namespace KfChatDotNetGui.Views;
@@ -54,7 +54,9 @@ public partial class RoomSettingsWindow : Window
Name = room.Name
});
}
File.WriteAllText("rooms.json", JsonConvert.SerializeObject(roomSettings, Formatting.Indented));
File.WriteAllText("rooms.json",
JsonSerializer.Serialize(roomSettings, new JsonSerializerOptions { WriteIndented = true }));
}
catch (Exception ex)
{
@@ -102,7 +104,7 @@ public partial class RoomSettingsWindow : Window
var kfDomain = "kiwifarms.net";
if (File.Exists("settings.json"))
{
var settings = JsonConvert.DeserializeObject<SettingsModel>(await File.ReadAllTextAsync("settings.json"));
var settings = JsonSerializer.Deserialize<SettingsModel>(await File.ReadAllTextAsync("settings.json"));
kfDomain = settings.WsUri.Host;
}

View File

@@ -8,10 +8,8 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="NLog" Version="5.3.2" />
<PackageReference Include="PuppeteerSharp" Version="18.0.1" />
<PackageReference Include="Spectre.Console" Version="0.48.0" />
<PackageReference Include="System.Text.Json" Version="8.0.3" />
</ItemGroup>

View File

@@ -1,32 +1,31 @@
using KfChatDotNetKickBot.Services;
using System.Text.Json;
using KfChatDotNetKickBot.Services;
using KfChatDotNetWsClient;
using KfChatDotNetWsClient.Models;
using KfChatDotNetWsClient.Models.Events;
using KfChatDotNetWsClient.Models.Json;
using KickWsClient.Models;
using Newtonsoft.Json;
using NLog;
using Spectre.Console;
using Websocket.Client;
namespace KfChatDotNetKickBot;
public class KickBot
{
private ChatClient _kfClient;
private KickWsClient.KickWsClient _kickClient;
private Logger _logger = LogManager.GetCurrentClassLogger();
private Models.ConfigModel _config;
private Thread _pingThread;
private bool _pingEnabled = true;
private bool _gambaSeshPresent = false;
private string _xfSessionToken;
private readonly ChatClient _kfClient;
private readonly KickWsClient.KickWsClient _kickClient;
private readonly Logger _logger = LogManager.GetCurrentClassLogger();
private readonly Models.ConfigModel _config;
private readonly bool _pingEnabled = true;
private bool _gambaSeshPresent;
private string _xfSessionToken = null!;
// Oh no it's an ever expanding list that may never get cleaned up!
// BUY MORE RAM
private List<int> _seenMsgIds = new List<int>();
// Suppresses the command handler on initial start so it doesn't pick up things already handled on restart
private readonly List<int> _seenMsgIds = [];
// Suppresses the command handler on initial start, so it doesn't pick up things already handled on restart
private bool _initialStartCooldown = true;
private readonly CancellationToken _cancellationToken = new();
public KickBot()
{
_logger.Info("Bot starting!");
@@ -37,9 +36,9 @@ public class KickBot
Environment.Exit(1);
}
_config = JsonConvert.DeserializeObject<Models.ConfigModel>(File.ReadAllText(configPath)) ??
_config = JsonSerializer.Deserialize<Models.ConfigModel>(File.ReadAllText(configPath)) ??
throw new InvalidOperationException();
RefreshXfToken();
RefreshXfToken().Wait(_cancellationToken);
_kfClient = new ChatClient(new ChatClientConfigModel
{
@@ -66,22 +65,22 @@ public class KickBot
_kickClient.OnPusherSubscriptionSucceeded += OnPusherSubscriptionSucceeded;
_kickClient.OnStopStreamBroadcast += OnStopStreamBroadcast;
_kfClient.StartWsClient().Wait();
_kfClient.StartWsClient().Wait(_cancellationToken);
_kfClient.JoinRoom(_config.KfChatRoomId);
_kickClient.StartWsClient().Wait();
_kickClient.StartWsClient().Wait(_cancellationToken);
foreach (var channel in _config.PusherChannels)
{
_kickClient.SendPusherSubscribe(channel);
}
_pingThread = new Thread(PingThread);
_pingThread.Start();
_logger.Debug("Creating ping thread and starting it");
var pingThread = new Thread(PingThread);
pingThread.Start();
while (true)
{
var input = AnsiConsole.Prompt(new TextPrompt<string>("Enter Message:"));
_kfClient.SendMessage(input);
Console.ReadLine();
}
}
@@ -89,11 +88,11 @@ public class KickBot
{
_logger.Error($"Couldn't join the room. KF returned: {message}");
_logger.Error("This is likely due to the session cookie expiring. Retrieving a new one.");
RefreshXfToken();
RefreshXfToken().Wait(_cancellationToken);
_kfClient.UpdateToken(_xfSessionToken);
_logger.Info("Retrieved fresh token. Reconnecting.");
_kfClient.Disconnect();
_kfClient.StartWsClient().Wait();
_kfClient.StartWsClient().Wait(_cancellationToken);
_logger.Info("Client should be reconnecting now");
}
@@ -109,24 +108,22 @@ public class KickBot
}
}
private void RefreshXfToken()
private async Task RefreshXfToken()
{
var cookie = KfTokenService.FetchSessionTokenAsync(_config.KfDomain, _config.KfUsername, _config.KfPassword,
_config.ChromiumPath, _config.KfProxy).Result;
var cookie = await KfTokenService.FetchSessionTokenAsync(_config.KfDomain, _config.KfUsername, _config.KfPassword,
_config.ChromiumPath, _config.KfProxy);
_logger.Debug($"FetchSessionTokenAsync returned {cookie}");
_xfSessionToken = cookie;
}
private void OnStreamerIsLive(object sender, KickModels.StreamerIsLiveEventModel? e)
{
AnsiConsole.MarkupLine("[green]Streamer is live!!![/]");
_sendChatMessage($"Bossman Live! {e?.Livestream.SessionTitle} https://kick.com/bossmanjack [i]This action was automated");
_sendChatMessage($"Bossman Live! {e?.Livestream.SessionTitle} https://kick.com/bossmanjack");
}
private void OnStopStreamBroadcast(object sender, KickModels.StopStreamBroadcastEventModel? e)
{
AnsiConsole.MarkupLine("[green]Stream stopped!!![/]");
_sendChatMessage("The stream is so over. [i]This action was automated");
_sendChatMessage("The stream is so over. :lossmanjack:");
}
private void OnKfChatMessage(object sender, List<MessageModel> messages, MessagesJsonModel jsonPayload)
@@ -134,7 +131,7 @@ public class KickBot
_logger.Debug($"Received {messages.Count} message(s)");
foreach (var message in messages)
{
AnsiConsole.MarkupLine($"[yellow]KF[/] <{message.Author.Username}> {message.Message.EscapeMarkup()} ({message.MessageDate.LocalDateTime.ToShortTimeString()})");
_logger.Info($"KF ({message.MessageDate.ToLocalTime():HH:mm:ss}) <{message.Author.Username}> {message.Message}");
if (!_seenMsgIds.Contains(message.MessageId) && !_initialStartCooldown)
{
if (message.MessageRaw.StartsWith("!time"))
@@ -149,16 +146,17 @@ public class KickBot
}
else if (message.MessageRaw.StartsWith("!insanity"))
{
// ReSharper disable once StringLiteralTypo
_sendChatMessage("definition of insanity = doing the same thing over and over and over excecting a different result, and heres my dumbass trying to get rich every day and losing everythign i fucking touch every fucking time FUCK this bullshit FUCK MY LIEFdefinition of insanity = doing the same thing over and over and over excecting a different result, and heres my dumbass trying to get rich every day and losing everythign i fucking touch every fucking time FUCK this bullshit FUCK MY LIEF");
}
else if (message.MessageRaw.StartsWith("!help"))
else if (message.MessageRaw.StartsWith("!helpme"))
{
_sendChatMessage("[img]https://i.postimg.cc/fTw6tGWZ/ineedmoneydumbfuck.png[/img]", true);
}
}
else
{
AnsiConsole.MarkupLine($"[blue]_seenMsgIds check => {!_seenMsgIds.Contains(message.MessageId)}, _initialStartCooldown => {_initialStartCooldown}[/]");
_logger.Debug($"_seenMsgIds check => {!_seenMsgIds.Contains(message.MessageId)}, _initialStartCooldown => {_initialStartCooldown}");
}
_seenMsgIds.Add(message.MessageId);
}
@@ -168,7 +166,7 @@ public class KickBot
{
if (_gambaSeshPresent && _config.EnableGambaSeshDetect && !bypassSeshDetect)
{
AnsiConsole.MarkupLine($"[red]Not sending message '{message.EscapeMarkup()}' as GambaSesh is present[/]");
_logger.Info($"Not sending message '{message}' as GambaSesh is present");
return;
}
@@ -177,15 +175,14 @@ public class KickBot
private void OnKickChatMessage(object sender, KickModels.ChatMessageEventModel? e)
{
if (e == null) return;
AnsiConsole.MarkupLine($"[green]Kick[/] <{e.Sender.Username}> {e.Content.EscapeMarkup()} ({e.CreatedAt.LocalDateTime.ToShortTimeString()})");
AnsiConsole.MarkupLine($"[cyan]BB Code Translation: {e.Content.TranslateKickEmotes().EscapeMarkup()}[/]");
if (e == null) return;
_logger.Info($"Kick ({e.CreatedAt.LocalDateTime.ToLocalTime():HH:mm:ss}) <{e.Sender.Username}> {e.Content}");
_logger.Debug($"BB Code Translation: {e.Content.TranslateKickEmotes()}");
if (e.Sender.Slug == "bossmanjack")
{
AnsiConsole.MarkupLine("[green]Message from BossmanJack[/]");
_sendChatMessage($"[img]{_config.KickIcon}[/img] BossmanJack: {e.Content.TranslateKickEmotes()}");
}
if (e.Sender.Slug != "bossmanjack") return;
_logger.Debug("Message from BossmanJack");
_sendChatMessage($"[img]{_config.KickIcon}[/img] BossmanJack: {e.Content.TranslateKickEmotes()}");
}
private void OnUsersJoined(object sender, List<UserModel> users, UsersJsonModel jsonPayload)
@@ -195,9 +192,10 @@ public class KickBot
{
if (user.Id == _config.GambaSeshUserId && _config.EnableGambaSeshDetect)
{
_logger.Info("GambaSesh is now present");
_gambaSeshPresent = true;
}
AnsiConsole.MarkupLine($"[green]{user.Username.EscapeMarkup()} joined![/]");
_logger.Info($"{user.Username} joined!");
}
}
@@ -205,13 +203,9 @@ public class KickBot
{
if (userIds.Contains(_config.GambaSeshUserId) && _config.EnableGambaSeshDetect)
{
_logger.Info("GambaSesh is no longer present");
_gambaSeshPresent = false;
}
_logger.Debug($"Received {userIds.Count} user part events");
foreach (var id in userIds)
{
AnsiConsole.MarkupLine($"[red]{id} left the chat...[/]");
}
}
private void OnKfWsDisconnected(object sender, DisconnectionInfo disconnectionInfo)

View File

@@ -40,11 +40,11 @@ public class KfTokenService
await page.WaitForSelectorAsync("img[alt=\"Kiwi Farms\"]");
if (await page.QuerySelectorAsync("html[data-template=\"login\"]") == null)
{
logger.Debug("Page template is not login. This is expected if we're already logged in. Retrieving cookies");
logger.Debug("Page template is not login. This is expected if we're already logged in. Reloading page to get the freshest cookies then retrieving");
await page.ReloadAsync();
return await GetXfSessionCookie();
}
var usernameFieldSelector = await page.QuerySelectorAsync("input[autocomplete=\"username\"]");
var passwordFieldSelector = await page.QuerySelectorAsync("input[autocomplete=\"current-password\"]");
var loginButtonSelector = await page.QuerySelectorAsync("div[class=\"formSubmitRow-controls\"] > button[type=\"submit\"]");

View File

@@ -1,9 +1,10 @@
using System.Net;
using System.Net.WebSockets;
using System.Text.Json;
using System.Xml;
using KfChatDotNetWsClient.Models;
using KfChatDotNetWsClient.Models.Events;
using KfChatDotNetWsClient.Models.Json;
using Newtonsoft.Json;
using NLog;
using Websocket.Client;
// It's a fucking lie. You must use conditional access or you WILL get NullReferenceErrors if an event is not in use
@@ -135,7 +136,7 @@ public class ChatClient
Dictionary<string, object> packetType = new Dictionary<string, object>();
try
{
packetType = JsonConvert.DeserializeObject<Dictionary<string, object>>(message.Text)!;
packetType = JsonSerializer.Deserialize<Dictionary<string, object>>(message.Text)!;
}
catch (Exception e)
{
@@ -198,23 +199,21 @@ public class ChatClient
public void EditMessage(int messageId, string newMessage)
{
// Explicitly set formatting to none as it must be inline (Newtonsoft will do this by default but just wanting to be explicit)
var payload = JsonConvert.SerializeObject(new EditMessageJsonModel {Id = messageId, Message = newMessage},
Formatting.None);
var payload = JsonSerializer.Serialize(new EditMessageJsonModel {Id = messageId, Message = newMessage});
_logger.Debug($"Editing {messageId} with '{newMessage}'");
_wsClient.Send($"/edit {payload}");
}
private void WsDeleteMessagesReceived(ResponseMessage message)
{
var data = JsonConvert.DeserializeObject<DeleteMessagesJsonModel>(message.Text);
var data = JsonSerializer.Deserialize<DeleteMessagesJsonModel>(message.Text);
_logger.Debug($"Received delete packet for messages: {string.Join(',', data.MessageIdsToDelete)}");
OnDeleteMessages?.Invoke(this, data.MessageIdsToDelete);
}
private void WsChatMessagesReceived(ResponseMessage message)
{
var data = JsonConvert.DeserializeObject<MessagesJsonModel>(message.Text);
var data = JsonSerializer.Deserialize<MessagesJsonModel>(message.Text);
var messages = new List<MessageModel>();
foreach (var chatMessage in data.Messages)
{
@@ -250,14 +249,14 @@ public class ChatClient
_logger.Debug($"Received {messages.Count} chat messages");
if (messages.Count == 1)
{
_logger.Debug($"{JsonConvert.SerializeObject(messages[0], Formatting.Indented)}");
_logger.Debug($"{JsonSerializer.Serialize(messages[0])}");
}
OnMessages?.Invoke(this, messages, data);
}
private void WsChatUsersJoined(ResponseMessage message)
{
var data = JsonConvert.DeserializeObject<UsersJsonModel>(message.Text);
var data = JsonSerializer.Deserialize<UsersJsonModel>(message.Text);
var users = new List<UserModel>();
foreach (var user in data.Users.Keys)
{
@@ -277,7 +276,7 @@ public class ChatClient
private void WsChatUsersParted(ResponseMessage message)
{
// {"user":{"1337":false}}
var data = JsonConvert.DeserializeObject<Dictionary<string, Dictionary<string, bool>>>(message.Text);
var data = JsonSerializer.Deserialize<Dictionary<string, Dictionary<string, bool>>>(message.Text);
var usersParted = data!["user"].Select(user => int.Parse(user.Key)).ToList();
_logger.Debug($"Following users have parted: {string.Join(',', usersParted)}");
OnUsersParted?.Invoke(this, usersParted);

View File

@@ -8,7 +8,6 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="NLog" Version="5.2.8" />
<PackageReference Include="Websocket.Client" Version="5.1.1" />
</ItemGroup>

View File

@@ -1,9 +1,9 @@
using Newtonsoft.Json;
using System.Text.Json.Serialization;
namespace KfChatDotNetWsClient.Models.Json;
public class DeleteMessagesJsonModel
{
[JsonProperty("delete")]
[JsonPropertyName("delete")]
public List<int> MessageIdsToDelete { get; set; }
}

View File

@@ -1,12 +1,12 @@
using Newtonsoft.Json;
using System.Text.Json.Serialization;
namespace KfChatDotNetWsClient.Models.Json;
public class EditMessageJsonModel
{
[JsonProperty("id")]
[JsonPropertyName("id")]
public int Id { get; set; }
[JsonProperty("message")]
[JsonPropertyName("message")]
public string Message { get; set; }
}

View File

@@ -1,5 +1,4 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using System.Text.Json.Serialization;
namespace KfChatDotNetWsClient.Models.Json;
@@ -28,32 +27,32 @@ public class MessagesJsonModel
{
public class AuthorModel
{
[JsonProperty("id")]
[JsonPropertyName("id")]
public int Id { get; set; }
[JsonProperty("username")]
[JsonPropertyName("username")]
public string Username { get; set; }
[JsonProperty("avatar_url")]
[JsonPropertyName("avatar_url")]
public Uri AvatarUrl { get; set; }
}
public class MessageModel
{
[JsonProperty("author")]
[JsonPropertyName("author")]
public AuthorModel Author { get; set; }
[JsonProperty("message")]
[JsonPropertyName("message")]
public string Message { get; set; }
[JsonProperty("message_id")]
[JsonPropertyName("message_id")]
public int MessageId { get; set; }
[JsonProperty("message_edit_date")]
[JsonPropertyName("message_edit_date")]
public int MessageEditDate { get; set; }
[JsonProperty("message_date")]
[JsonPropertyName("message_date")]
public int MessageDate { get; set; }
[JsonProperty("message_raw")]
[JsonPropertyName("message_raw")]
public string MessageRaw { get; set; }
[JsonProperty("room_id")]
[JsonPropertyName("room_id")]
public int RoomId { get; set; }
}
[JsonProperty("messages")]
[JsonPropertyName("messages")]
public List<MessageModel> Messages { get; set; }
}

View File

@@ -1,5 +1,4 @@
using Newtonsoft.Json;
using System.Text.Json.Serialization;
namespace KfChatDotNetWsClient.Models.Json;
// {
@@ -17,16 +16,16 @@ public class UsersJsonModel
{
public class UserModel
{
[JsonProperty("id")]
[JsonPropertyName("id")]
public int Id { get; set; }
[JsonProperty("username")]
[JsonPropertyName("username")]
public string Username { get; set; }
[JsonProperty("avatar_url")]
[JsonPropertyName("avatar_url")]
public Uri AvatarUrl { get; set; }
[JsonProperty("last_activity")]
[JsonPropertyName("last_activity")]
public int LastActivity { get; set; }
}
[JsonProperty("users")]
[JsonPropertyName("users")]
public Dictionary<string, UserModel> Users { get; set; }
}

View File

@@ -0,0 +1,37 @@
using System.Text.Json;
using System.Text.Json.Serialization;
namespace KickWsClient.Converters;
// Literal mess, but it is AI generated so what do you expect. Couldn't be bothered writing this myself.
public class StringOrObjectConverter<T> : JsonConverter<T>
{
public override bool CanConvert(Type typeToConvert)
{
return typeof(T).IsAssignableFrom(typeToConvert) || (typeof(T) == typeof(string) && typeToConvert == typeof(object));
}
public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType == JsonTokenType.String)
{
return (T)(object)reader.GetString()!;
}
else
{
return JsonSerializer.Deserialize<T>(ref reader, options)!;
}
}
public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
{
if (value is string)
{
writer.WriteStringValue((string)(object)value);
}
else
{
JsonSerializer.Serialize(writer, value, options);
}
}
}

View File

@@ -1,7 +1,8 @@
using System.Net;
using System.Net.WebSockets;
using System.Text.Json;
using System.Text.Json.Serialization;
using KickWsClient.Models;
using Newtonsoft.Json;
using Websocket.Client;
using NLog;
@@ -114,7 +115,7 @@ public class KickWsClient
public void SendPusherPacket(string eventName, object data)
{
var pkt = new PusherModels.BasePusherRequestModel { Event = eventName, Data = data};
var json = JsonConvert.SerializeObject(pkt);
var json = JsonSerializer.Serialize(pkt);
_logger.Debug("Sending message to Pusher");
_logger.Debug(json);
_wsClient.Send(json);
@@ -152,6 +153,10 @@ public class KickWsClient
private void WsMessageReceived(ResponseMessage message)
{
OnWsMessageReceived?.Invoke(this, message);
var options = new JsonSerializerOptions
{
NumberHandling = JsonNumberHandling.AllowReadingFromString
};
if (message.Text == null)
{
@@ -162,7 +167,7 @@ public class KickWsClient
PusherModels.BasePusherEventModel pusherMsg;
try
{
pusherMsg = JsonConvert.DeserializeObject<PusherModels.BasePusherEventModel>(message.Text) ??
pusherMsg = JsonSerializer.Deserialize<PusherModels.BasePusherEventModel>(message.Text, options) ??
throw new InvalidOperationException();
}
catch (Exception e)
@@ -182,7 +187,7 @@ public class KickWsClient
case "pusher:connection_established":
{
var data =
JsonConvert.DeserializeObject<PusherModels.PusherConnectionEstablishedEventModel>(pusherMsg.Data);
JsonSerializer.Deserialize<PusherModels.PusherConnectionEstablishedEventModel>(pusherMsg.Data, options);
OnPusherConnectionEstablished?.Invoke(this, data);
return;
}
@@ -194,67 +199,67 @@ public class KickWsClient
return;
case @"App\Events\FollowersUpdated":
{
var data = JsonConvert.DeserializeObject<KickModels.FollowersUpdatedEventModel>(pusherMsg.Data);
var data = JsonSerializer.Deserialize<KickModels.FollowersUpdatedEventModel>(pusherMsg.Data, options);
OnFollowersUpdated?.Invoke(this, data);
return;
}
case @"App\Events\ChatMessageEvent":
{
var data = JsonConvert.DeserializeObject<KickModels.ChatMessageEventModel>(pusherMsg.Data);
var data = JsonSerializer.Deserialize<KickModels.ChatMessageEventModel>(pusherMsg.Data, options);
OnChatMessage?.Invoke(this, data);
return;
}
case @"App\Events\ChannelSubscriptionEvent":
{
var data = JsonConvert.DeserializeObject<KickModels.ChannelSubscriptionEventModel>(pusherMsg.Data);
var data = JsonSerializer.Deserialize<KickModels.ChannelSubscriptionEventModel>(pusherMsg.Data, options);
OnChannelSubscription?.Invoke(this, data);
return;
}
case @"App\Events\SubscriptionEvent":
{
var data = JsonConvert.DeserializeObject<KickModels.SubscriptionEventModel>(pusherMsg.Data);
var data = JsonSerializer.Deserialize<KickModels.SubscriptionEventModel>(pusherMsg.Data, options);
OnSubscription?.Invoke(this, data);
return;
}
case @"App\Events\MessageDeletedEvent":
{
var data = JsonConvert.DeserializeObject<KickModels.MessageDeletedEventModel>(pusherMsg.Data);
var data = JsonSerializer.Deserialize<KickModels.MessageDeletedEventModel>(pusherMsg.Data, options);
OnMessageDeleted?.Invoke(this, data);
return;
}
case @"App\Events\UserBannedEvent":
{
var data = JsonConvert.DeserializeObject<KickModels.UserBannedEventModel>(pusherMsg.Data);
var data = JsonSerializer.Deserialize<KickModels.UserBannedEventModel>(pusherMsg.Data, options);
OnUserBanned?.Invoke(this, data);
return;
}
case @"App\Events\UserUnbannedEvent":
{
var data = JsonConvert.DeserializeObject<KickModels.UserUnbannedEventModel>(pusherMsg.Data);
var data = JsonSerializer.Deserialize<KickModels.UserUnbannedEventModel>(pusherMsg.Data, options);
OnUserUnbanned?.Invoke(this, data);
return;
}
case @"App\Events\LiveStream\UpdatedLiveStreamEvent":
{
var data = JsonConvert.DeserializeObject<KickModels.UpdatedLiveStreamEventModel>(pusherMsg.Data);
var data = JsonSerializer.Deserialize<KickModels.UpdatedLiveStreamEventModel>(pusherMsg.Data, options);
OnUpdatedLiveStream?.Invoke(this, data);
return;
}
case @"App\Events\StopStreamBroadcast":
{
var data = JsonConvert.DeserializeObject<KickModels.StopStreamBroadcastEventModel>(pusherMsg.Data);
var data = JsonSerializer.Deserialize<KickModels.StopStreamBroadcastEventModel>(pusherMsg.Data, options);
OnStopStreamBroadcast?.Invoke(this, data);
return;
}
case @"App\Events\StreamerIsLive":
{
var data = JsonConvert.DeserializeObject<KickModels.StreamerIsLiveEventModel>(pusherMsg.Data);
var data = JsonSerializer.Deserialize<KickModels.StreamerIsLiveEventModel>(pusherMsg.Data, options);
OnStreamerIsLive?.Invoke(this, data);
return;
}
case @"App\Events\PollUpdateEvent":
{
var data = JsonConvert.DeserializeObject<KickModels.PollUpdateEventModel>(pusherMsg.Data);
var data = JsonSerializer.Deserialize<KickModels.PollUpdateEventModel>(pusherMsg.Data, options);
OnPollUpdate?.Invoke(this, data);
return;
}

View File

@@ -7,8 +7,8 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="NLog" Version="5.2.8" />
<PackageReference Include="System.Text.Json" Version="8.0.3" />
<PackageReference Include="Websocket.Client" Version="5.1.1" />
</ItemGroup>

View File

@@ -1,4 +1,4 @@
using Newtonsoft.Json;
using System.Text.Json.Serialization;
namespace KickWsClient.Models;
@@ -9,17 +9,17 @@ public class KickModels
/// <summary>
/// Internal type for badge e.g. moderator
/// </summary>
[JsonProperty("type")]
[JsonPropertyName("type")]
public required string Type { get; set; }
/// <summary>
/// Friendly name for badge e.g. Moderator
/// </summary>
[JsonProperty("text")]
[JsonPropertyName("text")]
public required string Text { get; set; }
/// <summary>
/// Count (if applicable) for badge (e.g. sub count for gifted subs)
/// </summary>
[JsonProperty("count")]
[JsonPropertyName("count")]
public int? Count { get; set; }
}
@@ -28,13 +28,13 @@ public class KickModels
/// <summary>
/// User's hex color
/// </summary>
[JsonProperty("color")]
[JsonPropertyName("color")]
public required string Color { get; set; }
/// <summary>
/// Badges a user has
/// </summary>
[JsonProperty("badges")]
[JsonPropertyName("badges")]
public List<ChatMessageSenderIdentityBadgeModel> Badges = [];
}
@@ -43,22 +43,22 @@ public class KickModels
/// <summary>
/// Kick internal user ID
/// </summary>
[JsonProperty("id")]
[JsonPropertyName("id")]
public int Id { get; set; }
/// <summary>
/// Kick display name
/// </summary>
[JsonProperty("username")]
[JsonPropertyName("username")]
public required string Username { get; set; }
/// <summary>
/// Kick slug (for URLs)
/// </summary>
[JsonProperty("slug")]
[JsonPropertyName("slug")]
public required string Slug { get; set; }
/// <summary>
/// Identity info for display color and badges
/// </summary>
[JsonProperty("identity")]
[JsonPropertyName("identity")]
public required ChatMessageSenderIdentityModel Identity { get; set; }
}
@@ -67,12 +67,12 @@ public class KickModels
/// <summary>
/// Original sender's user ID
/// </summary>
[JsonProperty("id")]
[JsonPropertyName("id")]
public int Id { get; set; }
/// <summary>
/// Original sender's username
/// </summary>
[JsonProperty("username")]
[JsonPropertyName("username")]
public required string Username { get; set; }
}
@@ -81,12 +81,12 @@ public class KickModels
/// <summary>
/// ID (GUID) of the original message
/// </summary>
[JsonProperty("id")]
[JsonPropertyName("id")]
public required string Id { get; set; }
/// <summary>
/// Content of the original message
/// </summary>
[JsonProperty("content")]
[JsonPropertyName("content")]
public required string Content { get; set; }
}
@@ -95,12 +95,12 @@ public class KickModels
/// <summary>
/// Sender of the message that this message is in reply to
/// </summary>
[JsonProperty("original_sender")]
[JsonPropertyName("original_sender")]
public required ChatMessageMetadataOriginalSenderModel OriginalSender { get; set; }
/// <summary>
/// Content of the message that this message is in reply to
/// </summary>
[JsonProperty("original_message")]
[JsonPropertyName("original_message")]
public required ChatMessageMetadataOriginalMessageModel OriginalMessage { get; set; }
}
@@ -109,29 +109,29 @@ public class KickModels
/// <summary>
/// Channel follower count
/// </summary>
[JsonProperty("followersCount")]
[JsonPropertyName("followersCount")]
public int FollowersCount { get; set; }
/// <summary>
/// ID to identify what chatroom this event belongs to
/// </summary>
[JsonProperty("chatroom_id")]
[JsonPropertyName("chatroom_id")]
public int ChatroomId { get; set; }
/// <summary>
/// Maybe returns your username if you're auth'd? No idea. Just returned null for me
/// </summary>
[JsonProperty("username")]
[JsonPropertyName("username")]
public string? Username { get; set; }
/// <summary>
/// Epoch value that signifies ???
/// </summary>
[JsonProperty("created_at")]
[JsonPropertyName("created_at")]
public int? CreatedAtEpoch { get; set; }
// It returned true even though I'm not signed in which makes no sense, so I'll assume there's a chance it'll
// suddenly appear and mark as nullable as it's not really a useful property anyway.
/// <summary>
/// Does it mean we're following? Who knows, returns true even if you're a guest
/// </summary>
[JsonProperty("followed")]
[JsonPropertyName("followed")]
public bool? Followed { get; set; }
}
@@ -140,38 +140,38 @@ public class KickModels
/// <summary>
/// Message unique GUID that's referenced for replies and deletions
/// </summary>
[JsonProperty("id")]
[JsonPropertyName("id")]
public required string Id { get; set; }
/// <summary>
/// Chatroom ID you can use to differentiate this from other rooms if you sub to multiple at a time
/// </summary>
[JsonProperty("chatroom_id")]
[JsonPropertyName("chatroom_id")]
public int ChatroomId { get; set; }
/// <summary>
/// Content of the message. Emotes are encoded like [emote:161238:russW] which translates to -> https://files.kick.com/emotes/161238/fullsize
/// </summary>
[JsonProperty("content")]
[JsonPropertyName("content")]
public required string Content { get; set; }
/// <summary>
/// Regular message is 'message', replies are 'reply'
/// </summary>
[JsonProperty("type")]
[JsonPropertyName("type")]
public required string Type { get; set; }
// Why created at is an epoch for followers updated but ISO8601 for chat messages is just a mystery
/// <summary>
/// Time message was sent
/// </summary>
[JsonProperty("created_at")]
[JsonPropertyName("created_at")]
public DateTimeOffset CreatedAt { get; set; }
/// <summary>
/// Sender of the message
/// </summary>
[JsonProperty("sender")]
[JsonPropertyName("sender")]
public required ChatMessageSenderModel Sender { get; set; }
/// <summary>
/// Message metadata which is set for replies only
/// </summary>
[JsonProperty("metadata")]
[JsonPropertyName("metadata")]
public ChatMessageMetadataModel? Metadata { get; set; }
}
@@ -180,17 +180,17 @@ public class KickModels
/// <summary>
/// User IDs of subscription recipients
/// </summary>
[JsonProperty("user_ids")]
[JsonPropertyName("user_ids")]
public List<int> UserIds { get; set; } = [];
/// <summary>
/// Username of the person who subbed / gifted
/// </summary>
[JsonProperty("username")]
[JsonPropertyName("username")]
public required string Username { get; set; }
/// <summary>
/// Channel ID where the sub event occurred
/// </summary>
[JsonProperty("channel_id")]
[JsonPropertyName("channel_id")]
public int ChannelId { get; set; }
}
@@ -199,17 +199,17 @@ public class KickModels
/// <summary>
/// ID of channel where the subscription event occurred
/// </summary>
[JsonProperty("chatroom_id")]
[JsonPropertyName("chatroom_id")]
public int ChatroomId { get; set; }
/// <summary>
/// Username of the person who bought a sub
/// </summary>
[JsonProperty("username")]
[JsonPropertyName("username")]
public required string Username { get; set; }
/// <summary>
/// Number of months they've subbed now (e.g. 2 if they bought their 2nd month)
/// </summary>
[JsonProperty("months")]
[JsonPropertyName("months")]
public int Months { get; set; }
}
@@ -218,7 +218,7 @@ public class KickModels
/// <summary>
/// ID of the message that was deleted
/// </summary>
[JsonProperty("id")]
[JsonPropertyName("id")]
public required string Id { get; set; }
}
@@ -227,12 +227,12 @@ public class KickModels
/// <summary>
/// ID of this event (NOT the message to be removed!)
/// </summary>
[JsonProperty("id")]
[JsonPropertyName("id")]
public required string Id { get; set; }
/// <summary>
/// Message that was deleted
/// </summary>
[JsonProperty("message")]
[JsonPropertyName("message")]
public required MessageDeletedMessageModel Message { get; set; }
}
@@ -241,17 +241,17 @@ public class KickModels
/// <summary>
/// ID of the user. Note it'll be 0 for the janny
/// </summary>
[JsonProperty("id")]
[JsonPropertyName("id")]
public int Id { get; set; }
/// <summary>
/// User's username
/// </summary>
[JsonProperty("username")]
[JsonPropertyName("username")]
public required string Username { get; set; }
/// <summary>
/// Slug suitable for URLs
/// </summary>
[JsonProperty("slug")]
[JsonPropertyName("slug")]
public required string Slug { get; set; }
}
@@ -260,22 +260,22 @@ public class KickModels
/// <summary>
/// GUID of the event
/// </summary>
[JsonProperty("id")]
[JsonPropertyName("id")]
public required string Id { get; set; }
/// <summary>
/// User who was banished
/// </summary>
[JsonProperty("user")]
[JsonPropertyName("user")]
public required UserBannedUserModel User { get; set; }
/// <summary>
/// Janny who did the sweeping
/// </summary>
[JsonProperty("banned_by")]
[JsonPropertyName("banned_by")]
public required UserBannedUserModel BannedBy { get; set; }
/// <summary>
/// Datetime that the ban expires. Null for permabans
/// </summary>
[JsonProperty("expires_at")]
[JsonPropertyName("expires_at")]
public DateTimeOffset? ExpiresAt { get; set; }
}
@@ -284,17 +284,17 @@ public class KickModels
/// <summary>
/// GUID of the event
/// </summary>
[JsonProperty("id")]
[JsonPropertyName("id")]
public required string Id { get; set; }
/// <summary>
/// User who was unbanned
/// </summary>
[JsonProperty("user")]
[JsonPropertyName("user")]
public required UserBannedUserModel User { get; set; }
/// <summary>
/// Janny who unbanned
/// </summary>
[JsonProperty("unbanned_by")]
[JsonPropertyName("unbanned_by")]
public required UserBannedUserModel UnbannedBy { get; set; }
}
@@ -303,12 +303,12 @@ public class KickModels
/// <summary>
/// ID representing the category
/// </summary>
[JsonProperty("id")]
[JsonPropertyName("id")]
public int Id { get; set; }
/// <summary>
/// Slug representing the category
/// </summary>
[JsonProperty("slug")]
[JsonPropertyName("slug")]
public required string Slug { get; set; }
}
@@ -317,27 +317,27 @@ public class KickModels
/// <summary>
/// ID of the category
/// </summary>
[JsonProperty("id")]
[JsonPropertyName("id")]
public int Id { get; set; }
/// <summary>
/// Friendly name of the category
/// </summary>
[JsonProperty("name")]
[JsonPropertyName("name")]
public required string Name { get; set; }
/// <summary>
/// Category's slug for forming URls etc.
/// </summary>
[JsonProperty("slug")]
[JsonPropertyName("slug")]
public required string Slug { get; set; }
/// <summary>
/// Tags for the category
/// </summary>
[JsonProperty("tags")]
[JsonPropertyName("tags")]
public List<string> Tags { get; set; } = [];
/// <summary>
/// Parent category, if one is present. I think there usually is one, but made it nullable just in case
/// </summary>
[JsonProperty("parent_category")]
[JsonPropertyName("parent_category")]
public UpdatedLiveStreamCategoryParentModel? ParentCategory { get; set; }
}
@@ -346,42 +346,42 @@ public class KickModels
/// <summary>
/// ID of the livestream (numeric)
/// </summary>
[JsonProperty("id")]
[JsonPropertyName("id")]
public int Id { get; set; }
/// <summary>
/// Livestream slug
/// </summary>
[JsonProperty("slug")]
[JsonPropertyName("slug")]
public required string Slug { get; set; }
/// <summary>
/// Livestream title
/// </summary>
[JsonProperty("session_title")]
[JsonPropertyName("session_title")]
public required string SessionTitle { get; set; }
/// <summary>
/// Livestream start time
/// </summary>
[JsonProperty("created_at")]
[JsonPropertyName("created_at")]
public DateTimeOffset CreatedAt { get; set; }
/// <summary>
/// Language of the livestream (e.g. English)
/// </summary>
[JsonProperty("language")]
[JsonPropertyName("language")]
public string? Language { get; set; }
/// <summary>
/// Whether the stream is marked as for a mature audience
/// </summary>
[JsonProperty("is_mature")]
[JsonPropertyName("is_mature")]
public bool IsMature { get; set; }
/// <summary>
/// Number of viewers presently watching
/// </summary>
[JsonProperty("viewers")]
[JsonPropertyName("viewers")]
public int Viewers { get; set; }
/// <summary>
/// Category of the livestream. I believe this is always required but marked it as nullable just in case
/// </summary>
[JsonProperty("category")]
[JsonPropertyName("category")]
public UpdatedLiveStreamCategoryModel? Category { get; set; }
}
@@ -390,12 +390,12 @@ public class KickModels
/// <summary>
/// ID of the channel
/// </summary>
[JsonProperty("id")]
[JsonPropertyName("id")]
public int Id { get; set; }
/// <summary>
/// Whether the streamer was sent to ban world
/// </summary>
[JsonProperty("is_banned")]
[JsonPropertyName("is_banned")]
public bool IsBanned { get; set; }
}
@@ -404,12 +404,12 @@ public class KickModels
/// <summary>
/// Livestream event ID
/// </summary>
[JsonProperty("id")]
[JsonPropertyName("id")]
public int Id { get; set; }
/// <summary>
/// Channel that stopped streaming
/// </summary>
[JsonProperty("channel")]
[JsonPropertyName("channel")]
public required StopStreamBroadcastLiveStreamChannelModel Channel { get; set; }
}
@@ -418,7 +418,7 @@ public class KickModels
/// <summary>
/// Object containing information related to the livestream that stopped
/// </summary>
[JsonProperty("livestream")]
[JsonPropertyName("livestream")]
public required StopStreamBroadcastLiveStreamModel Livestream { get; set; }
}
@@ -427,27 +427,27 @@ public class KickModels
/// <summary>
/// ID of the livestream
/// </summary>
[JsonProperty("id")]
[JsonPropertyName("id")]
public int Id { get; set; }
/// <summary>
/// ID of the channel
/// </summary>
[JsonProperty("channel_id")]
[JsonPropertyName("channel_id")]
public int ChannelId { get; set; }
/// <summary>
/// Title of the stream
/// </summary>
[JsonProperty("session_title")]
[JsonPropertyName("session_title")]
public required string SessionTitle { get; set; }
/// <summary>
/// No idea, just null on my end
/// </summary>
[JsonProperty("source")]
[JsonPropertyName("source")]
public string? Source { get; set; }
/// <summary>
/// Time stream started
/// </summary>
[JsonProperty("created_at")]
[JsonPropertyName("created_at")]
public DateTimeOffset CreatedAt { get; set; }
}
@@ -456,7 +456,7 @@ public class KickModels
/// <summary>
/// Object containing information related to the livestream that has just started
/// </summary>
[JsonProperty("livestream")]
[JsonPropertyName("livestream")]
public required StreamerIsLiveLiveStreamModel Livestream { get; set; }
}
@@ -465,17 +465,17 @@ public class KickModels
/// <summary>
/// ID of the poll option
/// </summary>
[JsonProperty("id")]
[JsonPropertyName("id")]
public int Id { get; set; }
/// <summary>
/// Label of the poll option
/// </summary>
[JsonProperty("label")]
[JsonPropertyName("label")]
public required string Label { get; set; }
/// <summary>
/// Number of votes the poll option has gotten
/// </summary>
[JsonProperty("votes")]
[JsonPropertyName("votes")]
public int Votes { get; set; }
}
@@ -484,27 +484,27 @@ public class KickModels
/// <summary>
/// Title of the poll
/// </summary>
[JsonProperty("title")]
[JsonPropertyName("title")]
public required string Title { get; set; }
/// <summary>
/// Poll options
/// </summary>
[JsonProperty("options")]
[JsonPropertyName("options")]
public List<PollUpdatePollOptionModel> Options { get; set; } = [];
/// <summary>
/// Duration of the poll in seconds
/// </summary>
[JsonProperty("duration")]
[JsonPropertyName("duration")]
public int Duration { get; set; }
/// <summary>
/// Remaining time in seconds
/// </summary>
[JsonProperty("remaining")]
[JsonPropertyName("remaining")]
public int Remaining { get; set; }
/// <summary>
/// Time in seconds to display the results after completion?
/// </summary>
[JsonProperty("result_display_duration")]
[JsonPropertyName("result_display_duration")]
public int ResultDisplayDuration { get; set; }
}
@@ -513,7 +513,7 @@ public class KickModels
/// <summary>
/// Poll data
/// </summary>
[JsonProperty("poll")]
[JsonPropertyName("poll")]
public required PollUpdatePollModel Poll { get; set; }
}
}

View File

@@ -1,4 +1,5 @@
using Newtonsoft.Json;
using System.Text.Json.Serialization;
using KickWsClient.Converters;
namespace KickWsClient.Models;
@@ -9,17 +10,18 @@ public class PusherModels
/// <summary>
/// Name of the event
/// </summary>
[JsonProperty("event")]
[JsonPropertyName("event")]
public required string Event { get; set; }
/// <summary>
/// Stringified JSON payload
/// </summary>
[JsonProperty("data")]
[JsonPropertyName("data")]
[JsonConverter(typeof(StringOrObjectConverter<string>))]
public required string Data { get; set; }
/// <summary>
/// Channel where event originates. Only included events where a channel is applicable
/// </summary>
[JsonProperty("channel")]
[JsonPropertyName("channel")]
public string? Channel { get; set; }
}
@@ -28,12 +30,12 @@ public class PusherModels
/// <summary>
/// Name of the event
/// </summary>
[JsonProperty("event")]
[JsonPropertyName("event")]
public required string Event { get; set; }
/// <summary>
/// Data as object. It's only stringified for responses
/// </summary>
[JsonProperty("data")]
[JsonPropertyName("data")]
public required object Data { get; set; }
}
@@ -42,12 +44,12 @@ public class PusherModels
/// <summary>
/// Internal socket ID
/// </summary>
[JsonProperty("socket_id")]
[JsonPropertyName("socket_id")]
public required string SocketId { get; set; }
/// <summary>
/// Timeout on no activity in seconds
/// </summary>
[JsonProperty("activity_timeout")]
[JsonPropertyName("activity_timeout")]
public int ActivityTimeout { get; set; }
}
@@ -56,12 +58,12 @@ public class PusherModels
/// <summary>
/// Token to authenticate with, use an empty string for guest.
/// </summary>
[JsonProperty("auth")]
[JsonPropertyName("auth")]
public string Auth { get; set; } = "";
/// <summary>
/// Channel you wish to subscribe to. 'channel.2515504' for stream events. 'chatrooms.2515504.v2' for chat where 2515504 is the channel ID
/// </summary>
[JsonProperty("channel")]
[JsonPropertyName("channel")]
public required string Channel { get; set; }
}
@@ -70,7 +72,7 @@ public class PusherModels
/// <summary>
/// Channel you wish to unsubscribe from, e.g. 'channel.2515504'
/// </summary>
[JsonProperty("channel")]
[JsonPropertyName("channel")]
public required string Channel { get; set; }
}
}