3
0
mirror of https://github.com/jlu5/PyLink.git synced 2024-11-01 09:19:23 +01:00
PyLink/main.py
James Lu 6e37e1c05d make Irc.connected a threaded event object, setting it to True ONLY when we receive server capabilities from our uplink
The previous behavior set this to True as soon as we ran connect(), but this caused problems because the default capabilities (i.e. nicklen)
that Irc() initializes won't match the real value of the network.
2015-07-15 13:49:12 -07:00

147 lines
5.6 KiB
Python
Executable File

#!/usr/bin/python3
import imp
import os
import socket
import time
import sys
from collections import defaultdict
import threading
from log import log
import conf
import classes
import utils
class Irc():
def __init__(self, netname, proto, conf):
# Initialize some variables
self.connected = threading.Event()
self.name = netname.lower()
self.conf = conf
# Server, channel, and user indexes to be populated by our protocol module
self.servers = {}
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',
'voice': 'v', 'key': 'k',
# Type A, B, and C modes
'*A': 'b',
'*B': 'k',
'*C': 'l',
'*D': 'imnpstr'}
self.umodes = {'invisible': 'i', 'snomask': 's', 'wallops': 'w',
'oper': 'o',
'*A': '', '*B': '', '*C': 's', '*D': 'iow'}
self.maxnicklen = 30
self.prefixmodes = 'ov'
self.serverdata = conf['servers'][netname]
self.sid = self.serverdata["sid"]
self.botdata = conf['bot']
self.proto = proto
connection_thread = threading.Thread(target = self.connect)
connection_thread.start()
def connect(self):
ip = self.serverdata["ip"]
port = self.serverdata["port"]
log.info("Connecting to network %r on %s:%s", self.name, ip, port)
# 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)
self.socket.setblocking(0)
self.socket.settimeout(180)
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()
def disconnect(self):
self.connected.clear()
self.socket.close()
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()
def run(self):
buf = ""
data = ""
while True:
try:
data = self.socket.recv(2048).decode("utf-8")
buf += data
if not data:
break
while '\n' in buf:
line, buf = buf.split('\n', 1)
log.debug("(%s) <- %s", self.name, line)
proto.handle_events(self, line)
except (socket.error, classes.ProtocolError) as e:
log.error('(%s) Disconnected from IRC: %s: %s',
self.name, type(e).__name__, str(e))
self.disconnect()
break
def send(self, data):
# Safeguard against newlines in input!! Otherwise, each line gets
# treated as a separate command, which is particularly nasty.
data = data.replace('\n', ' ')
data = data.encode("utf-8") + b"\n"
log.debug("(%s) -> %s", self.name, data.decode("utf-8").strip("\n"))
self.socket.send(data)
if __name__ == '__main__':
log.info('PyLink starting...')
if conf.conf['login']['password'] == 'changeme':
log.critical("You have not set the login details correctly! Exiting...")
sys.exit(2)
protocols_folder = [os.path.join(os.getcwd(), 'protocols')]
# 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()
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)
else:
utils.networkobjects[network] = Irc(network, proto, conf.conf)
utils.started.set()
log.info("loaded plugins: %s", utils.plugins)