You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Grichelde/GricheldeChat.lua

815 lines
33 KiB
Lua

-- import addon read namespace from global env
local _G = _G
local Grichelde = _G.Grichelde or {}
local IsAddOnLoaded, assert, nilOrEmpty, pairs, ipairs, spairs, tContains, tFilter, tInsert, tConcat, tSize, tIsEmpty, find, sub, gsub, gmatch, getNextCharUtf8, isUpper, isLower, toUpper, toLower, capitalize, 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.tConcat, Grichelde.F.tSize, Grichelde.F.tIsEmpty,
Grichelde.F.find, Grichelde.F.sub, Grichelde.F.gsub, Grichelde.F.gmatch, Grichelde.F.getNextCharUtf8, Grichelde.F.isUpper, Grichelde.F.isLower, Grichelde.F.toUpper, Grichelde.F.toLower, Grichelde.F.capitalize, 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
-- @param text string
-- @return table
function Grichelde:SplitText(text)
local chunks = {}
local splitText = text
local textSize = length(splitText or "")
while (textSize > 255) do
local chunk = sub(splitText, 1, 255)
local remaining = ""
-- special case: if space is the start of the next chunk, don't split this chunk
if (sub(splitText, 256, 256) ~= ' ') then
-- split at last space, don't assign directly as nil might be returned
local left, right = self:SplitOnLastMatch(chunk)
if (left ~= nil) then
chunk = left
end
if (right ~= nil) then
remaining = right
end
end
self:DebugPrint("SplitText : chunk:", chunk)
tInsert(chunks, chunk)
splitText = remaining .. sub(splitText, 256)
textSize = length(splitText)
end
-- pickup remaining text < 255
self:DebugPrint("SplitText : last chunk:", splitText)
tInsert(chunks, splitText)
return chunks
end
--- Send text in chunks if length exceeds 255 bytes after replacement.
function Grichelde:SendChunkifiedChatMessage(message, ...)
if (length(message) > 255) then
local chunks = self:SplitText(message)
self:DebugPrint("SendChunkifiedChatMessage : #chunks:", #chunks)
for _, chunk in ipairs(chunks) do
self.hooks["SendChatMessage"](chunk, ...);
end
else
self.hooks["SendChatMessage"](message, ...);
end
end
function Grichelde:ReplaceCharacters(text, replName, replTable, consolidate, replacedTexts)
local function convertToCaseInsensitivePattern(pattern)
local ciPattern = ""
local ignored = {'^', '$', '(', ')', '.'}
local quantifiers = {'*', '+', '-', '?'}
local pos = 1
local p, patRest = getNextCharUtf8(pattern)
while (p ~= nil) do
if (tContains(ignored, p) or tContains(quantifiers, p)) then
-- ignore
ciPattern = ciPattern .. p
elseif (p == "%") then
-- ignore capture references
p, patRest = getNextCharUtf8(patRest)
if (p ~= nil) then
ciPattern = ciPattern .. "%" .. p
end
elseif (p == "[") then
-- skip pattern sets
ciPattern = ciPattern .. "["
p, patRest = getNextCharUtf8(patRest)
while ((p ~= nil) and (p ~= "]")) do
ciPattern = ciPattern .. Grichelde.F.toUpper(p) .. Grichelde.F.toLower(p)
p, patRest = getNextCharUtf8(patRest)
end
ciPattern = ciPattern .. "]"
else
ciPattern = ciPattern .. "[" .. Grichelde.F.toUpper(p) .. Grichelde.F.toLower(p) .. "]"
end
p, patRest = getNextCharUtf8(patRest)
end
self:TracePrint("convertToCaseInsensitivePattern : %s => %s", pattern, ciPattern)
return ciPattern
end
local function replaceCaptures(text, replaceText, captures)
local replText = replaceText
self:TracePrint("replaceCaptures : text: %s, #captures: %d", text, #captures)
if (#captures > 0) then
for i, cap in ipairs(captures) do
--self:TracePrint("replaceCaptures : i: %d, cap: %s", i, cap)
if (cap == nil) then
break
else
local oldRepl = replText
replText = gsub(oldRepl, "%%" .. i, cap)
self:TracePrint("ReplaceCaptures : substitute capture %s: %s => %s", oldRepl, cap, replText)
end
end
else
self:TracePrint("ReplaceCaptures : no captures")
end
return replText
end
--- this is more complicated to get it right than it looks like
local function applyCase(replRest, lastCase, nextCase)
local repl = ""
if (lastCase == nil) then
-- lastCase was unknown, always take over nextCase
if (nextCase == nil) then
repl = repl .. replRest
elseif (nextCase == true) then
--repl = repl .. toUpper(replRest)
repl = repl .. replRest
else
--repl = repl .. toLower(replRest)
repl = repl .. replRest
end
elseif (lastCase == true) then
-- lastCase was UPPER
if (nextCase == nil) then
repl = repl .. toUpper(replRest)
elseif (nextCase == true) then
repl = repl .. toUpper(replRest)
else
repl = repl .. replRest
end
else
-- lastCase was lower
if (nextCase == nil) then
repl = repl .. replRest
elseif (nextCase == true) then
--repl = repl .. toLower(replRest)
repl = repl .. replRest
else
--repl = repl .. toLower(replRest)
repl = repl .. replRest
end
end
return repl
end
local pos = 1
local result = text
local findText = result
local searchText = replTable.searchText
local replaceText = replTable.replaceText
local matchWhen = replTable.matchWhen
local doExactCase = replTable.exactCase
local doConsolidate = replTable.consolidate
local doStopOnMatch = replTable.stopOnMatch
local stopOnMatch = false
if doExactCase then
self:DebugPrint("ReplaceCharacters : \"%s => %s\" (exact case)", searchText, replaceText)
else
self:DebugPrint("ReplaceCharacters : \"%s => %s\" (ignoreCase)", searchText, replaceText)
searchText = convertToCaseInsensitivePattern(searchText)
end
local pos1, pos2, cap1, cap2, cap3, cap4, cap5, cap6, cap7, cap8, cap9 = find(findText, searchText, pos)
self:TracePrint("ReplaceCharacters : pos1: %d, pos2: %d", pos1, pos2)
self:TracePrint("ReplaceCharacters : cap1: %s, cap2: %s, cap3: %s, cap4: %s, cap5: %s, cap6: %s, cap7: %s, cap8: %s, cap9: %s", cap1, cap2, cap3, cap4, cap5, cap6, cap7, cap8, cap9)
while (pos1 ~= nil) and (pos2 ~= nil) and (pos1 <= pos2) do
-- continue from that position later
pos = pos2 + 1
if doStopOnMatch then
stopOnMatch = true
end
local pre = sub(result, 1, pos1 - 1)
local match = sub(result, pos1, pos2)
local post = sub(result, pos2 + 1)
local wordStart = sub(pre, -1, -1)
local wordEnd = sub(post, 1, 1)
-- additional checks for word boundaries
local doesMatchWhen = false
if (matchWhen == 2) then
-- replace always
doesMatchWhen = true
elseif (matchWhen == 3) then
-- replace only as a whole word
if (nilOrEmpty(wordStart) or (find(wordStart,"[%s%p]") ~= nil)) and (nilOrEmpty(wordEnd) or (find(wordEnd, "[%s%p]") ~= nil)) then
doesMatchWhen = true
end
elseif (matchWhen == 4) then
-- replace only at start
if (nilOrEmpty(wordStart) or (find(wordStart,"[%s%p]") ~= nil)) then
doesMatchWhen = true
end
elseif (matchWhen == 5) then
-- replace only at end
if (nilOrEmpty(wordEnd) or (find(wordEnd, "[%s%p]") ~= nil)) then
doesMatchWhen = true
end
elseif (matchWhen == 6) then
-- replace only at start or end
if (nilOrEmpty(wordStart) or (find(wordStart, "[%s%p]") ~= nil) or nilOrEmpty(wordEnd) or (find(wordEnd, "[%s%p]") ~= nil)) then
doesMatchWhen = true
end
elseif (matchWhen == 7) then
-- replace only in the middle
if (not nilOrEmpty(wordStart) and (find(wordStart, "[%w]") ~= nil) and not nilOrEmpty(wordEnd) and (find(wordEnd, "[%w]") ~= nil)) then
doesMatchWhen = true
end
end
if (doesMatchWhen) then
-- replace substitutions
self:TracePrint("ReplaceCharacters : pre: %s, match: %s, post: %s, repl: %s", pre, match, post, replaceText)
local caps = { cap1, cap2, cap3, cap4, cap5, cap6, cap7, cap8, cap9 }
local replText = replaceCaptures(match, replaceText, caps)
tInsert(replacedTexts[replName], replText)
self:DebugPrint("ReplaceCharacters : pre: %s, match: %s, post: %s, repl: %s", pre, match, post, replText)
if (not doExactCase) then
local repl, lastCase = "", nil
local c, matchRest = getNextCharUtf8(match)
local r, replRest = "", replText
while (c ~= nil) do
r, replRest = getNextCharUtf8(replRest)
r = r or ""
replRest = replRest or ""
self:TracePrint("ReplaceCharacters : c: %s, rest: %s", c, matchRest)
self:TracePrint("ReplaceCharacters : r: %s, rest: %s", r, replRest)
if (isUpper(c)) then
-- UPPER-CASE letter
self:TracePrint("ReplaceCharacters : characters: %s => %s", c, toUpper(r))
lastCase = true
repl = repl .. toUpper(r)
elseif (isLower(c)) then
-- lower_case letter
self:TracePrint("ReplaceCharacters : characters: %s => %s", c, toLower(r))
lastCase = false
repl = repl .. toLower(r)
else
-- no letter
self:TracePrint("ReplaceCharacters : characters: %s => %s", c, r)
lastCase = nil
repl = repl .. r
end
c, matchRest = getNextCharUtf8(matchRest)
end
self:TracePrint("ReplaceCharacters : remaining length %d", length(replRest))
if (length(replRest) > 0) then
local nextLetter, _ = getNextCharUtf8(post)
nextLetter = nextLetter or ""
local nextCase = nil
if (isUpper(nextLetter)) then
nextCase = true
elseif (isLower(nextLetter)) then
nextCase = false
end
self:TracePrint("ReplaceCharacters : rest: %s, lastCase: %s, nextLetter: %s, nextCase: %s", replRest, lastCase, nextLetter, nextCase)
repl = repl .. applyCase(replRest, lastCase, nextCase)
end
replText = repl
end
-- actual replacement
result = pre .. replText .. post
self:TracePrint("ReplaceCharacters : result: %s", result)
-- remember positions for consolidate
if doConsolidate then
tInsert(consolidate[replName], pos1)
end
-- update previous consolidate markers
local diff = lengthUtf8(replText) - lengthUtf8(match)
self:TracePrint("ReplaceCharacters : diff = %d - %d", lengthUtf8(replText), lengthUtf8(match))
for key, posList in pairs(consolidate) do
if (key ~= replName) then
for i, pc in ipairs(posList) do
if (pos1 < pc) then
consolidate[key][i] = consolidate[key][i] + diff
end
end
end
end
-- replacement text can lengthen or shorten the result
-- after replacement text and lowerText can have different sizes
pos = pos + diff
else
self:DebugPrint("ReplaceCharacters : does not match when: %d", matchWhen)
end
findText = result
-- update values for next iteration
pos1, pos2, cap1, cap2, cap3, cap4, cap5, cap6, cap7, cap8, cap9 = find(findText, searchText, pos)
end
if (text ~= result) then
self:DebugPrint("ReplaceCharacters : replaced \"%s\" with \"%s\" %s", text, result, stopOnMatch and "(stop)" or "")
end
if (matchWhen > 1) and doConsolidate then
self:DebugPrint("ReplaceCharacters : consolidate[" .. replName .. "] is:")
self:DebugPrint(consolidate[replName])
end
return result, stopOnMatch
end
--- Replaces all character occurrences for which replacements have been defined in the options
-- @param text string
-- @param replacements table of mappings
-- @return string
function Grichelde:ReplaceAndConsolidate(text, replacements)
local replacements = replacements or self.db.profile.replacements or {}
self:TracePrint("ReplaceAndConsolidate : replacements")
self:TracePrint(replacements)
local result = text
local consolidate = {}
local replacedTexts = {}
local stopOnMatch = nil
-- replacements are done first
for replName, replTable in spairs(replacements) do
local searchText = replTable.searchText
if (not nilOrEmpty(searchText) and (replTable.matchWhen > 1)) then
consolidate[replName] = {}
replacedTexts[replName] = {}
local stop = false
result, stop = self:ReplaceCharacters(result, replName, replTable, consolidate, replacedTexts)
if stop then
stopOnMatch = replName
self:DebugPrint("ReplaceAndConsolidate : Stopping followup replacements after %s", replName)
break
end
else
-- empty mapping or never matched
self:DebugPrint("ReplaceAndConsolidate : Skip replacement %s", replName)
end
end
-- consolidation is done last
for replName, replTable in spairs(replacements) do
local before = result
local search = replTable.searchText
if (not nilOrEmpty(search) and (replTable.matchWhen > 1)) then
local lowerResult = toLower(result)
local offset = 0
if (replTable.consolidate) then
self:DebugPrint("consolidate[" .. replName .. "] is:")
self:DebugPrint(consolidate[replName])
self:DebugPrint("replacedTexts[" .. replName .. "] is:")
self:DebugPrint(replacedTexts[replName])
for i, pos1 in spairs(consolidate[replName]) do
local replText = replacedTexts[replName][i]
self:DebugPrint("ReplaceAndConsolidate : consolidating \"%s => %s\"", search, replText)
local pos2 = pos1 + length(replText) - 1
self:TracePrint("ReplaceAndConsolidate : pos1: %d, pos2: %d", pos1, pos2)
local match = toLower(replText)
local next = sub(lowerResult, pos2 + 1, pos2 + 1 + pos2 - pos1)
self:TracePrint("ReplaceAndConsolidate : match: %s, next: %s", match, next)
local _, p2 = find(next, "^" .. match)
self:TracePrint("ReplaceAndConsolidate : p2: %d", p2)
if (p2 ~= nil) then
result = sub(result, 1, pos2 + offset) .. sub(result, pos2 + 1 + p2 + offset)
-- consolidation will shorten the resulting text
offset = offset + length(result) - length(lowerResult)
end
self:DebugPrint("ReplaceAndConsolidate : result: %s", result)
end
end
if (before ~= result) then
self:DebugPrint("ReplaceAndConsolidate : consolidate \"%s\" with \"%s\"", before, result)
end
else
self:DebugPrint("ReplaceAndConsolidate : Skip consolidation for %s", replName)
end
if (stopOnMatch ~= nil) and (stopOnMatch == replName) then
break
end
end
self:DebugPrint("ReplaceAndConsolidate : final text:", result)
return result
end
--- Checks if the text starts with a preversable ignore pattern, such as itemLinks, textures, raid target icons,
--- emotes, ooc or %-substitutons and returns the end location of the match, or 0 if no pattern was found
-- @param text string
-- @param currentChar string(1) current character (first one) of the text, given for performance reasons
-- @param previousChar string(1) previous character of the text, otherwise unreachable
-- @param preserveEmotes boolean ignore replacements for emotes, for testing purposes
-- @return number
function Grichelde:CheckForPreversableText(text, currentChar, previousChar, replaceEmotes)
self:DebugPrint("CheckForPreversableText : text:", text)
local replaceEmotes = replaceEmotes or self.db.profile.channels.emote or false
-- Calling find on ever pattern might be inefficient but its way less code than marching over every character
if (currentChar == "|" ) then
for _, pattern in ipairs(Grichelde.IGNORE_PATTERNS.LINKS) do
local pos1, pos2 = find(text, "^" .. pattern)
if (pos1 == 1) and (pos2 ~= nil) then
self:DebugPrint("CheckForPreversableText : Found link or texture pattern \"%s\" at (%d, %d)", pattern, pos1, pos2)
return pos2
end
end
end
-- emote detection
if (currentChar == "*" or currentChar == "<") then
for _, pattern in ipairs(Grichelde.IGNORE_PATTERNS.EMOTES) do
local pos1, pos2 = find(text, "^" .. pattern)
if (pos1 == 1) and (pos2 ~= nil) then
local emote = sub(text, pos1, pos2)
if (replaceEmotes) then
self:DebugPrint("CheckForPreversableText : Found emote \"%s\" but preserved it", emote)
return pos2
else
self:DebugPrint("CheckForPreversableText : Found emote \"%s\" at (%d, %d)", emote, pos1, pos2)
end
end
end
end
local lowerText = toLower(text)
-- %-substitutions
if (currentChar == "%") then
for _, pattern in ipairs(Grichelde.IGNORE_PATTERNS.SUBSTITUTES) do
local pos1, pos2 = find(lowerText, "^" .. pattern)
if (pos1 == 1) and (pos2 ~= nil) then
self:DebugPrint("CheckForPreversableText : Found substitute pattern \"%s\" at (%d, %d)", pattern, pos1, pos2)
return pos2
end
end
end
-- raid target markers
if (currentChar == "{") then
-- rt1-9
local pattern = Grichelde.IGNORE_PATTERNS.RAID_TARGETS[1]
local pos1, pos2 = find(lowerText, "^" .. pattern)
if (pos1 == 1) and (pos2 ~= nil) then
self:DebugPrint("CheckForPreversableText : Found raid target marker \"%s\" at (%d, %d)", pattern, pos1, pos2)
return pos2
end
for _, localizedRT in ipairs(Grichelde.IGNORE_PATTERNS.LOCALIZED_RAID_TARGETS) do
local translation = toLower(self.L["IgnorePattern_" .. localizedRT])
local localizedPattern = "^{" .. translation .. "}"
self:TracePrint("CheckForPreversableText : localizedPattern:", localizedPattern)
local pos1, pos2 = find(lowerText, localizedPattern)
if (pos1 == 1) and (pos2 ~= nil) then
self:DebugPrint("CheckForPreversableText : Found localized raid target marker \"%s\" at (%d, %d)", localizedPattern, pos1, pos2)
return pos2
end
end
end
-- ooc bracket detection
if (currentChar == "(") then
for _, pattern in ipairs(Grichelde.IGNORE_PATTERNS.OOC_BRACKETS) do
local pos1, pos2 = find(lowerText, "^" .. pattern)
if (pos1 == 1) and (pos2 ~= nil) then
self:DebugPrint("CheckForPreversableText : Found ooc pattern \"%s\" at (%d, %d)", pattern, pos1, pos2)
return pos2
end
end
end
-- ooc without brackets: remaing text is treated as ooc completely!
if (currentChar == "o") then
local pattern = Grichelde.IGNORE_PATTERNS.OOC_NO_BRACKETS[1]
if ((previousChar == nil) or (find(previousChar, "%s") ~= nil)) and (find(lowerText, pattern) ~= nil) then
self:DebugPrint("CheckForPreversableText : ooc for remaing text")
return length(text)
end
end
self:DebugPrint("CheckForPreversableText : no ignore pattern found")
return 0
end
--- Replaces all character occurrences for which replacements have been defined in the options,
--- while preserving any itemLinks or textures. (http://www.wowwiki.com/ItemLink)
-- @param text string the text to apply the mappings on
-- @param preserveEmotes boolean ignore replacements for emotes, for testing purposes
-- @return string
function Grichelde:ReplaceText(text, replacements, replaceEmotes)
local lookAheads = { '|', '*', '<', '%', '{', '(', 'o' }
local newText = text
local preserveEmotes = replaceEmotes or self.db.profile.channels.emote or false
local replacements = replacements or self.db.profile.replacements or {}
local finalText = ""
local currentChar, previousChar
local current = 1
local lastStart = 1
-- no UTF-8 support required here, as the positions are used
while current <= length(newText) do
previousChar = currentChar
currentChar = sub(newText, current, current)
self:TracePrint("ReplaceText : current/char : %s,%s", current, currentChar)
-- as there is not OR in Luas pattern matching, search for all of the exclude patterns after another is
-- cumbersome and inefficient -> look for each char consecutively if it matches the starting pattern only
-- and if if matches do full pattern matching
if (not tContains(lookAheads, currentChar)) then
current = current + 1
else
-- lookahead-check for all preservable patterns (itemLinks, textures, emotes, ooc, etc.)
local textAhead = sub(newText, current)
local posEnd = self:CheckForPreversableText(textAhead, currentChar, previousChar, preserveEmotes)
if (posEnd > 0) then
self:DebugPrint("ReplaceText : Found an ignore pattern")
-- split text and continue after preserved text
local textBefore = sub(newText, lastStart, current - 1)
local replacement = self:ReplaceAndConsolidate(textBefore, replacements)
local preservedText = sub(textAhead, 1, posEnd)
finalText = finalText .. replacement .. preservedText
current = current + posEnd
lastStart = current
self:DebugPrint("ReplaceText : restarting at", lastStart)
else
-- no corresponding end was found to start pattern, continue loop with next char
current = current + 1
end
end
end
-- catchup remaining text to the end
local remainingText = sub(newText, lastStart)
local replacement = self:ReplaceAndConsolidate(remainingText, replacements)
finalText = finalText .. replacement
self:DebugPrint("ReplaceText : replaced \"%s\"", text)
self:DebugPrint("ReplaceText : with \"%s\"", finalText)
return finalText
end
function Grichelde:IsOneBigEmote(text)
local firstWord, _ = self:SplitOnFirstMatch(text)
assert(firstWord ~= nil, "firstWord is never nil")
-- emote detection
local isEmote = false
-- scheme *emote*
if (sub(firstWord, 1, 1) == "<") then
-- search for emote end
local _, emoteEnd = find(text, "%>", 2)
isEmote = (emoteEnd == length(text))
end
if (not isEmote and (sub(firstWord, 1, 1) == "*")) then
-- search for emote end
local _, emoteEnd = find(text, "%*", 2)
isEmote = (emoteEnd == length(text))
end
-- scheme **emote**
if (not isEmote and (sub(firstWord, 1, 2) == "**")) then
-- search for emote end
local _, emoteEnd = find(text, "%*%*", 3)
isEmote = (emoteEnd == length(text))
end
-- the whole text is one big emote
return isEmote
end
function Grichelde:ConvertBlizzTypeToOption(channel)
local option = tFilter(self.BLIZZ_TYPE_TO_OPTIONS,
function(_, k, _) return channel == k end)
if (not tIsEmpty(option)) then
self:DebugPrint("ConvertBlizzTypeToOption : convert %s to %s", channel, option[1])
return option[1]
else
return nil
end
end
--- Checks if a message can be replaced according to configuration.
-- @return boolean
function Grichelde:CheckReplacementAllowed(text, channel)
self:DebugPrint("CheckReplacementAllowed : text:", text)
-- skip if disabled
if (not self.db.profile.enabled) then
self:DebugPrint("CheckReplacementAllowed : disabled")
return false
end
-- skip if no text
if (nilOrEmpty(text)) then
return false
end
-- skip if wrong channel
local chan = self:ConvertBlizzTypeToOption(channel)
local allowedChannels = tFilter(self.db.profile.channels,
function(_, k, v) return k == chan and v == true end,
function(_, k, _) return k end
)
self:DebugPrint("CheckReplacementAllowed : allowed channels:")
self:DebugPrint(allowedChannels)
if (tIsEmpty(allowedChannels)) then
self:DebugPrint("CheckReplacementAllowed : skip channel type:", chan)
return false
end
local firstWord, _ = self:SplitOnFirstMatch(text)
assert(firstWord ~= nil, "firstWord is never nil")
-- don't replace slash commands
if (sub(firstWord, 1, 1) == "/") then
self:DebugPrint("CheckReplacementAllowed : skip other slash commands:", firstWord)
return false
end
-- emote detection
if (self:IsOneBigEmote(text)) then
self:DebugPrint("CheckReplacementAllowed : one big emote")
return self.db.profile.channels.emote
end
-- in any other case, treat as ordinary text or emote
return true
end
--- Checks if the text from the Grichelde slash command can be replaced and if so
--- returns the replacable text, the chat type and target (player or channel) from the message text.
--- self is used the the override slash command: "/gri /emote text to replace".
-- @param message (string) the whole message
-- @return message (string) stripped message
-- @return type (string) chat type
-- @return channel (string|number) channel number for whispers
function Grichelde:CheckAndExtractMessageTypeTarget(message)
self:DebugPrint("CheckAndExtractMessageTypeTarget : text:", message)
-- skip if no further text
if (nilOrEmpty(message)) then
-- dont send text at all
return nil
end
-- first word should be a chat command
if (sub(message, 1, 1) == "/") then
-- extract chat command
local chatCmd, targetAndText = self:SplitOnFirstMatch(message)
assert(chatCmd ~= nil, "chatCmd is never nil")
local type = tFilter(self.SUPPORTED_CHAT_COMMANDS,
function(_, k, _) return chatCmd == k end
)
assert(#type < 2)
if (not tIsEmpty(type)) then
self:DebugPrint("CheckAndExtractMessageTypeTarget : determined type: %s", type[1])
-- valid /chattype
if (type[1] == "WHISPER") then
-- special reply handling
if ("/r" == chatCmd) or ("/reply" == chatCmd) then
-- reuse last type and target if possible
local lastTold, lastToldType = ChatEdit_GetLastToldTarget()
self:DebugPrint("CheckAndExtractMessageTypeTarget : lastTell, lastTellType =", lastTold, lastToldType)
return targetAndText, lastToldType or "WHISPER", lastTold
elseif ("/tt" == chatCmd) then
-- determine target from game world selection
if (not UnitExists("target") or not UnitIsPlayer("target")) then
self:ErrorPrint(self.L.Error_InvalidWhisperTarget)
-- dont send text at all
return nil
end
local target = UnitName("target");
if (target == nil) then
self:ErrorPrint(self.L.Error_InvalidWhisperTarget)
-- dont send text at all
return nil
end
-- eventually we found our target
self:DebugPrint("CheckAndExtractMessageTypeTarget : target:", target)
return targetAndText, "WHISPER", target
else
-- determine target from text
local target, text = self:SplitOnFirstMatch(targetAndText)
if (target == nil) then
self:ErrorPrint(self.L.Error_InvalidWhisperTarget)
-- dont send text at all
return nil
end
-- eventually we found our target
self:DebugPrint("CheckAndExtractMessageTypeTarget : determined target:", target)
return text, "WHISPER", target
end
else
-- all other chat types
return targetAndText, type[1], nil
end
else
self:DebugPrint("CheckAndExtractMessageTypeTarget : not a standard channel: %s", chatCmd)
-- if not a valid chat command, try as a numbered channel
local _, _, channelNumber = find(chatCmd, "^/(%d+)")
if (channelNumber ~= nil) then
local channelId = GetChannelName(channelNumber)
if (channelId ~= nil) then
return targetAndText, "CHANNEL", channelId
end
end
-- ignore any other slash commands
self:ErrorPrint(self.L.Error_InvalidChannel)
-- dont send text at all
return nil
end
elseif self:IsOneBigEmote(message) then
self:DebugPrint("CheckAndExtractMessageTypeTarget : determined EMOTE type")
return message, "EMOTE", nil
else
-- in any other case, treat as ordinary text, assume default type and channel
return message
end
end
function Grichelde:CleanseMessage(message)
local text = message or ""
if (IsAddOnLoaded("Misspelled")) then
self:DebugPrint("Misspelled detected: cleansing message")
text = _G.Misspelled:RemoveHighlighting(message)
end
return trim(text)
end
--- Always replaces the text accoording to the configuration, even if activation or channel was disabled.
--- self is used the the override slash command: "/gri /emote text to replace".
--- NOTE: type and channel (in case of whispers) are determined from the message text.
-- @param message string
-- @param type string
function Grichelde:SendChatMessageOverride(message, ...)
local cleasendText = self:CleanseMessage(message)
local fallbackType, fallbackLang = DEFAULT_CHAT_FRAME.editBox.chatType or "SAY", DEFAULT_CHAT_FRAME.editBox.languageID
local msg, type, chan = self:CheckAndExtractMessageTypeTarget(cleasendText)
if (msg ~= nil) then
msg = self:ReplaceText(msg)
if (type ~= nil) then
self:SendChunkifiedChatMessage(msg, type, fallbackLang, chan, ...)
else
self:SendChunkifiedChatMessage(msg, fallbackType, fallbackLang, chan, ...)
end
else
-- suppress invalid messages/channels/targets
end
end
--- Before a chat message is sent, check if replacement is required and replace the text accordingly.
-- @param message string
-- @param type string
-- @param language string
-- @param channel string
function Grichelde:SendChatMessage(message, type, ...)
if (not self.db.profile.enabled) then
self:DebugPrint("SendChatMessage : disabled")
self.hooks["SendChatMessage"](message, type, ...);
elseif (nilOrEmpty(message)) then
self:DebugPrint("SendChatMessage : no text")
self.hooks["SendChatMessage"](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, ...);
end
end
end