Lots of fixes, changes, and new goodies.
parent
7931fab145
commit
2fb0058943
31
bot.py
31
bot.py
|
@ -7,7 +7,7 @@ Licensed under the Eiffel Forum License 2.
|
||||||
http://inamidst.com/phenny/
|
http://inamidst.com/phenny/
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import sys, os, re, time, threading, optparse
|
import sys, os, re, threading, imp
|
||||||
import irc
|
import irc
|
||||||
|
|
||||||
home = os.getcwd()
|
home = os.getcwd()
|
||||||
|
@ -31,20 +31,29 @@ class Phenny(irc.Bot):
|
||||||
def setup(self):
|
def setup(self):
|
||||||
self.variables = {}
|
self.variables = {}
|
||||||
|
|
||||||
|
filenames = []
|
||||||
if not hasattr(self.config, 'enable'):
|
if not hasattr(self.config, 'enable'):
|
||||||
load = [('modules', filename[:-3])
|
for fn in os.listdir(os.path.join(home, 'modules')):
|
||||||
for filename in os.listdir(os.path.join(home, 'modules'))
|
if fn.endswith('.py') and not fn.startswith('_'):
|
||||||
if filename.endswith('.py') and
|
filenames.append(os.path.join(home, 'modules', fn))
|
||||||
not filename.startswith('_') and
|
else:
|
||||||
not filename[:-3] in self.config.disable]
|
for fn in self.config.enable:
|
||||||
else: load = [('modules', e) for e in self.config.enable]
|
filenames.append(os.path.join(home, 'modules', fn + '.py'))
|
||||||
|
# @@ exclude
|
||||||
|
|
||||||
if hasattr(self.config, 'opt'):
|
if hasattr(self.config, 'extra'):
|
||||||
load += [('opt', o) for o in self.config.opt]
|
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 = []
|
modules = []
|
||||||
for package, name in load:
|
for filename in filenames:
|
||||||
try: module = getattr(__import__(package + '.' + name), name)
|
name = os.path.basename(filename)[:-3]
|
||||||
|
try: module = imp.load_source(name, filename)
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
print >> sys.stderr, "Error loading %s: %s" % (name, e)
|
print >> sys.stderr, "Error loading %s: %s" % (name, e)
|
||||||
else:
|
else:
|
||||||
|
|
24
irc.py
24
irc.py
|
@ -41,11 +41,21 @@ class Bot(asynchat.async_chat):
|
||||||
import threading
|
import threading
|
||||||
self.sending = threading.RLock()
|
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:
|
if text is not None:
|
||||||
self.push(' '.join(args) + ' :' + text + '\r\n')
|
self.push(' '.join(args) + ' :' + text + '\r\n')
|
||||||
else: self.push(' '.join(args) + '\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):
|
def run(self, host, port=6667):
|
||||||
self.initiate_connect(host, port)
|
self.initiate_connect(host, port)
|
||||||
|
|
||||||
|
@ -103,6 +113,10 @@ class Bot(asynchat.async_chat):
|
||||||
try: text = text.encode('utf-8')
|
try: text = text.encode('utf-8')
|
||||||
except UnicodeEncodeError, e:
|
except UnicodeEncodeError, e:
|
||||||
text = e.__class__ + ': ' + str(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!
|
# No messages within the last 3 seconds? Go ahead!
|
||||||
# Otherwise, wait so it's been at least 0.8 seconds + penalty
|
# 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)
|
time.sleep(wait - elapsed)
|
||||||
|
|
||||||
# Loop detection
|
# Loop detection
|
||||||
messages = [m[1] for m in self.stack[-5:]]
|
messages = [m[1] for m in self.stack[-8:]]
|
||||||
if messages.count(text) >= 3:
|
if messages.count(text) >= 5:
|
||||||
text = '...'
|
text = '...'
|
||||||
if messages.count('...') >= 1:
|
if messages.count('...') >= 3:
|
||||||
self.sending.release()
|
self.sending.release()
|
||||||
return
|
return
|
||||||
|
|
||||||
self.write(('PRIVMSG', recipient), text)
|
self.__write(('PRIVMSG', recipient), text)
|
||||||
self.stack.append((time.time(), text))
|
self.stack.append((time.time(), text))
|
||||||
self.stack = self.stack[-10:]
|
self.stack = self.stack[-10:]
|
||||||
|
|
||||||
|
|
|
@ -187,7 +187,7 @@ TimeZones.update(TZ1)
|
||||||
@deprecated
|
@deprecated
|
||||||
def f_time(self, origin, match, args):
|
def f_time(self, origin, match, args):
|
||||||
""".t [ <timezone> ] - Returns the current time"""
|
""".t [ <timezone> ] - Returns the current time"""
|
||||||
tz = match.group(1) or 'GMT'
|
tz = match.group(2) or 'GMT'
|
||||||
|
|
||||||
# Personal time zones, because they're rad
|
# Personal time zones, because they're rad
|
||||||
if hasattr(self.config, 'timezones'):
|
if hasattr(self.config, 'timezones'):
|
||||||
|
@ -196,7 +196,7 @@ def f_time(self, origin, match, args):
|
||||||
|
|
||||||
if People.has_key(tz):
|
if People.has_key(tz):
|
||||||
tz = People[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 = People[origin.nick]
|
||||||
|
|
||||||
TZ = tz.upper()
|
TZ = tz.upper()
|
||||||
|
|
|
@ -76,7 +76,7 @@ def etymology(word):
|
||||||
def f_etymology(self, origin, match, args):
|
def f_etymology(self, origin, match, args):
|
||||||
word = match.group(2)
|
word = match.group(2)
|
||||||
|
|
||||||
try: result = etymology(word)
|
try: result = etymology(word.encode('utf-8'))
|
||||||
except IOError:
|
except IOError:
|
||||||
msg = "Can't connect to etymonline.com (%s)" % (etyuri % word)
|
msg = "Can't connect to etymonline.com (%s)" % (etyuri % word)
|
||||||
self.msg(origin.sender, msg)
|
self.msg(origin.sender, msg)
|
||||||
|
|
|
@ -7,7 +7,7 @@ Licensed under the Eiffel Forum License 2.
|
||||||
http://inamidst.com/phenny/
|
http://inamidst.com/phenny/
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import re, urllib
|
import re, urllib, urlparse
|
||||||
from htmlentitydefs import name2codepoint
|
from htmlentitydefs import name2codepoint
|
||||||
import web
|
import web
|
||||||
from tools import deprecated
|
from tools import deprecated
|
||||||
|
@ -66,7 +66,7 @@ def f_title(self, origin, match, args):
|
||||||
status = str(info[1])
|
status = str(info[1])
|
||||||
info = info[0]
|
info = info[0]
|
||||||
if status.startswith('3'):
|
if status.startswith('3'):
|
||||||
uri = info['Location']
|
uri = urlparse.urljoin(uri, info['Location'])
|
||||||
else: break
|
else: break
|
||||||
|
|
||||||
redirects += 1
|
redirects += 1
|
||||||
|
|
|
@ -13,8 +13,10 @@ def f_reload(phenny, input):
|
||||||
"""Reloads a module, for use by admins only."""
|
"""Reloads a module, for use by admins only."""
|
||||||
if not input.admin: return
|
if not input.admin: return
|
||||||
|
|
||||||
name = match.group(2)
|
name = input.group(2)
|
||||||
module = getattr(__import__('modules.' + name), name)
|
try: module = getattr(__import__('modules.' + name), name)
|
||||||
|
except ImportError:
|
||||||
|
module = getattr(__import__('opt.' + name), name)
|
||||||
reload(module)
|
reload(module)
|
||||||
|
|
||||||
if hasattr(module, '__file__'):
|
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))
|
modified = time.strftime('%Y-%m-%d %H:%M:%S', time.gmtime(mtime))
|
||||||
else: modified = 'unknown'
|
else: modified = 'unknown'
|
||||||
|
|
||||||
self.register(vars(module))
|
phenny.register(vars(module))
|
||||||
self.bind_commands()
|
phenny.bind_commands()
|
||||||
|
|
||||||
phenny.reply('%r (version: %s)' % (module, modified))
|
phenny.reply('%r (version: %s)' % (module, modified))
|
||||||
f_reload.name = 'reload'
|
f_reload.name = 'reload'
|
||||||
|
|
|
@ -30,10 +30,14 @@ def search(query, n=1):
|
||||||
|
|
||||||
def result(query):
|
def result(query):
|
||||||
results = search(query)
|
results = search(query)
|
||||||
return results['results'][0]['url']
|
if results['results']:
|
||||||
|
return results['results'][0]['url']
|
||||||
|
return None
|
||||||
|
|
||||||
def count(query):
|
def count(query):
|
||||||
results = search(query)
|
results = search(query)
|
||||||
|
if not results['results']:
|
||||||
|
return '0'
|
||||||
return results['estimatedCount']
|
return results['estimatedCount']
|
||||||
|
|
||||||
def formatnumber(n):
|
def formatnumber(n):
|
||||||
|
@ -44,8 +48,11 @@ def formatnumber(n):
|
||||||
return ''.join(parts)
|
return ''.join(parts)
|
||||||
|
|
||||||
def g(phenny, input):
|
def g(phenny, input):
|
||||||
uri = result(input.group(2))
|
query = input.group(2)
|
||||||
phenny.reply(uri)
|
uri = result(query)
|
||||||
|
if uri:
|
||||||
|
phenny.reply(uri)
|
||||||
|
else: phenny.reply("No results found for '%s'." % query)
|
||||||
g.commands = ['g']
|
g.commands = ['g']
|
||||||
g.priority = 'high'
|
g.priority = 'high'
|
||||||
|
|
||||||
|
@ -60,7 +67,7 @@ r_query = re.compile(
|
||||||
r'\+?"[^"\\]*(?:\\.[^"\\]*)*"|\[[^]\\]*(?:\\.[^]\\]*)*\]|\S+'
|
r'\+?"[^"\\]*(?:\\.[^"\\]*)*"|\[[^]\\]*(?:\\.[^]\\]*)*\]|\S+'
|
||||||
)
|
)
|
||||||
|
|
||||||
def compare(phenny, input):
|
def gcs(phenny, input):
|
||||||
queries = r_query.findall(input.group(2))
|
queries = r_query.findall(input.group(2))
|
||||||
if len(queries) > 6:
|
if len(queries) > 6:
|
||||||
return phenny.reply('Sorry, can only compare up to six things.')
|
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))]
|
results = [(term, n) for (n, term) in reversed(sorted(results))]
|
||||||
reply = ', '.join('%s (%s)' % (t, formatnumber(n)) for (t, n) in results)
|
reply = ', '.join('%s (%s)' % (t, formatnumber(n)) for (t, n) in results)
|
||||||
phenny.say(reply)
|
phenny.say(reply)
|
||||||
compare.commands = ['gco', 'comp']
|
gcs.commands = ['gcs']
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
print __doc__.strip()
|
print __doc__.strip()
|
||||||
|
|
|
@ -59,6 +59,10 @@ def f_remind(phenny, input):
|
||||||
|
|
||||||
# @@ Multiple comma-separated tellees? Cf. Terje, #swhack, 2006-04-15
|
# @@ Multiple comma-separated tellees? Cf. Terje, #swhack, 2006-04-15
|
||||||
verb, tellee, msg = input.groups()
|
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_original = tellee.rstrip(',:;')
|
||||||
tellee = tellee.lower()
|
tellee = tellee.lower()
|
||||||
|
|
||||||
|
|
|
@ -67,21 +67,24 @@ def translate(phrase, lang, target='en'):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def tr(phenny, input):
|
def tr(phenny, input):
|
||||||
|
"""Translates a phrase, with an optional language hint."""
|
||||||
lang, phrase = input.groups()
|
lang, phrase = input.groups()
|
||||||
|
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)
|
language = guess_language(phrase)
|
||||||
if language is None:
|
if language is None:
|
||||||
return phenny.reply('Unable to guess the language, sorry.')
|
return phenny.reply('Unable to guess the language, sorry.')
|
||||||
|
else: language = lang.encode('utf-8')
|
||||||
|
|
||||||
if language != 'en':
|
if language != 'en':
|
||||||
translation = translate(phrase, language)
|
translation = translate(phrase, language)
|
||||||
if translation is not None:
|
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())
|
return phenny.reply(error % language.title())
|
||||||
|
|
||||||
# Otherwise, it's English, so mangle it for fun
|
# 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(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.doc = ('phenny: "<phrase>"? or phenny: <lang> "<phrase>"?',
|
|
||||||
'Translate <phrase>, optionally forcing the <lang> interpretation.')
|
|
||||||
tr.rule = ('$nick', ur'(?:([a-z]{2}) +)?["“](.+?)["”]\? *$')
|
tr.rule = ('$nick', ur'(?:([a-z]{2}) +)?["“](.+?)["”]\? *$')
|
||||||
|
tr.example = '$nickname: "mon chien"? or $nickname: fr "mon chien"?'
|
||||||
tr.priority = 'low'
|
tr.priority = 'low'
|
||||||
|
|
||||||
|
# @@ mangle
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
print __doc__.strip()
|
print __doc__.strip()
|
||||||
|
|
|
@ -26,7 +26,7 @@ def json(text):
|
||||||
raise ValueError('Input must be serialised JSON.')
|
raise ValueError('Input must be serialised JSON.')
|
||||||
|
|
||||||
def location(name):
|
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
|
uri = 'http://ws.geonames.org/searchJSON?q=%s&maxRows=1' % name
|
||||||
for i in xrange(10):
|
for i in xrange(10):
|
||||||
u = urllib.urlopen(uri)
|
u = urllib.urlopen(uri)
|
||||||
|
|
|
@ -123,6 +123,8 @@ def wikipedia(term, last=False):
|
||||||
|
|
||||||
def wik(phenny, input):
|
def wik(phenny, input):
|
||||||
origterm = input.groups()[1]
|
origterm = input.groups()[1]
|
||||||
|
origterm = origterm.encode('utf-8')
|
||||||
|
|
||||||
term = urllib.unquote(origterm)
|
term = urllib.unquote(origterm)
|
||||||
if not term:
|
if not term:
|
||||||
return phenny.say(origin.sender, 'Maybe you meant ".wik Zen"?')
|
return phenny.say(origin.sender, 'Maybe you meant ".wik Zen"?')
|
||||||
|
|
|
@ -12,10 +12,22 @@ def replaced(phenny, input):
|
||||||
response = {
|
response = {
|
||||||
'cp': '.cp has been replaced by .u',
|
'cp': '.cp has been replaced by .u',
|
||||||
'pc': '.pc 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]
|
}[command]
|
||||||
phenny.reply(response)
|
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'
|
replaced.priority = 'low'
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|
4
phenny
4
phenny
|
@ -29,7 +29,7 @@ def create_default_config(fn):
|
||||||
# These are people who will be able to use admin.py's functions...
|
# These are people who will be able to use admin.py's functions...
|
||||||
admins = [owner, 'someoneyoutrust']
|
admins = [owner, 'someoneyoutrust']
|
||||||
# But admin.py is disabled by default, as follows:
|
# 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
|
# If you want to enumerate a list of modules rather than disabling
|
||||||
# some, use "enable = ['example']", which takes precedent over disable
|
# some, use "enable = ['example']", which takes precedent over disable
|
||||||
|
@ -37,7 +37,7 @@ def create_default_config(fn):
|
||||||
# enable = []
|
# enable = []
|
||||||
|
|
||||||
# Modules to load from the opt directory
|
# Modules to load from the opt directory
|
||||||
opt = []
|
extra = []
|
||||||
|
|
||||||
# EOF
|
# EOF
|
||||||
""")
|
""")
|
||||||
|
|
Loading…
Reference in New Issue