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
=== modified file 'INSTALL'
--- INSTALL 2009-02-24 23:55:20 +0000
+++ INSTALL 2009-02-24 16:03:17 +0000
@@ -7,7 +7,8 @@
7# apt-get install python-virtualenv python-soappy python-twisted \7# apt-get install python-virtualenv python-soappy python-twisted \
8 python-configobj python-sqllite2 python-feedparser \8 python-configobj python-sqllite2 python-feedparser \
9 python-httplib2 python-beautifulsoup python-dictclient \9 python-httplib2 python-beautifulsoup python-dictclient \
10 python-imdbpy python-dns pysilc python-pinder10 python-imdbpy python-dns python-simplejson \
11 python-jinja pysilc python-pinder
1112
12Switch to the user ibid will be running as.13Switch to the user ibid will be running as.
13Set up a virtual Python environment14Set up a virtual Python environment
1415
=== modified file 'ibid/config.ini'
--- ibid/config.ini 2009-02-23 09:44:40 +0000
+++ ibid/config.ini 2009-03-01 15:16:39 +0000
@@ -32,18 +32,18 @@
32 [[smtp]]32 [[smtp]]
33 relayhost = localhost33 relayhost = localhost
34 address = ibid@localhost34 address = ibid@localhost
35 accept = ibid@foo.com,35 accept = ibid@foo.com,
36 [[pb]]36 [[pb]]
37 [[reaper]]37 [[reaper]]
38 type = silc38 type = silc
39 server = silc.za.net39 server = silc.za.net
40 channels = ibid,40 channels = ibid,
41 name = Ibid Bot41 name = Ibid Bot
42 [[campfire]]42 [[campfire]]
43 subdomain = ibid43 subdomain = ibid
44 rooms = Room 1,44 rooms = Room 1,
45 username = foo@bar.com45 username = foo@bar.com
46 password = baz46 password = baz
4747
48[plugins]48[plugins]
49 [[ping]]49 [[ping]]
@@ -71,4 +71,4 @@
71 server = localhost71 server = localhost
7272
73[databases]73[databases]
74 ibid = sqlite:///ibid.db74 ibid = sqlite:///ibid.db
7575
=== modified file 'ibid/plugins/admin.py'
--- ibid/plugins/admin.py 2009-02-13 21:19:45 +0000
+++ ibid/plugins/admin.py 2009-03-01 23:01:30 +0000
@@ -5,9 +5,9 @@
55
6help = {}6help = {}
77
8help['plugins'] = 'Lists, loads and unloads plugins.'8help['plugins'] = u'Lists, loads and unloads plugins.'
9class ListPLugins(Processor):9class ListPLugins(Processor):
10 """list plugins"""10 u"""list plugins"""
11 feature = 'plugins'11 feature = 'plugins'
1212
13 @match(r'^lsmod|list\s+plugins$')13 @match(r'^lsmod|list\s+plugins$')
@@ -20,8 +20,9 @@
20 event.addresponse(', '.join(plugins))20 event.addresponse(', '.join(plugins))
21 return event21 return event
2222
23help['core'] = 'Reloads core modules.'23help['core'] = u'Reloads core modules.'
24class ReloadCoreModules(Processor):24class ReloadCoreModules(Processor):
25 u"""reload (reloader|dispatcher|databases|auth)"""
25 feature = 'core'26 feature = 'core'
2627
27 priority = -528 priority = -5
@@ -39,7 +40,7 @@
39 event.addresponse(result and u'%s reloaded' % module or u"Couldn't reload %s" % module)40 event.addresponse(result and u'%s reloaded' % module or u"Couldn't reload %s" % module)
4041
41class LoadModules(Processor):42class LoadModules(Processor):
42 """(load|unload|reload) <plugin|processor>"""43 u"""(load|unload|reload) <plugin|processor>"""
43 feature = 'plugins'44 feature = 'plugins'
4445
45 permission = u'plugins'46 permission = u'plugins'
@@ -59,7 +60,7 @@
5960
60help['die'] = u'Terminates the bot'61help['die'] = u'Terminates the bot'
61class Die(Processor):62class Die(Processor):
62 """die"""63 u"""die"""
63 feature = 'die'64 feature = 'die'
6465
65 permission = u'admin'66 permission = u'admin'
6667
=== modified file 'ibid/plugins/apt.py'
--- ibid/plugins/apt.py 2009-02-16 08:13:23 +0000
+++ ibid/plugins/apt.py 2009-03-01 19:58:06 +0000
@@ -2,67 +2,129 @@
22
3from ibid.plugins import Processor, match3from ibid.plugins import Processor, match
4from ibid.config import Option4from ibid.config import Option
5from ibid.utils import file_in_path, unicode_output
56
6help = {}7help = {}
78
8help['aptitude'] = u'Searches for packages'9help['aptitude'] = u'Searches for packages'
9class Aptitude(Processor):10class Aptitude(Processor):
10 """(apt|aptitude|apt-get) [search] <term>"""11 u"""apt (search|show) <term>"""
11 feature = 'aptitude'12 feature = 'aptitude'
1213
13 aptitude = Option('aptitude', 'Path to aptitude executable', 'aptitude')14 aptitude = Option('aptitude', 'Path to aptitude executable', 'aptitude')
1415
15 @match(r'^(?:apt|aptitude|apt-get)\s+(?:search\s+)(.+)$')16 bad_search_strings = (
17 "?action", "~a", "?automatic", "~A", "?broken", "~b",
18 "?config-files", "~c", "?garbage", "~g", "?installed", "~i",
19 "?new", "~N", "?obsolete", "~o", "?upgradable", "~U",
20 "?user-tag", "?version", "~V"
21 )
22
23 def setup(self):
24 if not file_in_path(self.aptitude):
25 raise Exception("Cannot locate aptitude executeable")
26
27 def _check_terms(self, event, term):
28 "Check for naughty users"
29
30 for word in self.bad_search_strings:
31 if word in term:
32 event.addresponse(u"I can't tell you about my host system. Sorry.")
33 return False
34
35 if term.strip().startswith("-"):
36 event.addresponse(False)
37 return False
38
39 return True
40
41 @match(r'^(?:apt|aptitude|apt-get|apt-cache)\s+search\s+(.+)$')
16 def search(self, event, term):42 def search(self, event, term):
43
44 if not self._check_terms(event, term):
45 return
46
17 apt = Popen([self.aptitude, 'search', '-F', '%p', term], stdout=PIPE, stderr=PIPE)47 apt = Popen([self.aptitude, 'search', '-F', '%p', term], stdout=PIPE, stderr=PIPE)
18 output, error = apt.communicate()48 output, error = apt.communicate()
19 code = apt.wait()49 code = apt.wait()
2050
21 if code == 0 and output:51 if code == 0:
22 if output:52 if output:
23 event.addresponse(u', '.join(line.strip() for line in output.splitlines()))53 output = unicode_output(output.strip())
54 output = [line.strip() for line in output.splitlines()]
55 event.addresponse(u"Found %i packages: %s" % (len(output), u', '.join(output)))
24 else:56 else:
25 event.addresponse(u'No packages found')57 event.addresponse(u'No packages found')
2658 else:
27 @match(r'(?:apt|aptitude|apt-get)\s+(?:show\s+)(.+)$')59 error = unicode_output(error.strip())
28 def show(self, event, package):60 if error.startswith(u"E: "):
29 apt = Popen([self.aptitude, 'show', package], stdout=PIPE, stderr=PIPE)61 error = error[3:]
62 event.addresponse(u"Couldn't search: %s" % error)
63
64 @match(r'(?:apt|aptitude|apt-get)\s+show\s+(.+)$')
65 def show(self, event, term):
66
67 if not self._check_terms(event, term):
68 return
69
70 apt = Popen([self.aptitude, 'show', term], stdout=PIPE, stderr=PIPE)
30 output, error = apt.communicate()71 output, error = apt.communicate()
31 code = apt.wait()72 code = apt.wait()
3273
33 if code == 0 and output:74 if code == 0:
34 print output
35 description = None75 description = None
76 output = unicode_output(output)
36 for line in output.splitlines():77 for line in output.splitlines():
37 if not description:78 if not description:
38 if line.startswith('Description:'):79 if line.startswith(u'Description:'):
39 description = u'%s:' % line.replace('Description:', '', 1).strip()80 description = u'%s:' % line.split(None, 1)[1]
40 elif line.startswith('Provided by:'):81 elif line.startswith(u'Provided by:'):
41 description = u'Virtual package provided by %s' % line.replace('Provided by:', '', 1).strip()82 description = u'Virtual package provided by %s' % line.split(None, 2)[2]
83 elif line != "":
84 description += u' ' + line.strip()
42 else:85 else:
43 description += ' ' + line.strip()86 # More than one package listed
87 break
44 if description:88 if description:
45 event.addresponse(description)89 event.addresponse(description)
46 else:90 else:
47 event.addresponse(u'No such package')91 raise Exception("We couldn't successfully parse aptitude's output")
92 else:
93 error = unicode_output(error.strip())
94 if error.startswith(u"E: "):
95 error = error[3:]
96 event.addresponse(u"Couldn't find package: %s" % error)
48 97
49help['apt-file'] = u'Searches for packages containing the specified file'98help['apt-file'] = u'Searches for packages containing the specified file'
50class AptFile(Processor):99class AptFile(Processor):
51 """apt-file [search] <term>"""100 u"""apt-file [search] <term>"""
52 feature = 'apt-file'101 feature = 'apt-file'
53102
54 aptfile = Option('apt-file', 'Path to apt-file executable', 'apt-file')103 aptfile = Option('apt-file', 'Path to apt-file executable', 'apt-file')
55104
105 def setup(self):
106 if not file_in_path(self.aptfile):
107 raise Exception("Cannot locate apt-file executeable")
108
56 @match(r'^apt-?file\s+(?:search\s+)?(.+)$')109 @match(r'^apt-?file\s+(?:search\s+)?(.+)$')
57 def search(self, event, term):110 def search(self, event, term):
58 apt = Popen([self.aptfile, 'search', term], stdout=PIPE, stderr=PIPE)111 apt = Popen([self.aptfile, 'search', term], stdout=PIPE, stderr=PIPE)
59 output, error = apt.communicate()112 output, error = apt.communicate()
60 code = apt.wait()113 code = apt.wait()
61114
62 if code == 0 and output:115 if code == 0:
63 if output:116 if output:
64 event.addresponse(u', '.join(line.split(':')[0] for line in output.splitlines()))117 output = unicode_output(output.strip())
65 else:118 output = [line.split(u':')[0] for line in output.splitlines()]
66 event.addresponse(u'No packages found')119 event.addresponse(u"Found %i packages: %s" % (len(output), u', '.join(output)))
120 else:
121 event.addresponse(u'No packages found.')
122 else:
123 error = unicode_output(error.strip())
124 if u"The cache directory is empty." in error:
125 event.addresponse(u'Search error: apt-file cache empty.')
126 else:
127 event.addresponse(u'Search error')
128 raise Exception("apt-file: %s" % error)
67129
68# vi: set et sta sw=4 ts=4:130# vi: set et sta sw=4 ts=4:
69131
=== modified file 'ibid/plugins/auth.py'
--- ibid/plugins/auth.py 2009-02-22 15:30:46 +0000
+++ ibid/plugins/auth.py 2009-03-01 23:01:30 +0000
@@ -13,9 +13,9 @@
1313
14actions = {'revoke': 'Revoked', 'grant': 'Granted', 'remove': 'Removed'}14actions = {'revoke': 'Revoked', 'grant': 'Granted', 'remove': 'Removed'}
1515
16help['auth'] = 'Adds and removes authentication credentials and permissions'16help['auth'] = u'Adds and removes authentication credentials and permissions'
17class AddAuth(Processor):17class AddAuth(Processor):
18 """authenticate <account> using <method> [<credential>]"""18 u"""authenticate <account> [on source] using <method> [<credential>]"""
19 feature = 'auth'19 feature = 'auth'
2020
21 @match(r'^authenticate\s+(.+?)(?:\s+on\s+(.+))?\s+using\s+(\S+)\s+(.+)$')21 @match(r'^authenticate\s+(.+?)(?:\s+on\s+(.+))?\s+using\s+(\S+)\s+(.+)$')
@@ -59,7 +59,9 @@
5959
60permission_values = {'no': '-', 'yes': '+', 'auth': ''}60permission_values = {'no': '-', 'yes': '+', 'auth': ''}
61class Permissions(Processor):61class Permissions(Processor):
62 """(grant|revoke|remove) <permission> (to|from|on) <username> [when authed] | list permissions"""62 u"""(grant|revoke) <permission> (to|from|on) <username> [when authed]
63 permissions [for <username>]
64 list permissions"""
63 feature = 'auth'65 feature = 'auth'
6466
65 permission = u'admin'67 permission = u'admin'
@@ -139,7 +141,7 @@
139 event.addresponse(', '.join(permissions))141 event.addresponse(', '.join(permissions))
140142
141class Auth(Processor):143class Auth(Processor):
142 """auth <credential>"""144 u"""auth <credential>"""
143 feature = 'auth'145 feature = 'auth'
144146
145 @match(r'^auth(?:\s+(.+))?$')147 @match(r'^auth(?:\s+(.+))?$')
146148
=== modified file 'ibid/plugins/basic.py'
--- ibid/plugins/basic.py 2009-02-13 21:19:45 +0000
+++ ibid/plugins/basic.py 2009-03-01 23:01:30 +0000
@@ -1,14 +1,13 @@
1from random import choice1from random import choice
2import re2import re
3import logging
43
5from ibid.plugins import Processor, match, handler, authorise4from ibid.plugins import Processor, match, handler, authorise
65
7help = {}6help = {}
87
9help['saydo'] = 'Says or does stuff in a channel.'8help['saydo'] = u'Says or does stuff in a channel.'
10class SayDo(Processor):9class SayDo(Processor):
11 """(say|do) <channel> <text>"""10 u"""(say|do) <channel> <text>"""
12 feature = 'saydo'11 feature = 'saydo'
1312
14 permission = u'saydo'13 permission = u'saydo'
@@ -25,7 +24,7 @@
2524
26help['redirect'] = u'Redirects the response to a command to a different channel.'25help['redirect'] = u'Redirects the response to a command to a different channel.'
27class RedirectCommand(Processor):26class RedirectCommand(Processor):
28 """redirect [to] <channel> [on <source>] <command>"""27 u"""redirect [to] <channel> [on <source>] <command>"""
29 feature = 'redirect'28 feature = 'redirect'
3029
31 priority = -120030 priority = -1200
@@ -57,31 +56,15 @@
57 responses.append(response)56 responses.append(response)
58 event.responses = responses57 event.responses = responses
5958
60choose_re = re.compile(r'(?:\s*,\s*(?:or\s+)?)|(?:\s+or\s+)', re.I)59help['choose'] = u'Choose one of the given options.'
61help['choose'] = 'Choose one of the given options.'
62class Choose(Processor):60class Choose(Processor):
63 """choose <choice> or <choice>..."""61 u"""choose <choice> or <choice>..."""
64 feature = 'choose'62 feature = 'choose'
6563
64 choose_re = re.compile(r'(?:\s*,\s*(?:or\s+)?)|(?:\s+or\s+)', re.I)
65
66 @match(r'^(?:choose|choice|pick)\s+(.+)$')66 @match(r'^(?:choose|choice|pick)\s+(.+)$')
67 def choose(self, event, choices):67 def choose(self, event, choices):
68 event.addresponse(u'I choose %s' % choice(choose_re.split(choices)))68 event.addresponse(u'I choose %s' % choice(self.choose_re.split(choices)))
69
70class UnicodeWarning(Processor):
71
72 priority = 1950
73
74 def setup(self):
75 self.log = logging.getLogger('plugins.unicode')
76
77 def process(self, object):
78 if isinstance(object, dict):
79 for value in object.values():
80 self.process(value)
81 elif isinstance(object, list):
82 for value in object:
83 self.process(value)
84 elif isinstance(object, str):
85 self.log.warning(u"Found a non-unicode string: %s" % object)
8669
87# vi: set et sta sw=4 ts=4:70# vi: set et sta sw=4 ts=4:
8871
=== modified file 'ibid/plugins/buildbot.py'
--- ibid/plugins/buildbot.py 2009-02-23 20:29:44 +0000
+++ ibid/plugins/buildbot.py 2009-03-01 23:01:30 +0000
@@ -9,7 +9,7 @@
9help = {'buildbot': u'Displays buildbot build results and triggers builds.'}9help = {'buildbot': u'Displays buildbot build results and triggers builds.'}
1010
11class BuildBot(Processor, RPC):11class BuildBot(Processor, RPC):
12 """rebuild <branch> [ (revision|r) <number> ]"""12 u"""rebuild <branch> [ (revision|r) <number> ]"""
13 feature = 'buildbot'13 feature = 'buildbot'
1414
15 server = Option('server', 'Buildbot server hostname', 'localhost')15 server = Option('server', 'Buildbot server hostname', 'localhost')
1616
=== modified file 'ibid/plugins/bzr.py'
--- ibid/plugins/bzr.py 2009-02-18 10:41:14 +0000
+++ ibid/plugins/bzr.py 2009-03-01 23:01:30 +0000
@@ -10,7 +10,7 @@
10from ibid.config import Option10from ibid.config import Option
11from ibid.utils import ago11from ibid.utils import ago
1212
13help = {'bzr': 'Retrieves commit logs from a Bazaar repository.'}13help = {'bzr': u'Retrieves commit logs from a Bazaar repository.'}
1414
15class LogFormatter(log.LogFormatter):15class LogFormatter(log.LogFormatter):
1616
@@ -42,7 +42,7 @@
42 self.to_file.write(commit)42 self.to_file.write(commit)
4343
44class Bazaar(Processor, RPC):44class Bazaar(Processor, RPC):
45 """last commit to <repo> | commit <revno> [full]45 u"""(last commit|commit <revno>) [to <repo>] [full]
46 repositories"""46 repositories"""
47 feature = 'bzr'47 feature = 'bzr'
4848
@@ -83,7 +83,7 @@
8383
84 for commit in commits:84 for commit in commits:
85 if commit:85 if commit:
86 event.addresponse(commit.strip())86 event.addresponse(unicode(commit.strip()))
8787
88 def get_commits(self, repository, start, end=None, full=None):88 def get_commits(self, repository, start, end=None, full=None):
89 branch = None89 branch = None
9090
=== modified file 'ibid/plugins/config.py'
--- ibid/plugins/config.py 2009-02-13 21:55:56 +0000
+++ ibid/plugins/config.py 2009-03-01 23:01:30 +0000
@@ -5,12 +5,14 @@
5from ibid.config import FileConfig5from ibid.config import FileConfig
6from ibid.plugins import Processor, match, authorise6from ibid.plugins import Processor, match, authorise
77
8help = {'config': 'Gets and sets configuration settings, and rereads the configuration file.'}8help = {'config': u'Gets and sets configuration settings, and rereads the configuration file.'}
99
10log = logging.getLogger('plugins.config')10log = logging.getLogger('plugins.config')
1111
12class Config(Processor):12class Config(Processor):
13 """reread config | set config <name> <value> | get config <name>"""13 u"""reread config
14 set config <name> to <value>
15 get config <name>"""
14 feature = 'config'16 feature = 'config'
1517
16 priority = -1018 priority = -10
1719
=== modified file 'ibid/plugins/core.py'
--- ibid/plugins/core.py 2009-02-23 20:29:44 +0000
+++ ibid/plugins/core.py 2009-03-01 19:58:06 +0000
@@ -1,13 +1,12 @@
1import re1import re
2from time import time2from time import time
3from random import choice3from random import choice
4import logging
45
5import ibid6import ibid
6from ibid.plugins import Processor, handler7from ibid.plugins import Processor, handler
7from ibid.config import Option, IntOption8from ibid.config import Option, IntOption
89
9help = {}
10
11class Addressed(Processor):10class Addressed(Processor):
1211
13 priority = -150012 priority = -1500
@@ -89,14 +88,20 @@
89class Address(Processor):88class Address(Processor):
9089
91 processed = True90 processed = True
92 acknowledgements = Option('acknowledgements', 'Responses for positive acknowledgements', (u'Okay', u'Sure', u'Done', u'Righto', u'Alrighty', u'Yessir'))91 acknowledgements = Option('acknowledgements', 'Responses for positive acknowledgements',
92 (u'Okay', u'Sure', u'Done', u'Righto', u'Alrighty', u'Yessir'))
93 refusals = Option('refusals', 'Responses for negative acknowledgements',
94 (u'No', u"I won't", u"Shan't", u"I'm sorry, but I can't do that"))
9395
94 @handler96 @handler
95 def address(self, event):97 def address(self, event):
96 addressed = []98 addressed = []
97 for response in event.responses:99 for response in event.responses:
98 if isinstance(response, bool):100 if isinstance(response, bool):
99 response = choice(self.acknowledgements)101 if response:
102 response = choice(self.acknowledgements)
103 else:
104 response = choice(self.refusals)
100 if isinstance(response, basestring) and event.public:105 if isinstance(response, basestring) and event.public:
101 addressed.append('%s: %s' % (event.sender['nick'], response))106 addressed.append('%s: %s' % (event.sender['nick'], response))
102 else:107 else:
@@ -111,9 +116,7 @@
111 def process(self, event):116 def process(self, event):
112 event.time = time()117 event.time = time()
113118
114help['complain'] = 'Responds with a complaint. Used to handle unprocessed messages.'
115class Complain(Processor):119class Complain(Processor):
116 feature = 'complain'
117120
118 priority = 950121 priority = 950
119 complaints = Option('complaints', 'Complaint responses', (u'Huh?', u'Sorry...', u'?', u'Excuse me?', u'*blink*', u'What?'))122 complaints = Option('complaints', 'Complaint responses', (u'Huh?', u'Sorry...', u'?', u'Excuse me?', u'*blink*', u'What?'))
@@ -147,4 +150,20 @@
147 else:150 else:
148 event.processed = True151 event.processed = True
149152
153class UnicodeWarning(Processor):
154 priority = 1950
155
156 def setup(self):
157 self.log = logging.getLogger('plugins.unicode')
158
159 def process(self, object):
160 if isinstance(object, dict):
161 for value in object.values():
162 self.process(value)
163 elif isinstance(object, list):
164 for value in object:
165 self.process(value)
166 elif isinstance(object, str):
167 self.log.warning(u"Found a non-unicode string: %s" % object)
168
150# vi: set et sta sw=4 ts=4:169# vi: set et sta sw=4 ts=4:
151170
=== modified file 'ibid/plugins/crypto.py'
--- ibid/plugins/crypto.py 2009-01-24 12:39:04 +0000
+++ ibid/plugins/crypto.py 2009-03-01 23:01:30 +0000
@@ -6,35 +6,36 @@
66
7help = {}7help = {}
88
9help['hash'] = 'Calculates numerous cryptographic hash functions.'9help['hash'] = u'Calculates numerous cryptographic hash functions.'
10class Hash(Processor):10class Hash(Processor):
11 """(md5|sha1|sha224|sha256|sha384|sha512|crypt) <string> [<salt>]"""11 u"""(md5|sha1|sha224|sha256|sha384|sha512) <string>
12 crypt <string> <salt>"""
12 feature = 'hash'13 feature = 'hash'
1314
14 @match(r'^(md5|sha1|sha224|sha256|sha384|sha512)\s+(.+?)$')15 @match(r'^(md5|sha1|sha224|sha256|sha384|sha512)\s+(.+?)$')
15 def hash(self, event, hash, string):16 def hash(self, event, hash, string):
16 event.addresponse(eval('hashlib.%s' % hash.lower())(string).hexdigest())17 event.addresponse(unicode(eval('hashlib.%s' % hash.lower())(string).hexdigest()))
1718
18 @match(r'^crypt\s+(.+)\s+(\S+)$')19 @match(r'^crypt\s+(.+)\s+(\S+)$')
19 def handle_crypt(self, event, string, salt):20 def handle_crypt(self, event, string, salt):
20 event.addresponse(crypt(string, salt))21 event.addresponse(unicode(crypt(string, salt)))
2122
22help['base64'] = 'Encodes and decodes base 16, 32 and 64.'23help['base64'] = u'Encodes and decodes base 16, 32 and 64.'
23class Base64(Processor):24class Base64(Processor):
24 """b(16|32|64)(encode|decode) <string>"""25 u"""b(16|32|64)(encode|decode) <string>"""
25 feature = 'base64'26 feature = 'base64'
2627
27 @match(r'^b(16|32|64)(enc|dec)(?:ode)?\s+(.+?)$')28 @match(r'^b(16|32|64)(enc|dec)(?:ode)?\s+(.+?)$')
28 def base64(self, event, base, operation, string):29 def base64(self, event, base, operation, string):
29 event.addresponse(eval('base64.b%s%sode' % (base, operation.lower()))(string))30 event.addresponse(unicode(eval('base64.b%s%sode' % (base, operation.lower()))(string)))
3031
31help['rot13'] = 'Transforms a string with ROT13.'32help['rot13'] = u'Transforms a string with ROT13.'
32class Rot13(Processor):33class Rot13(Processor):
33 """rot13 <string>"""34 u"""rot13 <string>"""
34 feature = 'rot13'35 feature = 'rot13'
3536
36 @match(r'^rot13\s+(.+)$')37 @match(r'^rot13\s+(.+)$')
37 def rot13(self, event, string):38 def rot13(self, event, string):
38 event.addresponse(string.encode('rot13'))39 event.addresponse(unicode(string.encode('rot13')))
3940
40# vi: set et sta sw=4 ts=4:41# vi: set et sta sw=4 ts=4:
4142
=== modified file 'ibid/plugins/dict.py'
--- ibid/plugins/dict.py 2009-02-03 18:03:49 +0000
+++ ibid/plugins/dict.py 2009-03-01 23:01:30 +0000
@@ -3,11 +3,12 @@
3from ibid.plugins import Processor, match3from ibid.plugins import Processor, match
4from ibid.config import Option, IntOption4from ibid.config import Option, IntOption
55
6help = {'dict': 'Defines words and checks spellings.'}6help = {'dict': u'Defines words and checks spellings.'}
77
8class Dict(Processor):8class Dict(Processor):
9 """(spell|define) <word> [using (<dictionary>|<stratergy>)]9 u"""(spell|define) <word> [using (<dictionary>|<strategy>)]
10 (dictionaries|strategies)"""10 (dictionaries|strategies)
11 (dictionary|strategy) <name>"""
11 feature = 'dict'12 feature = 'dict'
1213
13 server = Option('server', 'Dictionary server hostname', 'localhost')14 server = Option('server', 'Dictionary server hostname', 'localhost')
@@ -24,15 +25,15 @@
24 event.addresponse(u', '.join([d.getdefstr() for d in definitions]))25 event.addresponse(u', '.join([d.getdefstr() for d in definitions]))
2526
26 @match(r'spell\s+(.+?)(?:\s+using\s+(.+))?$')27 @match(r'spell\s+(.+?)(?:\s+using\s+(.+))?$')
27 def handle_spell(self, event, word, stratergy):28 def handle_spell(self, event, word, strategy):
28 suggestions = self.connection.match('*', stratergy or 'soundex', word)29 suggestions = self.connection.match('*', strategy or 'soundex', word)
29 event.addresponse(u', '.join([d.getword() for d in suggestions]))30 event.addresponse(u', '.join([d.getword() for d in suggestions]))
3031
31 @match(r'^dictionaries$')32 @match(r'^dictionaries$')
32 def handle_dictionaries(self, event):33 def handle_dictionaries(self, event):
33 event.addresponse(u', '.join(self.dictionaries.keys()))34 event.addresponse(u', '.join(self.dictionaries.keys()))
3435
35 @match(r'^strategies$')36 @match(r'^strater?gies$')
36 def handle_strategies(self, event):37 def handle_strategies(self, event):
37 event.addresponse(u', '.join(self.strategies.keys()))38 event.addresponse(u', '.join(self.strategies.keys()))
3839
@@ -43,10 +44,10 @@
43 else:44 else:
44 event.addresponse(u"I don't have that response")45 event.addresponse(u"I don't have that response")
4546
46 @match(r'^stratergy\s+(.+?)$')47 @match(r'^strater?gy\s+(.+?)$')
47 def handle_stratergy(self, event, stratergy):48 def handle_strategy(self, event, strategy):
48 if stratergy in self.strategies:49 if strategy in self.strategies:
49 event.addresponse(unicode(self.strategies[stratergy]))50 event.addresponse(unicode(self.strategies[strategy]))
50 else:51 else:
51 event.addresponse(u"I don't have that response")52 event.addresponse(u"I don't have that response")
5253
5354
=== modified file 'ibid/plugins/eval.py'
--- ibid/plugins/eval.py 2009-02-12 16:49:56 +0000
+++ ibid/plugins/eval.py 2009-03-01 23:01:30 +0000
@@ -10,10 +10,10 @@
1010
11from ibid.plugins import Processor, match, authorise11from ibid.plugins import Processor, match, authorise
1212
13help = {'eval': 'Evaluates Python, Perl and Lua code.'}13help = {'eval': u'Evaluates Python, Perl and Lua code.'}
1414
15class Python(Processor):15class Python(Processor):
16 """py <code>"""16 u"""py <code>"""
17 feature = 'eval'17 feature = 'eval'
1818
19 permission = u'eval'19 permission = u'eval'
@@ -27,13 +27,13 @@
27 exec('import sys', globals)27 exec('import sys', globals)
28 exec('import re', globals)28 exec('import re', globals)
29 exec('import time', globals)29 exec('import time', globals)
30 result = str(eval(code, globals, {}))30 result = unicode(eval(code, globals, {}))
31 except Exception, e:31 except Exception, e:
32 result = str(e)32 result = unicode(e)
33 event.addresponse(result)33 event.addresponse(result)
3434
35class Perl(Processor):35class Perl(Processor):
36 """pl <code>"""36 u"""pl <code>"""
37 feature = 'eval'37 feature = 'eval'
3838
39 permission = u'eval'39 permission = u'eval'
@@ -46,10 +46,10 @@
46 except Exception, e:46 except Exception, e:
47 result = e47 result = e
4848
49 event.addresponse(str(result))49 event.addresponse(unicode(result))
5050
51class Lua(Processor):51class Lua(Processor):
52 """lua <code>"""52 u"""lua <code>"""
53 feature = 'eval'53 feature = 'eval'
5454
55 permission = u'eval'55 permission = u'eval'
@@ -62,4 +62,4 @@
62 except Exception, e:62 except Exception, e:
63 result = e63 result = e
6464
65 event.addresponse(str(result))65 event.addresponse(unicode(result))
6666
=== modified file 'ibid/plugins/factoid.py'
--- ibid/plugins/factoid.py 2009-02-23 20:29:44 +0000
+++ ibid/plugins/factoid.py 2009-03-01 23:01:30 +0000
@@ -12,7 +12,9 @@
12from ibid.plugins.identity import get_identities12from ibid.plugins.identity import get_identities
13from ibid.models import Base13from ibid.models import Base
1414
15help = {'factoids': u'Factoids are arbitrary pieces of information stored by a key.'}15help = {'factoids': u'Factoids are arbitrary pieces of information stored by a key. ' +
16 u'Factoids beginning with a command such as "<action>" or "<reply>" will supress the "name verb value" output. ' +
17 u'Search searches the keys. Scan searches the values.'}
1618
17log = logging.getLogger('plugins.factoid')19log = logging.getLogger('plugins.factoid')
1820
@@ -85,7 +87,7 @@
85 return factoid or query.order_by(func.random()).first()87 return factoid or query.order_by(func.random()).first()
8688
87class Utils(Processor):89class Utils(Processor):
88 """literal <name> [starting at <number>]"""90 u"""literal <name> [starting from <number>]"""
89 feature = 'factoids'91 feature = 'factoids'
9092
91 @match(r'^literal\s+(.+?)(?:\s+start(?:ing)?\s+(?:from\s+)?(\d+))?$')93 @match(r'^literal\s+(.+?)(?:\s+start(?:ing)?\s+(?:from\s+)?(\d+))?$')
@@ -99,7 +101,8 @@
99 session.close()101 session.close()
100102
101class Forget(Processor):103class Forget(Processor):
102 """forget <name>"""104 u"""forget <name>
105 <name> is the same as <other name>"""
103 feature = 'factoids'106 feature = 'factoids'
104107
105 permission = u'factoid'108 permission = u'factoid'
@@ -172,7 +175,7 @@
172 event.addresponse(u"I don't know about %s" % name)175 event.addresponse(u"I don't know about %s" % name)
173176
174class Search(Processor):177class Search(Processor):
175 """(search|scan) for <pattern>"""178 u"""(search|scan) for <pattern> [from <start>]"""
176 feature = 'factoids'179 feature = 'factoids'
177180
178 limit = IntOption('search_limit', u'Maximum number of results to return', 30)181 limit = IntOption('search_limit', u'Maximum number of results to return', 30)
@@ -198,7 +201,7 @@
198 event.addresponse(u"I couldn't find anything with that name")201 event.addresponse(u"I couldn't find anything with that name")
199202
200class Get(Processor, RPC):203class Get(Processor, RPC):
201 """<factoid> [( #<number> | /<pattern>/ )]"""204 u"""<factoid> [( #<number> | /<pattern>/ )]"""
202 feature = 'factoids'205 feature = 'factoids'
203206
204 verbs = verbs207 verbs = verbs
@@ -265,8 +268,7 @@
265 return reply268 return reply
266269
267class Set(Processor):270class Set(Processor):
268 """<name> (<verb>|=<verb>=) <value>271 u"""<name> (<verb>|=<verb>=) [also] <value>"""
269 <name> is the same as <name>"""
270 feature = 'factoids'272 feature = 'factoids'
271273
272 verbs = verbs274 verbs = verbs
273275
=== modified file 'ibid/plugins/feeds.py'
--- ibid/plugins/feeds.py 2009-02-23 20:29:44 +0000
+++ ibid/plugins/feeds.py 2009-03-01 23:01:30 +0000
@@ -44,7 +44,7 @@
44 self.entries = self.feed['entries']44 self.entries = self.feed['entries']
4545
46class Manage(Processor):46class Manage(Processor):
47 """add feed <url> as <name>47 u"""add feed <url> as <name>
48 list feeds48 list feeds
49 remove <name> feed"""49 remove <name> feed"""
50 feature = 'feeds'50 feature = 'feeds'
@@ -72,7 +72,7 @@
7272
73 session.close()73 session.close()
7474
75 @match(r'^list\s+feeds$')75 @match(r'^(?:list\s+)?feeds$')
76 def list(self, event):76 def list(self, event):
77 session = ibid.databases.ibid()77 session = ibid.databases.ibid()
78 feeds = session.query(Feed).all()78 feeds = session.query(Feed).all()
@@ -98,7 +98,7 @@
98 session.close()98 session.close()
9999
100class Retrieve(Processor):100class Retrieve(Processor):
101 """(latest|last) [ <count> ] articles from <name> [ starting [(at|from)] <number> ]101 u"""latest [ <count> ] articles from <name> [ starting at <number> ]
102 article ( <number> | /<pattern>/ ) from <name>"""102 article ( <number> | /<pattern>/ ) from <name>"""
103 feature = 'feeds'103 feature = 'feeds'
104104
105105
=== modified file 'ibid/plugins/google.py'
--- ibid/plugins/google.py 2009-02-12 20:58:39 +0000
+++ ibid/plugins/google.py 2009-03-01 23:01:30 +0000
@@ -5,12 +5,12 @@
5from ibid.plugins import Processor, match5from ibid.plugins import Processor, match
6from ibid.config import Option6from ibid.config import Option
77
8help = {'google': 'Retrieves results from Google and Google Calculator.'}8help = {'google': u'Retrieves results from Google and Google Calculator.'}
99
10user_agent = 'Mozilla/5.0'10user_agent = 'Mozilla/5.0'
1111
12class Search(Processor):12class Search(Processor):
13 """google [for] <term>"""13 u"""google [for] <term>"""
14 feature = 'google'14 feature = 'google'
1515
16 user_agent = Option('user_agent', 'HTTP user agent to present to Google', user_agent)16 user_agent = Option('user_agent', 'HTTP user agent to present to Google', user_agent)
@@ -21,7 +21,6 @@
21 if country:21 if country:
22 url = url + '&meta=cr%%3Dcountry%s' % country.upper()22 url = url + '&meta=cr%%3Dcountry%s' % country.upper()
2323
24 print self.user_agent
25 f = urlopen(Request(url, headers={'user-agent': self.user_agent}))24 f = urlopen(Request(url, headers={'user-agent': self.user_agent}))
26 soup = BeautifulSoup(f.read())25 soup = BeautifulSoup(f.read())
27 f.close()26 f.close()
@@ -31,15 +30,15 @@
31 for item in items:30 for item in items:
32 try:31 try:
33 url = item.a['href']32 url = item.a['href']
34 title = ''.join([e.string for e in item.a.contents])33 title = u''.join([e.string for e in item.a.contents])
35 results.append('"%s" %s' % (title, url))34 results.append(u'"%s" %s' % (title, url))
36 except Exception:35 except Exception:
37 pass36 pass
3837
39 event.addresponse(', '.join(results))38 event.addresponse(u', '.join(results))
4039
41class Calc(Processor):40class Calc(Processor):
42 """gcalc <expression>"""41 u"""gcalc <expression>"""
43 feature = 'google'42 feature = 'google'
4443
45 user_agent = Option('user_agent', 'HTTP user agent to present to Google', user_agent)44 user_agent = Option('user_agent', 'HTTP user agent to present to Google', user_agent)
@@ -57,7 +56,7 @@
57 event.addresponse(font.b.string)56 event.addresponse(font.b.string)
5857
59class Define(Processor):58class Define(Processor):
60 """gdefine <term>"""59 u"""gdefine <term>"""
61 feature = 'google'60 feature = 'google'
6261
63 user_agent = Option('user_agent', 'HTTP user agent to present to Google', user_agent)62 user_agent = Option('user_agent', 'HTTP user agent to present to Google', user_agent)
@@ -70,7 +69,7 @@
7069
71 definitions = []70 definitions = []
72 for li in soup.findAll('li'):71 for li in soup.findAll('li'):
73 definitions.append('"%s"' % li.contents[0])72 definitions.append('"%s"' % li.contents[0].strip())
7473
75 if definitions:74 if definitions:
76 event.addresponse(', '.join(definitions))75 event.addresponse(', '.join(definitions))
@@ -78,7 +77,7 @@
78 event.addresponse(u"Are you making up words again?")77 event.addresponse(u"Are you making up words again?")
7978
80class Compare(Processor):79class Compare(Processor):
81 """google cmp [for] <term> and <term>"""80 u"""google cmp [for] <term> and <term>"""
82 feature = 'google'81 feature = 'google'
8382
84 user_agent = Option('user_agent', 'HTTP user agent to present to Google', user_agent)83 user_agent = Option('user_agent', 'HTTP user agent to present to Google', user_agent)
8584
=== modified file 'ibid/plugins/help.py'
--- ibid/plugins/help.py 2009-02-21 12:31:38 +0000
+++ ibid/plugins/help.py 2009-03-01 23:01:30 +0000
@@ -3,10 +3,12 @@
3import ibid3import ibid
4from ibid.plugins import Processor, match4from ibid.plugins import Processor, match
55
6help = {'help': 'Provides help and usage information about plugins.'}6help = {'help': u'Provides help and usage information about plugins.'}
77
8class Help(Processor):8class Help(Processor):
9 """(help|usage) [<feature>]"""9 u"""features
10 help [<feature>]
11 usage <feature>"""
10 feature = 'help'12 feature = 'help'
1113
12 @match(r'^help$')14 @match(r'^help$')
1315
=== modified file 'ibid/plugins/http.py'
--- ibid/plugins/http.py 2009-02-03 18:03:49 +0000
+++ ibid/plugins/http.py 2009-03-01 23:01:30 +0000
@@ -8,15 +8,18 @@
88
9title = re.compile(r'<title>(.*)<\/title>', re.I+re.S)9title = re.compile(r'<title>(.*)<\/title>', re.I+re.S)
1010
11help['get'] = 'Retrieves a URL and returns the HTTP status and optionally the HTML title.'11help['get'] = u'Retrieves a URL and returns the HTTP status and optionally the HTML title.'
12class HTTP(Processor):12class HTTP(Processor):
13 """(get|head) <url>"""13 u"""(get|head) <url>"""
14 feature = 'get'14 feature = 'get'
1515
16 max_size = IntOption('max_size', 'Only request this many bytes', 500)16 max_size = IntOption('max_size', 'Only request this many bytes', 500)
1717
18 @match(r'^(get|head)\s+(.+)$')18 @match(r'^(get|head)\s+(.+)$')
19 def handler(self, event, action, url):19 def handler(self, event, action, url):
20 if not url.lower().startswith("http://") and not url.lower().startswith("https://"):
21 url = "http://" + url
22
20 http = Http()23 http = Http()
21 headers={}24 headers={}
22 if action.lower() == 'get':25 if action.lower() == 'get':
2326
=== modified file 'ibid/plugins/identity.py'
--- ibid/plugins/identity.py 2009-02-23 20:43:40 +0000
+++ ibid/plugins/identity.py 2009-03-01 23:01:30 +0000
@@ -16,7 +16,7 @@
1616
17help['accounts'] = u'An account represents a person. An account has one or more identities, which is a user on a specific source.'17help['accounts'] = u'An account represents a person. An account has one or more identities, which is a user on a specific source.'
18class Accounts(Processor):18class Accounts(Processor):
19 """create account <name>"""19 u"""create account <name>"""
20 feature = 'accounts'20 feature = 'accounts'
2121
22 @match(r'^create\s+account\s+(.+)$')22 @match(r'^create\s+account\s+(.+)$')
@@ -56,7 +56,7 @@
56chars = string.letters + string.digits56chars = string.letters + string.digits
5757
58class Identities(Processor):58class Identities(Processor):
59 """(I|<username>) (am|is) <identity> on <source>59 u"""(I am|<username> is) <identity> on <source>
60 remove identity <identity> on <source> [from <username>]"""60 remove identity <identity> on <source> [from <username>]"""
61 feature = 'accounts'61 feature = 'accounts'
62 priority = -1062 priority = -10
@@ -179,9 +179,8 @@
179179
180 session.close()180 session.close()
181181
182help['attributes'] = 'Adds and removes attributes attached to an account'
183class Attributes(Processor):182class Attributes(Processor):
184 """set (my|<account>) <name> to <value>"""183 u"""set (my|<account>) <name> to <value>"""
185 feature = 'accounts'184 feature = 'accounts'
186185
187 @match(r"^set\s+(my|.+?)(?:\'s)?\s+(.+)\s+to\s+(.+)$")186 @match(r"^set\s+(my|.+?)(?:\'s)?\s+(.+)\s+to\s+(.+)$")
@@ -213,6 +212,8 @@
213 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'])212 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'])
214213
215class Describe(Processor):214class Describe(Processor):
215 u"""who (am I|is <username>)"""
216 feature = "accounts"
216217
217 @match(r'^who\s+(?:is|am)\s+(I|.+?)$')218 @match(r'^who\s+(?:is|am)\s+(I|.+?)$')
218 def describe(self, event, username):219 def describe(self, event, username):
219220
=== modified file 'ibid/plugins/imdb.py'
--- ibid/plugins/imdb.py 2009-02-23 14:59:37 +0000
+++ ibid/plugins/imdb.py 2009-03-01 23:01:30 +0000
@@ -6,14 +6,17 @@
6from .. imdb import IMDb, IMDbDataAccessError, IMDbError6from .. imdb import IMDb, IMDbDataAccessError, IMDbError
77
8from ibid.plugins import Processor, match8from ibid.plugins import Processor, match
9from ibid.config import Option, IntOption9from ibid.config import Option, BoolOption
1010
11help = {'imdb': 'Looks up movies on IMDB.com.'}11help = {'imdb': u'Looks up movies on IMDB.com.'}
1212
13class IMDB(Processor):13class IMDB(Processor):
14 "imdb [search] [character|company|episode|movie|person] <terms> [result <index>]"14 u"imdb [search] [character|company|episode|movie|person] <terms> [#<index>]"
15 feature = 'imdb'15 feature = 'imdb'
1616
17 access_system = Option("accesssystem", "Method of querying IMDB", "http")
18 adult_search = BoolOption("adultsearch", "Include adult films in search results", True)
19
17 name_keys = {20 name_keys = {
18 "character": "long imdb name",21 "character": "long imdb name",
19 "company": "long imdb name",22 "company": "long imdb name",
@@ -23,10 +26,9 @@
23 }26 }
2427
25 def setup(self):28 def setup(self):
26 # adultSearch = 1 is the default, but we expose this parameter for bot-owners to tweak.29 self.imdb = IMDb(accessSystem=self.access_system, adultSearch=int(self.adult_search))
27 self.imdb = IMDb(accessSystem='http', adultSearch=1)
2830
29 @match(r'^imdb(?:\s+search)?(?:\s+(character|company|episode|movie|person))?\s+(.+?)(?:\s+result\s+(\d+))?$')31 @match(r'^imdb(?:\s+search)?(?:\s+(character|company|episode|movie|person))?\s+(.+?)(?:\s+#(\d+))?$')
30 def search(self, event, search_type, terms, index):32 def search(self, event, search_type, terms, index):
31 if search_type is None:33 if search_type is None:
32 search_type = "movie"34 search_type = "movie"
@@ -60,7 +62,7 @@
60 return62 return
6163
62 if len(results) == 0:64 if len(results) == 0:
63 event.addresponse(u"Sorry, couldn't find that. You sure you know how to spell?")65 event.addresponse(u"Sorry, couldn't find that.")
64 else:66 else:
65 results = [x[self.name_keys[search_type]] for x in results]67 results = [x[self.name_keys[search_type]] for x in results]
66 results = enumerate(results)68 results = enumerate(results)
@@ -99,7 +101,7 @@
99 desc += u" Starring: %s." % (u", ".join(x["name"] for x in episode["cast"][:3]))101 desc += u" Starring: %s." % (u", ".join(x["name"] for x in episode["cast"][:3]))
100 if episode.has_key("rating"):102 if episode.has_key("rating"):
101 desc += u" Rated: %.1f " % episode["rating"]103 desc += u" Rated: %.1f " % episode["rating"]
102 desc += u", ".join(movie.get("genres", ()))104 desc += u", ".join(episode.get("genres", ()))
103 desc += u" Plot: %s" % episode.get("plot outline", u"Unknown")105 desc += u" Plot: %s" % episode.get("plot outline", u"Unknown")
104 return desc106 return desc
105107
@@ -116,12 +118,7 @@
116 return desc118 return desc
117119
118 def display_person(self, person):120 def display_person(self, person):
119 # Quite a few fields are normally repeated in the bio, so we won't bother including them.121 desc = u"%s: %s. %s." % (person.personID, person["name"],
120 #desc = u"%s: %s, Born " % (person.personID, person["name"])
121 #if person["birth name"] != person["name"]:
122 # desc += u"%s " % person["birth name"]
123 #desc += u"%s. %s" % (person["birth date"], u" ".join(person["mini biography"]))
124 return u"%s: %s. %s. Bio: %s" % (person.personID, person["name"],
125 u", ".join(role.title() for role in (122 u", ".join(role.title() for role in (
126 u"actor", u"animation department", u"art department",123 u"actor", u"animation department", u"art department",
127 u"art director", u"assistant director", u"camera department",124 u"art director", u"assistant director", u"camera department",
@@ -132,6 +129,12 @@
132 u"production designer", u"set decorator", u"sound department",129 u"production designer", u"set decorator", u"sound department",
133 u"speccial effects department", u"stunts", u"transport department",130 u"speccial effects department", u"stunts", u"transport department",
134 u"visual effects department", u"writer", u"miscellaneous crew"131 u"visual effects department", u"writer", u"miscellaneous crew"
135 ) if person.has_key(role)), u" ".join(person["mini biography"]))132 ) if person.has_key(role)))
133 if person.has_key("mini biography"):
134 desc += u" " + u" ".join(person["mini biography"])
135 else:
136 if person.has_key("birth name") or person.has_key("birth date"):
137 desc += u" Born %s." % u", ".join(person[attr] for attr in ("birth name", "birth date") if person.has_key(attr))
138 return desc
136139
137# vi: set et sta sw=4 ts=4:140# vi: set et sta sw=4 ts=4:
138141
=== modified file 'ibid/plugins/info.py'
--- ibid/plugins/info.py 2009-02-23 20:29:44 +0000
+++ ibid/plugins/info.py 2009-03-01 19:58:06 +0000
@@ -1,15 +1,17 @@
1from subprocess import Popen, PIPE1from subprocess import Popen, PIPE
2import os
23
3from nickometer import nickometer4from nickometer import nickometer
45
5from ibid.plugins import Processor, match, RPC6from ibid.plugins import Processor, match, RPC
6from ibid.config import Option7from ibid.config import Option
8from ibid.utils import file_in_path, unicode_output
79
8help = {}10help = {}
911
10help['fortune'] = u'Returns a random fortune.'12help['fortune'] = u'Returns a random fortune.'
11class Fortune(Processor, RPC):13class Fortune(Processor, RPC):
12 """fortune"""14 u"""fortune"""
13 feature = 'fortune'15 feature = 'fortune'
1416
15 fortune = Option('fortune', 'Path of the fortune executable', 'fortune')17 fortune = Option('fortune', 'Path of the fortune executable', 'fortune')
@@ -18,6 +20,10 @@
18 super(Fortune, self).__init__(name)20 super(Fortune, self).__init__(name)
19 RPC.__init__(self)21 RPC.__init__(self)
2022
23 def setup(self):
24 if not file_in_path(self.fortune):
25 raise Exception("Cannot locate fortune executeable")
26
21 @match(r'^fortune$')27 @match(r'^fortune$')
22 def handler(self, event):28 def handler(self, event):
23 event.addresponse(self.remote_fortune() or u"Couldn't execute fortune")29 event.addresponse(self.remote_fortune() or u"Couldn't execute fortune")
@@ -27,14 +33,16 @@
27 output, error = fortune.communicate()33 output, error = fortune.communicate()
28 code = fortune.wait()34 code = fortune.wait()
2935
36 output = unicode_output(output.strip())
37
30 if code == 0:38 if code == 0:
31 return output.strip()39 return output
32 else:40 else:
33 return None41 return None
3442
35help['nickometer'] = u'Calculates how lame a nick is.'43help['nickometer'] = u'Calculates how lame a nick is.'
36class Nickometer(Processor):44class Nickometer(Processor):
37 """nickometer [<nick>] [with reasons]"""45 u"""nickometer [<nick>] [with reasons]"""
38 feature = 'nickometer'46 feature = 'nickometer'
39 47
40 @match(r'^(?:nick|lame)-?o-?meter(?:(?:\s+for)?\s+(.+?))?(\s+with\s+reasons)?$')48 @match(r'^(?:nick|lame)-?o-?meter(?:(?:\s+for)?\s+(.+?))?(\s+with\s+reasons)?$')
@@ -47,30 +55,42 @@
4755
48help['man'] = u'Retrieves information from manpages.'56help['man'] = u'Retrieves information from manpages.'
49class Man(Processor):57class Man(Processor):
50 """man [<section>] <page>"""58 u"""man [<section>] <page>"""
51 feature = 'man'59 feature = 'man'
5260
53 man = Option('man', 'Path of the man executable', 'man')61 man = Option('man', 'Path of the man executable', 'man')
5462
63 def setup(self):
64 if not file_in_path(self.man):
65 raise Exception("Cannot locate man executeable")
66
55 @match(r'^man\s+(?:(\d)\s+)?(\S+)$')67 @match(r'^man\s+(?:(\d)\s+)?(\S+)$')
56 def handle_man(self, event, section, page):68 def handle_man(self, event, section, page):
57 command = [self.man, page]69 command = [self.man, page]
58 if section:70 if section:
59 command.insert(1, section)71 command.insert(1, section)
60 man = Popen(command, stdout=PIPE, stderr=PIPE)72
73 if page.strip().startswith("-"):
74 event.addresponse(False)
75 return
76
77 env = os.environ.copy()
78 env["COLUMNS"] = "500"
79
80 man = Popen(command, stdout=PIPE, stderr=PIPE, env=env)
61 output, error = man.communicate()81 output, error = man.communicate()
62 code = man.wait()82 code = man.wait()
6383
64 if code != 0:84 if code != 0:
65 event.addresponse(u'Manpage not found')85 event.addresponse(u'Manpage not found')
66 else:86 else:
67 lines = [unicode(line, 'utf-8', errors='replace') for line in output.splitlines()]87 output = unicode_output(output.strip(), errors="replace")
68 index = lines.index('NAME')88 output = output.splitlines()
69 if index:89 index = output.index('NAME')
70 event.addresponse(lines[index+1].strip())90 if index:
71 index = lines.index('SYNOPSIS')91 event.addresponse(output[index+1].strip())
72 if index:92 index = output.index('SYNOPSIS')
73 event.addresponse(lines[index+1].strip())93 if index:
74 94 event.addresponse(output[index+1].strip())
7595
76# vi: set et sta sw=4 ts=4:96# vi: set et sta sw=4 ts=4:
7797
=== modified file 'ibid/plugins/irc.py'
--- ibid/plugins/irc.py 2009-02-12 19:59:13 +0000
+++ ibid/plugins/irc.py 2009-03-01 23:01:30 +0000
@@ -3,10 +3,10 @@
3import ibid3import ibid
4from ibid.plugins import Processor, match, authorise4from ibid.plugins import Processor, match, authorise
55
6help = {"irc": "Provides commands for joining/parting channels on IRC and Jabber, and changing the bot's nick"}6help = {"irc": u"Provides commands for joining/parting channels on IRC and Jabber, and changing the bot's nick"}
77
8class Actions(Processor):8class Actions(Processor):
9 """(join|part|leave) [<channel> [on <source>]]9 u"""(join|part|leave) [<channel> [on <source>]]
10 change nick to <nick> [on <source>]"""10 change nick to <nick> [on <source>]"""
11 feature = 'irc'11 feature = 'irc'
1212
@@ -24,6 +24,10 @@
24 return24 return
25 channel = event.channel25 channel = event.channel
2626
27 if source.lower() not in ibid.sources:
28 event.addresponse(u"I don't have a source called %s" % source.lower())
29 return
30
27 source = ibid.sources[source.lower()]31 source = ibid.sources[source.lower()]
2832
29 if not hasattr(source, 'join'):33 if not hasattr(source, 'join'):
@@ -43,6 +47,11 @@
4347
44 if not source:48 if not source:
45 source = event.source49 source = event.source
50
51 if source.lower() not in ibid.sources:
52 event.addresponse(u"I don't have a source called %s" % source.lower())
53 return
54
46 source = ibid.sources[source.lower()]55 source = ibid.sources[source.lower()]
4756
48 if not hasattr(source, 'change_nick'):57 if not hasattr(source, 'change_nick'):
4958
=== modified file 'ibid/plugins/karma.py'
--- ibid/plugins/karma.py 2009-02-23 20:29:44 +0000
+++ ibid/plugins/karma.py 2009-03-01 23:01:30 +0000
@@ -28,9 +28,12 @@
28 self.value = 028 self.value = 0
2929
30class Set(Processor):30class Set(Processor):
31 """<subject> (++|--|==|ftw|ftl) [[reason]]"""31 u"""<subject> (++|--|==|ftw|ftl) [[reason]]"""
32 feature = 'karma'32 feature = 'karma'
3333
34 # Clashes with morse
35 priority = 10
36
34 permission = u'karma'37 permission = u'karma'
3538
36 increase = Option('increase', 'Suffixes which indicate increased karma', ('++', 'ftw'))39 increase = Option('increase', 'Suffixes which indicate increased karma', ('++', 'ftw'))
@@ -85,7 +88,7 @@
85 event.processed = True88 event.processed = True
8689
87class Get(Processor):90class Get(Processor):
88 """karma for <subject>91 u"""karma for <subject>
89 [reverse] karmaladder"""92 [reverse] karmaladder"""
90 feature = 'karma'93 feature = 'karma'
9194
9295
=== modified file 'ibid/plugins/lookup.py'
--- ibid/plugins/lookup.py 2009-02-22 10:37:32 +0000
+++ ibid/plugins/lookup.py 2009-03-01 23:01:30 +0000
@@ -14,7 +14,12 @@
1414
15help = {}15help = {}
1616
17help["lookup"] = u"Lookup things on popular sites."
18
17class Bash(Processor):19class Bash(Processor):
20 u"bash[.org] (random|<number>)"
21
22 feature = "lookup"
1823
19 @match(r'^bash(?:\.org)?\s+(random|\d+)$')24 @match(r'^bash(?:\.org)?\s+(random|\d+)$')
20 def bash(self, event, quote):25 def bash(self, event, quote):
@@ -22,15 +27,23 @@
22 soup = BeautifulSoup(f.read(), convertEntities=BeautifulSoup.HTML_ENTITIES)27 soup = BeautifulSoup(f.read(), convertEntities=BeautifulSoup.HTML_ENTITIES)
23 f.close()28 f.close()
2429
30 if quote.lower() == "random":
31 number = u"".join(soup.find('p', attrs={'class': 'quote'}).find('b').contents)
32 event.addresponse(u"%s:" % number)
33
25 quote = soup.find('p', attrs={'class': 'qt'})34 quote = soup.find('p', attrs={'class': 'qt'})
26 if not quote:35 if not quote:
27 event.addresponse(u"There's no such quote, but if you keep talking like that maybe there will be.")36 event.addresponse(u"There's no such quote, but if you keep talking like that maybe there will be.")
28 else:37 else:
29 for line in quote.contents:38 for line in quote.contents:
30 if str(line) != '<br />':39 if str(line) != '<br />':
31 event.addresponse(str(line).strip())40 event.addresponse(unicode(line).strip())
3241
33class LastFm(Processor):42class LastFm(Processor):
43 u"last.fm for <username>"
44
45 feature = "lookup"
46
34 @match(r'^last\.?fm\s+for\s+(\S+?)\s*$')47 @match(r'^last\.?fm\s+for\s+(\S+?)\s*$')
35 def listsongs(self, event, username):48 def listsongs(self, event, username):
36 songs = feedparser.parse("http://ws.audioscrobbler.com/1.0/user/%s/recenttracks.rss?%s" % (username, time()))49 songs = feedparser.parse("http://ws.audioscrobbler.com/1.0/user/%s/recenttracks.rss?%s" % (username, time()))
@@ -41,7 +54,8 @@
4154
42help['lotto'] = u"Gets the latest lotto results from the South African National Lottery"55help['lotto'] = u"Gets the latest lotto results from the South African National Lottery"
43class Lotto(Processor):56class Lotto(Processor):
44 """lotto"""57 u"""lotto"""
58
45 feature = 'lotto'59 feature = 'lotto'
46 60
47 errors = {61 errors = {
@@ -51,7 +65,7 @@
51 65
52 za_url = 'http://www.nationallottery.co.za/'66 za_url = 'http://www.nationallottery.co.za/'
53 za_re = re.compile(r'images/balls/ball_(\d+).gif')67 za_re = re.compile(r'images/balls/ball_(\d+).gif')
54 za_text = 'Latest lotto results for South Africa, Lotto: '68 za_text = u'Latest lotto results for South Africa, Lotto: '
55 69
56 @match(r'lotto(\s+for\s+south\s+africa)?')70 @match(r'lotto(\s+for\s+south\s+africa)?')
57 def za(self, event, za):71 def za(self, event, za):
@@ -69,15 +83,18 @@
69 83
70 if len(balls) != 14:84 if len(balls) != 14:
71 return event.addresponse(self.errors['balls'] % \85 return event.addresponse(self.errors['balls'] % \
72 (14, len(balls), ", ".join(balls)))86 (14, len(balls), u", ".join(balls)))
73 87
74 r += " ".join(balls[:6])88 r += u" ".join(balls[:6])
75 r += " (Bonus: %s), Lotto Plus: " % (balls[6], )89 r += u" (Bonus: %s), Lotto Plus: " % (balls[6], )
76 r += " ".join(balls[7:13])90 r += u" ".join(balls[7:13])
77 r += " (Bonus: %s)" % (balls[13], )91 r += u" (Bonus: %s)" % (balls[13], )
78 event.addresponse(r)92 event.addresponse(r)
7993
80class FMyLife(Processor):94class FMyLife(Processor):
95 u"""fml (<number>|random)"""
96
97 feature = "lookup"
8198
82 def remote_get(self, id):99 def remote_get(self, id):
83 f = urlopen('http://www.fmylife.com/' + str(id))100 f = urlopen('http://www.fmylife.com/' + str(id))
@@ -87,11 +104,17 @@
87 quote = soup.find('div', id='wrapper').div.p104 quote = soup.find('div', id='wrapper').div.p
88 return quote and u'"%s"' % (quote.contents[0],) or None105 return quote and u'"%s"' % (quote.contents[0],) or None
89106
90 @match(r'^(?:fml\s+|http://www\.fmylife\.com/\S+/)(\d+)$')107 @match(r'^(?:fml\s+|http://www\.fmylife\.com/\S+/)(\d+|random)$')
91 def fml(self, event, id):108 def fml(self, event, id):
92 event.addresponse(self.remote_get(int(id)) or u"No such quote")109 event.addresponse(self.remote_get(id) or u"No such quote")
110
111help["microblog"] = u"Looks up messages on microblogging services like twitter and identica."
93112
94class Twitter(Processor):113class Twitter(Processor):
114 u"""latest (tweet|identica) from <name>
115 (tweet|identica) <number>"""
116
117 feature = "microblog"
95118
96 default = { 'twitter': 'http://twitter.com/',119 default = { 'twitter': 'http://twitter.com/',
97 'tweet': 'http://twitter.com/',120 'tweet': 'http://twitter.com/',
@@ -135,6 +158,10 @@
135 event.addresponse(self.remote_update('identi.ca', int(id)))158 event.addresponse(self.remote_update('identi.ca', int(id)))
136159
137class Currency(Processor):160class Currency(Processor):
161 u"""exchange <amount> <currency> for <currency>
162 currencies for <country>"""
163
164 feature = "lookup"
138165
139 headers = {'User-Agent': 'Mozilla/5.0', 'Referer': 'http://www.xe.com/'}166 headers = {'User-Agent': 'Mozilla/5.0', 'Referer': 'http://www.xe.com/'}
140 currencies = []167 currencies = []
@@ -179,6 +206,10 @@
179 event.addresponse(u'No currencies found')206 event.addresponse(u'No currencies found')
180207
181class Weather(Processor):208class Weather(Processor):
209 u"""weather in <city>
210 forecast for <city>"""
211
212 feature = "lookup"
182213
183 defaults = { 'ct': 'Cape Town, South Africa',214 defaults = { 'ct': 'Cape Town, South Africa',
184 'jhb': 'Johannesburg, South Africa',215 'jhb': 'Johannesburg, South Africa',
185216
=== modified file 'ibid/plugins/math.py'
--- ibid/plugins/math.py 2009-02-26 10:05:53 +0000
+++ ibid/plugins/math.py 2009-03-01 19:58:06 +0000
@@ -2,17 +2,22 @@
22
3from ibid.plugins import Processor, match3from ibid.plugins import Processor, match
4from ibid.config import Option4from ibid.config import Option
5from ibid.utils import file_in_path, unicode_output
56
6help = {}7help = {}
78
8help['bc'] = u'Calculate mathematical expressions using bc'9help['bc'] = u'Calculate mathematical expressions using bc'
9class BC(Processor):10class BC(Processor):
10 """bc <expression>"""11 u"""bc <expression>"""
1112
12 feature = 'bc'13 feature = 'bc'
1314
14 bc = Option('bc', 'Path to bc executable', 'bc')15 bc = Option('bc', 'Path to bc executable', 'bc')
1516
17 def setup(self):
18 if not file_in_path(self.bc):
19 raise Exception("Cannot locate bc executeable")
20
16 @match(r'^bc\s+(.+)$')21 @match(r'^bc\s+(.+)$')
17 def calculate(self, event, expression):22 def calculate(self, event, expression):
18 bc = Popen([self.bc, '-l'], stdin=PIPE, stdout=PIPE, stderr=PIPE)23 bc = Popen([self.bc, '-l'], stdin=PIPE, stdout=PIPE, stderr=PIPE)
@@ -20,11 +25,23 @@
20 code = bc.wait()25 code = bc.wait()
2126
22 if code == 0:27 if code == 0:
23 event.addresponse(output.strip())28 if output:
29 output = unicode_output(output.strip())
30 output = output.replace('\\\n', '')
31 event.addresponse(output)
32 else:
33 error = unicode_output(error.strip())
34 error = error.split(":", 1)[1].strip()
35 error = error[0].lower() + error[1:]
36 event.addresponse(u"You can't %s" % error)
37 else:
38 event.addresponse(u"Error running bc")
39 error = unicode_output(error.strip())
40 raise Exception("BC Error: %s" % error)
2441
25help['calc'] = 'Returns the anwser to mathematical expressions'42help['calc'] = u'Returns the anwser to mathematical expressions'
26class Calc(Processor):43class Calc(Processor):
27 """[calc] <expression>"""44 u"""[calc] <expression>"""
28 feature = 'calc'45 feature = 'calc'
2946
30 priority = 50047 priority = 500
3148
=== modified file 'ibid/plugins/memo.py'
--- ibid/plugins/memo.py 2009-02-23 20:29:44 +0000
+++ ibid/plugins/memo.py 2009-03-01 23:01:30 +0000
@@ -13,7 +13,7 @@
13from ibid.models import Base, Identity, Account13from ibid.models import Base, Identity, Account
14from ibid.utils import ago14from ibid.utils import ago
1515
16help = {'memo': 'Keeps messages for people.'}16help = {'memo': u'Keeps messages for people.'}
1717
18memo_cache = {}18memo_cache = {}
19log = logging.getLogger('plugins.memo')19log = logging.getLogger('plugins.memo')
@@ -40,7 +40,7 @@
40Memo.recipient = relation(Identity, primaryjoin=Memo.to_id==Identity.id)40Memo.recipient = relation(Identity, primaryjoin=Memo.to_id==Identity.id)
4141
42class Tell(Processor):42class Tell(Processor):
43 """(tell|pm|privmsg|msg) <person> <message>"""43 u"""(tell|pm|privmsg|msg) <person> <message>"""
44 feature = 'memo'44 feature = 'memo'
4545
46 permission = u'sendmemo'46 permission = u'sendmemo'
@@ -135,7 +135,7 @@
135 session.close()135 session.close()
136136
137class Messages(Processor):137class Messages(Processor):
138 """my messages138 u"""my messages
139 message <number>"""139 message <number>"""
140 feature = 'memo'140 feature = 'memo'
141141
142142
=== modified file 'ibid/plugins/misc.py'
--- ibid/plugins/misc.py 2009-02-23 20:29:44 +0000
+++ ibid/plugins/misc.py 2009-03-01 23:01:30 +0000
@@ -9,7 +9,7 @@
99
10help['coffee'] = u"Times coffee brewing and reserves cups for people"10help['coffee'] = u"Times coffee brewing and reserves cups for people"
11class Coffee(Processor):11class Coffee(Processor):
12 """coffee (on|please)"""12 u"""coffee (on|please)"""
13 feature = 'coffee'13 feature = 'coffee'
1414
15 pot = None15 pot = None
@@ -48,7 +48,7 @@
4848
49help['version'] = u"Show the Ibid version currently running"49help['version'] = u"Show the Ibid version currently running"
50class Version(Processor):50class Version(Processor):
51 """version"""51 u"""version"""
52 feature = 'version'52 feature = 'version'
5353
54 @match(r'^version$')54 @match(r'^version$')
@@ -57,7 +57,7 @@
5757
58help['dvorak'] = u"Makes text typed on a QWERTY keyboard as if it was Dvorak work, and vice-versa"58help['dvorak'] = u"Makes text typed on a QWERTY keyboard as if it was Dvorak work, and vice-versa"
59class Dvorak(Processor):59class Dvorak(Processor):
60 """(aoeu|asdf) <text>"""60 u"""(aoeu|asdf) <text>"""
61 feature = 'dvorak'61 feature = 'dvorak'
62 62
63 # List of characters on each keyboard layout63 # List of characters on each keyboard layout
@@ -69,11 +69,11 @@
69 # Typed by a Dvorak typist on a QWERTY-mapped keyboard69 # Typed by a Dvorak typist on a QWERTY-mapped keyboard
70 typed_on_qwerty = dict(zip(map(ord, qwermap), dvormap))70 typed_on_qwerty = dict(zip(map(ord, qwermap), dvormap))
71 71
72 @match(r'asdf\s+(.+)')72 @match(r'(?:asdf|dvorak)\s+(.+)')
73 def convert_from_qwerty(self, event, text):73 def convert_from_qwerty(self, event, text):
74 event.addresponse(text.translate(self.typed_on_qwerty))74 event.addresponse(text.translate(self.typed_on_qwerty))
75 75
76 @match(r'aoeu\s+(.+)')76 @match(r'(?:aoeu|querty)\s+(.+)')
77 def convert_from_dvorak(self, event, text):77 def convert_from_dvorak(self, event, text):
78 event.addresponse(text.translate(self.typed_on_dvorak))78 event.addresponse(text.translate(self.typed_on_dvorak))
7979
8080
=== modified file 'ibid/plugins/morse.py'
--- ibid/plugins/morse.py 2009-01-24 12:39:04 +0000
+++ ibid/plugins/morse.py 2009-03-01 23:01:30 +0000
@@ -2,8 +2,10 @@
22
3help = {}3help = {}
44
5help["morse"] = u"Translates messages into and out of morse code."
6
5class Morse(Processor):7class Morse(Processor):
6 """morse (text|morsecode)"""8 u"""morse (text|morsecode)"""
7 feature = 'morse'9 feature = 'morse'
810
9 @match(r'^morse\s+(.+)$')11 @match(r'^morse\s+(.+)$')
@@ -64,12 +66,12 @@
6466
6567
66 def text2morse(text):68 def text2morse(text):
67 return " ".join(table.get(c.upper(), c) for c in text)69 return u" ".join(table.get(c.upper(), c) for c in text)
68 70
69 def morse2text(morse):71 def morse2text(morse):
70 rtable = dict((v, k) for k, v in table.items())72 rtable = dict((v, k) for k, v in table.items())
71 toks = morse.split(' ')73 toks = morse.split(' ')
72 return " ".join(rtable.get(t, t) for t in toks)74 return u" ".join(rtable.get(t, t) for t in toks)
7375
74 if message.replace('-', '').replace('.', '').isspace():76 if message.replace('-', '').replace('.', '').isspace():
75 event.addresponse(morse2text(message))77 event.addresponse(morse2text(message))
7678
=== modified file 'ibid/plugins/network.py'
--- ibid/plugins/network.py 2009-02-03 18:03:49 +0000
+++ ibid/plugins/network.py 2009-03-01 19:58:06 +0000
@@ -6,17 +6,18 @@
66
7from ibid.plugins import Processor, match7from ibid.plugins import Processor, match
8from ibid.config import Option8from ibid.config import Option
9from ibid.utils import file_in_path, unicode_output
910
10help = {}11help = {}
11ipaddr = re.compile('\d+\.\d+\.\d+\.\d+')12ipaddr = re.compile('\d+\.\d+\.\d+\.\d+')
1213
13help['dns'] = u'Performs DNS lookups'14help['dns'] = u'Performs DNS lookups'
14class DNS(Processor):15class DNS(Processor):
15 """(dns|nslookup|dig) [<record type>] [for] <host> [(from|@) <nameserver>]"""16 u"""dns [<record type>] [for] <host> [from <nameserver>]"""
1617
17 feature = 'dns'18 feature = 'dns'
1819
19 @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+))?$')20 @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+))?$')
20 def resolve(self, event, record, host, nameserver):21 def resolve(self, event, record, host, nameserver):
21 if not record:22 if not record:
22 if ipaddr.search(host):23 if ipaddr.search(host):
@@ -42,36 +43,49 @@
4243
43 responses = []44 responses = []
44 for rdata in answers:45 for rdata in answers:
45 responses.append(str(rdata))46 responses.append(unicode(rdata))
4647
47 event.addresponse(', '.join(responses))48 event.addresponse(u', '.join(responses))
4849
49help['ping'] = 'ICMP pings the specified host.'50help['ping'] = u'ICMP pings the specified host.'
50class Ping(Processor):51class Ping(Processor):
51 """ping <host>"""52 u"""ping <host>"""
52 feature = 'ping'53 feature = 'ping'
5354
54 ping = Option('ping', 'Path to ping executable', 'ping')55 ping = Option('ping', 'Path to ping executable', 'ping')
5556
57 def setup(self):
58 if not file_in_path(self.ping):
59 raise Exception("Cannot locate ping executeable")
60
56 @match(r'^ping\s+(\S+)$')61 @match(r'^ping\s+(\S+)$')
57 def handle_ping(self, event, host):62 def handle_ping(self, event, host):
58 63 if host.strip().startswith("-"):
64 event.addresponse(False)
65 return
66
59 ping = Popen([self.ping, '-q', '-c5', host], stdout=PIPE, stderr=PIPE)67 ping = Popen([self.ping, '-q', '-c5', host], stdout=PIPE, stderr=PIPE)
60 output, error = ping.communicate()68 output, error = ping.communicate()
61 code = ping.wait()69 code = ping.wait()
6270
63 if code == 0:71 if code == 0:
64 event.addresponse(' '.join(output.splitlines()[-2:]))72 output = unicode_output(' '.join(output.splitlines()[-2:]))
73 event.addresponse(output)
65 else:74 else:
66 event.addresponse(error.replace('\n', ' ').replace('ping:', '', 1).strip())75 error = unicode_output(error.replace('\n', ' ').replace('ping:', '', 1).strip())
76 event.addresponse(error)
6777
68help['tracepath'] = 'Traces the path to the given host.'78help['tracepath'] = u'Traces the path to the given host.'
69class Tracepath(Processor):79class Tracepath(Processor):
70 """tracepath <host>"""80 u"""tracepath <host>"""
71 feature = 'tracepath'81 feature = 'tracepath'
7282
73 tracepath = Option('tracepath', 'Path to tracepath executable', 'tracepath')83 tracepath = Option('tracepath', 'Path to tracepath executable', 'tracepath')
7484
85 def setup(self):
86 if not file_in_path(self.tracepath):
87 raise Exception("Cannot locate tracepath executeable")
88
75 @match(r'^tracepath\s+(\S+)$')89 @match(r'^tracepath\s+(\S+)$')
76 def handle_tracepath(self, event, host):90 def handle_tracepath(self, event, host):
7791
@@ -80,31 +94,53 @@
80 code = tracepath.wait()94 code = tracepath.wait()
8195
82 if code == 0:96 if code == 0:
97 output = unicode_output(output)
83 for line in output.splitlines():98 for line in output.splitlines():
84 event.addresponse(line)99 event.addresponse(line)
85 else:100 else:
86 event.addresponse(error.replace('\n', ' ').strip())101 error = unicode_output(error.strip())
102 event.addresponse(error.replace('\n', ' '))
87103
88help['ipcalc'] = 'IP address calculator'104help['ipcalc'] = u'IP address calculator'
89class IPCalc(Processor):105class IPCalc(Processor):
90 """ipcalc <network> <subnet>106 u"""ipcalc <network> <subnet>
91 ipcalc <address> - <address>"""107 ipcalc <address> - <address>"""
92 feature = 'ipcalc'108 feature = 'ipcalc'
93109
94 ipcalc = Option('ipcalc', 'Path to ipcalc executable', 'ipcalc')110 ipcalc = Option('ipcalc', 'Path to ipcalc executable', 'ipcalc')
95111
112 deaggregate_re = re.compile(r'^((?:\d{1,3}\.){3}\d{1,3})\s+-\s+((?:\d{1,3}\.){3}\d{1,3})$')
113
114 def setup(self):
115 if not file_in_path(self.ipcalc):
116 raise Exception("Cannot locate ipcalc executeable")
117
96 @match(r'^ipcalc\s+(.+)$')118 @match(r'^ipcalc\s+(.+)$')
97 def handle_ipcalc(self, event, parameter):119 def handle_ipcalc(self, event, parameter):
98 120 if parameter.strip().startswith("-"):
99 ipcalc = Popen([self.ipcalc, '-n', '-b', parameter], stdout=PIPE, stderr=PIPE)121 event.addresponse(False)
122 return
123
124 m = self.deaggregate_re.match(parameter)
125 if m:
126 parameter = [m.group(1), '-', m.group(2)]
127 else:
128 parameter = [parameter]
129
130 ipcalc = Popen([self.ipcalc, '-n', '-b'] + parameter, stdout=PIPE, stderr=PIPE)
100 output, error = ipcalc.communicate()131 output, error = ipcalc.communicate()
101 code = ipcalc.wait()132 code = ipcalc.wait()
102133
103 if code == 0:134 if code == 0:
104 for line in output.splitlines():135 output = unicode_output(output)
105 if line:136 if output.startswith("INVALID ADDRESS"):
106 event.addresponse(line)137 event.addresponse(u"That's an invalid address. Try something like 192.168.1.0/24")
138 else:
139 for line in output.splitlines():
140 if line.strip():
141 event.addresponse(line)
107 else:142 else:
143 error = unicode_output(error.strip())
108 event.addresponse(error.replace('\n', ' '))144 event.addresponse(error.replace('\n', ' '))
109145
110# vi: set et sta sw=4 ts=4:146# vi: set et sta sw=4 ts=4:
111147
=== modified file 'ibid/plugins/roshambo.py'
--- ibid/plugins/roshambo.py 2009-01-24 12:39:04 +0000
+++ ibid/plugins/roshambo.py 2009-03-01 23:01:30 +0000
@@ -6,9 +6,9 @@
66
7choices = ['paper', 'rock', 'scissors']7choices = ['paper', 'rock', 'scissors']
88
9help['roshambo'] = 'Plays rock, paper, scissors.'9help['roshambo'] = u'Plays rock, paper, scissors.'
10class RoShamBo(Processor):10class RoShamBo(Processor):
11 """roshambo (rock|paper|scissors)"""11 u"""roshambo (rock|paper|scissors)"""
12 feature = 'roshambo'12 feature = 'roshambo'
1313
14 @match(r'^roshambo\s+(rock|paper|scissors)$')14 @match(r'^roshambo\s+(rock|paper|scissors)$')
@@ -17,11 +17,11 @@
17 bchoice = randint(0, 2)17 bchoice = randint(0, 2)
18 18
19 if uchoice == bchoice:19 if uchoice == bchoice:
20 reply = 'We drew! I also chose %s' % choices[bchoice]20 reply = u'We drew! I also chose %s' % choices[bchoice]
21 elif (uchoice + 1) % 3 == bchoice:21 elif (uchoice + 1) % 3 == bchoice:
22 reply = 'You win! I chose %s :-(' % choices[bchoice]22 reply = u'You win! I chose %s :-(' % choices[bchoice]
23 else:23 else:
24 reply = 'I win! I chose %s' % choices[bchoice]24 reply = u'I win! I chose %s' % choices[bchoice]
25 25
26 event.addresponse(reply)26 event.addresponse(reply)
27 return event27 return event
2828
=== modified file 'ibid/plugins/seen.py'
--- ibid/plugins/seen.py 2009-02-18 19:05:10 +0000
+++ ibid/plugins/seen.py 2009-03-01 23:01:30 +0000
@@ -10,7 +10,7 @@
10from ibid.models import Base, Identity, Account10from ibid.models import Base, Identity, Account
11from ibid.utils import ago11from ibid.utils import ago
1212
13help = {'seen': 'Records when people were last seen.'}13help = {'seen': u'Records when people were last seen.'}
1414
15class Sighting(Base):15class Sighting(Base):
16 __table__ = Table('seen', Base.metadata,16 __table__ = Table('seen', Base.metadata,
@@ -64,7 +64,7 @@
64 session.close()64 session.close()
6565
66class Seen(Processor):66class Seen(Processor):
67 """seen <who>"""67 u"""seen <who>"""
68 feature = 'seen'68 feature = 'seen'
6969
70 datetime_format = Option('datetime_format', 'Format string for timestamps', '%Y/%m/%d %H:%M:%S')70 datetime_format = Option('datetime_format', 'Format string for timestamps', '%Y/%m/%d %H:%M:%S')
7171
=== modified file 'ibid/plugins/sources.py'
--- ibid/plugins/sources.py 2009-02-17 21:04:17 +0000
+++ ibid/plugins/sources.py 2009-03-01 23:01:30 +0000
@@ -1,10 +1,10 @@
1import ibid1import ibid
2from ibid.plugins import Processor, match, authorise2from ibid.plugins import Processor, match, authorise
33
4help = {'sources': 'Controls and lists the configured sources.'}4help = {'sources': u'Controls and lists the configured sources.'}
55
6class Admin(Processor):6class Admin(Processor):
7 """(connect|disconnect) (to|from) <source>7 u"""(connect|disconnect) (to|from) <source>
8 load <source> source"""8 load <source> source"""
9 feature = 'sources'9 feature = 'sources'
1010
@@ -37,7 +37,7 @@
37 event.addresponse(u"Couldn't load %s source" % source)37 event.addresponse(u"Couldn't load %s source" % source)
3838
39class Info(Processor):39class Info(Processor):
40 """(sources|list configured sources)"""40 u"""(sources|list configured sources)"""
41 feature = 'sources'41 feature = 'sources'
4242
43 @match(r'^sources$')43 @match(r'^sources$')
4444
=== modified file 'ibid/plugins/tools.py'
--- ibid/plugins/tools.py 2009-02-23 13:08:26 +0000
+++ ibid/plugins/tools.py 2009-03-01 19:58:06 +0000
@@ -4,31 +4,32 @@
44
5from ibid.plugins import Processor, match5from ibid.plugins import Processor, match
6from ibid.config import Option6from ibid.config import Option
7from ibid.utils import file_in_path, unicode_output
78
8help = {}9help = {}
910
10help['retest'] = 'Checks whether a regular expression matches a given string.'11help['retest'] = u'Checks whether a regular expression matches a given string.'
11class ReTest(Processor):12class ReTest(Processor):
12 """does <pattern> match <string>"""13 u"""does <pattern> match <string>"""
13 feature = 'retest'14 feature = 'retest'
1415
15 @match('^does\s+(.+?)\s+match\s+(.+?)$')16 @match('^does\s+(.+?)\s+match\s+(.+?)$')
16 def retest(self, event, regex, string):17 def retest(self, event, regex, string):
17 event.addresponse(re.search(regex, string) and 'Yes' or 'No')18 event.addresponse(re.search(regex, string) and u'Yes' or u'No')
1819
19help['random'] = 'Generates random numbers.'20help['random'] = u'Generates random numbers.'
20class Random(Processor):21class Random(Processor):
21 """random [ <max> | <min> <max> ]"""22 u"""random [ <max> | <min> <max> ]"""
22 feature = 'random'23 feature = 'random'
2324
24 @match('^rand(?:om)?(?:\s+(\d+)(?:\s+(\d+))?)?$')25 @match('^rand(?:om)?(?:\s+(\d+)(?:\s+(\d+))?)?$')
25 def random(self, event, begin, end):26 def random(self, event, begin, end):
26 if not begin and not end:27 if not begin and not end:
27 event.addresponse(str(random()))28 event.addresponse(unicode(random()))
28 else:29 else:
29 begin = int(begin)30 begin = int(begin)
30 end = end and int(end) or 031 end = end and int(end) or 0
31 event.addresponse(str(randint(min(begin,end), max(begin,end))))32 event.addresponse(unicode(randint(min(begin,end), max(begin,end))))
3233
33bases = { 'bin': (lambda x: int(x, 2), lambda x: "".join(map(lambda y:str((x>>y)&1), range(8-1, -1, -1)))),34bases = { 'bin': (lambda x: int(x, 2), lambda x: "".join(map(lambda y:str((x>>y)&1), range(8-1, -1, -1)))),
34 'hex': (lambda x: int(x, 6), hex),35 'hex': (lambda x: int(x, 6), hex),
@@ -36,9 +37,9 @@
36 'dec': (lambda x: int(x, 10), lambda x: x),37 'dec': (lambda x: int(x, 10), lambda x: x),
37 'ascii': (ord, chr),38 'ascii': (ord, chr),
38 }39 }
39help['base'] = 'Converts between numeric bases as well as ASCII.'40help['base'] = u'Converts between numeric bases as well as ASCII.'
40class Base(Processor):41class Base(Processor):
41 """convert <num> from <base> to <base>"""42 u"""convert <num> from <base> to <base>"""
42 feature = 'base'43 feature = 'base'
4344
44 @match(r'^convert\s+(\S+)\s+(?:from\s+)?(%s)\s+(?:to\s+)?(%s)$' % ('|'.join(bases.keys()), '|'.join(bases.keys())))45 @match(r'^convert\s+(\S+)\s+(?:from\s+)?(%s)\s+(?:to\s+)?(%s)$' % ('|'.join(bases.keys()), '|'.join(bases.keys())))
@@ -48,9 +49,9 @@
48 event.addresponse(str(number))49 event.addresponse(str(number))
4950
5051
51help['units'] = 'Converts values between various units.'52help['units'] = u'Converts values between various units.'
52class Units(Processor):53class Units(Processor):
53 """convert [<value>] <unit> to <unit>"""54 u"""convert [<value>] <unit> to <unit>"""
54 feature = 'units'55 feature = 'units'
5556
56 units = Option('units', 'Path to units executable', 'units')57 units = Option('units', 'Path to units executable', 'units')
@@ -69,6 +70,10 @@
6970
70 temp_function_names = set(temp_scale_names.values())71 temp_function_names = set(temp_scale_names.values())
7172
73 def setup(self):
74 if not file_in_path(self.units):
75 raise Exception("Cannot locate units executeable")
76
72 def format_temperature(self, unit):77 def format_temperature(self, unit):
73 "Return the unit, and convert to 'tempX' format if a known temperature scale"78 "Return the unit, and convert to 'tempX' format if a known temperature scale"
7479
@@ -99,6 +104,7 @@
99 output, error = units.communicate()104 output, error = units.communicate()
100 code = units.wait()105 code = units.wait()
101106
107 output = unicode_output(output)
102 result = output.splitlines()[0].strip()108 result = output.splitlines()[0].strip()
103109
104 if code == 0:110 if code == 0:
105111
=== modified file 'ibid/plugins/trac.py'
--- ibid/plugins/trac.py 2009-02-23 20:29:44 +0000
+++ ibid/plugins/trac.py 2009-03-01 23:01:30 +0000
@@ -9,7 +9,7 @@
9from ibid.config import Option9from ibid.config import Option
10from ibid.utils import ago10from ibid.utils import ago
1111
12help = {'trac': 'Retrieves tickets from a Trac database.'}12help = {'trac': u'Retrieves tickets from a Trac database.'}
1313
14class Ticket(object):14class Ticket(object):
15 pass15 pass
@@ -20,7 +20,7 @@
20mapper(Ticket, ticket_table)20mapper(Ticket, ticket_table)
21 21
22class GetTicket(Processor, RPC):22class GetTicket(Processor, RPC):
23 """ticket <number>23 u"""ticket <number>
24 (open|my|<who>'s) tickets"""24 (open|my|<who>'s) tickets"""
25 feature = 'trac'25 feature = 'trac'
2626
2727
=== modified file 'ibid/plugins/url.py'
--- ibid/plugins/url.py 2009-02-18 19:11:41 +0000
+++ ibid/plugins/url.py 2009-03-01 23:01:30 +0000
@@ -1,4 +1,5 @@
1from datetime import datetime1from datetime import datetime
2from urllib import urlencode
2from urllib2 import urlopen, HTTPRedirectHandler, build_opener, HTTPError3from urllib2 import urlopen, HTTPRedirectHandler, build_opener, HTTPError
3import re4import re
45
@@ -9,7 +10,7 @@
9from ibid.config import Option10from ibid.config import Option
10from ibid.models import Base11from ibid.models import Base
1112
12help = {'url': 'Captures URLs seen in channel, and shortens and lengthens URLs'}13help = {'url': u'Captures URLs seen in channel, and shortens and lengthens URLs'}
1314
14class URL(Base):15class URL(Base):
15 __table__ = Table('urls', Base.metadata,16 __table__ = Table('urls', Base.metadata,
@@ -46,16 +47,16 @@
46 session.close()47 session.close()
4748
48class Shorten(Processor):49class Shorten(Processor):
49 """shorten <url>"""50 u"""shorten <url>"""
50 feature = 'url'51 feature = 'url'
5152
52 @match(r'^shorten\s+(\S+\.\S+)$')53 @match(r'^shorten\s+(\S+\.\S+)$')
53 def shorten(self, event, url):54 def shorten(self, event, url):
54 f = urlopen('http://is.gd/api.php?longurl=%s' % url)55 f = urlopen('http://is.gd/api.php?%s' % urlencode({'longurl': url}))
55 shortened = f.read()56 shortened = f.read()
56 f.close()57 f.close()
5758
58 event.addresponse(shortened)59 event.addresponse(unicode(shortened))
5960
60class NullRedirect(HTTPRedirectHandler):61class NullRedirect(HTTPRedirectHandler):
6162
@@ -63,10 +64,14 @@
63 return None64 return None
6465
65class Lengthen(Processor):66class Lengthen(Processor):
66 """<url>"""67 u"""<url>"""
67 feature = 'url'68 feature = 'url'
6869
69 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/'))70 services = Option('services', 'List of URL prefixes of URL shortening services', (
71 'http://is.gd/', 'http://tinyurl.com/', 'http://ff.im/',
72 'http://shorl.com/', 'http://icanhaz.com/', 'http://url.omnia.za.net/',
73 'http://snipurl.com/', 'http://tr.im/', 'http://snipr.com/'
74 ))
7075
71 def setup(self):76 def setup(self):
72 self.lengthen.im_func.pattern = re.compile(r'^((?:%s)\S+)$' % '|'.join([re.escape(service) for service in self.services]), re.I)77 self.lengthen.im_func.pattern = re.compile(r'^((?:%s)\S+)$' % '|'.join([re.escape(service) for service in self.services]), re.I)
@@ -78,7 +83,7 @@
78 f = opener.open(url)83 f = opener.open(url)
79 except HTTPError, e:84 except HTTPError, e:
80 if e.code in (301, 302, 303, 307):85 if e.code in (301, 302, 303, 307):
81 event.addresponse(e.hdrs['location'])86 event.addresponse(unicode(e.hdrs['location']))
82 return87 return
8388
84 f.close()89 f.close()
8590
=== modified file 'ibid/source/irc.py'
--- ibid/source/irc.py 2009-02-23 20:29:44 +0000
+++ ibid/source/irc.py 2009-03-01 23:04:43 +0000
@@ -100,7 +100,8 @@
100 def send(self, response):100 def send(self, response):
101 message = response['reply'].replace('\n', ' ')[:490]101 message = response['reply'].replace('\n', ' ')[:490]
102 if 'action' in response and response['action']:102 if 'action' in response and response['action']:
103 self.me(response['target'].encode('utf-8'), message.encode('utf-8'))103 # We can't use self.me() because it prepends a # onto channel names
104 self.ctcpMakeQuery(response['target'].encode('utf-8'), [('ACTION', message.encode('utf-8'))])
104 self.factory.log.debug(u"Sent action to %s: %s", response['target'], message)105 self.factory.log.debug(u"Sent action to %s: %s", response['target'], message)
105 else:106 else:
106 self.msg(response['target'].encode('utf-8'), message.encode('utf-8'))107 self.msg(response['target'].encode('utf-8'), message.encode('utf-8'))
107108
=== modified file 'ibid/utils.py'
--- ibid/utils.py 2009-02-21 20:06:39 +0000
+++ ibid/utils.py 2009-03-01 19:58:06 +0000
@@ -1,4 +1,6 @@
1from htmlentitydefs import name2codepoint1from htmlentitydefs import name2codepoint
2import os
3import os.path
2import re4import re
35
4def ago(delta, units=None):6def ago(delta, units=None):
@@ -28,3 +30,16 @@
28def decode_htmlentities(string):30def decode_htmlentities(string):
29 entity_re = re.compile("&(#?)(\d{1,5}|\w{1,8});")31 entity_re = re.compile("&(#?)(\d{1,5}|\w{1,8});")
30 return entity_re.subn(substitute_entity, string)[0]32 return entity_re.subn(substitute_entity, string)[0]
33
34def file_in_path(program):
35 path = os.environ.get("PATH", os.defpath).split(os.pathsep)
36 path = [os.path.join(dir, program) for dir in path]
37 path = [True for file in path if os.path.isfile(file)]
38 return bool(path)
39
40def unicode_output(output, errors="strict"):
41 try:
42 encoding = os.getenv("LANG").split(".")[1]
43 except:
44 encoding = "ascii"
45 return unicode(output, encoding, errors)

Subscribers

People subscribed via source and target branches