Skip to content

Commit

Permalink
second commit, hopefully overwrite the first one
Browse files Browse the repository at this point in the history
  • Loading branch information
Steve-V committed Apr 16, 2011
0 parents commit c788170
Show file tree
Hide file tree
Showing 35 changed files with 7,657 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
*~
*pyc
.directory
25 changes: 25 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Makefile
# Copyright 2008, Sean B. Palmer, inamidst.com
# Licensed under the Eiffel Forum License 2.

# archive - Create phenny.tar.bz2 using git archive
archive: ;
# hg archive -t tbz2 phenny-hg.tar.bz2
git archive --format=tar --prefix=phenny/ HEAD | bzip2 > phenny.tar.bz2

# ci - Check the code into git and push to github
ci: ;
# hg ci
git commit -a && git push origin master

# log - Show a log of recent updates
log: ;
# git log --date=short --format='%h %ad %s'
git graph

# sync - Push phenny to pubble:opt/phenny/
sync: ;
rsync -avz ./ pubble:opt/phenny/

help: ;
@egrep '^# [a-z]+ - ' Makefile | sed 's/# //'
10 changes: 10 additions & 0 deletions README.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
Installation &c.

1) Run ./phenny - this creates a default config file
2) Edit ~/.phenny/default.py
3) Run ./phenny - this now runs phenny with your settings

Enjoy!

--
Sean B. Palmer, http://inamidst.com/sbp/
62 changes: 62 additions & 0 deletions __init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#!/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, os, time, threading, signal
import bot

class Watcher(object):
# Cf. http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/496735
def __init__(self):
self.child = os.fork()
if self.child != 0:
self.watch()

def watch(self):
try: os.wait()
except KeyboardInterrupt:
self.kill()
sys.exit()

def kill(self):
try: os.kill(self.child, signal.SIGKILL)
except OSError: pass

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, config.port)

try: Watcher()
except Exception, e:
print >> sys.stderr, 'Warning:', e, '(in __init__.py)'

while True:
try: connect(config)
except KeyboardInterrupt:
sys.exit()

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,))
if hasattr(t, 'run'):
t.run()
else: t.start()

if __name__ == '__main__':
print __doc__
227 changes: 227 additions & 0 deletions bot.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
#!/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, threading, imp
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):
args = (config.nick, config.name, config.channels, config.password)
irc.Bot.__init__(self, *args)
self.config = config
self.doc = {}
self.stats = {}
self.setup()

def setup(self):
self.variables = {}

filenames = []
if not hasattr(self.config, 'enable'):
for fn in os.listdir(os.path.join(home, 'modules')):
if fn.endswith('.py') and not fn.startswith('_'):
filenames.append(os.path.join(home, 'modules', fn))
else:
for fn in self.config.enable:
filenames.append(os.path.join(home, 'modules', fn + '.py'))

if hasattr(self.config, 'extra'):
for fn in self.config.extra:
if os.path.isfile(fn):
filenames.append(fn)
elif os.path.isdir(fn):
for n in os.listdir(fn):
if n.endswith('.py') and not n.startswith('_'):
filenames.append(os.path.join(fn, n))

modules = []
excluded_modules = getattr(self.config, 'exclude', [])
for filename in filenames:
name = os.path.basename(filename)[:-3]
if name in excluded_modules: continue
try: module = imp.load_source(name, filename)
except Exception, e:
print >> sys.stderr, "Error loading %s: %s (in bot.py)" % (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
# register documentation
if not hasattr(func, 'name'):
func.name = func.__name__
if func.__doc__:
if hasattr(func, 'example'):
example = func.example
example = example.replace('$nickname', self.nick)
else: example = None
self.doc[func.name] = (func.__doc__, example)
self.commands[priority].setdefault(regexp, []).append(func)

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)\b(?: +(?:%s))?' % (command, pattern)
regexp = re.compile(prefix + command)
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):
sender = origin.sender or text
if attr == 'reply':
return (lambda msg:
self.bot.msg(sender, origin.nick + ': ' + msg))
elif attr == 'say':
return lambda msg: self.bot.msg(sender, msg)
return getattr(self.bot, attr)

return PhennyWrapper(self)

def input(self, origin, text, bytes, match, event, args):
class CommandInput(unicode):
def __new__(cls, text, origin, bytes, match, event, args):
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.args = args
s.admin = origin.nick in self.config.admins
s.owner = origin.nick == self.config.owner
return s

return CommandInput(text, origin, bytes, match, event, args)

def call(self, func, origin, phenny, input):
try: func(phenny, input)
except Exception, e:
self.error(origin)

def limit(self, origin, func):
if origin.sender and origin.sender.startswith('#'):
if hasattr(self.config, 'limit'):
limits = self.config.limit.get(origin.sender)
if limits and (func.__module__ not in limits):
return True
return False

def dispatch(self, origin, args):
bytes, event, args = args[0], args[1], args[2:]
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:
if self.limit(origin, func): continue

phenny = self.wrapped(origin, text, match)
input = self.input(origin, text, bytes, match, event, args)

if func.thread:
targs = (func, origin, phenny, input)
t = threading.Thread(target=self.call, args=targs)
t.start()
else: self.call(func, origin, phenny, input)

for source in [origin.sender, origin.nick]:
try: self.stats[(func.name, source)] += 1
except KeyError:
self.stats[(func.name, source)] = 1

if __name__ == '__main__':
print __doc__
Loading

0 comments on commit c788170

Please sign in to comment.