Add auto-accept party invites from friends and FC members

- New AutoAcceptPartyService uses IAddonLifecycle to detect party invite popup
- Checks inviter against InfoProxyFriendList and InfoProxyFreeCompanyMember
- Config options: AutoAcceptEnabled, AutoAcceptFromFriends, AutoAcceptFromFreeCompany
- Bump version to 1.0.4, update CHANGELOG and README

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
jorg
2026-02-20 20:40:44 -06:00
parent 545e34e4d8
commit 4dcea5d7de
9 changed files with 200 additions and 4 deletions
+6
View File
@@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## [1.0.4] - 2025-02-20
### Added
- **Auto-accept party invites** Option to automatically accept party invites when the inviter is on your friend list or is a member of your Free Company. Configurable toggles for friends and FC members.
## [1.0.3] - 2025-02-03 ## [1.0.3] - 2025-02-03
### Added ### Added
@@ -45,4 +45,22 @@ public class HSRToolsConfiguration
/// Use this to diagnose cross-world invite issues. /// Use this to diagnose cross-world invite issues.
/// </summary> /// </summary>
public bool Debug { get; set; } = false; public bool Debug { get; set; } = false;
// --- Auto-accept party invites ---
/// <summary>
/// When true, automatically accept party invites from friends and/or Free Company members
/// (based on the options below).
/// </summary>
public bool AutoAcceptEnabled { get; set; } = false;
/// <summary>
/// When true and AutoAcceptEnabled, accept party invites from players on your friend list.
/// </summary>
public bool AutoAcceptFromFriends { get; set; } = true;
/// <summary>
/// When true and AutoAcceptEnabled, accept party invites from members of your Free Company.
/// </summary>
public bool AutoAcceptFromFreeCompany { get; set; } = true;
} }
+1 -1
View File
@@ -1,7 +1,7 @@
<Project Sdk="Dalamud.NET.Sdk/14.0.1"> <Project Sdk="Dalamud.NET.Sdk/14.0.1">
<PropertyGroup> <PropertyGroup>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Version>1.0.3.0</Version> <Version>1.0.4.0</Version>
<Author>Knack117</Author> <Author>Knack117</Author>
<Name>HSRTools</Name> <Name>HSRTools</Name>
<InternalName>HSRTools</InternalName> <InternalName>HSRTools</InternalName>
+2 -2
View File
@@ -1,8 +1,8 @@
{ {
"Author": "Knack117", "Author": "Knack117",
"Name": "HSRTools", "Name": "HSRTools",
"Punchline": "Auto-invite to party when a trigger word is said in FC, LS, CWLS, or tells.", "Punchline": "Auto-invite and auto-accept party invites from friends/FC.",
"Description": "Detects a user-defined trigger word in Free Company, Link Shell, Cross-World Link Shell, and tell chat, then invites the sender to your party. Works for same-world and cross-world (CWLS).", "Description": "Detects a trigger word in FC, LS, CWLS, and tells to auto-invite; optionally auto-accepts party invites from friends and FC members.",
"RepoUrl": "https://github.com/Knack117/HSRTools", "RepoUrl": "https://github.com/Knack117/HSRTools",
"Tags": [ "chat", "party", "invite", "automation" ], "Tags": [ "chat", "party", "invite", "automation" ],
"AcceptsFeedback": true "AcceptsFeedback": true
+9
View File
@@ -15,6 +15,7 @@ public sealed class HSRToolsPlugin : IDalamudPlugin
private readonly string _configDir; private readonly string _configDir;
private readonly ChatMonitorService _chatMonitorService; private readonly ChatMonitorService _chatMonitorService;
private readonly AutoAcceptPartyService _autoAcceptPartyService;
private readonly ConfigWindow _configWindow; private readonly ConfigWindow _configWindow;
private readonly WindowSystem _windowSystem; private readonly WindowSystem _windowSystem;
private HSRToolsConfiguration _config; private HSRToolsConfiguration _config;
@@ -32,6 +33,11 @@ public sealed class HSRToolsPlugin : IDalamudPlugin
PluginServices.PluginLog, PluginServices.PluginLog,
_config); _config);
_autoAcceptPartyService = new AutoAcceptPartyService(
PluginServices.AddonLifecycle,
PluginServices.PluginLog,
_config);
_configWindow = new ConfigWindow(_config); _configWindow = new ConfigWindow(_config);
_windowSystem = new WindowSystem("HSRTools"); _windowSystem = new WindowSystem("HSRTools");
_windowSystem.AddWindow(_configWindow); _windowSystem.AddWindow(_configWindow);
@@ -44,12 +50,15 @@ public sealed class HSRToolsPlugin : IDalamudPlugin
}); });
_chatMonitorService.SetConfiguration(_config); _chatMonitorService.SetConfiguration(_config);
_chatMonitorService.Start(); _chatMonitorService.Start();
_autoAcceptPartyService.SetConfiguration(_config);
_autoAcceptPartyService.Start();
} }
public void Dispose() public void Dispose()
{ {
PluginServices.CommandManager.RemoveHandler("/hsr"); PluginServices.CommandManager.RemoveHandler("/hsr");
_chatMonitorService.Stop(); _chatMonitorService.Stop();
_autoAcceptPartyService.Stop();
_windowSystem.RemoveAllWindows(); _windowSystem.RemoveAllWindows();
SaveConfig(_configDir, _config); SaveConfig(_configDir, _config);
} }
+134
View File
@@ -0,0 +1,134 @@
using System;
using Dalamud.Game.Addon.Lifecycle;
using Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
using FFXIVClientStructs.FFXIV.Client.UI.Info;
using HSRTools.Configuration;
namespace HSRTools.Services;
/// <summary>
/// Automatically accepts party invites when the inviter is a friend or Free Company member.
/// Uses IAddonLifecycle to detect when the party invite addon opens, then checks the
/// inviter against the friend list and FC roster before accepting.
/// </summary>
public sealed class AutoAcceptPartyService
{
private const string PartyInviteAddonName = "PartyInvite";
private readonly IAddonLifecycle _addonLifecycle;
private readonly IPluginLog _log;
private HSRToolsConfiguration _config;
public AutoAcceptPartyService(IAddonLifecycle addonLifecycle, IPluginLog log, HSRToolsConfiguration config)
{
_addonLifecycle = addonLifecycle;
_log = log;
_config = config;
}
public void SetConfiguration(HSRToolsConfiguration config)
{
_config = config;
}
public void Start()
{
_addonLifecycle.RegisterListener(AddonEvent.PreSetup, PartyInviteAddonName, OnPartyInviteAddon);
}
public void Stop()
{
_addonLifecycle.UnregisterListener(AddonEvent.PreSetup, PartyInviteAddonName, OnPartyInviteAddon);
}
private void OnPartyInviteAddon(AddonEvent eventType, AddonArgs args)
{
if (!_config.AutoAcceptEnabled)
return;
if (!_config.AutoAcceptFromFriends && !_config.AutoAcceptFromFreeCompany)
return;
unsafe
{
var agent = AgentPartyInvite.Instance();
if (agent == null)
return;
var proxy = agent->InfoProxyPartyInvite;
if (proxy == null)
return;
var inviterName = proxy->InviterName.ToString();
if (string.IsNullOrWhiteSpace(inviterName))
return;
var inviterWorldId = proxy->InviterWorldId;
var isFriend = _config.AutoAcceptFromFriends && IsFriend(inviterName, inviterWorldId);
var isFcMember = _config.AutoAcceptFromFreeCompany && IsFreeCompanyMember(inviterName, inviterWorldId);
if (!isFriend && !isFcMember)
return;
var reason = isFriend && isFcMember ? "friend and FC member" : isFriend ? "friend" : "FC member";
_log.Info($"Auto-accepting party invite from {inviterName} ({reason}).");
try
{
// RespondToInvitation is on InfoProxyInvitedList (parent of InfoProxyPartyInvite)
var accepted = ((InfoProxyInvitedList*)proxy)->RespondToInvitation(inviterName, true);
if (!accepted)
_log.Warning($"Failed to auto-accept party invite from {inviterName}.");
}
catch (Exception ex)
{
_log.Error(ex, $"Error auto-accepting party invite from {inviterName}");
}
}
}
private static unsafe bool IsFriend(string characterName, ushort worldId)
{
try
{
var infoModule = InfoModule.Instance();
if (infoModule == null)
return false;
var proxy = (InfoProxyCommonList*)infoModule->GetInfoProxyById(InfoProxyId.FriendList);
if (proxy == null)
return false;
var entry = proxy->GetEntryByName(characterName, worldId);
return entry != null;
}
catch
{
return false;
}
}
private static unsafe bool IsFreeCompanyMember(string characterName, ushort worldId)
{
try
{
var infoModule = InfoModule.Instance();
if (infoModule == null)
return false;
var proxy = (InfoProxyCommonList*)infoModule->GetInfoProxyById(InfoProxyId.FreeCompanyMember);
if (proxy == null)
return false;
var entry = proxy->GetEntryByName(characterName, worldId);
return entry != null;
}
catch
{
return false;
}
}
}
+1
View File
@@ -13,4 +13,5 @@ public sealed class PluginServices
[PluginService] public static IDalamudPluginInterface PluginInterface { get; private set; } = null!; [PluginService] public static IDalamudPluginInterface PluginInterface { get; private set; } = null!;
[PluginService] public static IToastGui ToastGui { get; private set; } = null!; [PluginService] public static IToastGui ToastGui { get; private set; } = null!;
[PluginService] public static ICommandManager CommandManager { get; private set; } = null!; [PluginService] public static ICommandManager CommandManager { get; private set; } = null!;
[PluginService] public static IAddonLifecycle AddonLifecycle { get; private set; } = null!;
} }
+23
View File
@@ -15,6 +15,9 @@ public sealed class ConfigWindow : Window
private bool _monitorTell; private bool _monitorTell;
private bool _enabled; private bool _enabled;
private bool _debug; private bool _debug;
private bool _autoAcceptEnabled;
private bool _autoAcceptFromFriends;
private bool _autoAcceptFromFreeCompany;
public ConfigWindow(HSRToolsConfiguration config) public ConfigWindow(HSRToolsConfiguration config)
: base("HSRTools Configuration", ImGuiWindowFlags.AlwaysAutoResize) : base("HSRTools Configuration", ImGuiWindowFlags.AlwaysAutoResize)
@@ -33,6 +36,9 @@ public sealed class ConfigWindow : Window
_monitorTell = _config.MonitorTell; _monitorTell = _config.MonitorTell;
_enabled = _config.Enabled; _enabled = _config.Enabled;
_debug = _config.Debug; _debug = _config.Debug;
_autoAcceptEnabled = _config.AutoAcceptEnabled;
_autoAcceptFromFriends = _config.AutoAcceptFromFriends;
_autoAcceptFromFreeCompany = _config.AutoAcceptFromFreeCompany;
} }
public override void Draw() public override void Draw()
@@ -47,6 +53,9 @@ public sealed class ConfigWindow : Window
_config.MonitorTell = true; _config.MonitorTell = true;
_config.Enabled = true; _config.Enabled = true;
_config.Debug = false; _config.Debug = false;
_config.AutoAcceptEnabled = false;
_config.AutoAcceptFromFriends = true;
_config.AutoAcceptFromFreeCompany = true;
SyncFromConfig(); SyncFromConfig();
} }
@@ -78,5 +87,19 @@ public sealed class ConfigWindow : Window
ImGui.Separator(); ImGui.Separator();
if (ImGui.Checkbox("Debug logging (diagnose cross-world invite)", ref _debug)) if (ImGui.Checkbox("Debug logging (diagnose cross-world invite)", ref _debug))
_config.Debug = _debug; _config.Debug = _debug;
ImGui.Separator();
ImGui.Text("Auto-accept party invites:");
ImGui.Indent();
if (ImGui.Checkbox("Auto-accept from friends and FC members", ref _autoAcceptEnabled))
_config.AutoAcceptEnabled = _autoAcceptEnabled;
if (_autoAcceptEnabled)
{
if (ImGui.Checkbox(" From friends", ref _autoAcceptFromFriends))
_config.AutoAcceptFromFriends = _autoAcceptFromFriends;
if (ImGui.Checkbox(" From Free Company members", ref _autoAcceptFromFreeCompany))
_config.AutoAcceptFromFreeCompany = _autoAcceptFromFreeCompany;
}
ImGui.Unindent();
} }
} }
+6 -1
View File
@@ -5,6 +5,7 @@ A [Dalamud](https://github.com/goatcorp/Dalamud) plugin for FFXIV (XIVLauncher)
## Features ## Features
- **Configurable trigger text** Set any word or phrase (e.g. `inv`, `invite`, `party`). When someone types it in a monitored channel, they are invited to your party. - **Configurable trigger text** Set any word or phrase (e.g. `inv`, `invite`, `party`). When someone types it in a monitored channel, they are invited to your party.
- **Auto-accept party invites** Optionally automatically accept party invites when the inviter is on your friend list or is a member of your Free Company.
- **Channel selection** Choose which channels to monitor: - **Channel selection** Choose which channels to monitor:
- Free Company - Free Company
- Link Shell (18) - Link Shell (18)
@@ -21,6 +22,10 @@ A [Dalamud](https://github.com/goatcorp/Dalamud) plugin for FFXIV (XIVLauncher)
For **tells**, the game provides the senders content ID and world, so invites work reliably. For **FC, LS, and CWLS**, the plugin uses the senders content ID when available (e.g. from the last message in CWLS), so cross-world invites work for CWLS and same-world for FC/LS. For **tells**, the game provides the senders content ID and world, so invites work reliably. For **FC, LS, and CWLS**, the plugin uses the senders content ID when available (e.g. from the last message in CWLS), so cross-world invites work for CWLS and same-world for FC/LS.
### Auto-accept party invites
When enabled, the plugin detects incoming party invites and automatically accepts them if the inviter is on your friend list or is a member of your Free Company. You can toggle friends and FC members independently. The friend list and FC roster must have been opened in-game at least once for the plugin to recognize members.
## Building ## Building
1. Open `HSRTools.sln` in Visual Studio or use the command line. 1. Open `HSRTools.sln` in Visual Studio or use the command line.
@@ -35,7 +40,7 @@ For **tells**, the game provides the senders content ID and world, so invites
## Configuration ## Configuration
- Open the plugin config from the Dalamud plugin list (right-click HSRTools → **Settings**). - Open the plugin config from the Dalamud plugin list (right-click HSRTools → **Settings**).
- Set the trigger text, enable/disable the plugin, choose channels, and set case sensitivity. - Set the trigger text, enable/disable the plugin, choose channels, set case sensitivity, and configure auto-accept (friends/FC).
- Settings are saved when you close the game or disable the plugin. - Settings are saved when you close the game or disable the plugin.
## Requirements ## Requirements