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