Phenny2, now being tested on Freenode as the main phenny.
commit
7931fab145
|
@ -0,0 +1,35 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
"""
|
||||||
|
__init__.py - Phenny Init Module
|
||||||
|
Copyright 2008, Sean B. Palmer, inamidst.com
|
||||||
|
Licensed under the Eiffel Forum License 2.
|
||||||
|
|
||||||
|
http://inamidst.com/phenny/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys, time, threading
|
||||||
|
import bot
|
||||||
|
|
||||||
|
def run_phenny(config):
|
||||||
|
if hasattr(config, 'delay'):
|
||||||
|
delay = config.delay
|
||||||
|
else: delay = 20
|
||||||
|
|
||||||
|
def connect(config):
|
||||||
|
p = bot.Phenny(config)
|
||||||
|
p.run(config.host)
|
||||||
|
|
||||||
|
while True:
|
||||||
|
connect(config)
|
||||||
|
if not isinstance(delay, int): break
|
||||||
|
|
||||||
|
warning = 'Warning: Disconnected. Reconnecting in %s seconds...' % delay
|
||||||
|
print >> sys.stderr, warning
|
||||||
|
time.sleep(delay)
|
||||||
|
|
||||||
|
def run(config):
|
||||||
|
t = threading.Thread(target=run_phenny, args=(config,))
|
||||||
|
t.start()
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
print __doc__
|
|
@ -0,0 +1,202 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
"""
|
||||||
|
bot.py - Phenny IRC Bot
|
||||||
|
Copyright 2008, Sean B. Palmer, inamidst.com
|
||||||
|
Licensed under the Eiffel Forum License 2.
|
||||||
|
|
||||||
|
http://inamidst.com/phenny/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys, os, re, time, threading, optparse
|
||||||
|
import irc
|
||||||
|
|
||||||
|
home = os.getcwd()
|
||||||
|
|
||||||
|
def decode(bytes):
|
||||||
|
try: text = bytes.decode('utf-8')
|
||||||
|
except UnicodeDecodeError:
|
||||||
|
try: text = bytes.decode('iso-8859-1')
|
||||||
|
except UnicodeDecodeError:
|
||||||
|
text = bytes.decode('cp1252')
|
||||||
|
return text
|
||||||
|
|
||||||
|
class Phenny(irc.Bot):
|
||||||
|
def __init__(self, config):
|
||||||
|
irc.Bot.__init__(self, config.nick, config.name, config.channels)
|
||||||
|
self.config = config
|
||||||
|
self.doc = {}
|
||||||
|
self.stats = {}
|
||||||
|
self.setup()
|
||||||
|
|
||||||
|
def setup(self):
|
||||||
|
self.variables = {}
|
||||||
|
|
||||||
|
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]
|
||||||
|
|
||||||
|
if hasattr(self.config, 'opt'):
|
||||||
|
load += [('opt', o) for o in self.config.opt]
|
||||||
|
|
||||||
|
modules = []
|
||||||
|
for package, name in load:
|
||||||
|
try: module = getattr(__import__(package + '.' + name), name)
|
||||||
|
except Exception, e:
|
||||||
|
print >> sys.stderr, "Error loading %s: %s" % (name, e)
|
||||||
|
else:
|
||||||
|
if hasattr(module, 'setup'):
|
||||||
|
module.setup(self)
|
||||||
|
self.register(vars(module))
|
||||||
|
modules.append(name)
|
||||||
|
|
||||||
|
if modules:
|
||||||
|
print >> sys.stderr, 'Registered modules:', ', '.join(modules)
|
||||||
|
else: print >> sys.stderr, "Warning: Couldn't find any modules"
|
||||||
|
|
||||||
|
self.bind_commands()
|
||||||
|
|
||||||
|
def register(self, variables):
|
||||||
|
# This is used by reload.py, hence it being methodised
|
||||||
|
for name, obj in variables.iteritems():
|
||||||
|
if hasattr(obj, 'commands') or hasattr(obj, 'rule'):
|
||||||
|
self.variables[name] = obj
|
||||||
|
|
||||||
|
def bind_commands(self):
|
||||||
|
self.commands = {'high': {}, 'medium': {}, 'low': {}}
|
||||||
|
|
||||||
|
def bind(self, priority, regexp, func):
|
||||||
|
print priority, regexp.pattern.encode('utf-8'), func
|
||||||
|
self.commands[priority].setdefault(regexp, []).append(func)
|
||||||
|
# @@ register documentation
|
||||||
|
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)
|
||||||
|
|
||||||
|
def sub(pattern, self=self):
|
||||||
|
# These replacements have significant order
|
||||||
|
pattern = pattern.replace('$nickname', self.nick)
|
||||||
|
return pattern.replace('$nick', r'%s[,:] +' % self.nick)
|
||||||
|
|
||||||
|
for name, func in self.variables.iteritems():
|
||||||
|
# print name, func
|
||||||
|
if not hasattr(func, 'priority'):
|
||||||
|
func.priority = 'medium'
|
||||||
|
|
||||||
|
if not hasattr(func, 'thread'):
|
||||||
|
func.thread = True
|
||||||
|
|
||||||
|
if not hasattr(func, 'event'):
|
||||||
|
func.event = 'PRIVMSG'
|
||||||
|
else: func.event = func.event.upper()
|
||||||
|
|
||||||
|
if hasattr(func, 'rule'):
|
||||||
|
if isinstance(func.rule, str):
|
||||||
|
pattern = sub(func.rule)
|
||||||
|
regexp = re.compile(pattern)
|
||||||
|
bind(self, func.priority, regexp, func)
|
||||||
|
|
||||||
|
if isinstance(func.rule, tuple):
|
||||||
|
# 1) e.g. ('$nick', '(.*)')
|
||||||
|
if len(func.rule) == 2 and isinstance(func.rule[0], str):
|
||||||
|
prefix, pattern = func.rule
|
||||||
|
prefix = sub(prefix)
|
||||||
|
regexp = re.compile(prefix + pattern)
|
||||||
|
bind(self, func.priority, regexp, func)
|
||||||
|
|
||||||
|
# 2) e.g. (['p', 'q'], '(.*)')
|
||||||
|
elif len(func.rule) == 2 and isinstance(func.rule[0], list):
|
||||||
|
prefix = self.config.prefix
|
||||||
|
commands, pattern = func.rule
|
||||||
|
for command in commands:
|
||||||
|
command = r'(%s) +' % command
|
||||||
|
regexp = re.compile(prefix + command + pattern)
|
||||||
|
bind(self, func.priority, regexp, func)
|
||||||
|
|
||||||
|
# 3) e.g. ('$nick', ['p', 'q'], '(.*)')
|
||||||
|
elif len(func.rule) == 3:
|
||||||
|
prefix, commands, pattern = func.rule
|
||||||
|
prefix = sub(prefix)
|
||||||
|
for command in commands:
|
||||||
|
command = r'(%s) +' % command
|
||||||
|
regexp = re.compile(prefix + command + pattern)
|
||||||
|
bind(self, func.priority, regexp, func)
|
||||||
|
|
||||||
|
if hasattr(func, 'commands'):
|
||||||
|
for command in func.commands:
|
||||||
|
template = r'^%s(%s)(?: +(.*))?$'
|
||||||
|
pattern = template % (self.config.prefix, command)
|
||||||
|
regexp = re.compile(pattern)
|
||||||
|
bind(self, func.priority, regexp, func)
|
||||||
|
|
||||||
|
def wrapped(self, origin, text, match):
|
||||||
|
class PhennyWrapper(object):
|
||||||
|
def __init__(self, phenny):
|
||||||
|
self.bot = phenny
|
||||||
|
|
||||||
|
def __getattr__(self, attr):
|
||||||
|
if attr == 'reply':
|
||||||
|
return (lambda msg:
|
||||||
|
self.bot.msg(origin.sender, origin.nick + ': ' + msg))
|
||||||
|
elif attr == 'say':
|
||||||
|
return lambda msg: self.bot.msg(origin.sender, msg)
|
||||||
|
return getattr(self.bot, attr)
|
||||||
|
|
||||||
|
return PhennyWrapper(self)
|
||||||
|
|
||||||
|
def input(self, origin, text, bytes, match, event):
|
||||||
|
class CommandInput(unicode):
|
||||||
|
def __new__(cls, text, origin, bytes, match, event):
|
||||||
|
s = unicode.__new__(cls, text)
|
||||||
|
s.sender = origin.sender
|
||||||
|
s.nick = origin.nick
|
||||||
|
s.event = event
|
||||||
|
s.bytes = bytes
|
||||||
|
s.match = match
|
||||||
|
s.group = match.group
|
||||||
|
s.groups = match.groups
|
||||||
|
s.admin = origin.nick in self.config.admins
|
||||||
|
s.owner = origin.nick == self.config.owner
|
||||||
|
return s
|
||||||
|
|
||||||
|
return CommandInput(text, origin, bytes, match, event)
|
||||||
|
|
||||||
|
def call(self, func, origin, phenny, input):
|
||||||
|
try: func(phenny, input)
|
||||||
|
except Exception, e:
|
||||||
|
self.error(origin)
|
||||||
|
|
||||||
|
def dispatch(self, origin, args):
|
||||||
|
bytes, event = args[0], args[1]
|
||||||
|
text = decode(bytes)
|
||||||
|
|
||||||
|
for priority in ('high', 'medium', 'low'):
|
||||||
|
items = self.commands[priority].items()
|
||||||
|
for regexp, funcs in items:
|
||||||
|
for func in funcs:
|
||||||
|
if event != func.event: continue
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
if func.thread:
|
||||||
|
args = (func, origin, phenny, input)
|
||||||
|
t = threading.Thread(target=self.call, args=args)
|
||||||
|
t.start()
|
||||||
|
else: self.call(func, origin, phenny, input)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
print __doc__
|
|
@ -0,0 +1,54 @@
|
||||||
|
|
||||||
|
<!-- Processed by Id: cwm.py,v 1.164 2004/10/28 17:41:59 timbl Exp -->
|
||||||
|
|
||||||
|
|
||||||
|
<rdf:RDF xmlns="http://xmlns.com/foaf/0.1/"
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:foaf="http://xmlns.com/foaf/0.1/"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#">
|
||||||
|
|
||||||
|
<PersonalProfileDocument rdf:about="http://inamidst.com/phenny/foaf">
|
||||||
|
<dc:title>Information about phenny</dc:title>
|
||||||
|
<maker rdf:resource="http://inamidst.com/phenny/foaf#sbp"/>
|
||||||
|
</PersonalProfileDocument>
|
||||||
|
|
||||||
|
<Agent rdf:about="http://inamidst.com/phenny/foaf#phenny">
|
||||||
|
<homepage rdf:resource="http://inamidst.com/phenny/"/>
|
||||||
|
<name>Phenny P. Palmersbot</name>
|
||||||
|
<nick>phenny</nick>
|
||||||
|
<title>Ms.</title>
|
||||||
|
</Agent>
|
||||||
|
|
||||||
|
<Person rdf:about="http://inamidst.com/phenny/foaf#sbp">
|
||||||
|
<rdfs:seeAlso rdf:resource="http://inamidst.com/sbp/foaf"/>
|
||||||
|
<homepage rdf:resource="http://inamidst.com/sbp/"/>
|
||||||
|
<name>Sean B. Palmer</name>
|
||||||
|
</Person>
|
||||||
|
</rdf:RDF>
|
||||||
|
|
||||||
|
<!-- Notation3 Source:
|
||||||
|
|
||||||
|
@prefix : <http://inamidst.com/phenny/foaf#> .
|
||||||
|
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
|
||||||
|
@prefix foaf: <http://xmlns.com/foaf/0.1/> .
|
||||||
|
@prefix dc: <http://purl.org/dc/elements/1.1/> .
|
||||||
|
@keywords a .
|
||||||
|
|
||||||
|
<http://inamidst.com/phenny/foaf> a foaf:PersonalProfileDocument;
|
||||||
|
foaf:maker sbp;
|
||||||
|
dc:title "Information about phenny" .
|
||||||
|
|
||||||
|
phenny a foaf:Agent;
|
||||||
|
foaf:name "Phenny P. Palmersbot";
|
||||||
|
foaf:title "Ms.";
|
||||||
|
foaf:homepage <http://inamidst.com/phenny/>;
|
||||||
|
# foaf:knows sbp;
|
||||||
|
foaf:nick "phenny" .
|
||||||
|
|
||||||
|
sbp a foaf:Person;
|
||||||
|
foaf:name "Sean B. Palmer";
|
||||||
|
foaf:homepage <http://inamidst.com/sbp/>;
|
||||||
|
rdfs:seeAlso <http://inamidst.com/sbp/foaf> .
|
||||||
|
|
||||||
|
-->
|
|
@ -0,0 +1,168 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
"""
|
||||||
|
irc.py - A Utility IRC Bot
|
||||||
|
Copyright 2008, Sean B. Palmer, inamidst.com
|
||||||
|
Licensed under the Eiffel Forum License 2.
|
||||||
|
|
||||||
|
http://inamidst.com/phenny/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys, re, time, traceback
|
||||||
|
import socket, asyncore, asynchat
|
||||||
|
|
||||||
|
class Origin(object):
|
||||||
|
source = re.compile(r'([^!]*)!?([^@]*)@?(.*)')
|
||||||
|
|
||||||
|
def __init__(self, bot, source, args):
|
||||||
|
match = Origin.source.match(source or '')
|
||||||
|
self.nick, self.user, self.host = match.groups()
|
||||||
|
|
||||||
|
if len(args) > 1:
|
||||||
|
target = args[1]
|
||||||
|
else: target = None
|
||||||
|
|
||||||
|
mappings = {bot.nick: self.nick, None: None}
|
||||||
|
self.sender = mappings.get(target, target)
|
||||||
|
|
||||||
|
class Bot(asynchat.async_chat):
|
||||||
|
def __init__(self, nick, name, channels):
|
||||||
|
asynchat.async_chat.__init__(self)
|
||||||
|
self.set_terminator('\n')
|
||||||
|
self.buffer = ''
|
||||||
|
|
||||||
|
self.nick = nick
|
||||||
|
self.user = nick
|
||||||
|
self.name = name
|
||||||
|
|
||||||
|
self.verbose = True
|
||||||
|
self.channels = channels or []
|
||||||
|
self.stack = []
|
||||||
|
|
||||||
|
import threading
|
||||||
|
self.sending = threading.RLock()
|
||||||
|
|
||||||
|
def write(self, args, text=None):
|
||||||
|
if text is not None:
|
||||||
|
self.push(' '.join(args) + ' :' + text + '\r\n')
|
||||||
|
else: self.push(' '.join(args) + '\r\n')
|
||||||
|
|
||||||
|
def run(self, host, port=6667):
|
||||||
|
self.initiate_connect(host, port)
|
||||||
|
|
||||||
|
def initiate_connect(self, host, port):
|
||||||
|
if self.verbose:
|
||||||
|
message = 'Connecting to %s:%s...' % (host, port)
|
||||||
|
print >> sys.stderr, message,
|
||||||
|
self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
self.connect((host, port))
|
||||||
|
asyncore.loop()
|
||||||
|
|
||||||
|
def handle_connect(self):
|
||||||
|
if self.verbose:
|
||||||
|
print >> sys.stderr, 'connected!'
|
||||||
|
self.write(('NICK', self.nick))
|
||||||
|
self.write(('USER', self.user, '+iw', self.nick), self.name)
|
||||||
|
|
||||||
|
def handle_close(self):
|
||||||
|
self.close()
|
||||||
|
print >> sys.stderr, 'Closed!'
|
||||||
|
|
||||||
|
def collect_incoming_data(self, data):
|
||||||
|
self.buffer += data
|
||||||
|
|
||||||
|
def found_terminator(self):
|
||||||
|
line = self.buffer
|
||||||
|
if line.endswith('\r'):
|
||||||
|
line = line[:-1]
|
||||||
|
self.buffer = ''
|
||||||
|
|
||||||
|
# print line
|
||||||
|
if line.startswith(':'):
|
||||||
|
source, line = line[1:].split(' ', 1)
|
||||||
|
else: source = None
|
||||||
|
|
||||||
|
if ' :' in line:
|
||||||
|
argstr, text = line.split(' :', 1)
|
||||||
|
else: argstr, text = line, ''
|
||||||
|
args = argstr.split()
|
||||||
|
|
||||||
|
origin = Origin(self, source, args)
|
||||||
|
self.dispatch(origin, tuple([text] + args))
|
||||||
|
|
||||||
|
if args[0] == 'PING':
|
||||||
|
self.write(('PONG', text))
|
||||||
|
|
||||||
|
def dispatch(self, origin, args):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def msg(self, recipient, text):
|
||||||
|
self.sending.acquire()
|
||||||
|
|
||||||
|
# Cf. http://swhack.com/logs/2006-03-01#T19-43-25
|
||||||
|
if isinstance(text, unicode):
|
||||||
|
try: text = text.encode('utf-8')
|
||||||
|
except UnicodeEncodeError, e:
|
||||||
|
text = e.__class__ + ': ' + str(e)
|
||||||
|
|
||||||
|
# No messages within the last 3 seconds? Go ahead!
|
||||||
|
# Otherwise, wait so it's been at least 0.8 seconds + penalty
|
||||||
|
if self.stack:
|
||||||
|
elapsed = time.time() - self.stack[-1][0]
|
||||||
|
if elapsed < 3:
|
||||||
|
penalty = float(max(0, len(text) - 50)) / 70
|
||||||
|
wait = 0.8 + penalty
|
||||||
|
if elapsed < wait:
|
||||||
|
time.sleep(wait - elapsed)
|
||||||
|
|
||||||
|
# Loop detection
|
||||||
|
messages = [m[1] for m in self.stack[-5:]]
|
||||||
|
if messages.count(text) >= 3:
|
||||||
|
text = '...'
|
||||||
|
if messages.count('...') >= 1:
|
||||||
|
self.sending.release()
|
||||||
|
return
|
||||||
|
|
||||||
|
self.write(('PRIVMSG', recipient), text)
|
||||||
|
self.stack.append((time.time(), text))
|
||||||
|
self.stack = self.stack[-10:]
|
||||||
|
|
||||||
|
self.sending.release()
|
||||||
|
|
||||||
|
def notice(self, dest, text):
|
||||||
|
self.write(('NOTICE', dest), text)
|
||||||
|
|
||||||
|
def error(self, origin):
|
||||||
|
try:
|
||||||
|
import traceback
|
||||||
|
trace = traceback.format_exc()
|
||||||
|
print trace
|
||||||
|
lines = list(reversed(trace.splitlines()))
|
||||||
|
|
||||||
|
report = [lines[0].strip()]
|
||||||
|
for line in lines:
|
||||||
|
line = line.strip()
|
||||||
|
if line.startswith('File "/'):
|
||||||
|
report.append(line[0].lower() + line[1:])
|
||||||
|
break
|
||||||
|
else: report.append('source unknown')
|
||||||
|
|
||||||
|
self.msg(origin.sender, report[0] + ' (' + report[1] + ')')
|
||||||
|
except: self.msg(origin.sender, "Got an error.")
|
||||||
|
|
||||||
|
class TestBot(Bot):
|
||||||
|
def f_ping(self, origin, match, args):
|
||||||
|
delay = m.group(1)
|
||||||
|
if delay is not None:
|
||||||
|
import time
|
||||||
|
time.sleep(int(delay))
|
||||||
|
self.msg(origin.sender, 'pong (%s)' % delay)
|
||||||
|
else: self.msg(origin.sender, 'pong')
|
||||||
|
f_ping.rule = r'^\.ping(?:[ \t]+(\d+))?$'
|
||||||
|
|
||||||
|
def main():
|
||||||
|
# bot = TestBot('testbot', ['#d8uv.com'])
|
||||||
|
# bot.run('irc.freenode.net')
|
||||||
|
print __doc__
|
||||||
|
|
||||||
|
if __name__=="__main__":
|
||||||
|
main()
|
|
@ -0,0 +1,53 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
"""
|
||||||
|
admin.py - Phenny Admin Module
|
||||||
|
Copyright 2008, Sean B. Palmer, inamidst.com
|
||||||
|
Licensed under the Eiffel Forum License 2.
|
||||||
|
|
||||||
|
http://inamidst.com/phenny/
|
||||||
|
"""
|
||||||
|
|
||||||
|
def join(phenny, input):
|
||||||
|
# Can only be done in privmsg by an admin
|
||||||
|
if input.sender.startswith('#'): return
|
||||||
|
if input.admin:
|
||||||
|
phenny.write(['JOIN'], input.group(2))
|
||||||
|
join.commands = ['join']
|
||||||
|
join.priority = 'low'
|
||||||
|
|
||||||
|
def part(phenny, input):
|
||||||
|
# Can only be done in privmsg by an admin
|
||||||
|
if input.sender.startswith('#'): return
|
||||||
|
if input.admin:
|
||||||
|
phenny.write(['PART'], input.group(2))
|
||||||
|
part.commands = ['part']
|
||||||
|
part.priority = 'low'
|
||||||
|
|
||||||
|
def quit(phenny, input):
|
||||||
|
# Can only be done in privmsg by the owner
|
||||||
|
if input.sender.startswith('#'): return
|
||||||
|
if input.owner:
|
||||||
|
phenny.write(['QUIT'])
|
||||||
|
__import__('os')._exit(0)
|
||||||
|
quit.commands = ['quit']
|
||||||
|
quit.priority = 'low'
|
||||||
|
|
||||||
|
def msg(phenny, input):
|
||||||
|
# Can only be done in privmsg by an admin
|
||||||
|
if input.sender.startswith('#'): return
|
||||||
|
if input.admin:
|
||||||
|
phenny.msg(input.group(2), input.group(3))
|
||||||
|
msg.rule = (['msg'], r'(#\S+) (.*)')
|
||||||
|
msg.priority = 'low'
|
||||||
|
|
||||||
|
def me(phenny, input):
|
||||||
|
# Can only be done in privmsg by an admin
|
||||||
|
if input.sender.startswith('#'): return
|
||||||
|
if input.admin:
|
||||||
|
msg = '\x01ACTION %s\x01' % input.group(3)
|
||||||
|
phenny.msg(input.group(2), msg)
|
||||||
|
me.rule = (['me'], r'(#\S+) (.*)')
|
||||||
|
me.priority = 'low'
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
print __doc__.strip()
|
|
@ -0,0 +1,266 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
"""
|
||||||
|
clock.py - Phenny Clock Module
|
||||||
|
Copyright 2008, Sean B. Palmer, inamidst.com
|
||||||
|
Licensed under the Eiffel Forum License 2.
|
||||||
|
|
||||||
|
http://inamidst.com/phenny/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import math, time, urllib
|
||||||
|
from tools import deprecated
|
||||||
|
|
||||||
|
TimeZones = {'KST': 9, 'CADT': 10.5, 'EETDST': 3, 'MESZ': 2, 'WADT': 9,
|
||||||
|
'EET': 2, 'MST': -7, 'WAST': 8, 'IST': 5.5, 'B': 2,
|
||||||
|
'MSK': 3, 'X': -11, 'MSD': 4, 'CETDST': 2, 'AST': -4,
|
||||||
|
'HKT': 8, 'JST': 9, 'CAST': 9.5, 'CET': 1, 'CEST': 2,
|
||||||
|
'EEST': 3, 'EAST': 10, 'METDST': 2, 'MDT': -6, 'A': 1,
|
||||||
|
'UTC': 0, 'ADT': -3, 'EST': -5, 'E': 5, 'D': 4, 'G': 7,
|
||||||
|
'F': 6, 'I': 9, 'H': 8, 'K': 10, 'PDT': -7, 'M': 12,
|
||||||
|
'L': 11, 'O': -2, 'MEST': 2, 'Q': -4, 'P': -3, 'S': -6,
|
||||||
|
'R': -5, 'U': -8, 'T': -7, 'W': -10, 'WET': 0, 'Y': -12,
|
||||||
|
'CST': -6, 'EADT': 11, 'Z': 0, 'GMT': 0, 'WETDST': 1,
|
||||||
|
'C': 3, 'WEST': 1, 'CDT': -5, 'MET': 1, 'N': -1, 'V': -9,
|
||||||
|
'EDT': -4, 'UT': 0, 'PST': -8, 'MEZ': 1, 'BST': 1,
|
||||||
|
'ACS': 9.5, 'ATL': -4, 'ALA': -9, 'HAW': -10, 'AKDT': -8,
|
||||||
|
'AKST': -9,
|
||||||
|
'BDST': 2}
|
||||||
|
|
||||||
|
TZ1 = {
|
||||||
|
'NDT': -2.5,
|
||||||
|
'BRST': -2,
|
||||||
|
'ADT': -3,
|
||||||
|
'EDT': -4,
|
||||||
|
'CDT': -5,
|
||||||
|
'MDT': -6,
|
||||||
|
'PDT': -7,
|
||||||
|
'YDT': -8,
|
||||||
|
'HDT': -9,
|
||||||
|
'BST': 1,
|
||||||
|
'MEST': 2,
|
||||||
|
'SST': 2,
|
||||||
|
'FST': 2,
|
||||||
|
'CEST': 2,
|
||||||
|
'EEST': 3,
|
||||||
|
'WADT': 8,
|
||||||
|
'KDT': 10,
|
||||||
|
'EADT': 13,
|
||||||
|
'NZD': 13,
|
||||||
|
'NZDT': 13,
|
||||||
|
'GMT': 0,
|
||||||
|
'UT': 0,
|
||||||
|
'UTC': 0,
|
||||||
|
'WET': 0,
|
||||||
|
'WAT': -1,
|
||||||
|
'AT': -2,
|
||||||
|
'FNT': -2,
|
||||||
|
'BRT': -3,
|
||||||
|
'MNT': -4,
|
||||||
|
'EWT': -4,
|
||||||
|
'AST': -4,
|
||||||
|
'EST': -5,
|
||||||
|
'ACT': -5,
|
||||||
|
'CST': -6,
|
||||||
|
'MST': -7,
|
||||||
|
'PST': -8,
|
||||||
|
'YST': -9,
|
||||||
|
'HST': -10,
|
||||||
|
'CAT': -10,
|
||||||
|
'AHST': -10,
|
||||||
|
'NT': -11,
|
||||||
|
'IDLW': -12,
|
||||||
|
'CET': 1,
|
||||||
|
'MEZ': 1,
|
||||||
|
'ECT': 1,
|
||||||
|
'MET': 1,
|
||||||
|
'MEWT': 1,
|
||||||
|
'SWT': 1,
|
||||||
|
'SET': 1,
|
||||||
|
'FWT': 1,
|
||||||
|
'EET': 2,
|
||||||
|
'UKR': 2,
|
||||||
|
'BT': 3,
|
||||||
|
'ZP4': 4,
|
||||||
|
'ZP5': 5,
|
||||||
|
'ZP6': 6,
|
||||||
|
'WST': 8,
|
||||||
|
'HKT': 8,
|
||||||
|
'CCT': 8,
|
||||||
|
'JST': 9,
|
||||||
|
'KST': 9,
|
||||||
|
'EAST': 10,
|
||||||
|
'GST': 10,
|
||||||
|
'NZT': 12,
|
||||||
|
'NZST': 12,
|
||||||
|
'IDLE': 12
|
||||||
|
}
|
||||||
|
|
||||||
|
TZ2 = {
|
||||||
|
'ACDT': -10.5,
|
||||||
|
'ACST': -9.5,
|
||||||
|
'ADT': 3,
|
||||||
|
'AEDT': 11, # hmm
|
||||||
|
'AEST': 10, # hmm
|
||||||
|
'AHDT': 9,
|
||||||
|
'AHST': 10,
|
||||||
|
'AST': 4,
|
||||||
|
'AT': 2,
|
||||||
|
'AWDT': -9,
|
||||||
|
'AWST': -8,
|
||||||
|
'BAT': -3,
|
||||||
|
'BDST': -2,
|
||||||
|
'BET': 11,
|
||||||
|
'BST': -1,
|
||||||
|
'BT': -3,
|
||||||
|
'BZT2': 3,
|
||||||
|
'CADT': -10.5,
|
||||||
|
'CAST': -9.5,
|
||||||
|
'CAT': 10,
|
||||||
|
'CCT': -8,
|
||||||
|
# 'CDT': 5,
|
||||||
|
'CED': -2,
|
||||||
|
'CET': -1,
|
||||||
|
'CST': 6,
|
||||||
|
'EAST': -10,
|
||||||
|
# 'EDT': 4,
|
||||||
|
'EED': -3,
|
||||||
|
'EET': -2,
|
||||||
|
'EEST': -3,
|
||||||
|
'EST': 5,
|
||||||
|
'FST': -2,
|
||||||
|
'FWT': -1,
|
||||||
|
'GMT': 0,
|
||||||
|
'GST': -10,
|
||||||
|
'HDT': 9,
|
||||||
|
'HST': 10,
|
||||||
|
'IDLE': -12,
|
||||||
|
'IDLW': 12,
|
||||||
|
'IST': -5.5,
|
||||||
|
'IT': -3.5,
|
||||||
|
'JST': -9,
|
||||||
|
'JT': -7,
|
||||||
|
'KST': -9,
|
||||||
|
'MDT': 6,
|
||||||
|
'MED': -2,
|
||||||
|
'MET': -1,
|
||||||
|
'MEST': -2,
|
||||||
|
'MEWT': -1,
|
||||||
|
'MST': 7,
|
||||||
|
'MT': -8,
|
||||||
|
'NDT': 2.5,
|
||||||
|
'NFT': 3.5,
|
||||||
|
'NT': 11,
|
||||||
|
'NST': -6.5,
|
||||||
|
'NZ': -11,
|
||||||
|
'NZST': -12,
|
||||||
|
'NZDT': -13,
|
||||||
|
'NZT': -12,
|
||||||
|
# 'PDT': 7,
|
||||||
|
'PST': 8,
|
||||||
|
'ROK': -9,
|
||||||
|
'SAD': -10,
|
||||||
|
'SAST': -9,
|
||||||
|
'SAT': -9,
|
||||||
|
'SDT': -10,
|
||||||
|
'SST': -2,
|
||||||
|
'SWT': -1,
|
||||||
|
'USZ3': -4,
|
||||||
|
'USZ4': -5,
|
||||||
|
'USZ5': -6,
|
||||||
|
'USZ6': -7,
|
||||||
|
'UT': 0,
|
||||||
|
'UTC': 0,
|
||||||
|
'UZ10': -11,
|
||||||
|
'WAT': 1,
|
||||||
|
'WET': 0,
|
||||||
|
'WST': -8,
|
||||||
|
'YDT': 8,
|
||||||
|
'YST': 9,
|
||||||
|
'ZP4': -4,
|
||||||
|
'ZP5': -5,
|
||||||
|
'ZP6': -6
|
||||||
|
}
|
||||||
|
|
||||||
|
TimeZones.update(TZ2)
|
||||||
|
TimeZones.update(TZ1)
|
||||||
|
|
||||||
|
@deprecated
|
||||||
|
def f_time(self, origin, match, args):
|
||||||
|
""".t [ <timezone> ] - Returns the current time"""
|
||||||
|
tz = match.group(1) or 'GMT'
|
||||||
|
|
||||||
|
# Personal time zones, because they're rad
|
||||||
|
if hasattr(self.config, 'timezones'):
|
||||||
|
People = self.config.timezones
|
||||||
|
else: People = {}
|
||||||
|
|
||||||
|
if People.has_key(tz):
|
||||||
|
tz = People[tz]
|
||||||
|
elif (not match.group(1)) and People.has_key(origin.nick):
|
||||||
|
tz = People[origin.nick]
|
||||||
|
|
||||||
|
TZ = tz.upper()
|
||||||
|
if len(tz) > 30: return
|
||||||
|
|
||||||
|
if (TZ == 'UTC') or (TZ == 'Z'):
|
||||||
|
msg = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime())
|
||||||
|
self.msg(origin.sender, msg)
|
||||||
|
elif TimeZones.has_key(TZ):
|
||||||
|
offset = TimeZones[TZ] * 3600
|
||||||
|
timenow = time.gmtime(time.time() + offset)
|
||||||
|
msg = time.strftime("%a, %d %b %Y %H:%M:%S " + str(TZ), timenow)
|
||||||
|
self.msg(origin.sender, msg)
|
||||||
|
elif tz and tz[0] in ('+', '-') and 4 <= len(tz) <= 6:
|
||||||
|
timenow = time.gmtime(time.time() + (int(tz[:3]) * 3600))
|
||||||
|
msg = time.strftime("%a, %d %b %Y %H:%M:%S " + str(tz), timenow)
|
||||||
|
self.msg(origin.sender, msg)
|
||||||
|
else:
|
||||||
|
try: t = float(tz)
|
||||||
|
except ValueError:
|
||||||
|
import os, re, subprocess
|
||||||
|
r_tz = re.compile(r'^[A-Za-z]+(?:/[A-Za-z_]+)*$')
|
||||||
|
if r_tz.match(tz) and os.path.isfile('/usr/share/zoneinfo/' + tz):
|
||||||
|
cmd, PIPE = 'TZ=%s date' % tz, subprocess.PIPE
|
||||||
|
proc = subprocess.Popen(cmd, shell=True, stdout=PIPE)
|
||||||
|
self.msg(origin.sender, proc.communicate()[0])
|
||||||
|
else:
|
||||||
|
error = "Sorry, I don't know about the '%s' timezone." % tz
|
||||||
|
self.msg(origin.sender, origin.nick + ': ' + error)
|
||||||
|
else:
|
||||||
|
timenow = time.gmtime(time.time() + (t * 3600))
|
||||||
|
msg = time.strftime("%a, %d %b %Y %H:%M:%S " + str(tz), timenow)
|
||||||
|
self.msg(origin.sender, msg)
|
||||||
|
f_time.commands = ['t']
|
||||||
|
|
||||||
|
def beats(phenny, input):
|
||||||
|
beats = ((time.time() + 3600) % 86400) / 86.4
|
||||||
|
beats = int(math.floor(beats))
|
||||||
|
phenny.say('@%03i' % beats)
|
||||||
|
beats.commands = ['beats']
|
||||||
|
beats.priority = 'low'
|
||||||
|
|
||||||
|
def divide(input, by):
|
||||||
|
return (input / by), (input % by)
|
||||||
|
|
||||||
|
def yi(phenny, input):
|
||||||
|
quadraels, remainder = divide(int(time.time()), 1753200)
|
||||||
|
raels = quadraels * 4
|
||||||
|
extraraels, remainder = divide(remainder, 432000)
|
||||||
|
if extraraels == 4:
|
||||||
|
return phenny.say('Yes!')
|
||||||
|
else: phenny.say('Not yet...')
|
||||||
|
yi.commands = ['yi']
|
||||||
|
yi.priority = 'low'
|
||||||
|
|
||||||
|
# d8uv d8uv d8uv d8uv d8uv d8uv d8uv
|
||||||
|
|
||||||
|
def tock(phenny, input):
|
||||||
|
u = urllib.urlopen('http://tycho.usno.navy.mil/cgi-bin/timer.pl')
|
||||||
|
info = u.info()
|
||||||
|
u.close()
|
||||||
|
phenny.say('"' + info['Date'] + '" - tycho.usno.navy.mil')
|
||||||
|
tock.commands = ['tock']
|
||||||
|
tock.priority = 'high'
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
print __doc__.strip()
|
|
@ -0,0 +1,89 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
"""
|
||||||
|
codepoints.py - Phenny Codepoints Module
|
||||||
|
Copyright 2008, Sean B. Palmer, inamidst.com
|
||||||
|
Licensed under the Eiffel Forum License 2.
|
||||||
|
|
||||||
|
http://inamidst.com/phenny/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import re, unicodedata
|
||||||
|
from itertools import islice
|
||||||
|
|
||||||
|
def about(u, cp=None, name=None):
|
||||||
|
if cp is None: cp = ord(u)
|
||||||
|
if name is None: name = unicodedata.name(u)
|
||||||
|
|
||||||
|
if not unicodedata.combining(u):
|
||||||
|
template = 'U+%04X %s (%s)'
|
||||||
|
else: template = 'U+%04X %s (\xe2\x97\x8c%s)'
|
||||||
|
return template % (cp, name, u.encode('utf-8'))
|
||||||
|
|
||||||
|
def codepoint_simple(arg):
|
||||||
|
arg = arg.upper()
|
||||||
|
r_label = re.compile('\\b' + arg.replace(' ', '.*\\b'))
|
||||||
|
|
||||||
|
results = []
|
||||||
|
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
|
||||||
|
|
||||||
|
length, u, cp, name = sorted(results)[0]
|
||||||
|
return about(u, cp, name)
|
||||||
|
|
||||||
|
def codepoint_extended(arg):
|
||||||
|
arg = arg.upper()
|
||||||
|
try: r_search = re.compile(arg)
|
||||||
|
except: raise ValueError('Broken regexp: %r' % arg)
|
||||||
|
|
||||||
|
for cp in xrange(1, 0x10FFFF):
|
||||||
|
u = unichr(cp)
|
||||||
|
name = unicodedata.name(u, '-')
|
||||||
|
|
||||||
|
if r_search.search(name):
|
||||||
|
yield about(u, cp, name)
|
||||||
|
|
||||||
|
def u(phenny, input):
|
||||||
|
arg = input.bytes[3:]
|
||||||
|
|
||||||
|
ascii = True
|
||||||
|
for c in arg:
|
||||||
|
if ord(c) >= 0x80:
|
||||||
|
ascii = False
|
||||||
|
|
||||||
|
if ascii:
|
||||||
|
if set(arg.upper()) - set('ABCDEFGHIJKLMNOPQRSTUVWXYZ '):
|
||||||
|
extended = True
|
||||||
|
else: extended = False
|
||||||
|
|
||||||
|
if extended:
|
||||||
|
# look up a codepoint with regexp
|
||||||
|
results = list(islice(codepoint_extended(arg), 4))
|
||||||
|
for i, result in enumerate(results):
|
||||||
|
if (i < 2) or ((i == 2) and (len(results) < 4)):
|
||||||
|
phenny.say(result)
|
||||||
|
elif (i == 2) and (len(results) > 3):
|
||||||
|
phenny.say(result + ' [...]')
|
||||||
|
else:
|
||||||
|
# look up a codepoint freely
|
||||||
|
result = codepoint_simple(arg)
|
||||||
|
if result is not None:
|
||||||
|
phenny.say(result)
|
||||||
|
else: phenny.reply("Sorry, no results for %r." % arg)
|
||||||
|
else:
|
||||||
|
text = arg.decode('utf-8')
|
||||||
|
# look up less than three podecoints
|
||||||
|
if len(text) <= 3:
|
||||||
|
for u in text:
|
||||||
|
phenny.say(about(u))
|
||||||
|
# look up more than three podecoints
|
||||||
|
elif len(text) <= 8:
|
||||||
|
phenny.reply(' '.join('U+%04X' % ord(c) for c in text))
|
||||||
|
else: phenny.reply('Sorry, your input is too long!')
|
||||||
|
u.commands = ['u']
|
|
@ -0,0 +1,102 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
"""
|
||||||
|
etymology.py - Phenny Etymology Module
|
||||||
|
Copyright 2007, Sean B. Palmer, inamidst.com
|
||||||
|
Licensed under the Eiffel Forum License 2.
|
||||||
|
|
||||||
|
http://inamidst.com/phenny/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import re
|
||||||
|
import web
|
||||||
|
from tools import deprecated
|
||||||
|
|
||||||
|
etyuri = 'http://etymonline.com/?term=%s'
|
||||||
|
etysearch = 'http://etymonline.com/?search=%s'
|
||||||
|
|
||||||
|
r_definition = re.compile(r'(?ims)<dd[^>]*>.*?</dd>')
|
||||||
|
r_tag = re.compile(r'<(?!!)[^>]+>')
|
||||||
|
r_whitespace = re.compile(r'[\t\r\n ]+')
|
||||||
|
|
||||||
|
abbrs = [
|
||||||
|
'cf', 'lit', 'etc', 'Ger', 'Du', 'Skt', 'Rus', 'Eng', 'Amer.Eng', 'Sp',
|
||||||
|
'Fr', 'N', 'E', 'S', 'W', 'L', 'Gen', 'J.C', 'dial', 'Gk',
|
||||||
|
'19c', '18c', '17c', '16c', 'St', 'Capt'
|
||||||
|
]
|
||||||
|
t_sentence = r'^.*?(?<!%s)(?:\.(?= [A-Z0-9]|\Z)|\Z)'
|
||||||
|
r_sentence = re.compile(t_sentence % ')(?<!'.join(abbrs))
|
||||||
|
|
||||||
|
def unescape(s):
|
||||||
|
s = s.replace('>', '>')
|
||||||
|
s = s.replace('<', '<')
|
||||||
|
s = s.replace('&', '&')
|
||||||
|
return s
|
||||||
|
|
||||||
|
def text(html):
|
||||||
|
html = r_tag.sub('', html)
|
||||||
|
html = r_whitespace.sub(' ', html)
|
||||||
|
return unescape(html).strip()
|
||||||
|
|
||||||
|
def etymology(word):
|
||||||
|
# @@ <nsh> sbp, would it be possible to have a flag for .ety to get 2nd/etc
|
||||||
|
# entries? - http://swhack.com/logs/2006-07-19#T15-05-29
|
||||||
|
|
||||||
|
if len(word) > 25:
|
||||||
|
raise ValueError("Word too long: %s[...]" % word[:10])
|
||||||
|
word = {'axe': 'ax/axe'}.get(word, word)
|
||||||
|
|
||||||
|
bytes = web.get(etyuri % word)
|
||||||
|
definitions = r_definition.findall(bytes)
|
||||||
|
|
||||||
|
if not definitions:
|
||||||
|
return None
|
||||||
|
|
||||||
|
defn = text(definitions[0])
|
||||||
|
m = r_sentence.match(defn)
|
||||||
|
if not m:
|
||||||
|
return None
|
||||||
|
sentence = m.group(0)
|
||||||
|
|
||||||
|
try:
|
||||||
|
sentence = unicode(sentence, 'iso-8859-1')
|
||||||
|
sentence = sentence.encode('utf-8')
|
||||||
|
except: pass
|
||||||
|
|
||||||
|
maxlength = 275
|
||||||
|
if len(sentence) > maxlength:
|
||||||
|
sentence = sentence[:maxlength]
|
||||||
|
words = sentence[:-5].split(' ')
|
||||||
|
words.pop()
|
||||||
|
sentence = ' '.join(words) + ' [...]'
|
||||||
|
|
||||||
|
sentence = '"' + sentence.replace('"', "'") + '"'
|
||||||
|
return sentence + ' - ' + (etyuri % word)
|
||||||
|
|
||||||
|
@deprecated
|
||||||
|
def f_etymology(self, origin, match, args):
|
||||||
|
word = match.group(2)
|
||||||
|
|
||||||
|
try: result = etymology(word)
|
||||||
|
except IOError:
|
||||||
|
msg = "Can't connect to etymonline.com (%s)" % (etyuri % word)
|
||||||
|
self.msg(origin.sender, msg)
|
||||||
|
return
|
||||||
|
|
||||||
|
if result is not None:
|
||||||
|
if (origin.sender == '#esp') and (origin.nick == 'nsh'):
|
||||||
|
self.msg(origin.nick, result)
|
||||||
|
note = 'nsh: see privmsg (yes, this only happens for you)'
|
||||||
|
self.msg(origin.sender, note)
|
||||||
|
else: self.msg(origin.sender, result)
|
||||||
|
else:
|
||||||
|
uri = etysearch % word
|
||||||
|
msg = 'Can\'t find the etymology for "%s". Try %s' % (word, uri)
|
||||||
|
self.msg(origin.sender, msg)
|
||||||
|
# @@ Cf. http://swhack.com/logs/2006-01-04#T01-50-22
|
||||||
|
f_etymology.rule = (['ety'], r"([A-Za-z0-9' -]+)")
|
||||||
|
f_etymology.thread = True
|
||||||
|
f_etymology.priority = 'high'
|
||||||
|
|
||||||
|
if __name__=="__main__":
|
||||||
|
import sys
|
||||||
|
print etymology(sys.argv[1])
|
|
@ -0,0 +1,126 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
"""
|
||||||
|
head.py - Phenny HTTP Metadata Utilities
|
||||||
|
Copyright 2008, Sean B. Palmer, inamidst.com
|
||||||
|
Licensed under the Eiffel Forum License 2.
|
||||||
|
|
||||||
|
http://inamidst.com/phenny/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import re, urllib
|
||||||
|
from htmlentitydefs import name2codepoint
|
||||||
|
import web
|
||||||
|
from tools import deprecated
|
||||||
|
|
||||||
|
@deprecated
|
||||||
|
def f_httphead(self, origin, match, args):
|
||||||
|
""".head <URI> <FieldName>? - Perform an HTTP HEAD on URI."""
|
||||||
|
if origin.sender == '#talis': return
|
||||||
|
uri = match.group(2)
|
||||||
|
header = match.group(3)
|
||||||
|
|
||||||
|
try: info = web.head(uri)
|
||||||
|
except IOError:
|
||||||
|
self.msg(origin.sender, "Can't connect to %s" % uri)
|
||||||
|
return
|
||||||
|
|
||||||
|
if not isinstance(info, list):
|
||||||
|
info = dict(info)
|
||||||
|
info['Status'] = '200'
|
||||||
|
else:
|
||||||
|
newInfo = dict(info[0])
|
||||||
|
newInfo['Status'] = str(info[1])
|
||||||
|
info = newInfo
|
||||||
|
|
||||||
|
if header is None:
|
||||||
|
msg = 'Status: %s (for more, try ".head uri header")' % info['Status']
|
||||||
|
self.msg(origin.sender, msg)
|
||||||
|
else:
|
||||||
|
headerlower = header.lower()
|
||||||
|
if info.has_key(headerlower):
|
||||||
|
self.msg(origin.sender, 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
|
||||||
|
|
||||||
|
r_title = re.compile(r'(?ims)<title[^>]*>(.*?)</title\s*>')
|
||||||
|
r_entity = re.compile(r'&[A-Za-z0-9#]+;')
|
||||||
|
|
||||||
|
@deprecated
|
||||||
|
def f_title(self, origin, match, args):
|
||||||
|
""".title <URI> - Return the title of URI."""
|
||||||
|
uri = match.group(2)
|
||||||
|
if not ':' in uri:
|
||||||
|
uri = 'http://' + uri
|
||||||
|
|
||||||
|
try:
|
||||||
|
redirects = 0
|
||||||
|
while True:
|
||||||
|
info = web.head(uri)
|
||||||
|
|
||||||
|
if not isinstance(info, list):
|
||||||
|
status = '200'
|
||||||
|
else:
|
||||||
|
status = str(info[1])
|
||||||
|
info = info[0]
|
||||||
|
if status.startswith('3'):
|
||||||
|
uri = info['Location']
|
||||||
|
else: break
|
||||||
|
|
||||||
|
redirects += 1
|
||||||
|
if redirects >= 25:
|
||||||
|
self.msg(origin.sender, origin.nick + ": Too many redirects")
|
||||||
|
return
|
||||||
|
|
||||||
|
try: mtype = info['Content-Type']
|
||||||
|
except:
|
||||||
|
self.msg(origin.sender, origin.nick + ": Document isn't HTML")
|
||||||
|
return
|
||||||
|
if not (('/html' in mtype) or ('/xhtml' in mtype)):
|
||||||
|
self.msg(origin.sender, origin.nick + ": Document isn't HTML")
|
||||||
|
return
|
||||||
|
|
||||||
|
u = urllib.urlopen(uri)
|
||||||
|
bytes = u.read(32768)
|
||||||
|
u.close()
|
||||||
|
|
||||||
|
except IOError:
|
||||||
|
self.msg(origin.sender, "Can't connect to %s" % uri)
|
||||||
|
return
|
||||||
|
|
||||||
|
m = r_title.search(bytes)
|
||||||
|
if m:
|
||||||
|
title = m.group(1)
|
||||||
|
title = title.strip()
|
||||||
|
title = title.replace('\t', ' ')
|
||||||
|
title = title.replace('\r', ' ')
|
||||||
|
title = title.replace('\n', ' ')
|
||||||
|
while ' ' in title:
|
||||||
|
title = title.replace(' ', ' ')
|
||||||
|
if len(title) > 200:
|
||||||
|
title = title[:200] + '[...]'
|
||||||
|
|
||||||
|
def e(m):
|
||||||
|
entity = m.group(0)
|
||||||
|
if entity.startswith('&#x'):
|
||||||
|
cp = int(entity[3:-1], 16)
|
||||||
|
return unichr(cp).encode('utf-8')
|
||||||
|
elif entity.startswith('&#'):
|
||||||
|
cp = int(entity[2:-1])
|
||||||
|
return unichr(cp).encode('utf-8')
|
||||||
|
else:
|
||||||
|
char = name2codepoint[entity[1:-1]]
|
||||||
|
return unichr(char).encode('utf-8')
|
||||||
|
title = r_entity.sub(e, title)
|
||||||
|
|
||||||
|
if not title:
|
||||||
|
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
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
print __doc__
|
|
@ -0,0 +1,44 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
"""
|
||||||
|
info.py - Phenny Information Module
|
||||||
|
Copyright 2008, Sean B. Palmer, inamidst.com
|
||||||
|
Licensed under the Eiffel Forum License 2.
|
||||||
|
|
||||||
|
http://inamidst.com/phenny/
|
||||||
|
"""
|
||||||
|
|
||||||
|
def doc(phenny, input):
|
||||||
|
"""Shows a command's documentation, and possibly an example."""
|
||||||
|
name = input.group(1)
|
||||||
|
name = name.lower()
|
||||||
|
|
||||||
|
if phenny.doc.has_key(name):
|
||||||
|
phenny.reply(phenny.doc[name][0])
|
||||||
|
if phenny.doc[name][1]:
|
||||||
|
phenny.say('e.g. ' + phenny.doc[name][1])
|
||||||
|
doc.rule = ('$nick', '(?i)help +([A-Za-z]+)(?:\?+)?$')
|
||||||
|
doc.example = '$nickname: help tell?'
|
||||||
|
doc.priority = 'low'
|
||||||
|
|
||||||
|
def commands(phenny, input):
|
||||||
|
# This function only works in private message
|
||||||
|
if input.startswith('#'): return
|
||||||
|
names = ', '.join(sorted(phenny.doc.iterkeys()))
|
||||||
|
phenny.say('Commands I recognise: ' + names + '.')
|
||||||
|
phenny.say(("For help, do '%s: help example?' where example is the " +
|
||||||
|
"name of the command you want help for.") % phenny.nick)
|
||||||
|
commands.commands = ['commands']
|
||||||
|
commands.priority = 'low'
|
||||||
|
|
||||||
|
def help(phenny, input):
|
||||||
|
response = (
|
||||||
|
'Hi, I\'m a bot. Say ".commands" to me in private for a list ' +
|
||||||
|
'of my commands, or see http://inamidst.com/phenny/ for more ' +
|
||||||
|
'general details. My owner is %s.'
|
||||||
|
) % phenny.config.owner
|
||||||
|
phenny.reply(response)
|
||||||
|
help.rule = ('$nick', r'(?i)help(?:[?!]+)?$')
|
||||||
|
help.priority = 'low'
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
print __doc__.strip()
|
|
@ -0,0 +1,23 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
"""
|
||||||
|
ping.py - Phenny Ping Module
|
||||||
|
Author: Sean B. Palmer, inamidst.com
|
||||||
|
About: http://inamidst.com/phenny/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import random
|
||||||
|
|
||||||
|
def hello(phenny, input):
|
||||||
|
greeting = random.choice(('Hi', 'Hey', 'Hello'))
|
||||||
|
punctuation = random.choice(('', '!'))
|
||||||
|
phenny.say(greeting + ' ' + input.nick + punctuation)
|
||||||
|
hello.rule = r'(?i)(hi|hello|hey) $nickname\b'
|
||||||
|
|
||||||
|
def interjection(phenny, input):
|
||||||
|
phenny.say(input.nick + '!')
|
||||||
|
interjection.rule = r'$nickname!'
|
||||||
|
interjection.priority = 'high'
|
||||||
|
interjection.thread = False
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
print __doc__.strip()
|
|
@ -0,0 +1,34 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
"""
|
||||||
|
reload.py - Phenny Module Reloader Module
|
||||||
|
Copyright 2008, Sean B. Palmer, inamidst.com
|
||||||
|
Licensed under the Eiffel Forum License 2.
|
||||||
|
|
||||||
|
http://inamidst.com/phenny/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import irc
|
||||||
|
|
||||||
|
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)
|
||||||
|
reload(module)
|
||||||
|
|
||||||
|
if hasattr(module, '__file__'):
|
||||||
|
import os.path, time
|
||||||
|
mtime = os.path.getmtime(module.__file__)
|
||||||
|
modified = time.strftime('%Y-%m-%d %H:%M:%S', time.gmtime(mtime))
|
||||||
|
else: modified = 'unknown'
|
||||||
|
|
||||||
|
self.register(vars(module))
|
||||||
|
self.bind_commands()
|
||||||
|
|
||||||
|
phenny.reply('%r (version: %s)' % (module, modified))
|
||||||
|
f_reload.name = 'reload'
|
||||||
|
f_reload.rule = ('$nick', ['reload'], r'(\S+)')
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
print __doc__.strip()
|
|
@ -0,0 +1,82 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
"""
|
||||||
|
search.py - Phenny Web Search Module
|
||||||
|
Copyright 2008, Sean B. Palmer, inamidst.com
|
||||||
|
Licensed under the Eiffel Forum License 2.
|
||||||
|
|
||||||
|
http://inamidst.com/phenny/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import re
|
||||||
|
import web
|
||||||
|
|
||||||
|
r_string = re.compile(r'("(\\.|[^"\\])*")')
|
||||||
|
r_json = re.compile(r'^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]+$')
|
||||||
|
env = {'__builtins__': None, 'null': None, 'true': True, 'false': False}
|
||||||
|
|
||||||
|
def json(text):
|
||||||
|
"""Evaluate JSON text safely (we hope)."""
|
||||||
|
if r_json.match(r_string.sub('', text)):
|
||||||
|
text = r_string.sub(lambda m: 'u' + m.group(1), text)
|
||||||
|
return eval(text.strip(' \t\r\n'), env, {})
|
||||||
|
raise ValueError('Input must be serialised JSON.')
|
||||||
|
|
||||||
|
def search(query, n=1):
|
||||||
|
"""Search using SearchMash, return its JSON."""
|
||||||
|
q = web.urllib.quote(query.encode('utf-8'))
|
||||||
|
uri = 'http://www.searchmash.com/results/' + q + '?n=' + str(n)
|
||||||
|
bytes = web.get(uri)
|
||||||
|
return json(bytes)
|
||||||
|
|
||||||
|
def result(query):
|
||||||
|
results = search(query)
|
||||||
|
return results['results'][0]['url']
|
||||||
|
|
||||||
|
def count(query):
|
||||||
|
results = search(query)
|
||||||
|
return results['estimatedCount']
|
||||||
|
|
||||||
|
def formatnumber(n):
|
||||||
|
"""Format a number with beautiful commas."""
|
||||||
|
parts = list(str(n))
|
||||||
|
for i in range((len(parts) - 3), 0, -3):
|
||||||
|
parts.insert(i, ',')
|
||||||
|
return ''.join(parts)
|
||||||
|
|
||||||
|
def g(phenny, input):
|
||||||
|
uri = result(input.group(2))
|
||||||
|
phenny.reply(uri)
|
||||||
|
g.commands = ['g']
|
||||||
|
g.priority = 'high'
|
||||||
|
|
||||||
|
def gc(phenny, input):
|
||||||
|
query = input.group(2)
|
||||||
|
num = count(query)
|
||||||
|
phenny.say(query + ': ' + num)
|
||||||
|
gc.commands = ['gc']
|
||||||
|
gc.priority = 'high'
|
||||||
|
|
||||||
|
r_query = re.compile(
|
||||||
|
r'\+?"[^"\\]*(?:\\.[^"\\]*)*"|\[[^]\\]*(?:\\.[^]\\]*)*\]|\S+'
|
||||||
|
)
|
||||||
|
|
||||||
|
def compare(phenny, input):
|
||||||
|
queries = r_query.findall(input.group(2))
|
||||||
|
if len(queries) > 6:
|
||||||
|
return phenny.reply('Sorry, can only compare up to six things.')
|
||||||
|
|
||||||
|
results = []
|
||||||
|
for i, query in enumerate(queries):
|
||||||
|
query = query.strip('[]')
|
||||||
|
n = int((count(query) or '0').replace(',', ''))
|
||||||
|
results.append((n, query))
|
||||||
|
if i >= 2: __import__('time').sleep(0.25)
|
||||||
|
if i >= 4: __import__('time').sleep(0.25)
|
||||||
|
|
||||||
|
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']
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
print __doc__.strip()
|
|
@ -0,0 +1,49 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
"""
|
||||||
|
seen.py - Phenny Seen Module
|
||||||
|
Copyright 2008, Sean B. Palmer, inamidst.com
|
||||||
|
Licensed under the Eiffel Forum License 2.
|
||||||
|
|
||||||
|
http://inamidst.com/phenny/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import time
|
||||||
|
from tools import deprecated
|
||||||
|
|
||||||
|
@deprecated
|
||||||
|
def f_seen(self, origin, match, args):
|
||||||
|
""".seen <nick> - Reports when <nick> was last seen."""
|
||||||
|
if origin.sender == '#talis': return
|
||||||
|
nick = match.group(2).lower()
|
||||||
|
if not hasattr(self, 'seen'):
|
||||||
|
return self.msg(origin.sender, '?')
|
||||||
|
if self.seen.has_key(nick):
|
||||||
|
channel, t = self.seen[nick]
|
||||||
|
t = time.strftime('%Y-%m-%d %H:%M:%S UTC', time.gmtime(t))
|
||||||
|
|
||||||
|
msg = "I last saw %s at %s on %s" % (nick, t, channel)
|
||||||
|
self.msg(origin.sender, str(origin.nick) + ': ' + msg)
|
||||||
|
else: self.msg(origin.sender, "Sorry, I haven't seen %s around." % nick)
|
||||||
|
f_seen.rule = (['seen'], r'(\S+)')
|
||||||
|
|
||||||
|
@deprecated
|
||||||
|
def f_note(self, origin, match, args):
|
||||||
|
def note(self, origin, match, args):
|
||||||
|
if not hasattr(self.bot, 'seen'):
|
||||||
|
self.bot.seen = {}
|
||||||
|
if origin.sender.startswith('#'):
|
||||||
|
# if origin.sender == '#inamidst': return
|
||||||
|
self.seen[origin.nick.lower()] = (origin.sender, time.time())
|
||||||
|
|
||||||
|
# if not hasattr(self, 'chanspeak'):
|
||||||
|
# self.chanspeak = {}
|
||||||
|
# if (len(args) > 2) and args[2].startswith('#'):
|
||||||
|
# self.chanspeak[args[2]] = args[0]
|
||||||
|
|
||||||
|
try: note(self, origin, match, args)
|
||||||
|
except Exception, e: print e
|
||||||
|
f_note.rule = r'(.*)'
|
||||||
|
f_note.priority = 'low'
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
print __doc__
|
|
@ -0,0 +1,23 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
"""
|
||||||
|
startup.py - Phenny Startup Module
|
||||||
|
Copyright 2008, Sean B. Palmer, inamidst.com
|
||||||
|
Licensed under the Eiffel Forum License 2.
|
||||||
|
|
||||||
|
http://inamidst.com/phenny/
|
||||||
|
"""
|
||||||
|
|
||||||
|
def startup(phenny, input):
|
||||||
|
if hasattr(phenny.config, 'password'):
|
||||||
|
phenny.msg('NickServ', 'IDENTIFY %s' % phenny.config.password)
|
||||||
|
__import__('time').sleep(5)
|
||||||
|
|
||||||
|
# Cf. http://swhack.com/logs/2005-12-05#T19-32-36
|
||||||
|
for channel in phenny.channels:
|
||||||
|
phenny.write(('JOIN', channel))
|
||||||
|
startup.rule = r'(.*)'
|
||||||
|
startup.event = '251'
|
||||||
|
startup.priority = 'low'
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
print __doc__.strip()
|
|
@ -0,0 +1,164 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
"""
|
||||||
|
tell.py - Phenny Tell and Ask Module
|
||||||
|
Copyright 2008, Sean B. Palmer, inamidst.com
|
||||||
|
Licensed under the Eiffel Forum License 2.
|
||||||
|
|
||||||
|
http://inamidst.com/phenny/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os, re, time, random
|
||||||
|
import web
|
||||||
|
|
||||||
|
maximum = 4
|
||||||
|
lispchannels = frozenset([ '#lisp', '#scheme', '#opendarwin', '#macdev',
|
||||||
|
'#fink', '#jedit', '#dylan', '#emacs', '#xemacs', '#colloquy', '#adium',
|
||||||
|
'#growl', '#chicken', '#quicksilver', '#svn', '#slate', '#squeak', '#wiki',
|
||||||
|
'#nebula', '#myko', '#lisppaste', '#pearpc', '#fpc', '#hprog',
|
||||||
|
'#concatenative', '#slate-users', '#swhack', '#ud', '#t', '#compilers',
|
||||||
|
'#erights', '#esp', '#scsh', '#sisc', '#haskell', '#rhype', '#sicp', '#darcs',
|
||||||
|
'#hardcider', '#lisp-it', '#webkit', '#launchd', '#mudwalker', '#darwinports',
|
||||||
|
'#muse', '#chatkit', '#kowaleba', '#vectorprogramming', '#opensolaris',
|
||||||
|
'#oscar-cluster', '#ledger', '#cairo', '#idevgames', '#hug-bunny', '##parsers',
|
||||||
|
'#perl6', '#sdlperl', '#ksvg', '#rcirc', '#code4lib', '#linux-quebec',
|
||||||
|
'#programmering', '#maxima', '#robin', '##concurrency', '#paredit' ])
|
||||||
|
|
||||||
|
def loadReminders(fn):
|
||||||
|
result = {}
|
||||||
|
f = open(fn)
|
||||||
|
for line in f:
|
||||||
|
line = line.strip()
|
||||||
|
if line:
|
||||||
|
tellee, teller, verb, timenow, msg = line.split('\t', 4)
|
||||||
|
result.setdefault(tellee, []).append((teller, verb, timenow, msg))
|
||||||
|
f.close()
|
||||||
|
return result
|
||||||
|
|
||||||
|
def dumpReminders(fn, data):
|
||||||
|
f = open(fn, 'w')
|
||||||
|
for tellee in data.iterkeys():
|
||||||
|
for remindon in data[tellee]:
|
||||||
|
line = '\t'.join((tellee,) + remindon)
|
||||||
|
f.write(line + '\n')
|
||||||
|
f.close()
|
||||||
|
return True
|
||||||
|
|
||||||
|
def setup(self):
|
||||||
|
fn = self.nick + '-' + self.config.host + '.tell.db'
|
||||||
|
self.tell_filename = os.path.join(os.path.expanduser('~/.phenny'), fn)
|
||||||
|
if not os.path.exists(self.tell_filename):
|
||||||
|
try: f = open(self.tell_filename, 'w')
|
||||||
|
except OSError: pass
|
||||||
|
else:
|
||||||
|
f.write('')
|
||||||
|
f.close()
|
||||||
|
self.reminders = loadReminders(self.tell_filename) # @@ tell
|
||||||
|
|
||||||
|
def f_remind(phenny, input):
|
||||||
|
teller = input.nick
|
||||||
|
|
||||||
|
# @@ Multiple comma-separated tellees? Cf. Terje, #swhack, 2006-04-15
|
||||||
|
verb, tellee, msg = input.groups()
|
||||||
|
tellee_original = tellee.rstrip(',:;')
|
||||||
|
tellee = tellee.lower()
|
||||||
|
|
||||||
|
if not os.path.exists(phenny.tell_filename):
|
||||||
|
return
|
||||||
|
|
||||||
|
if len(tellee) > 20:
|
||||||
|
return phenny.reply('That nickname is too long.')
|
||||||
|
|
||||||
|
timenow = time.strftime('%d %b %H:%MZ', time.gmtime())
|
||||||
|
if not tellee in (teller.lower(), phenny.nick, 'me'): # @@
|
||||||
|
# @@ <deltab> and year, if necessary
|
||||||
|
warn = False
|
||||||
|
if not phenny.reminders.has_key(tellee):
|
||||||
|
phenny.reminders[tellee] = [(teller, verb, timenow, msg)]
|
||||||
|
else:
|
||||||
|
if len(phenny.reminders[tellee]) >= maximum:
|
||||||
|
warn = True
|
||||||
|
phenny.reminders[tellee].append((teller, verb, timenow, msg))
|
||||||
|
# @@ Stephanie's augmentation
|
||||||
|
response = "I'll pass that on when %s is around." % tellee_original
|
||||||
|
if warn: response += (" I'll have to use a pastebin, though, so " +
|
||||||
|
"your message may get lost.")
|
||||||
|
|
||||||
|
rand = random.random()
|
||||||
|
if rand > 0.9999: response = "yeah, yeah"
|
||||||
|
elif rand > 0.999: response = "%s: yeah, sure, whatever" % teller
|
||||||
|
|
||||||
|
phenny.reply(response)
|
||||||
|
elif teller.lower() == tellee:
|
||||||
|
phenny.say('You can %s yourself that.' % verb)
|
||||||
|
else: phenny.say("Hey, I'm not as stupid as Monty you know!")
|
||||||
|
|
||||||
|
dumpReminders(phenny.tell_filename, phenny.reminders) # @@ tell
|
||||||
|
f_remind.rule = ('$nick', ['tell', 'ask'], r'(\S+) (.*)')
|
||||||
|
|
||||||
|
def getReminders(phenny, channel, key, tellee):
|
||||||
|
lines = []
|
||||||
|
template = "%s: %s <%s> %s %s %s"
|
||||||
|
today = time.strftime('%d %b', time.gmtime())
|
||||||
|
|
||||||
|
for (teller, verb, datetime, msg) in phenny.reminders[key]:
|
||||||
|
if datetime.startswith(today):
|
||||||
|
datetime = datetime[len(today)+1:]
|
||||||
|
lines.append(template % (tellee, datetime, teller, verb, tellee, msg))
|
||||||
|
|
||||||
|
try: del phenny.reminders[key]
|
||||||
|
except KeyError: phenny.msg(channel, 'Er...')
|
||||||
|
return lines
|
||||||
|
|
||||||
|
def message(phenny, input):
|
||||||
|
if not input.sender.startswith('#'): return
|
||||||
|
|
||||||
|
tellee = input.nick
|
||||||
|
channel = input.sender
|
||||||
|
|
||||||
|
if not os.path.exists(phenny.tell_filename):
|
||||||
|
return
|
||||||
|
|
||||||
|
reminders = []
|
||||||
|
remkeys = list(reversed(sorted(phenny.reminders.keys())))
|
||||||
|
for remkey in remkeys:
|
||||||
|
if not remkey.endswith('*'):
|
||||||
|
if tellee.lower() == remkey:
|
||||||
|
reminders.extend(getReminders(phenny, channel, remkey, tellee))
|
||||||
|
elif tellee.lower().startswith(remkey.rstrip('*')):
|
||||||
|
reminders.extend(getReminders(phenny, channel, remkey, tellee))
|
||||||
|
|
||||||
|
for line in reminders[:maximum]:
|
||||||
|
phenny.say(line)
|
||||||
|
|
||||||
|
if reminders[maximum:]:
|
||||||
|
try:
|
||||||
|
if origin.sender in lispchannels:
|
||||||
|
chan = origin.sender
|
||||||
|
else: chan = 'None'
|
||||||
|
|
||||||
|
result = web.post('http://paste.lisp.org/submit',
|
||||||
|
{'channel': chan,
|
||||||
|
'username': phenny.nick,
|
||||||
|
'title': 'Further Messages for %s' % tellee,
|
||||||
|
'colorize': 'None',
|
||||||
|
'text': '\n'.join(reminders[maximum:]) + '\n',
|
||||||
|
'captcha': 'lisp',
|
||||||
|
'captchaid': 'bdf447484f62a3e8b23816f9acee79d9'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
uris = re.findall('http://paste.lisp.org/display/\d+', result)
|
||||||
|
uri = list(reversed(uris)).pop()
|
||||||
|
if not origin.sender in lispchannels:
|
||||||
|
message = '%s: see %s for further messages' % (tellee, uri)
|
||||||
|
phenny.say(message)
|
||||||
|
except:
|
||||||
|
error = '[Sorry, some messages were elided and lost...]'
|
||||||
|
phenny.say(error)
|
||||||
|
|
||||||
|
if len(phenny.reminders.keys()) != remkeys:
|
||||||
|
dumpReminders(phenny.tell_filename, phenny.reminders) # @@ tell
|
||||||
|
message.rule = r'(.*)'
|
||||||
|
message.priority = 'low'
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
print __doc__.strip()
|
|
@ -0,0 +1,102 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
# coding=utf-8
|
||||||
|
"""
|
||||||
|
translate.py - Phenny Translation Module
|
||||||
|
Copyright 2008, Sean B. Palmer, inamidst.com
|
||||||
|
Licensed under the Eiffel Forum License 2.
|
||||||
|
|
||||||
|
http://inamidst.com/phenny/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import re
|
||||||
|
import web
|
||||||
|
|
||||||
|
r_translation = re.compile(r'<div style=padding:10px;>([^<]+)</div>')
|
||||||
|
|
||||||
|
def guess_language(phrase):
|
||||||
|
languages = {
|
||||||
|
'english': 'en',
|
||||||
|
'french': 'fr',
|
||||||
|
'spanish': 'es',
|
||||||
|
'portuguese': 'pt',
|
||||||
|
'german': 'de',
|
||||||
|
'italian': 'it',
|
||||||
|
'korean': 'ko',
|
||||||
|
'japanese': 'ja',
|
||||||
|
'chinese': 'zh',
|
||||||
|
'dutch': 'nl',
|
||||||
|
'greek': 'el',
|
||||||
|
'russian': 'ru'
|
||||||
|
}
|
||||||
|
|
||||||
|
uri = 'http://www.xrce.xerox.com/cgi-bin/mltt/LanguageGuesser'
|
||||||
|
form = {'Text': phrase}
|
||||||
|
bytes = web.post(uri, form)
|
||||||
|
for line in bytes.splitlines():
|
||||||
|
if '<listing><font size=+1>' in line:
|
||||||
|
i = line.find('<listing><font size=+1>')
|
||||||
|
lang = line[i+len('<listing><font size=+1>'):].strip()
|
||||||
|
lang = lang.lower()
|
||||||
|
if '_' in lang:
|
||||||
|
j = lang.find('_')
|
||||||
|
lang = lang[:j]
|
||||||
|
try: return languages[lang]
|
||||||
|
except KeyError:
|
||||||
|
return lang
|
||||||
|
return 'unknown'
|
||||||
|
|
||||||
|
def translate(phrase, lang, target='en'):
|
||||||
|
babelfish = 'http://world.altavista.com/tr'
|
||||||
|
form = {
|
||||||
|
'doit': 'done',
|
||||||
|
'intl': '1',
|
||||||
|
'tt': 'urltext',
|
||||||
|
'trtext': phrase,
|
||||||
|
'lp': lang + '_' + target
|
||||||
|
}
|
||||||
|
|
||||||
|
bytes = web.post(babelfish, form)
|
||||||
|
m = r_translation.search(bytes)
|
||||||
|
if m:
|
||||||
|
translation = m.group(1)
|
||||||
|
translation = translation.replace('\r', ' ')
|
||||||
|
translation = translation.replace('\n', ' ')
|
||||||
|
while ' ' in translation:
|
||||||
|
translation = translation.replace(' ', ' ')
|
||||||
|
return translation
|
||||||
|
return None
|
||||||
|
|
||||||
|
def tr(phenny, input):
|
||||||
|
lang, phrase = input.groups()
|
||||||
|
|
||||||
|
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.')
|
||||||
|
|
||||||
|
if language != 'en':
|
||||||
|
translation = translate(phrase, language)
|
||||||
|
if translation is not None:
|
||||||
|
return phenny.reply(u'"%s" (%s)' % (translation, language))
|
||||||
|
|
||||||
|
error = "I think it's %s, but I can't translate that language."
|
||||||
|
return phenny.reply(error % language.title())
|
||||||
|
|
||||||
|
# Otherwise, it's English, so mangle it for fun
|
||||||
|
for other in ['de', 'ja']:
|
||||||
|
phrase = translate(phrase, 'en', other)
|
||||||
|
phrase = translate(phrase, other, 'en')
|
||||||
|
|
||||||
|
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.doc = ('phenny: "<phrase>"? or phenny: <lang> "<phrase>"?',
|
||||||
|
'Translate <phrase>, optionally forcing the <lang> interpretation.')
|
||||||
|
tr.rule = ('$nick', ur'(?:([a-z]{2}) +)?["“](.+?)["”]\? *$')
|
||||||
|
tr.priority = 'low'
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
print __doc__.strip()
|
|
@ -0,0 +1,422 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
"""
|
||||||
|
weather.py - Phenny Weather Module
|
||||||
|
Copyright 2008, Sean B. Palmer, inamidst.com
|
||||||
|
Licensed under the Eiffel Forum License 2.
|
||||||
|
|
||||||
|
http://inamidst.com/phenny/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import re, urllib
|
||||||
|
import web
|
||||||
|
from tools import deprecated
|
||||||
|
|
||||||
|
r_from = re.compile(r'(?i)([+-]\d+):00 from')
|
||||||
|
|
||||||
|
r_json = re.compile(r'^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]+$')
|
||||||
|
r_string = re.compile(r'("(\\.|[^"\\])*")')
|
||||||
|
env = {'__builtins__': None, 'null': None,
|
||||||
|
'true': True, 'false': False}
|
||||||
|
|
||||||
|
def json(text):
|
||||||
|
"""Evaluate JSON text safely (we hope)."""
|
||||||
|
if r_json.match(r_string.sub('', text)):
|
||||||
|
text = r_string.sub(lambda m: 'u' + m.group(1), text)
|
||||||
|
return eval(text.strip(' \t\r\n'), env, {})
|
||||||
|
raise ValueError('Input must be serialised JSON.')
|
||||||
|
|
||||||
|
def location(name):
|
||||||
|
name = urllib.quote(name)
|
||||||
|
uri = 'http://ws.geonames.org/searchJSON?q=%s&maxRows=1' % name
|
||||||
|
for i in xrange(10):
|
||||||
|
u = urllib.urlopen(uri)
|
||||||
|
if u is not None: break
|
||||||
|
bytes = u.read()
|
||||||
|
u.close()
|
||||||
|
|
||||||
|
results = json(bytes)
|
||||||
|
try: name = results['geonames'][0]['name']
|
||||||
|
except IndexError:
|
||||||
|
return '?', '?', '0', '0'
|
||||||
|
countryName = results['geonames'][0]['countryName']
|
||||||
|
lat = results['geonames'][0]['lat']
|
||||||
|
lng = results['geonames'][0]['lng']
|
||||||
|
return name, countryName, lat, lng
|
||||||
|
|
||||||
|
class GrumbleError(object):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def local(icao, hour, minute):
|
||||||
|
uri = ('http://www.flightstats.com/' +
|
||||||
|
'go/Airport/airportDetails.do?airportCode=%s')
|
||||||
|
try: bytes = web.get(uri % icao)
|
||||||
|
except AttributeError:
|
||||||
|
raise GrumbleError('A WEBSITE HAS GONE DOWN WTF STUPID WEB')
|
||||||
|
m = r_from.search(bytes)
|
||||||
|
if m:
|
||||||
|
offset = m.group(1)
|
||||||
|
lhour = int(hour) + int(offset)
|
||||||
|
lhour = lhour % 24
|
||||||
|
return (str(lhour) + ':' + str(minute) + ', ' + str(hour) +
|
||||||
|
str(minute) + 'Z')
|
||||||
|
# return (str(lhour) + ':' + str(minute) + ' (' + str(hour) +
|
||||||
|
# ':' + str(minute) + 'Z)')
|
||||||
|
return str(hour) + ':' + str(minute) + 'Z'
|
||||||
|
|
||||||
|
def code(phenny, search):
|
||||||
|
name, country, latitude, longitude = location(search)
|
||||||
|
if name == '?': return False
|
||||||
|
|
||||||
|
sumOfSquares = (99999999999999999999999999999, 'ICAO')
|
||||||
|
from icao import data
|
||||||
|
for icao_code, lat, lon in data:
|
||||||
|
latDiff = abs(latitude - lat)
|
||||||
|
lonDiff = abs(longitude - lon)
|
||||||
|
diff = (latDiff * latDiff) + (lonDiff * lonDiff)
|
||||||
|
if diff < sumOfSquares[0]:
|
||||||
|
sumOfSquares = (diff, icao_code)
|
||||||
|
return sumOfSquares[1]
|
||||||
|
|
||||||
|
@deprecated
|
||||||
|
def f_weather(self, origin, match, args):
|
||||||
|
""".weather <ICAO> - Show the weather at airport with the code <ICAO>."""
|
||||||
|
if origin.sender == '#talis':
|
||||||
|
if args[0].startswith('.weather '): return
|
||||||
|
|
||||||
|
icao_code = match.group(2)
|
||||||
|
if (not len(icao_code) == 4) or \
|
||||||
|
(len(icao_code) > 1 and icao_code[0] in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' and
|
||||||
|
icao_code[1] in 'abcdefghijklmnopqrstuvwxyz'):
|
||||||
|
icao_code = code(self, icao_code)
|
||||||
|
else: icao_code = icao_code.upper()
|
||||||
|
|
||||||
|
if not icao_code:
|
||||||
|
self.msg(origin.sender, 'No ICAO code found, sorry')
|
||||||
|
return
|
||||||
|
|
||||||
|
uri = 'http://weather.noaa.gov/pub/data/observations/metar/stations/%s.TXT'
|
||||||
|
try: bytes = web.get(uri % icao_code)
|
||||||
|
except AttributeError:
|
||||||
|
raise GrumbleError('OH CRAP NOAA HAS GONE DOWN THE WEB IS BROKEN')
|
||||||
|
if 'Not Found' in bytes:
|
||||||
|
self.msg(origin.sender, icao_code+': no such ICAO code, or no NOAA data')
|
||||||
|
return
|
||||||
|
|
||||||
|
metar = bytes.splitlines().pop()
|
||||||
|
metar = metar.split(' ')
|
||||||
|
|
||||||
|
if len(metar[0]) == 4:
|
||||||
|
metar = metar[1:]
|
||||||
|
|
||||||
|
if metar[0].endswith('Z'):
|
||||||
|
time = metar[0]
|
||||||
|
metar = metar[1:]
|
||||||
|
else: time = None
|
||||||
|
|
||||||
|
if metar[0] == 'AUTO':
|
||||||
|
metar = metar[1:]
|
||||||
|
if metar[0] == 'VCU':
|
||||||
|
self.msg(origin.sender, icao_code + ': no data provided')
|
||||||
|
return
|
||||||
|
|
||||||
|
if metar[0].endswith('KT'):
|
||||||
|
wind = metar[0]
|
||||||
|
metar = metar[1:]
|
||||||
|
else: wind = None
|
||||||
|
|
||||||
|
if ('V' in metar[0]) and (metar[0] != 'CAVOK'):
|
||||||
|
vari = metar[0]
|
||||||
|
metar = metar[1:]
|
||||||
|
else: vari = None
|
||||||
|
|
||||||
|
if ((len(metar[0]) == 4) or
|
||||||
|
metar[0].endswith('SM')):
|
||||||
|
visibility = metar[0]
|
||||||
|
metar = metar[1:]
|
||||||
|
else: visibility = None
|
||||||
|
|
||||||
|
while metar[0].startswith('R') and (metar[0].endswith('L')
|
||||||
|
or 'L/' in metar[0]):
|
||||||
|
metar = metar[1:]
|
||||||
|
|
||||||
|
if len(metar[0]) == 6 and (metar[0].endswith('N') or
|
||||||
|
metar[0].endswith('E') or
|
||||||
|
metar[0].endswith('S') or
|
||||||
|
metar[0].endswith('W')):
|
||||||
|
metar = metar[1:] # 7000SE?
|
||||||
|
|
||||||
|
cond = []
|
||||||
|
while (((len(metar[0]) < 5) or
|
||||||
|
metar[0].startswith('+') or
|
||||||
|
metar[0].startswith('-')) and (not (metar[0].startswith('VV') or
|
||||||
|
metar[0].startswith('SKC') or metar[0].startswith('CLR') or
|
||||||
|
metar[0].startswith('FEW') or metar[0].startswith('SCT') or
|
||||||
|
metar[0].startswith('BKN') or metar[0].startswith('OVC')))):
|
||||||
|
cond.append(metar[0])
|
||||||
|
metar = metar[1:]
|
||||||
|
|
||||||
|
while '/P' in metar[0]:
|
||||||
|
metar = metar[1:]
|
||||||
|
|
||||||
|
if not metar:
|
||||||
|
self.msg(origin.sender, icao_code + ': no data provided')
|
||||||
|
return
|
||||||
|
|
||||||
|
cover = []
|
||||||
|
while (metar[0].startswith('VV') or metar[0].startswith('SKC') or
|
||||||
|
metar[0].startswith('CLR') or metar[0].startswith('FEW') or
|
||||||
|
metar[0].startswith('SCT') or metar[0].startswith('BKN') or
|
||||||
|
metar[0].startswith('OVC')):
|
||||||
|
cover.append(metar[0])
|
||||||
|
metar = metar[1:]
|
||||||
|
if not metar:
|
||||||
|
self.msg(origin.sender, icao_code + ': no data provided')
|
||||||
|
return
|
||||||
|
|
||||||
|
if metar[0] == 'CAVOK':
|
||||||
|
cover.append('CLR')
|
||||||
|
metar = metar[1:]
|
||||||
|
|
||||||
|
if metar[0] == 'PRFG':
|
||||||
|
cover.append('CLR') # @@?
|
||||||
|
metar = metar[1:]
|
||||||
|
|
||||||
|
if metar[0] == 'NSC':
|
||||||
|
cover.append('CLR')
|
||||||
|
metar = metar[1:]
|
||||||
|
|
||||||
|
if ('/' in metar[0]) or (len(metar[0]) == 5 and metar[0][2] == '.'):
|
||||||
|
temp = metar[0]
|
||||||
|
metar = metar[1:]
|
||||||
|
else: temp = None
|
||||||
|
|
||||||
|
if metar[0].startswith('QFE'):
|
||||||
|
metar = metar[1:]
|
||||||
|
|
||||||
|
if metar[0].startswith('Q') or metar[0].startswith('A'):
|
||||||
|
pressure = metar[0]
|
||||||
|
metar = metar[1:]
|
||||||
|
else: pressure = None
|
||||||
|
|
||||||
|
if time:
|
||||||
|
hour = time[2:4]
|
||||||
|
minute = time[4:6]
|
||||||
|
time = local(icao_code, hour, minute)
|
||||||
|
else: time = '(time unknown)'
|
||||||
|
|
||||||
|
if wind:
|
||||||
|
speed = int(wind[3:5])
|
||||||
|
if speed < 1:
|
||||||
|
description = 'Calm'
|
||||||
|
elif speed < 4:
|
||||||
|
description = 'Light air'
|
||||||
|
elif speed < 7:
|
||||||
|
description = 'Light breeze'
|
||||||
|
elif speed < 11:
|
||||||
|
description = 'Gentle breeze'
|
||||||
|
elif speed < 16:
|
||||||
|
description = 'Moderate breeze'
|
||||||
|
elif speed < 22:
|
||||||
|
description = 'Fresh breeze'
|
||||||
|
elif speed < 28:
|
||||||
|
description = 'Strong breeze'
|
||||||
|
elif speed < 34:
|
||||||
|
description = 'Near gale'
|
||||||
|
elif speed < 41:
|
||||||
|
description = 'Gale'
|
||||||
|
elif speed < 48:
|
||||||
|
description = 'Strong gale'
|
||||||
|
elif speed < 56:
|
||||||
|
description = 'Storm'
|
||||||
|
elif speed < 64:
|
||||||
|
description = 'Violent storm'
|
||||||
|
else: description = 'Hurricane'
|
||||||
|
|
||||||
|
degrees = wind[0:3]
|
||||||
|
if degrees == 'VRB':
|
||||||
|
degrees = u'\u21BB'.encode('utf-8')
|
||||||
|
elif (degrees <= 22.5) or (degrees > 337.5):
|
||||||
|
degrees = u'\u2191'.encode('utf-8')
|
||||||
|
elif (degrees > 22.5) and (degrees <= 67.5):
|
||||||
|
degrees = u'\u2197'.encode('utf-8')
|
||||||
|
elif (degrees > 67.5) and (degrees <= 112.5):
|
||||||
|
degrees = u'\u2192'.encode('utf-8')
|
||||||
|
elif (degrees > 112.5) and (degrees <= 157.5):
|
||||||
|
degrees = u'\u2198'.encode('utf-8')
|
||||||
|
elif (degrees > 157.5) and (degrees <= 202.5):
|
||||||
|
degrees = u'\u2193'.encode('utf-8')
|
||||||
|
elif (degrees > 202.5) and (degrees <= 247.5):
|
||||||
|
degrees = u'\u2199'.encode('utf-8')
|
||||||
|
elif (degrees > 247.5) and (degrees <= 292.5):
|
||||||
|
degrees = u'\u2190'.encode('utf-8')
|
||||||
|
elif (degrees > 292.5) and (degrees <= 337.5):
|
||||||
|
degrees = u'\u2196'.encode('utf-8')
|
||||||
|
|
||||||
|
if not icao_code.startswith('EN') and not icao_code.startswith('ED'):
|
||||||
|
wind = '%s %skt (%s)' % (description, speed, degrees)
|
||||||
|
elif icao_code.startswith('ED'):
|
||||||
|
kmh = int(round(speed * 1.852, 0))
|
||||||
|
wind = '%s %skm/h (%skt) (%s)' % (description, kmh, speed, degrees)
|
||||||
|
elif icao_code.startswith('EN'):
|
||||||
|
ms = int(round(speed * 0.514444444, 0))
|
||||||
|
wind = '%s %sm/s (%skt) (%s)' % (description, ms, speed, degrees)
|
||||||
|
else: wind = '(wind unknown)'
|
||||||
|
|
||||||
|
if visibility:
|
||||||
|
visibility = visibility + 'm'
|
||||||
|
else: visibility = '(visibility unknown)'
|
||||||
|
|
||||||
|
if cover:
|
||||||
|
level = None
|
||||||
|
for c in cover:
|
||||||
|
if c.startswith('OVC') or c.startswith('VV'):
|
||||||
|
if (level is None) or (level < 8):
|
||||||
|
level = 8
|
||||||
|
elif c.startswith('BKN'):
|
||||||
|
if (level is None) or (level < 5):
|
||||||
|
level = 5
|
||||||
|
elif c.startswith('SCT'):
|
||||||
|
if (level is None) or (level < 3):
|
||||||
|
level = 3
|
||||||
|
elif c.startswith('FEW'):
|
||||||
|
if (level is None) or (level < 1):
|
||||||
|
level = 1
|
||||||
|
elif c.startswith('SKC') or c.startswith('CLR'):
|
||||||
|
if level is None:
|
||||||
|
level = 0
|
||||||
|
|
||||||
|
if level == 8:
|
||||||
|
cover = u'Overcast \u2601'.encode('utf-8')
|
||||||
|
elif level == 5:
|
||||||
|
cover = 'Cloudy'
|
||||||
|
elif level == 3:
|
||||||
|
cover = 'Scattered'
|
||||||
|
elif (level == 1) or (level == 0):
|
||||||
|
cover = u'Clear \u263C'.encode('utf-8')
|
||||||
|
else: cover = 'Cover Unknown'
|
||||||
|
else: cover = 'Cover Unknown'
|
||||||
|
|
||||||
|
if temp:
|
||||||
|
if '/' in temp:
|
||||||
|
temp = temp.split('/')[0]
|
||||||
|
else: temp = temp.split('.')[0]
|
||||||
|
if temp.startswith('M'):
|
||||||
|
temp = '-' + temp[1:]
|
||||||
|
try: temp = int(temp)
|
||||||
|
except ValueError: temp = '?'
|
||||||
|
else: temp = '?'
|
||||||
|
|
||||||
|
if pressure:
|
||||||
|
if pressure.startswith('Q'):
|
||||||
|
pressure = pressure.lstrip('Q')
|
||||||
|
if pressure != 'NIL':
|
||||||
|
pressure = str(int(pressure)) + 'mb'
|
||||||
|
else: pressure = '?mb'
|
||||||
|
elif pressure.startswith('A'):
|
||||||
|
pressure = pressure.lstrip('A')
|
||||||
|
if pressure != 'NIL':
|
||||||
|
inches = pressure[:2] + '.' + pressure[2:]
|
||||||
|
mb = int(float(inches) * 33.7685)
|
||||||
|
pressure = '%sin (%smb)' % (inches, mb)
|
||||||
|
else: pressure = '?mb'
|
||||||
|
|
||||||
|
if isinstance(temp, int):
|
||||||
|
f = round((temp * 1.8) + 32, 2)
|
||||||
|
temp = u'%s\u2109 (%s\u2103)'.encode('utf-8') % (f, temp)
|
||||||
|
else: pressure = '?mb'
|
||||||
|
if isinstance(temp, int):
|
||||||
|
temp = u'%s\u2103'.encode('utf-8') % temp
|
||||||
|
|
||||||
|
if cond:
|
||||||
|
conds = cond
|
||||||
|
cond = ''
|
||||||
|
|
||||||
|
intensities = {
|
||||||
|
'-': 'Light',
|
||||||
|
'+': 'Heavy'
|
||||||
|
}
|
||||||
|
|
||||||
|
descriptors = {
|
||||||
|
'MI': 'Shallow',
|
||||||
|
'PR': 'Partial',
|
||||||
|
'BC': 'Patches',
|
||||||
|
'DR': 'Drifting',
|
||||||
|
'BL': 'Blowing',
|
||||||
|
'SH': 'Showers of',
|
||||||
|
'TS': 'Thundery',
|
||||||
|
'FZ': 'Freezing',
|
||||||
|
'VC': 'In the vicinity:'
|
||||||
|
}
|
||||||
|
|
||||||
|
phenomena = {
|
||||||
|
'DZ': 'Drizzle',
|
||||||
|
'RA': 'Rain',
|
||||||
|
'SN': 'Snow',
|
||||||
|
'SG': 'Snow Grains',
|
||||||
|
'IC': 'Ice Crystals',
|
||||||
|
'PL': 'Ice Pellets',
|
||||||
|
'GR': 'Hail',
|
||||||
|
'GS': 'Small Hail',
|
||||||
|
'UP': 'Unknown Precipitation',
|
||||||
|
'BR': 'Mist',
|
||||||
|
'FG': 'Fog',
|
||||||
|
'FU': 'Smoke',
|
||||||
|
'VA': 'Volcanic Ash',
|
||||||
|
'DU': 'Dust',
|
||||||
|
'SA': 'Sand',
|
||||||
|
'HZ': 'Haze',
|
||||||
|
'PY': 'Spray',
|
||||||
|
'PO': 'Whirls',
|
||||||
|
'SQ': 'Squalls',
|
||||||
|
'FC': 'Tornado',
|
||||||
|
'SS': 'Sandstorm',
|
||||||
|
'DS': 'Duststorm',
|
||||||
|
# ? Cf. http://swhack.com/logs/2007-10-05#T07-58-56
|
||||||
|
'TS': 'Thunderstorm',
|
||||||
|
'SH': 'Showers'
|
||||||
|
}
|
||||||
|
|
||||||
|
for c in conds:
|
||||||
|
if c.endswith('//'):
|
||||||
|
if cond: cond += ', '
|
||||||
|
cond += 'Some Precipitation'
|
||||||
|
elif len(c) == 5:
|
||||||
|
intensity = intensities[c[0]]
|
||||||
|
descriptor = descriptors[c[1:3]]
|
||||||
|
phenomenon = phenomena.get(c[3:], c[3:])
|
||||||
|
if cond: cond += ', '
|
||||||
|
cond += intensity + ' ' + descriptor + ' ' + phenomenon
|
||||||
|
elif len(c) == 4:
|
||||||
|
descriptor = descriptors.get(c[:2], c[:2])
|
||||||
|
phenomenon = phenomena.get(c[2:], c[2:])
|
||||||
|
if cond: cond += ', '
|
||||||
|
cond += descriptor + ' ' + phenomenon
|
||||||
|
elif len(c) == 3:
|
||||||
|
intensity = intensities.get(c[0], c[0])
|
||||||
|
phenomenon = phenomena.get(c[1:], c[1:])
|
||||||
|
if cond: cond += ', '
|
||||||
|
cond += intensity + ' ' + phenomenon
|
||||||
|
elif len(c) == 2:
|
||||||
|
phenomenon = phenomena.get(c, c)
|
||||||
|
if cond: cond += ', '
|
||||||
|
cond += phenomenon
|
||||||
|
|
||||||
|
# if not cond:
|
||||||
|
# format = u'%s at %s: %s, %s, %s, %s'
|
||||||
|
# args = (icao, time, cover, temp, pressure, wind)
|
||||||
|
# else:
|
||||||
|
# format = u'%s at %s: %s, %s, %s, %s, %s'
|
||||||
|
# args = (icao, time, cover, temp, pressure, cond, wind)
|
||||||
|
|
||||||
|
if not cond:
|
||||||
|
format = u'%s, %s, %s, %s - %s %s'
|
||||||
|
args = (cover, temp, pressure, wind, str(icao_code), time)
|
||||||
|
else:
|
||||||
|
format = u'%s, %s, %s, %s, %s - %s, %s'
|
||||||
|
args = (cover, temp, pressure, cond, wind, str(icao_code), time)
|
||||||
|
|
||||||
|
self.msg(origin.sender, format.encode('utf-8') % args)
|
||||||
|
f_weather.rule = (['weather'], r'(.*)')
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
print __doc__
|
|
@ -0,0 +1,146 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
"""
|
||||||
|
wikipedia.py - Phenny Wikipedia Module
|
||||||
|
Copyright 2008, Sean B. Palmer, inamidst.com
|
||||||
|
Licensed under the Eiffel Forum License 2.
|
||||||
|
|
||||||
|
http://inamidst.com/phenny/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import re, urllib
|
||||||
|
import web
|
||||||
|
|
||||||
|
wikiuri = 'http://en.wikipedia.org/wiki/%s'
|
||||||
|
wikisearch = 'http://en.wikipedia.org/wiki/Special:Search?' \
|
||||||
|
+ 'search=%s&fulltext=Search'
|
||||||
|
|
||||||
|
r_tr = re.compile(r'(?ims)<tr[^>]*>.*?</tr>')
|
||||||
|
r_paragraph = re.compile(r'(?ims)<p[^>]*>.*?</p>|<li(?!n)[^>]*>.*?</li>')
|
||||||
|
r_tag = re.compile(r'<(?!!)[^>]+>')
|
||||||
|
r_whitespace = re.compile(r'[\t\r\n ]+')
|
||||||
|
r_redirect = re.compile(
|
||||||
|
r'(?ims)class=.redirectText.>\s*<a\s*href=./wiki/([^"/]+)'
|
||||||
|
)
|
||||||
|
|
||||||
|
abbrs = ['etc', 'ca', 'cf', 'Co', 'Ltd', 'Inc', 'Mt', 'Mr', 'Mrs',
|
||||||
|
'Dr', 'Ms', 'Rev', 'Fr', 'St', 'Sgt', 'pron', 'approx', 'lit',
|
||||||
|
'syn'] \
|
||||||
|
+ list('ABCDEFGHIJKLMNOPQRSTUVWXYZ') \
|
||||||
|
+ list('abcdefghijklmnopqrstuvwxyz')
|
||||||
|
t_sentence = r'^.{5,}?(?<!\b%s)(?:\.(?= [A-Z0-9]|\Z)|\Z)'
|
||||||
|
r_sentence = re.compile(t_sentence % r')(?<!\b'.join(abbrs))
|
||||||
|
|
||||||
|
def unescape(s):
|
||||||
|
s = s.replace('>', '>')
|
||||||
|
s = s.replace('<', '<')
|
||||||
|
s = s.replace('&', '&')
|
||||||
|
s = s.replace(' ', ' ')
|
||||||
|
return s
|
||||||
|
|
||||||
|
def text(html):
|
||||||
|
html = r_tag.sub('', html)
|
||||||
|
html = r_whitespace.sub(' ', html)
|
||||||
|
return unescape(html).strip()
|
||||||
|
|
||||||
|
def search(term):
|
||||||
|
try: import google
|
||||||
|
except ImportError, e:
|
||||||
|
print e
|
||||||
|
return term
|
||||||
|
|
||||||
|
term = term.replace('_', ' ')
|
||||||
|
uri = google.google('site:en.wikipedia.org %s' % term)
|
||||||
|
if uri:
|
||||||
|
return uri[len('http://en.wikipedia.org/wiki/'):]
|
||||||
|
else: return term
|
||||||
|
|
||||||
|
def wikipedia(term, last=False):
|
||||||
|
bytes = web.get(wikiuri % urllib.quote(term))
|
||||||
|
bytes = r_tr.sub('', bytes)
|
||||||
|
|
||||||
|
if not last:
|
||||||
|
r = r_redirect.search(bytes[:4096])
|
||||||
|
if r:
|
||||||
|
term = urllib.unquote(r.group(1))
|
||||||
|
return wikipedia(term, last=True)
|
||||||
|
|
||||||
|
paragraphs = r_paragraph.findall(bytes)
|
||||||
|
|
||||||
|
if not paragraphs:
|
||||||
|
if not last:
|
||||||
|
term = search(term)
|
||||||
|
return wikipedia(term, last=True)
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Pre-process
|
||||||
|
paragraphs = [para for para in paragraphs
|
||||||
|
if (para and 'technical limitations' not in para
|
||||||
|
and 'window.showTocToggle' not in para
|
||||||
|
and 'Deletion_policy' not in para
|
||||||
|
and 'Template:AfD_footer' not in para
|
||||||
|
and not (para.startswith('<p><i>') and
|
||||||
|
para.endswith('</i></p>'))
|
||||||
|
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]
|
||||||
|
|
||||||
|
for i, para in enumerate(paragraphs):
|
||||||
|
para = para.replace('<sup>', '|')
|
||||||
|
para = para.replace('</sup>', '|')
|
||||||
|
paragraphs[i] = text(para).strip()
|
||||||
|
|
||||||
|
# Post-process
|
||||||
|
paragraphs = [para for para in paragraphs if
|
||||||
|
(para and not (para.endswith(':') and len(para) < 150))]
|
||||||
|
|
||||||
|
para = text(paragraphs[0])
|
||||||
|
m = r_sentence.match(para)
|
||||||
|
|
||||||
|
if not m:
|
||||||
|
if not last:
|
||||||
|
term = search(term)
|
||||||
|
return wikipedia(term, last=True)
|
||||||
|
return None
|
||||||
|
sentence = m.group(0)
|
||||||
|
|
||||||
|
maxlength = 275
|
||||||
|
if len(sentence) > maxlength:
|
||||||
|
sentence = sentence[:maxlength]
|
||||||
|
words = sentence[:-5].split(' ')
|
||||||
|
words.pop()
|
||||||
|
sentence = ' '.join(words) + ' [...]'
|
||||||
|
|
||||||
|
if ((sentence == 'Wikipedia does not have an article with this exact name.')
|
||||||
|
or (sentence == 'Wikipedia does not have a page with this exact name.')):
|
||||||
|
if not last:
|
||||||
|
term = search(term)
|
||||||
|
return wikipedia(term, last=True)
|
||||||
|
return None
|
||||||
|
|
||||||
|
sentence = '"' + sentence.replace('"', "'") + '"'
|
||||||
|
return sentence + ' - ' + (wikiuri % term)
|
||||||
|
|
||||||
|
def wik(phenny, input):
|
||||||
|
origterm = input.groups()[1]
|
||||||
|
term = urllib.unquote(origterm)
|
||||||
|
if not term:
|
||||||
|
return phenny.say(origin.sender, 'Maybe you meant ".wik Zen"?')
|
||||||
|
|
||||||
|
term = term[0].upper() + term[1:]
|
||||||
|
term = term.replace(' ', '_')
|
||||||
|
|
||||||
|
try: result = wikipedia(term)
|
||||||
|
except IOError:
|
||||||
|
error = "Can't connect to en.wikipedia.org (%s)" % (wikiuri % term)
|
||||||
|
return phenny.say(error)
|
||||||
|
|
||||||
|
if result is not None:
|
||||||
|
phenny.say(result)
|
||||||
|
else: phenny.say('Can\'t find anything in Wikipedia for "%s".' % origterm)
|
||||||
|
|
||||||
|
wik.commands = ['wik']
|
||||||
|
wik.priority = 'high'
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
print __doc__.strip()
|
|
@ -0,0 +1,22 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
"""
|
||||||
|
freenode.py - Freenode Specific Stuff
|
||||||
|
Copyright 2008, Sean B. Palmer, inamidst.com
|
||||||
|
Licensed under the Eiffel Forum License 2.
|
||||||
|
|
||||||
|
http://inamidst.com/phenny/
|
||||||
|
"""
|
||||||
|
|
||||||
|
def replaced(phenny, input):
|
||||||
|
command = input.group(1)
|
||||||
|
response = {
|
||||||
|
'cp': '.cp has been replaced by .u',
|
||||||
|
'pc': '.pc has been replaced by .u',
|
||||||
|
'unicode': '.unicode has been replaced by .u'
|
||||||
|
}[command]
|
||||||
|
phenny.reply(response)
|
||||||
|
replaced.commands = ['cp', 'pc', 'unicode']
|
||||||
|
replaced.priority = 'low'
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
print __doc__.strip()
|
|
@ -0,0 +1,143 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
"""
|
||||||
|
phenny - An IRC Bot
|
||||||
|
Copyright 2008, Sean B. Palmer, inamidst.com
|
||||||
|
Licensed under the Eiffel Forum License 2.
|
||||||
|
|
||||||
|
http://inamidst.com/phenny/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys, os, imp, optparse
|
||||||
|
from textwrap import dedent as trim
|
||||||
|
|
||||||
|
dotdir = os.path.expanduser('~/.phenny')
|
||||||
|
|
||||||
|
def check_python_version():
|
||||||
|
if sys.version_info < (2, 4):
|
||||||
|
error = 'Error: Requires Python 2.4 or later, from www.python.org'
|
||||||
|
print >> sys.stderr, error
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
def create_default_config(fn):
|
||||||
|
f = open(fn, 'w')
|
||||||
|
print >> f, trim("""\
|
||||||
|
nick = 'phenny'
|
||||||
|
host = 'irc.example.net'
|
||||||
|
channels = ['#example', '#test']
|
||||||
|
owner = 'yournickname'
|
||||||
|
|
||||||
|
# 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']
|
||||||
|
|
||||||
|
# If you want to enumerate a list of modules rather than disabling
|
||||||
|
# some, use "enable = ['example']", which takes precedent over disable
|
||||||
|
#
|
||||||
|
# enable = []
|
||||||
|
|
||||||
|
# Modules to load from the opt directory
|
||||||
|
opt = []
|
||||||
|
|
||||||
|
# EOF
|
||||||
|
""")
|
||||||
|
f.close()
|
||||||
|
|
||||||
|
def create_dotdir(dotdir):
|
||||||
|
print 'Creating a config directory at ~/.phenny...'
|
||||||
|
try: os.mkdir(dotdir)
|
||||||
|
except Exception, e:
|
||||||
|
print >> sys.stderr, 'There was a problem creating %s:' % dotdir
|
||||||
|
print >> sys.stderr, e.__class__, str(e)
|
||||||
|
print >> sys.stderr, 'Please fix this and then run phenny again.'
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
print 'Creating a default config file at ~/.phenny/default.py...'
|
||||||
|
default = os.path.join(dotdir, 'default.py')
|
||||||
|
create_default_config(default)
|
||||||
|
|
||||||
|
print 'Done; now you can edit default.py, and run phenny! Enjoy.'
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
def check_dotdir():
|
||||||
|
if not os.path.isdir(dotdir):
|
||||||
|
create_dotdir(dotdir)
|
||||||
|
|
||||||
|
def config_names(config):
|
||||||
|
config = config or 'default'
|
||||||
|
|
||||||
|
def files(d):
|
||||||
|
names = os.listdir(d)
|
||||||
|
return list(os.path.join(d, fn) for fn in names if fn.endswith('.py'))
|
||||||
|
|
||||||
|
here = os.path.join('.', config)
|
||||||
|
if os.path.isfile(here):
|
||||||
|
return [here]
|
||||||
|
if os.path.isfile(here + '.py'):
|
||||||
|
return [here + '.py']
|
||||||
|
if os.path.isdir(here):
|
||||||
|
return files(here)
|
||||||
|
|
||||||
|
there = os.path.join(dotdir, config)
|
||||||
|
if os.path.isfile(there):
|
||||||
|
return [there]
|
||||||
|
if os.path.isfile(there + '.py'):
|
||||||
|
return [there + '.py']
|
||||||
|
if os.path.isdir(there):
|
||||||
|
return files(there)
|
||||||
|
|
||||||
|
print >> sys.stderr, "Error: Couldn't find a config file!"
|
||||||
|
print >> sys.stderr, 'What happened to ~/.phenny/default.py?'
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
def main(argv=None):
|
||||||
|
# Step One: Check Dependencies
|
||||||
|
|
||||||
|
check_python_version() # require python2.4 or later
|
||||||
|
check_dotdir() # require ~/.phenny, or make it and exit
|
||||||
|
|
||||||
|
# Step Two: Parse The Command Line
|
||||||
|
|
||||||
|
parser = optparse.OptionParser('%prog [options]')
|
||||||
|
parser.add_option('-c', '--config', metavar='fn',
|
||||||
|
help='use this configuration file or directory')
|
||||||
|
opts, args = parser.parse_args(argv)
|
||||||
|
if args: print >> sys.stderr, 'Warning: ignoring spurious arguments'
|
||||||
|
|
||||||
|
# Step Three: Load The Configurations
|
||||||
|
|
||||||
|
config_modules = []
|
||||||
|
for config_name in config_names(opts.config):
|
||||||
|
name = os.path.basename(config_name).split('.')[0] + '_config'
|
||||||
|
module = imp.load_source(name, config_name)
|
||||||
|
module.filename = config_name
|
||||||
|
if not hasattr(module, 'prefix'):
|
||||||
|
module.prefix = r'\.'
|
||||||
|
if not hasattr(module, 'name'):
|
||||||
|
module.name = 'Phenny Palmersbot, http://inamidst.com/phenny/'
|
||||||
|
|
||||||
|
if module.host == 'irc.example.net':
|
||||||
|
error = ('Error: you must edit the config file first!\n' +
|
||||||
|
"You're currently using %s" % module.filename)
|
||||||
|
print >> sys.stderr, error
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
config_modules.append(module)
|
||||||
|
|
||||||
|
# Step Four: Load Phenny
|
||||||
|
|
||||||
|
try: from __init__ import run
|
||||||
|
except ImportError:
|
||||||
|
try: from phenny import run
|
||||||
|
except ImportError:
|
||||||
|
print >> sys.stderr, "Error: Couldn't find phenny to import"
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Step Five: Initialise And Run The Phennies
|
||||||
|
|
||||||
|
# @@ ignore SIGHUP
|
||||||
|
for config_module in config_modules:
|
||||||
|
run(config_module) # @@ thread this
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
|
@ -0,0 +1,25 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
"""
|
||||||
|
tools.py - Phenny Tools
|
||||||
|
Copyright 2008, Sean B. Palmer, inamidst.com
|
||||||
|
Licensed under the Eiffel Forum License 2.
|
||||||
|
|
||||||
|
http://inamidst.com/phenny/
|
||||||
|
"""
|
||||||
|
|
||||||
|
def deprecated(old):
|
||||||
|
def new(phenny, input, old=old):
|
||||||
|
self = phenny
|
||||||
|
origin = type('Origin', (object,), {
|
||||||
|
'sender': input.sender,
|
||||||
|
'nick': input.nick
|
||||||
|
})()
|
||||||
|
match = input.match
|
||||||
|
args = [input.bytes, input.sender, '@@']
|
||||||
|
|
||||||
|
old(self, origin, match, args)
|
||||||
|
new.__name__ = old.__name__
|
||||||
|
return new
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
print __doc__.strip()
|
|
@ -0,0 +1,38 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
"""
|
||||||
|
web.py - Web Facilities
|
||||||
|
Author: Sean B. Palmer, inamidst.com
|
||||||
|
About: http://inamidst.com/phenny/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import urllib
|
||||||
|
|
||||||
|
class Grab(urllib.URLopener):
|
||||||
|
def __init__(self, *args):
|
||||||
|
self.version = 'Mozilla/5.0 (Phenny)'
|
||||||
|
urllib.URLopener.__init__(self, *args)
|
||||||
|
def http_error_default(self, url, fp, errcode, errmsg, headers):
|
||||||
|
return urllib.addinfourl(fp, [headers, errcode], "http:" + url)
|
||||||
|
urllib._urlopener = Grab()
|
||||||
|
|
||||||
|
def get(uri):
|
||||||
|
u = urllib.urlopen(uri)
|
||||||
|
bytes = u.read()
|
||||||
|
u.close()
|
||||||
|
return bytes
|
||||||
|
|
||||||
|
def head(uri):
|
||||||
|
u = urllib.urlopen(uri)
|
||||||
|
info = u.info()
|
||||||
|
u.close()
|
||||||
|
return info
|
||||||
|
|
||||||
|
def post(uri, query):
|
||||||
|
data = urllib.urlencode(query)
|
||||||
|
u = urllib.urlopen(uri, data)
|
||||||
|
bytes = u.read()
|
||||||
|
u.close()
|
||||||
|
return bytes
|
||||||
|
|
||||||
|
if __name__=="__main__":
|
||||||
|
main()
|
Loading…
Reference in New Issue