AddOn communications protocol

As the number of addons using raid or guild communications is increasing it's become apparent that we need a community protocol for addon developers to use to standardize these comm functions. 
You can find discussion of this protocol on The US WoW forums or in Talk:Addon Comm Protocol
== Proposed Features ==
* Channels for Global, Guild and Group (Raid or party) communiction should be available
* Channels are only joined if a mod needs to use it
* Chat from the comm channels must be blocked from appearing in the default UI's chat channels
* Protocol should not be tied to any one mod, but be implementable by anyone
* Multiple addons implementing the protocol must not interfere with each other
* A standard message format must be established, so addons do not conflict with each other's messages
* A sobriety filter must be applied to outgoing messages to prevent modifications caused by drunk players. This involves replacing the chars 's' and 'S' or 'h' with unique, rarely used chars. Also "...hic!" must be removed from the end of messages received.
== Channel Management ==
There are two primary issues here, channel names and maintaining the channel connection, changing and chat suppression.
=== Channel Names ===
Channel names have a 31 char limit and should be considered case-insensitive.
==== Global ====
A simple global channel should be available, something like CommGlobal
==== Group ====
A group channel should be provided, with a unique name derrived from the party or raid leader's name. If the player is in a raid a party channel will not exist, but party--specific communication can be maintained by the addon receiving the messages by checking the sender against the party roster.
Sample channel name with raid leader named Tekkub: CommRaTekkub
Player names are unique within a realm, cannot contain spaces, and the name limit is 12 chars, but special characters are allowed. When we strip those characters out, there could be conflicts-- so we're going to use a checksum system here to prevent that. We can use the following function to strip out all special characters:
 function Strip(name)
 return string.gsub(name, "%W", "")
 end
There is a possibility that a channel name could conflict due to this, so addons using this channel should probably verrify that the sender of a message is in the raid or party before the message is parsed, but the checksum system should prevent most of this.
You could use the following function to create group names (where CheckSum is defined below):
 function GroupChannelName(name)
 local cs = CheckSum(name)
 name = Strip(name)
 name = string.sub(name, 1, 8)
 return "CommRa" .. name .. cs
 end
A group channel with leader "Tekkub" would become "CommRaTekkub21cdb4"
A group channel with leader "Clãdhaire" would become "CommRaCldhaire23e70a"
A group channel with leader "Clædhaire" would become "CommRaCldhairebc9459"
Any addon or library maintaining connection to this channel must handle leadership changes smoothly. It must close connection to the old group channel and open connection to the new channel.
==== Guild ====
A guild channel mechanism should be provided. This should not restrict the user to their current guild, as many guilds keep an "app guild" or "alt guild" wherein the players may need access to communication with the main guild's addon channel. The addon managing this channel should be able to give some sort of indication of the guild channel it is maintaining, so conflicts do not arrise but many guild channels could potentially be opened.
Guild names have the same issues as playernames for the group channels, plus they can take spaces. So a checksum is used here as well, and all non-alphanumeric chars are stripped from the name. For example:
"Khaz Modan Brigade" would have the channel CommGuKhazModadb3bb5
"Khaz Modan Reserve" would have the channel CommGuKhazModa0e3045
"KhazModanBrigade" would have the channel CommGuKhazModa4209dd
"KhazMødanBrigade" would have the channel CommGuKhazMdanfa944d
To get the guild name checksum, the following method can be used. It makes some concessions to prevent collisions in our specific case, but can be used as a general algorithm as well. In order to keep the channel names as clean as possible for the users, we've capped the checksums to six hex characters. Thanks to Iriel for the suggestion.
 local SOME_PRIME = 16777213
 CheckSum = function(text)
 	local counter = 1
 	local len = string.len(text)
 	for i=1,len do
 		counter = counter + string.byte(text, i)*math.pow(31, len- i)
 	end
 	counter = math.mod(counter, SOME_PRIME)
 	return string.format("%06x", counter)
 end
Using CheckSum and Strip from above, the following function could be used to get a full channel name from a string:
 function GuildChannelName(name)
 local cs = CheckSum(name)
 name = Strip(name)
 name = string.sub(name, 1, 8)
 return "CommGu" .. name .. cs
 end
=== Channel Management ===
There are a few things addons will need to do if they are using a channel:
* Leadership changes must result in leaving the old channel and joining the new one.
* Assure that unused channels are departed, some sort of global registrar table should be used to assure the channel is not closed when someone else is using it.
* ChatFrames must have the comm channel messages suppressed. To prevent duplicate processing a global registrar should be used to indicate if a channel is being filtered. Since one cannot effectively unhook the filter can remain in place after established.
* At the very beginning, about 2 seconds after PLAYER_LOGIN, a check needs to be done to get rid of dead Comm* channels.
== Global Registrar ==
A simple table could be used to maintain a registrar. Any addon using the comm protocol would look for this table when they need to register into a channel. If it doesn't exist they would create the basic empty table and add themselves in.
==== Proposed table structure ====
 AddonCommRegistrar = {
 -- Stores a list of addons currently using a channel
 -- Note that the indexes here don't have to be strings,
 -- anything can be used as long as it's unique to the addon
 channels = { 
 CommGlobal = { 
 ["Sample addon 1"] = true,
 ["Sample addon 2"] = true,
 },
 CommRa = {
 ["Sample raid addon"] = true, 
 },
 CommGuKhazModa12345678 = {
 ["Sample guild addon"] = true, 
 },
 },
 
 -- Flag indicating that a ChatFrame fliter is in place
 filter = true,
 
 -- GetTime() of the next raid message that can be sent.
 raidThrottle = 453278.329,
 -- GetTime() of the next guild message that can be sent.
 guildThrottle = 483587.467,
 -- GetTime() of the next global message that can be sent.
 globalThrottle = 454643.745,
 
 -- Name of the leader currently used for the group channel
 -- this is in place so that only one addon performs a channel switch on leadership change
 groupleader = "Joebob",
 }
==== Registering a channel ====
When registering into a channel, an addon should execute this block of code:
 -- Establish the registrar
 if not AddonCommRegistrar then 
 AddonCommRegistrar = {
 channels = {}, 
 }
 end
 
 -- Establish the channel registrar
 if not AddonCommRegistrar.channels[channelname] then 
 AddonCommRegistrar.channels[channelname] = {}
 end
 
 AddonCommRegistrar.channels[channelname]["Sample addon"] = true
 
 -- Implement a ChatFrame filter for our channel if one does not exist
 if not AddonCommRegistrar.filter then
 -- Hook ChatFrame_OnEvent here
 AddonCommRegistrar.filter = true
 end
 
 -- Join the channel
 if GetChannelName(channelname) == 0 then
 JoinChannelByName(channename)
 end
==== Unregistering a channel ====
When an addon is done with a channel it needs to check if anyone else is using it, if noone is it should make sure the channel is departed.
 AddonCommRegistrar.channels[channelname]["Sample addon"] = nil
 if not next(AddonCommRegistrar.channels[channelname]) then
 -- Leave the channel
 LeaveChannelByName(channelname)
 end
==== Leadership Changes ====
When the raid or party leader changes, each addon that uses the CommRa* channel should check to see if the switch has been made yet, if not it should perform the switch. Also it should be noted that leaving a channel has a slight delay, the best solution appears to be to delay 1 second between leaving and joining.
 -- To be performed when the raid leader changes
 if AddonCommRegistrar.groupleader ~= newleadername then
 AddonCommRegistrar.groupleader = newleadername
 LeaveChannelByName("CommRa".. oldleadername)
 -- Delay 1 second, by some means...
 JoinChannelByName("CommRa".. newleadername)
 end
==== Message throttling ====
Message throttling has it's advantages, but the jury's still out on if the protocol should require throttling or not, further details and discussion can be found at Addon Comm Throttling
== Chatframe Filtering ==
Here is a proposed hook for the ChatFrame_OnEvent to block all Comm text from ever appearing in the default UI's chatframes. Note that only the first addon to register should implement this (see sample in Registering a channel)
 local filters = {"^commglobal$", "^commgu", "^commra"}
 local cf_oe = ChatFrame_OnEvent
 ChatFrame_OnEvent = function(event)
 if event == "CHAT_MSG_CHANNEL" then
 local chan = string.lower(arg9)
 for _,str in pairs(filters) do
 if string.find(chan, str) then return end
 end
 end
 cf_oe(event)
 end
== Message Protocol ==
A standard protocol for messages sent should be used to assure addons do not unintentionally receive other addon's messages. The proposed standard is: "<Namespace>: <message>" <Namespace> is some unique token that a develop's addons use. Some devs may choose to share a token across their addons, like "Tekkub" or they may make it addon-specific like "CTRA". Handling of the <message> portion of the communication is entirely placed upon the receiving addon. This will allow for simple filtering of messages intended for other addons.
It is recommended that use of the chars 's' and 'S' in the <Namespace> section of the message be avoided, especially if the addon does not use a sobriety filter. For example, if the addon only communicates numbers the filter would not need to be implemented, it's just a wasted function in this case. Please see Addon Comm Sobriety Filter for possible implementations of this filter.
Category:Requests For Comment