promotional bannermobile promotional banner

LibGuildRoster

A library for use in WoW addons that manages the guild members and roster to make that data available for use in your addon.

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_SYSTEM payloads 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 three ChatMessagingLockdownReason states). 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_Update arithmetic, AreaPoiUtil SetPadding, 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.
  • OnChatMsgSystem now gates on C_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 into lib:ParseSystemMessage; OnChatMsgSystem is 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 the SetGuildRosterShowOffline path uses) plus a C_ChatInfo.InChatMessagingLockdown feature-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_UPDATE for 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_SYSTEM is a high-volume firehose (loot, achievements, M+ notices, system spam, ...), and the handler previously stripped chat-link markup (two gsubs) 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 same ERR_* global via BuildChatNeedle, e.g. " has joined the guild."); a plain string.find for 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.roster and every existing method reads it untouched. Externally fed sister rosters live under self.rosters[guildKey], where guildKey is "Faction-GuildName".
  • lib:GetHomeGuildKey()"Faction-GuildName" (the locale-independent UnitFactionGroup token + the raw GetGuildInfo name, e.g. "Horde-The Brave Ones"), or nil when 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). members entries are either a bare "Name-Realm" string or a { name, class, level, rank } table; every name is run through NormalizeName. meta is opaque caller metadata, stored and read back via GetRosterMeta but never interpreted (e.g. the provider charKey and snapshot timestamp for provenance); a re-feed that omits meta keeps the previous value, and only RemoveSisterRoster clears it. Ignored when guildKey is 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 within lib.PRESENCE_TTL (default 120 s), and for the home guild the authoritative live member.isOnline. Presence is stored in a separate table (self.presence[guildKey]), not inside the member tables, so a SetSisterRoster resync 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 opaque meta last fed to SetSisterRoster, lib:GetKnownRosters() → array of guildKeys.
  • lib:GetRosterHash(guildKey) → a stable 8-hex-digit FNV-1a digest over the sorted membership set only. It excludes isOnline, 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 each SetSisterRoster that alters the set. This is how a sister client learns to re-pull.

Changed

  • OnMemberJoined / OnMemberLeft now pass a guildKey 2nd argument. Home joins/leaves pass the home key; sister diffs pass the sister key. Existing one-arg consumers harmlessly ignore the extra argument. The first SetSisterRoster feed 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).
  • NormalizeName now 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 a SetSisterRoster feed 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 .toc dependency must move together, or LibStub may 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 SetSisterRoster on 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 against GetMember / GetAllMembers output. 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 returns nil if UnitName isn't available. Deliberately does not route through NormalizeName, which would emit a trailing-hyphen "Name-" in that fallback window.

Documentation

  • NormalizeName and GetRealmName are 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 its GetMember field list still showed the pre-0.0.2 short form; both are corrected, alongside the new GetNormalizedPlayer / NormalizeName / GetRealmName entries.

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_SYSTEM no longer errors on protected "secret" string values. Retail (TWW+) sometimes delivers internal BNet / whisper- related signals via CHAT_MSG_SYSTEM with a server-side protection flag. Any operation on such a value (==, gsub, match) throws attempt to compare local 'message' (a secret string value, while execution tainted by '<addon>') from the first comparison in OnChatMsgSystem. 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 LibStub and CallbackHandler-1.0 as .pkgmeta externals, so a raw git checkout (or a dev sync that didn't go through the CurseForge packager) had no Libs/LibStub/LibStub.lua on disk and the TOC's Libs\LibStub\LibStub.lua line produced a LUA_WARNING: Error loading GuildRoster/Libs/LibStub/LibStub.lua. The TOCs no longer reference those files at all; Ace3 supplies both libraries globally via LibStub before LibGuildRoster-1.0.lua runs.

Changed

  • Ace3 is now a required dependency. All five TOCs declare ## Dependencies: Ace3 and .pkgmeta lists required-dependencies: - ace3, so CurseForge auto-installs Ace3 alongside this lib and the game loader pulls it in first at runtime. The externals: block has been removed from .pkgmeta.
  • enable-toc-creation: no because 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 full GUILD_ROSTER_UPDATE rebuild 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 GetMember fields. The member table now includes zone, publicNote, officerNote, status (0 = available, 1 = AFK, 2 = DND), isMobile (Companion App connection), and lastOnline ({ years, months, days, hours } for offline members, nil otherwise). Previously these were silently discarded from the GetGuildRosterInfo return — 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 the OnRosterReady fire; check IsReady() 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_UPDATE drives full rebuilds; CHAT_MSG_SYSTEM drives 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 OnRosterReady fires, preventing partial-snapshot misfires during the retail login roster stream.
  • OnMemberJoined fires only from the authoritative CHAT_MSG_SYSTEM "X has joined the guild" message, never from roster diffs.
  • Recently-left dedup (60 s TTL) prevents stale GUILD_ROSTER_UPDATE responses from misfiring OnMemberJoined for a player who just left.
  • On retail, calls SetGuildRosterShowOffline(true) at PLAYER_LOGIN so 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.