mirror of
				https://github.com/jlu5/PyLink.git
				synced 2025-11-04 00:47:21 +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):
 | 
			
		||||
    """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
 | 
			
		||||
        (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.name = netname
 | 
			
		||||
        self.conf = conf
 | 
			
		||||
        self.conf = conf.conf
 | 
			
		||||
        self.sid = None
 | 
			
		||||
        self.serverdata = conf['servers'][netname]
 | 
			
		||||
        self.botdata = conf['bot']
 | 
			
		||||
        self.protoname = proto.__name__.split('.')[-1]  # Remove leading pylinkirc.protocols.
 | 
			
		||||
        self.proto = proto.Class(self)
 | 
			
		||||
        self.serverdata = conf.conf['servers'][netname]
 | 
			
		||||
        self.botdata = conf.conf['bot']
 | 
			
		||||
        self.protoname = None  # This is updated in init_vars()
 | 
			
		||||
        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.
 | 
			
		||||
        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
 | 
			
		||||
        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.pingfreq = self.serverdata.get('pingfreq') or 90
 | 
			
		||||
        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)
 | 
			
		||||
        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):
 | 
			
		||||
    def to_lower(self, text):
 | 
			
		||||
        """Returns a lowercase representation of text based on the IRC object's
 | 
			
		||||
@ -1009,6 +1050,85 @@ class PyLinkNetworkCoreWithUtils(PyLinkNetworkCore):
 | 
			
		||||
            result = not 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):
 | 
			
		||||
    def __init__(self, *args):
 | 
			
		||||
        super().__init__(*args)
 | 
			
		||||
@ -1433,122 +1553,3 @@ class Channel():
 | 
			
		||||
 | 
			
		||||
        return sorted(result, key=self.sortPrefixes)
 | 
			
		||||
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
 | 
			
		||||
from collections import defaultdict
 | 
			
		||||
 | 
			
		||||
from pylinkirc.classes import Protocol
 | 
			
		||||
from pylinkirc.classes import IRCNetwork
 | 
			
		||||
from pylinkirc.log import log
 | 
			
		||||
from pylinkirc import utils
 | 
			
		||||
 | 
			
		||||
class IRCCommonProtocol(Protocol):
 | 
			
		||||
class IRCCommonProtocol(IRCNetwork):
 | 
			
		||||
    def validateServerConf(self):
 | 
			
		||||
        """Validates that the server block given contains the required keys."""
 | 
			
		||||
        for k in self.conf_keys:
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										2
									
								
								pylink
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								pylink
									
									
									
									
									
								
							@ -85,7 +85,7 @@ if __name__ == '__main__':
 | 
			
		||||
        else:
 | 
			
		||||
            # Fetch the correct protocol module
 | 
			
		||||
            proto = utils.getProtocolModule(protoname)
 | 
			
		||||
            world.networkobjects[network] = irc = classes.Irc(network, proto, conf.conf)
 | 
			
		||||
            world.networkobjects[network] = proto.Class(network)
 | 
			
		||||
 | 
			
		||||
    world.started.set()
 | 
			
		||||
    log.info("Loaded plugins: %s", ', '.join(sorted(world.plugins.keys())))
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user