Merge lp:~stefanor/ibid/misc into lp:~ibid-core/ibid/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
Reviewer Review Type Date Requested Status
Michael Gorven Approve
Jonathan Hitchcock Approve
Review via email: mp+4051@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Stefano Rivera (stefanor) wrote :

A day's worth of plugin hacking. Unfortunately, rather disparate.

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)

Subscribers

People subscribed via source and target branches