3
0
mirror of https://github.com/jlu5/PyLink.git synced 2024-11-01 01:09:22 +01:00

Today's code dump, featuring:

- PLUGIN SUPPORT and COMMAND HANDLING, wow!!!!!!!
- Restructuring of files so that there's only one protocol module (anything else is too much to maintain for now)
- Split protocol things into utils.py
- Bugfixes: don't go into an endless loop of text spamming when the remote host closes the connection!
This commit is contained in:
James Lu 2015-04-24 22:37:07 -07:00
parent 80a2ce1d0a
commit 9b4fb50f25
6 changed files with 94 additions and 69 deletions

View File

@ -3,6 +3,7 @@ bot:
nick: pylink
user: pylink
realname: PyLink Service Client
prefix: "@"
login:
# PyLink administrative login - Change this, or the service will not start!
@ -21,6 +22,8 @@ server:
# SID - required for InspIRCd and TS6 based servers. This must be three characters long.
# The first char must be a digit [0-9], and the remaining two chars may be letters [A-Z] or digits.
sid: "0AL"
# Set protocol module
protocol: inspircd
channel: "#pylink"
# Plugins to load (omit the .py extension)
plugins:
- hello

57
main.py
View File

@ -7,6 +7,9 @@ import threading
import socket
import multiprocessing
import time
import sys
import proto
print('PyLink starting...')
with open("config.yml", 'r') as f:
@ -15,19 +18,6 @@ with open("config.yml", 'r') as f:
# if conf['login']['password'] == 'changeme':
# print("You have not set the login details correctly! Exiting...")
class IrcUser():
def __init__(self, nick, ts, uid, ident='null', host='null',
realname='PyLink dummy client', realhost='null',
ip='0.0.0.0'):
self.nick = nick
self.ts = ts
self.uid = uid
self.ident = ident
self.host = host
self.realhost = realhost
self.ip = ip
self.realname = realname
class Irc():
def __init__(self):
# Initialize some variables
@ -36,6 +26,7 @@ class Irc():
self.users = {}
self.channels = {}
self.name = conf['server']['netname']
self.conf = conf
self.serverdata = conf['server']
ip = self.serverdata["ip"]
@ -43,23 +34,12 @@ class Irc():
self.sid = self.serverdata["sid"]
print("Connecting to network %r on %s:%s" % (self.name, ip, port))
protoname = self.serverdata['protocol']
# With the introduction of Python 3, relative imports are no longer
# allowed from normal applications ran from the command line. Instead,
# these imported libraries must be installed as a package using distutils
# or something similar.
#
# But I don't want that! Where PyLink is at right now (a total WIP), it is
# a lot more convenient to run the program directly from the source folder.
protocols_folder = [os.path.join(os.getcwd(), 'protocols')]
# Here, we override the module lookup and import the protocol module
# dynamically depending on which module was configured.
moduleinfo = imp.find_module(protoname, protocols_folder)
self.proto = imp.load_source(protoname, moduleinfo[1])
self.socket = socket.socket()
self.socket.connect((ip, port))
self.proto.connect(self)
proto.connect(self)
self.connected = True
self.loaded = []
self.load_plugins()
self.run()
def run(self):
@ -67,22 +47,33 @@ class Irc():
data = ""
while self.connected:
try:
data += self.socket.recv(1024).decode("utf-8")
data = self.socket.recv(4096).decode("utf-8")
buf += data
if not data:
break
buf += data
while '\n' in buf:
line, buf = buf.split('\n', 1)
print("<- {}".format(line))
self.proto.handle_events(self, line)
except socket.error:
proto.handle_events(self, line)
except socket.error as e:
print('Received socket.error: %s, exiting.' % str(e))
self.connected = False
sys.exit(1)
break
sys.exit(1)
def send(self, data):
data = data.encode("utf-8") + b"\n"
print("-> {}".format(data.decode("utf-8").strip("\n")))
self.socket.send(data)
def load_plugins(self):
to_load = 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:
moduleinfo = imp.find_module(plugin, plugins_folder)
self.loaded.append(imp.load_source(plugin, moduleinfo[1]))
print("loaded plugins: %s" % self.loaded)
irc_obj = Irc()

7
plugins/hello.py Normal file
View File

@ -0,0 +1,7 @@
import sys, os
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
import proto
@proto.add_cmd
def hello(irc, source, command, args):
proto._sendFromUser(irc, 'PRIVMSG %s :hello!' % source)

View File

@ -2,26 +2,29 @@ import threading
import socket
import time
import re
import string
import sys
from utils import *
# TODO: make PyLink a package so I don't have to hack the import system
# like this.
from os import sys, path
sys.path.append(path.dirname(path.dirname(path.abspath(__file__))))
from main import IrcUser
global bot_commands
# This should be a mapping of command names to functions
bot_commands = {}
# From http://www.inspircd.org/wiki/Modules/spanningtree/UUIDs.html
chars = string.digits + string.ascii_uppercase
iters = [iter(chars) for _ in range(6)]
a = [next(i) for i in iters]
class IrcUser():
def __init__(self, nick, ts, uid, ident='null', host='null',
realname='PyLink dummy client', realhost='null',
ip='0.0.0.0'):
self.nick = nick
self.ts = ts
self.uid = uid
self.ident = ident
self.host = host
self.realhost = realhost
self.ip = ip
self.realname = realname
def next_uid(sid, level=-1):
try:
a[level] = next(iters[level])
return sid + ''.join(a)
except StopIteration:
return UID(level-1)
def __repr__(self):
keys = [k for k in dir(self) if not k.startswith("__")]
return ','.join(["%s=%s" % (k, getattr(self, k)) for k in keys])
def _sendFromServer(irc, msg):
irc.send(':%s %s' % (irc.sid, msg))
@ -32,7 +35,7 @@ def _sendFromUser(irc, msg, user=None):
irc.send(':%s %s' % (user, msg))
def _join(irc, channel):
_sendFromUser(irc, "FJOIN {channel} {ts} +nt :,{uid}".format(sid=irc.sid,
_sendFromUser(irc, "JOIN {channel} {ts} +nt :,{uid}".format(sid=irc.sid,
ts=int(time.time()), uid=irc.pseudoclient.uid, channel=channel))
def _uidToNick(irc, uid):
@ -47,7 +50,7 @@ def connect(irc):
irc.pseudoclient = IrcUser('PyLink', ts, uid, 'pylink', host,
'PyLink Client')
irc.users['PyLink'] = irc.pseudoclient
f = irc.send
f('CAPAB START 1203')
# This is hard coded atm... We should fix it eventually...
@ -71,13 +74,24 @@ def connect(irc):
# :7NU PING 7NU 0AL
def handle_ping(irc, servernumeric, command, args):
if args[3] == irc.sid:
_sendFromServer(irc, 'PONG %s' % args[2])
if args[1] == irc.sid:
_sendFromServer(irc, 'PONG %s' % args[1])
def handle_privmsg(irc, numeric, command, args):
def handle_privmsg(irc, source, command, args):
# _sendFromUser(irc, 'PRIVMSG %s :hello!' % numeric)
print(irc.users)
print(irc.channels)
prefix = irc.conf['bot']['prefix']
if args[0] == irc.pseudoclient.uid:
cmd_args = args[1].split(' ', 1)
cmd = cmd_args[0]
try:
cmd_args = cmd_args[1]
except IndexError:
cmd_args = []
try:
bot_commands[cmd](irc, source, command, args)
except KeyError:
_sendFromUser(irc, 'PRIVMSG %s :unknown command %r' % (source, cmd))
def handle_error(irc, numeric, command, args):
print('Received an ERROR, killing!')
@ -89,11 +103,9 @@ def handle_fjoin(irc, servernumeric, command, args):
channel = args[0]
# tl;dr InspIRCd sends each user's channel data in the form of 'modeprefix(es),UID'
# We'll save each user in this format too, at least for now.
print(args)
users = args[-1].split()
users = [x.split(',') for x in users]
print(users)
'''
if channel not in irc.channels.keys():
irc.channels[channel]['users'] = users
@ -103,7 +115,7 @@ def handle_fjoin(irc, servernumeric, command, args):
'''
def handle_uid(irc, numeric, command, args):
# :1SR UID 1SRAAAAAU 1428974823 synnero ow.my.eye.rs ow.my.eye.rs GLolol 2604:180:1::d34d:d87b 1425951245 +Wiosw +ACGKNOQXacfgklnoqvx :move along, nothing to see here!
# :70M UID 70MAAAAAB 1429934638 GL 0::1 hidden-7j810p.9mdf.lrek.0000.0000.IP gl 0::1 1429934638 +Wioswx +ACGKNOQXacfgklnoqvx :realname
uid, ts, nick, realhost, host, ident, ip = args[0:7]
realname = args[-1]
irc.users[nick] = IrcUser(nick, ts, uid, ident, host, realname, realhost, ip)
@ -112,11 +124,13 @@ def handle_quit(irc, numeric, command, args):
# :1SRAAGB4T QUIT :Quit: quit message goes here
nick = _uidToNick(irc, numeric)
del irc.users[nick]
'''
for k, v in irc.channels.items():
try:
del irc.channels[k][users][v]
except KeyError:
pass
'''
def handle_events(irc, data):
# Each server message looks something like this:
@ -146,6 +160,8 @@ def handle_events(irc, data):
numeric = args[0]
command = args[1]
args = args[2:]
print(args)
except IndexError:
return
@ -155,3 +171,6 @@ def handle_events(irc, data):
func(irc, numeric, command, args)
except KeyError: # unhandled event
pass
def add_cmd(func):
bot_commands[func.__name__.lower()] = func

View File

@ -1,8 +0,0 @@
def connect(irc):
print('%s: Using PyLink stub/testing protocol.' % irc.name)
print('Send password: %s' % irc.serverdata['sendpass'])
print('Receive password: %s' % irc.serverdata['recvpass'])
print('Server: %s:%s' % (irc.serverdata["ip"], irc.serverdata["port"]))
def handle_events(irc, data):
print('%s: Received event: %s' % (irc.name, data))

13
utils.py Normal file
View File

@ -0,0 +1,13 @@
import string
# From http://www.inspircd.org/wiki/Modules/spanningtree/UUIDs.html
chars = string.digits + string.ascii_uppercase
iters = [iter(chars) for _ in range(6)]
a = [next(i) for i in iters]
def next_uid(sid, level=-1):
try:
a[level] = next(iters[level])
return sid + ''.join(a)
except StopIteration:
return UID(level-1)