Log email subject line support
[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 class Who(object):
32     def __init__(self, mask, is_identified=False):
33         self.mask = mask
34         self.nick = mask.split('!', 1)[0]
35         self.is_identified = is_identified
36
37     def is_op(self):
38         return self.is_identified and self.nick in cmds.op_members
39
40 def autojoin():
41     del handlers['376']
42
43     # Receive '+' or '-' indicator with every message showing whether the
44     # sender is identified to nick services.  Only identified users may be bot
45     # operators.
46     cmd('CAPAB IDENTIFY-MSG')
47
48     return do_response(cmds.readrc_command())
49
50 def ping(_, arg):
51     cmd('PONG %s' % arg)
52     return False
53
54 def do_response(response):
55     # it should be in the format (command, args) 
56     # or [(command, args), (command, args)...]
57     # args is a tuple
58     if not response:
59         return False
60     if isinstance(response, list):
61         return any(do_response(r) for r in response)
62     print response
63     (command, args) = response
64     if command == 'PRIVMSG':
65         pmsg(*args)
66     elif command == 'CMD':
67         cmd(*args)
68     return command == 'RESTART'
69
70 def privmsg(_, target, msg):
71     # Establish identify of sender from CAPAB IDENTIFY-MSG info
72     is_identified = msg.startswith('+')
73     if msg.startswith('-') or msg.startswith('+'):
74         msg = msg[1:]
75     who = Who(mask, is_identified)
76
77     utils.do_log(cmds.logs, target, who.nick, msg)
78
79     words = msg.split()
80     if target.startswith('#'):
81         replyto = target
82         if not config.NICK in words[0]:
83             return False
84         words.pop(0)
85     elif target == config.NICK:
86         replyto = who.nick
87
88     return do_response(cmds.do_command(who, target, replyto, words))
89
90 def add_handler(command, handler, nargs):
91     handlers[command] = (handler, nargs)
92
93 def cmd(msg):
94     utils.dbg('WRITE ' + msg)
95     sock.sendall('%s\r\n' % msg)
96
97 def pmsg(target, msg):
98     if target:
99         utils.do_log(cmds.logs, target, config.NICK, msg)
100         cmd('PRIVMSG %s :%s' % (target, msg))
101
102 def dispatch(args):
103     command = args[0]
104     if command in handlers:
105         h = handlers[command]
106         if h[1] == NO_ARGS:
107             return h[0]()
108         elif len(args) == h[1]:
109             return h[0](*args)
110     return False
111
112 def parse(line):
113     if line[0] == ':':
114         mask, line = line[1:].split(None, 1)
115     else:
116         mask = None
117
118     args = []
119     while line and line[0] != ':':
120         fields = line.split(None, 1)
121         if len(fields) == 1:
122             fields.append(None)
123         arg, line = fields
124         args.append(arg)
125     if line:
126         args.append(line[1:])
127
128     return mask, args
129
130 add_handler('376', autojoin, NO_ARGS)
131 add_handler('PING', ping, 2)
132 add_handler('PRIVMSG', privmsg, 3)
133
134 if len(sys.argv) == 3 and sys.argv[1] == '--socket-fd':
135     # Restart with existing IRC session
136     fd = int(sys.argv[2])
137     sock = socket.fromfd(fd, socket.AF_INET, socket.SOCK_STREAM)
138     os.close(fd)
139     autojoin()
140 else:
141     # Connect to server and start new IRC session
142     sock = socket.socket()
143     sock.connect((config.HOST, config.PORT))
144     cmd('NICK %s' % config.NICK)
145     cmd('USER %s none none :%s' % (config.IDENT, config.REALNAME))
146
147 # Try to create command FIFO
148 try:
149     os.mkfifo(config.FIFO)
150 except OSError:
151     pass
152
153 # Opening a fifo for reading with no writers normally blocks, but opening
154 # read/write will not block on Linux.
155 cmdfifo = open(config.FIFO, 'r+')
156
157 restart = False
158 sockbuf = ''
159 while not restart:
160     rlist, _, xlist = select.select([sock, cmdfifo], [], [sock])
161
162     if sock in rlist or sock in xlist:
163         r = sock.recv(4096)
164         if not r:
165             break
166         sockbuf += r
167
168         while '\r\n' in sockbuf:
169             line, sockbuf = sockbuf.split('\r\n', 1)
170             if not line:
171                 continue
172             utils.dbg('READ ' + line)
173             mask, args = parse(line)
174             restart = dispatch(args)
175
176     if cmdfifo in rlist:
177         words = cmdfifo.readline().strip().split()
178         restart = do_response(cmds.do_command(Who(config.NICK, True), config.NICK, config.NICK, words)) or restart
179
180 cmdfifo.close()
181
182 # Restart with existing IRC session
183 fd = sock.fileno()
184 os.execl(sys.argv[0], sys.argv[0], '--socket-fd', str(fd))