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 channelId = Convert.ToInt32(arguments["channel_id"].Value);
channels ??= [];
if (channels.Any(channel => channel.ChannelId == channelId))
{
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)
{
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 channel = channels.FirstOrDefault(ch => ch.ChannelId == channelId);
if (channel == null)
@@ -144,6 +146,11 @@ public class ReconnectKickCommand : ICommand
public TimeSpan Timeout => TimeSpan.FromSeconds(10);
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();
await botInstance.SendChatMessageAsync("Disconnected from Kick. Client should reconnect shortly.", true);
}
@@ -365,6 +372,11 @@ public class SetAlmanacIntervalCommand : ICommand
return;
}
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();
botInstance.BotServices.AlmanacShill.StartShillTask();
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,
CancellationToken ctx)
{
if (botInstance.BotServices.AlmanacShill == null)
{
await botInstance.SendChatMessageAsync("AlmanacShill is null", true);
return;
}
if (!botInstance.BotServices.AlmanacShill.IsShillTaskRunning())
{
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,
CancellationToken ctx)
{
if (botInstance.BotServices.AlmanacShill == null)
{
await botInstance.SendChatMessageAsync("AlmanacShill is null", true);
return;
}
if (botInstance.BotServices.AlmanacShill.IsShillTaskRunning())
{
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;
// 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 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");
}

View File

@@ -64,8 +64,14 @@ public class GetVersionCommand : ICommand
public TimeSpan Timeout => TimeSpan.FromSeconds(10);
public async Task RunCommand(ChatBot botInstance, MessageModel message, UserDbModel user, GroupCollection arguments, CancellationToken ctx)
{
var version = Assembly.GetEntryAssembly()
.GetCustomAttribute<AssemblyInformationalVersionAttribute>().InformationalVersion;
var version = Assembly.GetEntryAssembly()?
.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);
}
}

View File

@@ -5,7 +5,7 @@ public class ChipsggBetModel
public DateTimeOffset Created { get; set; }
// Can actually get the duration of a game from this
public DateTimeOffset Updated { get; set; }
public string UserId { get; set; }
public string? UserId { get; set; }
// Sometimes null for no discernible reason
public string? Username { get; set; }
// 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 string? Currency { get; set; }
public float CurrencyPrice { get; set; }
public string BetId { get; set; }
public string? BetId { get; set; }
}
public class ChipsggCurrencyModel

View File

@@ -12,22 +12,22 @@ namespace KfChatDotNetBot.Services;
public class BetBolt : IDisposable
{
private Logger _logger = LogManager.GetCurrentClassLogger();
private WebsocketClient _wsClient;
private WebsocketClient? _wsClient;
private Uri _wsUri = new("wss://betbolt.com/api/ws");
// Pings every 5 seconds so 15 seconds should be reasonable
private int _reconnectTimeout = 15;
private string? _proxy;
public delegate void OnBetBoltBetEventHandler(object sender, BetBoltBetModel bet);
public delegate void OnWsDisconnectionEventHandler(object sender, DisconnectionInfo e);
public event OnBetBoltBetEventHandler OnBetBoltBet;
public event OnWsDisconnectionEventHandler OnWsDisconnection;
private CancellationToken _cancellationToken = CancellationToken.None;
private IEnumerable<string>? _cookies = null;
private string? _userAgent = null;
public BetBolt(string? proxy = null, CancellationToken? cancellationToken = null)
public event OnBetBoltBetEventHandler? OnBetBoltBet;
public event OnWsDisconnectionEventHandler? OnWsDisconnection;
private CancellationToken _cancellationToken;
private IEnumerable<string>? _cookies;
private string? _userAgent;
public BetBolt(string? proxy = null, CancellationToken cancellationToken = default)
{
_proxy = proxy;
if (cancellationToken != null) _cancellationToken = cancellationToken.Value;
_cancellationToken = cancellationToken;
_logger.Info("Clash.gg WebSocket client created");
}
public async Task StartWsClient()
@@ -86,7 +86,7 @@ public class BetBolt : IDisposable
if (reconnectionInfo.Type == ReconnectionType.Initial)
{
_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 Logger _logger = LogManager.GetCurrentClassLogger();
internal KickWsClient.KickWsClient KickClient;
private Twitch _twitch;
private Shuffle _shuffle;
private DiscordService _discord;
private TwitchChat _twitchChat;
private Jackpot _jackpot;
private Howlgg _howlgg;
private RainbetWs _rainbet;
private Chipsgg _chipsgg;
private Clashgg _clashgg;
private BetBolt _betBolt;
private Yeet _yeet;
public AlmanacShill AlmanacShill;
internal KickWsClient.KickWsClient? KickClient;
private Twitch? _twitch;
private Shuffle? _shuffle;
private DiscordService? _discord;
private TwitchChat? _twitchChat;
private Jackpot? _jackpot;
private Howlgg? _howlgg;
private RainbetWs? _rainbet;
private Chipsgg? _chipsgg;
private Clashgg? _clashgg;
private BetBolt? _betBolt;
private Yeet? _yeet;
public AlmanacShill? AlmanacShill;
private Task? _websocketWatchdog;
private Task? _howlggGetUserTimer;
private string _bmjTwitchUsername;
private bool _twitchDisabled = false;
private string? _lastDiscordStatus;
internal bool IsBmjLive = false;
private bool _isBmjLiveSynced = false;
internal bool IsChrisDjLive = false;
private string? _bmjTwitchUsername;
private bool _twitchDisabled;
internal bool IsBmjLive;
private bool _isBmjLiveSynced;
internal bool IsChrisDjLive;
private Dictionary<string, SeenYeetBet> _yeetBets = new();
// lol
@@ -142,7 +141,7 @@ public class BotServices
_logger.Debug("Chips.gg is disabled");
return;
}
_chipsgg = new Chipsgg(settings[BuiltIn.Keys.Proxy].Value, _cancellationToken);
_chipsgg = new Chipsgg(settings[BuiltIn.Keys.Proxy].Value);
_chipsgg.OnChipsggRecentBet += OnChipsggRecentBet;
await _chipsgg.StartWsClient();
_logger.Info("Built Chips.gg Websocket connection");
@@ -302,7 +301,7 @@ public class BotServices
]);
try
{
if (!_shuffle.IsConnected())
if (_shuffle != null && !_shuffle.IsConnected())
{
_logger.Error("Shuffle died, recreating it");
_shuffle.Dispose();
@@ -310,7 +309,7 @@ public class BotServices
await BuildShuffle();
}
if (!_discord.IsConnected())
if (_discord != null && !_discord.IsConnected())
{
_logger.Error("Discord died, recreating it");
_discord.Dispose();
@@ -318,7 +317,7 @@ public class BotServices
await BuildDiscord();
}
if (!_twitchDisabled && !_twitch.IsConnected())
if (!_twitchDisabled && _twitch != null && !_twitch.IsConnected())
{
_logger.Error("Twitch died, recreating it");
_twitch.Dispose();
@@ -326,7 +325,7 @@ public class BotServices
await BuildTwitch();
}
if (!_twitchChat.IsConnected())
if (_twitchChat != null && !_twitchChat.IsConnected())
{
_logger.Error("Twitch chat died, recreating it");
_twitchChat.Dispose();
@@ -334,7 +333,7 @@ public class BotServices
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");
_howlgg.Dispose();
@@ -342,7 +341,7 @@ public class BotServices
await BuildHowlgg();
}
if (!_jackpot.IsConnected())
if (_jackpot != null && !_jackpot.IsConnected())
{
_logger.Error("Jackpot died, recreating it");
_jackpot.Dispose();
@@ -350,7 +349,7 @@ public class BotServices
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");
_chipsgg.Dispose();
@@ -358,7 +357,7 @@ public class BotServices
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");
KickClient.Dispose();
@@ -366,7 +365,7 @@ public class BotServices
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");
_clashgg.Dispose();
@@ -374,7 +373,7 @@ public class BotServices
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");
_betBolt.Dispose();
@@ -382,7 +381,7 @@ public class BotServices
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");
_yeet.Dispose();
@@ -390,7 +389,7 @@ public class BotServices
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");
_rainbet.Dispose();
@@ -549,7 +548,6 @@ public class BotServices
}
_logger.Info("ALERT BMJ IS BETTING (on Yeet)");
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;
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});
@@ -809,7 +807,7 @@ public class BotServices
BuiltIn.Keys.KiwiFarmsGreenColor, BuiltIn.Keys.KiwiFarmsRedColor
]).Result;
_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;
}
@@ -817,9 +815,9 @@ public class BotServices
using var db = new ApplicationDbContext();
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,
Currency = bet.Currency!, CurrencyPrice = bet.CurrencyPrice, BetId = bet.BetId
Currency = bet.Currency!, CurrencyPrice = bet.CurrencyPrice, BetId = bet.BetId ?? "0"
});
db.SaveChanges();
if (CheckBmjIsLive(settings[BuiltIn.Keys.TwitchBossmanJackUsername].Value ?? "usernamenotset").Result) return;
@@ -840,7 +838,7 @@ public class BotServices
if (kickChannels == null) return;
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
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);
_isBmjLiveSynced = true;
}

View File

@@ -10,22 +10,20 @@ namespace KfChatDotNetBot.Services;
public class Chipsgg : IDisposable
{
private Logger _logger = LogManager.GetCurrentClassLogger();
private WebsocketClient _wsClient;
private WebsocketClient? _wsClient;
private Uri _wsUri = new("wss://api.chips.gg/prod/socket");
// Chips doesn't have a heartbeat packet
private int _reconnectTimeout = 30;
private string? _proxy;
public delegate void OnChipsggRecentBetEventHandler(object sender, ChipsggBetModel bet);
public delegate void OnWsDisconnectionEventHandler(object sender, DisconnectionInfo e);
public event OnChipsggRecentBetEventHandler OnChipsggRecentBet;
private CancellationToken _cancellationToken = CancellationToken.None;
public event OnChipsggRecentBetEventHandler? OnChipsggRecentBet;
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;
if (cancellationToken != null) _cancellationToken = cancellationToken.Value;
_logger.Info("Chipsgg WebSocket client created");
}
@@ -83,7 +81,7 @@ public class Chipsgg : IDisposable
if (reconnectionInfo.Type == ReconnectionType.Initial)
{
_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();
_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;
return;
}
@@ -130,7 +128,7 @@ public class Chipsgg : IDisposable
{
_logger.Info("Chips.gg responded to our auth with: " + firstElement[2].GetString());
_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;
}
@@ -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");
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");
var currencyData = dataElement[1].Deserialize<Dictionary<string, JsonElement>>();
@@ -172,7 +170,8 @@ public class Chipsgg : IDisposable
{
_logger.Info("Ignoring already defined currency");
continue;
};
}
float? price = null;
// Where a price is not set, the element is simply missing
if (currencies[currency].TryGetProperty("price", out var priceElement))
@@ -206,7 +205,7 @@ public class Chipsgg : IDisposable
{
_logger.Debug("Ignoring packet as it contains koth");
return;
};
}
var currency = innerDataPath[1];
if (_currencies.TryGetValue(currency, out var updatedCurrency))
{
@@ -227,7 +226,7 @@ public class Chipsgg : IDisposable
{
_logger.Info("Ignoring replay of recent bets");
return;
};
}
}
// Currency data may not be known until after so hold it here til we're done parsing
var amount = string.Empty;
@@ -323,7 +322,7 @@ public class Chipsgg : IDisposable
{
_logger.Debug("Currency or GameTitle was null, ignoring");
return;
};
}
if (!_currencies.TryGetValue(bet.Currency, out var currencyData))
{
throw new Exception($"Unknown currency {bet.Currency}");
@@ -352,7 +351,7 @@ public class Chipsgg : IDisposable
public void Dispose()
{
_wsClient.Dispose();
_wsClient?.Dispose();
GC.SuppressFinalize(this);
}
}

View File

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

View File

@@ -11,7 +11,7 @@ namespace KfChatDotNetBot.Services;
public class DiscordService : IDisposable
{
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");
// Not sure what a good value for this would be
private const int ReconnectTimeout = 60;
@@ -29,24 +29,24 @@ public class DiscordService : IDisposable
public delegate void ConversationSummaryUpdateEventHandler(object sender,
DiscordConversationSummaryUpdateModel summary, string guildId);
public event MessageReceivedEventHandler OnMessageReceived;
public event PresenceUpdateEventHandler OnPresenceUpdated;
public event WsDisconnectionEventHandler OnWsDisconnection;
public event InvalidCredentialsEventHandler OnInvalidCredentials;
public event ChannelCreatedEventHandler OnChannelCreated;
public event ChannelDeletedEventHandler OnChannelDeleted;
public event ConversationSummaryUpdateEventHandler OnConversationSummaryUpdate;
public event MessageReceivedEventHandler? OnMessageReceived;
public event PresenceUpdateEventHandler? OnPresenceUpdated;
public event WsDisconnectionEventHandler? OnWsDisconnection;
public event InvalidCredentialsEventHandler? OnInvalidCredentials;
public event ChannelCreatedEventHandler? OnChannelCreated;
public event ChannelDeletedEventHandler? OnChannelDeleted;
public event ConversationSummaryUpdateEventHandler? OnConversationSummaryUpdate;
private readonly CancellationToken _cancellationToken = CancellationToken.None;
private readonly CancellationToken _cancellationToken;
private readonly CancellationTokenSource _pingCts = new();
private Task? _heartbeatTask;
// 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);
public DiscordService(string authorization, string? proxy = null, CancellationToken? cancellationToken = null)
public DiscordService(string authorization, string? proxy = null, CancellationToken cancellationToken = default)
{
_proxy = proxy;
if (cancellationToken != null) _cancellationToken = cancellationToken.Value;
_cancellationToken = cancellationToken;
_authorization = authorization;
_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\"," +
"\"since\":0,\"activities\":[],\"afk\":false},\"compress\":false,\"client_state\":{\"guild_versions\":{}}}}";
_logger.Debug(initPayload);
_wsClient.SendInstant(initPayload).Wait(_cancellationToken);
_wsClient?.SendInstant(initPayload).Wait(_cancellationToken);
_heartbeatInterval =
TimeSpan.FromMilliseconds(packet.Data.GetProperty("heartbeat_interval").GetInt32());
if (_heartbeatTask != null) return;
@@ -235,7 +235,7 @@ public class DiscordService : IDisposable
public void Dispose()
{
_logger.Info("Disposing of the Discord service");
_wsClient.Dispose();
_wsClient?.Dispose();
_pingCts.Cancel();
_pingCts.Dispose();
_heartbeatTask?.Dispose();

View File

@@ -10,25 +10,25 @@ namespace KfChatDotNetBot.Services;
public class Howlgg : IDisposable
{
private Logger _logger = LogManager.GetCurrentClassLogger();
private WebsocketClient _wsClient;
private WebsocketClient? _wsClient;
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
private int _reconnectTimeout = 30;
private string? _proxy;
public delegate void OnHowlggBetHistoryResponse(object sender, HowlggBetHistoryResponseModel data);
public delegate void OnWsDisconnectionEventHandler(object sender, DisconnectionInfo e);
public event OnHowlggBetHistoryResponse OnHowlggBetHistory;
public event OnWsDisconnectionEventHandler OnWsDisconnection;
private CancellationToken _cancellationToken = CancellationToken.None;
public event OnHowlggBetHistoryResponse? OnHowlggBetHistory;
public event OnWsDisconnectionEventHandler? OnWsDisconnection;
private CancellationToken _cancellationToken;
private CancellationTokenSource _pingCts = new();
private Task? _heartbeatTask;
// 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);
public Howlgg(string? proxy = null, CancellationToken? cancellationToken = null)
public Howlgg(string? proxy = null, CancellationToken cancellationToken = default)
{
_proxy = proxy;
if (cancellationToken != null) _cancellationToken = cancellationToken.Value;
_cancellationToken = cancellationToken;
_logger.Info("Howlgg WebSocket client created");
}
@@ -77,7 +77,7 @@ public class Howlgg : IDisposable
{
var packet = "42/main,0[\"getUserInfo\",{\"userOrSteamId\":\"" + userId + "\",\"interval\":\"lifetime\"}]";
_logger.Debug($"Sending packet: {packet}");
_wsClient.Send(packet);
_wsClient?.Send(packet);
}
private async Task HeartbeatTimer()
@@ -141,7 +141,7 @@ public class Howlgg : IDisposable
if (message.Text == "40")
{
_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
return;
}
@@ -152,7 +152,6 @@ public class Howlgg : IDisposable
var jsonPayload = message.Text.Replace("43/main,0[null,", string.Empty).TrimEnd(']');
var data = JsonSerializer.Deserialize<HowlggBetHistoryResponseModel>(jsonPayload);
if (data != null) OnHowlggBetHistory?.Invoke(this, data);
return;
}
}
catch (Exception e)
@@ -167,7 +166,7 @@ public class Howlgg : IDisposable
public void Dispose()
{
_wsClient.Dispose();
_wsClient?.Dispose();
_pingCts.Cancel();
_pingCts.Dispose();
_heartbeatTask?.Dispose();

View File

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

View File

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

View File

@@ -11,8 +11,8 @@ public class Rainbet : IDisposable
{
private Logger _logger = LogManager.GetCurrentClassLogger();
public delegate void OnRainbetBetEventHandler(object sender, List<RainbetBetHistoryModel> bets);
public event OnRainbetBetEventHandler OnRainbetBet;
private CancellationToken _cancellationToken = CancellationToken.None;
public event OnRainbetBetEventHandler? OnRainbetBet;
private CancellationToken _cancellationToken;
private CancellationTokenSource _gameHistoryCts = new();
private Task? _gameHistoryTask;
private TimeSpan _gameHistoryInterval = TimeSpan.FromSeconds(60);
@@ -20,9 +20,9 @@ public class Rainbet : IDisposable
private string? _userAgent = null;
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");
}
@@ -118,6 +118,7 @@ public class Rainbet : IDisposable
throw new Exception("Cloudflare challenged");
}
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;
}

View File

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

View File

@@ -11,22 +11,22 @@ namespace KfChatDotNetBot.Services;
public class Shuffle : IDisposable
{
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 int _reconnectTimeout = 60;
private string? _proxy;
public delegate void OnLatestBetUpdatedEventHandler(object sender, ShuffleLatestBetModel bet);
public delegate void OnWsDisconnectionEventHandler(object sender, DisconnectionInfo e);
public event OnLatestBetUpdatedEventHandler OnLatestBetUpdated;
public event OnWsDisconnectionEventHandler OnWsDisconnection;
private CancellationToken _cancellationToken = CancellationToken.None;
public event OnLatestBetUpdatedEventHandler? OnLatestBetUpdated;
public event OnWsDisconnectionEventHandler? OnWsDisconnection;
private CancellationToken _cancellationToken;
private CancellationTokenSource _pingCts = new();
private Task _pingTask;
public Shuffle(string? proxy = null, CancellationToken? cancellationToken = null)
public Shuffle(string? proxy = null, CancellationToken cancellationToken = default)
{
_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
_pingTask = PeriodicPing();
}
@@ -103,7 +103,7 @@ public class Shuffle : IDisposable
var initPayload =
"{\"type\":\"connection_init\",\"payload\":{\"x-correlation-id\":\"pdvlnd9tej-di27abvq19-1.30.2-1i0nef1m7-g::anon\",\"authorization\":\"\"}}";
_logger.Debug(initPayload);
_wsClient.Send(initPayload);
_wsClient?.Send(initPayload);
}
private void WsMessageReceived(ResponseMessage message)
@@ -128,7 +128,7 @@ public class Shuffle : IDisposable
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}\"}}";
_logger.Debug(payload);
_wsClient.SendInstant(payload).Wait(_cancellationToken);
_wsClient?.SendInstant(payload).Wait(_cancellationToken);
return;
}
@@ -206,7 +206,7 @@ public class Shuffle : IDisposable
public void Dispose()
{
_wsClient.Dispose();
_wsClient?.Dispose();
// Rare bug but has happened at least once
try
{

View File

@@ -12,15 +12,17 @@ public class ThreeXplPocketWatch
private Logger _logger = LogManager.GetCurrentClassLogger();
private string _3xplToken = "3A0_t3st3xplor3rpub11cb3t4efcd21748a5e";
private string? _proxy;
private CancellationToken _cancellationToken = CancellationToken.None;
private CancellationToken _cancellationToken;
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");
_proxy = proxy;
if (cancellationToken != null) _cancellationToken = cancellationToken.Value;
_cancellationToken = cancellationToken;
}
private async Task CheckAddress(PocketWatchAddressDbModel addy)

View File

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

View File

@@ -10,7 +10,7 @@ namespace KfChatDotNetBot.Services;
public class TwitchChat : IDisposable
{
private readonly Logger _logger = LogManager.GetCurrentClassLogger();
private WebsocketClient _wsClient;
private WebsocketClient? _wsClient;
private readonly Uri _wsUri = new("wss://irc-ws.chat.twitch.tv/");
private const int ReconnectTimeout = 600;
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 WsDisconnectionEventHandler(object sender, DisconnectionInfo e);
public event MessageReceivedEventHandler OnMessageReceived;
public event WsDisconnectionEventHandler OnWsDisconnection;
public event MessageReceivedEventHandler? OnMessageReceived;
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;
if (cancellationToken != null) _cancellationToken = cancellationToken.Value;
_cancellationToken = cancellationToken;
_channel = channel;
var justinFan = new Random().Next(10000, 99999);
_nick = $"justinfan{justinFan}";
@@ -87,13 +87,13 @@ public class TwitchChat : IDisposable
_logger.Error($"Websocket connection dropped and reconnected. Reconnection type is {reconnectionInfo.Type}");
_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
_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
_wsClient.SendInstant("PASS SCHMOOPIIE").Wait(_cancellationToken);
_wsClient?.SendInstant("PASS SCHMOOPIIE").Wait(_cancellationToken);
// 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 :(
_wsClient.SendInstant($"USER {_nick} 8 * :{_nick}").Wait(_cancellationToken);
_wsClient?.SendInstant($"USER {_nick} 8 * :{_nick}").Wait(_cancellationToken);
}
private void WsMessageReceived(ResponseMessage message)
@@ -147,7 +147,7 @@ public class TwitchChat : IDisposable
{
case "PING":
_logger.Info("Received PING, sending PONG");
_wsClient.Send("PONG");
_wsClient?.Send("PONG");
return;
case "JOIN":
_logger.Debug("Received JOIN response");
@@ -155,7 +155,7 @@ public class TwitchChat : IDisposable
// MOTD
case "001":
_logger.Debug("Received MOTD. Sending JOIN");
_wsClient.Send($"JOIN {_channel}");
_wsClient?.Send($"JOIN {_channel}");
return;
default:
_logger.Debug($"Command {command} was not handled");
@@ -174,7 +174,7 @@ public class TwitchChat : IDisposable
public void Dispose()
{
_wsClient.Dispose();
_wsClient?.Dispose();
GC.SuppressFinalize(this);
}
}

View File

@@ -10,22 +10,22 @@ namespace KfChatDotNetBot.Services;
public class Yeet : IDisposable
{
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 int _reconnectTimeout = 30;
private string? _proxy;
public delegate void OnYeetBetEventHandler(object sender, YeetCasinoBetModel data);
public delegate void OnYeetWinEventHandler(object sender, YeetCasinoWinModel data);
public delegate void OnWsDisconnectionEventHandler(object sender, DisconnectionInfo e);
public event OnYeetBetEventHandler OnYeetBet;
public event OnYeetWinEventHandler OnYeetWin;
public event OnWsDisconnectionEventHandler OnWsDisconnection;
private CancellationToken _cancellationToken = CancellationToken.None;
public event OnYeetBetEventHandler? OnYeetBet;
public event OnYeetWinEventHandler? OnYeetWin;
public event OnWsDisconnectionEventHandler? OnWsDisconnection;
private CancellationToken _cancellationToken;
public Yeet(string? proxy = null, CancellationToken? cancellationToken = null)
public Yeet(string? proxy = null, CancellationToken cancellationToken = default)
{
_proxy = proxy;
if (cancellationToken != null) _cancellationToken = cancellationToken.Value;
_cancellationToken = cancellationToken;
_logger.Info("Yeet WebSocket client created");
}
@@ -84,7 +84,7 @@ public class Yeet : IDisposable
if (reconnectionInfo.Type == ReconnectionType.Initial)
{
_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")
{
_logger.Info("Received ping from Yeet, replying with pong");
_wsClient.Send("3");
_wsClient?.Send("3");
return;
}
@@ -112,14 +112,15 @@ public class Yeet : IDisposable
{
var data = JsonSerializer.Deserialize<List<JsonElement>>(message.Text.Replace("42/public,",
string.Empty));
if (data == null) throw new Exception("Caught a null when deserializing the Yeet bet payload");
if (data[0].GetString() == "casino-bet")
{
OnYeetBet?.Invoke(this, data[1].Deserialize<YeetCasinoBetModel>());
OnYeetBet?.Invoke(this, data[1].Deserialize<YeetCasinoBetModel>()!);
return;
}
if (data[0].GetString() == "casino-win")
{
OnYeetWin?.Invoke(this, data[1].Deserialize<YeetCasinoWinModel>());
OnYeetWin?.Invoke(this, data[1].Deserialize<YeetCasinoWinModel>()!);
return;
}
@@ -146,7 +147,7 @@ public class Yeet : IDisposable
public void Dispose()
{
_wsClient.Dispose();
_wsClient?.Dispose();
GC.SuppressFinalize(this);
}
}

View File

@@ -15,6 +15,11 @@ public static class SettingsProvider
if (!bypassCache && cache.Contains(key))
{
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;
if (cachedSetting.Value == "null") value = null;
return new Setting(value, cachedSetting, true);

View File

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

View File

@@ -2,17 +2,21 @@ namespace KfChatDotNetWsClient.Models.Events;
public class MessageModel
{
public UserModel Author { get; set; }
public required UserModel Author { get; set; }
/// <summary>
/// HTML formatted message
/// </summary>
public string Message { get; set; }
public int MessageId { get; set; }
public required string Message { get; set; }
public required int MessageId { get; set; }
public DateTimeOffset? MessageEditDate { get; set; }
public DateTimeOffset MessageDate { get; set; }
public required DateTimeOffset MessageDate { get; set; }
/// <summary>
/// Unformatted message with original BB code
/// Unformatted message with original BB code but retaining HTML encoding
/// </summary>
public string MessageRaw { get; set; }
public int RoomId { get; set; }
public required string MessageRaw { 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>
/// Forum display name. Note that it'll be HTML encoded
/// </summary>
public string Username { get; set; }
public Uri AvatarUrl { get; set; }
public required string Username { get; set; }
public Uri? AvatarUrl { get; set; }
// Unset if it's related to a chat message
public DateTimeOffset? LastActivity { get; set; }
}

View File

@@ -5,5 +5,5 @@ namespace KfChatDotNetWsClient.Models.Json;
public class DeleteMessagesJsonModel
{
[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
{
[JsonPropertyName("id")]
public int Id { get; set; }
public required int Id { get; set; }
[JsonPropertyName("message")]
public string Message { get; set; }
public required string Message { get; set; }
}

View File

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

View File

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