|
1 abstract class Module : Object { |
|
2 protected weak Config cfg; |
|
3 protected weak Connection conn; |
|
4 |
|
5 public Module(Config cfg, Connection conn) { |
|
6 this.cfg = cfg; |
|
7 this.conn = conn; |
|
8 } |
|
9 public abstract string name(); |
|
10 } |
|
11 |
|
12 class ModuleMuc : Module { |
|
13 public enum Affiliation { |
|
14 NONE, |
|
15 OUTCAST, |
|
16 MEMBER, |
|
17 ADMIN, |
|
18 OWNER |
|
19 } |
|
20 |
|
21 public enum Role { |
|
22 NONE, |
|
23 VISITOR, |
|
24 PARTICIPANT, |
|
25 MODERATOR |
|
26 } |
|
27 |
|
28 static Role role_from_string(string role) { |
|
29 switch (role) { |
|
30 case "moderator": return Role.MODERATOR; break; |
|
31 case "participant": return Role.PARTICIPANT; break; |
|
32 case "visitor": return Role.VISITOR; break; |
|
33 } |
|
34 return Role.NONE; |
|
35 } |
|
36 static Affiliation affil_from_string(string affil) { |
|
37 switch (affil) { |
|
38 case "owner": return Affiliation.OWNER; break; |
|
39 case "admin": return Affiliation.ADMIN; break; |
|
40 case "member": return Affiliation.MEMBER; break; |
|
41 case "outcast": return Affiliation.OUTCAST; break; |
|
42 } |
|
43 return Affiliation.NONE; |
|
44 } |
|
45 |
|
46 public class Occupant : Object { |
|
47 public weak Conference conference; |
|
48 public string nick; |
|
49 public string real_jid; |
|
50 public Affiliation affil; |
|
51 public Role role; |
|
52 public bool isme; |
|
53 } |
|
54 |
|
55 public enum State { |
|
56 CONNECTED, |
|
57 DISCOVERING, |
|
58 CONNECTING, |
|
59 DISCONNECTING, |
|
60 DISCONNECTED |
|
61 } |
|
62 |
|
63 public signal void state_changed(Conference conf, State old_state, State new_state, string description); |
|
64 public signal void on_join(Conference conf, Occupant occupant); |
|
65 public signal void on_part(Conference conf, Occupant occupant); |
|
66 public signal void on_role(Conference conf, Occupant occupant, Role prev); |
|
67 public signal void on_affil(Conference conf, Occupant occupant, Affiliation prev); |
|
68 public signal void on_nick(Conference conf, Occupant occupant, string prev); |
|
69 public signal void on_subject(Conference conf); |
|
70 |
|
71 public class Conference : Object { |
|
72 public string jid; |
|
73 public string nick; |
|
74 public string desired_nick; |
|
75 public string subject; |
|
76 |
|
77 protected State _state; |
|
78 public Time time; |
|
79 public State state { get { return _state; } } |
|
80 public Gee.HashMap<string,Occupant> occupants; |
|
81 protected string presence_join_id; |
|
82 protected weak ModuleMuc module; |
|
83 |
|
84 protected void _change_state(State new_state, string description) { |
|
85 var old_state = this._state; |
|
86 if (old_state != new_state) { |
|
87 this._state = new_state; |
|
88 stderr.printf("MUC State changed %s -> %s : %s\n",old_state.to_string(), new_state.to_string(), description); |
|
89 module.state_changed(this, old_state, new_state, description); |
|
90 } else |
|
91 stderr.printf("MUC State not changed %s : %s\n",old_state.to_string(), description); |
|
92 } |
|
93 |
|
94 public Conference(ModuleMuc module, string jid) { |
|
95 this.module = module; |
|
96 var section = "muc "+jid; |
|
97 this.desired_nick = module.cfg[section,"nick"]; |
|
98 this.nick = this.desired_nick; |
|
99 this.jid = jid; |
|
100 this._state = State.DISCONNECTED; |
|
101 this.occupants = new Gee.HashMap<string,Occupant>(); |
|
102 } |
|
103 public void join(string desc) { |
|
104 if (_state == State.DISCONNECTED) { |
|
105 var prs = new Lm.Message(jid+"/"+desired_nick, Lm.MessageType.PRESENCE); |
|
106 presence_join_id = jid+"_"+Random.next_int().to_string(); |
|
107 prs.node.set_attribute("id",presence_join_id); |
|
108 prs.node.add_child("x",null).set_attribute("xmlns","http://jabber.org/protocol/muc"); |
|
109 module.conn.cn.send(prs); |
|
110 nick = desired_nick; |
|
111 _change_state(State.CONNECTING, "requested to join"); |
|
112 } |
|
113 } |
|
114 |
|
115 public Lm.HandlerResult muc_handler(Lm.MessageHandler handler, Lm.Connection connection, Lm.Message message) { |
|
116 var node = message.node; |
|
117 var from = node.get_attribute("from").split("/",2); |
|
118 stdout.printf("MUC<%s>: %s\n",this.jid,node.to_string()); |
|
119 var type = node.get_attribute("type"); |
|
120 if (message.get_type()==Lm.MessageType.PRESENCE) { |
|
121 switch (type) { |
|
122 case null: |
|
123 case "unavailable": |
|
124 if (from.length==2) { |
|
125 var statuses = new Gee.HashSet<int>(); |
|
126 |
|
127 var x = node.get_child("x"); |
|
128 var child = x!=null ? x.children : null; |
|
129 while (child != null) { |
|
130 stdout.printf("Child %s %s\n", child.name, child.get_attribute("code")); |
|
131 if (child.name == "status") { |
|
132 var code = child.get_attribute("code"); |
|
133 if (code!=null) |
|
134 statuses.add(code.to_int()); |
|
135 } |
|
136 child = child.next; |
|
137 } |
|
138 |
|
139 if (statuses.contains(110) && (_state == State.CONNECTED)) |
|
140 log("Muc", LogLevelFlags.LEVEL_INFO, "Joined a room which I was already in: %s", from[0]); |
|
141 |
|
142 var occupant = occupants[from[1]]; |
|
143 var item = x != null ? x.get_child("item") : null; |
|
144 var affil = Affiliation.NONE; |
|
145 var role = Role.NONE; |
|
146 string nick = null; |
|
147 if (item != null) { |
|
148 affil = affil_from_string(item.get_attribute("affiliation")); |
|
149 role = role_from_string(item.get_attribute("role")); |
|
150 nick = item.get_attribute("nick"); |
|
151 } else |
|
152 log("Muc", LogLevelFlags.LEVEL_ERROR, "Your MUC server is shit. No role and affiliation info in presences: %s", node.to_string()); |
|
153 if (occupant != null) { |
|
154 if (role == Role.NONE) { |
|
155 occupants.unset(from[1]); |
|
156 log("Muc", LogLevelFlags.LEVEL_INFO, "MUC<%s> %s has parted.", this.jid, from[1]); |
|
157 module.on_part(this, occupant); |
|
158 if (from[1]==this.nick) { |
|
159 _change_state(State.DISCONNECTED, "we became unavailable"); |
|
160 occupants.clear(); |
|
161 } |
|
162 } else { |
|
163 if (affil != occupant.affil) { |
|
164 var prev = occupant.affil; |
|
165 occupant.affil = affil; |
|
166 module.on_affil(this, occupant, prev); |
|
167 } |
|
168 if (role != occupant.role) { |
|
169 var prev = occupant.role; |
|
170 occupant.role = role; |
|
171 module.on_role(this, occupant, prev); |
|
172 } |
|
173 if (statuses.contains(303) && (nick!=null)) { |
|
174 occupants[nick] = occupant; |
|
175 occupants.unset(from[1]); |
|
176 occupant.nick = nick; |
|
177 module.on_nick(this, occupant, from[1]); |
|
178 } |
|
179 } |
|
180 } else { |
|
181 assert( role!= Role.NONE); |
|
182 occupant = new Occupant(); |
|
183 occupant.role = role; |
|
184 occupant.affil = affil; |
|
185 occupant.nick = from[1]; |
|
186 occupants[from[1]] = occupant; |
|
187 occupant.isme = statuses.contains(110); |
|
188 log("Muc", LogLevelFlags.LEVEL_INFO, "MUC<%s> %s has joined as %s/%s.", this.jid, from[1], affil.to_string(), role.to_string()); |
|
189 if (statuses.contains(110)) |
|
190 _change_state(State.CONNECTED, "got own presence. we are "+this.nick); |
|
191 module.on_join(this, occupant); |
|
192 } |
|
193 |
|
194 stdout.printf("User list: "); |
|
195 foreach (var a in occupants.keys) |
|
196 stdout.printf("%s <%s,%s>, ", a, occupants[a].affil.to_string(), occupants[a].role.to_string()); |
|
197 stdout.printf("\n"); |
|
198 } |
|
199 |
|
200 break; |
|
201 case "error": |
|
202 if ((from.length==1)||(node.get_attribute("id")==presence_join_id)) { |
|
203 _change_state(State.DISCONNECTED, node.get_child("error").to_string()); |
|
204 return Lm.HandlerResult.ALLOW_MORE_HANDLERS; |
|
205 } else if (from.length==2) { |
|
206 //if (from[1]==this.nick) |
|
207 } |
|
208 break; |
|
209 } |
|
210 } else if ((message.get_type()==Lm.MessageType.MESSAGE)&&(type=="groupchat")) { |
|
211 var subj = node.get_attribute("subject"); |
|
212 if (subj != null) { |
|
213 if (subj != this.subject) { |
|
214 this.subject = subj; |
|
215 module.on_subject(this); |
|
216 } |
|
217 } |
|
218 } |
|
219 return Lm.HandlerResult.ALLOW_MORE_HANDLERS; |
|
220 } |
|
221 public void part(string desc) { |
|
222 } |
|
223 } |
|
224 |
|
225 public override string name() { return "muc"; } |
|
226 |
|
227 public Gee.HashMap<string,Conference> rooms; |
|
228 |
|
229 public ModuleMuc(Config cfg, Connection conn) { |
|
230 base(cfg,conn); |
|
231 this.rooms = new Gee.HashMap<string,Conference>(); |
|
232 |
|
233 var muc_handler = new Lm.MessageHandler((handler, connection, message) => { |
|
234 var node = message.node; |
|
235 var from = node.get_attribute("from"); |
|
236 if (from != null) { |
|
237 var froms = from.split("/",2); |
|
238 if (rooms.has_key(froms[0])) { |
|
239 return rooms[froms[0]].muc_handler(handler, connection, message); |
|
240 } |
|
241 } |
|
242 return Lm.HandlerResult.ALLOW_MORE_HANDLERS; |
|
243 }, null); |
|
244 conn.cn.register_message_handler(muc_handler, Lm.MessageType.MESSAGE, Lm.HandlerPriority.NORMAL); |
|
245 conn.cn.register_message_handler(muc_handler, Lm.MessageType.PRESENCE, Lm.HandlerPriority.NORMAL); |
|
246 conn.cn.register_message_handler(muc_handler, Lm.MessageType.IQ, Lm.HandlerPriority.NORMAL); |
|
247 conn.state_changed.connect( (olds, news, desc) => { |
|
248 if (news == Connection.State.CONNECTED) { |
|
249 var room = new Conference(this, "говнохост@conference.blasux.ru"); //, "Ζαλυπα"); |
|
250 room.join("Oh hai"); |
|
251 this.rooms[room.jid] = room; |
|
252 } |
|
253 }); |
|
254 } |
|
255 } |