From 37ab1138ef831667d2d034e6e9870e81d69d6839 Mon Sep 17 00:00:00 2001 From: barelyprofessional <150058423+barelyprofessional@users.noreply.github.com> Date: Thu, 13 Jun 2024 20:07:41 +0800 Subject: [PATCH] Updated session token retrieval system to use Puppeteer to automatically log in and retrieve a token --- .../.idea.KfChatDotNet/.idea/sqldialects.xml | 6 -- KfChatDotNetKickBot/Helpers.cs | 36 -------- .../KfChatDotNetKickBot.csproj | 9 +- KfChatDotNetKickBot/KickBot.cs | 54 +++++++----- KfChatDotNetKickBot/Models.cs | 7 +- .../Services/KfTokenService.cs | 83 +++++++++++++++++++ 6 files changed, 129 insertions(+), 66 deletions(-) delete mode 100644 .idea/.idea.KfChatDotNet/.idea/sqldialects.xml delete mode 100644 KfChatDotNetKickBot/Helpers.cs create mode 100644 KfChatDotNetKickBot/Services/KfTokenService.cs diff --git a/.idea/.idea.KfChatDotNet/.idea/sqldialects.xml b/.idea/.idea.KfChatDotNet/.idea/sqldialects.xml deleted file mode 100644 index 80f40a6..0000000 --- a/.idea/.idea.KfChatDotNet/.idea/sqldialects.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/KfChatDotNetKickBot/Helpers.cs b/KfChatDotNetKickBot/Helpers.cs deleted file mode 100644 index 2b6fcf0..0000000 --- a/KfChatDotNetKickBot/Helpers.cs +++ /dev/null @@ -1,36 +0,0 @@ -using Microsoft.Data.Sqlite; -using NLog; - -namespace KfChatDotNetKickBot; - -public static class Helpers -{ - // This ended up being pretty useless as it turns out Firefox doesn't store session cookies in cookies.sqlite - // But I'll leave it here in case it becomes useful one day - public static async Task GetXfToken(string cookieName, string cookieDomain, string containerPath) - { - var logger = LogManager.GetCurrentClassLogger(); - await using var connection = new SqliteConnection($"Data Source={containerPath}"); - - await connection.OpenAsync(); - logger.Debug($"Opened {containerPath}"); - - var command = connection.CreateCommand(); - command.CommandText = "SELECT value FROM moz_cookies WHERE host = $host AND name = $name ORDER BY creationTime DESC LIMIT 1"; - command.Parameters.AddWithValue("$host", cookieDomain); - command.Parameters.AddWithValue("$name", cookieName); - logger.Debug("Created command"); - logger.Debug(command.CommandText); - - await using var reader = await command.ExecuteReaderAsync(); - - while (await reader.ReadAsync()) - { - logger.Debug("Reading first row, which will be immediately returned anyway"); - return reader.GetString(0); - } - - logger.Error("Fucked up while retrieving cookie. Cookie doesn't exist?"); - return null; - } -} \ No newline at end of file diff --git a/KfChatDotNetKickBot/KfChatDotNetKickBot.csproj b/KfChatDotNetKickBot/KfChatDotNetKickBot.csproj index 51b8f5b..2eaf491 100644 --- a/KfChatDotNetKickBot/KfChatDotNetKickBot.csproj +++ b/KfChatDotNetKickBot/KfChatDotNetKickBot.csproj @@ -8,10 +8,11 @@ - - + + + @@ -31,4 +32,8 @@ + + + + diff --git a/KfChatDotNetKickBot/KickBot.cs b/KfChatDotNetKickBot/KickBot.cs index 85163d8..86a364d 100644 --- a/KfChatDotNetKickBot/KickBot.cs +++ b/KfChatDotNetKickBot/KickBot.cs @@ -1,4 +1,5 @@ -using KfChatDotNetWsClient; +using KfChatDotNetKickBot.Services; +using KfChatDotNetWsClient; using KfChatDotNetWsClient.Models; using KfChatDotNetWsClient.Models.Events; using KfChatDotNetWsClient.Models.Json; @@ -19,6 +20,7 @@ public class KickBot private Thread _pingThread; private bool _pingEnabled = true; private bool _gambaSeshPresent = false; + private string _xfSessionToken; // Oh no it's an ever expanding list that may never get cleaned up! // BUY MORE RAM private List _seenMsgIds = new List(); @@ -37,11 +39,12 @@ public class KickBot _config = JsonConvert.DeserializeObject(File.ReadAllText(configPath)) ?? throw new InvalidOperationException(); + RefreshXfToken(); _kfClient = new ChatClient(new ChatClientConfigModel { WsUri = _config.KfWsEndpoint, - XfSessionToken = GetXfToken(), + XfSessionToken = _xfSessionToken, CookieDomain = _config.KfWsEndpoint.Host, Proxy = _config.KfProxy, ReconnectTimeout = _config.KfReconnectTimeout @@ -55,6 +58,7 @@ public class KickBot _kfClient.OnUsersJoined += OnUsersJoined; _kfClient.OnWsDisconnection += OnKfWsDisconnected; _kfClient.OnWsReconnect += OnKfWsReconnected; + _kfClient.OnFailedToJoinRoom += OnFailedToJoinRoom; _kickClient.OnStreamerIsLive += OnStreamerIsLive; _kickClient.OnChatMessage += OnKickChatMessage; @@ -81,24 +85,36 @@ public class KickBot } } + private void OnFailedToJoinRoom(object sender, string message) + { + _logger.Error($"Couldn't join the room. KF returned: {message}"); + _logger.Error("This is likely due to the session cookie expiring. Retrieving a new one."); + RefreshXfToken(); + _kfClient.UpdateToken(_xfSessionToken); + _logger.Info("Retrieved fresh token. Reconnecting."); + _kfClient.Disconnect(); + _kfClient.StartWsClient().Wait(); + _logger.Info("Client should be reconnecting now"); + } + private void PingThread() { while (_pingEnabled) { Thread.Sleep(TimeSpan.FromSeconds(15)); _logger.Debug("Pinging KF and Pusher"); - AnsiConsole.MarkupLine("[yellow]Pinging KF and Pusher[/]"); _kfClient.SendMessage("/ping"); _kickClient.SendPusherPing(); if (_initialStartCooldown) _initialStartCooldown = false; } } - private string GetXfToken() + private void RefreshXfToken() { - //return Helpers.GetXfToken("xf_session", _config.KfWsEndpoint.Host, _config.FirefoxCookieContainer).Result ?? - // throw new InvalidOperationException(); - return _config.XfTokenValue; + var cookie = KfTokenService.FetchSessionTokenAsync(_config.KfDomain, _config.KfUsername, _config.KfPassword, + _config.ChromiumPath, _config.KfProxy).Result; + _logger.Debug($"FetchSessionTokenAsync returned {cookie}"); + _xfSessionToken = cookie; } private void OnStreamerIsLive(object sender, KickModels.StreamerIsLiveEventModel? e) @@ -135,6 +151,10 @@ public class KickBot { _sendChatMessage("definition of insanity = doing the same thing over and over and over excecting a different result, and heres my dumbass trying to get rich every day and losing everythign i fucking touch every fucking time FUCK this bullshit FUCK MY LIEFdefinition of insanity = doing the same thing over and over and over excecting a different result, and heres my dumbass trying to get rich every day and losing everythign i fucking touch every fucking time FUCK this bullshit FUCK MY LIEF"); } + else if (message.MessageRaw.StartsWith("!help")) + { + _sendChatMessage("[img]https://i.postimg.cc/fTw6tGWZ/ineedmoneydumbfuck.png[/img]", true); + } } else { @@ -144,9 +164,9 @@ public class KickBot } } - private void _sendChatMessage(string message) + private void _sendChatMessage(string message, bool bypassSeshDetect = false) { - if (_gambaSeshPresent && _config.EnableGambaSeshDetect) + if (_gambaSeshPresent && _config.EnableGambaSeshDetect && !bypassSeshDetect) { AnsiConsole.MarkupLine($"[red]Not sending message '{message.EscapeMarkup()}' as GambaSesh is present[/]"); return; @@ -196,32 +216,28 @@ public class KickBot private void OnKfWsDisconnected(object sender, DisconnectionInfo disconnectionInfo) { - AnsiConsole.MarkupLine($"[red]Sneedchat disconnected due to {disconnectionInfo.Type}[/]"); - AnsiConsole.MarkupLine("[yellow]Grabbing fresh token from browser[/]"); - var token = GetXfToken(); - AnsiConsole.MarkupLine($"[green]Obtained token = {token.EscapeMarkup()}[/]"); - _kfClient.UpdateToken(token); + _logger.Error($"Sneedchat disconnected due to {disconnectionInfo.Type}"); } private void OnKfWsReconnected(object sender, ReconnectionInfo reconnectionInfo) { - AnsiConsole.MarkupLine($"[red]Sneedchat reconnected due to {reconnectionInfo.Type}[/]"); - AnsiConsole.MarkupLine($"[green]Rejoining {_config.KfChatRoomId}[/]"); + _logger.Error($"Sneedchat reconnected due to {reconnectionInfo.Type}"); + _logger.Info($"Rejoining {_config.KfChatRoomId}"); _kfClient.JoinRoom(_config.KfChatRoomId); } private void OnPusherWsReconnected(object sender, ReconnectionInfo reconnectionInfo) { - AnsiConsole.MarkupLine($"[red]Pusher reconnected due to {reconnectionInfo.Type}[/]"); + _logger.Error($"Pusher reconnected due to {reconnectionInfo.Type}"); foreach (var channel in _config.PusherChannels) { - AnsiConsole.MarkupLine($"[green]Rejoining {channel}[/]"); + _logger.Info($"Rejoining {channel}"); _kickClient.SendPusherSubscribe(channel); } } private void OnPusherSubscriptionSucceeded(object sender, PusherModels.BasePusherEventModel? e) { - AnsiConsole.MarkupLine($"[green]Pusher indicates subscription to {e?.Channel.EscapeMarkup()} was successful[/]"); + _logger.Info($"Pusher indicates subscription to {e?.Channel} was successful"); } } \ No newline at end of file diff --git a/KfChatDotNetKickBot/Models.cs b/KfChatDotNetKickBot/Models.cs index 835b601..cb5474c 100644 --- a/KfChatDotNetKickBot/Models.cs +++ b/KfChatDotNetKickBot/Models.cs @@ -18,11 +18,12 @@ public class Models public string? PusherProxy { get; set; } public int KfReconnectTimeout { get; set; } = 30; public int PusherReconnectTimeout { get; set; } = 30; - // Todo: Find a way to extract this from the browser as it's not valid forever - public string? XfTokenValue { get; set; } - // Because his shitty bot crashed and it's annoying not having notifications public bool EnableGambaSeshDetect { get; set; } = true; public int GambaSeshUserId { get; set; } = 168162; public string KickIcon { get; set; } = "https://i.ibb.co/0cqwscx/kick.png"; + public string KfDomain { get; set; } = "kiwifarms.st"; + public required string KfUsername { get; set; } + public required string KfPassword { get; set; } + public string ChromiumPath { get; set; } = "chromium_install"; } } \ No newline at end of file diff --git a/KfChatDotNetKickBot/Services/KfTokenService.cs b/KfChatDotNetKickBot/Services/KfTokenService.cs new file mode 100644 index 0000000..edd2855 --- /dev/null +++ b/KfChatDotNetKickBot/Services/KfTokenService.cs @@ -0,0 +1,83 @@ +using System.Net; +using System.Text.Json; +using NLog; +using PuppeteerSharp; + +namespace KfChatDotNetKickBot.Services; + +public class KfTokenService +{ + // Shout out Gamba Sesh for open sourcing his token retriever which heavily inspired this implementation + public static async Task FetchSessionTokenAsync(string domain, string username, string password, string browserPath, string? proxy = null) + { + var logger = LogManager.GetCurrentClassLogger(); + var browserFetcher = new BrowserFetcher(new BrowserFetcherOptions + { Browser = SupportedBrowser.Chromium, Path = browserPath }); + if (proxy != null) + { + browserFetcher.WebProxy = new WebProxy(proxy); + logger.Debug($"Detected proxy settings for browser download: {proxy}"); + } + + var installedBrowser = await browserFetcher.DownloadAsync(); + logger.Debug("Downloaded browser"); + List launchArgs = ["--no-sandbox"]; + if (proxy != null) + { + logger.Debug($"Configuring Chromium to use proxy {proxy}"); + launchArgs.Add($"--proxy-server=\"{proxy}\""); + } + + var launchOptions = new LaunchOptions + { + Headless = false, ExecutablePath = installedBrowser.GetExecutablePath(), UserDataDir = "kf_profile", + Args = launchArgs.ToArray() + }; + + await using var browser = await Puppeteer.LaunchAsync(launchOptions); + await using var page = await browser.NewPageAsync(); + await page.GoToAsync($"https://{domain}/login"); + await page.WaitForSelectorAsync("img[alt=\"Kiwi Farms\"]"); + if (await page.QuerySelectorAsync("html[data-template=\"login\"]") == null) + { + logger.Debug("Page template is not login. This is expected if we're already logged in. Retrieving cookies"); + return await GetXfSessionCookie(); + } + + + var usernameFieldSelector = await page.QuerySelectorAsync("input[autocomplete=\"username\"]"); + var passwordFieldSelector = await page.QuerySelectorAsync("input[autocomplete=\"current-password\"]"); + var loginButtonSelector = await page.QuerySelectorAsync("div[class=\"formSubmitRow-controls\"] > button[type=\"submit\"]"); + if (usernameFieldSelector == null || passwordFieldSelector == null || loginButtonSelector == null) + { + // Realistically this shouldn't happen unless Null changes the login template in a big way + logger.Error("Username/password fields could not be found"); + throw new MissingLoginElementsException(); + } + + await usernameFieldSelector.TypeAsync(username); + await passwordFieldSelector.TypeAsync(password); + await loginButtonSelector.ClickAsync(); + logger.Debug("Login fields have been filled out and button clicked. Awaiting page navigation."); + await page.WaitForNavigationAsync(); + logger.Debug("Navigation completed. Doing the cookie needful"); + return await GetXfSessionCookie(); + + async Task GetXfSessionCookie() + { + var cookies = await page.GetCookiesAsync(); + var xfSession = cookies.FirstOrDefault(x => x.Name == "xf_session"); + if (xfSession == null) + { + logger.Error("xf_session cookie not set. Cookie data follows"); + logger.Error(JsonSerializer.Serialize(cookies)); + throw new MissingSessionCookieException(); + } + logger.Debug($"Returning xf_session value: {xfSession.Value}"); + return xfSession.Value; + } + } + + public class MissingSessionCookieException : Exception; + public class MissingLoginElementsException : Exception; +} \ No newline at end of file