Switch to errdb error code lookup
[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 import errdb
24
25 logs = {}
26 commands = {}
27 op_commands = {}
28 op_members = [config.NICK]
29 aliases = {}
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 op_members
39
40 def readrc_command(who=None, target=None, replyto=None, words=None):
41     '''Source bot command file (readrc)'''
42     try:
43         cmd = []
44         for line in open(config.RCFILE):
45             response = do_command(Who(config.NICK, True), config.NICK, config.NICK, line.split())
46             if isinstance(response, list):
47                 cmd += response
48             elif response:
49                 cmd.append(response)
50         return cmd
51     except IOError:
52         return 'PRIVMSG', (replyto, 'unable to open file')
53 op_commands['readrc'] = readrc_command
54
55 def alias_command(who, _, replyto, words):
56     '''Add or list email aliases (alias stefanha stefanha@gmail.com)'''
57     if len(words) == 2:
58         if words[1].lower() == 'list':
59             return [('PRIVMSG', (replyto, '%s    %s' % (user, email))) for (user, email) in aliases.items()]
60     if len(words) < 3:
61         return
62     user = words[1]
63     email = words[2]
64     aliases[user] = email
65 op_commands['alias'] = alias_command
66
67 def op_command(who, target, replyto, words):
68     '''Add or list bot operators (op stefanha, op list)'''
69     if len(words) == 2:
70         if words[1].lower() == 'list':
71             return [('PRIVMSG', (replyto, '%s' % nick)) for nick in op_members]
72     if len(words) < 2:
73         return
74     nick = words[1]
75     if not nick in op_members:
76         op_members.append(nick)
77 op_commands['op'] = op_command
78
79 def deop_command(who, target, replyto, words):
80     '''Remove bot operators (deop stefanha)'''
81     if len(words) < 2:
82         return
83     nick = words[1]
84     if nick in op_members:
85         op_members.remove(nick)
86 op_commands['deop'] = deop_command
87
88 ERRCODE_RE = re.compile(r'((?:0x)?[0-9a-fA-F]{8})')
89
90 def errcode_command(who, _, replyto, words):
91     '''Look up gPXE error code (errcode 0x12345678)'''
92     errors = []
93     m = ERRCODE_RE.search(' '.join(words))
94     if config.ERRDBFILE and m:
95         try:
96             errno = '%x' % int(m.group(1), 16)
97             errors = [('PRIVMSG', (replyto, '%s:%s: %s' % error)) for error in errdb.lookup(errno, dbfile=config.ERRDBFILE)]
98         except ValueError:
99             pass
100     return errors or ('PRIVMSG', (replyto, 'Error code not found'))
101 commands['errcode'] = errcode_command
102 commands['error'] = errcode_command
103
104 def lspci_command(who, _, replyto, words):
105     '''Look up driver for PCI ID (lspci 10ec:8139)'''
106     ids = []
107     if len(words) == 2:
108         ids = words[1].split(':')
109     elif len(words) == 3:
110         ids = words[1:3]
111     if len(ids) != 2:
112         return 'PRIVMSG', (replyto, 'Invalid arguments')
113
114     try:
115         vendor_id, device_id = [int(s, 16) for s in ids]
116     except ValueError:
117         return 'PRIVMSG', (replyto, 'Invalid arguments')
118     pci_id = '%04x,%04x' % (vendor_id, device_id)
119
120     try:
121         for line in open(config.NICFILE):
122             line = line.rstrip('\r\n')
123             if not line or line.startswith('#'):
124                 continue
125             fields = line.split('\t')
126
127             if fields[0] == 'family':
128                 driver = fields[1].split('/')[-1]
129                 continue
130
131             if fields[1] == pci_id:
132                 return 'PRIVMSG', (replyto, driver)
133     except IndexError, e:
134         utils.dbg(str(e))
135     except IOError, e:
136         utils.dbg(str(e))
137     return 'PRIVMSG', (replyto, 'No driver found')
138 if config.NICFILE:
139     commands['lspci'] = lspci_command
140 else:
141     sys.stderr.write('running without lspci support (NICFILE not given)\n')
142
143 def help_command(who, _, replyto, msg):
144     '''List commands (help)'''
145     def do_help(vocabulary):
146         output = []
147         names = sorted(vocabulary.keys())
148         max_width = max(len(name) for name in names)
149         for name in names:
150             cmd = vocabulary[name]
151             if hasattr(cmd, '__doc__'):
152                 output.append(('PRIVMSG', (replyto, '%s    %s' % (name.ljust(max_width), cmd.__doc__))))
153         return output
154     output = do_help(commands)
155     if who.is_op():
156         output.extend(do_help(op_commands))
157     return output
158 commands['help'] = help_command
159
160 def log_command(who, target, replyto, words):
161     '''Control channel logging (log start, log stop, log list)'''
162     if len(words) < 2:
163         return
164
165     # Parse arguments
166     words.pop(0)
167     cmd     = words.pop(0)
168     channel = words and words[0].startswith('#') and words.pop(0) or target
169     emails  = words and words.pop(0) or ''
170     subject = ' '.join(words)
171
172     if cmd == 'start':
173         if channel in logs:
174             return 'PRIVMSG', (replyto, 'Logging %s already started' % channel)
175         output = []
176         if channel.startswith('#') and channel != target:
177             output.append(('CMD', ('JOIN %s' % channel,)))
178         filename = utils.make_log(channel)
179         logs[channel] = {
180             'filename': filename,
181             'fileobj': open(filename, 'w'),
182             'emails': emails,
183             'subject': subject,
184         }
185         output.append(('PRIVMSG', (replyto, 'Start logging %s...' % channel)))
186         return output
187
188     elif cmd == 'stop':
189         if not channel in logs:
190             return 'PRIVMSG', (replyto, 'Logging %s not started yet' % channel)
191         log = logs.pop(channel)
192         log['fileobj'].close()
193
194         if not emails:
195             emails = log['emails']
196         if not subject:
197             subject = log['subject']
198         if not subject:
199             subject = 'IRC logs for ' + channel
200
201         unrecognized = utils.email_log(emails, subject, log['filename'], aliases)
202         output = [('PRIVMSG', (replyto, 'Unrecognized alias %s' % user)) for user in unrecognized]
203         output.append(('PRIVMSG', (replyto, 'Stop logging %s.  Saved log file (%s)' % (channel, log['filename']))))
204         return output
205
206     elif cmd == 'list':
207         return [('PRIVMSG', (replyto, channel)) for channel in logs.keys()]
208 op_commands['log'] = log_command
209
210 def join_command(who, target, replyto, words):
211     '''Join a channel (join #etherboot)'''
212     if len(words) != 2:
213         return
214     channel = words[1]
215     return 'CMD', ('JOIN %s' % (channel.startswith('#') and channel or '#' + channel),)
216 op_commands['join'] = join_command
217
218 def part_command(who, target, replyto, words):
219     '''Leave a channel (part #etherboot)'''
220     if len(words) != 2:
221         return
222     channel = words[1]
223     return 'CMD', ('PART %s' % (channel.startswith('#') and channel or '#' + channel),)
224 op_commands['part'] = part_command
225
226 def privmsg_command(who, target, replyto, words):
227     '''Send a chat message (privmsg #etherboot Hello all!)'''
228     if len(words) < 3:
229         return
230     return 'PRIVMSG', (words[1], ' '.join(words[2:]))
231 op_commands['privmsg'] = privmsg_command
232
233 def restart_command(who, target, replyto, words):
234     '''Restart bot (restart)'''
235     return ('RESTART', ())
236 op_commands['restart'] = restart_command
237
238 def do_command(who, target, replyto, words):
239     if not words:
240         return
241     command = words[0].lower()
242
243     if who.is_op() and command in op_commands:
244         return op_commands[command](who, target, replyto, words)
245     if command in commands:
246         return commands[command](who, target, replyto, words)