A small library which keeps track of group members and keeps an up-to-date cache of their specialization, talents and glyphs.

It's similar to the old LibGroupTalents/LibTalentQuery and the LibRaidInspect libraries, but unlike the former it's actually working on 5.0, and unlike the latter it works properly in Battlegrounds. Additionally it has the feature where it communicates spec/talent/glyph updates to other LibGroupInSpecT users. This is an important point as of the writing of this there is no way to detect when another player re-specs/talents/glyphs.

This library started out as a part of RaidChecklist as replacement for LibGroupTalents, but has since been split off into its own project as its usefulness increases the more widespread it is.

To make use of this library you'll need to also have the usual LibStub and LibCallbackHandler libs.

For a real usage example, take a look at the RaidChecklist project.

Important: WoD version change

As of Sep 18 the trunk development is changing to WoD/6.0 development, with the major version as 1.1. If you need 5.x compatibility, please lock to the 1.0.3 tag.


These events can be registered for using the regular CallbackHandler ways.


"GroupInSpecT_Update"guid, unit, info
"GroupInSpecT_InspectReady"guid, unit


Fires when info is ready or has been modified.
Fires when a member leaves the group.
Fires during INSPECT_READY so that clients can perform supplemental inspection handling (as of r78).


local LGIST = LibStub:GetLibrary("LibGroupInSpecT-1.1")
LGIST.RegisterCallback(addonObject, "GroupInSpecT_Remove", "UnitRemoved")
function addonObject:UnitRemoved(event, guid)
  -- unit with guid removed


Functions for external use:

lib:Rescan (guid or nil)
Force a fresh inspection of all group members. As of r76 it accepts an optional guid parameter, to rescan only a particular GUID rather than all group members.
lib:QueuedInspections ()
Returns an array of GUIDs of outstanding inspects.
lib:StaleInspections ()
Returns an array of GUIDs for which the data has been deemed stale and is awaiting an update (no action required, the refresh happens internally).
lib:GetCachedInfo (guid)
Returns the cached info for the given GUID, if available, nil otherwise. Information is cached for current group members only.
lib:GuidToUnit (guid)
Returns the unit id for the given GUID, provided said GUID represents a current group member, else nil.

info table structure

The fields of the table passed as an argument for "GroupInSpecT_Update" callback or returned by one of the API functions (eg. :GetCachedInfo(guid) ). A list of all the global specialization IDs is available here.

Note: Not all fields may be available at all times due to the Blizz API not returning the info at that point. Incremental updates will be sent, so coding with the possibility of nil in mind is highly advised.

Info structure

  .gender -- 2 = male, 3 = female
  .spec_role_detailed -- "tank", "melee", "ranged" or "healer" (introduced in 1.0.2)
  .spec_group -- active spec group (1/2/nil), introduced in 1.1
  .talents = {
    [<talent_id>] = { -- Note: Since 1.1 this is a talent_id, not a spell_id
      .talent_id -- Introduced in 1.1. This replaces the old 1.0.x .idx entry
  .glyphs = {
    [<spell_id>] = {
      .idx -- 1 to NUM_GLYPH_SLOTS
  .lku -- last known unit id


Typical usage example.

Libs/LibGroupInSpecT-1.1: svn://


## X-Embeds: LibGroupInSpecT-1.1
## OptionalDeps: LibGroupInSpecT-1.1

alternatively embeds.xml (referenced in .toc)

<ui xmlns="" xmlns:xsi="" xsi:schemaLocation="\FrameXML\UI.xsd">
  <script file="Libs\LibStub\LibStub.lua"/>
  <include file="Libs\CallbackHandler-1.0\CallbackHandler-1.0.xml"/>
  <include file="Libs\LibGroupInSpecT-1.1\lib.xml"/>


local LGIST=LibStub:GetLibrary("LibGroupInSpecT-1.1")
LGIST.RegisterCallback(addonObject, "GroupInSpecT_Update", "UpdateHandler")
LGIST.Registercallback(addonObject, "GroupInSpecT_Remove", "RemoveHandler")
function addonObject:UpdateHandler(event, guid, unit, info)
  if info.class and info.class == "DEATHKNIGHT" and info.spec_role and info.spec_role == "TANK" then
    print(UnitName(unit).." is now " -- may also be available
function addonObject:RemoveHandler(event, guid)
  -- guid no longer a group member
local info = LGIST:GetCachedInfo(guid)
local hasFocusingShot = info and next(info.talents) and info.talents[21729] -- focusing shot talent_id
local hasSSGlyph = info and next(info.glyphs) and info.glyphs[56231] -- soulstone glyph spell_id

Main page formatting by Dridzt. Much obliged!

You must login to post a comment. Don't have an account? Register to get one!

  • Avatar of mikk mikk Nov 13, 2014 at 02:00 UTC - 0 likes

    This needs release tagging. It's not being pushed to curse client.

  • Avatar of Tokotsi Tokotsi May 29, 2013 at 22:22 UTC - 0 likes

    Take a look at LibSpecRoster here on WowAce, and see how they deal with the spell ID of the talent. Isn't that what is trying to be done here via the tooltip scanning method? Is there a reason to go with one method vs. the other?

    Also, in answer to someone's question about differences between the two libraries, and since I was trying to figure that out myself, I posted what I saw after quick, cursory looks at them both. Given the quick look and lack of experience with either, and in general, I would be interested in feedback from those who've had more experience. I don't want to steer myself or others wrong :)

  • Avatar of Dridzt Dridzt Sep 22, 2012 at 11:35 UTC - 0 likes

    When this has hit its first release, you'd probably want to give it its forum thread here: (Libraries section)

    The Summary part of the project page, maybe the current info table and a link to the project page for further details should be sufficient :-)

    Last edited Sep 22, 2012 by Dridzt
  • Avatar of Anyia3 Anyia3 Sep 16, 2012 at 23:26 UTC - 0 likes

    @oscarucb: As Dridzt said, the 'sender' argument is unfortunately the unqualified character name (i.e. without realm information), so it cannot be reliably used to get the UUID from.

    @Dridzt: I'll have a look at thew new version after work tonight, or tomorrow, with a view of getting this stuff into mainline sooner rather than later.

  • Avatar of Dridzt Dridzt Sep 16, 2012 at 20:49 UTC - 0 likes

    @oscarucb: Go

    Sender is a name not unitid afaik, I'm not sure it always resolves to a guid if for example the sender is in an instance and you're not, or half across the world.

    Not sure and not very easy to test atm (for me).

    In any case I'm almost done with the new alpha, and transmitting the guid doesn't remotely push the message length near the limit.

    Edit: r27 cloned is in.
    Seeing as I've already sunk several hours in this overall, I'm giving it a rest, any improvements or changes be my guest :-p

    Last edited Sep 17, 2012 by Dridzt
  • Avatar of oscarucb oscarucb Sep 16, 2012 at 18:50 UTC - 0 likes

    - unitguid (need this or no way to assign the transmitted info to a unit)

    Wait, why is that? The CHAT_MSG_ADDON event already provides a sender argument that is sent with the message envelope, so isn't this field always equal to UnitGUID(sender)? Sending it explicitly should be redundant..

    Sorry to be picky, just trying to help us come up with the best possible library.

  • Avatar of oscarucb oscarucb Sep 16, 2012 at 15:38 UTC - 0 likes

    The argument about pulling in extra libraries could be used about everything tbh, but libraries only get loaded once, don't think a few extra kb of disk space is an issue at the benefit of being lazy

    It's not an issue of disk space, it's a matter of favoring laziness for the library CLIENT rather than the library WRITER. As an addon author, I dislike using a library that has a bunch of external dependencies and requires me to also suck in a bunch of additional libraries I don't need before the library can do its job. It means additional work for the addon writer to adopt your library and do the packaging correctly (and additional opportunities to get it wrong), and also additional places where version incompatibilities can arise and cause problems. Eg if the API for libgroupinspect changes in some version (or some version-specific bug arises), the addon writer is the direct client of that interface and can ensure he's embedding the working version and using the right call or workaround for the right version. If the API for libcompress were to change (or some version-specific bug arises), the addon writer knows nothing about the calls to that lib or what specific version he should be embedding in order to make libgroupinspect work.

    Speaking of versioning issues, it may be wise to include a (single-digit) comm format version number in the addon comm, in case a future version of libgroupinspect needs to expand or change the format of that comm. Receivers should ignore messages with version numbers higher than what they know how to parse.

  • Avatar of Dridzt Dridzt Sep 16, 2012 at 15:00 UTC - 0 likes

    A typical full infotable compressed is around 1337 bytes.

    Apart from the comedy value of getting a Leet number on my first test, I think I'll take your advice and commit a version at the clone with a custom serializer sending the minimum necessary data and building the full table with API + local lookups upon receipt :-)

    Last edited Sep 16, 2012 by Dridzt
  • Avatar of Dridzt Dridzt Sep 16, 2012 at 13:37 UTC - 0 likes

    @oscarucb: Go

    The minimum comms would need (speaking about r25 data structure)
    - unitguid (need this or no way to assign the transmitted info to a unit)
    - global_spec_id
    - glyph_spell_id_1
    - ...
    - glyph_spell_id_6
    - talent_spell_id_1
    - ...
    - talent_spell_id_6

    With the caveat emptor that missing data is "easily synthesized by the library on the receiver" as you mentioned it's certainly a valid implementation; light on the comms but still full on the info the library user can get.

    The argument about pulling in extra libraries could be used about everything tbh, but libraries only get loaded once, don't think a few extra kb of disk space is an issue at the benefit of being lazy :-)

    My comparison was not with this proposed implementation (which I haven't seen yet).
    Was with the mainline custom serializer that has at least 'some' overhead

    local datastr = "UPD,"..guid..","..(info.global_spec_id or 0)..","
    for _,v in ipairs (info.talents) do
      datastr = datastr..v
    datastr = datastr..","
    for i,t in pairs (info.glyphs) do
      datastr = datastr..i.."="..t..":"

    I'll post back with some metrics for a full info table sent in a bit and look at committing a version with a custom serializer as you propose and no libs. (options are good to have).

  • Avatar of oscarucb oscarucb Sep 16, 2012 at 12:50 UTC - 0 likes

    Sending the entire info structure seems wasteful ...I deliberately opted against them as the actual info that had to be transmitted was fairly minimal (most of it can be synthesized on the receiver end).

    I think I agree with Anyia here - there's no reason the library should be sending things across addon comms that can be easily synthesized by the library on the receiver. If I understand correctly, the sending library only has to send 13 numbers (global_spec_id, 6 glyph spellids, 6 talent idx 1..3) to completely describe a character, and everything else can be re-constructed using tables or queries on the reciever. Given the range of talent indexes, all six talents could easily be represented in 32 bits, so generously assuming 32 bits for the spec_id and 32 bits for each glyph spellid, that's 32 bytes of data total. I find it difficult to believe that libcompress could do anything close to that with a serialized table containing several long strings. 32 bytes should never require fragmentation or re-assembly, and can accomplish everything using a single SendAddonMessage without sucking in a bunch of additional libraries (which is MUCH nicer from the client perspective). Even if it means a few extra lines of code in the library, I think the savings in comm bandwidth and sub-library requirements is worthwhile.

    EDIT: even if you represent all 13 numbers as comma-delimited textual numbers (where each number is up to 7 digits in length), thats still under 100 characters, which is well under the 254 character limit for a single message.

    Last edited Sep 16, 2012 by oscarucb


Date created
Sep 01, 2012
Last update
Feb 21, 2015
Development stage
BSD License
Curse link
Recent files



Embedded library