commit
7dc5ece29b
|
@ -2,10 +2,9 @@ language: python
|
||||||
sudo: false
|
sudo: false
|
||||||
cache: pip
|
cache: pip
|
||||||
python:
|
python:
|
||||||
- 3.2
|
|
||||||
- 3.3
|
|
||||||
- 3.4
|
- 3.4
|
||||||
- 3.5
|
- 3.5
|
||||||
|
- 3.6
|
||||||
install:
|
install:
|
||||||
- pip install -r requirements.txt
|
- pip install -r requirements.txt
|
||||||
script: nosetests
|
script: nosetests
|
||||||
|
|
|
@ -12,7 +12,7 @@ will need to be updated to run on Python3 if they do not already. All of the
|
||||||
core modules have been ported, removed, or replaced.
|
core modules have been ported, removed, or replaced.
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
* Python 3.2+
|
* Python 3.4+
|
||||||
* [python-requests](http://docs.python-requests.org/en/latest/)
|
* [python-requests](http://docs.python-requests.org/en/latest/)
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
59
__init__.py
59
__init__.py
|
@ -7,9 +7,14 @@ Licensed under the Eiffel Forum License 2.
|
||||||
http://inamidst.com/phenny/
|
http://inamidst.com/phenny/
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import sys, os, time, threading, signal
|
import os
|
||||||
|
import signal
|
||||||
|
import sys
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
|
||||||
class Watcher(object):
|
|
||||||
|
class Watcher(object):
|
||||||
# Cf. http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/496735
|
# Cf. http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/496735
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.child = os.fork()
|
self.child = os.fork()
|
||||||
|
@ -18,51 +23,65 @@ class Watcher(object):
|
||||||
self.watch()
|
self.watch()
|
||||||
|
|
||||||
def watch(self):
|
def watch(self):
|
||||||
try: os.wait()
|
try:
|
||||||
|
os.wait()
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
self.kill()
|
self.kill()
|
||||||
sys.exit()
|
sys.exit()
|
||||||
|
|
||||||
def kill(self):
|
def kill(self):
|
||||||
try: os.kill(self.child, signal.SIGKILL)
|
try:
|
||||||
except OSError: pass
|
os.kill(self.child, signal.SIGKILL)
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
|
||||||
def sig_term(self, signum, frame):
|
def sig_term(self, signum, frame):
|
||||||
self.kill()
|
self.kill()
|
||||||
sys.exit()
|
sys.exit()
|
||||||
|
|
||||||
def run_phenny(config):
|
|
||||||
if hasattr(config, 'delay'):
|
|
||||||
delay = config.delay
|
|
||||||
else: delay = 20
|
|
||||||
|
|
||||||
def connect(config):
|
def run_phenny(config):
|
||||||
|
if hasattr(config, 'delay'):
|
||||||
|
delay = config.delay
|
||||||
|
else:
|
||||||
|
delay = 20
|
||||||
|
|
||||||
|
def connect(config):
|
||||||
import bot
|
import bot
|
||||||
p = bot.Phenny(config)
|
p = bot.Phenny(config)
|
||||||
p.run(config.host, config.port, config.ssl, config.ipv6,
|
|
||||||
config.ca_certs)
|
|
||||||
|
|
||||||
try: Watcher()
|
ssl_context = p.get_ssl_context(config.ca_certs)
|
||||||
|
if config.ssl_cert and config.ssl_key:
|
||||||
|
ssl_context.load_cert_chain(config.ssl_cert, config.ssl_key)
|
||||||
|
p.run(config.host, config.port, config.ssl, config.ipv6, None,
|
||||||
|
ssl_context)
|
||||||
|
|
||||||
|
try:
|
||||||
|
Watcher()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print('Warning:', e, '(in __init__.py)', file=sys.stderr)
|
print('Warning:', e, '(in __init__.py)', file=sys.stderr)
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
try: connect(config)
|
try:
|
||||||
|
connect(config)
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
sys.exit()
|
sys.exit()
|
||||||
|
|
||||||
if not isinstance(delay, int):
|
if not isinstance(delay, int):
|
||||||
break
|
break
|
||||||
|
|
||||||
warning = 'Warning: Disconnected. Reconnecting in %s seconds...' % delay
|
msg = "Warning: Disconnected. Reconnecting in {0} seconds..."
|
||||||
print(warning, file=sys.stderr)
|
print(msg.format(delay), file=sys.stderr)
|
||||||
time.sleep(delay)
|
time.sleep(delay)
|
||||||
|
|
||||||
def run(config):
|
|
||||||
|
def run(config):
|
||||||
t = threading.Thread(target=run_phenny, args=(config,))
|
t = threading.Thread(target=run_phenny, args=(config,))
|
||||||
if hasattr(t, 'run'):
|
if hasattr(t, 'run'):
|
||||||
t.run()
|
t.run()
|
||||||
else: t.start()
|
else:
|
||||||
|
t.start()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
print(__doc__)
|
print(__doc__)
|
||||||
|
|
175
irc.py
175
irc.py
|
@ -7,30 +7,35 @@ Licensed under the Eiffel Forum License 2.
|
||||||
http://inamidst.com/phenny/
|
http://inamidst.com/phenny/
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import sys, re, time, traceback
|
import asynchat
|
||||||
import socket, asyncore, asynchat
|
import asyncore
|
||||||
|
import re
|
||||||
|
import socket
|
||||||
import ssl
|
import ssl
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
|
||||||
|
|
||||||
class Origin(object):
|
class Origin(object):
|
||||||
source = re.compile(r'([^!]*)!?([^@]*)@?(.*)')
|
source = re.compile(r'([^!]*)!?([^@]*)@?(.*)')
|
||||||
|
|
||||||
def __init__(self, bot, source, args):
|
def __init__(self, bot, source, args):
|
||||||
if not source:
|
if not source:
|
||||||
source = ""
|
source = ""
|
||||||
match = Origin.source.match(source)
|
match = Origin.source.match(source)
|
||||||
self.nick, self.user, self.host = match.groups()
|
self.nick, self.user, self.host = match.groups()
|
||||||
|
|
||||||
if len(args) > 1:
|
if len(args) > 1:
|
||||||
target = args[1]
|
target = args[1]
|
||||||
else: target = None
|
else:
|
||||||
|
target = None
|
||||||
|
|
||||||
mappings = {bot.nick: self.nick, None: None}
|
mappings = {bot.nick: self.nick, None: None}
|
||||||
self.sender = mappings.get(target, target)
|
self.sender = mappings.get(target, target)
|
||||||
|
|
||||||
|
|
||||||
class Bot(asynchat.async_chat):
|
class Bot(asynchat.async_chat):
|
||||||
def __init__(self, nick, name, channels, password=None):
|
def __init__(self, nick, name, channels, password=None):
|
||||||
asynchat.async_chat.__init__(self)
|
asynchat.async_chat.__init__(self)
|
||||||
self.set_terminator(b'\n')
|
self.set_terminator(b'\n')
|
||||||
self.buffer = b''
|
self.buffer = b''
|
||||||
|
@ -52,97 +57,91 @@ class Bot(asynchat.async_chat):
|
||||||
asynchat.async_chat.initiate_send(self)
|
asynchat.async_chat.initiate_send(self)
|
||||||
self.sending.release()
|
self.sending.release()
|
||||||
|
|
||||||
# def push(self, *args, **kargs):
|
# def push(self, *args, **kargs):
|
||||||
# asynchat.async_chat.push(self, *args, **kargs)
|
# asynchat.async_chat.push(self, *args, **kargs)
|
||||||
|
|
||||||
def __write(self, args, text=None):
|
def __write(self, args, text=None):
|
||||||
# print 'PUSH: %r %r %r' % (self, args, text)
|
# print 'PUSH: %r %r %r' % (self, args, text)
|
||||||
try:
|
try:
|
||||||
if text is not None:
|
if text is not None:
|
||||||
# 510 because CR and LF count too, as nyuszika7h points out
|
# 510 because CR and LF count too, as nyuszika7h points out
|
||||||
self.push((b' '.join(args) + b' :' + text)[:510] + b'\r\n')
|
self.push((b' '.join(args) + b' :' + text)[:510] + b'\r\n')
|
||||||
else:
|
else:
|
||||||
self.push(b' '.join(args)[:512] + b'\r\n')
|
self.push(b' '.join(args)[:512] + b'\r\n')
|
||||||
except IndexError:
|
except IndexError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def write(self, args, text=None):
|
def write(self, args, text=None):
|
||||||
"""This is a safe version of __write"""
|
"""This is a safe version of __write"""
|
||||||
def safe(input):
|
def safe(input):
|
||||||
if type(input) == str:
|
if type(input) == str:
|
||||||
input = input.replace('\n', '')
|
input = input.replace('\n', '')
|
||||||
input = input.replace('\r', '')
|
input = input.replace('\r', '')
|
||||||
return input.encode('utf-8')
|
return input.encode('utf-8')
|
||||||
else:
|
else:
|
||||||
return input
|
return input
|
||||||
try:
|
try:
|
||||||
args = [safe(arg) for arg in args]
|
args = [safe(arg) for arg in args]
|
||||||
if text is not None:
|
if text is not None:
|
||||||
text = safe(text)
|
text = safe(text)
|
||||||
self.__write(args, text)
|
self.__write(args, text)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise
|
raise
|
||||||
#pass
|
|
||||||
|
|
||||||
def run(self, host, port=6667, ssl=False,
|
def run(self, host, port=6667, ssl=False, ipv6=False, ca_certs=None,
|
||||||
ipv6=False, ca_certs=None):
|
ssl_context=None):
|
||||||
self.ca_certs = ca_certs
|
if ssl_context is None:
|
||||||
self.initiate_connect(host, port, ssl, ipv6)
|
ssl_context = self.get_ssl_context(ca_certs)
|
||||||
|
self.initiate_connect(host, port, ssl, ipv6, ssl_context)
|
||||||
|
|
||||||
def initiate_connect(self, host, port, use_ssl, ipv6):
|
def get_ssl_context(self, ca_certs):
|
||||||
if self.verbose:
|
return ssl.create_default_context(
|
||||||
|
purpose=ssl.Purpose.SERVER_AUTH,
|
||||||
|
cafile=ca_certs)
|
||||||
|
|
||||||
|
def initiate_connect(self, host, port, use_ssl, ipv6, ssl_context):
|
||||||
|
if self.verbose:
|
||||||
message = 'Connecting to %s:%s...' % (host, port)
|
message = 'Connecting to %s:%s...' % (host, port)
|
||||||
print(message, end=' ', file=sys.stderr)
|
print(message, end=' ', file=sys.stderr)
|
||||||
if ipv6 and socket.has_ipv6:
|
if ipv6 and socket.has_ipv6:
|
||||||
af = socket.AF_INET6
|
af = socket.AF_INET6
|
||||||
else:
|
else:
|
||||||
af = socket.AF_INET
|
af = socket.AF_INET
|
||||||
self.create_socket(af, socket.SOCK_STREAM, use_ssl, host)
|
self.create_socket(af, socket.SOCK_STREAM, use_ssl, host, ssl_context)
|
||||||
self.connect((host, port))
|
self.connect((host, port))
|
||||||
try: asyncore.loop()
|
try:
|
||||||
except KeyboardInterrupt:
|
asyncore.loop()
|
||||||
|
except KeyboardInterrupt:
|
||||||
sys.exit()
|
sys.exit()
|
||||||
|
|
||||||
def create_socket(self, family, type, use_ssl=False, hostname=None):
|
def create_socket(self, family, type, use_ssl=False, hostname=None,
|
||||||
|
ssl_context=None):
|
||||||
self.family_and_type = family, type
|
self.family_and_type = family, type
|
||||||
sock = socket.socket(family, type)
|
sock = socket.socket(family, type)
|
||||||
if use_ssl:
|
if use_ssl:
|
||||||
# this stuff is all new in python 3.4, so fallback if needed
|
sock = ssl_context.wrap_socket(sock, server_hostname=hostname)
|
||||||
try:
|
|
||||||
context = ssl.create_default_context(
|
|
||||||
purpose=ssl.Purpose.SERVER_AUTH,
|
|
||||||
cafile=self.ca_certs)
|
|
||||||
sock = context.wrap_socket(sock, server_hostname=hostname)
|
|
||||||
except:
|
|
||||||
if self.ca_certs is None:
|
|
||||||
# default to standard path on most non-EL distros
|
|
||||||
ca_certs = "/etc/ssl/certs/ca-certificates.crt"
|
|
||||||
else:
|
|
||||||
ca_certs = self.ca_certs
|
|
||||||
sock = ssl.wrap_socket(sock, ssl_version=ssl.PROTOCOL_TLSv1,
|
|
||||||
cert_reqs=ssl.CERT_REQUIRED, ca_certs=ca_certs)
|
|
||||||
# FIXME: this doesn't work with SSL enabled
|
# FIXME: this doesn't work with SSL enabled
|
||||||
#sock.setblocking(False)
|
#sock.setblocking(False)
|
||||||
self.set_socket(sock)
|
self.set_socket(sock)
|
||||||
|
|
||||||
def handle_connect(self):
|
def handle_connect(self):
|
||||||
if self.verbose:
|
if self.verbose:
|
||||||
print('connected!', file=sys.stderr)
|
print('connected!', file=sys.stderr)
|
||||||
if self.password:
|
if self.password:
|
||||||
self.write(('PASS', self.password))
|
self.write(('PASS', self.password))
|
||||||
self.write(('NICK', self.nick))
|
self.write(('NICK', self.nick))
|
||||||
self.write(('USER', self.user, '+iw', self.nick), self.name)
|
self.write(('USER', self.user, '+iw', self.nick), self.name)
|
||||||
|
|
||||||
def handle_close(self):
|
def handle_close(self):
|
||||||
self.close()
|
self.close()
|
||||||
print('Closed!', file=sys.stderr)
|
print('Closed!', file=sys.stderr)
|
||||||
|
|
||||||
def collect_incoming_data(self, data):
|
def collect_incoming_data(self, data):
|
||||||
self.buffer += data
|
self.buffer += data
|
||||||
|
|
||||||
def found_terminator(self):
|
def found_terminator(self):
|
||||||
line = self.buffer
|
line = self.buffer
|
||||||
if line.endswith(b'\r'):
|
if line.endswith(b'\r'):
|
||||||
line = line[:-1]
|
line = line[:-1]
|
||||||
self.buffer = b''
|
self.buffer = b''
|
||||||
|
|
||||||
|
@ -151,35 +150,39 @@ class Bot(asynchat.async_chat):
|
||||||
except UnicodeDecodeError:
|
except UnicodeDecodeError:
|
||||||
line = line.decode('iso-8859-1')
|
line = line.decode('iso-8859-1')
|
||||||
|
|
||||||
if line.startswith(':'):
|
if line.startswith(':'):
|
||||||
source, line = line[1:].split(' ', 1)
|
source, line = line[1:].split(' ', 1)
|
||||||
else: source = None
|
else:
|
||||||
|
source = None
|
||||||
|
|
||||||
if ' :' in line:
|
if ' :' in line:
|
||||||
argstr, text = line.split(' :', 1)
|
argstr, text = line.split(' :', 1)
|
||||||
else: argstr, text = line, ''
|
else:
|
||||||
|
argstr, text = line, ''
|
||||||
args = argstr.split()
|
args = argstr.split()
|
||||||
|
|
||||||
origin = Origin(self, source, args)
|
origin = Origin(self, source, args)
|
||||||
self.dispatch(origin, tuple([text] + args))
|
self.dispatch(origin, tuple([text] + args))
|
||||||
|
|
||||||
if args[0] == 'PING':
|
if args[0] == 'PING':
|
||||||
self.write(('PONG', text))
|
self.write(('PONG', text))
|
||||||
|
|
||||||
def dispatch(self, origin, args):
|
def dispatch(self, origin, args):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def msg(self, recipient, text):
|
def msg(self, recipient, text):
|
||||||
self.sending.acquire()
|
self.sending.acquire()
|
||||||
|
|
||||||
# Cf. http://swhack.com/logs/2006-03-01#T19-43-25
|
# Cf. http://swhack.com/logs/2006-03-01#T19-43-25
|
||||||
if isinstance(text, str):
|
if isinstance(text, str):
|
||||||
try: text = text.encode('utf-8')
|
try:
|
||||||
except UnicodeEncodeError as e:
|
text = text.encode('utf-8')
|
||||||
|
except UnicodeEncodeError as e:
|
||||||
text = e.__class__ + ': ' + str(e)
|
text = e.__class__ + ': ' + str(e)
|
||||||
if isinstance(recipient, str):
|
if isinstance(recipient, str):
|
||||||
try: recipient = recipient.encode('utf-8')
|
try:
|
||||||
except UnicodeEncodeError as e:
|
recipient = recipient.encode('utf-8')
|
||||||
|
except UnicodeEncodeError as e:
|
||||||
return
|
return
|
||||||
|
|
||||||
# Split long messages
|
# Split long messages
|
||||||
|
@ -197,23 +200,23 @@ class Bot(asynchat.async_chat):
|
||||||
|
|
||||||
# No messages within the last 3 seconds? Go ahead!
|
# No messages within the last 3 seconds? Go ahead!
|
||||||
# Otherwise, wait so it's been at least 0.8 seconds + penalty
|
# Otherwise, wait so it's been at least 0.8 seconds + penalty
|
||||||
if self.stack:
|
if self.stack:
|
||||||
elapsed = time.time() - self.stack[-1][0]
|
elapsed = time.time() - self.stack[-1][0]
|
||||||
if elapsed < 3:
|
if elapsed < 3:
|
||||||
penalty = float(max(0, len(text) - 50)) / 70
|
penalty = float(max(0, len(text) - 50)) / 70
|
||||||
wait = 0.8 + penalty
|
wait = 0.8 + penalty
|
||||||
if elapsed < wait:
|
if elapsed < wait:
|
||||||
time.sleep(wait - elapsed)
|
time.sleep(wait - elapsed)
|
||||||
|
|
||||||
# Loop detection
|
# Loop detection
|
||||||
messages = [m[1] for m in self.stack[-8:]]
|
messages = [m[1] for m in self.stack[-8:]]
|
||||||
if messages.count(text) >= 5:
|
if messages.count(text) >= 5:
|
||||||
text = '...'
|
text = '...'
|
||||||
if messages.count('...') >= 3:
|
if messages.count('...') >= 3:
|
||||||
self.sending.release()
|
self.sending.release()
|
||||||
return
|
return
|
||||||
|
|
||||||
def safe(input):
|
def safe(input):
|
||||||
if type(input) == str:
|
if type(input) == str:
|
||||||
input = input.encode('utf-8')
|
input = input.encode('utf-8')
|
||||||
input = input.replace(b'\n', b'')
|
input = input.replace(b'\n', b'')
|
||||||
|
@ -228,43 +231,47 @@ class Bot(asynchat.async_chat):
|
||||||
text = "\x01ACTION {0}\x01".format(text)
|
text = "\x01ACTION {0}\x01".format(text)
|
||||||
return self.msg(recipient, text)
|
return self.msg(recipient, text)
|
||||||
|
|
||||||
def notice(self, dest, text):
|
def notice(self, dest, text):
|
||||||
self.write(('NOTICE', dest), text)
|
self.write(('NOTICE', dest), text)
|
||||||
|
|
||||||
def error(self, origin):
|
def error(self, origin):
|
||||||
try:
|
try:
|
||||||
import traceback
|
import traceback
|
||||||
trace = traceback.format_exc()
|
trace = traceback.format_exc()
|
||||||
print(trace)
|
print(trace)
|
||||||
lines = list(reversed(trace.splitlines()))
|
lines = list(reversed(trace.splitlines()))
|
||||||
|
|
||||||
report = [lines[0].strip()]
|
report = [lines[0].strip()]
|
||||||
for line in lines:
|
for line in lines:
|
||||||
line = line.strip()
|
line = line.strip()
|
||||||
if line.startswith('File "/'):
|
if line.startswith('File "/'):
|
||||||
report.append(line[0].lower() + line[1:])
|
report.append(line[0].lower() + line[1:])
|
||||||
break
|
break
|
||||||
else: report.append('source unknown')
|
else:
|
||||||
|
report.append('source unknown')
|
||||||
|
|
||||||
self.msg(origin.sender, report[0] + ' (' + report[1] + ')')
|
self.msg(origin.sender, report[0] + ' (' + report[1] + ')')
|
||||||
except: self.msg(origin.sender, "Got an error.")
|
except:
|
||||||
|
self.msg(origin.sender, "Got an error.")
|
||||||
|
|
||||||
|
|
||||||
class TestBot(Bot):
|
class TestBot(Bot):
|
||||||
def f_ping(self, origin, match, args):
|
def f_ping(self, origin, match, args):
|
||||||
delay = m.group(1)
|
delay = match.group(1)
|
||||||
if delay is not None:
|
if delay is not None:
|
||||||
import time
|
import time
|
||||||
time.sleep(int(delay))
|
time.sleep(int(delay))
|
||||||
self.msg(origin.sender, 'pong (%s)' % delay)
|
self.msg(origin.sender, 'pong (%s)' % delay)
|
||||||
else: self.msg(origin.sender, 'pong')
|
else:
|
||||||
|
self.msg(origin.sender, 'pong')
|
||||||
f_ping.rule = r'^\.ping(?:[ \t]+(\d+))?$'
|
f_ping.rule = r'^\.ping(?:[ \t]+(\d+))?$'
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
bot = TestBot('testbot007', 'testbot007', ['#wadsworth'])
|
bot = TestBot('testbot007', 'testbot007', ['#wadsworth'])
|
||||||
bot.run('irc.freenode.net')
|
bot.run('irc.freenode.net')
|
||||||
print(__doc__)
|
print(__doc__)
|
||||||
|
|
||||||
if __name__=="__main__":
|
|
||||||
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|
|
@ -184,16 +184,5 @@ def search(phenny, input):
|
||||||
phenny.reply(result)
|
phenny.reply(result)
|
||||||
search.commands = ['search']
|
search.commands = ['search']
|
||||||
|
|
||||||
def suggest(phenny, input):
|
|
||||||
if not input.group(2):
|
|
||||||
return phenny.reply("No query term.")
|
|
||||||
query = input.group(2)
|
|
||||||
uri = 'http://websitedev.de/temp-bin/suggest.pl?q='
|
|
||||||
answer = web.get(uri + web.quote(query).replace('+', '%2B'))
|
|
||||||
if answer:
|
|
||||||
phenny.say(answer)
|
|
||||||
else: phenny.reply('Sorry, no result.')
|
|
||||||
suggest.commands = ['suggest']
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
print(__doc__.strip())
|
print(__doc__.strip())
|
||||||
|
|
|
@ -8,7 +8,7 @@ import unittest
|
||||||
from mock import MagicMock, Mock
|
from mock import MagicMock, Mock
|
||||||
from modules.search import duck_api, google_search, google_count, \
|
from modules.search import duck_api, google_search, google_count, \
|
||||||
formatnumber, g, gc, gcs, bing_search, bing, duck_search, duck, \
|
formatnumber, g, gc, gcs, bing_search, bing, duck_search, duck, \
|
||||||
search, suggest
|
search
|
||||||
|
|
||||||
|
|
||||||
class TestSearch(unittest.TestCase):
|
class TestSearch(unittest.TestCase):
|
||||||
|
@ -75,10 +75,3 @@ class TestSearch(unittest.TestCase):
|
||||||
duck(self.phenny, input)
|
duck(self.phenny, input)
|
||||||
|
|
||||||
assert self.phenny.reply.called is True
|
assert self.phenny.reply.called is True
|
||||||
|
|
||||||
def test_suggest(self):
|
|
||||||
input = Mock(group=lambda x: 'vtluug')
|
|
||||||
suggest(self.phenny, input)
|
|
||||||
|
|
||||||
assert (self.phenny.reply.called is True or \
|
|
||||||
self.phenny.say.called is True)
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ import unittest
|
||||||
from mock import MagicMock, Mock
|
from mock import MagicMock, Mock
|
||||||
from modules import vtluugwiki
|
from modules import vtluugwiki
|
||||||
|
|
||||||
@unittest.skip('Skipping until wiki is back up')
|
|
||||||
class TestVtluugwiki(unittest.TestCase):
|
class TestVtluugwiki(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.phenny = MagicMock()
|
self.phenny = MagicMock()
|
||||||
|
|
121
phenny
121
phenny
|
@ -11,19 +11,23 @@ Run ./phenny, then edit ~/.phenny/default.py
|
||||||
Then run ./phenny again
|
Then run ./phenny again
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import sys, os, imp
|
|
||||||
import argparse
|
import argparse
|
||||||
|
import imp
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
from textwrap import dedent as trim
|
from textwrap import dedent as trim
|
||||||
|
|
||||||
dotdir = os.path.expanduser('~/.phenny')
|
dotdir = os.path.expanduser('~/.phenny')
|
||||||
|
|
||||||
def check_python_version():
|
|
||||||
if sys.version_info < (3, 0):
|
def check_python_version():
|
||||||
error = 'Error: Requires Python 3.0 or later, from www.python.org'
|
if sys.version_info < (3, 4):
|
||||||
|
error = 'Error: Requires Python 3.4 or later, from www.python.org'
|
||||||
print(error, file=sys.stderr)
|
print(error, file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
def create_default_config(fn):
|
|
||||||
|
def create_default_config(fn):
|
||||||
f = open(fn, 'w')
|
f = open(fn, 'w')
|
||||||
print(trim("""\
|
print(trim("""\
|
||||||
nick = 'phenny'
|
nick = 'phenny'
|
||||||
|
@ -51,7 +55,7 @@ def create_default_config(fn):
|
||||||
|
|
||||||
# If you want to enumerate a list of modules rather than disabling
|
# If you want to enumerate a list of modules rather than disabling
|
||||||
# some, use "enable = ['example']", which takes precedent over exclude
|
# some, use "enable = ['example']", which takes precedent over exclude
|
||||||
#
|
#
|
||||||
# enable = []
|
# enable = []
|
||||||
|
|
||||||
# Directories to load user modules from
|
# Directories to load user modules from
|
||||||
|
@ -59,7 +63,7 @@ def create_default_config(fn):
|
||||||
extra = []
|
extra = []
|
||||||
|
|
||||||
# Services to load: maps channel names to white or black lists
|
# Services to load: maps channel names to white or black lists
|
||||||
external = {
|
external = {
|
||||||
'#liberal': ['!'], # allow all
|
'#liberal': ['!'], # allow all
|
||||||
'#conservative': [], # allow none
|
'#conservative': [], # allow none
|
||||||
'*': ['!'] # default whitelist, allow all
|
'*': ['!'] # default whitelist, allow all
|
||||||
|
@ -69,6 +73,7 @@ def create_default_config(fn):
|
||||||
"""), file=f)
|
"""), file=f)
|
||||||
f.close()
|
f.close()
|
||||||
|
|
||||||
|
|
||||||
def create_default_config_file(dotdir):
|
def create_default_config_file(dotdir):
|
||||||
print('Creating a default config file at ~/.phenny/default.py...')
|
print('Creating a default config file at ~/.phenny/default.py...')
|
||||||
default = os.path.join(dotdir, 'default.py')
|
default = os.path.join(dotdir, 'default.py')
|
||||||
|
@ -77,10 +82,12 @@ def create_default_config_file(dotdir):
|
||||||
print('Done; now you can edit default.py, and run phenny! Enjoy.')
|
print('Done; now you can edit default.py, and run phenny! Enjoy.')
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
def create_dotdir(dotdir):
|
|
||||||
|
def create_dotdir(dotdir):
|
||||||
print('Creating a config directory at ~/.phenny...')
|
print('Creating a config directory at ~/.phenny...')
|
||||||
try: os.mkdir(dotdir)
|
try:
|
||||||
except Exception as e:
|
os.mkdir(dotdir)
|
||||||
|
except Exception as e:
|
||||||
print('There was a problem creating %s:' % dotdir, file=sys.stderr)
|
print('There was a problem creating %s:' % dotdir, file=sys.stderr)
|
||||||
print(e.__class__, str(e), file=sys.stderr)
|
print(e.__class__, str(e), file=sys.stderr)
|
||||||
print('Please fix this and then run phenny again.', file=sys.stderr)
|
print('Please fix this and then run phenny again.', file=sys.stderr)
|
||||||
|
@ -88,87 +95,96 @@ def create_dotdir(dotdir):
|
||||||
|
|
||||||
create_default_config_file(dotdir)
|
create_default_config_file(dotdir)
|
||||||
|
|
||||||
def check_dotdir():
|
|
||||||
|
def check_dotdir():
|
||||||
default = os.path.join(dotdir, 'default.py')
|
default = os.path.join(dotdir, 'default.py')
|
||||||
|
|
||||||
if not os.path.isdir(dotdir):
|
if not os.path.isdir(dotdir):
|
||||||
create_dotdir(dotdir)
|
create_dotdir(dotdir)
|
||||||
elif not os.path.isfile(default):
|
elif not os.path.isfile(default):
|
||||||
create_default_config_file(dotdir)
|
create_default_config_file(dotdir)
|
||||||
|
|
||||||
def config_names(config):
|
|
||||||
|
def config_names(config):
|
||||||
config = config or 'default'
|
config = config or 'default'
|
||||||
|
|
||||||
def files(d):
|
def files(d):
|
||||||
names = os.listdir(d)
|
names = os.listdir(d)
|
||||||
return list(os.path.join(d, fn) for fn in names if fn.endswith('.py'))
|
return list(os.path.join(d, fn) for fn in names if fn.endswith('.py'))
|
||||||
|
|
||||||
here = os.path.join('.', config)
|
here = os.path.join('.', config)
|
||||||
if os.path.isfile(here):
|
if os.path.isfile(here):
|
||||||
return [here]
|
return [here]
|
||||||
if os.path.isfile(here + '.py'):
|
if os.path.isfile(here + '.py'):
|
||||||
return [here + '.py']
|
return [here + '.py']
|
||||||
if os.path.isdir(here):
|
if os.path.isdir(here):
|
||||||
return files(here)
|
return files(here)
|
||||||
|
|
||||||
there = os.path.join(dotdir, config)
|
there = os.path.join(dotdir, config)
|
||||||
if os.path.isfile(there):
|
if os.path.isfile(there):
|
||||||
return [there]
|
return [there]
|
||||||
if os.path.isfile(there + '.py'):
|
if os.path.isfile(there + '.py'):
|
||||||
return [there + '.py']
|
return [there + '.py']
|
||||||
if os.path.isdir(there):
|
if os.path.isdir(there):
|
||||||
return files(there)
|
return files(there)
|
||||||
|
|
||||||
print("Error: Couldn't find a config file!", file=sys.stderr)
|
print("Error: Couldn't find a config file!", file=sys.stderr)
|
||||||
print('What happened to ~/.phenny/default.py?', file=sys.stderr)
|
print('What happened to ~/.phenny/default.py?', file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
def main(argv=None):
|
|
||||||
|
def main(argv=None):
|
||||||
# Step One: Parse The Command Line
|
# Step One: Parse The Command Line
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(description="A Python IRC bot.")
|
parser = argparse.ArgumentParser(description="A Python IRC bot.")
|
||||||
parser.add_argument('-c', '--config', metavar='fn',
|
parser.add_argument('-c', '--config', metavar='fn',
|
||||||
help='use this configuration file or directory')
|
help='use this configuration file or directory')
|
||||||
args = parser.parse_args(argv)
|
args = parser.parse_args(argv)
|
||||||
|
|
||||||
# Step Two: Check Dependencies
|
# Step Two: Check Dependencies
|
||||||
|
|
||||||
check_python_version() # require python2.4 or later
|
check_python_version()
|
||||||
if not args.config:
|
if not args.config:
|
||||||
check_dotdir() # require ~/.phenny, or make it and exit
|
check_dotdir() # require ~/.phenny, or make it and exit
|
||||||
|
|
||||||
# Step Three: Load The Configurations
|
# Step Three: Load The Configurations
|
||||||
|
|
||||||
config_modules = []
|
config_modules = []
|
||||||
for config_name in config_names(args.config):
|
for config_name in config_names(args.config):
|
||||||
name = os.path.basename(config_name).split('.')[0] + '_config'
|
name = os.path.basename(config_name).split('.')[0] + '_config'
|
||||||
module = imp.load_source(name, config_name)
|
module = imp.load_source(name, config_name)
|
||||||
module.filename = config_name
|
module.filename = config_name
|
||||||
|
|
||||||
if not hasattr(module, 'prefix'):
|
if not hasattr(module, 'prefix'):
|
||||||
module.prefix = r'\.'
|
module.prefix = r'\.'
|
||||||
|
|
||||||
if not hasattr(module, 'name'):
|
if not hasattr(module, 'name'):
|
||||||
module.name = 'Phenny Palmersbot, http://inamidst.com/phenny/'
|
module.name = 'Phenny Palmersbot, http://inamidst.com/phenny/'
|
||||||
|
|
||||||
if not hasattr(module, 'port'):
|
if not hasattr(module, 'port'):
|
||||||
module.port = 6667
|
module.port = 6667
|
||||||
|
|
||||||
if not hasattr(module, 'ssl'):
|
if not hasattr(module, 'ssl'):
|
||||||
module.ssl = False
|
module.ssl = False
|
||||||
|
|
||||||
if not hasattr(module, 'ca_certs'):
|
if not hasattr(module, 'ca_certs'):
|
||||||
module.ca_certs = None
|
module.ca_certs = None
|
||||||
|
|
||||||
|
if not hasattr(module, 'ssl_cert'):
|
||||||
|
module.ssl_cert = None
|
||||||
|
|
||||||
|
if not hasattr(module, 'ssl_key'):
|
||||||
|
module.ssl_key = None
|
||||||
|
|
||||||
if not hasattr(module, 'ipv6'):
|
if not hasattr(module, 'ipv6'):
|
||||||
module.ipv6 = False
|
module.ipv6 = False
|
||||||
|
|
||||||
if not hasattr(module, 'password'):
|
if not hasattr(module, 'password'):
|
||||||
module.password = None
|
module.password = None
|
||||||
|
|
||||||
if module.host == 'irc.example.net':
|
if module.host == 'irc.example.net':
|
||||||
error = ('Error: you must edit the config file first!\n' +
|
error = ('Error: you must edit the config file first!\n' +
|
||||||
"You're currently using %s" % module.filename)
|
"You're currently using %s" % module.filename)
|
||||||
print(error, file=sys.stderr)
|
print(error, file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
@ -176,18 +192,21 @@ def main(argv=None):
|
||||||
|
|
||||||
# Step Four: Load Phenny
|
# Step Four: Load Phenny
|
||||||
|
|
||||||
try: from __init__ import run
|
try:
|
||||||
except ImportError:
|
from __init__ import run
|
||||||
try: from phenny import run
|
except ImportError:
|
||||||
except ImportError:
|
try:
|
||||||
|
from phenny import run
|
||||||
|
except ImportError:
|
||||||
print("Error: Couldn't find phenny to import", file=sys.stderr)
|
print("Error: Couldn't find phenny to import", file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
# Step Five: Initialise And Run The Phennies
|
# Step Five: Initialise And Run The Phennies
|
||||||
|
|
||||||
# @@ ignore SIGHUP
|
# @@ ignore SIGHUP
|
||||||
for config_module in config_modules:
|
for config_module in config_modules:
|
||||||
run(config_module) # @@ thread this
|
run(config_module) # @@ thread this
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
|
if __name__ == '__main__':
|
||||||
main()
|
main()
|
||||||
|
|
Loading…
Reference in New Issue