Add privacy filter with channel whitelist (GDPR Art. 25)
Introduce an opt-out channel whitelist so the database only persists messages from channels the user explicitly wants to keep. Default profile follows GDPR data minimization: own conversations only (Tells, Party, FC, Linkshells, Cross-World Linkshells, Alliance, ExtraChat). Public chat (Say/Shout/Yell), Novice Network, NPC dialogue and system logs are dropped by default. The filter sits inside MessageStore.UpsertMessage so any current or future write path is covered uniformly. Configuration provides an IsAllowedForStorage(ChatType) helper plus a "persist unknown channels" failsafe (default off) for ChatTypes added by future patches. A new Privacy settings tab exposes the whitelist as grouped checkboxes with three preset buttons (Privacy-First, Clear all, Select all). Configuration version bumps from 6 to 7; existing users are migrated to the Privacy-First defaults on first load and notified once via the Dalamud notification manager. Also includes a small .env.example and gitignore hygiene for local development setup.
This commit is contained in:
@@ -40,6 +40,7 @@ public sealed class SettingsWindow : Window
|
||||
new Fonts(Mutable),
|
||||
new ChatColours(Plugin, Mutable),
|
||||
new Tabs(Plugin, Mutable),
|
||||
new SettingsTabs.Privacy(Mutable),
|
||||
new Database(Plugin, Mutable),
|
||||
new Webinterface(Plugin, Mutable),
|
||||
new Miscellaneous(Mutable),
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
using ChatTwo.Code;
|
||||
using ChatTwo.Privacy;
|
||||
using ChatTwo.Util;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
|
||||
namespace ChatTwo.Ui.SettingsTabs;
|
||||
|
||||
internal sealed class Privacy : ISettingsTab
|
||||
{
|
||||
private Configuration Mutable { get; }
|
||||
|
||||
public string Name => "Privacy###tabs-privacy";
|
||||
|
||||
internal Privacy(Configuration mutable)
|
||||
{
|
||||
Mutable = mutable;
|
||||
}
|
||||
|
||||
// Channels grouped for the UI. Order = display order.
|
||||
private static readonly (string Heading, ChatType[] Types)[] Groups =
|
||||
[
|
||||
("Direct Messages", [ChatType.TellIncoming, ChatType.TellOutgoing]),
|
||||
("Party & Alliance", [ChatType.Party, ChatType.CrossParty, ChatType.Alliance, ChatType.PvpTeam]),
|
||||
("Free Company", [ChatType.FreeCompany, ChatType.FreeCompanyAnnouncement, ChatType.FreeCompanyLoginLogout]),
|
||||
("Linkshells", [
|
||||
ChatType.Linkshell1, ChatType.Linkshell2, ChatType.Linkshell3, ChatType.Linkshell4,
|
||||
ChatType.Linkshell5, ChatType.Linkshell6, ChatType.Linkshell7, ChatType.Linkshell8,
|
||||
]),
|
||||
("Cross-World Linkshells", [
|
||||
ChatType.CrossLinkshell1, ChatType.CrossLinkshell2, ChatType.CrossLinkshell3, ChatType.CrossLinkshell4,
|
||||
ChatType.CrossLinkshell5, ChatType.CrossLinkshell6, ChatType.CrossLinkshell7, ChatType.CrossLinkshell8,
|
||||
]),
|
||||
("ExtraChat (Encrypted)", [
|
||||
ChatType.ExtraChatLinkshell1, ChatType.ExtraChatLinkshell2, ChatType.ExtraChatLinkshell3, ChatType.ExtraChatLinkshell4,
|
||||
ChatType.ExtraChatLinkshell5, ChatType.ExtraChatLinkshell6, ChatType.ExtraChatLinkshell7, ChatType.ExtraChatLinkshell8,
|
||||
]),
|
||||
("Public Chat (third-party data)", [ChatType.Say, ChatType.Shout, ChatType.Yell, ChatType.NoviceNetwork, ChatType.CustomEmote, ChatType.StandardEmote]),
|
||||
("System & Game Logs", [
|
||||
ChatType.System, ChatType.Notice, ChatType.Urgent, ChatType.Echo,
|
||||
ChatType.NpcDialogue, ChatType.NpcAnnouncement,
|
||||
ChatType.LootNotice, ChatType.LootRoll, ChatType.RetainerSale,
|
||||
ChatType.Crafting, ChatType.Gathering, ChatType.Sign, ChatType.RandomNumber,
|
||||
]),
|
||||
];
|
||||
|
||||
public void Draw(bool changed)
|
||||
{
|
||||
ImGuiUtil.OptionCheckbox(
|
||||
ref Mutable.PrivacyFilterEnabled,
|
||||
"Enable privacy filter",
|
||||
"When enabled, only messages from whitelisted channels are persisted to the database. " +
|
||||
"Disabling restores upstream ChatTwo behavior (everything except battle messages is stored).");
|
||||
|
||||
ImGui.Spacing();
|
||||
ImGui.Separator();
|
||||
ImGui.Spacing();
|
||||
|
||||
using (ImRaii.Disabled(!Mutable.PrivacyFilterEnabled))
|
||||
{
|
||||
ImGuiUtil.HelpText(
|
||||
"Pick which channels are stored in the local database. " +
|
||||
"Privacy-First default: only your own conversations. " +
|
||||
"Use the buttons below to apply a preset.");
|
||||
|
||||
ImGui.Spacing();
|
||||
|
||||
if (ImGui.Button("Privacy-First (recommended)"))
|
||||
Mutable.PrivacyPersistChannels = [..PrivacyDefaults.PrivacyFirstWhitelist];
|
||||
|
||||
ImGui.SameLine();
|
||||
if (ImGui.Button("Clear all"))
|
||||
Mutable.PrivacyPersistChannels.Clear();
|
||||
|
||||
ImGui.SameLine();
|
||||
if (ImGui.Button("Select all"))
|
||||
foreach (var group in Groups)
|
||||
foreach (var t in group.Types)
|
||||
Mutable.PrivacyPersistChannels.Add(t);
|
||||
|
||||
ImGui.Spacing();
|
||||
ImGui.Separator();
|
||||
ImGui.Spacing();
|
||||
|
||||
foreach (var (heading, types) in Groups)
|
||||
{
|
||||
using var tree = ImRaii.TreeNode(heading);
|
||||
if (!tree.Success)
|
||||
continue;
|
||||
|
||||
using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false))
|
||||
{
|
||||
foreach (var type in types)
|
||||
{
|
||||
var enabled = Mutable.PrivacyPersistChannels.Contains(type);
|
||||
var label = type.ToString();
|
||||
if (ImGui.Checkbox($"{label}##privacy-{(int)type}", ref enabled))
|
||||
{
|
||||
if (enabled)
|
||||
Mutable.PrivacyPersistChannels.Add(type);
|
||||
else
|
||||
Mutable.PrivacyPersistChannels.Remove(type);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.Spacing();
|
||||
ImGui.Separator();
|
||||
ImGui.Spacing();
|
||||
|
||||
ImGuiUtil.OptionCheckbox(
|
||||
ref Mutable.PrivacyPersistUnknownChannels,
|
||||
"Persist unknown channel types",
|
||||
"Failsafe for ChatTypes added by future FFXIV patches that this plugin does not yet know about. " +
|
||||
"Default OFF (Privacy-First). Turn ON if you want a complete log including future channels.");
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user