ReserveIgnore is a quality-of-life World of Warcraft addon (3.3.5 / Wrath Classic client) designed to automatically ignore players who spam “reserve” or loot-claim messages in public channels.
It’s specifically built to deal with LFG / Trade / Yell spam where raid leaders advertise runs with loot reserved, hard-reserved items, or “only X can roll” rules.
Instead of you manually right-clicking and ignoring people, the addon does it instantly and automatically.
The Core Problem It Solves
In Wrath (especially private servers):
LFG / Trade / Yell are flooded with:
“ICC 25nm need all, DBW res”
“TOC 25, cloak reserved”
“Need heals, shard res”
These messages drown out legitimate group ads
Blizzard provides no built-in filtering for this
ReserveIgnore turns those messages into silence.
How It Works (High Level)
Listens to all public chat channels
Trade
LookingForGroup
Yell
Say
Emotes
General
(Optionally whispers in some versions)
Scans each message for trigger words
Examples:
res
rez
reserved
hr
hardres
only X can roll
Detection is punctuation-aware
res., res,, res! still trigger
Separator-tolerant
r.e.s, r-e-s, re/s still trigger
Decides whether the message is spam or legitimate
Uses an exception list to prevent false positives
Example:
“respec now” → not loot reserve
“rezz me” → not loot reserve
“ress healer” → not loot reserve
Automatically ignores the player
Player is added to your ignore list
Their future messages are completely hidden
Key Features (Detailed)
1. Trigger-Based Auto Ignore
A customizable list of trigger words
Designed specifically around loot reservation spam
Extremely aggressive detection (by design)
2. Exception System (Critical)
Prevents ignoring people who are talking about:
Resurrection (rez, rezz, ress)
Respecs
Legitimate gameplay terms
In v1.8m:
Exceptions only suppress ambiguous triggers
Clear loot-reserve phrases still ignore instantly
This is what keeps the addon usable without breaking normal chat.
3. Whisper Delay Protection
When someone you ignored whispers you, the addon:
Delays or blocks processing
Prevents spam or harassment right after ignoring
Stops “WHY IGNORE??” rage whispers
4. Auto-Cleanup of Ignore List
WoW has a hard limit on ignored players
ReserveIgnore:
Automatically removes the oldest ignores
Makes room for new spammer ignores
No manual cleanup required
5. Enhanced Yell Detection
Many reserve spammers use /yell to bypass filters
The addon explicitly listens for yell spam
Uses the same aggressive detection logic
6. Scan Mode (Some Builds)
Can scan recent chat
Useful after login or UI reload
Immediately cleans up visible spam
.................................................. .........
What It Does NOT Do
❌ It does not block legitimate group ads
❌ It does not auto-ignore based on class, guild, or role
❌ It does not report players
❌ It does not modify loot rules
❌ It does not interact with Blizzard servers
It only affects your local chat view.
Why This Addon Is Unique
Most addons:
Filter text
Hide messages
Use regex replacements
ReserveIgnore goes one step further:
It removes the speaker entirely
Once ignored, they are gone from all channels
This is why it stays effective even when spammers repeat messages.
Who This Addon Is For
✔ Players who:
Want clean LFG / Trade chat
Don’t care about loot-reserved runs
Play on spam-heavy servers
Want zero manual moderation
❌ Players who:
Join reserved loot runs
Advertise HR/RES groups
Want to see everything
In One Sentence
ReserveIgnore automatically silences loot-reserve spammers by detecting reserve language in chat and permanently ignoring the sender—keeping your chat clean without any effort.
This is not 100% perfect, but it is a very solid base. It was made with ChatGPT, so it can be easily modified .
you can also add any words to the exception list or the trigger words easily by opening the lua file and scrolling down just a bit , that can be used for many things , say you want to filter horde chat , you can add a few orcish trigger words or if you dont want to see guild spam "recruiting" or if you dont want to see characters for sale spam WTS also character buying WTB, this addon is a very powerful chat filter ... and i will continue to work on this and try to make it more robust , untill then it is working really good for finding no reserve raids !! thanks for all the support !!
this is an addon i made for the community , if its not for you move along , but your the troll that was spamming me in game .. im glad my addon is working and your mad about it , now i asked chatgpt if this file is safe , since i made it with chatgpt ...
here is what it said ...
Yes — based on inspection, the file is safe. Here’s exactly what I checked and why:
What’s inside reserveignore2.zip
The ZIP contains only two plain-text World of Warcraft addon files:
ReserveIgnore.lua (26,931 bytes)
ReserveIgnore.toc (248 bytes)
What it does not contain
❌ No executables (.exe, .dll, .bat, etc.)
❌ No obfuscated or compiled binaries
❌ No external downloads, OS-level calls, or file system access
❌ No network, shell, or system APIs (Lua in WoW can’t do that anyway)
Why this is safe
WoW addons run in a sandboxed Lua environment
They cannot access your operating system, files, registry, or internet
The Lua code only interacts with WoW’s API (chat events, ignore list, UI, etc.)
This addon matches the normal structure of a WoW 3.3.5 addon
For the record: I was just ingame asking some things about your addon and you were being kinda rude / bragging and lying / falsly advertising.
Full conversation / screenshots below, do with that information what you want.
I even blurred your name in order to save your privacy. https://imgur.com/a/JvCfgxq
Please tell me again how you can safely open a zip file before downloading it? There is a reason github exists and why most addon creators use it for transparency. Use at own risk.
Opening a zip won't get you infected. It's executing stuff inside that will. But addons are made of lua files that also can't infect you unless you have installed a lua runtime on your computer (and executed the files). But if you had installed a lua runtime, you wouldn't be asking these questions.
few fixes , more trigger and exception words , also i will post the raw code below on the next post so no one has to download anything , i think it will fit ??... you can make a folder named Reserveignore put that in your addon folder .. then make a txt document in that folder name it Reserveignore.lua "you must enable file extensions in windows and change it from a .txt to a .lua , copy the raw code from the post below and paste it in the lua document , then make a second text file name it Reserveignore.toc .. again must change the .txt extension to a .lua extension... post the toc code in the toc document and reload wow and the addon should load... or you can just download the zip file https://www.mediafire.com/file/3ctvp...nore3.zip/file
------------------------------------------------------------
-- PREBUILD NORMALIZED TABLES
------------------------------------------------------------
local normalized_triggers = {}
local normalized_triggers_nospace = {}
local normalized_exceptions = {}
local normalized_exceptions_nospace = {}
local function normalize_text_local(s)
if not s then return "" end
s = tostring(s):lower()
s = s:gsub("[^%w%s]", " "):gsub("%s+", " ")
return (s:gsub("^%s+", ""):gsub("%s+$", ""))
end
local function nospace_text_local(s)
if not s then return "" end
s = tostring(s):lower()
s = s:gsub("[^%w]", "")
return (s:gsub("^%s+", ""):gsub("%s+$", ""))
end
for _, p in ipairs(TRIGGER_WORDS) do
table.insert(normalized_triggers, normalize_text_local(p))
table.insert(normalized_triggers_nospace, nospace_text_local(p))
end
for _, p in ipairs(EXCEPTION_WORDS) do
table.insert(normalized_exceptions, normalize_text_local(p))
table.insert(normalized_exceptions_nospace, nospace_text_local(p))
end
------------------------------------------------------------
-- HELPERS & IGNORE MANAGEMENT
------------------------------------------------------------
local function GetRandomWhisperDelay()
return math.random(WHISPER_DELAY_MIN, WHISPER_DELAY_MAX)
end
local function Trim(s)
if not s then return "" end
return (s:gsub("^%s+", ""):gsub("%s+$", ""))
end
local function ColorText(color, text)
return "|cff" .. color .. text .. "|r"
end
local function PrintMsg(msg)
DEFAULT_CHAT_FRAME:AddMessage(ColorText("ff0000", "[ReserveIgnore] ") .. tostring(msg))
end
local function WipeReserveIgnoreDB()
for k in pairs(ReserveIgnoreDB) do
ReserveIgnoreDB[k] = nil
end
announcedPlayers = {}
end
local function InitIgnoreCount()
ReserveIgnore_IgnoreCount = GetNumIgnores() or 0
if ReserveIgnore_IgnoreCount >= IGNORE_CLEAN_THRESHOLD then
local clearedCount = 0
for i = GetNumIgnores(), 1, -1 do
local name = GetIgnoreName(i)
if name then
DelIgnore(name)
clearedCount = clearedCount + 1
end
end
WipeReserveIgnoreDB()
ReserveIgnore_IgnoreCount = 0
PrintMsg(ColorText("00ffff", "Initial ignore list exceeded threshold. Cleared "..clearedCount.." in-game ignores + ReserveIgnoreDB."))
end
end
local function GetIgnoreListSizeCached()
return ReserveIgnore_IgnoreCount or GetNumIgnores()
end
local function AutoCleanIgnores()
local currentCount = GetIgnoreListSizeCached() or GetNumIgnores()
if currentCount < IGNORE_CLEAN_THRESHOLD then return end
PrintMsg(ColorText("ffff00", "Ignore list near capacity — clearing in-game ignore list AND ReserveIgnoreDB."))
local clearedCount = 0
for i = GetNumIgnores(), 1, -1 do
local name = GetIgnoreName(i)
if name then
DelIgnore(name)
clearedCount = clearedCount + 1
end
end
PrintMsg(ColorText("00ffff", "Cleared "..clearedCount.." in-game ignores + ReserveIgnoreDB."))
end
local function AddToIgnoreList(player, reason)
if not player or player == "" then return end
if ReserveIgnoreDB[player] then return end
AutoCleanIgnores()
ReserveIgnoreDB[player] = {
source = reason or "unknown",
time = date("%Y-%m-%d %H:%M:%S"),
}
AddIgnore(player)
ReserveIgnore_IgnoreCount = (ReserveIgnore_IgnoreCount or GetNumIgnores()) + 1
PrintMsg("Ignored " .. tostring(player) .. " for " .. tostring(reason or "unknown"))
end
------------------------------------------------------------
-- SAY / WHISPER QUEUE
------------------------------------------------------------
local whisperQueue, whisperTimer = {}, 0
local whisperFrame = CreateFrame("Frame")
local function QueueWhisper(player, reason)
if not player or not reason then return end
if ReserveIgnore_WhisperMode then
table.insert(whisperQueue, {
target = player,
msg = "You have been permanently blacklisted for reserving items. (" .. tostring(reason) .. ")"
})
end
end
whisperFrame:SetScript("OnUpdate", function(_, elapsed)
whisperTimer = whisperTimer + elapsed
if whisperTimer >= GetRandomWhisperDelay() and #whisperQueue > 0 and ReserveIgnore_WhisperMode then
whisperTimer = 0
local entry = table.remove(whisperQueue, 1)
if entry and entry.target and entry.msg then
SendChatMessage(entry.msg, "WHISPER", nil, entry.target)
end
end
end)
------------------------------------------------------------
-- AMBIGUOUS TRIGGER SET (Option C)
------------------------------------------------------------
local AMBIGUOUS_NOSPACE = {
["res"] = true, ["ress"] = true, ["rez"] = true, ["rezz"] = true,
["reserve"] = true, ["reserved"] = true, ["reserving"] = true,
["reseved"] = true, ["resv"] = true, ["resvd"] = true, ["rsv"] = true, ["rsvd"] = true,
["resed"] = true
}
------------------------------------------------------------
-- EXCEPTION CHECK
------------------------------------------------------------
local function MessageHasException(msg)
if not msg then return false end
local rawLower = tostring(msg):lower()
local ns = rawLower:gsub("[^%w]", "")
for _, ex in ipairs(normalized_exceptions) do
local cleaned = ex:gsub("%s+", "")
if ex ~= "" and (rawLower:find(ex,1,true) or ns:find(cleaned,1,true)) then
return true
end
end
for _, ex in ipairs(normalized_exceptions_nospace) do
if ex ~= "" and (rawLower:find(ex,1,true) or ns:find(ex,1,true)) then
return true
end
end
return false
end
------------------------------------------------------------
-- TEXT NORMALIZATION HELPERS
------------------------------------------------------------
local function TrimLocal(s) if not s then return "" end return (s:gsub("^%s+",""):gsub("%s+$","")) end
local function punctuation_normalize(s)
if not s then return "" end
local t = tostring(s):lower()
t = t:gsub("[%(%)]", " ")
t = t:gsub("[%[%]]", " ")
t = t:gsub("[%+%-%.%,%:%;%!%?%/%|%'"]", " ")
t = t:gsub("%s+", " ")
return TrimLocal(t)
end
local function nospace_text(s)
if not s then return "" end
s = tostring(s):lower()
s = s:gsub("[^%w]", "")
return TrimLocal(s)
end
local function escape_magic(s)
return (s:gsub("([%%%^%$%(%)%%%.%[%]%*%+%-%?])", "%%%1"))
end
local function build_sep_pattern(compact)
if not compact or compact == "" then return nil end
local pat = ""
for c in compact:gmatch(".") do
pat = pat .. escape_magic(c) .. "[^%w]*"
end
return pat
end
------------------------------------------------------------
-- CONTAINS REVIVE CONTEXT
------------------------------------------------------------
local function ContainsReviveContext(msg)
if not msg then return false end
msg = msg:lower()
for _, w in ipairs(REVIVE_WORDS) do
if msg:find(w, 1, true) then
return true
end
end
return false
end
------------------------------------------------------------
-- OPTION C SUPER-DETECT with FULL MESSAGE TRIGGER HIGHLIGHT
------------------------------------------------------------
local function MessageHasTrigger_Super(msg)
if not msg or msg == "" then return false end
local raw = tostring(msg)
local rawLower = raw:lower()
local punctNorm = punctuation_normalize(rawLower)
local ns = nospace_text(rawLower)
local tokens = {}
for tok in punctNorm:gmatch("%S+") do
table.insert(tokens, tok)
end
for i, trg in ipairs(normalized_triggers) do
if trg and trg ~= "" then
local trgNospace = normalized_triggers_nospace[i] or trg:gsub("%s+","")
local matched = false
if trgNospace ~= "" and ns:find(trgNospace, 1, true) then
matched = true
end
if not matched then
for _, t in ipairs(tokens) do
if t:find(trg, 1, true) or t:find(trgNospace,1,true) then
matched = true
break
end
end
end
if not matched and trgNospace ~= "" then
local sepPat = build_sep_pattern(trgNospace)
if sepPat and (rawLower:find(sepPat) or punctNorm:find(sepPat)) then
matched = true
end
end
if matched then
local isAmbiguous = AMBIGUOUS_NOSPACE[trgNospace]
if isAmbiguous then
if ReserveIgnore_SkipShortTriggers and (#trgNospace < 4) and ContainsReviveContext(punctNorm) then
-- treat as not matched
else
if not MessageHasException(rawLower) then
return true, trg
end
end
else
return true, trg
end
end
end
end
local hasStandalone =
rawLower:find("%f[%a]res%f[%A]") or
rawLower:find("%f[%a]ress%f[%A]") or
rawLower:find("%f[%a]rez%f[%A]")
if hasStandalone then
if ReserveIgnore_SkipShortTriggers and ContainsReviveContext(punctNorm) then
return false
end
if not MessageHasException(rawLower) then
if rawLower:find("%f[%a]res%f[%A]") then return true, "(standalone res)" end
if rawLower:find("%f[%a]ress%f[%A]") then return true, "(standalone ress)" end
if rawLower:find("%f[%a]rez%f[%A]") then return true, "(standalone rez)" end
end
end
return false
end
------------------------------------------------------------
-- CLEAN PLAYER NAME / EVENT HANDLING
------------------------------------------------------------
local function CleanPlayerName(name)
if not name then return nil end
name = tostring(name)
name = name:gsub("|Kf%d+|k","")
name = name:gsub("|c%x%x%x%x%x%x%x%x","")
name = name:gsub("|r","")
name = name:gsub("%-.+","")
return TrimLocal(name)
end
local lastScan = {}
local function FindSenderFromArgs(...)
for i=2,8 do
local a = select(i,...)
if a and type(a)=="string" and a~="" then
if not a:find("%.") then
return a
end
local left = a:match("^([%w%-]+)%-.+")
if left and left~="" then return left end
end
end
return nil
end
local function HandleMessage(msg, sender, ...)
if not ReserveIgnore_Enabled then return end
if not sender or sender=="" then
sender = FindSenderFromArgs(...)
end
if not sender or sender=="" then return end
if sender == UnitName("player") then return end
sender = CleanPlayerName(sender)
local now = GetTime()
if lastScan[sender] and (now-lastScan[sender]) < SCAN_THROTTLE then return end
lastScan[sender] = now
local hit, reason = MessageHasTrigger_Super(msg)
if hit then
if announcedPlayers[sender] then return end
if not ReserveIgnoreDB[sender] then
AddToIgnoreList(sender, reason)
QueueWhisper(sender, reason)
announcedPlayers[sender] = true
end
end
end
------------------------------------------------------------
-- MAIN EVENT HOOK
------------------------------------------------------------
-- MAIN EVENT HOOK (aggressive only, scan all channels)
ReserveIgnore:SetScript("OnEvent", function(self, event, ...)
if event == "PLAYER_LOGIN" then
PrintMsg("ReserveIgnore v1.9 loaded.")
InitIgnoreCount()
PrintMsg("Addon is " .. (ReserveIgnore_Enabled and ColorText("00ff00","ENABLED") or ColorText("ff0000","DISABLED")))
PrintMsg("Whisper mode is " .. (ReserveIgnore_WhisperMode and ColorText("00ff00","ON") or ColorText("ff8800","OFF")) .. ".")
return
end
local msg = select(1,...)
local sender = select(2,...)
if event == "CHAT_MSG_MONSTER_YELL" then return end
SlashCmdList["RESERVEIGNORE"] = function(msgInput)
local input = (msgInput or ""):lower():match("^%s*(.-)%s*$")
if input == "on" then
ReserveIgnore_Enabled = true
PrintMsg("Addon ENABLED.")
elseif input == "off" then
ReserveIgnore_Enabled = false
PrintMsg("Addon DISABLED.")
elseif input == "list" then
PrintMsg("Ignored players (ReserveIgnoreDB):")
for player, data in pairs(ReserveIgnoreDB) do
PrintMsg(" - " .. player .. " (" .. (data.source or "unknown") .. " at " .. (data.time or "?") .. ")")
end
elseif input == "cleardb" then
WipeReserveIgnoreDB()
for i=GetNumIgnores(),1,-1 do
local name = GetIgnoreName(i)
if name then DelIgnore(name) end
end
ReserveIgnore_IgnoreCount = 0
PrintMsg("ReserveIgnore database + in-game ignore list cleared.")
elseif input == "clearignores" then
for i=GetNumIgnores(),1,-1 do
local name = GetIgnoreName(i)
if name then DelIgnore(name) end
end
ReserveIgnore_IgnoreCount = 0
PrintMsg("Cleared in-game ignore list (DB untouched).")
elseif input:match("^whispermode%s+") then
local v = input:match("^whispermode%s+(%S+)")
if v == "on" then
ReserveIgnore_WhisperMode = true
PrintMsg("Whisper mode: ON")
elseif v == "off" then
ReserveIgnore_WhisperMode = false
PrintMsg("Whisper mode: OFF")
else
PrintMsg("Usage: /ri whispermode on | off")
end
elseif input:match("^mode%s+") then
local m = input:match("^mode%s+(%S+)")
if m == "aggressive" or m=="a" then
CURRENT_MODE = "aggressive"
PrintMsg("Mode set to Aggressive")
elseif m == "moderate" or m=="m" then
CURRENT_MODE = "moderate"
PrintMsg("Mode set to Moderate")
else
PrintMsg("Usage: /ri mode aggressive | moderate")
end
else
PrintMsg("Usage: /ri on | off | list | cleardb | clearignores | whispermode on/off | mode <aggressive|moderate>")
end
end
------------------------------------------------------------
-- SAY / WHISPER QUEUE
------------------------------------------------------------
local whisperQueue, whisperTimer = {}, 0
local whisperFrame = CreateFrame("Frame")
local function QueueWhisper(player, reason)
if not player or not reason then return end
if ReserveIgnore_WhisperMode then
table.insert(whisperQueue, {
target = player,
msg = "You have been permanently blacklisted for reserving items. (" .. tostring(reason) .. ")"
})
end
end
whisperFrame:SetScript("OnUpdate", function(_, elapsed)
whisperTimer = whisperTimer + elapsed
if whisperTimer >= GetRandomWhisperDelay() and #whisperQueue > 0 and ReserveIgnore_WhisperMode then
whisperTimer = 0
local entry = table.remove(whisperQueue, 1)
if entry and entry.target and entry.msg then
SendChatMessage(entry.msg, "WHISPER", nil, entry.target)
end
end
end)
------------------------------------------------------------
-- AUTO-CLEAN IGNORE LIST
------------------------------------------------------------
local function AutoCleanIgnores()
local currentCount = ReserveIgnore_IgnoreCount or GetNumIgnores()
if currentCount < IGNORE_CLEAN_THRESHOLD then return end
PrintMsg(ColorText("ffff00", "Ignore list near capacity — clearing in-game ignore list AND ReserveIgnoreDB."))
local clearedCount = 0
for i = GetNumIgnores(), 1, -1 do
local name = GetIgnoreName(i)
if name then
DelIgnore(name)
clearedCount = clearedCount + 1
end
end
PrintMsg(ColorText("00ffff", "Cleared "..clearedCount.." in-game ignores + ReserveIgnoreDB."))
end
------------------------------------------------------------
-- ADD TO IGNORE LIST
------------------------------------------------------------
local function AddToIgnoreList(player, reason)
if not player or player == "" then return end
if ReserveIgnoreDB[player] then return end
AutoCleanIgnores()
ReserveIgnoreDB[player] = {
source = reason or "unknown",
time = date("%Y-%m-%d %H:%M:%S"),
}
AddIgnore(player)
ReserveIgnore_IgnoreCount = (ReserveIgnore_IgnoreCount or GetNumIgnores()) + 1
PrintMsg("Ignored " .. tostring(player) .. " for " .. tostring(reason or "unknown"))
end