From 3d920f431789ac53596933785b5fe61463335e3b Mon Sep 17 00:00:00 2001 From: "Sean B. Palmer" Date: Fri, 29 Feb 2008 15:36:18 +0000 Subject: [PATCH] Some more little fixes, and added a Makefile. --- Makefile | 6 ++++ bot.py | 22 ++++++++------- modules/codepoints.py | 13 ++++++++- modules/head.py | 64 +++++++++++++++++++++++++++++-------------- modules/info.py | 43 +++++++++++++++++++++++++++++ modules/reload.py | 7 ++++- modules/translate.py | 29 ++++++++++++-------- modules/wikipedia.py | 8 ++++-- opt/freenode.py | 8 ++++-- 9 files changed, 150 insertions(+), 50 deletions(-) create mode 100644 Makefile diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..bec372a --- /dev/null +++ b/Makefile @@ -0,0 +1,6 @@ +# Makefile +# Copyright 2008, Sean B. Palmer, inamidst.com +# Licensed under the Eiffel Forum License 2. + +archive: ; + hg archive -t tbz2 phenny.tar.bz2 diff --git a/bot.py b/bot.py index 4c27b7d..ae97d93 100755 --- a/bot.py +++ b/bot.py @@ -79,17 +79,16 @@ class Phenny(irc.Bot): def bind(self, priority, regexp, func): print priority, regexp.pattern.encode('utf-8'), func - self.commands[priority].setdefault(regexp, []).append(func) - # @@ register documentation + # register documentation + if not hasattr(func, 'name'): + func.name = func.__name__ if func.__doc__: - if hasattr(func, 'name'): - name = func.name - else: name = func.__name__ if hasattr(func, 'example'): example = func.example example = example.replace('$nickname', self.nick) else: example = None - self.doc[name] = (func.__doc__, example) + self.doc[func.name] = (func.__doc__, example) + self.commands[priority].setdefault(regexp, []).append(func) def sub(pattern, self=self): # These replacements have significant order @@ -127,8 +126,8 @@ class Phenny(irc.Bot): prefix = self.config.prefix commands, pattern = func.rule for command in commands: - command = r'(%s) +' % command - regexp = re.compile(prefix + command + pattern) + command = r'(%s)(?: +(?:%s))?' % (command, pattern) + regexp = re.compile(prefix + command) bind(self, func.priority, regexp, func) # 3) e.g. ('$nick', ['p', 'q'], '(.*)') @@ -196,8 +195,6 @@ class Phenny(irc.Bot): match = regexp.match(text) if match: - # print 'STATS:', origin.sender, func.__name__ - phenny = self.wrapped(origin, text, match) input = self.input(origin, text, bytes, match, event) @@ -207,5 +204,10 @@ class Phenny(irc.Bot): t.start() else: self.call(func, origin, phenny, input) + for source in [origin.sender, origin.nick]: + try: self.stats[(func.name, source)] += 1 + except KeyError: + self.stats[(func.name, source)] = 1 + if __name__ == '__main__': print __doc__ diff --git a/modules/codepoints.py b/modules/codepoints.py index 83425c5..d966670 100644 --- a/modules/codepoints.py +++ b/modules/codepoints.py @@ -21,7 +21,8 @@ def about(u, cp=None, name=None): def codepoint_simple(arg): arg = arg.upper() - r_label = re.compile('\\b' + arg.replace(' ', '.*\\b')) + + r_label = re.compile('\\b' + arg.replace(' ', '.*\\b') + '\\b') results = [] for cp in xrange(0xFFFF): @@ -31,6 +32,16 @@ def codepoint_simple(arg): if r_label.search(name): results.append((len(name), u, cp, name)) + if not results: + r_label = re.compile('\\b' + arg.replace(' ', '.*\\b')) + for cp in xrange(0xFFFF): + u = unichr(cp) + try: name = unicodedata.name(u) + except ValueError: continue + + if r_label.search(name): + results.append((len(name), u, cp, name)) + if not results: return None diff --git a/modules/head.py b/modules/head.py index 193286a..8f687fa 100755 --- a/modules/head.py +++ b/modules/head.py @@ -7,22 +7,24 @@ Licensed under the Eiffel Forum License 2. http://inamidst.com/phenny/ """ -import re, urllib, urlparse +import re, urllib, urlparse, time from htmlentitydefs import name2codepoint import web from tools import deprecated -@deprecated -def f_httphead(self, origin, match, args): - """.head ? - Perform an HTTP HEAD on URI.""" - if origin.sender == '#talis': return - uri = match.group(2) - header = match.group(3) +def head(phenny, input): + """Provide HTTP HEAD information.""" + uri = input.group(2) + uri = (uri or '').encode('utf-8') + if ' ' in uri: + uri, header = uri.rsplit(' ', 1) + else: uri, header = uri, None + + if not uri and hasattr(phenny, 'last_seen_uri'): + uri = phenny.last_seen_uri try: info = web.head(uri) - except IOError: - self.msg(origin.sender, "Can't connect to %s" % uri) - return + except IOError: return phenny.say("Can't connect to %s" % uri) if not isinstance(info, list): info = dict(info) @@ -33,17 +35,27 @@ def f_httphead(self, origin, match, args): info = newInfo if header is None: - msg = 'Status: %s (for more, try ".head uri header")' % info['Status'] - self.msg(origin.sender, msg) + data = [] + if info.has_key('Status'): + data.append(info['Status']) + if info.has_key('content-type'): + data.append(info['content-type'].replace('; charset=', ', ')) + if info.has_key('last-modified'): + modified = info['last-modified'] + modified = time.strptime(modified, '%a, %d %b %Y %H:%M:%S %Z') + data.append(time.strftime('%Y-%m-%d %H:%M:%S UTC', modified)) + if info.has_key('content-length'): + data.append(info['content-length'] + ' bytes') + phenny.reply(', '.join(data)) else: headerlower = header.lower() if info.has_key(headerlower): - self.msg(origin.sender, header + ': ' + info.get(headerlower)) + phenny.say(header + ': ' + info.get(headerlower)) else: msg = 'There was no %s header in the response.' % header - self.msg(origin.sender, msg) -f_httphead.rule = (['head'], r'(\S+)(?: +(\S+))?') -f_httphead.thread = True + phenny.say(msg) +head.commands = ['head'] +head.example = '.head http://www.w3.org/' r_title = re.compile(r'(?ims)]*>(.*?)') r_entity = re.compile(r'&[A-Za-z0-9#]+;') @@ -52,6 +64,11 @@ r_entity = re.compile(r'&[A-Za-z0-9#]+;') def f_title(self, origin, match, args): """.title - Return the title of URI.""" uri = match.group(2) + uri = (uri or '').encode('utf-8') + + if not uri and hasattr(self, 'last_seen_uri'): + uri = self.last_seen_uri + if not ':' in uri: uri = 'http://' + uri @@ -74,10 +91,10 @@ def f_title(self, origin, match, args): self.msg(origin.sender, origin.nick + ": Too many redirects") return - try: mtype = info['Content-Type'] + try: mtype = info['content-type'] except: - self.msg(origin.sender, origin.nick + ": Document isn't HTML") - return + err = ": Couldn't get the Content-Type, sorry" + return self.msg(origin.sender, origin.nick + err) if not (('/html' in mtype) or ('/xhtml' in mtype)): self.msg(origin.sender, origin.nick + ": Document isn't HTML") return @@ -119,8 +136,13 @@ def f_title(self, origin, match, args): title = '[Title is the empty document, "".]' self.msg(origin.sender, origin.nick + ': ' + title) else: self.msg(origin.sender, origin.nick + ': No title found') -f_title.rule = (['title'], r'(\S+)') -f_title.thread = True +f_title.commands = ['title'] + +def noteuri(phenny, input): + uri = input.group(1).encode('utf-8') + phenny.bot.last_seen_uri = uri +noteuri.rule = r'.*(http://[^<> "]+)[,.]?' +noteuri.priority = 'low' if __name__ == '__main__': print __doc__ diff --git a/modules/info.py b/modules/info.py index a70c823..df6ad69 100644 --- a/modules/info.py +++ b/modules/info.py @@ -40,5 +40,48 @@ def help(phenny, input): help.rule = ('$nick', r'(?i)help(?:[?!]+)?$') help.priority = 'low' +def stats(phenny, input): + commands = {} + users = {} + channels = {} + + ignore = set(['f_note', 'startup', 'message', 'noteuri']) + for (name, user), count in phenny.stats.iteritems(): + if name in ignore: continue + + if not user.startswith('#'): + try: users[user] += count + except KeyError: users[user] = count + else: + try: commands[name] += count + except KeyError: commands[name] = count + + try: channels[user] += count + except KeyError: channels[user] = count + + comrank = sorted([(b, a) for (a, b) in commands.iteritems()], reverse=True) + userank = sorted([(b, a) for (a, b) in users.iteritems()], reverse=True) + charank = sorted([(b, a) for (a, b) in channels.iteritems()], reverse=True) + + # most heavily used commands + creply = 'most used commands: ' + for count, command in comrank[:10]: + creply += '%s (%s), ' % (command, count) + phenny.say(creply.rstrip(', ')) + + # most heavy users + reply = 'power users: ' + for count, user in userank[:10]: + reply += '%s (%s), ' % (user, count) + phenny.say(reply.rstrip(', ')) + + # most heavy channels + chreply = 'power channels: ' + for count, channel in charank[:3]: + chreply += '%s (%s), ' % (channel, count) + phenny.say(chreply.rstrip(', ')) +stats.commands = ['stats'] +stats.priority = 'low' + if __name__ == '__main__': print __doc__.strip() diff --git a/modules/reload.py b/modules/reload.py index 7a4c76f..2febcd2 100755 --- a/modules/reload.py +++ b/modules/reload.py @@ -14,6 +14,10 @@ def f_reload(phenny, input): if not input.admin: return name = input.group(2) + if not name: + phenny.setup() + return phenny.reply('done') + try: module = getattr(__import__('modules.' + name), name) except ImportError: module = getattr(__import__('opt.' + name), name) @@ -30,7 +34,8 @@ def f_reload(phenny, input): phenny.reply('%r (version: %s)' % (module, modified)) f_reload.name = 'reload' -f_reload.rule = ('$nick', ['reload'], r'(\S+)') +f_reload.rule = ('$nick', ['reload'], r'(\S+)?') +f_reload.priority = 'low' if __name__ == '__main__': print __doc__.strip() diff --git a/modules/translate.py b/modules/translate.py index 7e14b1d..d90de43 100644 --- a/modules/translate.py +++ b/modules/translate.py @@ -8,7 +8,7 @@ Licensed under the Eiffel Forum License 2. http://inamidst.com/phenny/ """ -import re +import re, time import web r_translation = re.compile(r'
([^<]+)
') @@ -43,7 +43,7 @@ def guess_language(phrase): try: return languages[lang] except KeyError: return lang - return 'unknown' + return 'Moon Language' def translate(phrase, lang, target='en'): babelfish = 'http://world.altavista.com/tr' @@ -68,35 +68,40 @@ def translate(phrase, lang, target='en'): def tr(phenny, input): """Translates a phrase, with an optional language hint.""" - lang, phrase = input.groups() + input, output, 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: + input = input or guess_language(phrase) + if not input: return phenny.reply('Unable to guess the language, sorry.') - else: language = lang.encode('utf-8') + input = input.encode('utf-8') + output = (output or 'en').encode('utf-8') - if language != 'en': - translation = translate(phrase, language) + if not ((input == 'en') and (output == 'en')): + translation = translate(phrase, input, output) if translation is not None: translation = translation.decode('utf-8').encode('utf-8') - return phenny.reply('"%s" (%s)' % (translation, language)) + if output == 'en': + return phenny.reply('"%s" (%s)' % (translation, input)) + else: return phenny.reply('"%s" (%s -> %s)' % \ + (translation, input, output)) error = "I think it's %s, which I can't translate." - return phenny.reply(error % language.title()) + return phenny.reply(error % input.title()) # Otherwise, it's English, so mangle it for fun - for other in ['de', 'ja']: + for other in ['de', 'ja', 'de', 'ja', 'de', 'ja', 'de', 'ja', 'de', 'ja']: phrase = translate(phrase, 'en', other) phrase = translate(phrase, other, 'en') + time.sleep(0.1) if phrase is not None: 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.rule = ('$nick', ur'(?:([a-z]{2}) +)?["“](.+?)["”]\? *$') +tr.rule = ('$nick', ur'(?:([a-z]{2}) +)?(?:([a-z]{2}) +)?["“](.+?)["”]\? *$') tr.example = '$nickname: "mon chien"? or $nickname: fr "mon chien"?' tr.priority = 'low' diff --git a/modules/wikipedia.py b/modules/wikipedia.py index 0a0a415..696bce4 100644 --- a/modules/wikipedia.py +++ b/modules/wikipedia.py @@ -55,6 +55,7 @@ def search(term): else: return term def wikipedia(term, last=False): + global wikiuri bytes = web.get(wikiuri % urllib.quote(term)) bytes = r_tr.sub('', bytes) @@ -83,7 +84,8 @@ def wikipedia(term, last=False): and not 'disambiguation)"' in para) and not '(images and media)' in para and not 'This article contains a' in para - and not 'id="coordinates"' in para] + and not 'id="coordinates"' in para + and not 'class="thumb' in para] for i, para in enumerate(paragraphs): para = para.replace('', '|') @@ -119,7 +121,9 @@ def wikipedia(term, last=False): return None sentence = '"' + sentence.replace('"', "'") + '"' - return sentence + ' - ' + (wikiuri % term) + sentence = sentence.decode('utf-8').encode('utf-8') + wikiuri = wikiuri.encode('utf-8') + return sentence + ' - ' + (wikiuri % term.encode('utf-8')) def wik(phenny, input): origterm = input.groups()[1] diff --git a/opt/freenode.py b/opt/freenode.py index b87a5e2..f3d285c 100644 --- a/opt/freenode.py +++ b/opt/freenode.py @@ -20,13 +20,15 @@ def replaced(phenny, input): '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!" + 'rates': "moon wanter. moOOoon wanter!", + 'web': 'the .web command has been removed; ask sbp for details', + 'mangle': ".mangle hasn't been ported to my new codebase yet", + 'origin': ".origin hasn't been ported to my new codebase yet" }[command] phenny.reply(response) replaced.commands = [ 'cp', 'pc', 'unicode', 'compare', 'map', 'acronym', 'img', - 'v', 'validate', 'thesaurus', 'rate', 'rates' + 'v', 'validate', 'thesaurus', 'rates', 'web', 'mangle', 'origin' ] replaced.priority = 'low'