File Details
GuildRoster-v0.2.1
- R
- Jun 9, 2026
- 35.58 KB
- 1.7K
- 12.0.1+6
- Retail + 2
File Name
GuildRoster-GuildRoster-v0.2.1.zip
Supported Versions
- 12.0.1
- 12.0.0
- 11.2.7
- 4.4.0
- 3.4.3
- 2.5.5
- 1.15.8
Changelog
All notable changes to LibGuildRoster-1.0 are documented here.
0.2.1 — Retail taint-safe CHAT_MSG_SYSTEM handling, and cheaper scanning
LibStub MINOR bumped to 7.
Fixed
- Retail "secret value" taint from CHAT_MSG_SYSTEM parsing. On retail
(TWW/11.x+)
CHAT_MSG_SYSTEMpayloads are marked as protected "secret" values only during "chat messaging lockdown" — an active Mythic+/Challenge Mode, an in-progress instance encounter, or an active PvP match (the threeChatMessagingLockdownReasonstates). While locked down, any string operation on the payload (compare,match,gsub, concat) taints execution, and that taint leaks into shared UI, surfacing as unrelated Blizzard errors (MoneyFrame_Updatearithmetic,AreaPoiUtilSetPadding, tooltip comparisons) "while execution tainted by '<addon>'". Outside lockdown the payload is an ordinary string. - The previous
pcall(function() return message == "" end)probe was itself the taint source — the==comparison is a forbidden operation on a secret value, so the probe performed the very taint it was meant to detect. OnChatMsgSystemnow gates onC_ChatInfo.InChatMessagingLockdown()and skips parsing while it returns true. Because the value is only secret during lockdown, skipping that window means the lib never operates on a secret value and never produces taint — prevention at the boundary, not operate-then-catch. The CHAT_MSG_SYSTEM body was extracted intolib:ParseSystemMessage;OnChatMsgSystemis now a thin lockdown gate in front of it.- No regression to normal play. Outside lockdown (questing, town, open
world — i.e. virtually all play, and when guildmates actually join) nothing
is secret, so online/offline/join/leave parsing and callbacks fire exactly as
before. The gate is guarded by
WOW_PROJECT_ID == WOW_PROJECT_MAINLINE(the same retail guard theSetGuildRosterShowOfflinepath uses) plus aC_ChatInfo.InChatMessagingLockdownfeature-detect, so Classic / TBC / Wrath / Cata — which have no secret-value system — never take the branch and parse unchanged. - Known limitation (tracked separately): transitions that arrive during
an encounter / M+ / PvP match are skipped in real time and reconciled by the
next post-lockdown
GUILD_ROSTER_UPDATEfor membership. Real-time join/leave callbacks for those in-lockdown events still need the roster-diff path (keyed off the accessible roster) to be fully restored.
Performance
- CHAT_MSG_SYSTEM scanned with a cheap pre-filter.
CHAT_MSG_SYSTEMis a high-volume firehose (loot, achievements, M+ notices, system spam, ...), and the handler previously stripped chat-link markup (twogsubs) and ran up to five anchored pattern matches on every line before deciding it wasn't a guild transition. Each event type now has a precomputed plain-text needle (derived per-locale from the sameERR_*global viaBuildChatNeedle, e.g." has joined the guild."); a plainstring.findfor it on the raw message rejects the overwhelming majority of traffic before any markup strip or capture runs. Markup is stripped lazily and only once, only on a needle hit. Because the needle is a substring of every message its pattern accepts, the pre-filter can never drop a line the pattern would have matched, and a non-derivable needle degrades to always attempting the match. This matters because the library sits in the chat hot path of every consumer addon.
0.2.0 — Cross-guild (sister-roster) support
LibStub MINOR bumped to 6.
This release adds a multi-roster store on top of the single self-scanned
guild roster, so a consumer can track one or more sister guilds whose
membership and presence are fed in from outside (e.g. discovered over
/who and synced across whispers by a higher layer). The library stays
framework-agnostic — it only stores, normalizes, hashes, diffs, queries,
and ages presence. Everything from MINOR 5 is unchanged and home-only;
all new behaviour is additive.
Added
- Multi-roster store. The self-scanned home roster still lives under
self.rosterand every existing method reads it untouched. Externally fed sister rosters live underself.rosters[guildKey], whereguildKeyis"Faction-GuildName". lib:GetHomeGuildKey()→"Faction-GuildName"(the locale-independentUnitFactionGrouptoken + the rawGetGuildInfoname, e.g."Horde-The Brave Ones"), ornilwhen guildless / before guild data resolves at login. Cached like the realm name and cleared on the not-in-guild wipe so a guild switch re-resolves.lib:SetSisterRoster(guildKey, members[, meta])— wipe-and-replace ingest for a sister roster (preserves the "stale ex-members are impossible" guarantee).membersentries are either a bare"Name-Realm"string or a{ name, class, level, rank }table; every name is run throughNormalizeName.metais opaque caller metadata, stored and read back viaGetRosterMetabut never interpreted (e.g. the provider charKey and snapshot timestamp for provenance); a re-feed that omitsmetakeeps the previous value, and onlyRemoveSisterRosterclears it. Ignored whenguildKeyis the home key — the authoritative self-scan always wins.lib:RemoveSisterRoster(guildKey)— silent teardown of a sister roster, its presence overlay, meta, and cached hash.- Presence overlay, separate from membership.
lib:MarkOnline(guildKey, names)stamps a last-seen timestamp for sister members;lib:GetOnlineMembersScoped(guildKey)returns, for a sister, members stamped withinlib.PRESENCE_TTL(default 120 s), and for the home guild the authoritative livemember.isOnline. Presence is stored in a separate table (self.presence[guildKey]), not inside the member tables, so aSetSisterRosterresync never wipes liveness. The lib only ever records a last-seen time, never a hard "offline" — the upstream presence source is sampled, so presence ages out and self-corrects. Presence is transient and never persisted. - Scoped queries (additive; the five home-only getters are unchanged):
lib:IsInAnyRoster(name)→guildKey | nil(home takes precedence),lib:IsInGuildScoped(guildKey, name)→ boolean,lib:GetRoster(guildKey)→{ charKey = member },lib:GetRosterMeta(guildKey)→ the opaquemetalast fed toSetSisterRoster,lib:GetKnownRosters()→ array of guildKeys. lib:GetRosterHash(guildKey)→ a stable 8-hex-digit FNV-1a digest over the sorted membership set only. It excludesisOnline, zone, status, rank, and presence, so it does not churn when someone logs in or out; two clients with the same membership produce the identical hash.OnRosterHashChanged(guildKey, newHash)callback. Fires when a roster's membership set changes — for the home roster on a post- stabilization rebuild, and for a sister roster on eachSetSisterRosterthat alters the set. This is how a sister client learns to re-pull.
Changed
OnMemberJoined/OnMemberLeftnow pass aguildKey2nd argument. Home joins/leaves pass the home key; sister diffs pass the sister key. Existing one-arg consumers harmlessly ignore the extra argument. The firstSetSisterRosterfeed for a guild is treated as a baseline — it fires the hash but not a join per member, so a consumer re-feeding its persisted roster on every login doesn't re-welcome the whole sister guild (mirroring the home path's login-flood avoidance).NormalizeNamenow squashes internal whitespace in the realm portion ("Name-Argent Dawn"→"Name-ArgentDawn"). This is a no-op for the home path and already-normalized fed names — pure defense-in- depth so a mismatched realm spelling in aSetSisterRosterfeed can't silently fail every cross-roster lookup.
Notes
- Embedding coordination. Because this lib is embedded in multiple
addons, the MINOR bump and every embedder's
.tocdependency must move together, orLibStubmay hand an older copy that lacks the new methods. Consumers should feature-detect each new method (if lib.SetSisterRoster then ...) so a load race against an older copy degrades to a no-op rather than erroring. - Hash is a frozen contract. The FNV-1a algorithm is fixed: changing it after ship would make mixed-version peers disagree forever. It is only ever compared between instances of this library.
- No persistence in the lib — it stays in-memory. The consuming addon
persists sister rosters in its own SavedVariables and re-feeds them via
SetSisterRosteron login/reload.
0.1.0 — GetNormalizedPlayer, documented normalization helpers
LibStub MINOR bumped to 5.
Added
lib:GetNormalizedPlayer()getter. Returns the local player's canonical"Name-Realm"string, formatted identically to the roster keys so consumers can compare it directly againstGetMember/GetAllMembersoutput. Reuses the lib's cached realm name (GetRealmName). Falls back to the bare"Name"during the brief pre-world-enter window where the realm name isn't resolved yet, and returnsnilifUnitNameisn't available. Deliberately does not route throughNormalizeName, which would emit a trailing-hyphen"Name-"in that fallback window.
Documentation
NormalizeNameandGetRealmNameare now documented as public API. Both have always been public methods; they're now listed in the file header, the README, and the CurseForge description so consumers that build"Name-Realm"strings (or compare against roster keys) can rely on them. No behaviour change to either method.- CurseForge description brought back in sync. Its Public API list
was missing
IsReady(added in 0.0.2) and itsGetMemberfield list still showed the pre-0.0.2 short form; both are corrected, alongside the newGetNormalizedPlayer/NormalizeName/GetRealmNameentries.
Notes
- The package version jumps from 0.0.x to 0.1.0 now that the standalone packaging and external-reference flow are validated against a real consumer. The LibStub MINOR continues to increment by one per behavioural change (now 5), independent of the package version.
0.0.4 — Defuse retail "secret string" taint in CHAT_MSG_SYSTEM
LibStub MINOR bumped to 4.
Fixed
CHAT_MSG_SYSTEMno longer errors on protected "secret" string values. Retail (TWW+) sometimes delivers internal BNet / whisper- related signals viaCHAT_MSG_SYSTEMwith a server-side protection flag. Any operation on such a value (==,gsub,match) throwsattempt to compare local 'message' (a secret string value, while execution tainted by '<addon>')from the first comparison inOnChatMsgSystem. The handler then aborted, and on some clients the event frame stayed tainted so subsequent legitimate guild events (online / offline / join / leave) were missed entirely. The lib now pcalls the empty-string check at the top of the handler; if the comparison itself errors, the message is protected and we silently bail before touching it further — those messages would never match our guild-event patterns anyway. Reported as 18× repeated errors from a consumer addon (FastGuildInvite) embedding this lib on retail.
0.0.3 — Switch to Ace3-supplied LibStub / CallbackHandler-1.0
Fixed
- Standalone install no longer errors on missing LibStub. v0.0.1 and
v0.0.2 declared
LibStubandCallbackHandler-1.0as.pkgmetaexternals, so a raw git checkout (or a dev sync that didn't go through the CurseForge packager) had noLibs/LibStub/LibStub.luaon disk and the TOC'sLibs\LibStub\LibStub.lualine produced aLUA_WARNING: Error loading GuildRoster/Libs/LibStub/LibStub.lua. The TOCs no longer reference those files at all; Ace3 supplies both libraries globally viaLibStubbeforeLibGuildRoster-1.0.luaruns.
Changed
- Ace3 is now a required dependency. All five TOCs declare
## Dependencies: Ace3and.pkgmetalistsrequired-dependencies: - ace3, so CurseForge auto-installs Ace3 alongside this lib and the game loader pulls it in first at runtime. Theexternals:block has been removed from.pkgmeta. enable-toc-creation: nobecause we ship explicit TOCs for every flavour and don't need the packager to synthesise any.
Notes for embedding consumers
If your addon embeds this lib via externals:, you still need
LibStub and CallbackHandler-1.0 available before
LibGuildRoster-1.0.lua loads — either from your own embedded copies,
from Ace3 as a dependency of your addon, or from any other lib that
brings them in. The lib itself no longer ships them.
No library API or behaviour changes; LibStub MINOR stays at 3.
0.0.2 — Locale fix, expanded member data, rank-change callback
LibStub MINOR bumped to 3.
Fixed
- CHAT_MSG_SYSTEM patterns are now locale-aware. The hardcoded English
strings (
"has come online","has joined the guild", etc.) silently failed on every non-English client — German, French, Russian, Chinese, and others were all reduced to picking up online/offline/join/leave transitions only on the next fullGUILD_ROSTER_UPDATErebuild instead of in real time. Patterns are now built once at file load from Blizzard's localized format-string globals (ERR_FRIEND_ONLINE_SS,ERR_FRIEND_OFFLINE_S,ERR_GUILD_JOIN_S,ERR_GUILD_LEAVE_S,ERR_GUILD_REMOVE_SS). Chat-hyperlink markup is stripped from incoming messages before matching, and brackets[Name]are made optional in the pattern so both bracketed and bare forms match.
Added
- Expanded
GetMemberfields. The member table now includeszone,publicNote,officerNote,status(0 = available, 1 = AFK, 2 = DND),isMobile(Companion App connection), andlastOnline({ years, months, days, hours }for offline members, nil otherwise). Previously these were silently discarded from theGetGuildRosterInforeturn — consumers had to re-iterate the whole roster themselves to recover them. lib:IsReady()getter. Returns true once the first stabilized full roster build has completed. Consumers that register callbacks after the lib has already initialized (common on/reload, or when the consumer addon loads later than the lib) would otherwise miss theOnRosterReadyfire; checkIsReady()and run the ready-time logic inline when it returns true.OnMemberRankChanged(name, oldRankIndex, newRankIndex)callback. Fires when a member's rank changes between rebuilds. Skipped during the initial roster stream so partial-snapshot rank transitions can't misfire; only fires for members that existed in the previous roster.
0.0.1 — Initial release
First release of LibGuildRoster-1.0 as a standalone CurseForge library, extracted from its prior home as a vendored copy inside FastGuildInvite. Pre-1.0 versioning while the standalone packaging and external-reference flow are validated end-to-end against a real consumer build.
Library behaviour
- Tracks the WoW guild roster with wipe-and-rebuild semantics — no stale ex-members possible.
GUILD_ROSTER_UPDATEdrives full rebuilds;CHAT_MSG_SYSTEMdrives real-time online / offline / join / leave transitions between rebuilds.- Retries the initial roster fetch up to 5 times at login to handle the
window where
GetNumGuildMembers()returns 0. - Stabilization gate (2 consecutive rebuilds with the same member total)
before
OnRosterReadyfires, preventing partial-snapshot misfires during the retail login roster stream. OnMemberJoinedfires only from the authoritativeCHAT_MSG_SYSTEM"X has joined the guild" message, never from roster diffs.- Recently-left dedup (60 s TTL) prevents stale
GUILD_ROSTER_UPDATEresponses from misfiringOnMemberJoinedfor a player who just left. - On retail, calls
SetGuildRosterShowOffline(true)atPLAYER_LOGINso the rebuilt roster sees the full guild rather than only online members.
Compatibility
- Classic Era (Interface 11508)
- Burning Crusade Classic (Interface 20505)
- Wrath Classic (Interface 30403)
- Cataclysm Classic (Interface 40400)
- Mainline / Retail (Interface 110207, 120001, 120000)
Public API
IsInGuild, IsOnline, GetMember, GetAllMembers, GetOnlineMembers.
Callbacks
OnRosterReady, OnRosterUpdated, OnMemberOnline, OnMemberOffline,
OnMemberJoined, OnMemberLeft.

