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/Grichelde.lua

366 lines
14 KiB
Lua

--[[---------------------------------------------------------------------------
Grichelde
Copyright 2020 Teilzeit-Jedi <tj@teilzeit-jedi.de>
based on Misspelled developed by Nathan Pieper - nrpieper (@) gmail (dot) com
This code freely distributed for your use in any GPL compliant project.
See conditions in the LICENSE file attached.
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
--------------------------------------------------------------------------]] --
local AddonName, AddonTable = ...
local Grichelde = LibStub("AceAddon-3.0"):NewAddon("Grichelde", "AceEvent-3.0", "AceHook-3.0")
Grichelde.version = GetAddOnMetadata(AddonName, "Version")
Grichelde.build = GetAddOnMetadata(AddonName, "X-Build") or "UNKNOWN"
local L = LibStub("AceLocale-3.0"):GetLocale("Grichelde", true)
local Grichelde_Debug = false
-- faster function lookups by mapping to local refs
local string_find = string.find
local string_gsub = string.gsub
local string_len = string.len
local string_rep = string.rep
local string_sub = string.sub
local strtrim = strtrim
local strmatch = strmatch
local tostring = tostring
local tInsert = table.insert
local tContains = tContains
local pairs = pairs
local ipairs = ipairs
local Grichelde_Hooks = {}
--local Grichelde_ChatTypes = { "SAY", "EMOTE", "YELL", "PARTY", "GUILD", "OFFICER", "RAID", "RAID_WARNING", "INSTANCE_CHAT", "BATTLEGROUND", "WHISPER" }
local Grichelde_ChatTypes = { "SAY", "EMOTE", "YELL", "PARTY", "GUILD" }
local Grichelde_ChatCommands = { "/s", "/e", "/me", "/y", "/p", "/pl", "/g", "/o", "/raid", "/rl", "/rw", "/i", "bg", "/w", "/r", "/tt" }
-- do not replace these patterns
local Grichelde_IgnorePatterns = {
"|[Cc]%x%x%x%x%x%x%x%x.-|r", -- colored items (or links)
"|H.-|h", -- item links (http://www.wowwiki.com/ItemLink)
"|T.-|t", -- textures
"|n", -- newline
"{rt[1-8]}", -- rumbered raid target icons
"{Star}", -- named raid target icon 1
"{Circle}", -- named raid target icon 2
"{Coin}", -- named raid target icon 2
"{Diamond}", -- named raid target icon 3
"{Triangle}", -- named raid target icon 4
"{Moon}", -- named raid target icon 5
"{Square}", -- named raid target icon 6
"{Cross}", -- named raid target icon 7
"{X}", -- named raid target icon 7
"{Skull}" -- named raid target icon 8
}
function Grichelde:OnInitialize()
-- Build Interface Options window
--self:CreateInterfaceOptions()
-- Watch for WIM and Prat to Load, then integrate
self:RegisterEvent("ADDON_LOADED", "HookIntoForOtherChatAddons")
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)
if (Misspelled) then
print("Misspelled detected, Grichelde will have any messsage being cleansed")
end
-- tell the world we are listening
print(L.AddonLoaded)
end
function Grichelde:OnDisable()
self:Unhook("SendChatMessage")
end
--- @param event string
--- @param addonName string
function Grichelde:HookIntoForOtherChatAddons(event, addonName)
if event == "ADDON_LOADED" then
if addonName == "WIM" then
WIM.RegisterWidgetTrigger("msg_box", "whisper,chat,w2w", "OnEnterPressed", Grichelde.EditBox_OnEnterPressed)
-- If available use the WIM API
if (WIM.RegisterPreSendFilterText) then -- avoid error if WIM not up to date.
WIM.RegisterPreSendFilterText(function(text)
return Grichelde:CheckAndReplace(text)
end)
else
-- WIM sends its chat messages via the API ChatThrottleLib, which itself hooks the default SendChatMessage api
-- many times before Grichelde will. ChatThrottleLib might potentially load before Grichelde, so we just hook
-- into ChatThrottleLib to be on the safe side.
if (ChatThrottleLib) then
Grichelde_Hooks["ChatThrottleLib"] = ChatThrottleLib.SendChatMessage
function ChatThrottleLib:SendChatMessage(prio, prefix, text, ...)
Grichelde:DebugPrint("ChatThrottleLib:SendChatMessage : Hook called")
local replacedText = Grichelde:CheckAndReplace(text)
return Grichelde_Hooks["ChatThrottleLib"](ChatThrottleLib, prio, prefix, replacedText, ...)
end
end
end
end
end
end
--- Before af 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, language, channel, ...)
local replacedText = self:CheckAndReplace(message, type)
self:DebugPrint("SendChatMessage : replacedText: " .. replacedText)
-- Send text in chunks if length exceeds 255 bytes after replacement
local chunks = self:SplitText(replacedText)
self:DebugPrint("SendChatMessage : #chunks: " .. #chunks)
for _, chunk in ipairs(chunks) do
self.hooks["SendChatMessage"](chunk, type, language, channel, ...);
end
end
function Grichelde:CheckAndReplace(message, type)
local text = message
if (Misspelled) then
self:DebugPrint("Misspelled detected: cleansing message")
text = Misspelled:RemoveHighlighting(text)
end
text = strtrim(text)
if (self:CheckReplacement(text, type)) then
text = self:ReplaceText(text)
end
return text
end
function Grichelde:CheckReplacement(text, type)
-- todo: globally disabled?
-- check type
if (not tContains(Grichelde_ChatTypes, type)) then
self:DebugPrint("CheckReplacement : skip channel type")
return false
end
-- don't replace slash commands except chat related commands
if string_sub(text, 1, 1) == "/" then
local firstWord, _ = self:SplitOnFirstMatch(text)
if (firstWord == nil or not tContains(Grichelde_ChatCommands, firstWord)) then
self:DebugPrint("CheckReplacement : ignore slash command")
return false
end
end
-- in any other case
return true
end
--- Checks if the text starts with a preversable ignore pattern, such as itemLinks, textures or raid target icons
--- and returns the end location of the match, or 0 if no pattern was found
--- @param text string
--- @return number
function Grichelde:CheckForPreversableText(text)
self:DebugPrint("CheckForPreversableText : text is " .. text)
-- Calling find on ever pattern might be inefficient but its way less code.
for _, pattern in ipairs(Grichelde_IgnorePatterns) do
local pos1, pos2 = string_find(text, pattern)
if pos1 == 1 and pos2 ~= nil then
self:DebugPrint("CheckForPreversableText : Found ignore pattern " .. pattern .. " at (" .. pos1 .. "," .. pos2 .. ")")
return pos2
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
--- @return string
function Grichelde:ReplaceText(text)
local finalText = ""
local newText = text
-- don't replace non-chat related slash commands
local firstWord, line = self:SplitOnFirstMatch(text)
if (firstWord ~= nil and tContains(Grichelde_ChatCommands, firstWord)) then
self:DebugPrint("ReplaceText : Found slash command " .. (firstWord + "") )
-- skip chat slash command
finalText = finalText .. firstWord .. ' '
newText = line
end
local current = 1
local lastStart = 1
while current <= string_len(newText) do
local currentChar = string_sub(newText, current, current)
self:DebugPrint("current/char : " .. current .. "," .. currentChar)
if currentChar ~= '|' and currentChar ~= '{' then
current = current + 1
else
-- lookahead-check for itemLinks, textures and raid target icons
local textAhead = string_sub(newText, current)
local posEnd = self:CheckForPreversableText(textAhead)
if posEnd > 0 then
self:DebugPrint("ReplaceText : Found an ignore pattern")
local textBehind = string_sub(newText, lastStart, current - 1)
local replacement = self:ReplaceCharacters(textBehind)
local preservedText = string_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
-- cleanup remaining text to the end
local remainingText = string_sub(newText, lastStart)
local replacement = self:ReplaceCharacters(remainingText)
finalText = finalText .. replacement
self:DebugPrint("ReplaceText : replaced \"" .. text .. "\"")
self:DebugPrint("ReplaceText : with \"" .. finalText .. "\"")
return finalText
end
--- Replaces all character occurrences for which replacements have been defined in the options
--- @param text string
--- @return string
function Grichelde:ReplaceCharacters(text)
-- todo: read from options
-- todo: case (in)sensitivity
local replacement = text
replacement = string_gsub(replacement, "s", "ch")
replacement = string_gsub(replacement, "S", "Ch")
replacement = string_gsub(replacement, "t", "k")
replacement = string_gsub(replacement, "T", "K")
self:DebugPrint("ReplaceCharacters : replaced \"" .. text .. "\" with \"" .. replacement .. "\"")
return replacement
end
--- 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 = string_len(splitText)
while textSize > 255 do
local chunk = string_sub(splitText, 1, 255)
local remaining = ""
-- special case: if space is the start of the next chunk, don't split this chunk
if string_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 .. string_sub(splitText, 256)
textSize = string_len(splitText)
end
-- pickup remaining text < 255
self:DebugPrint("SplitText : last chunk: " .. splitText)
tInsert(chunks, splitText)
return chunks
end
-- split first word of a text line
function Grichelde:SplitOnFirstMatch(text, start)
self:DebugPrint("SplitOnFirstMatch : text: " .. text .. ", start: " .. self:EmptyIfNil(start))
local pos = 1
if start ~= nil then pos = start end
local left, right = strmatch(text, "^.- .+", pos)
self:DebugPrint("SplitOnFirstMatch : left: " .. self:EmptyIfNil(left) .. ", right: " .. self:EmptyIfNil(right))
return left, right
end
function Grichelde:SplitOnLastMatch(text, start)
self:DebugPrint("SplitOnLastMatch : text: " .. text .. ", start: " .. self:EmptyIfNil(start))
local pos = 1
if start ~= nil then pos = start end
local left, right = strmatch(text, ".+ .-$", pos)
self:DebugPrint("SplitOnLastMatch : left: " .. self:EmptyIfNil(left) .. ", right: " .. self:EmptyIfNil(right))
return left, right
end
function Grichelde:DebugPrint(message)
if (Grichelde_Debug) then
print(GRAY_FONT_COLOR_CODE .. "Grichelde:" .. FONT_COLOR_CODE_CLOSE .. " " .. message)
end
end
function Grichelde:EmptyIfNil(value)
if value == nil then return "" end
return tostring(value)
end
function Grichelde:tprint(t, indent, done)
-- in case we run it standalone
local Note = Note or print
-- local Tell = Tell or io.write
-- show strings differently to distinguish them from numbers
local function show(val)
if type(val) == "string" then
return '"' .. val .. '"'
else
return tostring(val)
end
end
-- entry point here
done = done or {}
indent = indent or 0
for key, value in pairs(t) do
print(string_rep(" ", indent)) -- indent it
if type(value) == "table" and not done[value] then
done[value] = true
Note(show(key), ":");
self:tprint(value, indent + 2, done)
else
print(show(key), "=")
print(show(value))
end
end
end