Files
Craftimizer/Craftimizer/Utils/MacroImport.cs
T
2024-06-14 13:21:04 -07:00

159 lines
5.5 KiB
C#

using Craftimizer.Plugin;
using Craftimizer.Simulator;
using Craftimizer.Simulator.Actions;
using Dalamud.Networking.Http;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net;
using System.Net.Http;
using System.Net.Http.Json;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
using static Craftimizer.Utils.CommunityMacros;
namespace Craftimizer.Utils;
public static class MacroImport
{
public static IReadOnlyList<ActionType>? TryParseMacro(string inputMacro)
{
var actions = new List<ActionType>();
foreach (var line in inputMacro.ReplaceLineEndings("\n").Split("\n"))
{
if (TryParseLine(line) is { } action)
actions.Add(action);
}
return actions.Count > 0 ? actions : null;
}
private static ActionType? TryParseLine(string line)
{
if (line.StartsWith("/ac", StringComparison.OrdinalIgnoreCase))
line = line[3..];
else if (line.StartsWith("/action", StringComparison.OrdinalIgnoreCase))
line = line[7..];
else
return null;
line = line.TrimStart();
// get first word
if (line.StartsWith('"'))
{
line = line[1..];
var end = line.IndexOf('"', 1);
if (end != -1)
line = line[..end];
}
else
{
var end = line.IndexOf(' ', 1);
if (end != -1)
line = line[..end];
}
foreach (var action in Enum.GetValues<ActionType>())
{
if (line.Equals(action.GetName(ClassJob.Carpenter), StringComparison.OrdinalIgnoreCase))
return action;
}
return null;
}
public static bool TryParseUrl(string url, out Uri uri)
{
if (!Uri.TryCreate(url, UriKind.Absolute, out uri!))
return false;
if (!string.Equals(uri.Scheme, Uri.UriSchemeHttp, StringComparison.OrdinalIgnoreCase) && !string.Equals(uri.Scheme, Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase))
return false;
if (!uri.IsDefaultPort)
return false;
return uri.DnsSafeHost is "ffxivteamcraft.com" or "craftingway.app";
}
public static Task<CommunityMacro> RetrieveUrl(string url, CancellationToken token)
{
if (!TryParseUrl(url, out var uri))
throw new ArgumentException("Unsupported url", nameof(url));
return uri.DnsSafeHost switch
{
"ffxivteamcraft.com" => RetrieveTeamcraftUrl(uri, token),
"craftingway.app" => RetrieveCraftingwayUrl(uri, token),
_ => throw new UnreachableException("TryParseUrl should handle miscellaneous edge cases"),
};
}
private static async Task<CommunityMacro> RetrieveTeamcraftUrl(Uri uri, CancellationToken token)
{
using var heCallback = new HappyEyeballsCallback();
using var client = new HttpClient(new SocketsHttpHandler
{
AutomaticDecompression = DecompressionMethods.All,
ConnectCallback = heCallback.ConnectCallback,
});
var path = uri.GetComponents(UriComponents.Path, UriFormat.SafeUnescaped);
if (!path.StartsWith("simulator/", StringComparison.Ordinal))
throw new ArgumentException("Teamcraft macro url should start with /simulator", nameof(uri));
path = path[10..];
var lastSlash = path.LastIndexOf('/');
if (lastSlash == -1)
throw new ArgumentException("Teamcraft macro url is not in the right format", nameof(uri));
var id = path[(lastSlash + 1)..];
var resp = await client.GetFromJsonAsync<TeamcraftMacro>(
$"https://firestore.googleapis.com/v1beta1/projects/ffxivteamcraft/databases/(default)/documents/rotations/{id}",
token).
ConfigureAwait(false);
if (resp is null)
throw new Exception("Internal error; failed to retrieve macro");
if (resp.Error is { } error)
throw new Exception($"Internal server error ({error.Status}); {error.Message}");
return new(resp);
}
private static async Task<CommunityMacro> RetrieveCraftingwayUrl(Uri uri, CancellationToken token)
{
using var heCallback = new HappyEyeballsCallback();
using var client = new HttpClient(new SocketsHttpHandler
{
AutomaticDecompression = DecompressionMethods.All,
ConnectCallback = heCallback.ConnectCallback,
});
// https://craftingway.app/rotation/variable-blueprint-KmrvS
var path = uri.GetComponents(UriComponents.Path, UriFormat.SafeUnescaped);
if (!path.StartsWith("rotation/", StringComparison.Ordinal))
throw new ArgumentException("Craftingway macro url should start with /rotation", nameof(uri));
path = path[9..];
var lastSlash = path.LastIndexOf('/');
if (lastSlash != -1)
throw new ArgumentException("Craftingway macro url is not in the right format", nameof(uri));
var id = path;
var resp = await client.GetFromJsonAsync<CraftingwayMacro>(
$"https://servingway.fly.dev/rotation/{id}",
token)
.ConfigureAwait(false);
if (resp is null)
throw new Exception("Internal error; failed to retrieve macro");
if (resp.Error is { } error)
throw new Exception($"Internal server error; {error}");
return new(resp);
}
}