mirror of
https://github.com/jlu5/PyLink.git
synced 2025-01-24 11:14:07 +01:00
WIP: merge IRCNetwork and Protocol classes together
Eventually, the goal is to have both of these hotswappable with inheritance, so this distinction becomes moot.
This commit is contained in:
parent
f8155ff74c
commit
df18e318a8
251
classes.py
251
classes.py
@ -35,7 +35,7 @@ class ProtocolError(RuntimeError):
|
|||||||
class PyLinkNetworkCore(utils.DeprecatedAttributesObject, utils.CamelCaseToSnakeCase):
|
class PyLinkNetworkCore(utils.DeprecatedAttributesObject, utils.CamelCaseToSnakeCase):
|
||||||
"""Base IRC object for PyLink."""
|
"""Base IRC object for PyLink."""
|
||||||
|
|
||||||
def __init__(self, netname, proto, conf):
|
def __init__(self, netname):
|
||||||
"""
|
"""
|
||||||
Initializes an IRC object. This takes 3 variables: the network name
|
Initializes an IRC object. This takes 3 variables: the network name
|
||||||
(a string), the name of the protocol module to use for this connection,
|
(a string), the name of the protocol module to use for this connection,
|
||||||
@ -48,12 +48,26 @@ class PyLinkNetworkCore(utils.DeprecatedAttributesObject, utils.CamelCaseToSnake
|
|||||||
|
|
||||||
self.loghandlers = []
|
self.loghandlers = []
|
||||||
self.name = netname
|
self.name = netname
|
||||||
self.conf = conf
|
self.conf = conf.conf
|
||||||
self.sid = None
|
self.sid = None
|
||||||
self.serverdata = conf['servers'][netname]
|
self.serverdata = conf.conf['servers'][netname]
|
||||||
self.botdata = conf['bot']
|
self.botdata = conf.conf['bot']
|
||||||
self.protoname = proto.__name__.split('.')[-1] # Remove leading pylinkirc.protocols.
|
self.protoname = None # This is updated in init_vars()
|
||||||
self.proto = proto.Class(self)
|
self.proto = self.irc = self # Backwards compat
|
||||||
|
|
||||||
|
# Protocol stuff
|
||||||
|
self.casemapping = 'rfc1459'
|
||||||
|
self.hook_map = {}
|
||||||
|
|
||||||
|
# Lock for updateTS to make sure only one thread can change the channel TS at one time.
|
||||||
|
self.ts_lock = threading.Lock()
|
||||||
|
|
||||||
|
# Lists required conf keys for the server block.
|
||||||
|
self.conf_keys = {'ip', 'port', 'hostname', 'sid', 'sidrange', 'protocol', 'sendpass',
|
||||||
|
'recvpass'}
|
||||||
|
|
||||||
|
# Defines a set of PyLink protocol capabilities
|
||||||
|
self.protocol_caps = set()
|
||||||
|
|
||||||
# These options depend on self.serverdata from above to be set.
|
# These options depend on self.serverdata from above to be set.
|
||||||
self.encoding = None
|
self.encoding = None
|
||||||
@ -105,6 +119,8 @@ class PyLinkNetworkCore(utils.DeprecatedAttributesObject, utils.CamelCaseToSnake
|
|||||||
(Re)sets an IRC object to its default state. This should be called when
|
(Re)sets an IRC object to its default state. This should be called when
|
||||||
an IRC object is first created, and on every reconnection to a network.
|
an IRC object is first created, and on every reconnection to a network.
|
||||||
"""
|
"""
|
||||||
|
self.protoname = __name__.split('.')[-1] # Remove leading pylinkirc.protocols.
|
||||||
|
|
||||||
self.encoding = self.serverdata.get('encoding') or 'utf-8'
|
self.encoding = self.serverdata.get('encoding') or 'utf-8'
|
||||||
self.pingfreq = self.serverdata.get('pingfreq') or 90
|
self.pingfreq = self.serverdata.get('pingfreq') or 90
|
||||||
self.pingtimeout = self.pingfreq * 3
|
self.pingtimeout = self.pingfreq * 3
|
||||||
@ -394,6 +410,31 @@ class PyLinkNetworkCore(utils.DeprecatedAttributesObject, utils.CamelCaseToSnake
|
|||||||
log.debug('(%s) _post_disconnect: Clearing state via init_vars().', self.name)
|
log.debug('(%s) _post_disconnect: Clearing state via init_vars().', self.name)
|
||||||
self.init_vars()
|
self.init_vars()
|
||||||
|
|
||||||
|
def validate_server_conf(self):
|
||||||
|
return
|
||||||
|
|
||||||
|
def has_cap(self, capab):
|
||||||
|
"""
|
||||||
|
Returns whether this protocol module instance has the requested capability.
|
||||||
|
"""
|
||||||
|
return capab.lower() in self.protocol_caps
|
||||||
|
|
||||||
|
def remove_client(self, numeric):
|
||||||
|
"""Internal function to remove a client from our internal state."""
|
||||||
|
for c, v in self.irc.channels.copy().items():
|
||||||
|
v.removeuser(numeric)
|
||||||
|
# Clear empty non-permanent channels.
|
||||||
|
if not (self.irc.channels[c].users or ((self.irc.cmodes.get('permanent'), None) in self.irc.channels[c].modes)):
|
||||||
|
del self.irc.channels[c]
|
||||||
|
assert numeric not in v.users, "IrcChannel's removeuser() is broken!"
|
||||||
|
|
||||||
|
sid = self.irc.get_server(numeric)
|
||||||
|
log.debug('Removing client %s from self.irc.users', numeric)
|
||||||
|
del self.irc.users[numeric]
|
||||||
|
log.debug('Removing client %s from self.irc.servers[%s].users', numeric, sid)
|
||||||
|
self.irc.servers[sid].users.discard(numeric)
|
||||||
|
|
||||||
|
|
||||||
class PyLinkNetworkCoreWithUtils(PyLinkNetworkCore):
|
class PyLinkNetworkCoreWithUtils(PyLinkNetworkCore):
|
||||||
def to_lower(self, text):
|
def to_lower(self, text):
|
||||||
"""Returns a lowercase representation of text based on the IRC object's
|
"""Returns a lowercase representation of text based on the IRC object's
|
||||||
@ -1009,6 +1050,85 @@ class PyLinkNetworkCoreWithUtils(PyLinkNetworkCore):
|
|||||||
result = not result
|
result = not result
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
def updateTS(self, sender, channel, their_ts, modes=None):
|
||||||
|
"""
|
||||||
|
Merges modes of a channel given the remote TS and a list of modes.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Okay, so the situation is that we have 6 possible TS/sender combinations:
|
||||||
|
|
||||||
|
# | our TS lower | TS equal | their TS lower
|
||||||
|
# mode origin is us | OVERWRITE | MERGE | IGNORE
|
||||||
|
# mode origin is uplink | IGNORE | MERGE | OVERWRITE
|
||||||
|
|
||||||
|
if modes is None:
|
||||||
|
modes = []
|
||||||
|
|
||||||
|
def _clear():
|
||||||
|
log.debug("(%s) Clearing local modes from channel %s due to TS change", self.irc.name,
|
||||||
|
channel)
|
||||||
|
self.irc.channels[channel].modes.clear()
|
||||||
|
for p in self.irc.channels[channel].prefixmodes.values():
|
||||||
|
for user in p.copy():
|
||||||
|
if not self.irc.is_internal_client(user):
|
||||||
|
p.discard(user)
|
||||||
|
|
||||||
|
def _apply():
|
||||||
|
if modes:
|
||||||
|
log.debug("(%s) Applying modes on channel %s (TS ok)", self.irc.name,
|
||||||
|
channel)
|
||||||
|
self.irc.apply_modes(channel, modes)
|
||||||
|
|
||||||
|
# Use a lock so only one thread can change a channel's TS at once: this prevents race
|
||||||
|
# conditions from desyncing the channel list.
|
||||||
|
with self.ts_lock:
|
||||||
|
our_ts = self.irc.channels[channel].ts
|
||||||
|
assert type(our_ts) == int, "Wrong type for our_ts (expected int, got %s)" % type(our_ts)
|
||||||
|
assert type(their_ts) == int, "Wrong type for their_ts (expected int, got %s)" % type(their_ts)
|
||||||
|
|
||||||
|
# Check if we're the mode sender based on the UID / SID given.
|
||||||
|
our_mode = self.irc.is_internal_client(sender) or self.irc.is_internal_server(sender)
|
||||||
|
|
||||||
|
log.debug("(%s/%s) our_ts: %s; their_ts: %s; is the mode origin us? %s", self.irc.name,
|
||||||
|
channel, our_ts, their_ts, our_mode)
|
||||||
|
|
||||||
|
if their_ts == our_ts:
|
||||||
|
log.debug("(%s/%s) remote TS of %s is equal to our %s; mode query %s",
|
||||||
|
self.irc.name, channel, their_ts, our_ts, modes)
|
||||||
|
# Their TS is equal to ours. Merge modes.
|
||||||
|
_apply()
|
||||||
|
|
||||||
|
elif (their_ts < our_ts):
|
||||||
|
if their_ts < 750000:
|
||||||
|
log.warning('(%s) Possible desync? Not setting bogus TS %s on channel %s', self.irc.name, their_ts, channel)
|
||||||
|
else:
|
||||||
|
log.debug('(%s) Resetting channel TS of %s from %s to %s (remote has lower TS)',
|
||||||
|
self.irc.name, channel, our_ts, their_ts)
|
||||||
|
self.irc.channels[channel].ts = their_ts
|
||||||
|
|
||||||
|
# Remote TS was lower and we're receiving modes. Clear the modelist and apply theirs.
|
||||||
|
|
||||||
|
_clear()
|
||||||
|
_apply()
|
||||||
|
|
||||||
|
# TODO: these wrappers really need to be standardized
|
||||||
|
def _get_SID(self, sname):
|
||||||
|
"""Returns the SID of a server with the given name, if present."""
|
||||||
|
name = sname.lower()
|
||||||
|
for k, v in self.irc.servers.items():
|
||||||
|
if v.name.lower() == name:
|
||||||
|
return k
|
||||||
|
else:
|
||||||
|
return sname # Fall back to given text instead of None
|
||||||
|
_getSid = _get_SID
|
||||||
|
|
||||||
|
def _get_UID(self, target):
|
||||||
|
"""Converts a nick argument to its matching UID. This differs from irc.nick_to_uid()
|
||||||
|
in that it returns the original text instead of None, if no matching nick is found."""
|
||||||
|
target = self.irc.nick_to_uid(target) or target
|
||||||
|
return target
|
||||||
|
_getUid = _get_UID
|
||||||
|
|
||||||
class IRCNetwork(PyLinkNetworkCoreWithUtils):
|
class IRCNetwork(PyLinkNetworkCoreWithUtils):
|
||||||
def __init__(self, *args):
|
def __init__(self, *args):
|
||||||
super().__init__(*args)
|
super().__init__(*args)
|
||||||
@ -1433,122 +1553,3 @@ class Channel():
|
|||||||
|
|
||||||
return sorted(result, key=self.sortPrefixes)
|
return sorted(result, key=self.sortPrefixes)
|
||||||
IrcChannel = Channel
|
IrcChannel = Channel
|
||||||
|
|
||||||
class Protocol():
|
|
||||||
"""Base Protocol module class for PyLink."""
|
|
||||||
def __init__(self, irc):
|
|
||||||
self.irc = irc
|
|
||||||
self.casemapping = 'rfc1459'
|
|
||||||
self.hook_map = {}
|
|
||||||
|
|
||||||
# Lock for updateTS to make sure only one thread can change the channel TS at one time.
|
|
||||||
self.ts_lock = threading.Lock()
|
|
||||||
|
|
||||||
# Lists required conf keys for the server block.
|
|
||||||
self.conf_keys = {'ip', 'port', 'hostname', 'sid', 'sidrange', 'protocol', 'sendpass',
|
|
||||||
'recvpass'}
|
|
||||||
|
|
||||||
# Defines a set of PyLink protocol capabilities
|
|
||||||
self.protocol_caps = set()
|
|
||||||
|
|
||||||
def validateServerConf(self):
|
|
||||||
return
|
|
||||||
|
|
||||||
def hasCap(self, capab):
|
|
||||||
"""
|
|
||||||
Returns whether this protocol module instance has the requested capability.
|
|
||||||
"""
|
|
||||||
return capab.lower() in self.protocol_caps
|
|
||||||
|
|
||||||
def removeClient(self, numeric):
|
|
||||||
"""Internal function to remove a client from our internal state."""
|
|
||||||
for c, v in self.irc.channels.copy().items():
|
|
||||||
v.removeuser(numeric)
|
|
||||||
# Clear empty non-permanent channels.
|
|
||||||
if not (self.irc.channels[c].users or ((self.irc.cmodes.get('permanent'), None) in self.irc.channels[c].modes)):
|
|
||||||
del self.irc.channels[c]
|
|
||||||
assert numeric not in v.users, "IrcChannel's removeuser() is broken!"
|
|
||||||
|
|
||||||
sid = self.irc.get_server(numeric)
|
|
||||||
log.debug('Removing client %s from self.irc.users', numeric)
|
|
||||||
del self.irc.users[numeric]
|
|
||||||
log.debug('Removing client %s from self.irc.servers[%s].users', numeric, sid)
|
|
||||||
self.irc.servers[sid].users.discard(numeric)
|
|
||||||
|
|
||||||
def updateTS(self, sender, channel, their_ts, modes=None):
|
|
||||||
"""
|
|
||||||
Merges modes of a channel given the remote TS and a list of modes.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Okay, so the situation is that we have 6 possible TS/sender combinations:
|
|
||||||
|
|
||||||
# | our TS lower | TS equal | their TS lower
|
|
||||||
# mode origin is us | OVERWRITE | MERGE | IGNORE
|
|
||||||
# mode origin is uplink | IGNORE | MERGE | OVERWRITE
|
|
||||||
|
|
||||||
if modes is None:
|
|
||||||
modes = []
|
|
||||||
|
|
||||||
def _clear():
|
|
||||||
log.debug("(%s) Clearing local modes from channel %s due to TS change", self.irc.name,
|
|
||||||
channel)
|
|
||||||
self.irc.channels[channel].modes.clear()
|
|
||||||
for p in self.irc.channels[channel].prefixmodes.values():
|
|
||||||
for user in p.copy():
|
|
||||||
if not self.irc.is_internal_client(user):
|
|
||||||
p.discard(user)
|
|
||||||
|
|
||||||
def _apply():
|
|
||||||
if modes:
|
|
||||||
log.debug("(%s) Applying modes on channel %s (TS ok)", self.irc.name,
|
|
||||||
channel)
|
|
||||||
self.irc.apply_modes(channel, modes)
|
|
||||||
|
|
||||||
# Use a lock so only one thread can change a channel's TS at once: this prevents race
|
|
||||||
# conditions from desyncing the channel list.
|
|
||||||
with self.ts_lock:
|
|
||||||
our_ts = self.irc.channels[channel].ts
|
|
||||||
assert type(our_ts) == int, "Wrong type for our_ts (expected int, got %s)" % type(our_ts)
|
|
||||||
assert type(their_ts) == int, "Wrong type for their_ts (expected int, got %s)" % type(their_ts)
|
|
||||||
|
|
||||||
# Check if we're the mode sender based on the UID / SID given.
|
|
||||||
our_mode = self.irc.is_internal_client(sender) or self.irc.is_internal_server(sender)
|
|
||||||
|
|
||||||
log.debug("(%s/%s) our_ts: %s; their_ts: %s; is the mode origin us? %s", self.irc.name,
|
|
||||||
channel, our_ts, their_ts, our_mode)
|
|
||||||
|
|
||||||
if their_ts == our_ts:
|
|
||||||
log.debug("(%s/%s) remote TS of %s is equal to our %s; mode query %s",
|
|
||||||
self.irc.name, channel, their_ts, our_ts, modes)
|
|
||||||
# Their TS is equal to ours. Merge modes.
|
|
||||||
_apply()
|
|
||||||
|
|
||||||
elif (their_ts < our_ts):
|
|
||||||
if their_ts < 750000:
|
|
||||||
log.warning('(%s) Possible desync? Not setting bogus TS %s on channel %s', self.irc.name, their_ts, channel)
|
|
||||||
else:
|
|
||||||
log.debug('(%s) Resetting channel TS of %s from %s to %s (remote has lower TS)',
|
|
||||||
self.irc.name, channel, our_ts, their_ts)
|
|
||||||
self.irc.channels[channel].ts = their_ts
|
|
||||||
|
|
||||||
# Remote TS was lower and we're receiving modes. Clear the modelist and apply theirs.
|
|
||||||
|
|
||||||
_clear()
|
|
||||||
_apply()
|
|
||||||
|
|
||||||
def _get_SID(self, sname):
|
|
||||||
"""Returns the SID of a server with the given name, if present."""
|
|
||||||
name = sname.lower()
|
|
||||||
for k, v in self.irc.servers.items():
|
|
||||||
if v.name.lower() == name:
|
|
||||||
return k
|
|
||||||
else:
|
|
||||||
return sname # Fall back to given text instead of None
|
|
||||||
_getSid = _get_SID
|
|
||||||
|
|
||||||
def _get_UID(self, target):
|
|
||||||
"""Converts a nick argument to its matching UID. This differs from irc.nick_to_uid()
|
|
||||||
in that it returns the original text instead of None, if no matching nick is found."""
|
|
||||||
target = self.irc.nick_to_uid(target) or target
|
|
||||||
return target
|
|
||||||
_getUid = _get_UID
|
|
||||||
|
@ -6,11 +6,11 @@ import time
|
|||||||
import re
|
import re
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
|
||||||
from pylinkirc.classes import Protocol
|
from pylinkirc.classes import IRCNetwork
|
||||||
from pylinkirc.log import log
|
from pylinkirc.log import log
|
||||||
from pylinkirc import utils
|
from pylinkirc import utils
|
||||||
|
|
||||||
class IRCCommonProtocol(Protocol):
|
class IRCCommonProtocol(IRCNetwork):
|
||||||
def validateServerConf(self):
|
def validateServerConf(self):
|
||||||
"""Validates that the server block given contains the required keys."""
|
"""Validates that the server block given contains the required keys."""
|
||||||
for k in self.conf_keys:
|
for k in self.conf_keys:
|
||||||
|
2
pylink
2
pylink
@ -85,7 +85,7 @@ if __name__ == '__main__':
|
|||||||
else:
|
else:
|
||||||
# Fetch the correct protocol module
|
# Fetch the correct protocol module
|
||||||
proto = utils.getProtocolModule(protoname)
|
proto = utils.getProtocolModule(protoname)
|
||||||
world.networkobjects[network] = irc = classes.Irc(network, proto, conf.conf)
|
world.networkobjects[network] = proto.Class(network)
|
||||||
|
|
||||||
world.started.set()
|
world.started.set()
|
||||||
log.info("Loaded plugins: %s", ', '.join(sorted(world.plugins.keys())))
|
log.info("Loaded plugins: %s", ', '.join(sorted(world.plugins.keys())))
|
||||||
|
Loading…
Reference in New Issue
Block a user