Merge lp:~stefanor/ibid/misc into lp:~ibid-core/ibid/old-trunk-pack-0.92
- misc
- Merge into old-trunk-pack-0.92
Proposed by
Stefano Rivera
Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | Michael Gorven | ||||
Approved revision: | 559 | ||||
Merged at revision: | 556 | ||||
Proposed branch: | lp:~stefanor/ibid/misc | ||||
Merge into: | lp:~ibid-core/ibid/old-trunk-pack-0.92 | ||||
Diff against target: | None lines | ||||
To merge this branch: | bzr merge lp:~stefanor/ibid/misc | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Michael Gorven | Approve | ||
Jonathan Hitchcock | Approve | ||
Review via email: mp+4051@code.launchpad.net |
Commit message
Description of the change
To post a comment you must log in.
Revision history for this message
Stefano Rivera (stefanor) wrote : | # |
Revision history for this message
Jonathan Hitchcock (vhata) : | # |
review:
Approve
Revision history for this message
Michael Gorven (mgorven) wrote : | # |
I have a few minor changes, but looks fine otherwise. Good work.
review approve
status approve
review:
Approve
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'INSTALL' |
2 | --- INSTALL 2009-02-24 23:55:20 +0000 |
3 | +++ INSTALL 2009-02-24 16:03:17 +0000 |
4 | @@ -7,7 +7,8 @@ |
5 | # apt-get install python-virtualenv python-soappy python-twisted \ |
6 | python-configobj python-sqllite2 python-feedparser \ |
7 | python-httplib2 python-beautifulsoup python-dictclient \ |
8 | - python-imdbpy python-dns pysilc python-pinder |
9 | + python-imdbpy python-dns python-simplejson \ |
10 | + python-jinja pysilc python-pinder |
11 | |
12 | Switch to the user ibid will be running as. |
13 | Set up a virtual Python environment |
14 | |
15 | === modified file 'ibid/config.ini' |
16 | --- ibid/config.ini 2009-02-23 09:44:40 +0000 |
17 | +++ ibid/config.ini 2009-03-01 15:16:39 +0000 |
18 | @@ -32,18 +32,18 @@ |
19 | [[smtp]] |
20 | relayhost = localhost |
21 | address = ibid@localhost |
22 | - accept = ibid@foo.com, |
23 | + accept = ibid@foo.com, |
24 | [[pb]] |
25 | [[reaper]] |
26 | type = silc |
27 | server = silc.za.net |
28 | channels = ibid, |
29 | name = Ibid Bot |
30 | - [[campfire]] |
31 | - subdomain = ibid |
32 | - rooms = Room 1, |
33 | - username = foo@bar.com |
34 | - password = baz |
35 | + [[campfire]] |
36 | + subdomain = ibid |
37 | + rooms = Room 1, |
38 | + username = foo@bar.com |
39 | + password = baz |
40 | |
41 | [plugins] |
42 | [[ping]] |
43 | @@ -71,4 +71,4 @@ |
44 | server = localhost |
45 | |
46 | [databases] |
47 | - ibid = sqlite:///ibid.db |
48 | + ibid = sqlite:///ibid.db |
49 | |
50 | === modified file 'ibid/plugins/admin.py' |
51 | --- ibid/plugins/admin.py 2009-02-13 21:19:45 +0000 |
52 | +++ ibid/plugins/admin.py 2009-03-01 23:01:30 +0000 |
53 | @@ -5,9 +5,9 @@ |
54 | |
55 | help = {} |
56 | |
57 | -help['plugins'] = 'Lists, loads and unloads plugins.' |
58 | +help['plugins'] = u'Lists, loads and unloads plugins.' |
59 | class ListPLugins(Processor): |
60 | - """list plugins""" |
61 | + u"""list plugins""" |
62 | feature = 'plugins' |
63 | |
64 | @match(r'^lsmod|list\s+plugins$') |
65 | @@ -20,8 +20,9 @@ |
66 | event.addresponse(', '.join(plugins)) |
67 | return event |
68 | |
69 | -help['core'] = 'Reloads core modules.' |
70 | +help['core'] = u'Reloads core modules.' |
71 | class ReloadCoreModules(Processor): |
72 | + u"""reload (reloader|dispatcher|databases|auth)""" |
73 | feature = 'core' |
74 | |
75 | priority = -5 |
76 | @@ -39,7 +40,7 @@ |
77 | event.addresponse(result and u'%s reloaded' % module or u"Couldn't reload %s" % module) |
78 | |
79 | class LoadModules(Processor): |
80 | - """(load|unload|reload) <plugin|processor>""" |
81 | + u"""(load|unload|reload) <plugin|processor>""" |
82 | feature = 'plugins' |
83 | |
84 | permission = u'plugins' |
85 | @@ -59,7 +60,7 @@ |
86 | |
87 | help['die'] = u'Terminates the bot' |
88 | class Die(Processor): |
89 | - """die""" |
90 | + u"""die""" |
91 | feature = 'die' |
92 | |
93 | permission = u'admin' |
94 | |
95 | === modified file 'ibid/plugins/apt.py' |
96 | --- ibid/plugins/apt.py 2009-02-16 08:13:23 +0000 |
97 | +++ ibid/plugins/apt.py 2009-03-01 19:58:06 +0000 |
98 | @@ -2,67 +2,129 @@ |
99 | |
100 | from ibid.plugins import Processor, match |
101 | from ibid.config import Option |
102 | +from ibid.utils import file_in_path, unicode_output |
103 | |
104 | help = {} |
105 | |
106 | help['aptitude'] = u'Searches for packages' |
107 | class Aptitude(Processor): |
108 | - """(apt|aptitude|apt-get) [search] <term>""" |
109 | + u"""apt (search|show) <term>""" |
110 | feature = 'aptitude' |
111 | |
112 | aptitude = Option('aptitude', 'Path to aptitude executable', 'aptitude') |
113 | |
114 | - @match(r'^(?:apt|aptitude|apt-get)\s+(?:search\s+)(.+)$') |
115 | + bad_search_strings = ( |
116 | + "?action", "~a", "?automatic", "~A", "?broken", "~b", |
117 | + "?config-files", "~c", "?garbage", "~g", "?installed", "~i", |
118 | + "?new", "~N", "?obsolete", "~o", "?upgradable", "~U", |
119 | + "?user-tag", "?version", "~V" |
120 | + ) |
121 | + |
122 | + def setup(self): |
123 | + if not file_in_path(self.aptitude): |
124 | + raise Exception("Cannot locate aptitude executeable") |
125 | + |
126 | + def _check_terms(self, event, term): |
127 | + "Check for naughty users" |
128 | + |
129 | + for word in self.bad_search_strings: |
130 | + if word in term: |
131 | + event.addresponse(u"I can't tell you about my host system. Sorry.") |
132 | + return False |
133 | + |
134 | + if term.strip().startswith("-"): |
135 | + event.addresponse(False) |
136 | + return False |
137 | + |
138 | + return True |
139 | + |
140 | + @match(r'^(?:apt|aptitude|apt-get|apt-cache)\s+search\s+(.+)$') |
141 | def search(self, event, term): |
142 | + |
143 | + if not self._check_terms(event, term): |
144 | + return |
145 | + |
146 | apt = Popen([self.aptitude, 'search', '-F', '%p', term], stdout=PIPE, stderr=PIPE) |
147 | output, error = apt.communicate() |
148 | code = apt.wait() |
149 | |
150 | - if code == 0 and output: |
151 | + if code == 0: |
152 | if output: |
153 | - event.addresponse(u', '.join(line.strip() for line in output.splitlines())) |
154 | + output = unicode_output(output.strip()) |
155 | + output = [line.strip() for line in output.splitlines()] |
156 | + event.addresponse(u"Found %i packages: %s" % (len(output), u', '.join(output))) |
157 | else: |
158 | event.addresponse(u'No packages found') |
159 | - |
160 | - @match(r'(?:apt|aptitude|apt-get)\s+(?:show\s+)(.+)$') |
161 | - def show(self, event, package): |
162 | - apt = Popen([self.aptitude, 'show', package], stdout=PIPE, stderr=PIPE) |
163 | + else: |
164 | + error = unicode_output(error.strip()) |
165 | + if error.startswith(u"E: "): |
166 | + error = error[3:] |
167 | + event.addresponse(u"Couldn't search: %s" % error) |
168 | + |
169 | + @match(r'(?:apt|aptitude|apt-get)\s+show\s+(.+)$') |
170 | + def show(self, event, term): |
171 | + |
172 | + if not self._check_terms(event, term): |
173 | + return |
174 | + |
175 | + apt = Popen([self.aptitude, 'show', term], stdout=PIPE, stderr=PIPE) |
176 | output, error = apt.communicate() |
177 | code = apt.wait() |
178 | |
179 | - if code == 0 and output: |
180 | - print output |
181 | + if code == 0: |
182 | description = None |
183 | + output = unicode_output(output) |
184 | for line in output.splitlines(): |
185 | if not description: |
186 | - if line.startswith('Description:'): |
187 | - description = u'%s:' % line.replace('Description:', '', 1).strip() |
188 | - elif line.startswith('Provided by:'): |
189 | - description = u'Virtual package provided by %s' % line.replace('Provided by:', '', 1).strip() |
190 | + if line.startswith(u'Description:'): |
191 | + description = u'%s:' % line.split(None, 1)[1] |
192 | + elif line.startswith(u'Provided by:'): |
193 | + description = u'Virtual package provided by %s' % line.split(None, 2)[2] |
194 | + elif line != "": |
195 | + description += u' ' + line.strip() |
196 | else: |
197 | - description += ' ' + line.strip() |
198 | + # More than one package listed |
199 | + break |
200 | if description: |
201 | event.addresponse(description) |
202 | else: |
203 | - event.addresponse(u'No such package') |
204 | + raise Exception("We couldn't successfully parse aptitude's output") |
205 | + else: |
206 | + error = unicode_output(error.strip()) |
207 | + if error.startswith(u"E: "): |
208 | + error = error[3:] |
209 | + event.addresponse(u"Couldn't find package: %s" % error) |
210 | |
211 | help['apt-file'] = u'Searches for packages containing the specified file' |
212 | class AptFile(Processor): |
213 | - """apt-file [search] <term>""" |
214 | + u"""apt-file [search] <term>""" |
215 | feature = 'apt-file' |
216 | |
217 | aptfile = Option('apt-file', 'Path to apt-file executable', 'apt-file') |
218 | |
219 | + def setup(self): |
220 | + if not file_in_path(self.aptfile): |
221 | + raise Exception("Cannot locate apt-file executeable") |
222 | + |
223 | @match(r'^apt-?file\s+(?:search\s+)?(.+)$') |
224 | def search(self, event, term): |
225 | apt = Popen([self.aptfile, 'search', term], stdout=PIPE, stderr=PIPE) |
226 | output, error = apt.communicate() |
227 | code = apt.wait() |
228 | |
229 | - if code == 0 and output: |
230 | + if code == 0: |
231 | if output: |
232 | - event.addresponse(u', '.join(line.split(':')[0] for line in output.splitlines())) |
233 | - else: |
234 | - event.addresponse(u'No packages found') |
235 | + output = unicode_output(output.strip()) |
236 | + output = [line.split(u':')[0] for line in output.splitlines()] |
237 | + event.addresponse(u"Found %i packages: %s" % (len(output), u', '.join(output))) |
238 | + else: |
239 | + event.addresponse(u'No packages found.') |
240 | + else: |
241 | + error = unicode_output(error.strip()) |
242 | + if u"The cache directory is empty." in error: |
243 | + event.addresponse(u'Search error: apt-file cache empty.') |
244 | + else: |
245 | + event.addresponse(u'Search error') |
246 | + raise Exception("apt-file: %s" % error) |
247 | |
248 | # vi: set et sta sw=4 ts=4: |
249 | |
250 | === modified file 'ibid/plugins/auth.py' |
251 | --- ibid/plugins/auth.py 2009-02-22 15:30:46 +0000 |
252 | +++ ibid/plugins/auth.py 2009-03-01 23:01:30 +0000 |
253 | @@ -13,9 +13,9 @@ |
254 | |
255 | actions = {'revoke': 'Revoked', 'grant': 'Granted', 'remove': 'Removed'} |
256 | |
257 | -help['auth'] = 'Adds and removes authentication credentials and permissions' |
258 | +help['auth'] = u'Adds and removes authentication credentials and permissions' |
259 | class AddAuth(Processor): |
260 | - """authenticate <account> using <method> [<credential>]""" |
261 | + u"""authenticate <account> [on source] using <method> [<credential>]""" |
262 | feature = 'auth' |
263 | |
264 | @match(r'^authenticate\s+(.+?)(?:\s+on\s+(.+))?\s+using\s+(\S+)\s+(.+)$') |
265 | @@ -59,7 +59,9 @@ |
266 | |
267 | permission_values = {'no': '-', 'yes': '+', 'auth': ''} |
268 | class Permissions(Processor): |
269 | - """(grant|revoke|remove) <permission> (to|from|on) <username> [when authed] | list permissions""" |
270 | + u"""(grant|revoke) <permission> (to|from|on) <username> [when authed] |
271 | + permissions [for <username>] |
272 | + list permissions""" |
273 | feature = 'auth' |
274 | |
275 | permission = u'admin' |
276 | @@ -139,7 +141,7 @@ |
277 | event.addresponse(', '.join(permissions)) |
278 | |
279 | class Auth(Processor): |
280 | - """auth <credential>""" |
281 | + u"""auth <credential>""" |
282 | feature = 'auth' |
283 | |
284 | @match(r'^auth(?:\s+(.+))?$') |
285 | |
286 | === modified file 'ibid/plugins/basic.py' |
287 | --- ibid/plugins/basic.py 2009-02-13 21:19:45 +0000 |
288 | +++ ibid/plugins/basic.py 2009-03-01 23:01:30 +0000 |
289 | @@ -1,14 +1,13 @@ |
290 | from random import choice |
291 | import re |
292 | -import logging |
293 | |
294 | from ibid.plugins import Processor, match, handler, authorise |
295 | |
296 | help = {} |
297 | |
298 | -help['saydo'] = 'Says or does stuff in a channel.' |
299 | +help['saydo'] = u'Says or does stuff in a channel.' |
300 | class SayDo(Processor): |
301 | - """(say|do) <channel> <text>""" |
302 | + u"""(say|do) <channel> <text>""" |
303 | feature = 'saydo' |
304 | |
305 | permission = u'saydo' |
306 | @@ -25,7 +24,7 @@ |
307 | |
308 | help['redirect'] = u'Redirects the response to a command to a different channel.' |
309 | class RedirectCommand(Processor): |
310 | - """redirect [to] <channel> [on <source>] <command>""" |
311 | + u"""redirect [to] <channel> [on <source>] <command>""" |
312 | feature = 'redirect' |
313 | |
314 | priority = -1200 |
315 | @@ -57,31 +56,15 @@ |
316 | responses.append(response) |
317 | event.responses = responses |
318 | |
319 | -choose_re = re.compile(r'(?:\s*,\s*(?:or\s+)?)|(?:\s+or\s+)', re.I) |
320 | -help['choose'] = 'Choose one of the given options.' |
321 | +help['choose'] = u'Choose one of the given options.' |
322 | class Choose(Processor): |
323 | - """choose <choice> or <choice>...""" |
324 | + u"""choose <choice> or <choice>...""" |
325 | feature = 'choose' |
326 | |
327 | + choose_re = re.compile(r'(?:\s*,\s*(?:or\s+)?)|(?:\s+or\s+)', re.I) |
328 | + |
329 | @match(r'^(?:choose|choice|pick)\s+(.+)$') |
330 | def choose(self, event, choices): |
331 | - event.addresponse(u'I choose %s' % choice(choose_re.split(choices))) |
332 | - |
333 | -class UnicodeWarning(Processor): |
334 | - |
335 | - priority = 1950 |
336 | - |
337 | - def setup(self): |
338 | - self.log = logging.getLogger('plugins.unicode') |
339 | - |
340 | - def process(self, object): |
341 | - if isinstance(object, dict): |
342 | - for value in object.values(): |
343 | - self.process(value) |
344 | - elif isinstance(object, list): |
345 | - for value in object: |
346 | - self.process(value) |
347 | - elif isinstance(object, str): |
348 | - self.log.warning(u"Found a non-unicode string: %s" % object) |
349 | + event.addresponse(u'I choose %s' % choice(self.choose_re.split(choices))) |
350 | |
351 | # vi: set et sta sw=4 ts=4: |
352 | |
353 | === modified file 'ibid/plugins/buildbot.py' |
354 | --- ibid/plugins/buildbot.py 2009-02-23 20:29:44 +0000 |
355 | +++ ibid/plugins/buildbot.py 2009-03-01 23:01:30 +0000 |
356 | @@ -9,7 +9,7 @@ |
357 | help = {'buildbot': u'Displays buildbot build results and triggers builds.'} |
358 | |
359 | class BuildBot(Processor, RPC): |
360 | - """rebuild <branch> [ (revision|r) <number> ]""" |
361 | + u"""rebuild <branch> [ (revision|r) <number> ]""" |
362 | feature = 'buildbot' |
363 | |
364 | server = Option('server', 'Buildbot server hostname', 'localhost') |
365 | |
366 | === modified file 'ibid/plugins/bzr.py' |
367 | --- ibid/plugins/bzr.py 2009-02-18 10:41:14 +0000 |
368 | +++ ibid/plugins/bzr.py 2009-03-01 23:01:30 +0000 |
369 | @@ -10,7 +10,7 @@ |
370 | from ibid.config import Option |
371 | from ibid.utils import ago |
372 | |
373 | -help = {'bzr': 'Retrieves commit logs from a Bazaar repository.'} |
374 | +help = {'bzr': u'Retrieves commit logs from a Bazaar repository.'} |
375 | |
376 | class LogFormatter(log.LogFormatter): |
377 | |
378 | @@ -42,7 +42,7 @@ |
379 | self.to_file.write(commit) |
380 | |
381 | class Bazaar(Processor, RPC): |
382 | - """last commit to <repo> | commit <revno> [full] |
383 | + u"""(last commit|commit <revno>) [to <repo>] [full] |
384 | repositories""" |
385 | feature = 'bzr' |
386 | |
387 | @@ -83,7 +83,7 @@ |
388 | |
389 | for commit in commits: |
390 | if commit: |
391 | - event.addresponse(commit.strip()) |
392 | + event.addresponse(unicode(commit.strip())) |
393 | |
394 | def get_commits(self, repository, start, end=None, full=None): |
395 | branch = None |
396 | |
397 | === modified file 'ibid/plugins/config.py' |
398 | --- ibid/plugins/config.py 2009-02-13 21:55:56 +0000 |
399 | +++ ibid/plugins/config.py 2009-03-01 23:01:30 +0000 |
400 | @@ -5,12 +5,14 @@ |
401 | from ibid.config import FileConfig |
402 | from ibid.plugins import Processor, match, authorise |
403 | |
404 | -help = {'config': 'Gets and sets configuration settings, and rereads the configuration file.'} |
405 | +help = {'config': u'Gets and sets configuration settings, and rereads the configuration file.'} |
406 | |
407 | log = logging.getLogger('plugins.config') |
408 | |
409 | class Config(Processor): |
410 | - """reread config | set config <name> <value> | get config <name>""" |
411 | + u"""reread config |
412 | + set config <name> to <value> |
413 | + get config <name>""" |
414 | feature = 'config' |
415 | |
416 | priority = -10 |
417 | |
418 | === modified file 'ibid/plugins/core.py' |
419 | --- ibid/plugins/core.py 2009-02-23 20:29:44 +0000 |
420 | +++ ibid/plugins/core.py 2009-03-01 19:58:06 +0000 |
421 | @@ -1,13 +1,12 @@ |
422 | import re |
423 | from time import time |
424 | from random import choice |
425 | +import logging |
426 | |
427 | import ibid |
428 | from ibid.plugins import Processor, handler |
429 | from ibid.config import Option, IntOption |
430 | |
431 | -help = {} |
432 | - |
433 | class Addressed(Processor): |
434 | |
435 | priority = -1500 |
436 | @@ -89,14 +88,20 @@ |
437 | class Address(Processor): |
438 | |
439 | processed = True |
440 | - acknowledgements = Option('acknowledgements', 'Responses for positive acknowledgements', (u'Okay', u'Sure', u'Done', u'Righto', u'Alrighty', u'Yessir')) |
441 | + acknowledgements = Option('acknowledgements', 'Responses for positive acknowledgements', |
442 | + (u'Okay', u'Sure', u'Done', u'Righto', u'Alrighty', u'Yessir')) |
443 | + refusals = Option('refusals', 'Responses for negative acknowledgements', |
444 | + (u'No', u"I won't", u"Shan't", u"I'm sorry, but I can't do that")) |
445 | |
446 | @handler |
447 | def address(self, event): |
448 | addressed = [] |
449 | for response in event.responses: |
450 | if isinstance(response, bool): |
451 | - response = choice(self.acknowledgements) |
452 | + if response: |
453 | + response = choice(self.acknowledgements) |
454 | + else: |
455 | + response = choice(self.refusals) |
456 | if isinstance(response, basestring) and event.public: |
457 | addressed.append('%s: %s' % (event.sender['nick'], response)) |
458 | else: |
459 | @@ -111,9 +116,7 @@ |
460 | def process(self, event): |
461 | event.time = time() |
462 | |
463 | -help['complain'] = 'Responds with a complaint. Used to handle unprocessed messages.' |
464 | class Complain(Processor): |
465 | - feature = 'complain' |
466 | |
467 | priority = 950 |
468 | complaints = Option('complaints', 'Complaint responses', (u'Huh?', u'Sorry...', u'?', u'Excuse me?', u'*blink*', u'What?')) |
469 | @@ -147,4 +150,20 @@ |
470 | else: |
471 | event.processed = True |
472 | |
473 | +class UnicodeWarning(Processor): |
474 | + priority = 1950 |
475 | + |
476 | + def setup(self): |
477 | + self.log = logging.getLogger('plugins.unicode') |
478 | + |
479 | + def process(self, object): |
480 | + if isinstance(object, dict): |
481 | + for value in object.values(): |
482 | + self.process(value) |
483 | + elif isinstance(object, list): |
484 | + for value in object: |
485 | + self.process(value) |
486 | + elif isinstance(object, str): |
487 | + self.log.warning(u"Found a non-unicode string: %s" % object) |
488 | + |
489 | # vi: set et sta sw=4 ts=4: |
490 | |
491 | === modified file 'ibid/plugins/crypto.py' |
492 | --- ibid/plugins/crypto.py 2009-01-24 12:39:04 +0000 |
493 | +++ ibid/plugins/crypto.py 2009-03-01 23:01:30 +0000 |
494 | @@ -6,35 +6,36 @@ |
495 | |
496 | help = {} |
497 | |
498 | -help['hash'] = 'Calculates numerous cryptographic hash functions.' |
499 | +help['hash'] = u'Calculates numerous cryptographic hash functions.' |
500 | class Hash(Processor): |
501 | - """(md5|sha1|sha224|sha256|sha384|sha512|crypt) <string> [<salt>]""" |
502 | + u"""(md5|sha1|sha224|sha256|sha384|sha512) <string> |
503 | + crypt <string> <salt>""" |
504 | feature = 'hash' |
505 | |
506 | @match(r'^(md5|sha1|sha224|sha256|sha384|sha512)\s+(.+?)$') |
507 | def hash(self, event, hash, string): |
508 | - event.addresponse(eval('hashlib.%s' % hash.lower())(string).hexdigest()) |
509 | + event.addresponse(unicode(eval('hashlib.%s' % hash.lower())(string).hexdigest())) |
510 | |
511 | @match(r'^crypt\s+(.+)\s+(\S+)$') |
512 | def handle_crypt(self, event, string, salt): |
513 | - event.addresponse(crypt(string, salt)) |
514 | + event.addresponse(unicode(crypt(string, salt))) |
515 | |
516 | -help['base64'] = 'Encodes and decodes base 16, 32 and 64.' |
517 | +help['base64'] = u'Encodes and decodes base 16, 32 and 64.' |
518 | class Base64(Processor): |
519 | - """b(16|32|64)(encode|decode) <string>""" |
520 | + u"""b(16|32|64)(encode|decode) <string>""" |
521 | feature = 'base64' |
522 | |
523 | @match(r'^b(16|32|64)(enc|dec)(?:ode)?\s+(.+?)$') |
524 | def base64(self, event, base, operation, string): |
525 | - event.addresponse(eval('base64.b%s%sode' % (base, operation.lower()))(string)) |
526 | + event.addresponse(unicode(eval('base64.b%s%sode' % (base, operation.lower()))(string))) |
527 | |
528 | -help['rot13'] = 'Transforms a string with ROT13.' |
529 | +help['rot13'] = u'Transforms a string with ROT13.' |
530 | class Rot13(Processor): |
531 | - """rot13 <string>""" |
532 | + u"""rot13 <string>""" |
533 | feature = 'rot13' |
534 | |
535 | @match(r'^rot13\s+(.+)$') |
536 | def rot13(self, event, string): |
537 | - event.addresponse(string.encode('rot13')) |
538 | + event.addresponse(unicode(string.encode('rot13'))) |
539 | |
540 | # vi: set et sta sw=4 ts=4: |
541 | |
542 | === modified file 'ibid/plugins/dict.py' |
543 | --- ibid/plugins/dict.py 2009-02-03 18:03:49 +0000 |
544 | +++ ibid/plugins/dict.py 2009-03-01 23:01:30 +0000 |
545 | @@ -3,11 +3,12 @@ |
546 | from ibid.plugins import Processor, match |
547 | from ibid.config import Option, IntOption |
548 | |
549 | -help = {'dict': 'Defines words and checks spellings.'} |
550 | +help = {'dict': u'Defines words and checks spellings.'} |
551 | |
552 | class Dict(Processor): |
553 | - """(spell|define) <word> [using (<dictionary>|<stratergy>)] |
554 | - (dictionaries|strategies)""" |
555 | + u"""(spell|define) <word> [using (<dictionary>|<strategy>)] |
556 | + (dictionaries|strategies) |
557 | + (dictionary|strategy) <name>""" |
558 | feature = 'dict' |
559 | |
560 | server = Option('server', 'Dictionary server hostname', 'localhost') |
561 | @@ -24,15 +25,15 @@ |
562 | event.addresponse(u', '.join([d.getdefstr() for d in definitions])) |
563 | |
564 | @match(r'spell\s+(.+?)(?:\s+using\s+(.+))?$') |
565 | - def handle_spell(self, event, word, stratergy): |
566 | - suggestions = self.connection.match('*', stratergy or 'soundex', word) |
567 | + def handle_spell(self, event, word, strategy): |
568 | + suggestions = self.connection.match('*', strategy or 'soundex', word) |
569 | event.addresponse(u', '.join([d.getword() for d in suggestions])) |
570 | |
571 | @match(r'^dictionaries$') |
572 | def handle_dictionaries(self, event): |
573 | event.addresponse(u', '.join(self.dictionaries.keys())) |
574 | |
575 | - @match(r'^strategies$') |
576 | + @match(r'^strater?gies$') |
577 | def handle_strategies(self, event): |
578 | event.addresponse(u', '.join(self.strategies.keys())) |
579 | |
580 | @@ -43,10 +44,10 @@ |
581 | else: |
582 | event.addresponse(u"I don't have that response") |
583 | |
584 | - @match(r'^stratergy\s+(.+?)$') |
585 | - def handle_stratergy(self, event, stratergy): |
586 | - if stratergy in self.strategies: |
587 | - event.addresponse(unicode(self.strategies[stratergy])) |
588 | + @match(r'^strater?gy\s+(.+?)$') |
589 | + def handle_strategy(self, event, strategy): |
590 | + if strategy in self.strategies: |
591 | + event.addresponse(unicode(self.strategies[strategy])) |
592 | else: |
593 | event.addresponse(u"I don't have that response") |
594 | |
595 | |
596 | === modified file 'ibid/plugins/eval.py' |
597 | --- ibid/plugins/eval.py 2009-02-12 16:49:56 +0000 |
598 | +++ ibid/plugins/eval.py 2009-03-01 23:01:30 +0000 |
599 | @@ -10,10 +10,10 @@ |
600 | |
601 | from ibid.plugins import Processor, match, authorise |
602 | |
603 | -help = {'eval': 'Evaluates Python, Perl and Lua code.'} |
604 | +help = {'eval': u'Evaluates Python, Perl and Lua code.'} |
605 | |
606 | class Python(Processor): |
607 | - """py <code>""" |
608 | + u"""py <code>""" |
609 | feature = 'eval' |
610 | |
611 | permission = u'eval' |
612 | @@ -27,13 +27,13 @@ |
613 | exec('import sys', globals) |
614 | exec('import re', globals) |
615 | exec('import time', globals) |
616 | - result = str(eval(code, globals, {})) |
617 | + result = unicode(eval(code, globals, {})) |
618 | except Exception, e: |
619 | - result = str(e) |
620 | + result = unicode(e) |
621 | event.addresponse(result) |
622 | |
623 | class Perl(Processor): |
624 | - """pl <code>""" |
625 | + u"""pl <code>""" |
626 | feature = 'eval' |
627 | |
628 | permission = u'eval' |
629 | @@ -46,10 +46,10 @@ |
630 | except Exception, e: |
631 | result = e |
632 | |
633 | - event.addresponse(str(result)) |
634 | + event.addresponse(unicode(result)) |
635 | |
636 | class Lua(Processor): |
637 | - """lua <code>""" |
638 | + u"""lua <code>""" |
639 | feature = 'eval' |
640 | |
641 | permission = u'eval' |
642 | @@ -62,4 +62,4 @@ |
643 | except Exception, e: |
644 | result = e |
645 | |
646 | - event.addresponse(str(result)) |
647 | + event.addresponse(unicode(result)) |
648 | |
649 | === modified file 'ibid/plugins/factoid.py' |
650 | --- ibid/plugins/factoid.py 2009-02-23 20:29:44 +0000 |
651 | +++ ibid/plugins/factoid.py 2009-03-01 23:01:30 +0000 |
652 | @@ -12,7 +12,9 @@ |
653 | from ibid.plugins.identity import get_identities |
654 | from ibid.models import Base |
655 | |
656 | -help = {'factoids': u'Factoids are arbitrary pieces of information stored by a key.'} |
657 | +help = {'factoids': u'Factoids are arbitrary pieces of information stored by a key. ' + |
658 | + u'Factoids beginning with a command such as "<action>" or "<reply>" will supress the "name verb value" output. ' + |
659 | + u'Search searches the keys. Scan searches the values.'} |
660 | |
661 | log = logging.getLogger('plugins.factoid') |
662 | |
663 | @@ -85,7 +87,7 @@ |
664 | return factoid or query.order_by(func.random()).first() |
665 | |
666 | class Utils(Processor): |
667 | - """literal <name> [starting at <number>]""" |
668 | + u"""literal <name> [starting from <number>]""" |
669 | feature = 'factoids' |
670 | |
671 | @match(r'^literal\s+(.+?)(?:\s+start(?:ing)?\s+(?:from\s+)?(\d+))?$') |
672 | @@ -99,7 +101,8 @@ |
673 | session.close() |
674 | |
675 | class Forget(Processor): |
676 | - """forget <name>""" |
677 | + u"""forget <name> |
678 | + <name> is the same as <other name>""" |
679 | feature = 'factoids' |
680 | |
681 | permission = u'factoid' |
682 | @@ -172,7 +175,7 @@ |
683 | event.addresponse(u"I don't know about %s" % name) |
684 | |
685 | class Search(Processor): |
686 | - """(search|scan) for <pattern>""" |
687 | + u"""(search|scan) for <pattern> [from <start>]""" |
688 | feature = 'factoids' |
689 | |
690 | limit = IntOption('search_limit', u'Maximum number of results to return', 30) |
691 | @@ -198,7 +201,7 @@ |
692 | event.addresponse(u"I couldn't find anything with that name") |
693 | |
694 | class Get(Processor, RPC): |
695 | - """<factoid> [( #<number> | /<pattern>/ )]""" |
696 | + u"""<factoid> [( #<number> | /<pattern>/ )]""" |
697 | feature = 'factoids' |
698 | |
699 | verbs = verbs |
700 | @@ -265,8 +268,7 @@ |
701 | return reply |
702 | |
703 | class Set(Processor): |
704 | - """<name> (<verb>|=<verb>=) <value> |
705 | - <name> is the same as <name>""" |
706 | + u"""<name> (<verb>|=<verb>=) [also] <value>""" |
707 | feature = 'factoids' |
708 | |
709 | verbs = verbs |
710 | |
711 | === modified file 'ibid/plugins/feeds.py' |
712 | --- ibid/plugins/feeds.py 2009-02-23 20:29:44 +0000 |
713 | +++ ibid/plugins/feeds.py 2009-03-01 23:01:30 +0000 |
714 | @@ -44,7 +44,7 @@ |
715 | self.entries = self.feed['entries'] |
716 | |
717 | class Manage(Processor): |
718 | - """add feed <url> as <name> |
719 | + u"""add feed <url> as <name> |
720 | list feeds |
721 | remove <name> feed""" |
722 | feature = 'feeds' |
723 | @@ -72,7 +72,7 @@ |
724 | |
725 | session.close() |
726 | |
727 | - @match(r'^list\s+feeds$') |
728 | + @match(r'^(?:list\s+)?feeds$') |
729 | def list(self, event): |
730 | session = ibid.databases.ibid() |
731 | feeds = session.query(Feed).all() |
732 | @@ -98,7 +98,7 @@ |
733 | session.close() |
734 | |
735 | class Retrieve(Processor): |
736 | - """(latest|last) [ <count> ] articles from <name> [ starting [(at|from)] <number> ] |
737 | + u"""latest [ <count> ] articles from <name> [ starting at <number> ] |
738 | article ( <number> | /<pattern>/ ) from <name>""" |
739 | feature = 'feeds' |
740 | |
741 | |
742 | === modified file 'ibid/plugins/google.py' |
743 | --- ibid/plugins/google.py 2009-02-12 20:58:39 +0000 |
744 | +++ ibid/plugins/google.py 2009-03-01 23:01:30 +0000 |
745 | @@ -5,12 +5,12 @@ |
746 | from ibid.plugins import Processor, match |
747 | from ibid.config import Option |
748 | |
749 | -help = {'google': 'Retrieves results from Google and Google Calculator.'} |
750 | +help = {'google': u'Retrieves results from Google and Google Calculator.'} |
751 | |
752 | user_agent = 'Mozilla/5.0' |
753 | |
754 | class Search(Processor): |
755 | - """google [for] <term>""" |
756 | + u"""google [for] <term>""" |
757 | feature = 'google' |
758 | |
759 | user_agent = Option('user_agent', 'HTTP user agent to present to Google', user_agent) |
760 | @@ -21,7 +21,6 @@ |
761 | if country: |
762 | url = url + '&meta=cr%%3Dcountry%s' % country.upper() |
763 | |
764 | - print self.user_agent |
765 | f = urlopen(Request(url, headers={'user-agent': self.user_agent})) |
766 | soup = BeautifulSoup(f.read()) |
767 | f.close() |
768 | @@ -31,15 +30,15 @@ |
769 | for item in items: |
770 | try: |
771 | url = item.a['href'] |
772 | - title = ''.join([e.string for e in item.a.contents]) |
773 | - results.append('"%s" %s' % (title, url)) |
774 | + title = u''.join([e.string for e in item.a.contents]) |
775 | + results.append(u'"%s" %s' % (title, url)) |
776 | except Exception: |
777 | pass |
778 | |
779 | - event.addresponse(', '.join(results)) |
780 | + event.addresponse(u', '.join(results)) |
781 | |
782 | class Calc(Processor): |
783 | - """gcalc <expression>""" |
784 | + u"""gcalc <expression>""" |
785 | feature = 'google' |
786 | |
787 | user_agent = Option('user_agent', 'HTTP user agent to present to Google', user_agent) |
788 | @@ -57,7 +56,7 @@ |
789 | event.addresponse(font.b.string) |
790 | |
791 | class Define(Processor): |
792 | - """gdefine <term>""" |
793 | + u"""gdefine <term>""" |
794 | feature = 'google' |
795 | |
796 | user_agent = Option('user_agent', 'HTTP user agent to present to Google', user_agent) |
797 | @@ -70,7 +69,7 @@ |
798 | |
799 | definitions = [] |
800 | for li in soup.findAll('li'): |
801 | - definitions.append('"%s"' % li.contents[0]) |
802 | + definitions.append('"%s"' % li.contents[0].strip()) |
803 | |
804 | if definitions: |
805 | event.addresponse(', '.join(definitions)) |
806 | @@ -78,7 +77,7 @@ |
807 | event.addresponse(u"Are you making up words again?") |
808 | |
809 | class Compare(Processor): |
810 | - """google cmp [for] <term> and <term>""" |
811 | + u"""google cmp [for] <term> and <term>""" |
812 | feature = 'google' |
813 | |
814 | user_agent = Option('user_agent', 'HTTP user agent to present to Google', user_agent) |
815 | |
816 | === modified file 'ibid/plugins/help.py' |
817 | --- ibid/plugins/help.py 2009-02-21 12:31:38 +0000 |
818 | +++ ibid/plugins/help.py 2009-03-01 23:01:30 +0000 |
819 | @@ -3,10 +3,12 @@ |
820 | import ibid |
821 | from ibid.plugins import Processor, match |
822 | |
823 | -help = {'help': 'Provides help and usage information about plugins.'} |
824 | +help = {'help': u'Provides help and usage information about plugins.'} |
825 | |
826 | class Help(Processor): |
827 | - """(help|usage) [<feature>]""" |
828 | + u"""features |
829 | + help [<feature>] |
830 | + usage <feature>""" |
831 | feature = 'help' |
832 | |
833 | @match(r'^help$') |
834 | |
835 | === modified file 'ibid/plugins/http.py' |
836 | --- ibid/plugins/http.py 2009-02-03 18:03:49 +0000 |
837 | +++ ibid/plugins/http.py 2009-03-01 23:01:30 +0000 |
838 | @@ -8,15 +8,18 @@ |
839 | |
840 | title = re.compile(r'<title>(.*)<\/title>', re.I+re.S) |
841 | |
842 | -help['get'] = 'Retrieves a URL and returns the HTTP status and optionally the HTML title.' |
843 | +help['get'] = u'Retrieves a URL and returns the HTTP status and optionally the HTML title.' |
844 | class HTTP(Processor): |
845 | - """(get|head) <url>""" |
846 | + u"""(get|head) <url>""" |
847 | feature = 'get' |
848 | |
849 | max_size = IntOption('max_size', 'Only request this many bytes', 500) |
850 | |
851 | @match(r'^(get|head)\s+(.+)$') |
852 | def handler(self, event, action, url): |
853 | + if not url.lower().startswith("http://") and not url.lower().startswith("https://"): |
854 | + url = "http://" + url |
855 | + |
856 | http = Http() |
857 | headers={} |
858 | if action.lower() == 'get': |
859 | |
860 | === modified file 'ibid/plugins/identity.py' |
861 | --- ibid/plugins/identity.py 2009-02-23 20:43:40 +0000 |
862 | +++ ibid/plugins/identity.py 2009-03-01 23:01:30 +0000 |
863 | @@ -16,7 +16,7 @@ |
864 | |
865 | help['accounts'] = u'An account represents a person. An account has one or more identities, which is a user on a specific source.' |
866 | class Accounts(Processor): |
867 | - """create account <name>""" |
868 | + u"""create account <name>""" |
869 | feature = 'accounts' |
870 | |
871 | @match(r'^create\s+account\s+(.+)$') |
872 | @@ -56,7 +56,7 @@ |
873 | chars = string.letters + string.digits |
874 | |
875 | class Identities(Processor): |
876 | - """(I|<username>) (am|is) <identity> on <source> |
877 | + u"""(I am|<username> is) <identity> on <source> |
878 | remove identity <identity> on <source> [from <username>]""" |
879 | feature = 'accounts' |
880 | priority = -10 |
881 | @@ -179,9 +179,8 @@ |
882 | |
883 | session.close() |
884 | |
885 | -help['attributes'] = 'Adds and removes attributes attached to an account' |
886 | class Attributes(Processor): |
887 | - """set (my|<account>) <name> to <value>""" |
888 | + u"""set (my|<account>) <name> to <value>""" |
889 | feature = 'accounts' |
890 | |
891 | @match(r"^set\s+(my|.+?)(?:\'s)?\s+(.+)\s+to\s+(.+)$") |
892 | @@ -213,6 +212,8 @@ |
893 | log.info(u"Added attribute '%s' = '%s' to account %s (%s) by %s/%s (%s)", name, value, account.id, account.username, event.account, event.identity, event.sender['connection']) |
894 | |
895 | class Describe(Processor): |
896 | + u"""who (am I|is <username>)""" |
897 | + feature = "accounts" |
898 | |
899 | @match(r'^who\s+(?:is|am)\s+(I|.+?)$') |
900 | def describe(self, event, username): |
901 | |
902 | === modified file 'ibid/plugins/imdb.py' |
903 | --- ibid/plugins/imdb.py 2009-02-23 14:59:37 +0000 |
904 | +++ ibid/plugins/imdb.py 2009-03-01 23:01:30 +0000 |
905 | @@ -6,14 +6,17 @@ |
906 | from .. imdb import IMDb, IMDbDataAccessError, IMDbError |
907 | |
908 | from ibid.plugins import Processor, match |
909 | -from ibid.config import Option, IntOption |
910 | +from ibid.config import Option, BoolOption |
911 | |
912 | -help = {'imdb': 'Looks up movies on IMDB.com.'} |
913 | +help = {'imdb': u'Looks up movies on IMDB.com.'} |
914 | |
915 | class IMDB(Processor): |
916 | - "imdb [search] [character|company|episode|movie|person] <terms> [result <index>]" |
917 | + u"imdb [search] [character|company|episode|movie|person] <terms> [#<index>]" |
918 | feature = 'imdb' |
919 | |
920 | + access_system = Option("accesssystem", "Method of querying IMDB", "http") |
921 | + adult_search = BoolOption("adultsearch", "Include adult films in search results", True) |
922 | + |
923 | name_keys = { |
924 | "character": "long imdb name", |
925 | "company": "long imdb name", |
926 | @@ -23,10 +26,9 @@ |
927 | } |
928 | |
929 | def setup(self): |
930 | - # adultSearch = 1 is the default, but we expose this parameter for bot-owners to tweak. |
931 | - self.imdb = IMDb(accessSystem='http', adultSearch=1) |
932 | + self.imdb = IMDb(accessSystem=self.access_system, adultSearch=int(self.adult_search)) |
933 | |
934 | - @match(r'^imdb(?:\s+search)?(?:\s+(character|company|episode|movie|person))?\s+(.+?)(?:\s+result\s+(\d+))?$') |
935 | + @match(r'^imdb(?:\s+search)?(?:\s+(character|company|episode|movie|person))?\s+(.+?)(?:\s+#(\d+))?$') |
936 | def search(self, event, search_type, terms, index): |
937 | if search_type is None: |
938 | search_type = "movie" |
939 | @@ -60,7 +62,7 @@ |
940 | return |
941 | |
942 | if len(results) == 0: |
943 | - event.addresponse(u"Sorry, couldn't find that. You sure you know how to spell?") |
944 | + event.addresponse(u"Sorry, couldn't find that.") |
945 | else: |
946 | results = [x[self.name_keys[search_type]] for x in results] |
947 | results = enumerate(results) |
948 | @@ -99,7 +101,7 @@ |
949 | desc += u" Starring: %s." % (u", ".join(x["name"] for x in episode["cast"][:3])) |
950 | if episode.has_key("rating"): |
951 | desc += u" Rated: %.1f " % episode["rating"] |
952 | - desc += u", ".join(movie.get("genres", ())) |
953 | + desc += u", ".join(episode.get("genres", ())) |
954 | desc += u" Plot: %s" % episode.get("plot outline", u"Unknown") |
955 | return desc |
956 | |
957 | @@ -116,12 +118,7 @@ |
958 | return desc |
959 | |
960 | def display_person(self, person): |
961 | - # Quite a few fields are normally repeated in the bio, so we won't bother including them. |
962 | - #desc = u"%s: %s, Born " % (person.personID, person["name"]) |
963 | - #if person["birth name"] != person["name"]: |
964 | - # desc += u"%s " % person["birth name"] |
965 | - #desc += u"%s. %s" % (person["birth date"], u" ".join(person["mini biography"])) |
966 | - return u"%s: %s. %s. Bio: %s" % (person.personID, person["name"], |
967 | + desc = u"%s: %s. %s." % (person.personID, person["name"], |
968 | u", ".join(role.title() for role in ( |
969 | u"actor", u"animation department", u"art department", |
970 | u"art director", u"assistant director", u"camera department", |
971 | @@ -132,6 +129,12 @@ |
972 | u"production designer", u"set decorator", u"sound department", |
973 | u"speccial effects department", u"stunts", u"transport department", |
974 | u"visual effects department", u"writer", u"miscellaneous crew" |
975 | - ) if person.has_key(role)), u" ".join(person["mini biography"])) |
976 | + ) if person.has_key(role))) |
977 | + if person.has_key("mini biography"): |
978 | + desc += u" " + u" ".join(person["mini biography"]) |
979 | + else: |
980 | + if person.has_key("birth name") or person.has_key("birth date"): |
981 | + desc += u" Born %s." % u", ".join(person[attr] for attr in ("birth name", "birth date") if person.has_key(attr)) |
982 | + return desc |
983 | |
984 | # vi: set et sta sw=4 ts=4: |
985 | |
986 | === modified file 'ibid/plugins/info.py' |
987 | --- ibid/plugins/info.py 2009-02-23 20:29:44 +0000 |
988 | +++ ibid/plugins/info.py 2009-03-01 19:58:06 +0000 |
989 | @@ -1,15 +1,17 @@ |
990 | from subprocess import Popen, PIPE |
991 | +import os |
992 | |
993 | from nickometer import nickometer |
994 | |
995 | from ibid.plugins import Processor, match, RPC |
996 | from ibid.config import Option |
997 | +from ibid.utils import file_in_path, unicode_output |
998 | |
999 | help = {} |
1000 | |
1001 | help['fortune'] = u'Returns a random fortune.' |
1002 | class Fortune(Processor, RPC): |
1003 | - """fortune""" |
1004 | + u"""fortune""" |
1005 | feature = 'fortune' |
1006 | |
1007 | fortune = Option('fortune', 'Path of the fortune executable', 'fortune') |
1008 | @@ -18,6 +20,10 @@ |
1009 | super(Fortune, self).__init__(name) |
1010 | RPC.__init__(self) |
1011 | |
1012 | + def setup(self): |
1013 | + if not file_in_path(self.fortune): |
1014 | + raise Exception("Cannot locate fortune executeable") |
1015 | + |
1016 | @match(r'^fortune$') |
1017 | def handler(self, event): |
1018 | event.addresponse(self.remote_fortune() or u"Couldn't execute fortune") |
1019 | @@ -27,14 +33,16 @@ |
1020 | output, error = fortune.communicate() |
1021 | code = fortune.wait() |
1022 | |
1023 | + output = unicode_output(output.strip()) |
1024 | + |
1025 | if code == 0: |
1026 | - return output.strip() |
1027 | + return output |
1028 | else: |
1029 | return None |
1030 | |
1031 | help['nickometer'] = u'Calculates how lame a nick is.' |
1032 | class Nickometer(Processor): |
1033 | - """nickometer [<nick>] [with reasons]""" |
1034 | + u"""nickometer [<nick>] [with reasons]""" |
1035 | feature = 'nickometer' |
1036 | |
1037 | @match(r'^(?:nick|lame)-?o-?meter(?:(?:\s+for)?\s+(.+?))?(\s+with\s+reasons)?$') |
1038 | @@ -47,30 +55,42 @@ |
1039 | |
1040 | help['man'] = u'Retrieves information from manpages.' |
1041 | class Man(Processor): |
1042 | - """man [<section>] <page>""" |
1043 | + u"""man [<section>] <page>""" |
1044 | feature = 'man' |
1045 | |
1046 | man = Option('man', 'Path of the man executable', 'man') |
1047 | |
1048 | + def setup(self): |
1049 | + if not file_in_path(self.man): |
1050 | + raise Exception("Cannot locate man executeable") |
1051 | + |
1052 | @match(r'^man\s+(?:(\d)\s+)?(\S+)$') |
1053 | def handle_man(self, event, section, page): |
1054 | command = [self.man, page] |
1055 | if section: |
1056 | command.insert(1, section) |
1057 | - man = Popen(command, stdout=PIPE, stderr=PIPE) |
1058 | + |
1059 | + if page.strip().startswith("-"): |
1060 | + event.addresponse(False) |
1061 | + return |
1062 | + |
1063 | + env = os.environ.copy() |
1064 | + env["COLUMNS"] = "500" |
1065 | + |
1066 | + man = Popen(command, stdout=PIPE, stderr=PIPE, env=env) |
1067 | output, error = man.communicate() |
1068 | code = man.wait() |
1069 | |
1070 | if code != 0: |
1071 | event.addresponse(u'Manpage not found') |
1072 | else: |
1073 | - lines = [unicode(line, 'utf-8', errors='replace') for line in output.splitlines()] |
1074 | - index = lines.index('NAME') |
1075 | - if index: |
1076 | - event.addresponse(lines[index+1].strip()) |
1077 | - index = lines.index('SYNOPSIS') |
1078 | - if index: |
1079 | - event.addresponse(lines[index+1].strip()) |
1080 | - |
1081 | + output = unicode_output(output.strip(), errors="replace") |
1082 | + output = output.splitlines() |
1083 | + index = output.index('NAME') |
1084 | + if index: |
1085 | + event.addresponse(output[index+1].strip()) |
1086 | + index = output.index('SYNOPSIS') |
1087 | + if index: |
1088 | + event.addresponse(output[index+1].strip()) |
1089 | |
1090 | # vi: set et sta sw=4 ts=4: |
1091 | |
1092 | === modified file 'ibid/plugins/irc.py' |
1093 | --- ibid/plugins/irc.py 2009-02-12 19:59:13 +0000 |
1094 | +++ ibid/plugins/irc.py 2009-03-01 23:01:30 +0000 |
1095 | @@ -3,10 +3,10 @@ |
1096 | import ibid |
1097 | from ibid.plugins import Processor, match, authorise |
1098 | |
1099 | -help = {"irc": "Provides commands for joining/parting channels on IRC and Jabber, and changing the bot's nick"} |
1100 | +help = {"irc": u"Provides commands for joining/parting channels on IRC and Jabber, and changing the bot's nick"} |
1101 | |
1102 | class Actions(Processor): |
1103 | - """(join|part|leave) [<channel> [on <source>]] |
1104 | + u"""(join|part|leave) [<channel> [on <source>]] |
1105 | change nick to <nick> [on <source>]""" |
1106 | feature = 'irc' |
1107 | |
1108 | @@ -24,6 +24,10 @@ |
1109 | return |
1110 | channel = event.channel |
1111 | |
1112 | + if source.lower() not in ibid.sources: |
1113 | + event.addresponse(u"I don't have a source called %s" % source.lower()) |
1114 | + return |
1115 | + |
1116 | source = ibid.sources[source.lower()] |
1117 | |
1118 | if not hasattr(source, 'join'): |
1119 | @@ -43,6 +47,11 @@ |
1120 | |
1121 | if not source: |
1122 | source = event.source |
1123 | + |
1124 | + if source.lower() not in ibid.sources: |
1125 | + event.addresponse(u"I don't have a source called %s" % source.lower()) |
1126 | + return |
1127 | + |
1128 | source = ibid.sources[source.lower()] |
1129 | |
1130 | if not hasattr(source, 'change_nick'): |
1131 | |
1132 | === modified file 'ibid/plugins/karma.py' |
1133 | --- ibid/plugins/karma.py 2009-02-23 20:29:44 +0000 |
1134 | +++ ibid/plugins/karma.py 2009-03-01 23:01:30 +0000 |
1135 | @@ -28,9 +28,12 @@ |
1136 | self.value = 0 |
1137 | |
1138 | class Set(Processor): |
1139 | - """<subject> (++|--|==|ftw|ftl) [[reason]]""" |
1140 | + u"""<subject> (++|--|==|ftw|ftl) [[reason]]""" |
1141 | feature = 'karma' |
1142 | |
1143 | + # Clashes with morse |
1144 | + priority = 10 |
1145 | + |
1146 | permission = u'karma' |
1147 | |
1148 | increase = Option('increase', 'Suffixes which indicate increased karma', ('++', 'ftw')) |
1149 | @@ -85,7 +88,7 @@ |
1150 | event.processed = True |
1151 | |
1152 | class Get(Processor): |
1153 | - """karma for <subject> |
1154 | + u"""karma for <subject> |
1155 | [reverse] karmaladder""" |
1156 | feature = 'karma' |
1157 | |
1158 | |
1159 | === modified file 'ibid/plugins/lookup.py' |
1160 | --- ibid/plugins/lookup.py 2009-02-22 10:37:32 +0000 |
1161 | +++ ibid/plugins/lookup.py 2009-03-01 23:01:30 +0000 |
1162 | @@ -14,7 +14,12 @@ |
1163 | |
1164 | help = {} |
1165 | |
1166 | +help["lookup"] = u"Lookup things on popular sites." |
1167 | + |
1168 | class Bash(Processor): |
1169 | + u"bash[.org] (random|<number>)" |
1170 | + |
1171 | + feature = "lookup" |
1172 | |
1173 | @match(r'^bash(?:\.org)?\s+(random|\d+)$') |
1174 | def bash(self, event, quote): |
1175 | @@ -22,15 +27,23 @@ |
1176 | soup = BeautifulSoup(f.read(), convertEntities=BeautifulSoup.HTML_ENTITIES) |
1177 | f.close() |
1178 | |
1179 | + if quote.lower() == "random": |
1180 | + number = u"".join(soup.find('p', attrs={'class': 'quote'}).find('b').contents) |
1181 | + event.addresponse(u"%s:" % number) |
1182 | + |
1183 | quote = soup.find('p', attrs={'class': 'qt'}) |
1184 | if not quote: |
1185 | event.addresponse(u"There's no such quote, but if you keep talking like that maybe there will be.") |
1186 | else: |
1187 | for line in quote.contents: |
1188 | if str(line) != '<br />': |
1189 | - event.addresponse(str(line).strip()) |
1190 | + event.addresponse(unicode(line).strip()) |
1191 | |
1192 | class LastFm(Processor): |
1193 | + u"last.fm for <username>" |
1194 | + |
1195 | + feature = "lookup" |
1196 | + |
1197 | @match(r'^last\.?fm\s+for\s+(\S+?)\s*$') |
1198 | def listsongs(self, event, username): |
1199 | songs = feedparser.parse("http://ws.audioscrobbler.com/1.0/user/%s/recenttracks.rss?%s" % (username, time())) |
1200 | @@ -41,7 +54,8 @@ |
1201 | |
1202 | help['lotto'] = u"Gets the latest lotto results from the South African National Lottery" |
1203 | class Lotto(Processor): |
1204 | - """lotto""" |
1205 | + u"""lotto""" |
1206 | + |
1207 | feature = 'lotto' |
1208 | |
1209 | errors = { |
1210 | @@ -51,7 +65,7 @@ |
1211 | |
1212 | za_url = 'http://www.nationallottery.co.za/' |
1213 | za_re = re.compile(r'images/balls/ball_(\d+).gif') |
1214 | - za_text = 'Latest lotto results for South Africa, Lotto: ' |
1215 | + za_text = u'Latest lotto results for South Africa, Lotto: ' |
1216 | |
1217 | @match(r'lotto(\s+for\s+south\s+africa)?') |
1218 | def za(self, event, za): |
1219 | @@ -69,15 +83,18 @@ |
1220 | |
1221 | if len(balls) != 14: |
1222 | return event.addresponse(self.errors['balls'] % \ |
1223 | - (14, len(balls), ", ".join(balls))) |
1224 | + (14, len(balls), u", ".join(balls))) |
1225 | |
1226 | - r += " ".join(balls[:6]) |
1227 | - r += " (Bonus: %s), Lotto Plus: " % (balls[6], ) |
1228 | - r += " ".join(balls[7:13]) |
1229 | - r += " (Bonus: %s)" % (balls[13], ) |
1230 | + r += u" ".join(balls[:6]) |
1231 | + r += u" (Bonus: %s), Lotto Plus: " % (balls[6], ) |
1232 | + r += u" ".join(balls[7:13]) |
1233 | + r += u" (Bonus: %s)" % (balls[13], ) |
1234 | event.addresponse(r) |
1235 | |
1236 | class FMyLife(Processor): |
1237 | + u"""fml (<number>|random)""" |
1238 | + |
1239 | + feature = "lookup" |
1240 | |
1241 | def remote_get(self, id): |
1242 | f = urlopen('http://www.fmylife.com/' + str(id)) |
1243 | @@ -87,11 +104,17 @@ |
1244 | quote = soup.find('div', id='wrapper').div.p |
1245 | return quote and u'"%s"' % (quote.contents[0],) or None |
1246 | |
1247 | - @match(r'^(?:fml\s+|http://www\.fmylife\.com/\S+/)(\d+)$') |
1248 | + @match(r'^(?:fml\s+|http://www\.fmylife\.com/\S+/)(\d+|random)$') |
1249 | def fml(self, event, id): |
1250 | - event.addresponse(self.remote_get(int(id)) or u"No such quote") |
1251 | + event.addresponse(self.remote_get(id) or u"No such quote") |
1252 | + |
1253 | +help["microblog"] = u"Looks up messages on microblogging services like twitter and identica." |
1254 | |
1255 | class Twitter(Processor): |
1256 | + u"""latest (tweet|identica) from <name> |
1257 | + (tweet|identica) <number>""" |
1258 | + |
1259 | + feature = "microblog" |
1260 | |
1261 | default = { 'twitter': 'http://twitter.com/', |
1262 | 'tweet': 'http://twitter.com/', |
1263 | @@ -135,6 +158,10 @@ |
1264 | event.addresponse(self.remote_update('identi.ca', int(id))) |
1265 | |
1266 | class Currency(Processor): |
1267 | + u"""exchange <amount> <currency> for <currency> |
1268 | + currencies for <country>""" |
1269 | + |
1270 | + feature = "lookup" |
1271 | |
1272 | headers = {'User-Agent': 'Mozilla/5.0', 'Referer': 'http://www.xe.com/'} |
1273 | currencies = [] |
1274 | @@ -179,6 +206,10 @@ |
1275 | event.addresponse(u'No currencies found') |
1276 | |
1277 | class Weather(Processor): |
1278 | + u"""weather in <city> |
1279 | + forecast for <city>""" |
1280 | + |
1281 | + feature = "lookup" |
1282 | |
1283 | defaults = { 'ct': 'Cape Town, South Africa', |
1284 | 'jhb': 'Johannesburg, South Africa', |
1285 | |
1286 | === modified file 'ibid/plugins/math.py' |
1287 | --- ibid/plugins/math.py 2009-02-26 10:05:53 +0000 |
1288 | +++ ibid/plugins/math.py 2009-03-01 19:58:06 +0000 |
1289 | @@ -2,17 +2,22 @@ |
1290 | |
1291 | from ibid.plugins import Processor, match |
1292 | from ibid.config import Option |
1293 | +from ibid.utils import file_in_path, unicode_output |
1294 | |
1295 | help = {} |
1296 | |
1297 | help['bc'] = u'Calculate mathematical expressions using bc' |
1298 | class BC(Processor): |
1299 | - """bc <expression>""" |
1300 | + u"""bc <expression>""" |
1301 | |
1302 | feature = 'bc' |
1303 | |
1304 | bc = Option('bc', 'Path to bc executable', 'bc') |
1305 | |
1306 | + def setup(self): |
1307 | + if not file_in_path(self.bc): |
1308 | + raise Exception("Cannot locate bc executeable") |
1309 | + |
1310 | @match(r'^bc\s+(.+)$') |
1311 | def calculate(self, event, expression): |
1312 | bc = Popen([self.bc, '-l'], stdin=PIPE, stdout=PIPE, stderr=PIPE) |
1313 | @@ -20,11 +25,23 @@ |
1314 | code = bc.wait() |
1315 | |
1316 | if code == 0: |
1317 | - event.addresponse(output.strip()) |
1318 | + if output: |
1319 | + output = unicode_output(output.strip()) |
1320 | + output = output.replace('\\\n', '') |
1321 | + event.addresponse(output) |
1322 | + else: |
1323 | + error = unicode_output(error.strip()) |
1324 | + error = error.split(":", 1)[1].strip() |
1325 | + error = error[0].lower() + error[1:] |
1326 | + event.addresponse(u"You can't %s" % error) |
1327 | + else: |
1328 | + event.addresponse(u"Error running bc") |
1329 | + error = unicode_output(error.strip()) |
1330 | + raise Exception("BC Error: %s" % error) |
1331 | |
1332 | -help['calc'] = 'Returns the anwser to mathematical expressions' |
1333 | +help['calc'] = u'Returns the anwser to mathematical expressions' |
1334 | class Calc(Processor): |
1335 | - """[calc] <expression>""" |
1336 | + u"""[calc] <expression>""" |
1337 | feature = 'calc' |
1338 | |
1339 | priority = 500 |
1340 | |
1341 | === modified file 'ibid/plugins/memo.py' |
1342 | --- ibid/plugins/memo.py 2009-02-23 20:29:44 +0000 |
1343 | +++ ibid/plugins/memo.py 2009-03-01 23:01:30 +0000 |
1344 | @@ -13,7 +13,7 @@ |
1345 | from ibid.models import Base, Identity, Account |
1346 | from ibid.utils import ago |
1347 | |
1348 | -help = {'memo': 'Keeps messages for people.'} |
1349 | +help = {'memo': u'Keeps messages for people.'} |
1350 | |
1351 | memo_cache = {} |
1352 | log = logging.getLogger('plugins.memo') |
1353 | @@ -40,7 +40,7 @@ |
1354 | Memo.recipient = relation(Identity, primaryjoin=Memo.to_id==Identity.id) |
1355 | |
1356 | class Tell(Processor): |
1357 | - """(tell|pm|privmsg|msg) <person> <message>""" |
1358 | + u"""(tell|pm|privmsg|msg) <person> <message>""" |
1359 | feature = 'memo' |
1360 | |
1361 | permission = u'sendmemo' |
1362 | @@ -135,7 +135,7 @@ |
1363 | session.close() |
1364 | |
1365 | class Messages(Processor): |
1366 | - """my messages |
1367 | + u"""my messages |
1368 | message <number>""" |
1369 | feature = 'memo' |
1370 | |
1371 | |
1372 | === modified file 'ibid/plugins/misc.py' |
1373 | --- ibid/plugins/misc.py 2009-02-23 20:29:44 +0000 |
1374 | +++ ibid/plugins/misc.py 2009-03-01 23:01:30 +0000 |
1375 | @@ -9,7 +9,7 @@ |
1376 | |
1377 | help['coffee'] = u"Times coffee brewing and reserves cups for people" |
1378 | class Coffee(Processor): |
1379 | - """coffee (on|please)""" |
1380 | + u"""coffee (on|please)""" |
1381 | feature = 'coffee' |
1382 | |
1383 | pot = None |
1384 | @@ -48,7 +48,7 @@ |
1385 | |
1386 | help['version'] = u"Show the Ibid version currently running" |
1387 | class Version(Processor): |
1388 | - """version""" |
1389 | + u"""version""" |
1390 | feature = 'version' |
1391 | |
1392 | @match(r'^version$') |
1393 | @@ -57,7 +57,7 @@ |
1394 | |
1395 | help['dvorak'] = u"Makes text typed on a QWERTY keyboard as if it was Dvorak work, and vice-versa" |
1396 | class Dvorak(Processor): |
1397 | - """(aoeu|asdf) <text>""" |
1398 | + u"""(aoeu|asdf) <text>""" |
1399 | feature = 'dvorak' |
1400 | |
1401 | # List of characters on each keyboard layout |
1402 | @@ -69,11 +69,11 @@ |
1403 | # Typed by a Dvorak typist on a QWERTY-mapped keyboard |
1404 | typed_on_qwerty = dict(zip(map(ord, qwermap), dvormap)) |
1405 | |
1406 | - @match(r'asdf\s+(.+)') |
1407 | + @match(r'(?:asdf|dvorak)\s+(.+)') |
1408 | def convert_from_qwerty(self, event, text): |
1409 | event.addresponse(text.translate(self.typed_on_qwerty)) |
1410 | |
1411 | - @match(r'aoeu\s+(.+)') |
1412 | + @match(r'(?:aoeu|querty)\s+(.+)') |
1413 | def convert_from_dvorak(self, event, text): |
1414 | event.addresponse(text.translate(self.typed_on_dvorak)) |
1415 | |
1416 | |
1417 | === modified file 'ibid/plugins/morse.py' |
1418 | --- ibid/plugins/morse.py 2009-01-24 12:39:04 +0000 |
1419 | +++ ibid/plugins/morse.py 2009-03-01 23:01:30 +0000 |
1420 | @@ -2,8 +2,10 @@ |
1421 | |
1422 | help = {} |
1423 | |
1424 | +help["morse"] = u"Translates messages into and out of morse code." |
1425 | + |
1426 | class Morse(Processor): |
1427 | - """morse (text|morsecode)""" |
1428 | + u"""morse (text|morsecode)""" |
1429 | feature = 'morse' |
1430 | |
1431 | @match(r'^morse\s+(.+)$') |
1432 | @@ -64,12 +66,12 @@ |
1433 | |
1434 | |
1435 | def text2morse(text): |
1436 | - return " ".join(table.get(c.upper(), c) for c in text) |
1437 | + return u" ".join(table.get(c.upper(), c) for c in text) |
1438 | |
1439 | def morse2text(morse): |
1440 | rtable = dict((v, k) for k, v in table.items()) |
1441 | toks = morse.split(' ') |
1442 | - return " ".join(rtable.get(t, t) for t in toks) |
1443 | + return u" ".join(rtable.get(t, t) for t in toks) |
1444 | |
1445 | if message.replace('-', '').replace('.', '').isspace(): |
1446 | event.addresponse(morse2text(message)) |
1447 | |
1448 | === modified file 'ibid/plugins/network.py' |
1449 | --- ibid/plugins/network.py 2009-02-03 18:03:49 +0000 |
1450 | +++ ibid/plugins/network.py 2009-03-01 19:58:06 +0000 |
1451 | @@ -6,17 +6,18 @@ |
1452 | |
1453 | from ibid.plugins import Processor, match |
1454 | from ibid.config import Option |
1455 | +from ibid.utils import file_in_path, unicode_output |
1456 | |
1457 | help = {} |
1458 | ipaddr = re.compile('\d+\.\d+\.\d+\.\d+') |
1459 | |
1460 | help['dns'] = u'Performs DNS lookups' |
1461 | class DNS(Processor): |
1462 | - """(dns|nslookup|dig) [<record type>] [for] <host> [(from|@) <nameserver>]""" |
1463 | + u"""dns [<record type>] [for] <host> [from <nameserver>]""" |
1464 | |
1465 | feature = 'dns' |
1466 | |
1467 | - @match(r'^(?:dns|nslookup|dig)(?:\s+(a|aaaa|ptr|ns|soa|cname|mx|txt|spf|srv|sshfp|cert))?\s+(?:for\s+)?(\S+?)(?:\s+(?:from\s+|@)\s*(\S+))?$') |
1468 | + @match(r'^(?:dns|nslookup|dig|host)(?:\s+(a|aaaa|ptr|ns|soa|cname|mx|txt|spf|srv|sshfp|cert))?\s+(?:for\s+)?(\S+?)(?:\s+(?:from\s+|@)\s*(\S+))?$') |
1469 | def resolve(self, event, record, host, nameserver): |
1470 | if not record: |
1471 | if ipaddr.search(host): |
1472 | @@ -42,36 +43,49 @@ |
1473 | |
1474 | responses = [] |
1475 | for rdata in answers: |
1476 | - responses.append(str(rdata)) |
1477 | - |
1478 | - event.addresponse(', '.join(responses)) |
1479 | - |
1480 | -help['ping'] = 'ICMP pings the specified host.' |
1481 | + responses.append(unicode(rdata)) |
1482 | + |
1483 | + event.addresponse(u', '.join(responses)) |
1484 | + |
1485 | +help['ping'] = u'ICMP pings the specified host.' |
1486 | class Ping(Processor): |
1487 | - """ping <host>""" |
1488 | + u"""ping <host>""" |
1489 | feature = 'ping' |
1490 | |
1491 | ping = Option('ping', 'Path to ping executable', 'ping') |
1492 | |
1493 | + def setup(self): |
1494 | + if not file_in_path(self.ping): |
1495 | + raise Exception("Cannot locate ping executeable") |
1496 | + |
1497 | @match(r'^ping\s+(\S+)$') |
1498 | def handle_ping(self, event, host): |
1499 | - |
1500 | + if host.strip().startswith("-"): |
1501 | + event.addresponse(False) |
1502 | + return |
1503 | + |
1504 | ping = Popen([self.ping, '-q', '-c5', host], stdout=PIPE, stderr=PIPE) |
1505 | output, error = ping.communicate() |
1506 | code = ping.wait() |
1507 | |
1508 | if code == 0: |
1509 | - event.addresponse(' '.join(output.splitlines()[-2:])) |
1510 | + output = unicode_output(' '.join(output.splitlines()[-2:])) |
1511 | + event.addresponse(output) |
1512 | else: |
1513 | - event.addresponse(error.replace('\n', ' ').replace('ping:', '', 1).strip()) |
1514 | + error = unicode_output(error.replace('\n', ' ').replace('ping:', '', 1).strip()) |
1515 | + event.addresponse(error) |
1516 | |
1517 | -help['tracepath'] = 'Traces the path to the given host.' |
1518 | +help['tracepath'] = u'Traces the path to the given host.' |
1519 | class Tracepath(Processor): |
1520 | - """tracepath <host>""" |
1521 | + u"""tracepath <host>""" |
1522 | feature = 'tracepath' |
1523 | |
1524 | tracepath = Option('tracepath', 'Path to tracepath executable', 'tracepath') |
1525 | |
1526 | + def setup(self): |
1527 | + if not file_in_path(self.tracepath): |
1528 | + raise Exception("Cannot locate tracepath executeable") |
1529 | + |
1530 | @match(r'^tracepath\s+(\S+)$') |
1531 | def handle_tracepath(self, event, host): |
1532 | |
1533 | @@ -80,31 +94,53 @@ |
1534 | code = tracepath.wait() |
1535 | |
1536 | if code == 0: |
1537 | + output = unicode_output(output) |
1538 | for line in output.splitlines(): |
1539 | event.addresponse(line) |
1540 | else: |
1541 | - event.addresponse(error.replace('\n', ' ').strip()) |
1542 | + error = unicode_output(error.strip()) |
1543 | + event.addresponse(error.replace('\n', ' ')) |
1544 | |
1545 | -help['ipcalc'] = 'IP address calculator' |
1546 | +help['ipcalc'] = u'IP address calculator' |
1547 | class IPCalc(Processor): |
1548 | - """ipcalc <network> <subnet> |
1549 | + u"""ipcalc <network> <subnet> |
1550 | ipcalc <address> - <address>""" |
1551 | feature = 'ipcalc' |
1552 | |
1553 | ipcalc = Option('ipcalc', 'Path to ipcalc executable', 'ipcalc') |
1554 | |
1555 | + deaggregate_re = re.compile(r'^((?:\d{1,3}\.){3}\d{1,3})\s+-\s+((?:\d{1,3}\.){3}\d{1,3})$') |
1556 | + |
1557 | + def setup(self): |
1558 | + if not file_in_path(self.ipcalc): |
1559 | + raise Exception("Cannot locate ipcalc executeable") |
1560 | + |
1561 | @match(r'^ipcalc\s+(.+)$') |
1562 | def handle_ipcalc(self, event, parameter): |
1563 | - |
1564 | - ipcalc = Popen([self.ipcalc, '-n', '-b', parameter], stdout=PIPE, stderr=PIPE) |
1565 | + if parameter.strip().startswith("-"): |
1566 | + event.addresponse(False) |
1567 | + return |
1568 | + |
1569 | + m = self.deaggregate_re.match(parameter) |
1570 | + if m: |
1571 | + parameter = [m.group(1), '-', m.group(2)] |
1572 | + else: |
1573 | + parameter = [parameter] |
1574 | + |
1575 | + ipcalc = Popen([self.ipcalc, '-n', '-b'] + parameter, stdout=PIPE, stderr=PIPE) |
1576 | output, error = ipcalc.communicate() |
1577 | code = ipcalc.wait() |
1578 | |
1579 | if code == 0: |
1580 | - for line in output.splitlines(): |
1581 | - if line: |
1582 | - event.addresponse(line) |
1583 | + output = unicode_output(output) |
1584 | + if output.startswith("INVALID ADDRESS"): |
1585 | + event.addresponse(u"That's an invalid address. Try something like 192.168.1.0/24") |
1586 | + else: |
1587 | + for line in output.splitlines(): |
1588 | + if line.strip(): |
1589 | + event.addresponse(line) |
1590 | else: |
1591 | + error = unicode_output(error.strip()) |
1592 | event.addresponse(error.replace('\n', ' ')) |
1593 | |
1594 | # vi: set et sta sw=4 ts=4: |
1595 | |
1596 | === modified file 'ibid/plugins/roshambo.py' |
1597 | --- ibid/plugins/roshambo.py 2009-01-24 12:39:04 +0000 |
1598 | +++ ibid/plugins/roshambo.py 2009-03-01 23:01:30 +0000 |
1599 | @@ -6,9 +6,9 @@ |
1600 | |
1601 | choices = ['paper', 'rock', 'scissors'] |
1602 | |
1603 | -help['roshambo'] = 'Plays rock, paper, scissors.' |
1604 | +help['roshambo'] = u'Plays rock, paper, scissors.' |
1605 | class RoShamBo(Processor): |
1606 | - """roshambo (rock|paper|scissors)""" |
1607 | + u"""roshambo (rock|paper|scissors)""" |
1608 | feature = 'roshambo' |
1609 | |
1610 | @match(r'^roshambo\s+(rock|paper|scissors)$') |
1611 | @@ -17,11 +17,11 @@ |
1612 | bchoice = randint(0, 2) |
1613 | |
1614 | if uchoice == bchoice: |
1615 | - reply = 'We drew! I also chose %s' % choices[bchoice] |
1616 | + reply = u'We drew! I also chose %s' % choices[bchoice] |
1617 | elif (uchoice + 1) % 3 == bchoice: |
1618 | - reply = 'You win! I chose %s :-(' % choices[bchoice] |
1619 | + reply = u'You win! I chose %s :-(' % choices[bchoice] |
1620 | else: |
1621 | - reply = 'I win! I chose %s' % choices[bchoice] |
1622 | + reply = u'I win! I chose %s' % choices[bchoice] |
1623 | |
1624 | event.addresponse(reply) |
1625 | return event |
1626 | |
1627 | === modified file 'ibid/plugins/seen.py' |
1628 | --- ibid/plugins/seen.py 2009-02-18 19:05:10 +0000 |
1629 | +++ ibid/plugins/seen.py 2009-03-01 23:01:30 +0000 |
1630 | @@ -10,7 +10,7 @@ |
1631 | from ibid.models import Base, Identity, Account |
1632 | from ibid.utils import ago |
1633 | |
1634 | -help = {'seen': 'Records when people were last seen.'} |
1635 | +help = {'seen': u'Records when people were last seen.'} |
1636 | |
1637 | class Sighting(Base): |
1638 | __table__ = Table('seen', Base.metadata, |
1639 | @@ -64,7 +64,7 @@ |
1640 | session.close() |
1641 | |
1642 | class Seen(Processor): |
1643 | - """seen <who>""" |
1644 | + u"""seen <who>""" |
1645 | feature = 'seen' |
1646 | |
1647 | datetime_format = Option('datetime_format', 'Format string for timestamps', '%Y/%m/%d %H:%M:%S') |
1648 | |
1649 | === modified file 'ibid/plugins/sources.py' |
1650 | --- ibid/plugins/sources.py 2009-02-17 21:04:17 +0000 |
1651 | +++ ibid/plugins/sources.py 2009-03-01 23:01:30 +0000 |
1652 | @@ -1,10 +1,10 @@ |
1653 | import ibid |
1654 | from ibid.plugins import Processor, match, authorise |
1655 | |
1656 | -help = {'sources': 'Controls and lists the configured sources.'} |
1657 | +help = {'sources': u'Controls and lists the configured sources.'} |
1658 | |
1659 | class Admin(Processor): |
1660 | - """(connect|disconnect) (to|from) <source> |
1661 | + u"""(connect|disconnect) (to|from) <source> |
1662 | load <source> source""" |
1663 | feature = 'sources' |
1664 | |
1665 | @@ -37,7 +37,7 @@ |
1666 | event.addresponse(u"Couldn't load %s source" % source) |
1667 | |
1668 | class Info(Processor): |
1669 | - """(sources|list configured sources)""" |
1670 | + u"""(sources|list configured sources)""" |
1671 | feature = 'sources' |
1672 | |
1673 | @match(r'^sources$') |
1674 | |
1675 | === modified file 'ibid/plugins/tools.py' |
1676 | --- ibid/plugins/tools.py 2009-02-23 13:08:26 +0000 |
1677 | +++ ibid/plugins/tools.py 2009-03-01 19:58:06 +0000 |
1678 | @@ -4,31 +4,32 @@ |
1679 | |
1680 | from ibid.plugins import Processor, match |
1681 | from ibid.config import Option |
1682 | +from ibid.utils import file_in_path, unicode_output |
1683 | |
1684 | help = {} |
1685 | |
1686 | -help['retest'] = 'Checks whether a regular expression matches a given string.' |
1687 | +help['retest'] = u'Checks whether a regular expression matches a given string.' |
1688 | class ReTest(Processor): |
1689 | - """does <pattern> match <string>""" |
1690 | + u"""does <pattern> match <string>""" |
1691 | feature = 'retest' |
1692 | |
1693 | @match('^does\s+(.+?)\s+match\s+(.+?)$') |
1694 | def retest(self, event, regex, string): |
1695 | - event.addresponse(re.search(regex, string) and 'Yes' or 'No') |
1696 | + event.addresponse(re.search(regex, string) and u'Yes' or u'No') |
1697 | |
1698 | -help['random'] = 'Generates random numbers.' |
1699 | +help['random'] = u'Generates random numbers.' |
1700 | class Random(Processor): |
1701 | - """random [ <max> | <min> <max> ]""" |
1702 | + u"""random [ <max> | <min> <max> ]""" |
1703 | feature = 'random' |
1704 | |
1705 | @match('^rand(?:om)?(?:\s+(\d+)(?:\s+(\d+))?)?$') |
1706 | def random(self, event, begin, end): |
1707 | if not begin and not end: |
1708 | - event.addresponse(str(random())) |
1709 | + event.addresponse(unicode(random())) |
1710 | else: |
1711 | begin = int(begin) |
1712 | end = end and int(end) or 0 |
1713 | - event.addresponse(str(randint(min(begin,end), max(begin,end)))) |
1714 | + event.addresponse(unicode(randint(min(begin,end), max(begin,end)))) |
1715 | |
1716 | bases = { 'bin': (lambda x: int(x, 2), lambda x: "".join(map(lambda y:str((x>>y)&1), range(8-1, -1, -1)))), |
1717 | 'hex': (lambda x: int(x, 6), hex), |
1718 | @@ -36,9 +37,9 @@ |
1719 | 'dec': (lambda x: int(x, 10), lambda x: x), |
1720 | 'ascii': (ord, chr), |
1721 | } |
1722 | -help['base'] = 'Converts between numeric bases as well as ASCII.' |
1723 | +help['base'] = u'Converts between numeric bases as well as ASCII.' |
1724 | class Base(Processor): |
1725 | - """convert <num> from <base> to <base>""" |
1726 | + u"""convert <num> from <base> to <base>""" |
1727 | feature = 'base' |
1728 | |
1729 | @match(r'^convert\s+(\S+)\s+(?:from\s+)?(%s)\s+(?:to\s+)?(%s)$' % ('|'.join(bases.keys()), '|'.join(bases.keys()))) |
1730 | @@ -48,9 +49,9 @@ |
1731 | event.addresponse(str(number)) |
1732 | |
1733 | |
1734 | -help['units'] = 'Converts values between various units.' |
1735 | +help['units'] = u'Converts values between various units.' |
1736 | class Units(Processor): |
1737 | - """convert [<value>] <unit> to <unit>""" |
1738 | + u"""convert [<value>] <unit> to <unit>""" |
1739 | feature = 'units' |
1740 | |
1741 | units = Option('units', 'Path to units executable', 'units') |
1742 | @@ -69,6 +70,10 @@ |
1743 | |
1744 | temp_function_names = set(temp_scale_names.values()) |
1745 | |
1746 | + def setup(self): |
1747 | + if not file_in_path(self.units): |
1748 | + raise Exception("Cannot locate units executeable") |
1749 | + |
1750 | def format_temperature(self, unit): |
1751 | "Return the unit, and convert to 'tempX' format if a known temperature scale" |
1752 | |
1753 | @@ -99,6 +104,7 @@ |
1754 | output, error = units.communicate() |
1755 | code = units.wait() |
1756 | |
1757 | + output = unicode_output(output) |
1758 | result = output.splitlines()[0].strip() |
1759 | |
1760 | if code == 0: |
1761 | |
1762 | === modified file 'ibid/plugins/trac.py' |
1763 | --- ibid/plugins/trac.py 2009-02-23 20:29:44 +0000 |
1764 | +++ ibid/plugins/trac.py 2009-03-01 23:01:30 +0000 |
1765 | @@ -9,7 +9,7 @@ |
1766 | from ibid.config import Option |
1767 | from ibid.utils import ago |
1768 | |
1769 | -help = {'trac': 'Retrieves tickets from a Trac database.'} |
1770 | +help = {'trac': u'Retrieves tickets from a Trac database.'} |
1771 | |
1772 | class Ticket(object): |
1773 | pass |
1774 | @@ -20,7 +20,7 @@ |
1775 | mapper(Ticket, ticket_table) |
1776 | |
1777 | class GetTicket(Processor, RPC): |
1778 | - """ticket <number> |
1779 | + u"""ticket <number> |
1780 | (open|my|<who>'s) tickets""" |
1781 | feature = 'trac' |
1782 | |
1783 | |
1784 | === modified file 'ibid/plugins/url.py' |
1785 | --- ibid/plugins/url.py 2009-02-18 19:11:41 +0000 |
1786 | +++ ibid/plugins/url.py 2009-03-01 23:01:30 +0000 |
1787 | @@ -1,4 +1,5 @@ |
1788 | from datetime import datetime |
1789 | +from urllib import urlencode |
1790 | from urllib2 import urlopen, HTTPRedirectHandler, build_opener, HTTPError |
1791 | import re |
1792 | |
1793 | @@ -9,7 +10,7 @@ |
1794 | from ibid.config import Option |
1795 | from ibid.models import Base |
1796 | |
1797 | -help = {'url': 'Captures URLs seen in channel, and shortens and lengthens URLs'} |
1798 | +help = {'url': u'Captures URLs seen in channel, and shortens and lengthens URLs'} |
1799 | |
1800 | class URL(Base): |
1801 | __table__ = Table('urls', Base.metadata, |
1802 | @@ -46,16 +47,16 @@ |
1803 | session.close() |
1804 | |
1805 | class Shorten(Processor): |
1806 | - """shorten <url>""" |
1807 | + u"""shorten <url>""" |
1808 | feature = 'url' |
1809 | |
1810 | @match(r'^shorten\s+(\S+\.\S+)$') |
1811 | def shorten(self, event, url): |
1812 | - f = urlopen('http://is.gd/api.php?longurl=%s' % url) |
1813 | + f = urlopen('http://is.gd/api.php?%s' % urlencode({'longurl': url})) |
1814 | shortened = f.read() |
1815 | f.close() |
1816 | |
1817 | - event.addresponse(shortened) |
1818 | + event.addresponse(unicode(shortened)) |
1819 | |
1820 | class NullRedirect(HTTPRedirectHandler): |
1821 | |
1822 | @@ -63,10 +64,14 @@ |
1823 | return None |
1824 | |
1825 | class Lengthen(Processor): |
1826 | - """<url>""" |
1827 | + u"""<url>""" |
1828 | feature = 'url' |
1829 | |
1830 | - services = Option('services', 'List of URL prefixes of URL shortening services', ('http://is.gd/', 'http://tinyurl.com/', 'http://ff.im/', 'http://shorl.com/', 'http://icanhaz.com/', 'http://url.omnia.za.net/', 'http://snipurl.com/', 'http://tr.im/', 'http://snipr.com/')) |
1831 | + services = Option('services', 'List of URL prefixes of URL shortening services', ( |
1832 | + 'http://is.gd/', 'http://tinyurl.com/', 'http://ff.im/', |
1833 | + 'http://shorl.com/', 'http://icanhaz.com/', 'http://url.omnia.za.net/', |
1834 | + 'http://snipurl.com/', 'http://tr.im/', 'http://snipr.com/' |
1835 | + )) |
1836 | |
1837 | def setup(self): |
1838 | self.lengthen.im_func.pattern = re.compile(r'^((?:%s)\S+)$' % '|'.join([re.escape(service) for service in self.services]), re.I) |
1839 | @@ -78,7 +83,7 @@ |
1840 | f = opener.open(url) |
1841 | except HTTPError, e: |
1842 | if e.code in (301, 302, 303, 307): |
1843 | - event.addresponse(e.hdrs['location']) |
1844 | + event.addresponse(unicode(e.hdrs['location'])) |
1845 | return |
1846 | |
1847 | f.close() |
1848 | |
1849 | === modified file 'ibid/source/irc.py' |
1850 | --- ibid/source/irc.py 2009-02-23 20:29:44 +0000 |
1851 | +++ ibid/source/irc.py 2009-03-01 23:04:43 +0000 |
1852 | @@ -100,7 +100,8 @@ |
1853 | def send(self, response): |
1854 | message = response['reply'].replace('\n', ' ')[:490] |
1855 | if 'action' in response and response['action']: |
1856 | - self.me(response['target'].encode('utf-8'), message.encode('utf-8')) |
1857 | + # We can't use self.me() because it prepends a # onto channel names |
1858 | + self.ctcpMakeQuery(response['target'].encode('utf-8'), [('ACTION', message.encode('utf-8'))]) |
1859 | self.factory.log.debug(u"Sent action to %s: %s", response['target'], message) |
1860 | else: |
1861 | self.msg(response['target'].encode('utf-8'), message.encode('utf-8')) |
1862 | |
1863 | === modified file 'ibid/utils.py' |
1864 | --- ibid/utils.py 2009-02-21 20:06:39 +0000 |
1865 | +++ ibid/utils.py 2009-03-01 19:58:06 +0000 |
1866 | @@ -1,4 +1,6 @@ |
1867 | from htmlentitydefs import name2codepoint |
1868 | +import os |
1869 | +import os.path |
1870 | import re |
1871 | |
1872 | def ago(delta, units=None): |
1873 | @@ -28,3 +30,16 @@ |
1874 | def decode_htmlentities(string): |
1875 | entity_re = re.compile("&(#?)(\d{1,5}|\w{1,8});") |
1876 | return entity_re.subn(substitute_entity, string)[0] |
1877 | + |
1878 | +def file_in_path(program): |
1879 | + path = os.environ.get("PATH", os.defpath).split(os.pathsep) |
1880 | + path = [os.path.join(dir, program) for dir in path] |
1881 | + path = [True for file in path if os.path.isfile(file)] |
1882 | + return bool(path) |
1883 | + |
1884 | +def unicode_output(output, errors="strict"): |
1885 | + try: |
1886 | + encoding = os.getenv("LANG").split(".")[1] |
1887 | + except: |
1888 | + encoding = "ascii" |
1889 | + return unicode(output, encoding, errors) |
A day's worth of plugin hacking. Unfortunately, rather disparate.