From 2fb00589439a4efb3906d4e681e7ed815dcd180a Mon Sep 17 00:00:00 2001
From: "Sean B. Palmer"
Date: Sat, 23 Feb 2008 12:16:43 +0000
Subject: [PATCH] Lots of fixes, changes, and new goodies.
---
bot.py | 31 ++++++++++++++++++++-----------
irc.py | 24 +++++++++++++++++++-----
modules/clock.py | 4 ++--
modules/etymology.py | 2 +-
modules/head.py | 4 ++--
modules/reload.py | 10 ++++++----
modules/search.py | 17 ++++++++++++-----
modules/tell.py | 4 ++++
modules/translate.py | 14 +++++++++-----
modules/weather.py | 2 +-
modules/wikipedia.py | 2 ++
opt/freenode.py | 16 ++++++++++++++--
phenny | 4 ++--
13 files changed, 94 insertions(+), 40 deletions(-)
diff --git a/bot.py b/bot.py
index d84a635..4c27b7d 100755
--- a/bot.py
+++ b/bot.py
@@ -7,7 +7,7 @@ Licensed under the Eiffel Forum License 2.
http://inamidst.com/phenny/
"""
-import sys, os, re, time, threading, optparse
+import sys, os, re, threading, imp
import irc
home = os.getcwd()
@@ -31,20 +31,29 @@ class Phenny(irc.Bot):
def setup(self):
self.variables = {}
+ filenames = []
if not hasattr(self.config, 'enable'):
- load = [('modules', filename[:-3])
- for filename in os.listdir(os.path.join(home, 'modules'))
- if filename.endswith('.py') and
- not filename.startswith('_') and
- not filename[:-3] in self.config.disable]
- else: load = [('modules', e) for e in self.config.enable]
+ for fn in os.listdir(os.path.join(home, 'modules')):
+ if fn.endswith('.py') and not fn.startswith('_'):
+ filenames.append(os.path.join(home, 'modules', fn))
+ else:
+ for fn in self.config.enable:
+ filenames.append(os.path.join(home, 'modules', fn + '.py'))
+ # @@ exclude
- if hasattr(self.config, 'opt'):
- load += [('opt', o) for o in self.config.opt]
+ if hasattr(self.config, 'extra'):
+ for fn in self.config.extra:
+ if os.path.isfile(fn):
+ filenames.append(fn)
+ elif os.path.isdir(fn):
+ for n in os.listdir(fn):
+ if n.endswith('.py') and not n.startswith('_'):
+ filenames.append(os.path.join(fn, n))
modules = []
- for package, name in load:
- try: module = getattr(__import__(package + '.' + name), name)
+ for filename in filenames:
+ name = os.path.basename(filename)[:-3]
+ try: module = imp.load_source(name, filename)
except Exception, e:
print >> sys.stderr, "Error loading %s: %s" % (name, e)
else:
diff --git a/irc.py b/irc.py
index 5916396..89114af 100755
--- a/irc.py
+++ b/irc.py
@@ -41,11 +41,21 @@ class Bot(asynchat.async_chat):
import threading
self.sending = threading.RLock()
- def write(self, args, text=None):
+ def __write(self, args, text=None):
+ # print '%r %r %r' % (self, args, text)
if text is not None:
self.push(' '.join(args) + ' :' + text + '\r\n')
else: self.push(' '.join(args) + '\r\n')
+ def write(self, args, text=None):
+ # This is a safe version of __write
+ try:
+ args = [arg.encode('utf-8') for arg in args]
+ if text is not None:
+ text = text.encode('utf-8')
+ self.__write(args, text)
+ except Exception, e: pass
+
def run(self, host, port=6667):
self.initiate_connect(host, port)
@@ -103,6 +113,10 @@ class Bot(asynchat.async_chat):
try: text = text.encode('utf-8')
except UnicodeEncodeError, e:
text = e.__class__ + ': ' + str(e)
+ if isinstance(recipient, unicode):
+ try: recipient = recipient.encode('utf-8')
+ except UnicodeEncodeError, e:
+ return
# No messages within the last 3 seconds? Go ahead!
# Otherwise, wait so it's been at least 0.8 seconds + penalty
@@ -115,14 +129,14 @@ class Bot(asynchat.async_chat):
time.sleep(wait - elapsed)
# Loop detection
- messages = [m[1] for m in self.stack[-5:]]
- if messages.count(text) >= 3:
+ messages = [m[1] for m in self.stack[-8:]]
+ if messages.count(text) >= 5:
text = '...'
- if messages.count('...') >= 1:
+ if messages.count('...') >= 3:
self.sending.release()
return
- self.write(('PRIVMSG', recipient), text)
+ self.__write(('PRIVMSG', recipient), text)
self.stack.append((time.time(), text))
self.stack = self.stack[-10:]
diff --git a/modules/clock.py b/modules/clock.py
index 210f8fb..9e38d7b 100755
--- a/modules/clock.py
+++ b/modules/clock.py
@@ -187,7 +187,7 @@ TimeZones.update(TZ1)
@deprecated
def f_time(self, origin, match, args):
""".t [ ] - Returns the current time"""
- tz = match.group(1) or 'GMT'
+ tz = match.group(2) or 'GMT'
# Personal time zones, because they're rad
if hasattr(self.config, 'timezones'):
@@ -196,7 +196,7 @@ def f_time(self, origin, match, args):
if People.has_key(tz):
tz = People[tz]
- elif (not match.group(1)) and People.has_key(origin.nick):
+ elif (not match.group(2)) and People.has_key(origin.nick):
tz = People[origin.nick]
TZ = tz.upper()
diff --git a/modules/etymology.py b/modules/etymology.py
index ecdbb7b..52ae1bc 100755
--- a/modules/etymology.py
+++ b/modules/etymology.py
@@ -76,7 +76,7 @@ def etymology(word):
def f_etymology(self, origin, match, args):
word = match.group(2)
- try: result = etymology(word)
+ try: result = etymology(word.encode('utf-8'))
except IOError:
msg = "Can't connect to etymonline.com (%s)" % (etyuri % word)
self.msg(origin.sender, msg)
diff --git a/modules/head.py b/modules/head.py
index 4b75cb4..193286a 100755
--- a/modules/head.py
+++ b/modules/head.py
@@ -7,7 +7,7 @@ Licensed under the Eiffel Forum License 2.
http://inamidst.com/phenny/
"""
-import re, urllib
+import re, urllib, urlparse
from htmlentitydefs import name2codepoint
import web
from tools import deprecated
@@ -66,7 +66,7 @@ def f_title(self, origin, match, args):
status = str(info[1])
info = info[0]
if status.startswith('3'):
- uri = info['Location']
+ uri = urlparse.urljoin(uri, info['Location'])
else: break
redirects += 1
diff --git a/modules/reload.py b/modules/reload.py
index 257eaf7..7a4c76f 100755
--- a/modules/reload.py
+++ b/modules/reload.py
@@ -13,8 +13,10 @@ def f_reload(phenny, input):
"""Reloads a module, for use by admins only."""
if not input.admin: return
- name = match.group(2)
- module = getattr(__import__('modules.' + name), name)
+ name = input.group(2)
+ try: module = getattr(__import__('modules.' + name), name)
+ except ImportError:
+ module = getattr(__import__('opt.' + name), name)
reload(module)
if hasattr(module, '__file__'):
@@ -23,8 +25,8 @@ def f_reload(phenny, input):
modified = time.strftime('%Y-%m-%d %H:%M:%S', time.gmtime(mtime))
else: modified = 'unknown'
- self.register(vars(module))
- self.bind_commands()
+ phenny.register(vars(module))
+ phenny.bind_commands()
phenny.reply('%r (version: %s)' % (module, modified))
f_reload.name = 'reload'
diff --git a/modules/search.py b/modules/search.py
index 9ad1a04..06241d4 100755
--- a/modules/search.py
+++ b/modules/search.py
@@ -30,10 +30,14 @@ def search(query, n=1):
def result(query):
results = search(query)
- return results['results'][0]['url']
+ if results['results']:
+ return results['results'][0]['url']
+ return None
def count(query):
results = search(query)
+ if not results['results']:
+ return '0'
return results['estimatedCount']
def formatnumber(n):
@@ -44,8 +48,11 @@ def formatnumber(n):
return ''.join(parts)
def g(phenny, input):
- uri = result(input.group(2))
- phenny.reply(uri)
+ query = input.group(2)
+ uri = result(query)
+ if uri:
+ phenny.reply(uri)
+ else: phenny.reply("No results found for '%s'." % query)
g.commands = ['g']
g.priority = 'high'
@@ -60,7 +67,7 @@ r_query = re.compile(
r'\+?"[^"\\]*(?:\\.[^"\\]*)*"|\[[^]\\]*(?:\\.[^]\\]*)*\]|\S+'
)
-def compare(phenny, input):
+def gcs(phenny, input):
queries = r_query.findall(input.group(2))
if len(queries) > 6:
return phenny.reply('Sorry, can only compare up to six things.')
@@ -76,7 +83,7 @@ def compare(phenny, input):
results = [(term, n) for (n, term) in reversed(sorted(results))]
reply = ', '.join('%s (%s)' % (t, formatnumber(n)) for (t, n) in results)
phenny.say(reply)
-compare.commands = ['gco', 'comp']
+gcs.commands = ['gcs']
if __name__ == '__main__':
print __doc__.strip()
diff --git a/modules/tell.py b/modules/tell.py
index 3b487c8..e4af264 100755
--- a/modules/tell.py
+++ b/modules/tell.py
@@ -59,6 +59,10 @@ def f_remind(phenny, input):
# @@ Multiple comma-separated tellees? Cf. Terje, #swhack, 2006-04-15
verb, tellee, msg = input.groups()
+ verb = verb.encode('utf-8')
+ tellee = tellee.encode('utf-8')
+ msg = msg.encode('utf-8')
+
tellee_original = tellee.rstrip(',:;')
tellee = tellee.lower()
diff --git a/modules/translate.py b/modules/translate.py
index ed3589f..7e14b1d 100644
--- a/modules/translate.py
+++ b/modules/translate.py
@@ -67,21 +67,24 @@ def translate(phrase, lang, target='en'):
return None
def tr(phenny, input):
+ """Translates a phrase, with an optional language hint."""
lang, phrase = input.groups()
-
+ phrase = phrase.encode('utf-8')
if (len(phrase) > 350) and (not phenny.admin(input.nick)):
return phenny.reply('Phrase must be under 350 characters.')
language = guess_language(phrase)
if language is None:
return phenny.reply('Unable to guess the language, sorry.')
+ else: language = lang.encode('utf-8')
if language != 'en':
translation = translate(phrase, language)
if translation is not None:
- return phenny.reply(u'"%s" (%s)' % (translation, language))
+ translation = translation.decode('utf-8').encode('utf-8')
+ return phenny.reply('"%s" (%s)' % (translation, language))
- error = "I think it's %s, but I can't translate that language."
+ error = "I think it's %s, which I can't translate."
return phenny.reply(error % language.title())
# Otherwise, it's English, so mangle it for fun
@@ -93,10 +96,11 @@ def tr(phenny, input):
return phenny.reply(u'"%s" (en-unmangled)' % phrase)
return phenny.reply("I think it's English already.")
# @@ or 'Why but that be English, sire.'
-tr.doc = ('phenny: ""? or phenny: ""?',
- 'Translate , optionally forcing the interpretation.')
tr.rule = ('$nick', ur'(?:([a-z]{2}) +)?["“](.+?)["”]\? *$')
+tr.example = '$nickname: "mon chien"? or $nickname: fr "mon chien"?'
tr.priority = 'low'
+# @@ mangle
+
if __name__ == '__main__':
print __doc__.strip()
diff --git a/modules/weather.py b/modules/weather.py
index 9e03bf4..a8fdf4f 100755
--- a/modules/weather.py
+++ b/modules/weather.py
@@ -26,7 +26,7 @@ def json(text):
raise ValueError('Input must be serialised JSON.')
def location(name):
- name = urllib.quote(name)
+ name = urllib.quote(name.encode('utf-8'))
uri = 'http://ws.geonames.org/searchJSON?q=%s&maxRows=1' % name
for i in xrange(10):
u = urllib.urlopen(uri)
diff --git a/modules/wikipedia.py b/modules/wikipedia.py
index 893ecab..0a0a415 100644
--- a/modules/wikipedia.py
+++ b/modules/wikipedia.py
@@ -123,6 +123,8 @@ def wikipedia(term, last=False):
def wik(phenny, input):
origterm = input.groups()[1]
+ origterm = origterm.encode('utf-8')
+
term = urllib.unquote(origterm)
if not term:
return phenny.say(origin.sender, 'Maybe you meant ".wik Zen"?')
diff --git a/opt/freenode.py b/opt/freenode.py
index 50e6b5d..b87a5e2 100644
--- a/opt/freenode.py
+++ b/opt/freenode.py
@@ -12,10 +12,22 @@ def replaced(phenny, input):
response = {
'cp': '.cp has been replaced by .u',
'pc': '.pc has been replaced by .u',
- 'unicode': '.unicode has been replaced by .u'
+ 'unicode': '.unicode has been replaced by .u',
+ 'compare': '.compare has been replaced by .gcs (googlecounts)',
+ 'map': 'the .map command has been removed; ask sbp for details',
+ 'acronym': 'the .acronym command has been removed; ask sbp for details',
+ 'img': 'the .img command has been removed; ask sbp for details',
+ 'v': '.v has been replaced by .val',
+ 'validate': '.validate has been replaced by .validate',
+ 'thesaurus': ".thesaurus hasn't been ported to my new codebase yet",
+ 'rate': ".rate hasn't been ported to my new codebase yet, sorry!",
+ 'rates': ".rates hasn't been ported to my new codebase yet, sorry!"
}[command]
phenny.reply(response)
-replaced.commands = ['cp', 'pc', 'unicode']
+replaced.commands = [
+ 'cp', 'pc', 'unicode', 'compare', 'map', 'acronym', 'img',
+ 'v', 'validate', 'thesaurus', 'rate', 'rates'
+]
replaced.priority = 'low'
if __name__ == '__main__':
diff --git a/phenny b/phenny
index ca52b6d..f54cc6c 100755
--- a/phenny
+++ b/phenny
@@ -29,7 +29,7 @@ def create_default_config(fn):
# These are people who will be able to use admin.py's functions...
admins = [owner, 'someoneyoutrust']
# But admin.py is disabled by default, as follows:
- disable = ['admin']
+ exclude = ['admin']
# If you want to enumerate a list of modules rather than disabling
# some, use "enable = ['example']", which takes precedent over disable
@@ -37,7 +37,7 @@ def create_default_config(fn):
# enable = []
# Modules to load from the opt directory
- opt = []
+ extra = []
# EOF
""")