2015-03-19 20:55:18 +01:00
|
|
|
#!/usr/bin/python3
|
|
|
|
|
|
|
|
import imp
|
|
|
|
import os
|
2015-04-03 09:17:03 +02:00
|
|
|
import socket
|
2015-04-03 21:35:55 +02:00
|
|
|
import time
|
2015-04-25 07:37:07 +02:00
|
|
|
import sys
|
2015-06-24 04:29:53 +02:00
|
|
|
from collections import defaultdict
|
2015-07-10 03:29:00 +02:00
|
|
|
import threading
|
2015-04-25 07:37:07 +02:00
|
|
|
|
2015-07-05 22:22:17 +02:00
|
|
|
from log import log
|
2015-07-06 04:19:49 +02:00
|
|
|
import conf
|
2015-06-24 04:29:53 +02:00
|
|
|
import classes
|
2015-07-10 03:29:00 +02:00
|
|
|
import utils
|
2015-03-19 20:55:18 +01:00
|
|
|
|
2015-04-18 04:55:48 +02:00
|
|
|
class Irc():
|
2015-07-10 03:29:00 +02:00
|
|
|
def __init__(self, netname, proto, conf):
|
2015-04-04 03:45:18 +02:00
|
|
|
# Initialize some variables
|
2015-07-15 22:49:12 +02:00
|
|
|
self.connected = threading.Event()
|
2015-07-13 02:59:09 +02:00
|
|
|
self.name = netname.lower()
|
2015-04-25 07:37:07 +02:00
|
|
|
self.conf = conf
|
2015-07-05 08:12:00 +02:00
|
|
|
# Server, channel, and user indexes to be populated by our protocol module
|
2015-05-31 07:15:19 +02:00
|
|
|
self.servers = {}
|
2015-07-05 08:12:00 +02:00
|
|
|
self.users = {}
|
|
|
|
self.channels = defaultdict(classes.IrcChannel)
|
|
|
|
# Sets flags such as whether to use halfops, etc. The default RFC1459
|
|
|
|
# modes are implied.
|
|
|
|
self.cmodes = {'op': 'o', 'secret': 's', 'private': 'p',
|
|
|
|
'noextmsg': 'n', 'moderated': 'm', 'inviteonly': 'i',
|
|
|
|
'topiclock': 't', 'limit': 'l', 'ban': 'b',
|
2015-07-06 04:19:49 +02:00
|
|
|
'voice': 'v', 'key': 'k',
|
|
|
|
# Type A, B, and C modes
|
|
|
|
'*A': 'b',
|
|
|
|
'*B': 'k',
|
|
|
|
'*C': 'l',
|
|
|
|
'*D': 'imnpstr'}
|
2015-07-05 08:12:00 +02:00
|
|
|
self.umodes = {'invisible': 'i', 'snomask': 's', 'wallops': 'w',
|
2015-07-06 04:19:49 +02:00
|
|
|
'oper': 'o',
|
|
|
|
'*A': '', '*B': '', '*C': 's', '*D': 'iow'}
|
2015-07-15 23:00:40 +02:00
|
|
|
# This nicklen value is only a default, and SHOULD be set by the
|
|
|
|
# protocol module as soon as the relevant capability information is
|
|
|
|
# received from the uplink. Plugins that depend on maxnicklen being
|
|
|
|
# set MUST call "irc.connected.wait()", which blocks until the
|
|
|
|
# capability information is received. This handling of irc.connected
|
|
|
|
# is also dependent on the protocol module.
|
2015-07-05 08:12:00 +02:00
|
|
|
self.maxnicklen = 30
|
2015-07-06 04:19:49 +02:00
|
|
|
self.prefixmodes = 'ov'
|
2015-04-03 09:17:03 +02:00
|
|
|
|
2015-07-10 03:29:00 +02:00
|
|
|
self.serverdata = conf['servers'][netname]
|
2015-07-06 04:19:49 +02:00
|
|
|
self.sid = self.serverdata["sid"]
|
2015-07-08 22:54:45 +02:00
|
|
|
self.botdata = conf['bot']
|
2015-07-06 04:19:49 +02:00
|
|
|
self.proto = proto
|
2015-07-15 20:52:13 +02:00
|
|
|
connection_thread = threading.Thread(target = self.connect)
|
|
|
|
connection_thread.start()
|
2015-07-06 04:19:49 +02:00
|
|
|
|
|
|
|
def connect(self):
|
2015-04-03 09:17:03 +02:00
|
|
|
ip = self.serverdata["ip"]
|
|
|
|
port = self.serverdata["port"]
|
2015-07-05 22:29:01 +02:00
|
|
|
log.info("Connecting to network %r on %s:%s", self.name, ip, port)
|
2015-07-15 20:52:13 +02:00
|
|
|
# Initial connection timeout is a lot smaller than the timeout after
|
|
|
|
# we've connected; this is intentional.
|
|
|
|
self.socket = socket.create_connection((ip, port), timeout=10)
|
2015-07-10 03:29:00 +02:00
|
|
|
self.socket.setblocking(0)
|
2015-07-12 05:10:21 +02:00
|
|
|
self.socket.settimeout(180)
|
2015-07-15 20:52:13 +02:00
|
|
|
try:
|
|
|
|
self.proto.connect(self)
|
|
|
|
except (socket.error, socket.timeout):
|
|
|
|
log.error('(%s) Failed to connect to IRC: %s: %s',
|
|
|
|
self.name, type(e).__name__, str(e))
|
|
|
|
self.disconnect()
|
|
|
|
self.run()
|
2015-07-10 03:29:00 +02:00
|
|
|
|
|
|
|
def disconnect(self):
|
2015-07-15 22:49:12 +02:00
|
|
|
self.connected.clear()
|
2015-07-10 03:29:00 +02:00
|
|
|
self.socket.close()
|
2015-07-15 20:52:13 +02:00
|
|
|
autoconnect = self.serverdata.get('autoconnect')
|
|
|
|
if autoconnect is not None and autoconnect >= 0:
|
|
|
|
log.warning('(%s) Going to auto-reconnect in %s seconds.', self.name, autoconnect)
|
|
|
|
time.sleep(autoconnect)
|
|
|
|
self.connect()
|
2015-04-03 09:17:03 +02:00
|
|
|
|
2015-04-03 21:35:55 +02:00
|
|
|
def run(self):
|
2015-04-18 07:11:49 +02:00
|
|
|
buf = ""
|
|
|
|
data = ""
|
2015-07-15 22:49:12 +02:00
|
|
|
while True:
|
2015-04-03 21:35:55 +02:00
|
|
|
try:
|
2015-05-31 07:15:19 +02:00
|
|
|
data = self.socket.recv(2048).decode("utf-8")
|
2015-04-25 07:37:07 +02:00
|
|
|
buf += data
|
2015-04-18 07:11:49 +02:00
|
|
|
if not data:
|
|
|
|
break
|
|
|
|
while '\n' in buf:
|
|
|
|
line, buf = buf.split('\n', 1)
|
2015-07-10 03:29:00 +02:00
|
|
|
log.debug("(%s) <- %s", self.name, line)
|
2015-04-25 07:37:07 +02:00
|
|
|
proto.handle_events(self, line)
|
2015-07-10 03:29:00 +02:00
|
|
|
except (socket.error, classes.ProtocolError) as e:
|
2015-07-15 20:52:13 +02:00
|
|
|
log.error('(%s) Disconnected from IRC: %s: %s',
|
2015-07-10 03:29:00 +02:00
|
|
|
self.name, type(e).__name__, str(e))
|
|
|
|
self.disconnect()
|
2015-07-15 22:49:12 +02:00
|
|
|
break
|
2015-04-03 09:17:03 +02:00
|
|
|
|
|
|
|
def send(self, data):
|
2015-07-10 02:25:10 +02:00
|
|
|
# Safeguard against newlines in input!! Otherwise, each line gets
|
|
|
|
# treated as a separate command, which is particularly nasty.
|
|
|
|
data = data.replace('\n', ' ')
|
2015-04-03 09:17:03 +02:00
|
|
|
data = data.encode("utf-8") + b"\n"
|
2015-07-10 03:29:00 +02:00
|
|
|
log.debug("(%s) -> %s", self.name, data.decode("utf-8").strip("\n"))
|
2015-04-03 09:17:03 +02:00
|
|
|
self.socket.send(data)
|
2015-03-19 20:55:18 +01:00
|
|
|
|
2015-05-31 21:20:09 +02:00
|
|
|
if __name__ == '__main__':
|
2015-07-05 22:44:48 +02:00
|
|
|
log.info('PyLink starting...')
|
2015-07-06 04:19:49 +02:00
|
|
|
if conf.conf['login']['password'] == 'changeme':
|
2015-07-05 22:44:48 +02:00
|
|
|
log.critical("You have not set the login details correctly! Exiting...")
|
2015-06-17 05:05:41 +02:00
|
|
|
sys.exit(2)
|
|
|
|
protocols_folder = [os.path.join(os.getcwd(), 'protocols')]
|
2015-07-13 08:28:34 +02:00
|
|
|
|
|
|
|
# Import plugins first globally, because they can listen for events
|
|
|
|
# that happen before the connection phase.
|
|
|
|
to_load = conf.conf['plugins']
|
|
|
|
plugins_folder = [os.path.join(os.getcwd(), 'plugins')]
|
|
|
|
# Here, we override the module lookup and import the plugins
|
|
|
|
# dynamically depending on which were configured.
|
|
|
|
for plugin in to_load:
|
|
|
|
try:
|
|
|
|
moduleinfo = imp.find_module(plugin, plugins_folder)
|
|
|
|
pl = imp.load_source(plugin, moduleinfo[1])
|
|
|
|
utils.plugins.append(pl)
|
|
|
|
except ImportError as e:
|
|
|
|
if str(e).startswith('No module named'):
|
|
|
|
log.error('Failed to load plugin %r: the plugin could not be found.', plugin)
|
|
|
|
else:
|
|
|
|
log.error('Failed to load plugin %r: import error %s', plugin, str(e))
|
|
|
|
else:
|
|
|
|
if hasattr(pl, 'main'):
|
|
|
|
log.debug('Calling main() function of plugin %r', pl)
|
|
|
|
pl.main()
|
|
|
|
|
2015-07-10 03:29:00 +02:00
|
|
|
for network in conf.conf['servers']:
|
|
|
|
protoname = conf.conf['servers'][network]['protocol']
|
|
|
|
try:
|
|
|
|
moduleinfo = imp.find_module(protoname, protocols_folder)
|
|
|
|
proto = imp.load_source(protoname, moduleinfo[1])
|
|
|
|
except ImportError as e:
|
|
|
|
if str(e).startswith('No module named'):
|
|
|
|
log.critical('Failed to load protocol module %r: the file could not be found.', protoname)
|
|
|
|
else:
|
|
|
|
log.critical('Failed to load protocol module: import error %s', protoname, str(e))
|
|
|
|
sys.exit(2)
|
2015-06-17 05:05:41 +02:00
|
|
|
else:
|
2015-07-12 05:11:48 +02:00
|
|
|
utils.networkobjects[network] = Irc(network, proto, conf.conf)
|
2015-07-14 01:07:55 +02:00
|
|
|
utils.started.set()
|
2015-07-13 08:28:34 +02:00
|
|
|
log.info("loaded plugins: %s", utils.plugins)
|
|
|
|
|