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