-
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathLibChatFilter.lua
More file actions
213 lines (173 loc) · 6.97 KB
/
LibChatFilter.lua
File metadata and controls
213 lines (173 loc) · 6.97 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
local major, minor = "LibChatFilter", 1;
---@class LibChatFilter
local LibChatFilter = LibStub:NewLibrary(major, minor);
if not LibChatFilter then
return;
end
------------
--- Context
---@alias ChatEditBox EditBox & ChatFrameEditBoxMixin
---@class LibChatFilter.Context
---@field originalText string Starting text from the chatframe edit box
---@field message string Current message to be sent
---@field historyText string Current message to be pushed to chat history
---@field chatType string ChatType
---@field target string ChatTarget (player name or channel ID)
---@field language string Selected chat language (furbolg, dragon)
---@field editBox ChatEditBox The active chatframe edit box
local CURRENT_CONTEXT;
local function CreateContext(message, chatType, target, language, editBox)
---@type LibChatFilter.Context
local context = {};
context.originalText = message;
context.message = message;
context.historyText = message;
context.chatType = chatType;
context.target = target;
context.language = language;
context.editBox = editBox;
CURRENT_CONTEXT = context;
return context;
end
local function CallMutator(func, message, context)
local success, result = pcall(func, message, context);
if not success then
CallErrorHandler("Error calling mutator: " .. tostring(result));
return message;
end
return result;
end
local function ApplyToContext(mutator, context)
local track = mutator.track;
if track == LibChatFilter.Track.SEND then
context.message = CallMutator(mutator.func, context.message, context) or context.message;
elseif track == LibChatFilter.Track.HISTORY then
context.historyText = CallMutator(mutator.func, context.historyText, context) or context.historyText;
elseif track == LibChatFilter.Track.BOTH then
context.message = CallMutator(mutator.func, context.message, context) or context.message;
context.historyText = CallMutator(mutator.func, context.historyText, context) or context.historyText;
end
end
------------
--- Event Handling
local function ApplyMutators()
local mutators = LibChatFilter.mutators;
local sorted = {
mutators[LibChatFilter.Stage.TRANSFORM],
mutators[LibChatFilter.Stage.EXCLUSIVE_TRANSFORM],
mutators[LibChatFilter.Stage.FINALIZE],
mutators[LibChatFilter.Stage.EXCLUSIVE_FINALIZE]
};
for _, mutatorList in ipairs(sorted) do
for _, mutator in ipairs(mutatorList) do
ApplyToContext(mutator, CURRENT_CONTEXT);
end
end
return false;
end
---@param editBox ChatEditBox
local function OnPreSendText(_, editBox)
local message = editBox:GetText();
if not message or message == "" then
return;
end
local chatType = ChatFrameUtil.GetActiveChatType();
chatType = chatType and chatType:upper() or "SAY";
local target = editBox:GetTellTarget() or editBox:GetChannelTarget();
if target == 0 then
target = nil;
end
local language = editBox.languageID;
local context = CreateContext(
message,
chatType,
target,
language,
editBox
);
ApplyMutators();
editBox:SetText(context.message);
end
local function OnSubstituteBeforeSend()
if not CURRENT_CONTEXT then
return;
end
local context = CURRENT_CONTEXT;
CURRENT_CONTEXT = nil;
context.editBox:SetText(context.historyText);
end
EventRegistry:RegisterCallback("ChatFrame.OnEditBoxPreSendText", OnPreSendText);
hooksecurefunc(ChatFrameUtil, "SubstituteChatMessageBeforeSend", OnSubstituteBeforeSend);
------------
--- API
---@enum LibChatFilter.Track
LibChatFilter.Track = {
SEND = 1,
HISTORY = 2,
BOTH = 3
};
---@enum LibChatFilter.Stage
LibChatFilter.Stage = {
TRANSFORM = 1, -- token resolve, emoji expand, owospeak
EXCLUSIVE_TRANSFORM = 2, -- very last transform to run, only one of these is allowed
FINALIZE = 3, -- chunking, chattery'ing
EXCLUSIVE_FINALIZE = 4, -- very last finalizer to run, only one of these is allowed
};
local stageInvert = tInvert(LibChatFilter.Stage);
---Mutator function type expected by the functions below
---@alias LibChatFilter.MutatorFunc fun(text: string, context: LibChatFilter.Context): string
---@class LibChatFilter.Mutator
---@field func LibChatFilter.MutatorFunc Message mutation function, returns the altered message
---@field stage LibChatFilter.Stage Mutator pipeline stage
---@field track LibChatFilter.Track Mutator target message track
---@type table<LibChatFilter.Stage, LibChatFilter.Mutator[]>
LibChatFilter.mutators = {};
for _, stage in pairs(LibChatFilter.Stage) do
LibChatFilter.mutators[stage] = {};
end
local function HasExclusive(stage)
return #LibChatFilter.mutators[stage] > 0;
end
---Registers a mutator
---@param mutator LibChatFilter.MutatorFunc Message mutation function
---@param stage? LibChatFilter.Stage Defaults to TRANSFORM
---@param track? LibChatFilter.Track Defaults to SEND
---@return boolean success Only false if trying to register an exclusive hook when one is already set
function LibChatFilter.RegisterMutator(mutator, stage, track)
assert(type(mutator) == "function", "Mutator must be a function");
stage = stage or LibChatFilter.Stage.TRANSFORM;
for _, exclusive in ipairs({LibChatFilter.Stage.EXCLUSIVE_TRANSFORM, LibChatFilter.Stage.EXCLUSIVE_FINALIZE}) do
if stage == exclusive and HasExclusive(exclusive) then
local current = LibChatFilter.mutators[exclusive][1];
local fmt = "Only one filter can be set for stage '%s'. %s and %s may not be compatible. (%s loaded first)";
local _, oldAddon = issecurevalue(current);
local _, newAddon = issecurevalue(mutator);
local msg = format(fmt, stageInvert[exclusive], oldAddon, newAddon, oldAddon);
CallErrorHandler(msg);
return false;
end
end
local mutatorObj = {
func = mutator,
stage = stage,
track = track or LibChatFilter.Track.SEND,
};
tinsert(LibChatFilter.mutators[stage], mutatorObj);
return true;
end
---Registers a mutator as a message transformer
---@param transform LibChatFilter.MutatorFunc Message mutation function
---@param track? LibChatFilter.Track Defaults to SEND
---@return boolean success Only false if trying to register an exclusive hook when one is already set
function LibChatFilter.RegisterTransform(transform, track)
track = track or LibChatFilter.Track.SEND;
return LibChatFilter.RegisterMutator(transform, LibChatFilter.Stage.TRANSFORM, track);
end
---Registers a mutator as a message finalizer
---@param mutator LibChatFilter.MutatorFunc Message mutation function
---@param track? LibChatFilter.Track Defaults to SEND
---@return boolean success Only false if trying to register an exclusive hook when one is already set
function LibChatFilter.RegisterFinalizer(mutator, track)
track = track or LibChatFilter.Track.SEND;
return LibChatFilter.RegisterMutator(mutator, LibChatFilter.Stage.FINALIZE, track);
end