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