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