29426db71273428ce013389f1201b8cbd74c2d30
[people/stefanha/gpxebot.git] / cmds.py
1 # Copyright (C) 2010 Stefan Hajnoczi <stefanha@gmail.com>.
2 #
3 # This program is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU General Public License as
5 # published by the Free Software Foundation; either version 2 of the
6 # License, or any later version.
7 #
8 # This program is distributed in the hope that it will be useful, but
9 # WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11 # General Public License for more details.
12 #
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the Free Software
15 # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
16
17 import re
18 import sys
19 import time
20
21 import config
22 import utils
23
24 # For development an errcode database may not be available
25 try:
26     import errcode
27 except ImportError:
28     sys.stderr.write('running without errcode support\n')
29
30 logs = {}
31 commands = {}
32 op_commands = {}
33 op_members = [config.NICK]
34 aliases = {}
35
36 class Who(object):
37     def __init__(self, mask, is_identified=False):
38         self.mask = mask
39         self.nick = mask.split('!', 1)[0]
40         self.is_identified = is_identified
41
42     def is_op(self):
43         return self.is_identified and self.nick in op_members
44
45 def readrc_command(who=None, target=None, replyto=None, words=None):
46     '''Source bot command file (readrc)'''
47     try:
48         cmd = []
49         for line in open(config.RCFILE):
50             response = do_command(Who(config.NICK, True), config.NICK, config.NICK, line.split())
51             if isinstance(response, list):
52                 cmd += response
53             elif response:
54                 cmd.append(response)
55         return cmd
56     except IOError:
57         return 'PRIVMSG', (replyto, 'unable to open file')
58 op_commands['readrc'] = readrc_command
59
60 def alias_command(who, _, replyto, words):
61     '''Add or list email aliases (alias stefanha stefanha@gmail.com)'''
62     if len(words) == 2:
63         if words[1].lower() == 'list':
64             return [('PRIVMSG', (replyto, '%s    %s' % (user, email))) for (user, email) in aliases.items()]
65     if len(words) < 3:
66         return
67     user = words[1]
68     email = words[2]
69     aliases[user] = email
70 op_commands['alias'] = alias_command
71
72 def op_command(who, target, replyto, words):
73     '''Add or list bot operators (op stefanha, op list)'''
74     if len(words) == 2:
75         if words[1].lower() == 'list':
76             return [('PRIVMSG', (replyto, '%s' % nick)) for nick in op_members]
77     if len(words) < 2:
78         return
79     nick = words[1]
80     if not nick in op_members:
81         op_members.append(nick)
82 op_commands['op'] = op_command
83
84 def deop_command(who, target, replyto, words):
85     '''Remove bot operators (deop stefanha)'''
86     if len(words) < 2:
87         return
88     nick = words[1]
89     if nick in op_members:
90         op_members.remove(nick)
91 op_commands['deop'] = deop_command
92
93 ERRCODE_RE = re.compile(r'((?:0x)?[0-9a-fA-F]{8})')
94
95 def errcode_command(who, _, replyto, words):
96     '''Look up gPXE error code (errcode 0x12345678)'''
97     msg = ' '.join(words)
98     m = ERRCODE_RE.search(msg)
99     if m:
100         try:
101             return 'PRIVMSG', (replyto, str(errcode.Errcode(int(m.group(1), 16))))
102         except ValueError:
103             pass
104 commands['errcode'] = errcode_command
105 commands['error'] = errcode_command
106
107 def lspci_command(who, _, replyto, words):
108     '''Look up driver for PCI ID (lspci 10ec:8139)'''
109     ids = []
110     if len(words) == 2:
111         ids = words[1].split(':')
112     elif len(words) == 3:
113         ids = words[1:3]
114     if len(ids) != 2:
115         return 'PRIVMSG', (replyto, 'Invalid arguments')
116
117     try:
118         vendor_id, device_id = [int(s, 16) for s in ids]
119     except ValueError:
120         return 'PRIVMSG', (replyto, 'Invalid arguments')
121     pci_id = '%04x,%04x' % (vendor_id, device_id)
122
123     try:
124         for line in open(config.NICFILE):
125             line = line.rstrip('\r\n')
126             if not line or line.startswith('#'):
127                 continue
128             fields = line.split('\t')
129
130             if fields[0] == 'family':
131                 driver = fields[1].split('/')[-1]
132                 continue
133
134             if fields[1] == pci_id:
135                 return 'PRIVMSG', (replyto, driver)
136     except IndexError, e:
137         utils.dbg(str(e))
138     except IOError, e:
139         utils.dbg(str(e))
140     return 'PRIVMSG', (replyto, 'No driver found')
141 if config.NICFILE:
142     commands['lspci'] = lspci_command
143 else:
144     sys.stderr.write('running without lspci support (NICFILE not given)\n')
145
146 def help_command(who, _, replyto, msg):
147     '''List commands (help)'''
148     def do_help(vocabulary):
149         output = []
150         names = sorted(vocabulary.keys())
151         max_width = max(len(name) for name in names)
152         for name in names:
153             cmd = vocabulary[name]
154             if hasattr(cmd, '__doc__'):
155                 output.append(('PRIVMSG', (replyto, '%s    %s' % (name.ljust(max_width), cmd.__doc__))))
156         return output
157     output = do_help(commands)
158     if who.is_op():
159         output.extend(do_help(op_commands))
160     return output
161 commands['help'] = help_command
162
163 def log_command(who, target, replyto, words):
164     '''Control channel logging (log start, log stop, log list)'''
165     if len(words) < 2:
166         return
167
168     # Parse arguments
169     words.pop(0)
170     cmd     = words.pop(0)
171     channel = words and words[0].startswith('#') and words.pop(0) or target
172     emails  = words and words.pop(0) or ''
173     subject = ' '.join(words)
174
175     if cmd == 'start':
176         if channel in logs:
177             return 'PRIVMSG', (replyto, 'Logging %s already started' % channel)
178         output = []
179         if channel.startswith('#') and channel != target:
180             output.append(('CMD', ('JOIN %s' % channel,)))
181         filename = utils.make_log(channel)
182         logs[channel] = {
183             'filename': filename,
184             'fileobj': open(filename, 'w'),
185             'emails': emails,
186             'subject': subject,
187         }
188         output.append(('PRIVMSG', (replyto, 'Start logging %s...' % channel)))
189         return output
190
191     elif cmd == 'stop':
192         if not channel in logs:
193             return 'PRIVMSG', (replyto, 'Logging %s not started yet' % channel)
194         log = logs.pop(channel)
195         log['fileobj'].close()
196
197         if not emails:
198             emails = log['emails']
199         if not subject:
200             subject = log['subject']
201         if not subject:
202             subject = 'IRC logs for ' + channel
203
204         unrecognized = utils.email_log(emails, subject, log['filename'], aliases)
205         output = [('PRIVMSG', (replyto, 'Unrecognized alias %s' % user)) for user in unrecognized]
206         output.append(('PRIVMSG', (replyto, 'Stop logging %s.  Saved log file (%s)' % (channel, log['filename']))))
207         return output
208
209     elif cmd == 'list':
210         return [('PRIVMSG', (replyto, channel)) for channel in logs.keys()]
211 op_commands['log'] = log_command
212
213 def join_command(who, target, replyto, words):
214     '''Join a channel (join #etherboot)'''
215     if len(words) != 2:
216         return
217     channel = words[1]
218     return 'CMD', ('JOIN %s' % (channel.startswith('#') and channel or '#' + channel),)
219 op_commands['join'] = join_command
220
221 def part_command(who, target, replyto, words):
222     '''Leave a channel (part #etherboot)'''
223     if len(words) != 2:
224         return
225     channel = words[1]
226     return 'CMD', ('PART %s' % (channel.startswith('#') and channel or '#' + channel),)
227 op_commands['part'] = part_command
228
229 def privmsg_command(who, target, replyto, words):
230     '''Send a chat message (privmsg #etherboot Hello all!)'''
231     if len(words) < 3:
232         return
233     return 'PRIVMSG', (words[1], ' '.join(words[2:]))
234 op_commands['privmsg'] = privmsg_command
235
236 def restart_command(who, target, replyto, words):
237     '''Restart bot (restart)'''
238     return ('RESTART', ())
239 op_commands['restart'] = restart_command
240
241 def do_command(who, target, replyto, words):
242     if not words:
243         return
244     command = words[0].lower()
245
246     if who.is_op() and command in op_commands:
247         return op_commands[command](who, target, replyto, words)
248     if command in commands:
249         return commands[command](who, target, replyto, words)