Experimental ttrs support

This commit is contained in:
barelyprofessional
2026-02-05 23:04:43 -06:00
parent 32ae015c3b
commit 42804c90e4
2 changed files with 60 additions and 57 deletions

View File

@@ -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<string> 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;
}

View File

@@ -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<bool> CheckAuth(string authToken)
public async Task<string> 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<KeyValuePair<string, string>>
{
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<JsonElement>(_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