diff -r e27ed261417d -r 76caf6a3f413 muc.vala --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/muc.vala Fri Oct 19 00:38:54 2012 +0400 @@ -0,0 +1,255 @@ +abstract class Module : Object { + protected weak Config cfg; + protected weak Connection conn; + + public Module(Config cfg, Connection conn) { + this.cfg = cfg; + this.conn = conn; + } + public abstract string name(); +} + +class ModuleMuc : Module { + public enum Affiliation { + NONE, + OUTCAST, + MEMBER, + ADMIN, + OWNER + } + + public enum Role { + NONE, + VISITOR, + PARTICIPANT, + MODERATOR + } + + static Role role_from_string(string role) { + switch (role) { + case "moderator": return Role.MODERATOR; break; + case "participant": return Role.PARTICIPANT; break; + case "visitor": return Role.VISITOR; break; + } + return Role.NONE; + } + static Affiliation affil_from_string(string affil) { + switch (affil) { + case "owner": return Affiliation.OWNER; break; + case "admin": return Affiliation.ADMIN; break; + case "member": return Affiliation.MEMBER; break; + case "outcast": return Affiliation.OUTCAST; break; + } + return Affiliation.NONE; + } + + public class Occupant : Object { + public weak Conference conference; + public string nick; + public string real_jid; + public Affiliation affil; + public Role role; + public bool isme; + } + + public enum State { + CONNECTED, + DISCOVERING, + CONNECTING, + DISCONNECTING, + DISCONNECTED + } + + public signal void state_changed(Conference conf, State old_state, State new_state, string description); + public signal void on_join(Conference conf, Occupant occupant); + public signal void on_part(Conference conf, Occupant occupant); + public signal void on_role(Conference conf, Occupant occupant, Role prev); + public signal void on_affil(Conference conf, Occupant occupant, Affiliation prev); + public signal void on_nick(Conference conf, Occupant occupant, string prev); + public signal void on_subject(Conference conf); + + public class Conference : Object { + public string jid; + public string nick; + public string desired_nick; + public string subject; + + protected State _state; + public Time time; + public State state { get { return _state; } } + public Gee.HashMap occupants; + protected string presence_join_id; + protected weak ModuleMuc module; + + protected void _change_state(State new_state, string description) { + var old_state = this._state; + if (old_state != new_state) { + this._state = new_state; + stderr.printf("MUC State changed %s -> %s : %s\n",old_state.to_string(), new_state.to_string(), description); + module.state_changed(this, old_state, new_state, description); + } else + stderr.printf("MUC State not changed %s : %s\n",old_state.to_string(), description); + } + + public Conference(ModuleMuc module, string jid) { + this.module = module; + var section = "muc "+jid; + this.desired_nick = module.cfg[section,"nick"]; + this.nick = this.desired_nick; + this.jid = jid; + this._state = State.DISCONNECTED; + this.occupants = new Gee.HashMap(); + } + public void join(string desc) { + if (_state == State.DISCONNECTED) { + var prs = new Lm.Message(jid+"/"+desired_nick, Lm.MessageType.PRESENCE); + presence_join_id = jid+"_"+Random.next_int().to_string(); + prs.node.set_attribute("id",presence_join_id); + prs.node.add_child("x",null).set_attribute("xmlns","http://jabber.org/protocol/muc"); + module.conn.cn.send(prs); + nick = desired_nick; + _change_state(State.CONNECTING, "requested to join"); + } + } + + public Lm.HandlerResult muc_handler(Lm.MessageHandler handler, Lm.Connection connection, Lm.Message message) { + var node = message.node; + var from = node.get_attribute("from").split("/",2); + stdout.printf("MUC<%s>: %s\n",this.jid,node.to_string()); + var type = node.get_attribute("type"); + if (message.get_type()==Lm.MessageType.PRESENCE) { + switch (type) { + case null: + case "unavailable": + if (from.length==2) { + var statuses = new Gee.HashSet(); + + var x = node.get_child("x"); + var child = x!=null ? x.children : null; + while (child != null) { + stdout.printf("Child %s %s\n", child.name, child.get_attribute("code")); + if (child.name == "status") { + var code = child.get_attribute("code"); + if (code!=null) + statuses.add(code.to_int()); + } + child = child.next; + } + + if (statuses.contains(110) && (_state == State.CONNECTED)) + log("Muc", LogLevelFlags.LEVEL_INFO, "Joined a room which I was already in: %s", from[0]); + + var occupant = occupants[from[1]]; + var item = x != null ? x.get_child("item") : null; + var affil = Affiliation.NONE; + var role = Role.NONE; + string nick = null; + if (item != null) { + affil = affil_from_string(item.get_attribute("affiliation")); + role = role_from_string(item.get_attribute("role")); + nick = item.get_attribute("nick"); + } else + log("Muc", LogLevelFlags.LEVEL_ERROR, "Your MUC server is shit. No role and affiliation info in presences: %s", node.to_string()); + if (occupant != null) { + if (role == Role.NONE) { + occupants.unset(from[1]); + log("Muc", LogLevelFlags.LEVEL_INFO, "MUC<%s> %s has parted.", this.jid, from[1]); + module.on_part(this, occupant); + if (from[1]==this.nick) { + _change_state(State.DISCONNECTED, "we became unavailable"); + occupants.clear(); + } + } else { + if (affil != occupant.affil) { + var prev = occupant.affil; + occupant.affil = affil; + module.on_affil(this, occupant, prev); + } + if (role != occupant.role) { + var prev = occupant.role; + occupant.role = role; + module.on_role(this, occupant, prev); + } + if (statuses.contains(303) && (nick!=null)) { + occupants[nick] = occupant; + occupants.unset(from[1]); + occupant.nick = nick; + module.on_nick(this, occupant, from[1]); + } + } + } else { + assert( role!= Role.NONE); + occupant = new Occupant(); + occupant.role = role; + occupant.affil = affil; + occupant.nick = from[1]; + occupants[from[1]] = occupant; + occupant.isme = statuses.contains(110); + log("Muc", LogLevelFlags.LEVEL_INFO, "MUC<%s> %s has joined as %s/%s.", this.jid, from[1], affil.to_string(), role.to_string()); + if (statuses.contains(110)) + _change_state(State.CONNECTED, "got own presence. we are "+this.nick); + module.on_join(this, occupant); + } + + stdout.printf("User list: "); + foreach (var a in occupants.keys) + stdout.printf("%s <%s,%s>, ", a, occupants[a].affil.to_string(), occupants[a].role.to_string()); + stdout.printf("\n"); + } + + break; + case "error": + if ((from.length==1)||(node.get_attribute("id")==presence_join_id)) { + _change_state(State.DISCONNECTED, node.get_child("error").to_string()); + return Lm.HandlerResult.ALLOW_MORE_HANDLERS; + } else if (from.length==2) { + //if (from[1]==this.nick) + } + break; + } + } else if ((message.get_type()==Lm.MessageType.MESSAGE)&&(type=="groupchat")) { + var subj = node.get_attribute("subject"); + if (subj != null) { + if (subj != this.subject) { + this.subject = subj; + module.on_subject(this); + } + } + } + return Lm.HandlerResult.ALLOW_MORE_HANDLERS; + } + public void part(string desc) { + } + } + + public override string name() { return "muc"; } + + public Gee.HashMap rooms; + + public ModuleMuc(Config cfg, Connection conn) { + base(cfg,conn); + this.rooms = new Gee.HashMap(); + + var muc_handler = new Lm.MessageHandler((handler, connection, message) => { + var node = message.node; + var from = node.get_attribute("from"); + if (from != null) { + var froms = from.split("/",2); + if (rooms.has_key(froms[0])) { + return rooms[froms[0]].muc_handler(handler, connection, message); + } + } + return Lm.HandlerResult.ALLOW_MORE_HANDLERS; + }, null); + conn.cn.register_message_handler(muc_handler, Lm.MessageType.MESSAGE, Lm.HandlerPriority.NORMAL); + conn.cn.register_message_handler(muc_handler, Lm.MessageType.PRESENCE, Lm.HandlerPriority.NORMAL); + conn.cn.register_message_handler(muc_handler, Lm.MessageType.IQ, Lm.HandlerPriority.NORMAL); + conn.state_changed.connect( (olds, news, desc) => { + if (news == Connection.State.CONNECTED) { + var room = new Conference(this, "говнохост@conference.blasux.ru"); //, "Ζαλυπα"); + room.join("Oh hai"); + this.rooms[room.jid] = room; + } + }); + } +}