- 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 ### Fixed
- handling of german umlauts (and other unicode characters) - handling of german umlauts (and other unicode characters)
- updated libraries and bumped version for all incarnations of WoW (Classic Era, Forever, Classic, Retail) - 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 ## Version 1.1.6 - 2023-01-18
### Changed ### Changed
- bumped version for latest WotLK Classic patch - bumped version for latest WotLK Classic patch
+4 -2
View File
@@ -20,6 +20,7 @@ local _G = _G
-- initialize addon -- initialize addon
local Grichelde = LibStub("AceAddon-3.0"):NewAddon(AddonTable, AddonName, "AceConsole-3.0", "AceEvent-3.0", "AceHook-3.0") 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.version = GetAddOnMetadata(AddonName, "Version")
Grichelde.build = GetAddOnMetadata(AddonName, "X-Build") or "Development" Grichelde.build = GetAddOnMetadata(AddonName, "X-Build") or "Development"
--Grichelde.era = _G.WOW_PROJECT_ID == _G.WOW_PROJECT_CLASSIC --Grichelde.era = _G.WOW_PROJECT_ID == _G.WOW_PROJECT_CLASSIC
@@ -40,7 +41,7 @@ end
function Grichelde:OnEnable() function Grichelde:OnEnable()
-- Hook in before message is sent to replace all character occurrences where replacements have been defined in the options -- 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.options, self.dialog = self:SetupOptions()
self.ldb, self.icon = self:MinimapButton() self.ldb, self.icon = self:MinimapButton()
@@ -56,7 +57,8 @@ function Grichelde:OnEnable()
end end
function Grichelde:OnDisable() function Grichelde:OnDisable()
self:Unhook("SendChatMessage") self:UnhookAll()
self.originalSend = nil
self:CloseOptions() self:CloseOptions()
self:HideMinimapButton() self:HideMinimapButton()
self:UnregisterChatCommand("grichelde") self:UnregisterChatCommand("grichelde")
+87 -10
View File
@@ -2,8 +2,8 @@
local _G = _G or getfenv() local _G = _G or getfenv()
local Grichelde = _G.Grichelde or {} 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 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.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, = 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 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 --- 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)) self:DebugPrint("SendChunkifiedChatMessage : #chunks:", tSize(chunks))
for _, chunk in ipairs(chunks) do for _, chunk in ipairs(chunks) do
self.hooks["SendChatMessage"](chunk, ...); self:SendOriginal(chunk, ...)
end end
else else
self.hooks["SendChatMessage"](message, ...); self:SendOriginal(message, ...)
end end
end end
@@ -886,8 +886,18 @@ function Grichelde:CheckAndExtractMessageTypeTarget(message)
-- special reply handling -- special reply handling
if ("/r" == chatCmd) or ("/reply" == chatCmd) then if ("/r" == chatCmd) or ("/reply" == chatCmd) then
-- reuse last type and target if possible -- 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) 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 return targetAndText, lastToldType or "WHISPER", lastTold
elseif ("/tt" == chatCmd) then elseif ("/tt" == chatCmd) then
-- determine target from game world selection -- determine target from game world selection
@@ -931,7 +941,7 @@ function Grichelde:CheckAndExtractMessageTypeTarget(message)
local _, _, channelNumber = find(chatCmd, "^/(%d+)") local _, _, channelNumber = find(chatCmd, "^/(%d+)")
if (channelNumber ~= nil) then if (channelNumber ~= nil) then
local channelId = GetChannelName(channelNumber) local channelId = GetChannelName(channelNumber)
if (channelId ~= nil) then if (channelId ~= nil) and (channelId > 0) then
return targetAndText, "CHANNEL", channelId return targetAndText, "CHANNEL", channelId
end end
end end
@@ -950,8 +960,50 @@ function Grichelde:CheckAndExtractMessageTypeTarget(message)
end end
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) function Grichelde:CleanseMessage(message)
local text = message or "" local text = message or ""
local IsAddOnLoaded = (_G.C_AddOns and _G.C_AddOns.IsAddOnLoaded) or _G.IsAddOnLoaded
if (IsAddOnLoaded("Misspelled")) then if (IsAddOnLoaded("Misspelled")) then
self:DebugPrint("Misspelled detected: cleansing message") self:DebugPrint("Misspelled detected: cleansing message")
text = _G.Misspelled:RemoveHighlighting(message) text = _G.Misspelled:RemoveHighlighting(message)
@@ -971,7 +1023,12 @@ function Grichelde:SendChatMessageOverride(message, ...)
local msg, type, chan = self:CheckAndExtractMessageTypeTarget(cleasendText) local msg, type, chan = self:CheckAndExtractMessageTypeTarget(cleasendText)
if (msg ~= nil) then if (msg ~= nil) then
msg = self:ReplaceText(msg) 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 if (type ~= nil) then
self:SendChunkifiedChatMessage(msg, type, fallbackLang, chan, ...) self:SendChunkifiedChatMessage(msg, type, fallbackLang, chan, ...)
@@ -989,19 +1046,39 @@ end
-- @param language string -- @param language string
-- @param channel string -- @param channel string
function Grichelde:SendChatMessage(message, type, ...) 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 if (not self.db.profile.enabled) then
self:DebugPrint("SendChatMessage : disabled") self:DebugPrint("SendChatMessage : disabled")
self.hooks["SendChatMessage"](message, type, ...); self:SendOriginal(message, type, ...)
elseif (nilOrEmpty(message)) then elseif (nilOrEmpty(message)) then
self:DebugPrint("SendChatMessage : no text") 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 else
local cleasendText = self:CleanseMessage(message) local cleasendText = self:CleanseMessage(message)
if (self:CheckReplacementAllowed(cleasendText, type)) then if (self:CheckReplacementAllowed(cleasendText, type)) then
cleasendText = self:ReplaceText(cleasendText) cleasendText = self:ReplaceText(cleasendText)
self:SendChunkifiedChatMessage(cleasendText, type, ...) self:SendChunkifiedChatMessage(cleasendText, type, ...)
else else
self.hooks["SendChatMessage"](message, type, ...); self:SendOriginal(message, type, ...)
end end
end end
end end
+11 -12
View File
@@ -106,17 +106,17 @@ Grichelde.SUPPORTED_CHAT_COMMANDS = {
} }
Grichelde.BLIZZ_TYPE_TO_OPTIONS = { Grichelde.BLIZZ_TYPE_TO_OPTIONS = {
["SAY"] = "say", ["SAY"] = "say",
["EMOTE"] = "emote", ["EMOTE"] = "emote",
["YELL"] = "yell", ["YELL"] = "yell",
["PARTY"] = "party", ["PARTY"] = "party",
["GUILD"] = "guild", ["GUILD"] = "guild",
["OFFICER"] = "officer", ["OFFICER"] = "officer",
["RAID"] = "raid", ["RAID"] = "raid",
["RAID_WARNING"] = "raidWarning", ["RAID_WARNING"] = "raidWarning",
["INSTANCE"] = "instance", ["INSTANCE_CHAT"] = "instance",
["BATTLEGROUND"] = "battleground", ["BATTLEGROUND"] = "battleground",
["WHISPER"] = "whisper", ["WHISPER"] = "whisper",
} }
-- do not replace these patterns -- do not replace these patterns
@@ -486,7 +486,6 @@ end
-- faster function lookups by mapping to local refs -- faster function lookups by mapping to local refs
Grichelde.F = { Grichelde.F = {
IsAddOnLoaded = _G.IsAddOnLoaded,
assert = _G.assert, assert = _G.assert,
type = _G.type, type = _G.type,
print = _G.print, print = _G.print,
+1 -1
View File
@@ -48,7 +48,7 @@ function Grichelde:MinimapButton()
icon = "Interface\\Icons\\Spell_Holy_Silence", icon = "Interface\\Icons\\Spell_Holy_Silence",
--icon = ([[Interface\Addons\%s\%s]]):format(self.name, self.name), --icon = ([[Interface\Addons\%s\%s]]):format(self.name, self.name),
OnClick = clickHandler, OnClick = clickHandler,
OnRightClick = function() self:ShowMappings() end, OnRightClick = function() self:ToogleMappings() end,
OnTooltipShow = updateTooltip, OnTooltipShow = updateTooltip,
iconR = darkened, iconR = darkened,
iconG = darkened, iconG = darkened,
+4 -5
View File
@@ -415,9 +415,7 @@ function Grichelde:SetupOptions()
-- Adding options to blizzard frame -- Adding options to blizzard frame
LibStub("AceConfig-3.0"):RegisterOptionsTable(self.name, options) LibStub("AceConfig-3.0"):RegisterOptionsTable(self.name, options)
local dialog = LibStub("AceConfigDialog-3.0") local dialog = LibStub("AceConfigDialog-3.0")
local _, categoryID = dialog:AddToBlizOptions(self.name, self.L.AddonName) dialog:AddToBlizOptions(self.name, self.L.AddonName)
self.optionsCategoryID = categoryID
return options, dialog return options, dialog
end end
@@ -466,8 +464,9 @@ function Grichelde:RefreshProfiles(event, _, profileName)
end end
function Grichelde:ToggleOptions() function Grichelde:ToggleOptions()
self:DebugPrint("ToggleOptions : options open: ", not not self.dialog.OpenFrames[self.name]) local isOpen = (self.dialog ~= nil) and (self.dialog.OpenFrames[self.name] ~= nil)
if (self.dialog ~= nil) and (self.dialog.OpenFrames[self.name] ~= nil) then self:DebugPrint("ToggleOptions : options open: ", isOpen)
if (isOpen) then
self:CloseOptions() self:CloseOptions()
else else
self:OpenOptions() self:OpenOptions()
+3 -3
View File
@@ -171,7 +171,7 @@ end
--- Print UI options to chat frame --- Print UI options to chat frame
function Grichelde:PrintOptions() function Grichelde:PrintOptions()
self:PrefixedPrint(cPrefix(self.L.Debug_Options)) self:PrefixedPrint(cPrefix(self.L.Debug_Options))
self:LogPrint(-1, function(...) self:LogPrint(function(...)
print(cPrefix(self.L.AddonName) .. ":", self:Format(...)) print(cPrefix(self.L.AddonName) .. ":", self:Format(...))
end, self.options.args.replacements.args) end, self.options.args.replacements.args)
end end
@@ -179,7 +179,7 @@ end
--- Print current DB profile --- Print current DB profile
function Grichelde:PrintProfile() function Grichelde:PrintProfile()
self:PrefixedPrint(cPrefix(self.L.Debug_Profile)) self:PrefixedPrint(cPrefix(self.L.Debug_Profile))
self:LogPrint(-1, function(...) self:LogPrint(function(...)
print(cPrefix(self.L.AddonName) .. ":", self:Format(...)) print(cPrefix(self.L.AddonName) .. ":", self:Format(...))
end, self.db.profile) end, self.db.profile)
end end
@@ -187,7 +187,7 @@ end
--- Print DB replacements to chat frame --- Print DB replacements to chat frame
function Grichelde:PrintMappings() function Grichelde:PrintMappings()
self:PrefixedPrint(cPrefix(self.L.Debug_Mappings)) self:PrefixedPrint(cPrefix(self.L.Debug_Mappings))
self:LogPrint(-1, function(...) self:LogPrint(function(...)
print(cPrefix(self.L.AddonName) .. ":", self:Format(...)) print(cPrefix(self.L.AddonName) .. ":", self:Format(...))
end, self.db.profile.replacements) end, self.db.profile.replacements)
end end
+2
View File
@@ -33,6 +33,8 @@ L.Error_InvalidCommand = "Ung\195\188ltiger Befehl"
L.Error_InvalidChannel = "Ung\195\188ltiger Kanal" L.Error_InvalidChannel = "Ung\195\188ltiger Kanal"
L.Error_InvalidWhisperTarget = "Ung\195\188ltiger Fl\195\188sterziel" L.Error_InvalidWhisperTarget = "Ung\195\188ltiger Fl\195\188sterziel"
L.Error_UnsupportedChannel = "Nicht unterst\195\188tzter Kanal" 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 -- profiles
L.Profiles_Available = "Verf\195\188gbare Profile:" 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_InvalidChannel = "Invalid channel"
L.Error_InvalidWhisperTarget = "Invalid whisper target" L.Error_InvalidWhisperTarget = "Invalid whisper target"
L.Error_UnsupportedChannel = "Unsupported channel" 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 -- profiles
L.Profiles_Available = "Available profiles:" L.Profiles_Available = "Available profiles:"