Skip to content
30 changes: 28 additions & 2 deletions Silkstring/Configuration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,40 @@ namespace Silkstring;
[Serializable]
public class Configuration : IPluginConfiguration
{
[NonSerialized]
private bool _isDirty = false;
[NonSerialized]
private DateTime _lastDirty = DateTime.MinValue;

private int _commandDelay = 100;
public int CommandDelay
{
get => _commandDelay;
set => _commandDelay = Math.Clamp(value, 0, 1000);
}

public int Version { get; set; } = 1;
public List<AliasFolder> Folders = new();
public List<AliasEntry> Aliases = new();
public int CommandDelay { get; set; } = 100;
public bool MultilineCommands { get; set; } = false;
public bool MultilineCommands { get; set; }

public void Save()
{
Plugin.PluginInterface.SavePluginConfig(this);
}

public void MarkDirty()
{
_isDirty = true;
_lastDirty = DateTime.UtcNow;
}

public void TrySave(TimeSpan debounce)
{
if (_isDirty && DateTime.UtcNow - _lastDirty > debounce)
{
Save();
_isDirty = false;
}
}
}
22 changes: 18 additions & 4 deletions Silkstring/Models/AliasEntry.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading;

namespace Silkstring.Models;

public class AliasEntry
{
public static readonly string[] Blacklist = ["silkstring", "xlplugins", "xlsettings", "xldclose", "xldev"];
private static int _nextId = 0;
public static readonly JsonSerializerOptions SerializerOptions = new() { IncludeFields = true };

public static readonly HashSet<string> Blacklist = new(StringComparer.OrdinalIgnoreCase)
{
"silkstring", "xlplugins", "xlsettings", "xldclose", "xldev"
};

public string DisplayName = string.Empty;
public bool Enabled = true;
Expand All @@ -17,21 +26,26 @@ public class AliasEntry
public bool Delete;

[NonSerialized]
[JsonIgnore]
public int UniqueId;

public AliasEntry()
{
UniqueId = Interlocked.Increment(ref _nextId);
}

public bool IsValid()
{
var names = Name.Split('|', StringSplitOptions.TrimEntries);
var names = Name.Split('|', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries);
if (names.Length == 0) return false;

foreach (var name in names)
{
if (string.IsNullOrWhiteSpace(name)) return false;
if (Blacklist.Contains(name)) return false;
if (name.Contains(' ')) return false;
if (name.Contains('/')) return false;
}
if (Output.Count == 0) return false;
if (Output.Count == 0) return false;
return !Output.Any(command => string.IsNullOrWhiteSpace(command.Command));
}

Expand Down
13 changes: 13 additions & 0 deletions Silkstring/Models/AliasFolder.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,22 @@
using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;
using System.Threading;

namespace Silkstring.Models;

public class AliasFolder
{
private static int _nextId = 0;
public string Name { get; set; } = string.Empty;
public List<AliasEntry> Aliases { get; set; } = new();

[NonSerialized]
[JsonIgnore]
public int UniqueId;

public AliasFolder()
{
UniqueId = Interlocked.Increment(ref _nextId);
}
}
10 changes: 10 additions & 0 deletions Silkstring/Models/CommandEntry.cs
Original file line number Diff line number Diff line change
@@ -1,17 +1,27 @@
using System;
using System.Text.Json.Serialization;
using System.Threading;

namespace Silkstring.Models;

public class CommandEntry
{
private static int _nextId = 0;

public string Command = string.Empty;

[NonSerialized]
public bool Delete;

[NonSerialized]
[JsonIgnore]
public int UniqueId;

public CommandEntry()
{
UniqueId = Interlocked.Increment(ref _nextId);
}

public CommandEntry Clone()
{
return new CommandEntry()
Expand Down
54 changes: 33 additions & 21 deletions Silkstring/Plugin.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Linq;
using System.Threading;
using Dalamud.Game.Command;
using Dalamud.Hooking;
using Dalamud.IoC;
Expand Down Expand Up @@ -31,17 +32,21 @@ public sealed unsafe class Plugin : IDalamudPlugin
[PluginService]
internal static IFramework Framework { get; private set; } = null!;

[PluginService]
internal static INotificationManager NotificationManager { get; private set; } = null!;

private const string CommandName = "/silkstring";

public Configuration Configuration { get; init; }

public readonly WindowSystem WindowSystem = new("Silkstring");
private EditWindow EditWindow { get; init; }
private ConfigWindow ConfigWindow { get; init; }
private MainWindow MainWindow { get; init; }

private Hook<ShellCommandModule.Delegates.ExecuteCommandInner> processChatInputHook;

private readonly CancellationTokenSource _cts = new();

public Plugin()
{
Configuration = PluginInterface.GetPluginConfig() as Configuration ?? new Configuration();
Expand All @@ -53,13 +58,11 @@ public Plugin()
ProcessChatInputDetour);
processChatInputHook.Enable();

EditWindow = new EditWindow(this);
ConfigWindow = new ConfigWindow(this);
MainWindow = new MainWindow(this, ToggleConfigUi);

WindowSystem.AddWindow(MainWindow);
WindowSystem.AddWindow(ConfigWindow);
WindowSystem.AddWindow(EditWindow);

CommandManager.AddHandler(CommandName, new CommandInfo(OnCommand)
{
Expand All @@ -69,15 +72,20 @@ public Plugin()
PluginInterface.UiBuilder.Draw += WindowSystem.Draw;
PluginInterface.UiBuilder.OpenConfigUi += ToggleConfigUi;
PluginInterface.UiBuilder.OpenMainUi += ToggleMainUi;
Framework.Update += OnFrameworkUpdate;
}

public void Dispose()
{
ECommonsMain.Dispose();

_cts.Cancel();
_cts.Dispose();

PluginInterface.UiBuilder.Draw -= WindowSystem.Draw;
PluginInterface.UiBuilder.OpenConfigUi -= ToggleConfigUi;
PluginInterface.UiBuilder.OpenMainUi -= ToggleMainUi;
Framework.Update -= OnFrameworkUpdate;

processChatInputHook?.Disable();
processChatInputHook?.Dispose();
Expand All @@ -86,7 +94,6 @@ public void Dispose()

MainWindow.Dispose();
ConfigWindow.Dispose();
EditWindow.Dispose();

CommandManager.RemoveHandler(CommandName);
}
Expand All @@ -96,6 +103,11 @@ private void OnCommand(string command, string args)
MainWindow.Toggle();
}

private void OnFrameworkUpdate(IFramework framework)
{
Configuration.TrySave(TimeSpan.FromMilliseconds(500));
}

public void ToggleConfigUi() => ConfigWindow.Toggle();
public void ToggleMainUi() => MainWindow.Toggle();

Expand All @@ -107,29 +119,29 @@ private void ProcessChatInputDetour(ShellCommandModule* shellCommandModule, Utf8
if (inputString.StartsWith('/'))
{
var splitString = inputString.Split(' ');
if (splitString.Length > 0)
var commandName = splitString[0][1..];
var alias = Configuration.Aliases.Concat(
Configuration.Folders.SelectMany(g => g.Aliases)).FirstOrDefault(a =>
a.Enabled &&
a.IsValid() &&
a.Name.Split('|', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries).Any(n => n.Equals(commandName, StringComparison.OrdinalIgnoreCase)));
if (alias != null)
{
var commandName = splitString[0][1..];
var alias = Configuration.Aliases.Concat(
Configuration.Folders.SelectMany(g => g.Aliases)).FirstOrDefault(a =>
a.Enabled &&
a.IsValid() &&
a.Name.Split('|', StringSplitOptions.TrimEntries).Any(n => n.Equals(commandName, StringComparison.OrdinalIgnoreCase)));
if (alias != null)
{
var commands = alias.Output
.Where(c => !string.IsNullOrWhiteSpace(c.Command))
.Select(c => "/" + c.Command.TrimStart('/'))
.ToList();
_ = CommandHandler.ExecuteAsync(commands, Configuration.CommandDelay);
return;
}
var commands = alias.Output
.Where(c => !string.IsNullOrWhiteSpace(c.Command))
.Select(c => "/" + c.Strip())
.ToList();


CommandHandler.ExecuteAsync(commands, Configuration.CommandDelay, _cts.Token)
.ContinueWith(t => Log.Error(t.Exception, "Command execution failed"), TaskContinuationOptions.OnlyOnFaulted);
return;
}
}
}
catch (Exception ex)
{
Log.Error(ex.Message);
Log.Error(ex, "An error occurred while processing command");
}
processChatInputHook.Original(shellCommandModule, message, uiModule);
}
Expand Down
13 changes: 6 additions & 7 deletions Silkstring/Services/CommandHandler.cs
Original file line number Diff line number Diff line change
@@ -1,20 +1,19 @@
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using ECommons.Automation;

namespace Silkstring.Services;

public static class CommandHandler
{
public static async Task ExecuteAsync(List<string> commands, int delayMs = 100)
public static async Task ExecuteAsync(IReadOnlyList<string> commands, int delayMs = 100, CancellationToken cancellationToken = default)
{
foreach (var command in commands)
for (var i = 0; i < commands.Count; i++)
{
await Plugin.Framework.RunOnFrameworkThread(() =>
{
Chat.SendMessage(command);
});
await Task.Delay(delayMs);
var cmd = commands[i];
await Plugin.Framework.RunOnFrameworkThread(() => Chat.SendMessage(cmd));
if (i < commands.Count - 1) await Task.Delay(delayMs, cancellationToken);
}
}
}
Loading
Loading