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 """)