- switching to new C_AddOns API changes

- cascaded message hooking for Retail v12 engine
- messages during encounters are sent unchanged, with a one-time notice
- ignore numbered chats that do not exist
- /r no longer sends to an empty whisper target if the last told target is unavailable
- fixed replacements in instance chats
- messages to unknown numbered channels are now discarded
- right-click on the minimap button
This commit is contained in:
2026-11-01 00:57:12 +01:00
parent 79873b0523
commit 01931510eb
9 changed files with 117 additions and 34 deletions
+3 -1
View File
@@ -7,7 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed
- handling of german umlauts (and other unicode characters)
- updated libraries and bumped version for all incarnations of WoW (Classic Era, Forever, Classic, Retail)
-
- adaptations to Retail v12 new Chat handling (Send API, encounter lockdown, instance chat)
- /r no longer sends to an empty whisper target if the last told target is unavailable
## Version 1.1.6 - 2023-01-18
### Changed
- bumped version for latest WotLK Classic patch
+4 -2
View File
@@ -20,6 +20,7 @@ local _G = _G
-- initialize addon
local Grichelde = LibStub("AceAddon-3.0"):NewAddon(AddonTable, AddonName, "AceConsole-3.0", "AceEvent-3.0", "AceHook-3.0")
local GetAddOnMetadata = (_G.C_AddOns and _G.C_AddOns.GetAddOnMetadata) or _G.GetAddOnMetadata
Grichelde.version = GetAddOnMetadata(AddonName, "Version")
Grichelde.build = GetAddOnMetadata(AddonName, "X-Build") or "Development"
--Grichelde.era = _G.WOW_PROJECT_ID == _G.WOW_PROJECT_CLASSIC
@@ -40,7 +41,7 @@ end
function Grichelde:OnEnable()
-- Hook in before message is sent to replace all character occurrences where replacements have been defined in the options
self:RawHook("SendChatMessage", true)
self:HookSendChatMessage()
self.options, self.dialog = self:SetupOptions()
self.ldb, self.icon = self:MinimapButton()
@@ -56,7 +57,8 @@ function Grichelde:OnEnable()
end
function Grichelde:OnDisable()
self:Unhook("SendChatMessage")
self:UnhookAll()
self.originalSend = nil
self:CloseOptions()
self:HideMinimapButton()
self:UnregisterChatCommand("grichelde")
+86 -9
View File
@@ -2,8 +2,8 @@
local _G = _G or getfenv()
local Grichelde = _G.Grichelde or {}
local IsAddOnLoaded, assert, nilOrEmpty, pairs, ipairs, spairs, tContains, tFilter, tInsert, tIsEmpty, tSize, find, sub, gsub, getNextCharUtf8, isUtf8MultiByte, getUtf8Sequence, isUpper, isLower, toUpper, toLower, bytes2Char, trim, length, lengthUtf8
= Grichelde.F.IsAddOnLoaded, Grichelde.F.assert, Grichelde.F.nilOrEmpty, Grichelde.F.pairs, Grichelde.F.ipairs, Grichelde.F.spairs, Grichelde.F.tContains, Grichelde.F.tFilter, Grichelde.F.tInsert, Grichelde.F.tIsEmpty, Grichelde.F.tSize,
local assert, nilOrEmpty, pairs, ipairs, spairs, tContains, tFilter, tInsert, tIsEmpty, tSize, find, sub, gsub, getNextCharUtf8, isUtf8MultiByte, getUtf8Sequence, isUpper, isLower, toUpper, toLower, bytes2Char, trim, length, lengthUtf8
= Grichelde.F.assert, Grichelde.F.nilOrEmpty, Grichelde.F.pairs, Grichelde.F.ipairs, Grichelde.F.spairs, Grichelde.F.tContains, Grichelde.F.tFilter, Grichelde.F.tInsert, Grichelde.F.tIsEmpty, Grichelde.F.tSize,
Grichelde.F.find, Grichelde.F.sub, Grichelde.F.gsub, Grichelde.F.getNextCharUtf8, Grichelde.F.isUtf8MultiByte, Grichelde.F.getUtf8Sequence, Grichelde.F.isUpper, Grichelde.F.isLower, Grichelde.F.toUpper, Grichelde.F.toLower, Grichelde.F.bytes2Char, Grichelde.F.trim, Grichelde.F.length, Grichelde.F.lengthUtf8
--- Splits a long text in longest possible chunks of <= 255 length, split at last available space
@@ -145,10 +145,10 @@ function Grichelde:SendChunkifiedChatMessage(message, ...)
self:DebugPrint("SendChunkifiedChatMessage : #chunks:", tSize(chunks))
for _, chunk in ipairs(chunks) do
self.hooks["SendChatMessage"](chunk, ...);
self:SendOriginal(chunk, ...)
end
else
self.hooks["SendChatMessage"](message, ...);
self:SendOriginal(message, ...)
end
end
@@ -886,8 +886,18 @@ function Grichelde:CheckAndExtractMessageTypeTarget(message)
-- special reply handling
if ("/r" == chatCmd) or ("/reply" == chatCmd) then
-- reuse last type and target if possible
local lastTold, lastToldType = ChatEdit_GetLastToldTarget()
-- TODO: add the Retail 12.x replacement of ChatEdit_GetLastToldTarget once confirmed in-game
local lastTold, lastToldType
if (_G.ChatEdit_GetLastToldTarget ~= nil) then
lastTold, lastToldType = _G.ChatEdit_GetLastToldTarget()
end
self:DebugPrint("CheckAndExtractMessageTypeTarget : lastTell, lastTellType =", lastTold, lastToldType)
if (nilOrEmpty(lastTold)) then
self:ErrorPrint(self.L.Error_InvalidWhisperTarget)
-- dont send text at all
return nil
end
return targetAndText, lastToldType or "WHISPER", lastTold
elseif ("/tt" == chatCmd) then
-- determine target from game world selection
@@ -931,7 +941,7 @@ function Grichelde:CheckAndExtractMessageTypeTarget(message)
local _, _, channelNumber = find(chatCmd, "^/(%d+)")
if (channelNumber ~= nil) then
local channelId = GetChannelName(channelNumber)
if (channelId ~= nil) then
if (channelId ~= nil) and (channelId > 0) then
return targetAndText, "CHANNEL", channelId
end
end
@@ -950,8 +960,50 @@ function Grichelde:CheckAndExtractMessageTypeTarget(message)
end
end
--- Hooks C_ChatInfo.SendChatMessage (Retail) and the global SendChatMessage (Classic, deprecated alias on Retail).
--- The original C_ChatInfo function is preferred for sending.
function Grichelde:HookSendChatMessage()
local chatInfo = _G.C_ChatInfo
if (chatInfo ~= nil) and (chatInfo.SendChatMessage ~= nil) then
self:RawHook(chatInfo, "SendChatMessage", "SendChatMessage", true)
self.originalSend = self.hooks[chatInfo]["SendChatMessage"]
end
if (_G.SendChatMessage ~= nil) then
self:RawHook("SendChatMessage", true)
self.originalSend = self.originalSend or self.hooks["SendChatMessage"]
end
end
--- Sends a message with the original (unhooked) send function.
function Grichelde:SendOriginal(...)
return self.originalSend(...)
end
--- Midnight blocks chat modifications by addons during instance encounters.
--- Prints a notice once per lockdown.
-- @return boolean
function Grichelde:IsChatLockedDown()
local chatInfo = _G.C_ChatInfo
if (chatInfo == nil) or (chatInfo.InChatMessagingLockdown == nil) then
return false
end
if (chatInfo.InChatMessagingLockdown()) then
if (not self.lockdownNoticeShown) then
self.lockdownNoticeShown = true
self:PrefixedPrint(self.L.Chat_Lockdown_Notice)
end
return true
end
self.lockdownNoticeShown = false
return false
end
function Grichelde:CleanseMessage(message)
local text = message or ""
local IsAddOnLoaded = (_G.C_AddOns and _G.C_AddOns.IsAddOnLoaded) or _G.IsAddOnLoaded
if (IsAddOnLoaded("Misspelled")) then
self:DebugPrint("Misspelled detected: cleansing message")
text = _G.Misspelled:RemoveHighlighting(message)
@@ -971,7 +1023,12 @@ function Grichelde:SendChatMessageOverride(message, ...)
local msg, type, chan = self:CheckAndExtractMessageTypeTarget(cleasendText)
if (msg ~= nil) then
if (not self:IsChatLockedDown()) then
msg = self:ReplaceText(msg)
else
-- do not modify message in any way during encounters
-- msg = msg .. self.L.Chat_Lockdown_Suffix
end
if (type ~= nil) then
self:SendChunkifiedChatMessage(msg, type, fallbackLang, chan, ...)
@@ -989,19 +1046,39 @@ end
-- @param language string
-- @param channel string
function Grichelde:SendChatMessage(message, type, ...)
if (self.isReplacing) then
-- re-entered through the other hooked send function, text is already replaced
self:DebugPrint("SendChatMessage : already replacing")
self:SendOriginal(message, type, ...)
return
end
self.isReplacing = true
-- always reset the guard, even if replacing fails
local ok, err = pcall(self.ReplaceAndSendChatMessage, self, message, type, ...)
self.isReplacing = false
if (not ok) then
error(err, 0)
end
end
function Grichelde:ReplaceAndSendChatMessage(message, type, ...)
if (not self.db.profile.enabled) then
self:DebugPrint("SendChatMessage : disabled")
self.hooks["SendChatMessage"](message, type, ...);
self:SendOriginal(message, type, ...)
elseif (nilOrEmpty(message)) then
self:DebugPrint("SendChatMessage : no text")
self.hooks["SendChatMessage"](message, type, ...);
self:SendOriginal(message, type, ...)
elseif (self:IsChatLockedDown()) then
self:DebugPrint("SendChatMessage : chat lockdown")
self:SendOriginal(message, type, ...)
else
local cleasendText = self:CleanseMessage(message)
if (self:CheckReplacementAllowed(cleasendText, type)) then
cleasendText = self:ReplaceText(cleasendText)
self:SendChunkifiedChatMessage(cleasendText, type, ...)
else
self.hooks["SendChatMessage"](message, type, ...);
self:SendOriginal(message, type, ...)
end
end
end
+1 -2
View File
@@ -114,7 +114,7 @@ Grichelde.BLIZZ_TYPE_TO_OPTIONS = {
["OFFICER"] = "officer",
["RAID"] = "raid",
["RAID_WARNING"] = "raidWarning",
["INSTANCE"] = "instance",
["INSTANCE_CHAT"] = "instance",
["BATTLEGROUND"] = "battleground",
["WHISPER"] = "whisper",
}
@@ -486,7 +486,6 @@ end
-- faster function lookups by mapping to local refs
Grichelde.F = {
IsAddOnLoaded = _G.IsAddOnLoaded,
assert = _G.assert,
type = _G.type,
print = _G.print,
+1 -1
View File
@@ -48,7 +48,7 @@ function Grichelde:MinimapButton()
icon = "Interface\\Icons\\Spell_Holy_Silence",
--icon = ([[Interface\Addons\%s\%s]]):format(self.name, self.name),
OnClick = clickHandler,
OnRightClick = function() self:ShowMappings() end,
OnRightClick = function() self:ToogleMappings() end,
OnTooltipShow = updateTooltip,
iconR = darkened,
iconG = darkened,
+4 -5
View File
@@ -415,9 +415,7 @@ function Grichelde:SetupOptions()
-- Adding options to blizzard frame
LibStub("AceConfig-3.0"):RegisterOptionsTable(self.name, options)
local dialog = LibStub("AceConfigDialog-3.0")
local _, categoryID = dialog:AddToBlizOptions(self.name, self.L.AddonName)
self.optionsCategoryID = categoryID
dialog:AddToBlizOptions(self.name, self.L.AddonName)
return options, dialog
end
@@ -466,8 +464,9 @@ function Grichelde:RefreshProfiles(event, _, profileName)
end
function Grichelde:ToggleOptions()
self:DebugPrint("ToggleOptions : options open: ", not not self.dialog.OpenFrames[self.name])
if (self.dialog ~= nil) and (self.dialog.OpenFrames[self.name] ~= nil) then
local isOpen = (self.dialog ~= nil) and (self.dialog.OpenFrames[self.name] ~= nil)
self:DebugPrint("ToggleOptions : options open: ", isOpen)
if (isOpen) then
self:CloseOptions()
else
self:OpenOptions()
+3 -3
View File
@@ -171,7 +171,7 @@ end
--- Print UI options to chat frame
function Grichelde:PrintOptions()
self:PrefixedPrint(cPrefix(self.L.Debug_Options))
self:LogPrint(-1, function(...)
self:LogPrint(function(...)
print(cPrefix(self.L.AddonName) .. ":", self:Format(...))
end, self.options.args.replacements.args)
end
@@ -179,7 +179,7 @@ end
--- Print current DB profile
function Grichelde:PrintProfile()
self:PrefixedPrint(cPrefix(self.L.Debug_Profile))
self:LogPrint(-1, function(...)
self:LogPrint(function(...)
print(cPrefix(self.L.AddonName) .. ":", self:Format(...))
end, self.db.profile)
end
@@ -187,7 +187,7 @@ end
--- Print DB replacements to chat frame
function Grichelde:PrintMappings()
self:PrefixedPrint(cPrefix(self.L.Debug_Mappings))
self:LogPrint(-1, function(...)
self:LogPrint(function(...)
print(cPrefix(self.L.AddonName) .. ":", self:Format(...))
end, self.db.profile.replacements)
end
+2
View File
@@ -33,6 +33,8 @@ L.Error_InvalidCommand = "Ung\195\188ltiger Befehl"
L.Error_InvalidChannel = "Ung\195\188ltiger Kanal"
L.Error_InvalidWhisperTarget = "Ung\195\188ltiger Fl\195\188sterziel"
L.Error_UnsupportedChannel = "Nicht unterst\195\188tzter Kanal"
L.Chat_Lockdown_Notice = "Der Chat ist w\195\164hrend dieses Kampfes durch das Spiel gesperrt, Grichelde hält sich zurück."
L.Chat_Lockdown_Suffix = "(ignoriert von Grichelde)"
-- profiles
L.Profiles_Available = "Verf\195\188gbare Profile:"
+2
View File
@@ -33,6 +33,8 @@ L.Error_InvalidCommand = "Invalid command"
L.Error_InvalidChannel = "Invalid channel"
L.Error_InvalidWhisperTarget = "Invalid whisper target"
L.Error_UnsupportedChannel = "Unsupported channel"
L.Chat_Lockdown_Notice = "Chat is locked by the game during this encounter. Grichelde will remain passive."
L.Chat_Lockdown_Suffix = "(ignored by Grichelde)"
-- profiles
L.Profiles_Available = "Available profiles:"