Do not warn about empty email address
[people/stefanha/gpxebot.git] / gpxebot.py
1 #!/usr/bin/env python
2 # Copyright (C) 2008 Stefan Hajnoczi <stefanha@gmail.com>.
3 #
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License as
6 # published by the Free Software Foundation; either version 2 of the
7 # License, or any later version.
8 #
9 # This program is distributed in the hope that it will be useful, but
10 # WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12 # General Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17 import select
18 import socket
19 import sys
20 import time
21 import os
22
23 import config
24 import cmds
25 import utils
26
27 NO_ARGS = -1
28
29 handlers = {}
30
31 def autojoin():
32     del handlers['376']
33
34     # Receive '+' or '-' indicator with every message showing whether the
35     # sender is identified to nick services.  Only identified users may be bot
36     # operators.
37     cmd('CAPAB IDENTIFY-MSG')
38
39     return do_response(cmds.readrc_command())
40
41 def ping(_, arg):
42     cmd('PONG %s' % arg)
43     return False
44
45 def do_response(response):
46     # it should be in the format (command, args) 
47     # or [(command, args), (command, args)...]
48     # args is a tuple
49     if not response:
50         return False
51     if isinstance(response, list):
52         return any(do_response(r) for r in response)
53     print response
54     (command, args) = response
55     if command == 'PRIVMSG':
56         pmsg(*args)
57     elif command == 'CMD':
58         cmd(*args)
59     return command == 'RESTART'
60
61 def privmsg(_, target, msg):
62     # Establish identify of sender from CAPAB IDENTIFY-MSG info
63     is_identified = msg.startswith('+')
64     if msg.startswith('-') or msg.startswith('+'):
65         msg = msg[1:]
66     who = cmds.Who(mask, is_identified)
67
68     utils.do_log(cmds.logs, target, who.nick, msg)
69
70     words = msg.split()
71     if target.startswith('#'):
72         replyto = target
73         if not config.NICK in words[0]:
74             return False
75         words.pop(0)
76     elif target == config.NICK:
77         replyto = who.nick
78
79     return do_response(cmds.do_command(who, target, replyto, words))
80
81 def add_handler(command, handler, nargs):
82     handlers[command] = (handler, nargs)
83
84 def cmd(msg):
85     utils.dbg('WRITE ' + msg)
86     sock.sendall('%s\r\n' % msg)
87
88 def pmsg(target, msg):
89     if target:
90         utils.do_log(cmds.logs, target, config.NICK, msg)
91         cmd('PRIVMSG %s :%s' % (target, msg))
92
93 def dispatch(args):
94     command = args[0]
95     if command in handlers:
96         h = handlers[command]
97         if h[1] == NO_ARGS:
98             return h[0]()
99         elif len(args) == h[1]:
100             return h[0](*args)
101     return False
102
103 def parse(line):
104     if line[0] == ':':
105         mask, line = line[1:].split(None, 1)
106     else:
107         mask = None
108
109     args = []
110     while line and line[0] != ':':
111         fields = line.split(None, 1)
112         if len(fields) == 1:
113             fields.append(None)
114         arg, line = fields
115         args.append(arg)
116     if line:
117         args.append(line[1:])
118
119     return mask, args
120
121 add_handler('376', autojoin, NO_ARGS)
122 add_handler('PING', ping, 2)
123 add_handler('PRIVMSG', privmsg, 3)
124
125 if len(sys.argv) == 3 and sys.argv[1] == '--socket-fd':
126     # Restart with existing IRC session
127     fd = int(sys.argv[2])
128     sock = socket.fromfd(fd, socket.AF_INET, socket.SOCK_STREAM)
129     os.close(fd)
130     autojoin()
131 else:
132     # Connect to server and start new IRC session
133     sock = socket.socket()
134     sock.connect((config.HOST, config.PORT))
135     cmd('NICK %s' % config.NICK)
136     cmd('USER %s none none :%s' % (config.IDENT, config.REALNAME))
137
138 # Try to create command FIFO
139 try:
140     os.mkfifo(config.FIFO)
141 except OSError:
142     pass
143
144 # Opening a fifo for reading with no writers normally blocks, but opening
145 # read/write will not block on Linux.
146 cmdfifo = open(config.FIFO, 'r+')
147
148 restart = False
149 sockbuf = ''
150 while not restart:
151     rlist, _, xlist = select.select([sock, cmdfifo], [], [sock])
152
153     if sock in rlist or sock in xlist:
154         r = sock.recv(4096)
155         if not r:
156             break
157         sockbuf += r
158
159         while '\r\n' in sockbuf:
160             line, sockbuf = sockbuf.split('\r\n', 1)
161             if not line:
162                 continue
163             utils.dbg('READ ' + line)
164             mask, args = parse(line)
165             restart = dispatch(args)
166
167     if cmdfifo in rlist:
168         words = cmdfifo.readline().strip().split()
169         restart = do_response(cmds.do_command(Who(config.NICK, True), config.NICK, config.NICK, words)) or restart
170
171 cmdfifo.close()
172
173 # Restart with existing IRC session
174 fd = sock.fileno()
175 os.execl(sys.argv[0], sys.argv[0], '--socket-fd', str(fd))