diff --git a/.gitignore b/.gitignore index 6c58fd9..9c029ff 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ bin/ packages/ Paissa/Secrets.cs logs/ +packages.lock.json diff --git a/AutoSweep.csproj b/AutoSweep.csproj index f9c9182..91ce71f 100644 --- a/AutoSweep.csproj +++ b/AutoSweep.csproj @@ -3,7 +3,7 @@ autoSweep zhudotexe - net6.0-windows + net7.0-windows 9 true true @@ -11,10 +11,14 @@ false false bin\$(Configuration)\ - $(appdata)\XIVLauncher\addon\Hooks\dev\ true + true autoSweep - 1.4.1.0 + 1.4.2.0 + + + + $(appdata)\XIVLauncher\addon\Hooks\dev\ @@ -38,71 +42,41 @@ - $(AppData)\XIVLauncher\addon\Hooks\dev\Dalamud.dll + $(DalamudLibPath)Dalamud.dll False - $(AppData)\XIVLauncher\addon\Hooks\dev\ImGui.NET.dll + $(DalamudLibPath)ImGui.NET.dll False - $(AppData)\XIVLauncher\addon\Hooks\dev\ImGuiScene.dll + $(DalamudLibPath)ImGuiScene.dll False - $(AppData)\XIVLauncher\addon\Hooks\dev\Lumina.dll + $(DalamudLibPath)Lumina.dll False - $(AppData)\XIVLauncher\addon\Hooks\dev\Lumina.Excel.dll + $(DalamudLibPath)Lumina.Excel.dll False - $(AppData)\XIVLauncher\addon\Hooks\dev\Newtonsoft.Json.dll + $(DalamudLibPath)Newtonsoft.Json.dll False - + - - - + PreserveNewest + false - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Paissa/Client.cs b/Paissa/Client.cs index c63dcf0..a436ebf 100644 --- a/Paissa/Client.cs +++ b/Paissa/Client.cs @@ -11,9 +11,6 @@ using Dalamud.Game.Gui; using Dalamud.Logging; using DebounceThrottle; -using JWT; -using JWT.Algorithms; -using JWT.Serializers; using Newtonsoft.Json; using WebSocketSharp; @@ -21,8 +18,8 @@ namespace AutoSweep.Paissa { public class PaissaClient : IDisposable { private readonly HttpClient http; private WebSocket ws; - private readonly JwtEncoder encoder = new(new HMACSHA256Algorithm(), new JsonNetSerializer(), new JwtBase64UrlEncoder()); private bool disposed = false; + private string sessionToken; // dalamud private readonly ClientState clientState; @@ -40,7 +37,6 @@ public class PaissaClient : IDisposable { private const string wsRoute = "wss://paissadb.zhu.codes/ws"; #endif - private readonly byte[] secret = Encoding.UTF8.GetBytes(Secrets.JwtSecret); public event EventHandler OnPlotOpened; public event EventHandler OnPlotUpdate; @@ -61,9 +57,9 @@ public void Dispose() { // ==== Interface ==== /// - /// Fire and forget a POST request to register the current character's content ID. + /// Make a POST request to register the current character's content ID. /// - public void Hello() { + public async Task Hello() { PlayerCharacter player = clientState.LocalPlayer; if (player == null) return; @@ -75,7 +71,12 @@ public void Hello() { }; string content = JsonConvert.SerializeObject(charInfo); PluginLog.Debug(content); - PostFireAndForget("/hello", content); + var response = await Post("/hello", content, false); + if (response.IsSuccessStatusCode) { + string respText = await response.Content.ReadAsStringAsync(); + sessionToken = JsonConvert.DeserializeObject(respText).session_token; + PluginLog.Log("Completed PaissaDB HELLO"); + } } /// @@ -139,20 +140,33 @@ private void queueIngest(object data) { }); } - private async void PostFireAndForget(string route, string content) { - await PostFireAndForget(route, content, 5); + private async void PostFireAndForget(string route, string content, bool auth = true, ushort retries = 5) { + await Post(route, content, auth, retries); } - private async Task PostFireAndForget(string route, string content, ushort retries) { + private async Task Post(string route, string content, bool auth = true, ushort retries = 5) { HttpResponseMessage response = null; + PluginLog.Verbose(content); for (var i = 0; i < retries; i++) { - var request = new HttpRequestMessage(HttpMethod.Post, $"{apiBase}{route}") { - Content = new StringContent(content, Encoding.UTF8, "application/json"), - Headers = { - Authorization = new AuthenticationHeaderValue("Bearer", GenerateJwt()) + HttpRequestMessage request; + if (auth) { + if (sessionToken == null) { + PluginLog.LogWarning("Trying to send authed request but no session token!"); + await Hello(); + continue; } - }; + request = new HttpRequestMessage(HttpMethod.Post, $"{apiBase}{route}") { + Content = new StringContent(content, Encoding.UTF8, "application/json"), + Headers = { + Authorization = new AuthenticationHeaderValue("Bearer", sessionToken) + } + }; + } else { + request = new HttpRequestMessage(HttpMethod.Post, $"{apiBase}{route}") { + Content = new StringContent(content, Encoding.UTF8, "application/json"), + }; + } try { response = await http.SendAsync(request); PluginLog.Debug($"{request.Method} {request.RequestUri} returned {response.StatusCode} ({response.ReasonPhrase})"); @@ -174,10 +188,12 @@ private async Task PostFireAndForget(string route, string content, ushort retrie } // todo better error handling - if (response == null) + if (response == null) { chat.PrintError("There was an error connecting to PaissaDB."); - else if (!response.IsSuccessStatusCode) + } else if (!response.IsSuccessStatusCode) { chat.PrintError($"There was an error connecting to PaissaDB: {response.ReasonPhrase}"); + } + return response; } @@ -185,7 +201,7 @@ private async Task PostFireAndForget(string route, string content, ushort retrie private void ReconnectWS() { Task.Run(() => { ws?.Close(1000); - ws = new WebSocket(GetWSRouteWithAuth()); + ws = new WebSocket(wsRoute); ws.OnOpen += OnWSOpen; ws.OnMessage += OnWSMessage; ws.OnClose += OnWSClose; @@ -249,21 +265,5 @@ private void WSReconnectSoon() { if (!disposed) ReconnectWS(); }); } - - - // ==== helpers ==== - private string GenerateJwt() { - var payload = new Dictionary { - { "cid", clientState.LocalContentId }, - { "aud", "PaissaHouse" }, - { "iss", "PaissaDB" }, - { "iat", DateTimeOffset.Now.ToUnixTimeSeconds() } - }; - return encoder.Encode(payload, secret); - } - - private string GetWSRouteWithAuth() { - return $"{wsRoute}?jwt={GenerateJwt()}"; - } } } diff --git a/Paissa/LotteryObserver.cs b/Paissa/LotteryObserver.cs index 0627b33..60b7f82 100644 --- a/Paissa/LotteryObserver.cs +++ b/Paissa/LotteryObserver.cs @@ -54,7 +54,7 @@ long a8 PluginLog.LogDebug( $"Got PlacardSaleInfo: PurchaseType={saleInfo.PurchaseType}, TenantType={saleInfo.TenantType}, available={saleInfo.AvailabilityType}, until={saleInfo.PhaseEndsAt}, numEntries={saleInfo.EntryCount}"); - PluginLog.LogDebug($"unknown1={saleInfo.Unknown1}, unknown2={saleInfo.Unknown2}, unknown3={BitConverter.ToString(saleInfo.Unknown3)}"); + PluginLog.LogDebug($"unknown1={saleInfo.Unknown1}, unknown2={saleInfo.Unknown2}, unknown3={saleInfo.Unknown3}, unknown4={BitConverter.ToString(saleInfo.Unknown4)}"); PluginLog.LogDebug( $"housingType={housingType}, territoryTypeId={territoryTypeId}, wardId={wardId}, plotId={plotId}, apartmentNumber={apartmentNumber}, placardSaleInfoPtr={placardSaleInfoPtr}, a8={a8}"); diff --git a/Paissa/Schema.cs b/Paissa/Schema.cs index fda8679..7bb06c9 100644 --- a/Paissa/Schema.cs +++ b/Paissa/Schema.cs @@ -10,6 +10,12 @@ public class WSMessage { public JObject Data { get; set; } } + public class HelloResponse { + public string message { get; set; } + public double server_time { get; set; } + public string session_token { get; set; } + } + public class DistrictDetail { public ushort district_id { get; set; } public string name { get; set; } diff --git a/Paissa/Utils.cs b/Paissa/Utils.cs index 036b3f8..bcf9081 100644 --- a/Paissa/Utils.cs +++ b/Paissa/Utils.cs @@ -7,7 +7,7 @@ public class Utils { // configuration constants public const string CommandName = "/psweep"; public const string HouseCommandName = "/phouse"; - public const int NumWardsPerDistrict = 24; + public const int NumWardsPerDistrict = 30; public static uint TerritoryTypeIdToLandSetId(uint territoryTypeId) { return territoryTypeId switch { diff --git a/Plugin.cs b/Plugin.cs index 6665190..e4f773c 100644 --- a/Plugin.cs +++ b/Plugin.cs @@ -1,5 +1,6 @@ using System; using System.Diagnostics; +using System.Threading.Tasks; using AutoSweep.Paissa; using AutoSweep.Structures; using Dalamud.Data; @@ -144,7 +145,7 @@ private void OnLogin(object _, EventArgs __) { private void OnUpdateEvent(Framework f) { if (clientNeedsHello && ClientState?.LocalPlayer != null && PaissaClient != null) { clientNeedsHello = false; - PaissaClient.Hello(); + Task.Run(async () => await PaissaClient.Hello()); } } diff --git a/Structures/Lottery.cs b/Structures/Lottery.cs index 76c3820..162d2a1 100644 --- a/Structures/Lottery.cs +++ b/Structures/Lottery.cs @@ -7,10 +7,11 @@ public class PlacardSaleInfo { public TenantType TenantType; // 0x21 public AvailabilityType AvailabilityType; // 0x22 public byte Unknown1; // 0x23 - public uint PhaseEndsAt; // 0x24 - 0x27 - public uint Unknown2; // 0x28 - 0x2B - public uint EntryCount; // 0x2C - 0x2F - public byte[] Unknown3; // 0x30 - 0x3F + public uint Unknown2; // 0x24 - 0x27 + public uint PhaseEndsAt; // 0x28 - 0x2B + public uint Unknown3; // 0x2C - 0x2F + public uint EntryCount; // 0x30 - 0x33 + public byte[] Unknown4; // 0x34 - 0x4B public static unsafe PlacardSaleInfo Read(IntPtr dataPtr) { var saleInfo = new PlacardSaleInfo(); @@ -20,10 +21,11 @@ public static unsafe PlacardSaleInfo Read(IntPtr dataPtr) { saleInfo.TenantType = (TenantType)binaryReader.ReadByte(); saleInfo.AvailabilityType = (AvailabilityType)binaryReader.ReadByte(); saleInfo.Unknown1 = binaryReader.ReadByte(); - saleInfo.PhaseEndsAt = binaryReader.ReadUInt32(); saleInfo.Unknown2 = binaryReader.ReadUInt32(); + saleInfo.PhaseEndsAt = binaryReader.ReadUInt32(); + saleInfo.Unknown3 = binaryReader.ReadUInt32(); saleInfo.EntryCount = binaryReader.ReadUInt32(); - saleInfo.Unknown3 = binaryReader.ReadBytes(16); + saleInfo.Unknown4 = binaryReader.ReadBytes(16); return saleInfo; } } diff --git a/autoSweep.json b/autoSweep.json index efdba3c..01cf356 100644 --- a/autoSweep.json +++ b/autoSweep.json @@ -9,6 +9,5 @@ "housing" ], "IconUrl": "https://raw.githubusercontent.com/zhudotexe/FFXIV_PaissaHouse/main/images/icon.png", - "Punchline": "Crowdsourced housing alerts and lottery tracking for all.", - "DalamudApiLevel": 7 + "Punchline": "Crowdsourced housing alerts and lottery tracking for all." }