From 42804c90e4c51534687239cfc3895083e4efedbd Mon Sep 17 00:00:00 2001 From: barelyprofessional <150058423+barelyprofessional@users.noreply.github.com> Date: Thu, 5 Feb 2026 23:04:43 -0600 Subject: [PATCH] Experimental ttrs support --- KfChatDotNetBot/Services/KfTokenService.cs | 44 ++++++------- KfChatDotNetBot/Services/KiwiFlare.cs | 73 ++++++++++++---------- 2 files changed, 60 insertions(+), 57 deletions(-) diff --git a/KfChatDotNetBot/Services/KfTokenService.cs b/KfChatDotNetBot/Services/KfTokenService.cs index 35dd92d..41f9e2b 100644 --- a/KfChatDotNetBot/Services/KfTokenService.cs +++ b/KfChatDotNetBot/Services/KfTokenService.cs @@ -43,21 +43,8 @@ public class KfTokenService return handler; } - private async Task CheckClearanceToken() + private async Task GetNewClearanceToken() { - var clearanceCookie = _cookies.GetAllCookies()["sssg_clearance"]; - _logger.Debug($"Got clearance cookie with value: {clearanceCookie}"); - if (await _kiwiFlare.CheckAuth(Guid.NewGuid().ToString())) - { - _logger.Debug("KiwiFlare has been turned off as it's accepting a non-existent sssg_clearance"); - return; - }; - if (clearanceCookie != null) - { - if (await _kiwiFlare.CheckAuth(clearanceCookie.Value)) return; - _logger.Debug("Cookie is no longer valid, removing"); - _cookies.GetAllCookies().Remove(clearanceCookie); - } _logger.Debug("Getting a new clearance token"); var i = 0; // Shitty retry logic as the forum is still annoyingly unstable @@ -73,8 +60,18 @@ public class KfTokenService return; } var solution = await _kiwiFlare.SolveChallenge(challenge); - var token = await _kiwiFlare.SubmitAnswer(solution); - _cookies.Add(new Cookie("sssg_clearance", token, "/", _kfDomain)); + string token; + if (challenge.IsTtrs) + { + token = await _kiwiFlare.SubmitAnswerTtrs(solution); + _cookies.Add(new Cookie("ttrs_clearance", token, "/", _kfDomain)); + + } + else + { + token = await _kiwiFlare.SubmitAnswer(solution); + _cookies.Add(new Cookie("sssg_clearance", token, "/", _kfDomain)); + } _logger.Debug("Successfully retrieved a new token and added to the cookie container"); return; } @@ -90,19 +87,17 @@ public class KfTokenService private async Task GetLoginPage() { - _logger.Debug("Checking clearance token is actually valid first"); - await CheckClearanceToken(); using var client = new HttpClient(GetHttpClientHandler()); var response = await client.GetAsync($"https://{_kfDomain}/login", _ctx); response.EnsureSuccessStatusCode(); var content = await response.Content.ReadAsStringAsync(_ctx); - var document = new HtmlDocument(); - document.LoadHtml(content); - var challengeData = document.DocumentNode.SelectSingleNode("//html[@id=\"sssg\"]"); - if (response.StatusCode == HttpStatusCode.NonAuthoritativeInformation && challengeData != null) + if (response.StatusCode == HttpStatusCode.NonAuthoritativeInformation) { - _logger.Error("Caught a 203 response when trying to load logon page which means we were KiwiFlare challenged"); - throw new KiwiFlareChallengedException(); + _logger.Info("Caught a 203 response when trying to load the logon page which means we have to solve a KiwiFlare challenge"); + await GetNewClearanceToken(); + _logger.Info("Solved the challenge, now going to grab the login page again"); + response = await client.GetAsync($"https://{_kfDomain}/login", _ctx); + content = await response.Content.ReadAsStringAsync(_ctx); } return content; } @@ -184,6 +179,5 @@ public class KfTokenService _cookies = new CookieContainer(); } - public class KiwiFlareChallengedException : Exception; public class KiwiFarmsLogonFailedException : Exception; } \ No newline at end of file diff --git a/KfChatDotNetBot/Services/KiwiFlare.cs b/KfChatDotNetBot/Services/KiwiFlare.cs index 4792c4f..2d5d6f3 100644 --- a/KfChatDotNetBot/Services/KiwiFlare.cs +++ b/KfChatDotNetBot/Services/KiwiFlare.cs @@ -36,25 +36,37 @@ public class KiwiFlare(string kfDomain, string? proxy = null, CancellationToken? var response = await client.GetAsync($"https://{kfDomain}/", _ctx); var document = new HtmlDocument(); document.Load(await response.Content.ReadAsStreamAsync(_ctx)); - var challengeData = document.DocumentNode.SelectSingleNode("//html[@id=\"sssg\"]"); + var pow = "sssg"; + var challengeData = document.DocumentNode.SelectSingleNode($"//html[@id=\"{pow}\"]"); if (challengeData == null) { - _logger.Info("challengeData was null. Couldn't find html element with id = sssg, returning null"); + _logger.Info("challengeData was null. Couldn't find html element with id = sssg, trying ttrs"); + pow = "ttrs"; + challengeData = document.DocumentNode.SelectSingleNode($"//html[@id=\"{pow}\"]"); + if (challengeData == null) + { + _logger.Info("challengeData was still null even looking for ttrs"); + } return null; } - if (!challengeData.Attributes.Contains("data-sssg-challenge")) throw new Exception("data-sssg-challenge attribute missing"); - if (!challengeData.Attributes.Contains("data-sssg-difficulty")) throw new Exception("data-sssg-difficulty attribute missing"); - if (!challengeData.Attributes.Contains("data-sssg-patience")) throw new Exception("data-sssg-patience attribute missing"); - var salt = challengeData.Attributes["data-sssg-challenge"].Value; - var difficulty = Convert.ToInt32(challengeData.Attributes["data-sssg-difficulty"].Value); - var patience = TimeSpan.FromMinutes(Convert.ToDouble(challengeData.Attributes["data-sssg-patience"].Value)); - _logger.Info($"Got sssg challenge parameters. Salt = {salt}, Difficulty = {difficulty}, Patience = {patience.TotalMinutes} minutes"); + if (!challengeData.Attributes.Contains($"data-{pow}-challenge")) throw new Exception($"data-{pow}-challenge attribute missing"); + if (!challengeData.Attributes.Contains($"data-{pow}-difficulty")) throw new Exception($"data-{pow}-difficulty attribute missing"); + var patience = TimeSpan.MaxValue; + // ttrs has no patience value + if (challengeData.Attributes.Contains("data-sssg-patience")) + { + patience = TimeSpan.FromMinutes(Convert.ToDouble(challengeData.Attributes["data-sssg-patience"].Value)); + } + var salt = challengeData.Attributes[$"data-{pow}-challenge"].Value; + var difficulty = Convert.ToInt32(challengeData.Attributes[$"data-{pow}-difficulty"].Value); + _logger.Info($"Got {pow} challenge parameters. IsTtrs = {pow == "ttrs"}, Salt = {salt}, Difficulty = {difficulty}, Patience = {patience.TotalMinutes} minutes"); return new KiwiFlareChallengeModel { Salt = salt, Difficulty = difficulty, - Patience = patience + Patience = patience, + IsTtrs = pow == "ttrs" }; } @@ -147,37 +159,33 @@ public class KiwiFlare(string kfDomain, string? proxy = null, CancellationToken? _logger.Error(json.GetRawText()); throw new Exception($"Auth property was missing from sssg response: {json.GetRawText()}"); } - - public async Task CheckAuth(string authToken) + + public async Task SubmitAnswerTtrs(KiwiFlareChallengeSolutionModel solution) { - using var client = new HttpClient(GetHttpClientHandler()); + var handler = GetHttpClientHandler(); + var container = new CookieContainer(); + handler.CookieContainer = container; + handler.UseCookies = true; + using var client = new HttpClient(); client.Timeout = TimeSpan.FromSeconds(10); var formData = new FormUrlEncodedContent(new List> { - new("f", authToken), + new("salt", solution.Salt), + new("nonce", solution.Nonce.ToString()) }); - - var response = await client.PostAsync($"https://{kfDomain}/.sssg/api/check", formData, _ctx); - // KiwiFlare has been turned off - if (response.StatusCode == HttpStatusCode.NotFound) - { - return true; - } + + var response = await client.PostAsync($"https://{kfDomain}/.ttrs/challenge", formData, _ctx); var json = await response.Content.ReadFromJsonAsync(_ctx); - if (json.TryGetProperty("error", out var error)) + var success = json.GetProperty("success").GetBoolean(); + if (!success) { - _logger.Error($"Received error when checking the auth token: {error.GetString()}"); - return false; + var reason = json.GetProperty("reason").GetString(); + _logger.Error($"ttrs didn't accept our solution with reason: {reason}"); + throw new Exception($"ttrs didn't accept our solution with reason: {reason}"); } - if (json.TryGetProperty("auth", out _)) - { - return true; - } - - _logger.Error("Auth property was missing from sssg response"); - _logger.Error(json.GetRawText()); - return false; + var cookies = container.GetAllCookies(); + return cookies["ttrs_clearance"]?.Value ?? throw new InvalidOperationException(); } } @@ -186,6 +194,7 @@ public class KiwiFlareChallengeModel public required string Salt { get; set; } public required int Difficulty { get; set; } public required TimeSpan Patience { get; set; } + public required bool IsTtrs { get; set; } } public class KiwiFlareChallengeSolutionModel