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:
parent
80a2ce1d0a
commit
9b4fb50f25
@ -3,6 +3,7 @@ bot:
|
|||||||
nick: pylink
|
nick: pylink
|
||||||
user: pylink
|
user: pylink
|
||||||
realname: PyLink Service Client
|
realname: PyLink Service Client
|
||||||
|
prefix: "@"
|
||||||
|
|
||||||
login:
|
login:
|
||||||
# PyLink administrative login - Change this, or the service will not start!
|
# 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.
|
# 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.
|
# The first char must be a digit [0-9], and the remaining two chars may be letters [A-Z] or digits.
|
||||||
sid: "0AL"
|
sid: "0AL"
|
||||||
# Set protocol module
|
|
||||||
protocol: inspircd
|
|
||||||
channel: "#pylink"
|
channel: "#pylink"
|
||||||
|
|
||||||
|
# Plugins to load (omit the .py extension)
|
||||||
|
plugins:
|
||||||
|
- hello
|
||||||
|
57
main.py
57
main.py
@ -7,6 +7,9 @@ import threading
|
|||||||
import socket
|
import socket
|
||||||
import multiprocessing
|
import multiprocessing
|
||||||
import time
|
import time
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import proto
|
||||||
print('PyLink starting...')
|
print('PyLink starting...')
|
||||||
|
|
||||||
with open("config.yml", 'r') as f:
|
with open("config.yml", 'r') as f:
|
||||||
@ -15,19 +18,6 @@ with open("config.yml", 'r') as f:
|
|||||||
# if conf['login']['password'] == 'changeme':
|
# if conf['login']['password'] == 'changeme':
|
||||||
# print("You have not set the login details correctly! Exiting...")
|
# 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():
|
class Irc():
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
# Initialize some variables
|
# Initialize some variables
|
||||||
@ -36,6 +26,7 @@ class Irc():
|
|||||||
self.users = {}
|
self.users = {}
|
||||||
self.channels = {}
|
self.channels = {}
|
||||||
self.name = conf['server']['netname']
|
self.name = conf['server']['netname']
|
||||||
|
self.conf = conf
|
||||||
|
|
||||||
self.serverdata = conf['server']
|
self.serverdata = conf['server']
|
||||||
ip = self.serverdata["ip"]
|
ip = self.serverdata["ip"]
|
||||||
@ -43,23 +34,12 @@ class Irc():
|
|||||||
self.sid = self.serverdata["sid"]
|
self.sid = self.serverdata["sid"]
|
||||||
print("Connecting to network %r on %s:%s" % (self.name, ip, port))
|
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 = socket.socket()
|
||||||
self.socket.connect((ip, port))
|
self.socket.connect((ip, port))
|
||||||
self.proto.connect(self)
|
proto.connect(self)
|
||||||
self.connected = True
|
self.connected = True
|
||||||
|
self.loaded = []
|
||||||
|
self.load_plugins()
|
||||||
self.run()
|
self.run()
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
@ -67,22 +47,33 @@ class Irc():
|
|||||||
data = ""
|
data = ""
|
||||||
while self.connected:
|
while self.connected:
|
||||||
try:
|
try:
|
||||||
data += self.socket.recv(1024).decode("utf-8")
|
data = self.socket.recv(4096).decode("utf-8")
|
||||||
|
buf += data
|
||||||
if not data:
|
if not data:
|
||||||
break
|
break
|
||||||
buf += data
|
|
||||||
while '\n' in buf:
|
while '\n' in buf:
|
||||||
line, buf = buf.split('\n', 1)
|
line, buf = buf.split('\n', 1)
|
||||||
print("<- {}".format(line))
|
print("<- {}".format(line))
|
||||||
self.proto.handle_events(self, line)
|
proto.handle_events(self, line)
|
||||||
except socket.error:
|
except socket.error as e:
|
||||||
print('Received socket.error: %s, exiting.' % str(e))
|
print('Received socket.error: %s, exiting.' % str(e))
|
||||||
self.connected = False
|
break
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
def send(self, data):
|
def send(self, data):
|
||||||
data = data.encode("utf-8") + b"\n"
|
data = data.encode("utf-8") + b"\n"
|
||||||
print("-> {}".format(data.decode("utf-8").strip("\n")))
|
print("-> {}".format(data.decode("utf-8").strip("\n")))
|
||||||
self.socket.send(data)
|
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()
|
irc_obj = Irc()
|
||||||
|
7
plugins/hello.py
Normal file
7
plugins/hello.py
Normal 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)
|
@ -2,26 +2,29 @@ import threading
|
|||||||
import socket
|
import socket
|
||||||
import time
|
import time
|
||||||
import re
|
import re
|
||||||
import string
|
|
||||||
import sys
|
import sys
|
||||||
|
from utils import *
|
||||||
|
|
||||||
# TODO: make PyLink a package so I don't have to hack the import system
|
global bot_commands
|
||||||
# like this.
|
# This should be a mapping of command names to functions
|
||||||
from os import sys, path
|
bot_commands = {}
|
||||||
sys.path.append(path.dirname(path.dirname(path.abspath(__file__))))
|
|
||||||
from main import IrcUser
|
|
||||||
|
|
||||||
# From http://www.inspircd.org/wiki/Modules/spanningtree/UUIDs.html
|
class IrcUser():
|
||||||
chars = string.digits + string.ascii_uppercase
|
def __init__(self, nick, ts, uid, ident='null', host='null',
|
||||||
iters = [iter(chars) for _ in range(6)]
|
realname='PyLink dummy client', realhost='null',
|
||||||
a = [next(i) for i in iters]
|
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):
|
def __repr__(self):
|
||||||
try:
|
keys = [k for k in dir(self) if not k.startswith("__")]
|
||||||
a[level] = next(iters[level])
|
return ','.join(["%s=%s" % (k, getattr(self, k)) for k in keys])
|
||||||
return sid + ''.join(a)
|
|
||||||
except StopIteration:
|
|
||||||
return UID(level-1)
|
|
||||||
|
|
||||||
def _sendFromServer(irc, msg):
|
def _sendFromServer(irc, msg):
|
||||||
irc.send(':%s %s' % (irc.sid, msg))
|
irc.send(':%s %s' % (irc.sid, msg))
|
||||||
@ -32,7 +35,7 @@ def _sendFromUser(irc, msg, user=None):
|
|||||||
irc.send(':%s %s' % (user, msg))
|
irc.send(':%s %s' % (user, msg))
|
||||||
|
|
||||||
def _join(irc, channel):
|
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))
|
ts=int(time.time()), uid=irc.pseudoclient.uid, channel=channel))
|
||||||
|
|
||||||
def _uidToNick(irc, uid):
|
def _uidToNick(irc, uid):
|
||||||
@ -47,7 +50,7 @@ def connect(irc):
|
|||||||
irc.pseudoclient = IrcUser('PyLink', ts, uid, 'pylink', host,
|
irc.pseudoclient = IrcUser('PyLink', ts, uid, 'pylink', host,
|
||||||
'PyLink Client')
|
'PyLink Client')
|
||||||
irc.users['PyLink'] = irc.pseudoclient
|
irc.users['PyLink'] = irc.pseudoclient
|
||||||
|
|
||||||
f = irc.send
|
f = irc.send
|
||||||
f('CAPAB START 1203')
|
f('CAPAB START 1203')
|
||||||
# This is hard coded atm... We should fix it eventually...
|
# This is hard coded atm... We should fix it eventually...
|
||||||
@ -71,13 +74,24 @@ def connect(irc):
|
|||||||
|
|
||||||
# :7NU PING 7NU 0AL
|
# :7NU PING 7NU 0AL
|
||||||
def handle_ping(irc, servernumeric, command, args):
|
def handle_ping(irc, servernumeric, command, args):
|
||||||
if args[3] == irc.sid:
|
if args[1] == irc.sid:
|
||||||
_sendFromServer(irc, 'PONG %s' % args[2])
|
_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)
|
# _sendFromUser(irc, 'PRIVMSG %s :hello!' % numeric)
|
||||||
print(irc.users)
|
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):
|
def handle_error(irc, numeric, command, args):
|
||||||
print('Received an ERROR, killing!')
|
print('Received an ERROR, killing!')
|
||||||
@ -89,11 +103,9 @@ def handle_fjoin(irc, servernumeric, command, args):
|
|||||||
channel = args[0]
|
channel = args[0]
|
||||||
# tl;dr InspIRCd sends each user's channel data in the form of 'modeprefix(es),UID'
|
# 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.
|
# We'll save each user in this format too, at least for now.
|
||||||
print(args)
|
|
||||||
users = args[-1].split()
|
users = args[-1].split()
|
||||||
users = [x.split(',') for x in users]
|
users = [x.split(',') for x in users]
|
||||||
print(users)
|
|
||||||
|
|
||||||
'''
|
'''
|
||||||
if channel not in irc.channels.keys():
|
if channel not in irc.channels.keys():
|
||||||
irc.channels[channel]['users'] = users
|
irc.channels[channel]['users'] = users
|
||||||
@ -103,7 +115,7 @@ def handle_fjoin(irc, servernumeric, command, args):
|
|||||||
'''
|
'''
|
||||||
|
|
||||||
def handle_uid(irc, numeric, 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]
|
uid, ts, nick, realhost, host, ident, ip = args[0:7]
|
||||||
realname = args[-1]
|
realname = args[-1]
|
||||||
irc.users[nick] = IrcUser(nick, ts, uid, ident, host, realname, realhost, ip)
|
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
|
# :1SRAAGB4T QUIT :Quit: quit message goes here
|
||||||
nick = _uidToNick(irc, numeric)
|
nick = _uidToNick(irc, numeric)
|
||||||
del irc.users[nick]
|
del irc.users[nick]
|
||||||
|
'''
|
||||||
for k, v in irc.channels.items():
|
for k, v in irc.channels.items():
|
||||||
try:
|
try:
|
||||||
del irc.channels[k][users][v]
|
del irc.channels[k][users][v]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
'''
|
||||||
|
|
||||||
def handle_events(irc, data):
|
def handle_events(irc, data):
|
||||||
# Each server message looks something like this:
|
# Each server message looks something like this:
|
||||||
@ -146,6 +160,8 @@ def handle_events(irc, data):
|
|||||||
|
|
||||||
numeric = args[0]
|
numeric = args[0]
|
||||||
command = args[1]
|
command = args[1]
|
||||||
|
args = args[2:]
|
||||||
|
print(args)
|
||||||
except IndexError:
|
except IndexError:
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -155,3 +171,6 @@ def handle_events(irc, data):
|
|||||||
func(irc, numeric, command, args)
|
func(irc, numeric, command, args)
|
||||||
except KeyError: # unhandled event
|
except KeyError: # unhandled event
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def add_cmd(func):
|
||||||
|
bot_commands[func.__name__.lower()] = func
|
@ -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
13
utils.py
Normal 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)
|
Loading…
Reference in New Issue
Block a user