Some more little fixes, and added a Makefile.

master
Sean B. Palmer 2008-02-29 15:36:18 +00:00
parent cbdf9ebd73
commit 3d920f4317
9 changed files with 150 additions and 50 deletions

6
Makefile Normal file
View File

@ -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

22
bot.py
View File

@ -79,17 +79,16 @@ class Phenny(irc.Bot):
def bind(self, priority, regexp, func): def bind(self, priority, regexp, func):
print priority, regexp.pattern.encode('utf-8'), 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 func.__doc__:
if hasattr(func, 'name'):
name = func.name
else: name = func.__name__
if hasattr(func, 'example'): if hasattr(func, 'example'):
example = func.example example = func.example
example = example.replace('$nickname', self.nick) example = example.replace('$nickname', self.nick)
else: example = None 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): def sub(pattern, self=self):
# These replacements have significant order # These replacements have significant order
@ -127,8 +126,8 @@ class Phenny(irc.Bot):
prefix = self.config.prefix prefix = self.config.prefix
commands, pattern = func.rule commands, pattern = func.rule
for command in commands: for command in commands:
command = r'(%s) +' % command command = r'(%s)(?: +(?:%s))?' % (command, pattern)
regexp = re.compile(prefix + command + pattern) regexp = re.compile(prefix + command)
bind(self, func.priority, regexp, func) bind(self, func.priority, regexp, func)
# 3) e.g. ('$nick', ['p', 'q'], '(.*)') # 3) e.g. ('$nick', ['p', 'q'], '(.*)')
@ -196,8 +195,6 @@ class Phenny(irc.Bot):
match = regexp.match(text) match = regexp.match(text)
if match: if match:
# print 'STATS:', origin.sender, func.__name__
phenny = self.wrapped(origin, text, match) phenny = self.wrapped(origin, text, match)
input = self.input(origin, text, bytes, match, event) input = self.input(origin, text, bytes, match, event)
@ -207,5 +204,10 @@ class Phenny(irc.Bot):
t.start() t.start()
else: self.call(func, origin, phenny, input) 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__': if __name__ == '__main__':
print __doc__ print __doc__

View File

@ -21,7 +21,8 @@ def about(u, cp=None, name=None):
def codepoint_simple(arg): def codepoint_simple(arg):
arg = arg.upper() arg = arg.upper()
r_label = re.compile('\\b' + arg.replace(' ', '.*\\b'))
r_label = re.compile('\\b' + arg.replace(' ', '.*\\b') + '\\b')
results = [] results = []
for cp in xrange(0xFFFF): for cp in xrange(0xFFFF):
@ -31,6 +32,16 @@ def codepoint_simple(arg):
if r_label.search(name): if r_label.search(name):
results.append((len(name), u, cp, 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: if not results:
return None return None

View File

@ -7,22 +7,24 @@ Licensed under the Eiffel Forum License 2.
http://inamidst.com/phenny/ http://inamidst.com/phenny/
""" """
import re, urllib, urlparse import re, urllib, urlparse, time
from htmlentitydefs import name2codepoint from htmlentitydefs import name2codepoint
import web import web
from tools import deprecated from tools import deprecated
@deprecated def head(phenny, input):
def f_httphead(self, origin, match, args): """Provide HTTP HEAD information."""
""".head <URI> <FieldName>? - Perform an HTTP HEAD on URI.""" uri = input.group(2)
if origin.sender == '#talis': return uri = (uri or '').encode('utf-8')
uri = match.group(2) if ' ' in uri:
header = match.group(3) 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) try: info = web.head(uri)
except IOError: except IOError: return phenny.say("Can't connect to %s" % uri)
self.msg(origin.sender, "Can't connect to %s" % uri)
return
if not isinstance(info, list): if not isinstance(info, list):
info = dict(info) info = dict(info)
@ -33,17 +35,27 @@ def f_httphead(self, origin, match, args):
info = newInfo info = newInfo
if header is None: if header is None:
msg = 'Status: %s (for more, try ".head uri header")' % info['Status'] data = []
self.msg(origin.sender, msg) 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: else:
headerlower = header.lower() headerlower = header.lower()
if info.has_key(headerlower): if info.has_key(headerlower):
self.msg(origin.sender, header + ': ' + info.get(headerlower)) phenny.say(header + ': ' + info.get(headerlower))
else: else:
msg = 'There was no %s header in the response.' % header msg = 'There was no %s header in the response.' % header
self.msg(origin.sender, msg) phenny.say(msg)
f_httphead.rule = (['head'], r'(\S+)(?: +(\S+))?') head.commands = ['head']
f_httphead.thread = True head.example = '.head http://www.w3.org/'
r_title = re.compile(r'(?ims)<title[^>]*>(.*?)</title\s*>') r_title = re.compile(r'(?ims)<title[^>]*>(.*?)</title\s*>')
r_entity = re.compile(r'&[A-Za-z0-9#]+;') 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): def f_title(self, origin, match, args):
""".title <URI> - Return the title of URI.""" """.title <URI> - Return the title of URI."""
uri = match.group(2) 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: if not ':' in uri:
uri = 'http://' + uri uri = 'http://' + uri
@ -74,10 +91,10 @@ def f_title(self, origin, match, args):
self.msg(origin.sender, origin.nick + ": Too many redirects") self.msg(origin.sender, origin.nick + ": Too many redirects")
return return
try: mtype = info['Content-Type'] try: mtype = info['content-type']
except: except:
self.msg(origin.sender, origin.nick + ": Document isn't HTML") err = ": Couldn't get the Content-Type, sorry"
return return self.msg(origin.sender, origin.nick + err)
if not (('/html' in mtype) or ('/xhtml' in mtype)): if not (('/html' in mtype) or ('/xhtml' in mtype)):
self.msg(origin.sender, origin.nick + ": Document isn't HTML") self.msg(origin.sender, origin.nick + ": Document isn't HTML")
return return
@ -119,8 +136,13 @@ def f_title(self, origin, match, args):
title = '[Title is the empty document, "".]' title = '[Title is the empty document, "".]'
self.msg(origin.sender, origin.nick + ': ' + title) self.msg(origin.sender, origin.nick + ': ' + title)
else: self.msg(origin.sender, origin.nick + ': No title found') else: self.msg(origin.sender, origin.nick + ': No title found')
f_title.rule = (['title'], r'(\S+)') f_title.commands = ['title']
f_title.thread = True
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__': if __name__ == '__main__':
print __doc__ print __doc__

View File

@ -40,5 +40,48 @@ def help(phenny, input):
help.rule = ('$nick', r'(?i)help(?:[?!]+)?$') help.rule = ('$nick', r'(?i)help(?:[?!]+)?$')
help.priority = 'low' 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__': if __name__ == '__main__':
print __doc__.strip() print __doc__.strip()

View File

@ -14,6 +14,10 @@ def f_reload(phenny, input):
if not input.admin: return if not input.admin: return
name = input.group(2) name = input.group(2)
if not name:
phenny.setup()
return phenny.reply('done')
try: module = getattr(__import__('modules.' + name), name) try: module = getattr(__import__('modules.' + name), name)
except ImportError: except ImportError:
module = getattr(__import__('opt.' + name), name) module = getattr(__import__('opt.' + name), name)
@ -30,7 +34,8 @@ def f_reload(phenny, input):
phenny.reply('%r (version: %s)' % (module, modified)) phenny.reply('%r (version: %s)' % (module, modified))
f_reload.name = 'reload' 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__': if __name__ == '__main__':
print __doc__.strip() print __doc__.strip()

View File

@ -8,7 +8,7 @@ Licensed under the Eiffel Forum License 2.
http://inamidst.com/phenny/ http://inamidst.com/phenny/
""" """
import re import re, time
import web import web
r_translation = re.compile(r'<div style=padding:10px;>([^<]+)</div>') r_translation = re.compile(r'<div style=padding:10px;>([^<]+)</div>')
@ -43,7 +43,7 @@ def guess_language(phrase):
try: return languages[lang] try: return languages[lang]
except KeyError: except KeyError:
return lang return lang
return 'unknown' return 'Moon Language'
def translate(phrase, lang, target='en'): def translate(phrase, lang, target='en'):
babelfish = 'http://world.altavista.com/tr' babelfish = 'http://world.altavista.com/tr'
@ -68,35 +68,40 @@ def translate(phrase, lang, target='en'):
def tr(phenny, input): def tr(phenny, input):
"""Translates a phrase, with an optional language hint.""" """Translates a phrase, with an optional language hint."""
lang, phrase = input.groups() input, output, phrase = input.groups()
phrase = phrase.encode('utf-8') phrase = phrase.encode('utf-8')
if (len(phrase) > 350) and (not phenny.admin(input.nick)): if (len(phrase) > 350) and (not phenny.admin(input.nick)):
return phenny.reply('Phrase must be under 350 characters.') return phenny.reply('Phrase must be under 350 characters.')
language = guess_language(phrase) input = input or guess_language(phrase)
if language is None: if not input:
return phenny.reply('Unable to guess the language, sorry.') 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': if not ((input == 'en') and (output == 'en')):
translation = translate(phrase, language) translation = translate(phrase, input, output)
if translation is not None: if translation is not None:
translation = translation.decode('utf-8').encode('utf-8') 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." 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 # 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, 'en', other)
phrase = translate(phrase, other, 'en') phrase = translate(phrase, other, 'en')
time.sleep(0.1)
if phrase is not None: if phrase is not None:
return phenny.reply(u'"%s" (en-unmangled)' % phrase) return phenny.reply(u'"%s" (en-unmangled)' % phrase)
return phenny.reply("I think it's English already.") return phenny.reply("I think it's English already.")
# @@ or 'Why but that be English, sire.' # @@ 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.example = '$nickname: "mon chien"? or $nickname: fr "mon chien"?'
tr.priority = 'low' tr.priority = 'low'

View File

@ -55,6 +55,7 @@ def search(term):
else: return term else: return term
def wikipedia(term, last=False): def wikipedia(term, last=False):
global wikiuri
bytes = web.get(wikiuri % urllib.quote(term)) bytes = web.get(wikiuri % urllib.quote(term))
bytes = r_tr.sub('', bytes) bytes = r_tr.sub('', bytes)
@ -83,7 +84,8 @@ def wikipedia(term, last=False):
and not 'disambiguation)"' in para) and not 'disambiguation)"' in para)
and not '(images and media)' in para and not '(images and media)' in para
and not 'This article contains a' 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): for i, para in enumerate(paragraphs):
para = para.replace('<sup>', '|') para = para.replace('<sup>', '|')
@ -119,7 +121,9 @@ def wikipedia(term, last=False):
return None return None
sentence = '"' + sentence.replace('"', "'") + '"' 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): def wik(phenny, input):
origterm = input.groups()[1] origterm = input.groups()[1]

View File

@ -20,13 +20,15 @@ def replaced(phenny, input):
'v': '.v has been replaced by .val', 'v': '.v has been replaced by .val',
'validate': '.validate has been replaced by .validate', 'validate': '.validate has been replaced by .validate',
'thesaurus': ".thesaurus hasn't been ported to my new codebase yet", 'thesaurus': ".thesaurus hasn't been ported to my new codebase yet",
'rate': ".rate hasn't been ported to my new codebase yet, sorry!", 'rates': "moon wanter. moOOoon wanter!",
'rates': ".rates hasn't been ported to my new codebase yet, sorry!" '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] }[command]
phenny.reply(response) phenny.reply(response)
replaced.commands = [ replaced.commands = [
'cp', 'pc', 'unicode', 'compare', 'map', 'acronym', 'img', 'cp', 'pc', 'unicode', 'compare', 'map', 'acronym', 'img',
'v', 'validate', 'thesaurus', 'rate', 'rates' 'v', 'validate', 'thesaurus', 'rates', 'web', 'mangle', 'origin'
] ]
replaced.priority = 'low' replaced.priority = 'low'