Refactored to fix compiler warnings

This commit is contained in:
barelyprofessional
2025-07-07 20:12:07 -05:00
parent bcc3bde6c9
commit 5f189cb9cc
28 changed files with 279 additions and 228 deletions

View File

@@ -88,6 +88,7 @@ public class NewKickChannelCommand : ICommand
{ {
var channels = (await SettingsProvider.GetValueAsync(BuiltIn.Keys.KickChannels)).JsonDeserialize<List<KickChannelModel>>(); var channels = (await SettingsProvider.GetValueAsync(BuiltIn.Keys.KickChannels)).JsonDeserialize<List<KickChannelModel>>();
var channelId = Convert.ToInt32(arguments["channel_id"].Value); var channelId = Convert.ToInt32(arguments["channel_id"].Value);
channels ??= [];
if (channels.Any(channel => channel.ChannelId == channelId)) if (channels.Any(channel => channel.ChannelId == channelId))
{ {
await botInstance.SendChatMessageAsync("Channel is already in the database", true); await botInstance.SendChatMessageAsync("Channel is already in the database", true);
@@ -119,6 +120,7 @@ public class RemoveKickChannelCommand : ICommand
public async Task RunCommand(ChatBot botInstance, MessageModel message, UserDbModel user, GroupCollection arguments, CancellationToken ctx) public async Task RunCommand(ChatBot botInstance, MessageModel message, UserDbModel user, GroupCollection arguments, CancellationToken ctx)
{ {
var channels = (await SettingsProvider.GetValueAsync(BuiltIn.Keys.KickChannels)).JsonDeserialize<List<KickChannelModel>>(); var channels = (await SettingsProvider.GetValueAsync(BuiltIn.Keys.KickChannels)).JsonDeserialize<List<KickChannelModel>>();
if (channels == null) throw new Exception("Caught a null when deserializing Kick channels");
var channelId = Convert.ToInt32(arguments["channel_id"].Value); var channelId = Convert.ToInt32(arguments["channel_id"].Value);
var channel = channels.FirstOrDefault(ch => ch.ChannelId == channelId); var channel = channels.FirstOrDefault(ch => ch.ChannelId == channelId);
if (channel == null) if (channel == null)
@@ -144,6 +146,11 @@ public class ReconnectKickCommand : ICommand
public TimeSpan Timeout => TimeSpan.FromSeconds(10); public TimeSpan Timeout => TimeSpan.FromSeconds(10);
public async Task RunCommand(ChatBot botInstance, MessageModel message, UserDbModel user, GroupCollection arguments, CancellationToken ctx) public async Task RunCommand(ChatBot botInstance, MessageModel message, UserDbModel user, GroupCollection arguments, CancellationToken ctx)
{ {
if (botInstance.BotServices.KickClient == null)
{
await botInstance.SendChatMessageAsync("Kick client is not initialized", true);
return;
}
botInstance.BotServices.KickClient.Disconnect(); botInstance.BotServices.KickClient.Disconnect();
await botInstance.SendChatMessageAsync("Disconnected from Kick. Client should reconnect shortly.", true); await botInstance.SendChatMessageAsync("Disconnected from Kick. Client should reconnect shortly.", true);
} }
@@ -365,6 +372,11 @@ public class SetAlmanacIntervalCommand : ICommand
return; return;
} }
await SettingsProvider.SetValueAsync(BuiltIn.Keys.BotAlmanacInterval, arguments["interval"].Value); await SettingsProvider.SetValueAsync(BuiltIn.Keys.BotAlmanacInterval, arguments["interval"].Value);
if (botInstance.BotServices.AlmanacShill == null)
{
await botInstance.SendChatMessageAsync("Value has been saved but almanac shill has not been initialized", true);
return;
}
await botInstance.BotServices.AlmanacShill.StopShillTaskAsync(); await botInstance.BotServices.AlmanacShill.StopShillTaskAsync();
botInstance.BotServices.AlmanacShill.StartShillTask(); botInstance.BotServices.AlmanacShill.StartShillTask();
await botInstance.SendChatMessageAsync($"@{message.Author.Username}, updated interval and restarted the shill task", true); await botInstance.SendChatMessageAsync($"@{message.Author.Username}, updated interval and restarted the shill task", true);
@@ -383,6 +395,11 @@ public class StopAlmanacCommand : ICommand
public async Task RunCommand(ChatBot botInstance, MessageModel message, UserDbModel user, GroupCollection arguments, public async Task RunCommand(ChatBot botInstance, MessageModel message, UserDbModel user, GroupCollection arguments,
CancellationToken ctx) CancellationToken ctx)
{ {
if (botInstance.BotServices.AlmanacShill == null)
{
await botInstance.SendChatMessageAsync("AlmanacShill is null", true);
return;
}
if (!botInstance.BotServices.AlmanacShill.IsShillTaskRunning()) if (!botInstance.BotServices.AlmanacShill.IsShillTaskRunning())
{ {
await botInstance.SendChatMessageAsync("Looks like the task isn't even running", true); await botInstance.SendChatMessageAsync("Looks like the task isn't even running", true);
@@ -407,6 +424,11 @@ public class StartAlmanacCommand : ICommand
public async Task RunCommand(ChatBot botInstance, MessageModel message, UserDbModel user, GroupCollection arguments, public async Task RunCommand(ChatBot botInstance, MessageModel message, UserDbModel user, GroupCollection arguments,
CancellationToken ctx) CancellationToken ctx)
{ {
if (botInstance.BotServices.AlmanacShill == null)
{
await botInstance.SendChatMessageAsync("AlmanacShill is null", true);
return;
}
if (botInstance.BotServices.AlmanacShill.IsShillTaskRunning()) if (botInstance.BotServices.AlmanacShill.IsShillTaskRunning())
{ {
await botInstance.SendChatMessageAsync("Looks like the task is already running", true); await botInstance.SendChatMessageAsync("Looks like the task is already running", true);

View File

@@ -79,7 +79,7 @@ public class ExceptionTestCommand : ICommand
public UserRight RequiredRight => UserRight.Admin; public UserRight RequiredRight => UserRight.Admin;
// Increased timeout as it has to wait for Sneedchat to echo the message and that can be slow sometimes // Increased timeout as it has to wait for Sneedchat to echo the message and that can be slow sometimes
public TimeSpan Timeout => TimeSpan.FromSeconds(15); public TimeSpan Timeout => TimeSpan.FromSeconds(15);
public async Task RunCommand(ChatBot botInstance, MessageModel message, UserDbModel user, GroupCollection arguments, CancellationToken ctx) public Task RunCommand(ChatBot botInstance, MessageModel message, UserDbModel user, GroupCollection arguments, CancellationToken ctx)
{ {
throw new Exception("Caused by the test exception command"); throw new Exception("Caused by the test exception command");
} }

View File

@@ -64,8 +64,14 @@ public class GetVersionCommand : ICommand
public TimeSpan Timeout => TimeSpan.FromSeconds(10); public TimeSpan Timeout => TimeSpan.FromSeconds(10);
public async Task RunCommand(ChatBot botInstance, MessageModel message, UserDbModel user, GroupCollection arguments, CancellationToken ctx) public async Task RunCommand(ChatBot botInstance, MessageModel message, UserDbModel user, GroupCollection arguments, CancellationToken ctx)
{ {
var version = Assembly.GetEntryAssembly() var version = Assembly.GetEntryAssembly()?
.GetCustomAttribute<AssemblyInformationalVersionAttribute>().InformationalVersion; .GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion;
if (version == null)
{
await botInstance.SendChatMessageAsync($"Caught a null when trying to retrieve the bot's assembly version.",
true);
return;
}
await botInstance.SendChatMessageAsync($"Bot compiled against {version.Split('+')[1]}", true); await botInstance.SendChatMessageAsync($"Bot compiled against {version.Split('+')[1]}", true);
} }
} }

View File

@@ -5,7 +5,7 @@ public class ChipsggBetModel
public DateTimeOffset Created { get; set; } public DateTimeOffset Created { get; set; }
// Can actually get the duration of a game from this // Can actually get the duration of a game from this
public DateTimeOffset Updated { get; set; } public DateTimeOffset Updated { get; set; }
public string UserId { get; set; } public string? UserId { get; set; }
// Sometimes null for no discernible reason // Sometimes null for no discernible reason
public string? Username { get; set; } public string? Username { get; set; }
// Win of any amount even if it's less than a 1x multi // Win of any amount even if it's less than a 1x multi
@@ -16,7 +16,7 @@ public class ChipsggBetModel
public float Multiplier { get; set; } public float Multiplier { get; set; }
public string? Currency { get; set; } public string? Currency { get; set; }
public float CurrencyPrice { get; set; } public float CurrencyPrice { get; set; }
public string BetId { get; set; } public string? BetId { get; set; }
} }
public class ChipsggCurrencyModel public class ChipsggCurrencyModel

View File

@@ -12,22 +12,22 @@ namespace KfChatDotNetBot.Services;
public class BetBolt : IDisposable public class BetBolt : IDisposable
{ {
private Logger _logger = LogManager.GetCurrentClassLogger(); private Logger _logger = LogManager.GetCurrentClassLogger();
private WebsocketClient _wsClient; private WebsocketClient? _wsClient;
private Uri _wsUri = new("wss://betbolt.com/api/ws"); private Uri _wsUri = new("wss://betbolt.com/api/ws");
// Pings every 5 seconds so 15 seconds should be reasonable // Pings every 5 seconds so 15 seconds should be reasonable
private int _reconnectTimeout = 15; private int _reconnectTimeout = 15;
private string? _proxy; private string? _proxy;
public delegate void OnBetBoltBetEventHandler(object sender, BetBoltBetModel bet); public delegate void OnBetBoltBetEventHandler(object sender, BetBoltBetModel bet);
public delegate void OnWsDisconnectionEventHandler(object sender, DisconnectionInfo e); public delegate void OnWsDisconnectionEventHandler(object sender, DisconnectionInfo e);
public event OnBetBoltBetEventHandler OnBetBoltBet; public event OnBetBoltBetEventHandler? OnBetBoltBet;
public event OnWsDisconnectionEventHandler OnWsDisconnection; public event OnWsDisconnectionEventHandler? OnWsDisconnection;
private CancellationToken _cancellationToken = CancellationToken.None; private CancellationToken _cancellationToken;
private IEnumerable<string>? _cookies = null; private IEnumerable<string>? _cookies;
private string? _userAgent = null; private string? _userAgent;
public BetBolt(string? proxy = null, CancellationToken? cancellationToken = null) public BetBolt(string? proxy = null, CancellationToken cancellationToken = default)
{ {
_proxy = proxy; _proxy = proxy;
if (cancellationToken != null) _cancellationToken = cancellationToken.Value; _cancellationToken = cancellationToken;
_logger.Info("Clash.gg WebSocket client created"); _logger.Info("Clash.gg WebSocket client created");
} }
public async Task StartWsClient() public async Task StartWsClient()
@@ -86,7 +86,7 @@ public class BetBolt : IDisposable
if (reconnectionInfo.Type == ReconnectionType.Initial) if (reconnectionInfo.Type == ReconnectionType.Initial)
{ {
_logger.Info("Sending subscribe payload to BetBolt"); _logger.Info("Sending subscribe payload to BetBolt");
_wsClient.Send("{\"topic\":\"system/EN\",\"action\":\"subscribe\"}"); _wsClient?.Send("{\"topic\":\"system/EN\",\"action\":\"subscribe\"}");
} }
} }

View File

@@ -19,29 +19,28 @@ public class BotServices
private readonly CancellationToken _cancellationToken; private readonly CancellationToken _cancellationToken;
private readonly Logger _logger = LogManager.GetCurrentClassLogger(); private readonly Logger _logger = LogManager.GetCurrentClassLogger();
internal KickWsClient.KickWsClient KickClient; internal KickWsClient.KickWsClient? KickClient;
private Twitch _twitch; private Twitch? _twitch;
private Shuffle _shuffle; private Shuffle? _shuffle;
private DiscordService _discord; private DiscordService? _discord;
private TwitchChat _twitchChat; private TwitchChat? _twitchChat;
private Jackpot _jackpot; private Jackpot? _jackpot;
private Howlgg _howlgg; private Howlgg? _howlgg;
private RainbetWs _rainbet; private RainbetWs? _rainbet;
private Chipsgg _chipsgg; private Chipsgg? _chipsgg;
private Clashgg _clashgg; private Clashgg? _clashgg;
private BetBolt _betBolt; private BetBolt? _betBolt;
private Yeet _yeet; private Yeet? _yeet;
public AlmanacShill AlmanacShill; public AlmanacShill? AlmanacShill;
private Task? _websocketWatchdog; private Task? _websocketWatchdog;
private Task? _howlggGetUserTimer; private Task? _howlggGetUserTimer;
private string _bmjTwitchUsername; private string? _bmjTwitchUsername;
private bool _twitchDisabled = false; private bool _twitchDisabled;
private string? _lastDiscordStatus; internal bool IsBmjLive;
internal bool IsBmjLive = false; private bool _isBmjLiveSynced;
private bool _isBmjLiveSynced = false; internal bool IsChrisDjLive;
internal bool IsChrisDjLive = false;
private Dictionary<string, SeenYeetBet> _yeetBets = new(); private Dictionary<string, SeenYeetBet> _yeetBets = new();
// lol // lol
@@ -142,7 +141,7 @@ public class BotServices
_logger.Debug("Chips.gg is disabled"); _logger.Debug("Chips.gg is disabled");
return; return;
} }
_chipsgg = new Chipsgg(settings[BuiltIn.Keys.Proxy].Value, _cancellationToken); _chipsgg = new Chipsgg(settings[BuiltIn.Keys.Proxy].Value);
_chipsgg.OnChipsggRecentBet += OnChipsggRecentBet; _chipsgg.OnChipsggRecentBet += OnChipsggRecentBet;
await _chipsgg.StartWsClient(); await _chipsgg.StartWsClient();
_logger.Info("Built Chips.gg Websocket connection"); _logger.Info("Built Chips.gg Websocket connection");
@@ -302,7 +301,7 @@ public class BotServices
]); ]);
try try
{ {
if (!_shuffle.IsConnected()) if (_shuffle != null && !_shuffle.IsConnected())
{ {
_logger.Error("Shuffle died, recreating it"); _logger.Error("Shuffle died, recreating it");
_shuffle.Dispose(); _shuffle.Dispose();
@@ -310,7 +309,7 @@ public class BotServices
await BuildShuffle(); await BuildShuffle();
} }
if (!_discord.IsConnected()) if (_discord != null && !_discord.IsConnected())
{ {
_logger.Error("Discord died, recreating it"); _logger.Error("Discord died, recreating it");
_discord.Dispose(); _discord.Dispose();
@@ -318,7 +317,7 @@ public class BotServices
await BuildDiscord(); await BuildDiscord();
} }
if (!_twitchDisabled && !_twitch.IsConnected()) if (!_twitchDisabled && _twitch != null && !_twitch.IsConnected())
{ {
_logger.Error("Twitch died, recreating it"); _logger.Error("Twitch died, recreating it");
_twitch.Dispose(); _twitch.Dispose();
@@ -326,7 +325,7 @@ public class BotServices
await BuildTwitch(); await BuildTwitch();
} }
if (!_twitchChat.IsConnected()) if (_twitchChat != null && !_twitchChat.IsConnected())
{ {
_logger.Error("Twitch chat died, recreating it"); _logger.Error("Twitch chat died, recreating it");
_twitchChat.Dispose(); _twitchChat.Dispose();
@@ -334,7 +333,7 @@ public class BotServices
await BuildTwitchChat(); await BuildTwitchChat();
} }
if (settings[BuiltIn.Keys.HowlggEnabled].ToBoolean() && !_howlgg.IsConnected()) if (settings[BuiltIn.Keys.HowlggEnabled].ToBoolean() && _howlgg != null && !_howlgg.IsConnected())
{ {
_logger.Error("Howl.gg died, recreating it"); _logger.Error("Howl.gg died, recreating it");
_howlgg.Dispose(); _howlgg.Dispose();
@@ -342,7 +341,7 @@ public class BotServices
await BuildHowlgg(); await BuildHowlgg();
} }
if (!_jackpot.IsConnected()) if (_jackpot != null && !_jackpot.IsConnected())
{ {
_logger.Error("Jackpot died, recreating it"); _logger.Error("Jackpot died, recreating it");
_jackpot.Dispose(); _jackpot.Dispose();
@@ -350,7 +349,7 @@ public class BotServices
await BuildJackpot(); await BuildJackpot();
} }
if (settings[BuiltIn.Keys.ChipsggEnabled].ToBoolean() && !_chipsgg.IsConnected()) if (settings[BuiltIn.Keys.ChipsggEnabled].ToBoolean() && _chipsgg != null && !_chipsgg.IsConnected())
{ {
_logger.Error("Chips died, recreating it"); _logger.Error("Chips died, recreating it");
_chipsgg.Dispose(); _chipsgg.Dispose();
@@ -358,7 +357,7 @@ public class BotServices
await BuildChipsgg(); await BuildChipsgg();
} }
if (settings[BuiltIn.Keys.KickEnabled].ToBoolean() && !KickClient.IsConnected()) if (settings[BuiltIn.Keys.KickEnabled].ToBoolean() && KickClient != null && !KickClient.IsConnected())
{ {
_logger.Error("Kick died, recreating it"); _logger.Error("Kick died, recreating it");
KickClient.Dispose(); KickClient.Dispose();
@@ -366,7 +365,7 @@ public class BotServices
await BuildKick(); await BuildKick();
} }
if (settings[BuiltIn.Keys.ClashggEnabled].ToBoolean() && !_clashgg.IsConnected()) if (settings[BuiltIn.Keys.ClashggEnabled].ToBoolean() && _clashgg != null && !_clashgg.IsConnected())
{ {
_logger.Error("Clash.gg died, recreating it"); _logger.Error("Clash.gg died, recreating it");
_clashgg.Dispose(); _clashgg.Dispose();
@@ -374,7 +373,7 @@ public class BotServices
await BuildClashgg(); await BuildClashgg();
} }
if (settings[BuiltIn.Keys.BetBoltEnabled].ToBoolean() && !_betBolt.IsConnected()) if (settings[BuiltIn.Keys.BetBoltEnabled].ToBoolean() && _betBolt != null && !_betBolt.IsConnected())
{ {
_logger.Error("BetBolt died, recreating it"); _logger.Error("BetBolt died, recreating it");
_betBolt.Dispose(); _betBolt.Dispose();
@@ -382,7 +381,7 @@ public class BotServices
await BuildBetBolt(); await BuildBetBolt();
} }
if (settings[BuiltIn.Keys.YeetEnabled].ToBoolean() && !_yeet.IsConnected()) if (settings[BuiltIn.Keys.YeetEnabled].ToBoolean() && _yeet != null && !_yeet.IsConnected())
{ {
_logger.Error("Yeet died, recreating it"); _logger.Error("Yeet died, recreating it");
_yeet.Dispose(); _yeet.Dispose();
@@ -390,7 +389,7 @@ public class BotServices
await BuildYeet(); await BuildYeet();
} }
if (settings[BuiltIn.Keys.RainbetEnabled].ToBoolean() && !_rainbet.IsConnected()) if (settings[BuiltIn.Keys.RainbetEnabled].ToBoolean() && _rainbet != null && !_rainbet.IsConnected())
{ {
_logger.Error("Rainbet died, recreating it"); _logger.Error("Rainbet died, recreating it");
_rainbet.Dispose(); _rainbet.Dispose();
@@ -549,7 +548,6 @@ public class BotServices
} }
_logger.Info("ALERT BMJ IS BETTING (on Yeet)"); _logger.Info("ALERT BMJ IS BETTING (on Yeet)");
if (CheckBmjIsLive(settings[BuiltIn.Keys.TwitchBossmanJackUsername].Value ?? "usernamenotset").Result) return; if (CheckBmjIsLive(settings[BuiltIn.Keys.TwitchBossmanJackUsername].Value ?? "usernamenotset").Result) return;
var payoutColor = settings[BuiltIn.Keys.KiwiFarmsGreenColor].Value;
//if (bet.WinAmountFiat < 0) payoutColor = settings[BuiltIn.Keys.KiwiFarmsRedColor].Value; //if (bet.WinAmountFiat < 0) payoutColor = settings[BuiltIn.Keys.KiwiFarmsRedColor].Value;
var msg = _chatBot.SendChatMessage($"🚨🚨 JEET BETTING 🚨🚨 {bet.Username} just bet {bet.BetAmount:C} worth of {bet.CurrencyCode} on {bet.GameName} 💩💩", true); var msg = _chatBot.SendChatMessage($"🚨🚨 JEET BETTING 🚨🚨 {bet.Username} just bet {bet.BetAmount:C} worth of {bet.CurrencyCode} on {bet.GameName} 💩💩", true);
_yeetBets.Add(bet.BetIdentifier, new SeenYeetBet {Bet = bet, Message = msg}); _yeetBets.Add(bet.BetIdentifier, new SeenYeetBet {Bet = bet, Message = msg});
@@ -809,7 +807,7 @@ public class BotServices
BuiltIn.Keys.KiwiFarmsGreenColor, BuiltIn.Keys.KiwiFarmsRedColor BuiltIn.Keys.KiwiFarmsGreenColor, BuiltIn.Keys.KiwiFarmsRedColor
]).Result; ]).Result;
_logger.Trace("Chips.gg bet has arrived"); _logger.Trace("Chips.gg bet has arrived");
if (!settings[BuiltIn.Keys.ChipsggBmjUserIds].JsonDeserialize<List<string>>()!.Contains(bet.UserId)) if (!settings[BuiltIn.Keys.ChipsggBmjUserIds].JsonDeserialize<List<string>>()!.Contains(bet.UserId ?? "0"))
{ {
return; return;
} }
@@ -817,9 +815,9 @@ public class BotServices
using var db = new ApplicationDbContext(); using var db = new ApplicationDbContext();
db.ChipsggBets.Add(new ChipsggBetDbModel db.ChipsggBets.Add(new ChipsggBetDbModel
{ {
Created = bet.Created, Updated = bet.Updated, UserId = bet.UserId, Username = bet.Username ?? "Unknown", Win = bet.Win, Created = bet.Created, Updated = bet.Updated, UserId = bet.UserId ?? "0", Username = bet.Username ?? "Unknown", Win = bet.Win,
Winnings = bet.Winnings, GameTitle = bet.GameTitle!, Amount = bet.Amount, Multiplier = bet.Multiplier, Winnings = bet.Winnings, GameTitle = bet.GameTitle!, Amount = bet.Amount, Multiplier = bet.Multiplier,
Currency = bet.Currency!, CurrencyPrice = bet.CurrencyPrice, BetId = bet.BetId Currency = bet.Currency!, CurrencyPrice = bet.CurrencyPrice, BetId = bet.BetId ?? "0"
}); });
db.SaveChanges(); db.SaveChanges();
if (CheckBmjIsLive(settings[BuiltIn.Keys.TwitchBossmanJackUsername].Value ?? "usernamenotset").Result) return; if (CheckBmjIsLive(settings[BuiltIn.Keys.TwitchBossmanJackUsername].Value ?? "usernamenotset").Result) return;
@@ -840,7 +838,7 @@ public class BotServices
if (kickChannels == null) return; if (kickChannels == null) return;
foreach (var channel in kickChannels) foreach (var channel in kickChannels)
{ {
KickClient.SendPusherSubscribe($"channel.{channel.ChannelId}"); KickClient?.SendPusherSubscribe($"channel.{channel.ChannelId}");
} }
} }
@@ -951,6 +949,11 @@ public class BotServices
// He was schizo betting on Dice, so I want to avoid a lot of API requests to Twitch in case they rate limit // He was schizo betting on Dice, so I want to avoid a lot of API requests to Twitch in case they rate limit
if (!_isBmjLiveSynced) if (!_isBmjLiveSynced)
{ {
if (_twitch == null)
{
_logger.Error("Twitch client has not been built!");
throw new Exception("Twitch client not initialized");
}
IsBmjLive = await _twitch.IsStreamLive(bmjUsername); IsBmjLive = await _twitch.IsStreamLive(bmjUsername);
_isBmjLiveSynced = true; _isBmjLiveSynced = true;
} }

View File

@@ -10,22 +10,20 @@ namespace KfChatDotNetBot.Services;
public class Chipsgg : IDisposable public class Chipsgg : IDisposable
{ {
private Logger _logger = LogManager.GetCurrentClassLogger(); private Logger _logger = LogManager.GetCurrentClassLogger();
private WebsocketClient _wsClient; private WebsocketClient? _wsClient;
private Uri _wsUri = new("wss://api.chips.gg/prod/socket"); private Uri _wsUri = new("wss://api.chips.gg/prod/socket");
// Chips doesn't have a heartbeat packet // Chips doesn't have a heartbeat packet
private int _reconnectTimeout = 30; private int _reconnectTimeout = 30;
private string? _proxy; private string? _proxy;
public delegate void OnChipsggRecentBetEventHandler(object sender, ChipsggBetModel bet); public delegate void OnChipsggRecentBetEventHandler(object sender, ChipsggBetModel bet);
public delegate void OnWsDisconnectionEventHandler(object sender, DisconnectionInfo e); public delegate void OnWsDisconnectionEventHandler(object sender, DisconnectionInfo e);
public event OnChipsggRecentBetEventHandler OnChipsggRecentBet; public event OnChipsggRecentBetEventHandler? OnChipsggRecentBet;
private CancellationToken _cancellationToken = CancellationToken.None;
private Dictionary<string, ChipsggCurrencyModel> _currencies = new(); private Dictionary<string, ChipsggCurrencyModel> _currencies = new();
private bool _authenticated = false; private bool _authenticated;
public Chipsgg(string? proxy = null, CancellationToken? cancellationToken = null) public Chipsgg(string? proxy = null)
{ {
_proxy = proxy; _proxy = proxy;
if (cancellationToken != null) _cancellationToken = cancellationToken.Value;
_logger.Info("Chipsgg WebSocket client created"); _logger.Info("Chipsgg WebSocket client created");
} }
@@ -83,7 +81,7 @@ public class Chipsgg : IDisposable
if (reconnectionInfo.Type == ReconnectionType.Initial) if (reconnectionInfo.Type == ReconnectionType.Initial)
{ {
_logger.Info("Sending auth payload to Chips.gg"); _logger.Info("Sending auth payload to Chips.gg");
_wsClient.Send("[\"auth\",1,\"token\",[]]"); _wsClient?.Send("[\"auth\",1,\"token\",[]]");
} }
} }
@@ -121,7 +119,7 @@ public class Chipsgg : IDisposable
{ {
var guid = firstElement[2].GetString(); var guid = firstElement[2].GetString();
_logger.Info("Received auth packet, sending back GUID auth with " + guid); _logger.Info("Received auth packet, sending back GUID auth with " + guid);
_wsClient.Send("[\"auth\",2,\"authenticate\",[\"" + guid+ "\"]]"); _wsClient?.Send("[\"auth\",2,\"authenticate\",[\"" + guid+ "\"]]");
_authenticated = true; _authenticated = true;
return; return;
} }
@@ -130,7 +128,7 @@ public class Chipsgg : IDisposable
{ {
_logger.Info("Chips.gg responded to our auth with: " + firstElement[2].GetString()); _logger.Info("Chips.gg responded to our auth with: " + firstElement[2].GetString());
_logger.Info("Sending Chips.gg recent bets subscription packet"); _logger.Info("Sending Chips.gg recent bets subscription packet");
_wsClient.Send("[\"stats\",12,\"on\",[{\"game\":\"bets\",\"type\":\"recentBets\"}]]"); _wsClient?.Send("[\"stats\",12,\"on\",[{\"game\":\"bets\",\"type\":\"recentBets\"}]]");
return; return;
} }
@@ -157,7 +155,7 @@ public class Chipsgg : IDisposable
{ {
_logger.Info("Received currency payload without getting the auth response. Retarded inconsistent Chips.gg behavior again, sending a made-up GUID"); _logger.Info("Received currency payload without getting the auth response. Retarded inconsistent Chips.gg behavior again, sending a made-up GUID");
var guid = Guid.NewGuid().ToString(); var guid = Guid.NewGuid().ToString();
_wsClient.Send("[\"auth\",2,\"authenticate\",[\"" + guid+ "\"]]"); _wsClient?.Send("[\"auth\",2,\"authenticate\",[\"" + guid+ "\"]]");
} }
_logger.Info("Received initial currency payload as the path array was empty"); _logger.Info("Received initial currency payload as the path array was empty");
var currencyData = dataElement[1].Deserialize<Dictionary<string, JsonElement>>(); var currencyData = dataElement[1].Deserialize<Dictionary<string, JsonElement>>();
@@ -172,7 +170,8 @@ public class Chipsgg : IDisposable
{ {
_logger.Info("Ignoring already defined currency"); _logger.Info("Ignoring already defined currency");
continue; continue;
}; }
float? price = null; float? price = null;
// Where a price is not set, the element is simply missing // Where a price is not set, the element is simply missing
if (currencies[currency].TryGetProperty("price", out var priceElement)) if (currencies[currency].TryGetProperty("price", out var priceElement))
@@ -206,7 +205,7 @@ public class Chipsgg : IDisposable
{ {
_logger.Debug("Ignoring packet as it contains koth"); _logger.Debug("Ignoring packet as it contains koth");
return; return;
}; }
var currency = innerDataPath[1]; var currency = innerDataPath[1];
if (_currencies.TryGetValue(currency, out var updatedCurrency)) if (_currencies.TryGetValue(currency, out var updatedCurrency))
{ {
@@ -227,7 +226,7 @@ public class Chipsgg : IDisposable
{ {
_logger.Info("Ignoring replay of recent bets"); _logger.Info("Ignoring replay of recent bets");
return; return;
}; }
} }
// Currency data may not be known until after so hold it here til we're done parsing // Currency data may not be known until after so hold it here til we're done parsing
var amount = string.Empty; var amount = string.Empty;
@@ -323,7 +322,7 @@ public class Chipsgg : IDisposable
{ {
_logger.Debug("Currency or GameTitle was null, ignoring"); _logger.Debug("Currency or GameTitle was null, ignoring");
return; return;
}; }
if (!_currencies.TryGetValue(bet.Currency, out var currencyData)) if (!_currencies.TryGetValue(bet.Currency, out var currencyData))
{ {
throw new Exception($"Unknown currency {bet.Currency}"); throw new Exception($"Unknown currency {bet.Currency}");
@@ -352,7 +351,7 @@ public class Chipsgg : IDisposable
public void Dispose() public void Dispose()
{ {
_wsClient.Dispose(); _wsClient?.Dispose();
GC.SuppressFinalize(this); GC.SuppressFinalize(this);
} }
} }

View File

@@ -1,5 +1,4 @@
using System.Net; using System.Net;
using System.Net.Http.Json;
using System.Net.WebSockets; using System.Net.WebSockets;
using System.Text.Json; using System.Text.Json;
using KfChatDotNetBot.Models; using KfChatDotNetBot.Models;
@@ -11,28 +10,28 @@ namespace KfChatDotNetBot.Services;
public class Clashgg : IDisposable public class Clashgg : IDisposable
{ {
private Logger _logger = LogManager.GetCurrentClassLogger(); private Logger _logger = LogManager.GetCurrentClassLogger();
private WebsocketClient _wsClient; private WebsocketClient? _wsClient;
private Uri _wsUri = new("wss://ws.clash.gg/"); private Uri _wsUri = new("wss://ws.clash.gg/");
// Ping interval is 30 seconds // Ping interval is 30 seconds
private int _reconnectTimeout = 60; private int _reconnectTimeout = 60;
private string? _proxy; private string? _proxy;
public delegate void OnClashBetEventHandler(object sender, ClashggBetModel data, JsonElement jsonElement); public delegate void OnClashBetEventHandler(object sender, ClashggBetModel data, JsonElement jsonElement);
public delegate void OnWsDisconnectionEventHandler(object sender, DisconnectionInfo e); public delegate void OnWsDisconnectionEventHandler(object sender, DisconnectionInfo e);
public event OnClashBetEventHandler OnClashBet; public event OnClashBetEventHandler? OnClashBet;
public event OnWsDisconnectionEventHandler OnWsDisconnection; public event OnWsDisconnectionEventHandler? OnWsDisconnection;
private CancellationToken _cancellationToken = CancellationToken.None; private CancellationToken _cancellationToken;
private CancellationTokenSource _pingCts = new(); private CancellationTokenSource _pingCts = new();
private Task? _heartbeatTask; private Task? _heartbeatTask;
private TimeSpan _heartbeatInterval = TimeSpan.FromSeconds(60); private TimeSpan _heartbeatInterval = TimeSpan.FromSeconds(60);
private bool _isOnline = false; private bool _isOnline;
private DateTime _lastBet = DateTime.Now; private DateTime _lastBet = DateTime.Now;
// How long we can go without any gambling activity until we go 'fuck it' and reconnect // How long we can go without any gambling activity until we go 'fuck it' and reconnect
private TimeSpan _lastBetTolerance = TimeSpan.FromMinutes(5); private TimeSpan _lastBetTolerance = TimeSpan.FromMinutes(5);
public Clashgg(string? proxy = null, CancellationToken? cancellationToken = null) public Clashgg(string? proxy = null, CancellationToken cancellationToken = default)
{ {
_proxy = proxy; _proxy = proxy;
if (cancellationToken != null) _cancellationToken = cancellationToken.Value; _cancellationToken = cancellationToken;
_logger.Info("Clash.gg WebSocket client created"); _logger.Info("Clash.gg WebSocket client created");
} }
@@ -128,9 +127,9 @@ public class Clashgg : IDisposable
if (packet[0].GetString() == "online" && !_isOnline) if (packet[0].GetString() == "online" && !_isOnline)
{ {
_logger.Info("Received online packet from Clash.gg. Subscribing to Plinko, Mines and Keno"); _logger.Info("Received online packet from Clash.gg. Subscribing to Plinko, Mines and Keno");
_wsClient.Send("[\"subscribe\",\"plinko\"]"); _wsClient?.Send("[\"subscribe\",\"plinko\"]");
_wsClient.Send("[\"subscribe\",\"mines\"]"); _wsClient?.Send("[\"subscribe\",\"mines\"]");
_wsClient.Send("[\"subscribe\",\"keno\"]"); _wsClient?.Send("[\"subscribe\",\"keno\"]");
_isOnline = true; _isOnline = true;
_heartbeatTask = Task.Run(HeartbeatTimer, _cancellationToken); _heartbeatTask = Task.Run(HeartbeatTimer, _cancellationToken);
return; return;
@@ -221,7 +220,7 @@ public class Clashgg : IDisposable
public void Dispose() public void Dispose()
{ {
_wsClient.Dispose(); _wsClient?.Dispose();
_pingCts.Cancel(); _pingCts.Cancel();
_pingCts.Dispose(); _pingCts.Dispose();
_heartbeatTask?.Dispose(); _heartbeatTask?.Dispose();

View File

@@ -11,7 +11,7 @@ namespace KfChatDotNetBot.Services;
public class DiscordService : IDisposable public class DiscordService : IDisposable
{ {
private readonly Logger _logger = LogManager.GetCurrentClassLogger(); private readonly Logger _logger = LogManager.GetCurrentClassLogger();
private WebsocketClient _wsClient; private WebsocketClient? _wsClient;
private readonly Uri _wsUri = new Uri("wss://gateway.discord.gg/?encoding=json&v=9"); private readonly Uri _wsUri = new Uri("wss://gateway.discord.gg/?encoding=json&v=9");
// Not sure what a good value for this would be // Not sure what a good value for this would be
private const int ReconnectTimeout = 60; private const int ReconnectTimeout = 60;
@@ -29,24 +29,24 @@ public class DiscordService : IDisposable
public delegate void ConversationSummaryUpdateEventHandler(object sender, public delegate void ConversationSummaryUpdateEventHandler(object sender,
DiscordConversationSummaryUpdateModel summary, string guildId); DiscordConversationSummaryUpdateModel summary, string guildId);
public event MessageReceivedEventHandler OnMessageReceived; public event MessageReceivedEventHandler? OnMessageReceived;
public event PresenceUpdateEventHandler OnPresenceUpdated; public event PresenceUpdateEventHandler? OnPresenceUpdated;
public event WsDisconnectionEventHandler OnWsDisconnection; public event WsDisconnectionEventHandler? OnWsDisconnection;
public event InvalidCredentialsEventHandler OnInvalidCredentials; public event InvalidCredentialsEventHandler? OnInvalidCredentials;
public event ChannelCreatedEventHandler OnChannelCreated; public event ChannelCreatedEventHandler? OnChannelCreated;
public event ChannelDeletedEventHandler OnChannelDeleted; public event ChannelDeletedEventHandler? OnChannelDeleted;
public event ConversationSummaryUpdateEventHandler OnConversationSummaryUpdate; public event ConversationSummaryUpdateEventHandler? OnConversationSummaryUpdate;
private readonly CancellationToken _cancellationToken = CancellationToken.None; private readonly CancellationToken _cancellationToken;
private readonly CancellationTokenSource _pingCts = new(); private readonly CancellationTokenSource _pingCts = new();
private Task? _heartbeatTask; private Task? _heartbeatTask;
// Discord tells us the heartbeat interval to use in the op 10 response so this is just a placeholder // Discord tells us the heartbeat interval to use in the op 10 response so this is just a placeholder
private TimeSpan _heartbeatInterval = TimeSpan.FromSeconds(40); private TimeSpan _heartbeatInterval = TimeSpan.FromSeconds(40);
public DiscordService(string authorization, string? proxy = null, CancellationToken? cancellationToken = null) public DiscordService(string authorization, string? proxy = null, CancellationToken cancellationToken = default)
{ {
_proxy = proxy; _proxy = proxy;
if (cancellationToken != null) _cancellationToken = cancellationToken.Value; _cancellationToken = cancellationToken;
_authorization = authorization; _authorization = authorization;
_logger.Info("Discord Service created"); _logger.Info("Discord Service created");
} }
@@ -159,7 +159,7 @@ public class DiscordService : IDisposable
"\"client_build_number\":306208,\"client_event_source\":null,\"design_id\":0},\"presence\":{\"status\":\"unknown\"," + "\"client_build_number\":306208,\"client_event_source\":null,\"design_id\":0},\"presence\":{\"status\":\"unknown\"," +
"\"since\":0,\"activities\":[],\"afk\":false},\"compress\":false,\"client_state\":{\"guild_versions\":{}}}}"; "\"since\":0,\"activities\":[],\"afk\":false},\"compress\":false,\"client_state\":{\"guild_versions\":{}}}}";
_logger.Debug(initPayload); _logger.Debug(initPayload);
_wsClient.SendInstant(initPayload).Wait(_cancellationToken); _wsClient?.SendInstant(initPayload).Wait(_cancellationToken);
_heartbeatInterval = _heartbeatInterval =
TimeSpan.FromMilliseconds(packet.Data.GetProperty("heartbeat_interval").GetInt32()); TimeSpan.FromMilliseconds(packet.Data.GetProperty("heartbeat_interval").GetInt32());
if (_heartbeatTask != null) return; if (_heartbeatTask != null) return;
@@ -235,7 +235,7 @@ public class DiscordService : IDisposable
public void Dispose() public void Dispose()
{ {
_logger.Info("Disposing of the Discord service"); _logger.Info("Disposing of the Discord service");
_wsClient.Dispose(); _wsClient?.Dispose();
_pingCts.Cancel(); _pingCts.Cancel();
_pingCts.Dispose(); _pingCts.Dispose();
_heartbeatTask?.Dispose(); _heartbeatTask?.Dispose();

View File

@@ -10,25 +10,25 @@ namespace KfChatDotNetBot.Services;
public class Howlgg : IDisposable public class Howlgg : IDisposable
{ {
private Logger _logger = LogManager.GetCurrentClassLogger(); private Logger _logger = LogManager.GetCurrentClassLogger();
private WebsocketClient _wsClient; private WebsocketClient? _wsClient;
private Uri _wsUri = new("wss://howl.gg/socket.io/?EIO=3&transport=websocket"); private Uri _wsUri = new("wss://howl.gg/socket.io/?EIO=3&transport=websocket");
// Howl will send its own timeout but seems it's always 30 seconds // Howl will send its own timeout but seems it's always 30 seconds
private int _reconnectTimeout = 30; private int _reconnectTimeout = 30;
private string? _proxy; private string? _proxy;
public delegate void OnHowlggBetHistoryResponse(object sender, HowlggBetHistoryResponseModel data); public delegate void OnHowlggBetHistoryResponse(object sender, HowlggBetHistoryResponseModel data);
public delegate void OnWsDisconnectionEventHandler(object sender, DisconnectionInfo e); public delegate void OnWsDisconnectionEventHandler(object sender, DisconnectionInfo e);
public event OnHowlggBetHistoryResponse OnHowlggBetHistory; public event OnHowlggBetHistoryResponse? OnHowlggBetHistory;
public event OnWsDisconnectionEventHandler OnWsDisconnection; public event OnWsDisconnectionEventHandler? OnWsDisconnection;
private CancellationToken _cancellationToken = CancellationToken.None; private CancellationToken _cancellationToken;
private CancellationTokenSource _pingCts = new(); private CancellationTokenSource _pingCts = new();
private Task? _heartbeatTask; private Task? _heartbeatTask;
// Howl.gg tells us the heartbeat interval to use in the initial payload so this is just a placeholder // Howl.gg tells us the heartbeat interval to use in the initial payload so this is just a placeholder
private TimeSpan _heartbeatInterval = TimeSpan.FromSeconds(40); private TimeSpan _heartbeatInterval = TimeSpan.FromSeconds(40);
public Howlgg(string? proxy = null, CancellationToken? cancellationToken = null) public Howlgg(string? proxy = null, CancellationToken cancellationToken = default)
{ {
_proxy = proxy; _proxy = proxy;
if (cancellationToken != null) _cancellationToken = cancellationToken.Value; _cancellationToken = cancellationToken;
_logger.Info("Howlgg WebSocket client created"); _logger.Info("Howlgg WebSocket client created");
} }
@@ -77,7 +77,7 @@ public class Howlgg : IDisposable
{ {
var packet = "42/main,0[\"getUserInfo\",{\"userOrSteamId\":\"" + userId + "\",\"interval\":\"lifetime\"}]"; var packet = "42/main,0[\"getUserInfo\",{\"userOrSteamId\":\"" + userId + "\",\"interval\":\"lifetime\"}]";
_logger.Debug($"Sending packet: {packet}"); _logger.Debug($"Sending packet: {packet}");
_wsClient.Send(packet); _wsClient?.Send(packet);
} }
private async Task HeartbeatTimer() private async Task HeartbeatTimer()
@@ -141,7 +141,7 @@ public class Howlgg : IDisposable
if (message.Text == "40") if (message.Text == "40")
{ {
_logger.Trace("Ready to subscribe, sending main subscription"); _logger.Trace("Ready to subscribe, sending main subscription");
_wsClient.Send("40/main,"); _wsClient?.Send("40/main,");
// To indicate successful subscription it echoes back the channel to you // To indicate successful subscription it echoes back the channel to you
return; return;
} }
@@ -152,7 +152,6 @@ public class Howlgg : IDisposable
var jsonPayload = message.Text.Replace("43/main,0[null,", string.Empty).TrimEnd(']'); var jsonPayload = message.Text.Replace("43/main,0[null,", string.Empty).TrimEnd(']');
var data = JsonSerializer.Deserialize<HowlggBetHistoryResponseModel>(jsonPayload); var data = JsonSerializer.Deserialize<HowlggBetHistoryResponseModel>(jsonPayload);
if (data != null) OnHowlggBetHistory?.Invoke(this, data); if (data != null) OnHowlggBetHistory?.Invoke(this, data);
return;
} }
} }
catch (Exception e) catch (Exception e)
@@ -167,7 +166,7 @@ public class Howlgg : IDisposable
public void Dispose() public void Dispose()
{ {
_wsClient.Dispose(); _wsClient?.Dispose();
_pingCts.Cancel(); _pingCts.Cancel();
_pingCts.Dispose(); _pingCts.Dispose();
_heartbeatTask?.Dispose(); _heartbeatTask?.Dispose();

View File

@@ -11,25 +11,25 @@ namespace KfChatDotNetBot.Services;
public class Jackpot : IDisposable public class Jackpot : IDisposable
{ {
private Logger _logger = LogManager.GetCurrentClassLogger(); private Logger _logger = LogManager.GetCurrentClassLogger();
private WebsocketClient _wsClient; private WebsocketClient? _wsClient;
private Uri _wsUri = new("wss://api.jackpot.bet/feeds/websocket"); private Uri _wsUri = new("wss://api.jackpot.bet/feeds/websocket");
// Ping interval is 30 seconds // Ping interval is 30 seconds
private int _reconnectTimeout = 60; private int _reconnectTimeout = 60;
private string? _proxy; private string? _proxy;
public delegate void OnJackpotBetEventHandler(object sender, JackpotWsBetPayloadModel data); public delegate void OnJackpotBetEventHandler(object sender, JackpotWsBetPayloadModel data);
public delegate void OnWsDisconnectionEventHandler(object sender, DisconnectionInfo e); public delegate void OnWsDisconnectionEventHandler(object sender, DisconnectionInfo e);
public event OnJackpotBetEventHandler OnJackpotBet; public event OnJackpotBetEventHandler? OnJackpotBet;
public event OnWsDisconnectionEventHandler OnWsDisconnection; public event OnWsDisconnectionEventHandler? OnWsDisconnection;
private CancellationToken _cancellationToken = CancellationToken.None; private CancellationToken _cancellationToken;
private CancellationTokenSource _pingCts = new(); private CancellationTokenSource _pingCts = new();
private Task? _heartbeatTask; private Task? _heartbeatTask;
// There's no smarts, it just does 30-second pings // There's no smarts, it just does 30-second pings
private TimeSpan _heartbeatInterval = TimeSpan.FromSeconds(30); private TimeSpan _heartbeatInterval = TimeSpan.FromSeconds(30);
public Jackpot(string? proxy = null, CancellationToken? cancellationToken = null) public Jackpot(string? proxy = null, CancellationToken cancellationToken = default)
{ {
_proxy = proxy; _proxy = proxy;
if (cancellationToken != null) _cancellationToken = cancellationToken.Value; _cancellationToken = cancellationToken;
_logger.Info("Jackpot WebSocket client created"); _logger.Info("Jackpot WebSocket client created");
} }
@@ -103,7 +103,7 @@ public class Jackpot : IDisposable
if (reconnectionInfo.Type == ReconnectionType.Initial) if (reconnectionInfo.Type == ReconnectionType.Initial)
{ {
_logger.Debug("Sending initial payload"); _logger.Debug("Sending initial payload");
_wsClient.Send("{\"id\":\"lfgkenokasinoinit\",\"type\":\"connection_init\"}"); _wsClient?.Send("{\"id\":\"lfgkenokasinoinit\",\"type\":\"connection_init\"}");
} }
} }
@@ -129,7 +129,7 @@ public class Jackpot : IDisposable
if (packet.Type == "connection_ack") if (packet.Type == "connection_ack")
{ {
_logger.Debug("Received ack from Jackpot. Sending subscription"); _logger.Debug("Received ack from Jackpot. Sending subscription");
_wsClient.Send( _wsClient?.Send(
"{\"id\":\"lfgkenokasinoallbets\",\"type\":\"subscribe\",\"payload\":{\"feed\":\"all_bets\"}}\n"); "{\"id\":\"lfgkenokasinoallbets\",\"type\":\"subscribe\",\"payload\":{\"feed\":\"all_bets\"}}\n");
_logger.Debug("Setting up heartbeat timer"); _logger.Debug("Setting up heartbeat timer");
if (_heartbeatTask != null) return; if (_heartbeatTask != null) return;
@@ -178,7 +178,7 @@ public class Jackpot : IDisposable
public void Dispose() public void Dispose()
{ {
_wsClient.Dispose(); _wsClient?.Dispose();
_pingCts.Cancel(); _pingCts.Cancel();
_pingCts.Dispose(); _pingCts.Dispose();
_heartbeatTask?.Dispose(); _heartbeatTask?.Dispose();

View File

@@ -75,7 +75,7 @@ public class KiwiFlare(string kfDomain, string? proxy = null, CancellationToken?
return true; return true;
} }
private async Task<KiwiFlareChallengeSolutionModel> ChallengeWorker(KiwiFlareChallengeModel challenge) private Task<KiwiFlareChallengeSolutionModel> ChallengeWorker(KiwiFlareChallengeModel challenge)
{ {
var nonce = _random.NextInt64(); var nonce = _random.NextInt64();
while (true) while (true)
@@ -84,11 +84,11 @@ public class KiwiFlare(string kfDomain, string? proxy = null, CancellationToken?
var input = Encoding.UTF8.GetBytes($"{challenge.Salt}{nonce}"); var input = Encoding.UTF8.GetBytes($"{challenge.Salt}{nonce}");
if (!TestHash(SHA256.HashData(input), challenge.Difficulty)) continue; if (!TestHash(SHA256.HashData(input), challenge.Difficulty)) continue;
_logger.Debug($"Hash passed the test, nonce: {nonce}"); _logger.Debug($"Hash passed the test, nonce: {nonce}");
return new KiwiFlareChallengeSolutionModel return Task.FromResult(new KiwiFlareChallengeSolutionModel
{ {
Nonce = nonce, Nonce = nonce,
Salt = challenge.Salt Salt = challenge.Salt
}; });
} }
} }

View File

@@ -11,8 +11,8 @@ public class Rainbet : IDisposable
{ {
private Logger _logger = LogManager.GetCurrentClassLogger(); private Logger _logger = LogManager.GetCurrentClassLogger();
public delegate void OnRainbetBetEventHandler(object sender, List<RainbetBetHistoryModel> bets); public delegate void OnRainbetBetEventHandler(object sender, List<RainbetBetHistoryModel> bets);
public event OnRainbetBetEventHandler OnRainbetBet; public event OnRainbetBetEventHandler? OnRainbetBet;
private CancellationToken _cancellationToken = CancellationToken.None; private CancellationToken _cancellationToken;
private CancellationTokenSource _gameHistoryCts = new(); private CancellationTokenSource _gameHistoryCts = new();
private Task? _gameHistoryTask; private Task? _gameHistoryTask;
private TimeSpan _gameHistoryInterval = TimeSpan.FromSeconds(60); private TimeSpan _gameHistoryInterval = TimeSpan.FromSeconds(60);
@@ -20,9 +20,9 @@ public class Rainbet : IDisposable
private string? _userAgent = null; private string? _userAgent = null;
public DateTimeOffset LastSuccessfulRefresh = DateTimeOffset.MinValue; public DateTimeOffset LastSuccessfulRefresh = DateTimeOffset.MinValue;
public Rainbet(CancellationToken? cancellationToken = null) public Rainbet(CancellationToken cancellationToken = default)
{ {
if (cancellationToken != null) _cancellationToken = cancellationToken.Value; _cancellationToken = cancellationToken;
_logger.Info("Rainbet client created"); _logger.Info("Rainbet client created");
} }
@@ -118,6 +118,7 @@ public class Rainbet : IDisposable
throw new Exception("Cloudflare challenged"); throw new Exception("Cloudflare challenged");
} }
var bets = await response.Content.ReadFromJsonAsync<List<RainbetBetHistoryModel>>(cancellationToken: _cancellationToken); var bets = await response.Content.ReadFromJsonAsync<List<RainbetBetHistoryModel>>(cancellationToken: _cancellationToken);
if (bets == null) throw new Exception("Caught a null when deserializing Rainbet bet history");
return bets; return bets;
} }

View File

@@ -18,16 +18,16 @@ public class RainbetWs : IDisposable
private string? _proxy; private string? _proxy;
public delegate void OnRainbetBetEventHandler(object sender, RainbetWsBetModel bet); public delegate void OnRainbetBetEventHandler(object sender, RainbetWsBetModel bet);
public delegate void OnWsDisconnectionEventHandler(object sender, DisconnectionInfo e); public delegate void OnWsDisconnectionEventHandler(object sender, DisconnectionInfo e);
public event OnRainbetBetEventHandler OnRainbetBet; public event OnRainbetBetEventHandler? OnRainbetBet;
public event OnWsDisconnectionEventHandler OnWsDisconnection; public event OnWsDisconnectionEventHandler? OnWsDisconnection;
private CancellationToken _cancellationToken = CancellationToken.None; private CancellationToken _cancellationToken;
private IEnumerable<string>? _cookies = null; private IEnumerable<string>? _cookies;
private string? _userAgent = null; private string? _userAgent;
public RainbetWs(string? proxy = null, CancellationToken? cancellationToken = null) public RainbetWs(string? proxy = null, CancellationToken cancellationToken = default)
{ {
_proxy = proxy; _proxy = proxy;
if (cancellationToken != null) _cancellationToken = cancellationToken.Value; _cancellationToken = cancellationToken;
_logger.Info("Rainbet WebSocket client created"); _logger.Info("Rainbet WebSocket client created");
} }
@@ -101,14 +101,14 @@ public class RainbetWs : IDisposable
{ {
// 0{"sid":"Pf940zhaAqb6BHBiSgLa","upgrades":[],"pingInterval":25000,"pingTimeout":20000,"maxPayload":100000000} // 0{"sid":"Pf940zhaAqb6BHBiSgLa","upgrades":[],"pingInterval":25000,"pingTimeout":20000,"maxPayload":100000000}
_logger.Info("Received initial connection message from Rainbet, sending subscribe"); _logger.Info("Received initial connection message from Rainbet, sending subscribe");
_wsClient.Send("40/game-history,{}"); _wsClient?.Send("40/game-history,{}");
return; return;
} }
var packetType = message.Text.Split('/')[0]; var packetType = message.Text.Split('/')[0];
if (packetType == "2") if (packetType == "2")
{ {
_logger.Info("Received ping from Rainbet, replying with pong"); _logger.Info("Received ping from Rainbet, replying with pong");
_wsClient.Send("3"); _wsClient?.Send("3");
return; return;
} }
@@ -124,14 +124,14 @@ public class RainbetWs : IDisposable
{ {
var data = JsonSerializer.Deserialize<List<JsonElement>>(message.Text.Replace("42/game-history,", var data = JsonSerializer.Deserialize<List<JsonElement>>(message.Text.Replace("42/game-history,",
string.Empty)); string.Empty));
if (data == null) throw new Exception("Caught a null when deserializing game history");
if (data[0].GetString() == "new-history") if (data[0].GetString() == "new-history")
{ {
OnRainbetBet?.Invoke(this, data[1].Deserialize<RainbetWsBetModel>()); OnRainbetBet?.Invoke(this, data[1].Deserialize<RainbetWsBetModel>()!);
return; return;
} }
_logger.Info($"Event {data[0].GetString()} from Rainbet was not handled"); _logger.Info($"Event {data[0].GetString()} from Rainbet was not handled");
_logger.Info(message.Text); _logger.Info(message.Text);
return;
} }
} }
catch (Exception e) catch (Exception e)

View File

@@ -11,22 +11,22 @@ namespace KfChatDotNetBot.Services;
public class Shuffle : IDisposable public class Shuffle : IDisposable
{ {
private Logger _logger = LogManager.GetCurrentClassLogger(); private Logger _logger = LogManager.GetCurrentClassLogger();
private WebsocketClient _wsClient; private WebsocketClient? _wsClient;
private Uri _wsUri = new("wss://shuffle.com/main-api/bp-subscription/subscription/graphql"); private Uri _wsUri = new("wss://shuffle.com/main-api/bp-subscription/subscription/graphql");
private int _reconnectTimeout = 60; private int _reconnectTimeout = 60;
private string? _proxy; private string? _proxy;
public delegate void OnLatestBetUpdatedEventHandler(object sender, ShuffleLatestBetModel bet); public delegate void OnLatestBetUpdatedEventHandler(object sender, ShuffleLatestBetModel bet);
public delegate void OnWsDisconnectionEventHandler(object sender, DisconnectionInfo e); public delegate void OnWsDisconnectionEventHandler(object sender, DisconnectionInfo e);
public event OnLatestBetUpdatedEventHandler OnLatestBetUpdated; public event OnLatestBetUpdatedEventHandler? OnLatestBetUpdated;
public event OnWsDisconnectionEventHandler OnWsDisconnection; public event OnWsDisconnectionEventHandler? OnWsDisconnection;
private CancellationToken _cancellationToken = CancellationToken.None; private CancellationToken _cancellationToken;
private CancellationTokenSource _pingCts = new(); private CancellationTokenSource _pingCts = new();
private Task _pingTask; private Task _pingTask;
public Shuffle(string? proxy = null, CancellationToken? cancellationToken = null) public Shuffle(string? proxy = null, CancellationToken cancellationToken = default)
{ {
_proxy = proxy; _proxy = proxy;
if (cancellationToken != null) _cancellationToken = cancellationToken.Value; _cancellationToken = cancellationToken;
// Moved it up here as I'm concerned about the possibility of reconnections creating multiple ping tasks // Moved it up here as I'm concerned about the possibility of reconnections creating multiple ping tasks
_pingTask = PeriodicPing(); _pingTask = PeriodicPing();
} }
@@ -103,7 +103,7 @@ public class Shuffle : IDisposable
var initPayload = var initPayload =
"{\"type\":\"connection_init\",\"payload\":{\"x-correlation-id\":\"pdvlnd9tej-di27abvq19-1.30.2-1i0nef1m7-g::anon\",\"authorization\":\"\"}}"; "{\"type\":\"connection_init\",\"payload\":{\"x-correlation-id\":\"pdvlnd9tej-di27abvq19-1.30.2-1i0nef1m7-g::anon\",\"authorization\":\"\"}}";
_logger.Debug(initPayload); _logger.Debug(initPayload);
_wsClient.Send(initPayload); _wsClient?.Send(initPayload);
} }
private void WsMessageReceived(ResponseMessage message) private void WsMessageReceived(ResponseMessage message)
@@ -128,7 +128,7 @@ public class Shuffle : IDisposable
var payload = "{\"id\":\"" + Guid.NewGuid() + var payload = "{\"id\":\"" + Guid.NewGuid() +
"\",\"type\":\"subscribe\",\"payload\":{\"variables\":{},\"extensions\":{},\"operationName\":\"LatestBetUpdated\",\"query\":\"subscription LatestBetUpdated {\\n latestBetUpdated {\\n ...BetActivityFields\\n __typename\\n }\\n}\\n\\nfragment BetActivityFields on BetActivityPayload {\\n id\\n username\\n vipLevel\\n currency\\n amount\\n payout\\n multiplier\\n gameName\\n gameCategory\\n gameSlug\\n __typename\\n}\"}}"; "\",\"type\":\"subscribe\",\"payload\":{\"variables\":{},\"extensions\":{},\"operationName\":\"LatestBetUpdated\",\"query\":\"subscription LatestBetUpdated {\\n latestBetUpdated {\\n ...BetActivityFields\\n __typename\\n }\\n}\\n\\nfragment BetActivityFields on BetActivityPayload {\\n id\\n username\\n vipLevel\\n currency\\n amount\\n payout\\n multiplier\\n gameName\\n gameCategory\\n gameSlug\\n __typename\\n}\"}}";
_logger.Debug(payload); _logger.Debug(payload);
_wsClient.SendInstant(payload).Wait(_cancellationToken); _wsClient?.SendInstant(payload).Wait(_cancellationToken);
return; return;
} }
@@ -206,7 +206,7 @@ public class Shuffle : IDisposable
public void Dispose() public void Dispose()
{ {
_wsClient.Dispose(); _wsClient?.Dispose();
// Rare bug but has happened at least once // Rare bug but has happened at least once
try try
{ {

View File

@@ -12,15 +12,17 @@ public class ThreeXplPocketWatch
private Logger _logger = LogManager.GetCurrentClassLogger(); private Logger _logger = LogManager.GetCurrentClassLogger();
private string _3xplToken = "3A0_t3st3xplor3rpub11cb3t4efcd21748a5e"; private string _3xplToken = "3A0_t3st3xplor3rpub11cb3t4efcd21748a5e";
private string? _proxy; private string? _proxy;
private CancellationToken _cancellationToken = CancellationToken.None; private CancellationToken _cancellationToken;
public delegate void OnPocketWatchEventHandler(object sender, PocketWatchTransactionDbModel e); public delegate void OnPocketWatchEventHandler(object sender, PocketWatchTransactionDbModel e);
public event OnPocketWatchEventHandler OnPocketWatchEvent; #pragma warning disable CS0067 // Event is never used
public event OnPocketWatchEventHandler? OnPocketWatchEvent;
#pragma warning restore CS0067 // Event is never used
public ThreeXplPocketWatch(string? proxy = null, CancellationToken? cancellationToken = null) public ThreeXplPocketWatch(string? proxy = null, CancellationToken cancellationToken = default)
{ {
_logger.Info("Starting the pocket watch"); _logger.Info("Starting the pocket watch");
_proxy = proxy; _proxy = proxy;
if (cancellationToken != null) _cancellationToken = cancellationToken.Value; _cancellationToken = cancellationToken;
} }
private async Task CheckAddress(PocketWatchAddressDbModel addy) private async Task CheckAddress(PocketWatchAddressDbModel addy)

View File

@@ -11,7 +11,7 @@ namespace KfChatDotNetBot.Services;
public class Twitch : IDisposable public class Twitch : IDisposable
{ {
private Logger _logger = LogManager.GetCurrentClassLogger(); private Logger _logger = LogManager.GetCurrentClassLogger();
private WebsocketClient _wsClient; private WebsocketClient? _wsClient;
private Uri _wsUri = new("wss://pubsub-edge.twitch.tv/v1"); private Uri _wsUri = new("wss://pubsub-edge.twitch.tv/v1");
private int _reconnectTimeout = 300; private int _reconnectTimeout = 300;
private string? _proxy; private string? _proxy;
@@ -19,18 +19,18 @@ public class Twitch : IDisposable
public delegate void OnStreamStateUpdateEventHandler(object sender, int channelId, bool isLive); public delegate void OnStreamStateUpdateEventHandler(object sender, int channelId, bool isLive);
public delegate void OnStreamCommercialEventHandler(object sender, int channelId, int length, bool scheduled); public delegate void OnStreamCommercialEventHandler(object sender, int channelId, int length, bool scheduled);
public delegate void OnStreamTosStrikeEventHandler(object sender, int channelId); public delegate void OnStreamTosStrikeEventHandler(object sender, int channelId);
public event OnStreamStateUpdateEventHandler OnStreamStateUpdated; public event OnStreamStateUpdateEventHandler? OnStreamStateUpdated;
public event OnStreamCommercialEventHandler OnStreamCommercial; public event OnStreamCommercialEventHandler? OnStreamCommercial;
public event OnStreamTosStrikeEventHandler OnStreamTosStrike; public event OnStreamTosStrikeEventHandler? OnStreamTosStrike;
private CancellationToken _cancellationToken = CancellationToken.None; private CancellationToken _cancellationToken;
private Task? _pingTask = null; private Task? _pingTask;
private CancellationTokenSource _pingCts = new(); private CancellationTokenSource _pingCts = new();
public Twitch(List<int> channels, string? proxy = null, CancellationToken? cancellationToken = null) public Twitch(List<int> channels, string? proxy = null, CancellationToken cancellationToken = default)
{ {
_proxy = proxy; _proxy = proxy;
_channels = channels; _channels = channels;
if (cancellationToken != null) _cancellationToken = cancellationToken.Value; _cancellationToken = cancellationToken;
} }
public async Task StartWsClient() public async Task StartWsClient()
@@ -77,7 +77,7 @@ public class Twitch : IDisposable
private void SendPing() private void SendPing()
{ {
_logger.Info("Sending ping to Twitch"); _logger.Info("Sending ping to Twitch");
_wsClient.Send("{\"type\":\"PING\"}"); _wsClient?.Send("{\"type\":\"PING\"}");
} }
private async Task PeriodicPing() private async Task PeriodicPing()
@@ -107,7 +107,7 @@ public class Twitch : IDisposable
Guid.NewGuid() + "\",\"type\":\"LISTEN\"}"; Guid.NewGuid() + "\",\"type\":\"LISTEN\"}";
_logger.Debug("Sending the following JSON to Twitch"); _logger.Debug("Sending the following JSON to Twitch");
_logger.Debug(payload); _logger.Debug(payload);
_wsClient.Send(payload); _wsClient?.Send(payload);
} }
} }
@@ -131,13 +131,13 @@ public class Twitch : IDisposable
var packet = JsonSerializer.Deserialize<JsonElement>(message.Text); var packet = JsonSerializer.Deserialize<JsonElement>(message.Text);
if (packet.GetProperty("type").GetString() != "MESSAGE") if (packet.GetProperty("type").GetString() != "MESSAGE")
return; return;
var data = packet.GetProperty("data")!; var data = packet.GetProperty("data");
var topicString = data.GetProperty("topic")!.GetString()!; var topicString = data.GetProperty("topic").GetString()!;
if (!topicString.StartsWith("video-playback-by-id.")) if (!topicString.StartsWith("video-playback-by-id."))
return; return;
var topicParts = topicString.Split('.'); var topicParts = topicString.Split('.');
var channelId = int.Parse(topicParts[^1]); var channelId = int.Parse(topicParts[^1]);
var twitchMessage = JsonSerializer.Deserialize<JsonElement>(data.GetProperty("message").GetString()); var twitchMessage = JsonSerializer.Deserialize<JsonElement>(data.GetProperty("message").GetString()!);
if (twitchMessage.GetProperty("type").GetString() == "stream-up") if (twitchMessage.GetProperty("type").GetString() == "stream-up")
{ {
@@ -242,7 +242,7 @@ public class Twitch : IDisposable
public void Dispose() public void Dispose()
{ {
_wsClient.Dispose(); _wsClient?.Dispose();
_pingCts.Cancel(); _pingCts.Cancel();
_pingCts.Dispose(); _pingCts.Dispose();
_pingTask?.Dispose(); _pingTask?.Dispose();

View File

@@ -10,7 +10,7 @@ namespace KfChatDotNetBot.Services;
public class TwitchChat : IDisposable public class TwitchChat : IDisposable
{ {
private readonly Logger _logger = LogManager.GetCurrentClassLogger(); private readonly Logger _logger = LogManager.GetCurrentClassLogger();
private WebsocketClient _wsClient; private WebsocketClient? _wsClient;
private readonly Uri _wsUri = new("wss://irc-ws.chat.twitch.tv/"); private readonly Uri _wsUri = new("wss://irc-ws.chat.twitch.tv/");
private const int ReconnectTimeout = 600; private const int ReconnectTimeout = 600;
private readonly string? _proxy; private readonly string? _proxy;
@@ -19,15 +19,15 @@ public class TwitchChat : IDisposable
public delegate void MessageReceivedEventHandler(object sender, string nick, string target, string message); public delegate void MessageReceivedEventHandler(object sender, string nick, string target, string message);
public delegate void WsDisconnectionEventHandler(object sender, DisconnectionInfo e); public delegate void WsDisconnectionEventHandler(object sender, DisconnectionInfo e);
public event MessageReceivedEventHandler OnMessageReceived; public event MessageReceivedEventHandler? OnMessageReceived;
public event WsDisconnectionEventHandler OnWsDisconnection; public event WsDisconnectionEventHandler? OnWsDisconnection;
private readonly CancellationToken _cancellationToken = CancellationToken.None; private readonly CancellationToken _cancellationToken;
public TwitchChat(string channel, string? proxy = null, CancellationToken? cancellationToken = null) public TwitchChat(string channel, string? proxy = null, CancellationToken cancellationToken = default)
{ {
_proxy = proxy; _proxy = proxy;
if (cancellationToken != null) _cancellationToken = cancellationToken.Value; _cancellationToken = cancellationToken;
_channel = channel; _channel = channel;
var justinFan = new Random().Next(10000, 99999); var justinFan = new Random().Next(10000, 99999);
_nick = $"justinfan{justinFan}"; _nick = $"justinfan{justinFan}";
@@ -87,13 +87,13 @@ public class TwitchChat : IDisposable
_logger.Error($"Websocket connection dropped and reconnected. Reconnection type is {reconnectionInfo.Type}"); _logger.Error($"Websocket connection dropped and reconnected. Reconnection type is {reconnectionInfo.Type}");
_logger.Info("Sending registration info to Twitch IRC"); _logger.Info("Sending registration info to Twitch IRC");
// I've found if you use the message queue then things come out of order, hence using SendInstant // I've found if you use the message queue then things come out of order, hence using SendInstant
_wsClient.SendInstant("CAP REQ :twitch.tv/tags twitch.tv/commands").Wait(_cancellationToken); _wsClient?.SendInstant("CAP REQ :twitch.tv/tags twitch.tv/commands").Wait(_cancellationToken);
// Would be an oauth token if you were signed in, but this is just guest access // Would be an oauth token if you were signed in, but this is just guest access
_wsClient.SendInstant("PASS SCHMOOPIIE").Wait(_cancellationToken); _wsClient?.SendInstant("PASS SCHMOOPIIE").Wait(_cancellationToken);
// Guest users are just justinfan12345 where the 5 digits are random // Guest users are just justinfan12345 where the 5 digits are random
_wsClient.SendInstant($"NICK {_nick}").Wait(_cancellationToken); _wsClient?.SendInstant($"NICK {_nick}").Wait(_cancellationToken);
// I'm ashamed I've forgotten so much IRC protocol shit that I can't remember what the USER params mean :( // I'm ashamed I've forgotten so much IRC protocol shit that I can't remember what the USER params mean :(
_wsClient.SendInstant($"USER {_nick} 8 * :{_nick}").Wait(_cancellationToken); _wsClient?.SendInstant($"USER {_nick} 8 * :{_nick}").Wait(_cancellationToken);
} }
private void WsMessageReceived(ResponseMessage message) private void WsMessageReceived(ResponseMessage message)
@@ -147,7 +147,7 @@ public class TwitchChat : IDisposable
{ {
case "PING": case "PING":
_logger.Info("Received PING, sending PONG"); _logger.Info("Received PING, sending PONG");
_wsClient.Send("PONG"); _wsClient?.Send("PONG");
return; return;
case "JOIN": case "JOIN":
_logger.Debug("Received JOIN response"); _logger.Debug("Received JOIN response");
@@ -155,7 +155,7 @@ public class TwitchChat : IDisposable
// MOTD // MOTD
case "001": case "001":
_logger.Debug("Received MOTD. Sending JOIN"); _logger.Debug("Received MOTD. Sending JOIN");
_wsClient.Send($"JOIN {_channel}"); _wsClient?.Send($"JOIN {_channel}");
return; return;
default: default:
_logger.Debug($"Command {command} was not handled"); _logger.Debug($"Command {command} was not handled");
@@ -174,7 +174,7 @@ public class TwitchChat : IDisposable
public void Dispose() public void Dispose()
{ {
_wsClient.Dispose(); _wsClient?.Dispose();
GC.SuppressFinalize(this); GC.SuppressFinalize(this);
} }
} }

View File

@@ -10,22 +10,22 @@ namespace KfChatDotNetBot.Services;
public class Yeet : IDisposable public class Yeet : IDisposable
{ {
private Logger _logger = LogManager.GetCurrentClassLogger(); private Logger _logger = LogManager.GetCurrentClassLogger();
private WebsocketClient _wsClient; private WebsocketClient? _wsClient;
private Uri _wsUri = new("wss://api.yeet.com/room-service/socket/?EIO=4&transport=websocket"); private Uri _wsUri = new("wss://api.yeet.com/room-service/socket/?EIO=4&transport=websocket");
private int _reconnectTimeout = 30; private int _reconnectTimeout = 30;
private string? _proxy; private string? _proxy;
public delegate void OnYeetBetEventHandler(object sender, YeetCasinoBetModel data); public delegate void OnYeetBetEventHandler(object sender, YeetCasinoBetModel data);
public delegate void OnYeetWinEventHandler(object sender, YeetCasinoWinModel data); public delegate void OnYeetWinEventHandler(object sender, YeetCasinoWinModel data);
public delegate void OnWsDisconnectionEventHandler(object sender, DisconnectionInfo e); public delegate void OnWsDisconnectionEventHandler(object sender, DisconnectionInfo e);
public event OnYeetBetEventHandler OnYeetBet; public event OnYeetBetEventHandler? OnYeetBet;
public event OnYeetWinEventHandler OnYeetWin; public event OnYeetWinEventHandler? OnYeetWin;
public event OnWsDisconnectionEventHandler OnWsDisconnection; public event OnWsDisconnectionEventHandler? OnWsDisconnection;
private CancellationToken _cancellationToken = CancellationToken.None; private CancellationToken _cancellationToken;
public Yeet(string? proxy = null, CancellationToken? cancellationToken = null) public Yeet(string? proxy = null, CancellationToken cancellationToken = default)
{ {
_proxy = proxy; _proxy = proxy;
if (cancellationToken != null) _cancellationToken = cancellationToken.Value; _cancellationToken = cancellationToken;
_logger.Info("Yeet WebSocket client created"); _logger.Info("Yeet WebSocket client created");
} }
@@ -84,7 +84,7 @@ public class Yeet : IDisposable
if (reconnectionInfo.Type == ReconnectionType.Initial) if (reconnectionInfo.Type == ReconnectionType.Initial)
{ {
_logger.Info("Sending subscribe payload to Yeet"); _logger.Info("Sending subscribe payload to Yeet");
_wsClient.Send("40/public,"); _wsClient?.Send("40/public,");
} }
} }
@@ -104,7 +104,7 @@ public class Yeet : IDisposable
if (packetType == "2") if (packetType == "2")
{ {
_logger.Info("Received ping from Yeet, replying with pong"); _logger.Info("Received ping from Yeet, replying with pong");
_wsClient.Send("3"); _wsClient?.Send("3");
return; return;
} }
@@ -112,14 +112,15 @@ public class Yeet : IDisposable
{ {
var data = JsonSerializer.Deserialize<List<JsonElement>>(message.Text.Replace("42/public,", var data = JsonSerializer.Deserialize<List<JsonElement>>(message.Text.Replace("42/public,",
string.Empty)); string.Empty));
if (data == null) throw new Exception("Caught a null when deserializing the Yeet bet payload");
if (data[0].GetString() == "casino-bet") if (data[0].GetString() == "casino-bet")
{ {
OnYeetBet?.Invoke(this, data[1].Deserialize<YeetCasinoBetModel>()); OnYeetBet?.Invoke(this, data[1].Deserialize<YeetCasinoBetModel>()!);
return; return;
} }
if (data[0].GetString() == "casino-win") if (data[0].GetString() == "casino-win")
{ {
OnYeetWin?.Invoke(this, data[1].Deserialize<YeetCasinoWinModel>()); OnYeetWin?.Invoke(this, data[1].Deserialize<YeetCasinoWinModel>()!);
return; return;
} }
@@ -146,7 +147,7 @@ public class Yeet : IDisposable
public void Dispose() public void Dispose()
{ {
_wsClient.Dispose(); _wsClient?.Dispose();
GC.SuppressFinalize(this); GC.SuppressFinalize(this);
} }
} }

View File

@@ -15,6 +15,11 @@ public static class SettingsProvider
if (!bypassCache && cache.Contains(key)) if (!bypassCache && cache.Contains(key))
{ {
var cachedSetting = cache.Get(key) as SettingDbModel; var cachedSetting = cache.Get(key) as SettingDbModel;
if (cachedSetting == null)
{
logger.Error($"Caught a null in spite of the cache supposedly containing {key}");
throw new Exception("Cached Setting entry was null");
}
var value = cachedSetting.Value; var value = cachedSetting.Value;
if (cachedSetting.Value == "null") value = null; if (cachedSetting.Value == "null") value = null;
return new Setting(value, cachedSetting, true); return new Setting(value, cachedSetting, true);

View File

@@ -1,7 +1,6 @@
using System.Net; using System.Net;
using System.Net.WebSockets; using System.Net.WebSockets;
using System.Text.Json; using System.Text.Json;
using System.Xml;
using KfChatDotNetWsClient.Models; using KfChatDotNetWsClient.Models;
using KfChatDotNetWsClient.Models.Events; using KfChatDotNetWsClient.Models.Events;
using KfChatDotNetWsClient.Models.Json; using KfChatDotNetWsClient.Models.Json;
@@ -14,15 +13,15 @@ namespace KfChatDotNetWsClient;
public class ChatClient public class ChatClient
{ {
public event EventHandlers.OnMessagesEventHandler OnMessages; public event EventHandlers.OnMessagesEventHandler? OnMessages;
public event EventHandlers.OnUsersPartedEventHandler OnUsersParted; public event EventHandlers.OnUsersPartedEventHandler? OnUsersParted;
public event EventHandlers.OnUsersJoinedEventHandler OnUsersJoined; public event EventHandlers.OnUsersJoinedEventHandler? OnUsersJoined;
public event EventHandlers.OnWsReconnectEventHandler OnWsReconnect; public event EventHandlers.OnWsReconnectEventHandler? OnWsReconnect;
public event EventHandlers.OnDeleteMessagesEventHandler OnDeleteMessages; public event EventHandlers.OnDeleteMessagesEventHandler? OnDeleteMessages;
public event EventHandlers.OnWsDisconnectionEventHandler OnWsDisconnection; public event EventHandlers.OnWsDisconnectionEventHandler? OnWsDisconnection;
public event EventHandlers.OnFailedToJoinRoom OnFailedToJoinRoom; public event EventHandlers.OnFailedToJoinRoom? OnFailedToJoinRoom;
public event EventHandlers.OnUnknownCommand OnUnknownCommand; public event EventHandlers.OnUnknownCommand? OnUnknownCommand;
private WebsocketClient _wsClient; private WebsocketClient? _wsClient;
private readonly Logger _logger = LogManager.GetCurrentClassLogger(); private readonly Logger _logger = LogManager.GetCurrentClassLogger();
private ChatClientConfigModel _config; private ChatClientConfigModel _config;
public DateTime LastPacketReceived = DateTime.UtcNow; public DateTime LastPacketReceived = DateTime.UtcNow;
@@ -49,6 +48,7 @@ public class ChatClient
public void Disconnect() public void Disconnect()
{ {
if (_wsClient == null) throw new WebSocketNotInitializedException();
_wsClient.Stop(WebSocketCloseStatus.NormalClosure, "Closing websocket").Wait(); _wsClient.Stop(WebSocketCloseStatus.NormalClosure, "Closing websocket").Wait();
} }
@@ -57,11 +57,13 @@ public class ChatClient
// none for reconnect. // none for reconnect.
public async Task DisconnectAsync() public async Task DisconnectAsync()
{ {
if (_wsClient == null) throw new WebSocketNotInitializedException();
await _wsClient.Stop(WebSocketCloseStatus.NormalClosure, "Closing websocket"); await _wsClient.Stop(WebSocketCloseStatus.NormalClosure, "Closing websocket");
} }
public async Task Reconnect() public async Task Reconnect()
{ {
if (_wsClient == null) throw new WebSocketNotInitializedException();
await _wsClient.Reconnect(); await _wsClient.Reconnect();
} }
@@ -144,7 +146,7 @@ public class ChatClient
return; return;
} }
Dictionary<string, object> packetType = new Dictionary<string, object>(); Dictionary<string, object> packetType;
try try
{ {
packetType = JsonSerializer.Deserialize<Dictionary<string, object>>(message.Text)!; packetType = JsonSerializer.Deserialize<Dictionary<string, object>>(message.Text)!;
@@ -193,30 +195,35 @@ public class ChatClient
public void JoinRoom(int roomId) public void JoinRoom(int roomId)
{ {
_logger.Debug($"Joining {roomId}"); _logger.Debug($"Joining {roomId}");
if (_wsClient == null) throw new WebSocketNotInitializedException();
_wsClient.Send($"/join {roomId}"); _wsClient.Send($"/join {roomId}");
} }
public void SendMessage(string message) public void SendMessage(string message)
{ {
_logger.Debug($"Sending '{message}'"); _logger.Debug($"Sending '{message}'");
if (_wsClient == null) throw new WebSocketNotInitializedException();
_wsClient.Send(message); _wsClient.Send(message);
} }
public async Task SendMessageInstantAsync(string message) public async Task SendMessageInstantAsync(string message)
{ {
_logger.Debug($"Sending '{message}', bypassing the queue"); _logger.Debug($"Sending '{message}', bypassing the queue");
if (_wsClient == null) throw new WebSocketNotInitializedException();
await _wsClient.SendInstant(message); await _wsClient.SendInstant(message);
} }
public void DeleteMessage(int messageId) public void DeleteMessage(int messageId)
{ {
_logger.Debug($"Deleting {messageId}"); _logger.Debug($"Deleting {messageId}");
if (_wsClient == null) throw new WebSocketNotInitializedException();
_wsClient.Send($"/delete {messageId}"); _wsClient.Send($"/delete {messageId}");
} }
public async Task DeleteMessageAsync(int messageId) public async Task DeleteMessageAsync(int messageId)
{ {
_logger.Debug($"Deleting {messageId}"); _logger.Debug($"Deleting {messageId}");
if (_wsClient == null) throw new WebSocketNotInitializedException();
await _wsClient.SendInstant($"/delete {messageId}"); await _wsClient.SendInstant($"/delete {messageId}");
} }
@@ -224,6 +231,7 @@ public class ChatClient
{ {
var payload = JsonSerializer.Serialize(new EditMessageJsonModel {Id = messageId, Message = newMessage}); var payload = JsonSerializer.Serialize(new EditMessageJsonModel {Id = messageId, Message = newMessage});
_logger.Debug($"Editing {messageId} with '{newMessage}'"); _logger.Debug($"Editing {messageId} with '{newMessage}'");
if (_wsClient == null) throw new WebSocketNotInitializedException();
_wsClient.Send($"/edit {payload}"); _wsClient.Send($"/edit {payload}");
} }
@@ -231,21 +239,22 @@ public class ChatClient
{ {
var payload = JsonSerializer.Serialize(new EditMessageJsonModel {Id = messageId, Message = newMessage}); var payload = JsonSerializer.Serialize(new EditMessageJsonModel {Id = messageId, Message = newMessage});
_logger.Debug($"Editing {messageId} with '{newMessage}'"); _logger.Debug($"Editing {messageId} with '{newMessage}'");
if (_wsClient == null) throw new WebSocketNotInitializedException();
await _wsClient.SendInstant($"/edit {payload}"); await _wsClient.SendInstant($"/edit {payload}");
} }
private void WsDeleteMessagesReceived(ResponseMessage message) private void WsDeleteMessagesReceived(ResponseMessage message)
{ {
var data = JsonSerializer.Deserialize<DeleteMessagesJsonModel>(message.Text); var data = JsonSerializer.Deserialize<DeleteMessagesJsonModel>(message.Text!);
_logger.Debug($"Received delete packet for messages: {string.Join(',', data.MessageIdsToDelete)}"); _logger.Debug($"Received delete packet for messages: {string.Join(',', data!.MessageIdsToDelete)}");
OnDeleteMessages?.Invoke(this, data.MessageIdsToDelete); OnDeleteMessages?.Invoke(this, data.MessageIdsToDelete);
} }
private void WsChatMessagesReceived(ResponseMessage message) private void WsChatMessagesReceived(ResponseMessage message)
{ {
var data = JsonSerializer.Deserialize<MessagesJsonModel>(message.Text); var data = JsonSerializer.Deserialize<MessagesJsonModel>(message.Text!);
var messages = new List<MessageModel>(); var messages = new List<MessageModel>();
foreach (var chatMessage in data.Messages) foreach (var chatMessage in data!.Messages)
{ {
var model = new MessageModel var model = new MessageModel
{ {
@@ -260,10 +269,12 @@ public class ChatClient
Message = chatMessage.Message, Message = chatMessage.Message,
MessageId = chatMessage.MessageId, MessageId = chatMessage.MessageId,
MessageRaw = chatMessage.MessageRaw, MessageRaw = chatMessage.MessageRaw,
RoomId = chatMessage.RoomId RoomId = chatMessage.RoomId,
MessageRawHtmlDecoded = WebUtility.HtmlDecode(chatMessage.MessageRaw),
MessageDate = DateTimeOffset.FromUnixTimeSeconds(chatMessage.MessageDate)
}; };
if(chatMessage.MessageEditDate == 0) if (chatMessage.MessageEditDate == 0)
{ {
model.MessageEditDate = null; model.MessageEditDate = null;
} }
@@ -272,8 +283,6 @@ public class ChatClient
model.MessageEditDate = DateTimeOffset.FromUnixTimeSeconds(chatMessage.MessageEditDate); model.MessageEditDate = DateTimeOffset.FromUnixTimeSeconds(chatMessage.MessageEditDate);
} }
model.MessageDate = DateTimeOffset.FromUnixTimeSeconds(chatMessage.MessageDate);
messages.Add(model); messages.Add(model);
} }
_logger.Debug($"Received {messages.Count} chat messages"); _logger.Debug($"Received {messages.Count} chat messages");
@@ -286,9 +295,9 @@ public class ChatClient
private void WsChatUsersJoined(ResponseMessage message) private void WsChatUsersJoined(ResponseMessage message)
{ {
var data = JsonSerializer.Deserialize<UsersJsonModel>(message.Text); var data = JsonSerializer.Deserialize<UsersJsonModel>(message.Text!);
var users = new List<UserModel>(); var users = new List<UserModel>();
foreach (var user in data.Users.Keys) foreach (var user in data!.Users.Keys)
{ {
users.Add(new UserModel users.Add(new UserModel
{ {
@@ -306,9 +315,11 @@ public class ChatClient
private void WsChatUsersParted(ResponseMessage message) private void WsChatUsersParted(ResponseMessage message)
{ {
// {"user":{"1337":false}} // {"user":{"1337":false}}
var data = JsonSerializer.Deserialize<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(); var usersParted = data!["user"].Select(user => int.Parse(user.Key)).ToList();
_logger.Debug($"Following users have parted: {string.Join(',', usersParted)}"); _logger.Debug($"Following users have parted: {string.Join(',', usersParted)}");
OnUsersParted?.Invoke(this, usersParted); OnUsersParted?.Invoke(this, usersParted);
} }
} }
public class WebSocketNotInitializedException : Exception;

View File

@@ -5,7 +5,7 @@ public class ChatClientConfigModel
// XF session token. Sent as a cookie to auth the user // XF session token. Sent as a cookie to auth the user
public string? XfSessionToken { get; set; } public string? XfSessionToken { get; set; }
// Currently wss://kiwifarms.net/chat.ws // Currently wss://kiwifarms.net/chat.ws
public Uri WsUri { get; set; } public required Uri WsUri { get; set; }
public int ReconnectTimeout { get; set; } = 30; public int ReconnectTimeout { get; set; } = 30;
public string CookieDomain { get; set; } = "kiwifarms.net"; public string CookieDomain { get; set; } = "kiwifarms.net";
public string? Proxy { get; set; } public string? Proxy { get; set; }

View File

@@ -2,17 +2,21 @@ namespace KfChatDotNetWsClient.Models.Events;
public class MessageModel public class MessageModel
{ {
public UserModel Author { get; set; } public required UserModel Author { get; set; }
/// <summary> /// <summary>
/// HTML formatted message /// HTML formatted message
/// </summary> /// </summary>
public string Message { get; set; } public required string Message { get; set; }
public int MessageId { get; set; } public required int MessageId { get; set; }
public DateTimeOffset? MessageEditDate { get; set; } public DateTimeOffset? MessageEditDate { get; set; }
public DateTimeOffset MessageDate { get; set; } public required DateTimeOffset MessageDate { get; set; }
/// <summary> /// <summary>
/// Unformatted message with original BB code /// Unformatted message with original BB code but retaining HTML encoding
/// </summary> /// </summary>
public string MessageRaw { get; set; } public required string MessageRaw { get; set; }
public int RoomId { get; set; } public required int RoomId { get; set; }
/// <summary>
/// Unformatted message with original BB code and HTML entities decoded
/// </summary>
public required string MessageRawHtmlDecoded { get; set; }
} }

View File

@@ -6,8 +6,8 @@ public class UserModel
/// <summary> /// <summary>
/// Forum display name. Note that it'll be HTML encoded /// Forum display name. Note that it'll be HTML encoded
/// </summary> /// </summary>
public string Username { get; set; } public required string Username { get; set; }
public Uri AvatarUrl { get; set; } public Uri? AvatarUrl { get; set; }
// Unset if it's related to a chat message // Unset if it's related to a chat message
public DateTimeOffset? LastActivity { get; set; } public DateTimeOffset? LastActivity { get; set; }
} }

View File

@@ -5,5 +5,5 @@ namespace KfChatDotNetWsClient.Models.Json;
public class DeleteMessagesJsonModel public class DeleteMessagesJsonModel
{ {
[JsonPropertyName("delete")] [JsonPropertyName("delete")]
public List<int> MessageIdsToDelete { get; set; } public required List<int> MessageIdsToDelete { get; set; }
} }

View File

@@ -5,8 +5,8 @@ namespace KfChatDotNetWsClient.Models.Json;
public class EditMessageJsonModel public class EditMessageJsonModel
{ {
[JsonPropertyName("id")] [JsonPropertyName("id")]
public int Id { get; set; } public required int Id { get; set; }
[JsonPropertyName("message")] [JsonPropertyName("message")]
public string Message { get; set; } public required string Message { get; set; }
} }

View File

@@ -30,17 +30,17 @@ public class MessagesJsonModel
[JsonPropertyName("id")] [JsonPropertyName("id")]
public int Id { get; set; } public int Id { get; set; }
[JsonPropertyName("username")] [JsonPropertyName("username")]
public string Username { get; set; } public required string Username { get; set; }
[JsonPropertyName("avatar_url")] [JsonPropertyName("avatar_url")]
public Uri AvatarUrl { get; set; } public Uri? AvatarUrl { get; set; }
} }
public class MessageModel public class MessageModel
{ {
[JsonPropertyName("author")] [JsonPropertyName("author")]
public AuthorModel Author { get; set; } public required AuthorModel Author { get; set; }
[JsonPropertyName("message")] [JsonPropertyName("message")]
public string Message { get; set; } public required string Message { get; set; }
[JsonPropertyName("message_id")] [JsonPropertyName("message_id")]
public int MessageId { get; set; } public int MessageId { get; set; }
[JsonPropertyName("message_edit_date")] [JsonPropertyName("message_edit_date")]
@@ -48,11 +48,10 @@ public class MessagesJsonModel
[JsonPropertyName("message_date")] [JsonPropertyName("message_date")]
public int MessageDate { get; set; } public int MessageDate { get; set; }
[JsonPropertyName("message_raw")] [JsonPropertyName("message_raw")]
public string MessageRaw { get; set; } public required string MessageRaw { get; set; }
[JsonPropertyName("room_id")] [JsonPropertyName("room_id")]
public int RoomId { get; set; } public int RoomId { get; set; }
} }
[JsonPropertyName("messages")] [JsonPropertyName("messages")] public List<MessageModel> Messages { get; set; } = [];
public List<MessageModel> Messages { get; set; }
} }

View File

@@ -19,13 +19,13 @@ public class UsersJsonModel
[JsonPropertyName("id")] [JsonPropertyName("id")]
public int Id { get; set; } public int Id { get; set; }
[JsonPropertyName("username")] [JsonPropertyName("username")]
public string Username { get; set; } public required string Username { get; set; }
[JsonPropertyName("avatar_url")] [JsonPropertyName("avatar_url")]
public Uri AvatarUrl { get; set; } public Uri? AvatarUrl { get; set; }
[JsonPropertyName("last_activity")] [JsonPropertyName("last_activity")]
public int LastActivity { get; set; } public int LastActivity { get; set; }
} }
[JsonPropertyName("users")] [JsonPropertyName("users")]
public Dictionary<string, UserModel> Users { get; set; } public Dictionary<string, UserModel> Users { get; set; } = new();
} }