Use CAPAB IDENTIFY-MSG to validate bot operators
[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 def readrc_command(who=None, target=None, replyto=None, words=None):
21     '''Source bot command file (readrc)'''
22     try:
23         f = open(config.RCFILE).readlines()
24     except IOError:
25         return 'PRIVMSG', (replyto, 'unable to open file')
26     cmd = []
27     for line in f:
28         response = do_command(config.NICK, config.NICK, config.NICK, line.split())
29         if isinstance(response, list):
30             cmd += response
31         elif response:
32             cmd.append(response)
33     return cmd
34 op_commands['readrc'] = readrc_command
35
36 def alias_command(who, _, replyto, words):
37     '''Add or list email aliases (alias stefanha stefanha@gmail.com)'''
38     if len(words) == 2:
39         if words[1].lower() == 'list':
40             return [('PRIVMSG', (replyto, '%s    %s' % (user, email))) for (user, email) in aliases.items()]
41     if len(words) < 3:
42         return
43     user = words[1]
44     email = words[2]
45     aliases[user] = email
46 op_commands['alias'] = alias_command
47
48 def op_command(who, target, replyto, words):
49     '''Add or list bot operators (op stefanha, op list)'''
50     if len(words) == 2:
51         if words[1].lower() == 'list':
52             return [('PRIVMSG', (replyto, '%s' % nick)) for nick in op_members]
53     if len(words) < 2:
54         return
55     nick = words[1]
56     if not nick in op_members:
57         op_members.append(nick)
58 op_commands['op'] = op_command
59
60 def deop_command(who, target, replyto, words):
61     '''Remove bot operators (deop stefanha)'''
62     if len(words) < 2:
63         return
64     nick = words[1]
65     if nick in op_members:
66         op_members.remove(nick)
67 op_commands['deop'] = deop_command
68
69 ERRCODE_RE = re.compile(r'((?:0x)?[0-9a-fA-F]{8})')
70
71 def errcode_command(who, _, replyto, words):
72     '''Look up gPXE error code (errcode 0x12345678)'''
73     # errcode 0x12345678
74     msg = ' '.join(words)
75     m = ERRCODE_RE.search(msg)
76     if m:
77         try:
78             return 'PRIVMSG', (replyto, str(errcode.Errcode(int(m.group(1), 16))))
79         except ValueError:
80             pass
81 commands['errcode'] = errcode_command
82 commands['error'] = errcode_command
83
84 def help_command(who, _, replyto, msg):
85     '''List commands (help)'''
86     def do_help(vocabulary):
87         output = []
88         names = sorted(vocabulary.keys())
89         max_width = max(len(name) for name in names)
90         for name in names:
91             cmd = vocabulary[name]
92             if hasattr(cmd, '__doc__'):
93                 output.append(('PRIVMSG', (replyto, '%s    %s' % (name.ljust(max_width), cmd.__doc__))))
94         return output
95     output = do_help(commands)
96     if who.is_op():
97         output.extend(do_help(op_commands))
98     return output
99 commands['help'] = help_command
100
101 def log_command(who, target, replyto, words):
102     '''Control channel logging (log start, log stop, log list)'''
103     if len(words) < 2:
104         return
105     channel = target
106     if len(words) >= 3:
107         channel = words[2].startswith('#') and words[2] or target
108     if words[1] == 'start':
109         if channel in logs:
110             return 'PRIVMSG', (replyto, 'Logging %s already started' % channel)
111         cmd = []
112         if channel.startswith('#') and channel != target:
113             cmd.append(('CMD', ('JOIN %s' % channel,)))
114         name = utils.make_log(channel)
115         f = open(name, 'w')
116         logs[channel] = (name, f)
117         cmd.append(('PRIVMSG', (replyto, 'Start logging %s...' % channel)))
118         return cmd
119
120     elif words[1] == 'stop':
121         if not channel in logs:
122             return 'PRIVMSG', (replyto, 'Logging %s not started yet' % channel)
123         (name, f) = logs.pop(channel)
124         f.close()
125
126         to_address = None
127         if len(words) >= 3 and words[-1] != channel:
128             to_address = words[-1]
129         if to_address:
130             unrecognized = utils.email_log(to_address, channel, name, aliases)
131             cmd = [('PRIVMSG', (replyto, 'Unrecognized user %s' % user)) for user in unrecognized]
132         return [('PRIVMSG', (replyto, 'Stop logging %s.  Saved log file (%s)' % (channel, name)))] + cmd
133
134     elif words[1] == 'list':
135         return 'PRIVMSG', (replyto, 'Logging: %s' % ','.join(logs.keys()))
136 op_commands['log'] = log_command
137
138 def join_command(who, target, replyto, words):
139     '''Join a channel (join #etherboot)'''
140     index = words.index('join') + 1
141     if index < len(words):
142         channel = words[index]
143         return 'CMD', ('JOIN %s' % (channel.startswith('#') and channel or '#' + channel),)
144 op_commands['join'] = join_command
145
146 def part_command(who, target, replyto, words):
147     '''Leave a channel (part #etherboot)'''
148     index = words.index('part') + 1
149     if index < len(words):
150         channel = words[index]
151         return 'CMD', ('PART %s' % (channel.startswith('#') and channel or '#' + channel),)
152 op_commands['part'] = part_command
153
154 def privmsg_command(who, target, replyto, words):
155     '''Send a chat message (privmsg #etherboot Hello all!)'''
156     if len(words) < 3:
157         return
158     return 'PRIVMSG', (words[1], ' '.join(words[2:]))
159 op_commands['privmsg'] = privmsg_command
160
161 def restart_command(who, target, replyto, words):
162     '''Restart bot (restart)'''
163     return ('RESTART', ())
164 op_commands['restart'] = restart_command
165
166 def do_command(who, target, replyto, words):
167     if not words:
168         return
169     command = words[0].lower()
170
171     if who.is_op() and command in op_commands:
172         return op_commands[command](who, target, replyto, words)
173     if command in commands:
174         return commands[command](who, target, replyto, words)