root/trunk/jjigw/channel.py

Revision 165, 18.9 kB (checked in by jajcus, 3 years ago)

- addresses updated

Line 
1 #!/usr/bin/python -u
2 #
3 #  Jajcus' Jabber to IRC Gateway
4 #  Copyright (C) 2004  Jacek Konieczny <jajcus@jajcus.net>
5 #
6 #  This program is free software; you can redistribute it and/or modify
7 #  it under the terms of the GNU General Public License as published by
8 #  the Free Software Foundation; either version 2 of the License, or
9 #  (at your option) any later version.
10 #
11 #  This program is distributed in the hope that it will be useful,
12 #  but WITHOUT ANY WARRANTY; without even the implied warranty of
13 #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 #  GNU General Public License for more details.
15 #
16 #  You should have received a copy of the GNU General Public License along
17 #  with this program; if not, write to the Free Software Foundation, Inc.,
18 #  59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
19
20 import string
21 import logging
22
23 from pyxmpp.message import Message
24 from pyxmpp.jid import JID
25 from pyxmpp.jabber.muc import MucPresence,MucItem,MucStatus
26
27 from requests import Request,RequestQueue
28 from common import channel_re
29 from common import normalize,remove_evil_characters,strip_colors
30 from ircuser import IRCUser
31
32 class Channel:
33     toggle_modes="aimnqpsrt"
34     arg_modes="kl"
35     multiarg_modes="OovbeI"
36     def __init__(self,session,name):
37         self.__logger=logging.getLogger("jjigw.Channel")
38         if not channel_re.match(name):
39             raise ValueError,"Bad channel name"
40         self.name=name
41         self.session=session
42         self.state=None
43         self.room_jid=None
44         self.config=session.network.get_channel_config(name)
45         if self.config and self.config.encoding:
46             self.encoding=self.config.encoding
47         else:
48             self.encoding=session.default_encoding
49         self.modes={}
50         self.users=[self.session.user]
51         self.muc=0
52         self.requests=RequestQueue(10)
53
54     def sync_user(self,user,status=None):
55         if user.channels.has_key(normalize(self.name)):
56             if user not in self.users:
57                 self.users.append(user)
58         else:
59             for m in self.multiarg_modes:
60                 ul=self.modes.get(m,[])
61                 if user in ul:
62                     ul.remove(user)
63             if user in self.users:
64                 self.users.remove(user)
65                 self.send_notice_message(u"%s has quit"
66                         % (unicode(user.nick,self.encoding,"replace"),))
67         if self.state:
68             p=self.get_user_presence(user,status=status)
69             self.session.component.send(p)
70
71     def send_notice_message(self,msg,not_in_muc=1):
72         if not self.state or (self.muc and not_in_muc):
73             return
74         m=Message(from_jid=self.room_jid.bare(),to_jid=self.session.jid,stanza_type="groupchat",body=msg)
75         self.session.component.send(m)
76
77     def join(self,stanza):
78         if self.state:
79             self.__logger.debug("Channel %r not in the initial state, not joining!" % (self.name,))
80             p=stanza.make_error_response(stanza,"bad-request")
81             self.session.component.send(p)
82             return
83         self.room_jid=stanza.get_to()
84         self.__logger.debug("Joining channel %r" % (self.name,))
85         self.session.send("JOIN %s" % (self.name,))
86         self.requests.add("JOIN",stanza)
87         self.state="join"
88         if stanza.get_join_info():
89             self.muc=1
90
91     def leave(self,stanza):
92         status=stanza.get_status()
93         if not self.state:
94             self.__logger.debug("Channel %r in the initial state - nothing to do." % (self.name,))
95         else:
96             if not status:
97                 self.session.send("PART %s" % (self.name,))
98             else:
99                 self.session.send("PART %s :%s" % (self.name,
100                         status.encode(self.encoding,"replace")))
101             self.state=None
102         p=MucPresence(stanza_type="unavailable",from_jid=stanza.get_to(),to_jid=stanza.get_from(),status=status)
103         self.session.component.send(p)
104         for u in self.users:
105             u.leave_channel(self)
106         self.state=None
107
108     def prefix_to_jid(self,prefix):
109         if "!" in prefix:
110             return self.nick_to_jid(prefix.split("!")[0])
111         return self.nick_to_jid(prefix)
112
113     def nick_to_jid(self,nick):
114         return JID(self.room_jid.node,self.room_jid.domain,
115                 unicode(nick,self.encoding,"replace"))
116
117     def get_user_presence(self,user,nick=None,actor=None,reason=None,status=None):
118         if self.state and user in self.users:
119             p=MucPresence(from_jid=self.nick_to_jid(user.nick),to_jid=self.session.jid)
120         else:
121             p=MucPresence(stanza_type="unavailable",from_jid=self.nick_to_jid(user.nick),to_jid=self.session.jid)
122         if self.muc:
123             if user in self.modes.get("o",[]):
124                 aff="admin"
125                 role="moderator"
126             elif user in self.modes.get("v",[]):
127                 aff="member"
128                 role="participant"
129             elif self.modes.get("m"):
130                 aff="none"
131                 role="visitor"
132             elif user in self.users:
133                 aff="none"
134                 role="participant"
135             else:
136                 aff="none"
137                 role="none"
138             ui=p.make_muc_userinfo()
139             if nick:
140                 nick=unicode(user.nick,self.encoding,"replace")
141             it=MucItem(aff,role,user.jid(),nick=nick,actor=actor,reason=reason)
142             ui.add_item(it)
143             if status:
144                 ui.add_item(MucStatus(status))
145         return p
146
147     def nick_changed(self,oldnick,user):
148         self.__logger.debug("Nick changed: %r -> %r" % (oldnick,user.nick))
149         p_unaval=self.get_user_presence(user,nick=user.nick,status=303)
150         p_unaval.set_type("unavailable")
151         p_unaval.set_show(None)
152         p_unaval.set_status(None)
153         p_unaval.set_from(self.nick_to_jid(oldnick))
154         p_aval=self.get_user_presence(user,status=303)
155         self.session.component.send(p_unaval)
156         self.session.component.send(p_aval)
157         self.send_notice_message(u"%s is now known as %s"
158                 % (unicode(oldnick,self.encoding,"replace"),
159                     unicode(user.nick,self.encoding,"replace")))
160
161     def set_mode(self,mode,arg):
162         if mode in self.toggle_modes:
163             self.modes[mode]=1
164         elif mode in self.arg_modes:
165             self.modes[mode]=arg
166         elif mode in self.multiarg_modes:
167             if self.modes.has_key(mode):
168                 self.modes[mode].append(arg)
169             else:
170                 self.modes[mode]=[arg]
171
172     def reset_mode(self,mode,arg):
173         try:
174             if mode in self.toggle_modes:
175                 del self.modes[mode]
176             elif mode in self.arg_modes:
177                 del self.modes[mode]
178             elif mode in self.multiarg_modes:
179                 if self.modes.has_key(mode):
180                     self.modes[mode].remove(arg)
181                     if not self.modes[mode]:
182                         del self.modes[mode]
183         except (KeyError,ValueError):
184             pass
185
186     def irc_cmd_324(self,prefix,command,params): # RPL_CHANNELMODEIS
187         for m in self.toggle_modes:
188             try:
189                 del self.modes[m]
190             except KeyError:
191                 pass
192         self.irc_mode_changed(prefix,command,params)
193
194     def irc_cmd_482(self,prefix,command,params): # ERR_CHANOPRIVSNEEDED
195         self.irc_error_response(prefix,command,params,["TOPIC","KICK","MODE"],"forbidden")
196
197     def irc_cmd_461(self,prefix,command,params): # ERR_NEEDMOREPARAMS
198         self.irc_error_response(prefix,command,params,["TOPIC","KICK","MODE"],"bad-request")
199
200     def irc_cmd_403(self,prefix,command,params): # ERR_NOSUCHCHANNEL
201         self.irc_error_response(prefix,command,params,["KICK"],"recipient-unavailable")
202
203     def irc_cmd_476(self,prefix,command,params): # ERR_BADCHANMASK
204         self.irc_error_response(prefix,command,params,["KICK"],"bad-request")
205
206     def irc_cmd_441(self,prefix,command,params): # ERR_USERNOTINCHANNEL
207         self.irc_error_response(prefix,command,params,["KICK","MODE"],"item-not-found")
208
209     def irc_cmd_442(self,prefix,command,params): # ERR_NOTONCHANNEL
210         self.irc_error_response(prefix,command,params,["TOPIC","KICK"],"forbidden")
211
212     def irc_cmd_472(self,prefix,command,params): # ERR_UNKNOWNMODE
213         self.irc_error_response(prefix,command,params,["MODE"],"feature-not-implemented")
214
215     def irc_cmd_477(self,prefix,command,params): # ERR_NOCHANMODES
216         # this often is not an error
217         pass
218         #self.irc_error_response(prefix,command,params,["TOPIC","MODE"],"not-acceptable")
219
220     def irc_error_response(self,prefix,command,params,requests,condition):
221         r=self.requests.get(requests)
222         if r:
223             m=r.stanza.make_error_response(condition)
224         else:
225             m=Message(from_jid=self.room_jid.bare(),to_jid=self.session.jid,
226                     stanza_type="error", error_cond=condition)
227         self.session.component.send(m)
228
229     def irc_cmd_331(self,prefix,command,params): # RPL_NOTOPIC
230         m=Message(from_jid=self.room_jid.bare(),to_jid=self.session.jid, stanza_type="groupchat", subject=u"")
231         self.session.component.send(m)
232
233     def irc_cmd_332(self,prefix,command,params): # RPL_TOPIC
234         topic=remove_evil_characters(params[1])
235         m=Message(from_jid=self.room_jid.bare(),to_jid=self.session.jid,
236                 stanza_type="groupchat", subject=unicode(topic,self.encoding,"replace"))
237         self.session.component.send(m)
238
239     def irc_cmd_TOPIC(self,prefix,command,params):
240         self.requests.get("TOPIC")
241         topic=remove_evil_characters(params[1])
242         m=Message(from_jid=self.prefix_to_jid(prefix),to_jid=self.session.jid,
243                 stanza_type="groupchat", subject=unicode(topic,self.encoding,"replace"))
244         self.session.component.send(m)
245
246     def irc_cmd_MODE(self,prefix,command,params):
247         if len(params)<2:
248             self.__logger.debug("No parameters in received MODE")
249             return
250         params_str=string.join(params[2:]," ").strip()
251         if params_str:
252             params_str=" "+params_str
253         if "!" in prefix:
254             nick,iuser=prefix.split("!",1)
255             iuser="(%s)" % (iuser,)
256         else:
257             nick,iuser=prefix,""
258         self.send_notice_message(u"Mode change: [%s%s] by %s%s"
259                 % (unicode(params[1],self.encoding,"replace"),
260                         unicode(params_str,self.encoding,"replace"),
261                         unicode(nick,self.encoding,"replace"),
262                         unicode(iuser,self.encoding,"replace")),
263                 0)
264         if self.session.check_prefix(prefix) and len(params)>=3:
265             r=self.requests.get("MODE",string.join(params[1:]," "))
266             if r:
267                 reply=r.stanza.make_result_response()
268                 self.session.component.send(reply)
269         self.irc_mode_changed(prefix,command,params)
270
271     def irc_mode_changed(self,prefix,command,params):
272         actor=self.session.get_user(prefix)
273         modes=params[1]
274         params=params[2:]
275         pm=None
276         for m in modes:
277             if m in "+-":
278                 pm=m
279                 continue
280             elif not pm:
281                 self.__logger.debug("Not '+' or '-' before '%s' in received MODE" % (m,))
282                 continue
283             elif m in self.arg_modes or m in self.multiarg_modes:
284                 if not len(params):
285                     self.__logger.debug("No argument for mode '%s' in received MODE" % (m,))
286                     continue
287                 arg=params.pop(0)
288             elif m in self.toggle_modes:
289                 arg=None
290             else:
291                 self.__logger.debug("Unknown mode '%s' in received MODE" % (m,))
292                 continue
293             if m in "oOv":
294                 arg=self.session.get_user(arg)
295                 if not arg:
296                     continue
297             if pm=="+":
298                 self.set_mode(m,arg)
299             else:
300                 self.reset_mode(m,arg)
301             if m in "oOv":
302                 self.user_mode_changed(arg,actor,m)
303             elif m=="m":
304                 for u in self.users:
305                     self.sync_user(u)
306
307     def user_mode_changed(self,user,actor,mode):
308         if actor:
309             actor_jid=self.nick_to_jid(actor.nick)
310         else:
311             actor_jid=None
312         self.__logger.debug("Mode changed: %r by %r" % (user.nick,actor_jid))
313         p=self.get_user_presence(user,actor_jid)
314         if actor:
315             by=u" by %s" % (unicode(actor.nick,self.encoding,"replace"),)
316         else:
317             by=u""
318         self.session.component.send(p)
319         if mode=="v":
320             self.send_notice_message(u"%s was granted voice%s"
321                     % (unicode(user.nick,self.encoding,"replace"),by))
322         elif mode=="o":
323             self.send_notice_message(u"%s was granted operator status%s"
324                     % (unicode(user.nick,self.encoding,"replace"),by))
325         elif mode=="O":
326             self.send_notice_message(u"%s was granted got owner status%s"
327                     % (unicode(user.nick,self.encoding,"replace"),by))
328
329     def irc_cmd_JOIN(self,prefix,command,params):
330         nprefix=normalize(prefix)
331         nnick=normalize(self.session.nick)
332         if nprefix==nnick or nprefix.startswith(nnick+"!"):
333             if self.state=="join":
334                 self.__logger.debug("Channel %r joined!" % (self.name,))
335                 self.session.user.sync_delay+=1
336                 try:
337                     self.session.user.join_channel(self)
338                 finally:
339                     self.session.user.sync_delay-=1
340                 self.state="joined"
341                 self.requests.get("JOIN")
342                 self.session.send("MODE %s" % (self.name,))
343                 self.session.send("WHO %s" % (self.name,))
344         else:
345             user=self.session.get_user(prefix)
346             user.join_channel(self)
347             self.send_notice_message(u"%s has joined"
348                     % (unicode(user.nick,self.encoding,"replace"),))
349             self.session.send("WHO %s" % (user.nick,))
350
351     def irc_cmd_PART(self,prefix,command,params):
352         user=self.session.get_user(prefix)
353         try:
354             self.users.remove(user)
355         except ValueError:
356             pass
357         user.leave_channel(self)
358         if self.session.check_prefix(prefix):
359             self.session.channel_left(self)
360         else:
361             self.send_notice_message(u"%s has left"
362                     % (unicode(user.nick,self.encoding,"replace"),))
363
364     def irc_cmd_KICK(self,prefix,command,params):
365         actor=self.session.get_user(prefix)
366         user=self.session.get_user(params[1])
367         try:
368             self.users.remove(user)
369         except ValueError:
370             pass
371         self.send_notice_message(u"%s was kicked by %s"
372                 % (unicode(user.descr(),self.encoding,"replace"),
373                     unicode(actor.descr(),self.encoding,"replace")),
374                 0)
375         user.leave_channel(self,status=307)
376         if user and self.session.check_prefix(prefix):
377             r=self.requests.get("KICK",user.nick)
378             if r:
379                 iq=r.stanza.make_result_response()
380                 self.session.component.send(iq)
381         if self.session.check_nick(params[1]):
382             self.session.channel_left(self)
383
384     def irc_cmd_PRIVMSG(self,prefix,command,params):
385         self.irc_message(prefix,command,params)
386
387     def irc_cmd_NOTICE(self,prefix,command,params):
388         self.irc_message(prefix,command,params)
389
390     def irc_message(self,prefix,command,params):
391         if not self.state or len(params)<2:
392             self.__logger.debug("ignoring it")
393             return
394         body=unicode(params[1],self.encoding,"replace")
395         if body[0]=="\001" and body[-1]=="\001":
396             self.CTCP(prefix,body[1:-1])
397         else:
398             m=Message(stanza_type="groupchat",from_jid=self.prefix_to_jid(prefix),to_jid=self.session.jid,
399                     body=remove_evil_characters(strip_colors(body)))
400             self.session.component.send(m)
401
402     def CTCP(self,prefix,command):
403         if " " in command:
404             command,arg=command.split(" ",1)
405         else:
406             arg=None
407         if command=="ACTION":
408             m=Message(stanza_type="groupchat",from_jid=self.prefix_to_jid(prefix),to_jid=self.session.jid,
409                     body="/me "+remove_evil_characters(strip_colors(arg)))
410             self.session.component.send(m)
411         else:
412             self.__logger.debug("Unknown CTCP command: %r %r" % (command,arg))
413
414     def change_topic(self,topic,stanza):
415         topic=topic.encode(self.encoding,"replace")
416         topic=topic.replace("\n"," ").replace("\r"," ")
417         self.session.send("TOPIC %s :%s" % (self.name,topic))
418         self.requests.add("TOPIC",stanza)
419
420     def kick_user(self,nick,reason,stanza):
421         nick=nick.encode(self.encoding,"strict")
422         self.session.send("KICK %s %s :%s" % (self.name,nick,reason))
423         self.requests.add("KICK",stanza,nick)
424
425     def op_user(self,nick,stanza):
426         nick=nick.encode(self.encoding,"strict")
427         user=self.session.users.get(normalize(nick))
428         if not user in self.users:
429            r=stanza.make_error_response("item-not-found")
430            self.session.component.send(r)
431            return
432         if user in self.modes.get("o",[]):
433            r=stanza.make_result_response()
434            self.session.component.send(r)
435            return
436         if user in self.modes.get("v",[]):
437             change="-v+o %s %s" % (nick,nick)
438         else:
439             change="+o "+nick
440         self.session.send("MODE %s %s" % (self.name,change))
441         self.requests.add("MODE",stanza,change)
442
443     def voice_user(self,nick,stanza):
444         nick=nick.encode(self.encoding,"strict")
445         user=self.session.users.get(normalize(nick))
446         if not user in self.users:
447            r=stanza.make_error_response("item-not-found")
448            self.session.component.send(r)
449            return
450         if user in self.modes.get("v",[]):
451            r=stanza.make_result_response()
452            self.session.component.send(r)
453            return
454         if user in self.modes.get("o",[]):
455             change="-o+v %s %s" % (nick,nick)
456         else:
457             change="+v "+nick
458         self.session.send("MODE %s %s" % (self.name,change))
459         self.requests.add("MODE",stanza,change)
460
461     def devoice_user(self,nick,stanza):
462         nick=nick.encode(self.encoding,"strict")
463         user=self.session.users.get(normalize(nick))
464         if not user in self.users:
465            r=stanza.make_error_response("item-not-found")
466            self.session.component.send(r)
467            return
468         if user in self.modes.get("v",[]) and user in self.modes.get("o",[]):
469            change="-o-v %s %s" % (nick,nick)
470         elif user in self.modes.get("o",[]):
471             change="-o "+nick
472         elif user in self.modes.get("v",[]):
473             change="-v "+nick
474         else:
475            r=stanza.make_result_response()
476            self.session.component.send(r)
477            return
478         self.session.send("MODE %s %s" % (self.name,change))
479         self.requests.add("MODE",stanza,change)
480
481     def __repr__(self):
482         return "<IRCChannel %r>" % (self.name,)
483
484 # vi: sts=4 et sw=4
485
Note: See TracBrowser for help on using the browser.