mirror of
				https://github.com/jlu5/PyLink.git
				synced 2025-11-04 00:47:21 +01:00 
			
		
		
		
	Merge branch 'devel'
This commit is contained in:
		
						commit
						da0101e750
					
				@ -14,6 +14,7 @@ Dependencies currently include:
 | 
			
		||||
 | 
			
		||||
* Python 3.4+
 | 
			
		||||
* PyYAML (`pip install pyyaml` or `apt-get install python3-yaml`)
 | 
			
		||||
* *For the relay plugin only*: expiringdict (`pip install expiringdict`/`apt-get install python3-expiringdict`)
 | 
			
		||||
 | 
			
		||||
#### Supported IRCds
 | 
			
		||||
 | 
			
		||||
@ -23,7 +24,7 @@ Dependencies currently include:
 | 
			
		||||
 | 
			
		||||
### Installation
 | 
			
		||||
 | 
			
		||||
1) Rename `config.yml.example` to `config.yml` and configure your instance there. Not all options are properly implemented yet, and the configuration schema isn't finalized yet - this means your configuration may break in an update!
 | 
			
		||||
1) Rename `config.yml.example` to `config.yml` and configure your instance there. Not all options are properly implemented yet, and the configuration schema isn't finalized yet - this means that your configuration may break in an update!
 | 
			
		||||
 | 
			
		||||
2) Run `main.py` from the command line.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -8,6 +8,10 @@ bot:
 | 
			
		||||
    nick: pylink
 | 
			
		||||
    user: pylink
 | 
			
		||||
    realname: PyLink Service Client
 | 
			
		||||
 | 
			
		||||
    # Server description (shown in /links, /whois, etc.)
 | 
			
		||||
    serverdesc: PyLink Server
 | 
			
		||||
 | 
			
		||||
    # Console log verbosity: see https://docs.python.org/3/library/logging.html#logging-levels
 | 
			
		||||
    loglevel: DEBUG
 | 
			
		||||
 | 
			
		||||
@ -31,6 +35,12 @@ servers:
 | 
			
		||||
        # The first char must be a digit [0-9], and the remaining two chars may be letters [A-Z] or digits.
 | 
			
		||||
        sid: "0AL"
 | 
			
		||||
 | 
			
		||||
        # SID range - the range of SIDs PyLink is allowed to use to generate server IDs. On TS6,
 | 
			
		||||
        # this should be a combination of digits, letters, and #'s. Each # denotes a range (0-9A-Z)
 | 
			
		||||
        # of characters that can be used by PyLink. You will want to make sure no other servers
 | 
			
		||||
        # are using this range. There must be at least one # in the entry.
 | 
			
		||||
        sidrange: "8##"
 | 
			
		||||
 | 
			
		||||
        # Autojoin channels
 | 
			
		||||
        channels: ["#pylink"]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -38,7 +38,7 @@ def handle_commands(irc, source, command, args):
 | 
			
		||||
            return
 | 
			
		||||
utils.add_hook(handle_commands, 'PRIVMSG')
 | 
			
		||||
 | 
			
		||||
# Return WHOIS replies to IRCds that use them.
 | 
			
		||||
# Handle WHOIS queries, for IRCds that send them across servers (charybdis, UnrealIRCd; NOT InspIRCd).
 | 
			
		||||
def handle_whois(irc, source, command, args):
 | 
			
		||||
    target = args['target']
 | 
			
		||||
    user = irc.users.get(target)
 | 
			
		||||
@ -51,16 +51,6 @@ def handle_whois(irc, source, command, args):
 | 
			
		||||
    # https://www.alien.net.au/irc/irc2numerics.html
 | 
			
		||||
    # 311: sends nick!user@host information
 | 
			
		||||
    f(irc, server, 311, source, "%s %s %s * :%s" % (nick, user.ident, user.host, user.realname))
 | 
			
		||||
    # 312: sends the server the target is on, and the name
 | 
			
		||||
    f(irc, server, 312, source, "%s %s :PyLink Server" % (nick, irc.serverdata['hostname']))
 | 
			
		||||
    # 313: sends a string denoting the target's operator privilege;
 | 
			
		||||
    # we'll only send it if the user has umode +o.
 | 
			
		||||
    if ('o', None) in user.modes:
 | 
			
		||||
        f(irc, server, 313, source, "%s :is an IRC Operator" % nick)
 | 
			
		||||
    # 379: RPL_WHOISMODES, used by UnrealIRCd and InspIRCd.
 | 
			
		||||
    # Only shown to opers!
 | 
			
		||||
    if sourceisOper:
 | 
			
		||||
        f(irc, server, 379, source, '%s :is using modes %s' % (nick, utils.joinModes(user.modes)))
 | 
			
		||||
    # 319: RPL_WHOISCHANNELS, shows channel list
 | 
			
		||||
    public_chans = []
 | 
			
		||||
    for chan in user.channels:
 | 
			
		||||
@ -71,27 +61,41 @@ def handle_whois(irc, source, command, args):
 | 
			
		||||
            (irc.cmodes.get('private'), None) in c.modes) \
 | 
			
		||||
            and not (sourceisOper or source in c.users):
 | 
			
		||||
                continue
 | 
			
		||||
        # TODO: show prefix modes like a regular IRCd does.
 | 
			
		||||
        # Show prefix modes like a regular IRCd does.
 | 
			
		||||
        for prefixmode, prefixchar in irc.prefixmodes.items():
 | 
			
		||||
            modename = [mname for mname, char in irc.cmodes.items() if char == prefixmode]
 | 
			
		||||
            if modename and target in c.prefixmodes[modename[0]+'s']:
 | 
			
		||||
                chan = prefixchar + chan
 | 
			
		||||
        public_chans.append(chan)
 | 
			
		||||
    if public_chans:
 | 
			
		||||
        f(irc, server, 319, source, '%s :%s' % (nick, ' '.join(public_chans)))
 | 
			
		||||
    # 317: shows idle and signon time. Though we don't track the user's real
 | 
			
		||||
    # idle time; we just return 0.
 | 
			
		||||
    # 317 GL GL 15 1437632859 :seconds idle, signon time
 | 
			
		||||
    # 312: sends the server the target is on, and its server description.
 | 
			
		||||
    f(irc, server, 312, source, "%s %s :%s" % (nick, irc.serverdata['hostname'],
 | 
			
		||||
      irc.serverdata.get('serverdesc') or irc.botdata['serverdesc']))
 | 
			
		||||
    # 313: sends a string denoting the target's operator privilege,
 | 
			
		||||
    # only if they have umode +o.
 | 
			
		||||
    if ('o', None) in user.modes:
 | 
			
		||||
        f(irc, server, 313, source, "%s :is an IRC Operator" % nick)
 | 
			
		||||
    # 379: RPL_WHOISMODES, used by UnrealIRCd and InspIRCd.
 | 
			
		||||
    # Only show this to opers!
 | 
			
		||||
    if sourceisOper:
 | 
			
		||||
        f(irc, server, 379, source, '%s :is using modes %s' % (nick, utils.joinModes(user.modes)))
 | 
			
		||||
    # 317: shows idle and signon time. However, we don't track the user's real
 | 
			
		||||
    # idle time, so we simply return 0.
 | 
			
		||||
    # <- 317 GL GL 15 1437632859 :seconds idle, signon time
 | 
			
		||||
    f(irc, server, 317, source, "%s 0 %s :seconds idle, signon time" % (nick, user.ts))
 | 
			
		||||
    try:
 | 
			
		||||
        # Iterate over plugin-created WHOIS handlers. They return a tuple
 | 
			
		||||
        # or list with two arguments: the numeric, and the text to send.
 | 
			
		||||
        for func in utils.whois_handlers:
 | 
			
		||||
    for func in utils.whois_handlers:
 | 
			
		||||
    # Iterate over custom plugin WHOIS handlers. They return a tuple
 | 
			
		||||
    # or list with two arguments: the numeric, and the text to send.
 | 
			
		||||
        try:
 | 
			
		||||
            res = func(irc, target)
 | 
			
		||||
            if res:
 | 
			
		||||
                num, text = res
 | 
			
		||||
                f(irc, server, num, source, text)
 | 
			
		||||
    except Exception as e:
 | 
			
		||||
        # Again, we wouldn't want this to crash our service, in case
 | 
			
		||||
        # something goes wrong!
 | 
			
		||||
        log.exception('Error caught in WHOIS handler: %s', e)
 | 
			
		||||
    finally:
 | 
			
		||||
        # 318: End of WHOIS.
 | 
			
		||||
        f(irc, server, 318, source, "%s :End of /WHOIS list" % nick)
 | 
			
		||||
        except Exception as e:
 | 
			
		||||
            # Again, we wouldn't want this to crash our service, in case
 | 
			
		||||
            # something goes wrong!
 | 
			
		||||
            log.exception('(%s) Error caught in WHOIS handler: %s', irc.name, e)
 | 
			
		||||
    # 318: End of WHOIS.
 | 
			
		||||
    f(irc, server, 318, source, "%s :End of /WHOIS list" % nick)
 | 
			
		||||
utils.add_hook(handle_whois, 'WHOIS')
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										12
									
								
								main.py
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								main.py
									
									
									
									
									
								
							@ -278,10 +278,10 @@ if __name__ == '__main__':
 | 
			
		||||
            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)
 | 
			
		||||
            if str(e) == ('No module named %r' % plugin):
 | 
			
		||||
                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))
 | 
			
		||||
                log.error('Failed to load plugin %r: ImportError: %s', plugin, str(e))
 | 
			
		||||
        else:
 | 
			
		||||
            if hasattr(pl, 'main'):
 | 
			
		||||
                log.debug('Calling main() function of plugin %r', pl)
 | 
			
		||||
@ -293,10 +293,10 @@ if __name__ == '__main__':
 | 
			
		||||
            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)
 | 
			
		||||
            if str(e) == ('No module named %r' % protoname):
 | 
			
		||||
                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))
 | 
			
		||||
                log.critical('Failed to load protocol module: ImportError: %s', protoname, str(e))
 | 
			
		||||
            sys.exit(2)
 | 
			
		||||
        else:
 | 
			
		||||
            utils.networkobjects[network] = Irc(network, proto, conf.conf)
 | 
			
		||||
 | 
			
		||||
@ -8,6 +8,8 @@ import threading
 | 
			
		||||
import string
 | 
			
		||||
from collections import defaultdict
 | 
			
		||||
 | 
			
		||||
from expiringdict import ExpiringDict
 | 
			
		||||
 | 
			
		||||
import utils
 | 
			
		||||
from log import log
 | 
			
		||||
from conf import confname
 | 
			
		||||
@ -19,6 +21,7 @@ dbname += '.db'
 | 
			
		||||
 | 
			
		||||
relayusers = defaultdict(dict)
 | 
			
		||||
spawnlocks = defaultdict(threading.Lock)
 | 
			
		||||
savecache = ExpiringDict(max_len=5, max_age_seconds=10)
 | 
			
		||||
 | 
			
		||||
def relayWhoisHandlers(irc, target):
 | 
			
		||||
    user = irc.users[target]
 | 
			
		||||
@ -31,41 +34,46 @@ def relayWhoisHandlers(irc, target):
 | 
			
		||||
                                                     remotenick)]
 | 
			
		||||
utils.whois_handlers.append(relayWhoisHandlers)
 | 
			
		||||
 | 
			
		||||
def normalizeNick(irc, netname, nick, separator=None, oldnick=''):
 | 
			
		||||
def normalizeNick(irc, netname, nick, separator=None, uid=''):
 | 
			
		||||
    separator = separator or irc.serverdata.get('separator') or "/"
 | 
			
		||||
    log.debug('(%s) normalizeNick: using %r as separator.', irc.name, separator)
 | 
			
		||||
    orig_nick = nick
 | 
			
		||||
    protoname = irc.proto.__name__
 | 
			
		||||
    maxnicklen = irc.maxnicklen
 | 
			
		||||
    if not protoname.startswith(('insp', 'unreal')):
 | 
			
		||||
        # Charybdis doesn't allow / in usernames, and will quit with
 | 
			
		||||
        # a protocol violation if there is one.
 | 
			
		||||
        # Charybdis doesn't allow / in usernames, and will SQUIT with
 | 
			
		||||
        # a protocol violation if it sees one.
 | 
			
		||||
        separator = separator.replace('/', '|')
 | 
			
		||||
        nick = nick.replace('/', '|')
 | 
			
		||||
    if nick.startswith(tuple(string.digits)):
 | 
			
		||||
        # On TS6 IRCds, nicks that start with 0-9 are only allowed if
 | 
			
		||||
        # they match the UID of the originating server. Otherwise, you'll
 | 
			
		||||
        # get nasty protocol violations!
 | 
			
		||||
        # get nasty protocol violation SQUITs!
 | 
			
		||||
        nick = '_' + nick
 | 
			
		||||
    tagnicks = True
 | 
			
		||||
 | 
			
		||||
    suffix = separator + netname
 | 
			
		||||
    nick = nick[:maxnicklen]
 | 
			
		||||
    # Maximum allowed length of a nickname.
 | 
			
		||||
    # Maximum allowed length of a nickname, minus the obligatory /network tag.
 | 
			
		||||
    allowedlength = maxnicklen - len(suffix)
 | 
			
		||||
    # If a nick is too long, the real nick portion must be cut off, but the
 | 
			
		||||
    # /network suffix must remain the same.
 | 
			
		||||
 | 
			
		||||
    # If a nick is too long, the real nick portion will be cut off, but the
 | 
			
		||||
    # /network suffix MUST remain the same.
 | 
			
		||||
    nick = nick[:allowedlength]
 | 
			
		||||
    nick += suffix
 | 
			
		||||
    # FIXME: factorize
 | 
			
		||||
    while utils.nickToUid(irc, nick) or utils.nickToUid(irc, oldnick) and not \
 | 
			
		||||
            isRelayClient(irc, utils.nickToUid(irc, nick)):
 | 
			
		||||
        # The nick we want exists? Darn, create another one then, but only if
 | 
			
		||||
        # the target isn't an internal client!
 | 
			
		||||
        # Increase the separator length by 1 if the user was already tagged,
 | 
			
		||||
        # but couldn't be created due to a nick conflict.
 | 
			
		||||
        # This can happen when someone steals a relay user's nick.
 | 
			
		||||
 | 
			
		||||
    # The nick we want exists? Darn, create another one then.
 | 
			
		||||
    # Increase the separator length by 1 if the user was already tagged,
 | 
			
		||||
    # but couldn't be created due to a nick conflict.
 | 
			
		||||
    # This can happen when someone steals a relay user's nick.
 | 
			
		||||
 | 
			
		||||
    # However, if the user is changing from, say, a long, cut-off nick to another long,
 | 
			
		||||
    # cut-off nick, we don't need to check for duplicates and tag the nick twice.
 | 
			
		||||
 | 
			
		||||
    # somecutoffnick/net would otherwise be erroneous NICK'ed to somecutoffnic//net,
 | 
			
		||||
    # even though there would be no collision because the old and new nicks are from
 | 
			
		||||
    # the same client.
 | 
			
		||||
    while utils.nickToUid(irc, nick) and utils.nickToUid(irc, nick) != uid:
 | 
			
		||||
        new_sep = separator + separator[-1]
 | 
			
		||||
        log.debug('(%s) normalizeNick: nick %r is in use; using %r as new_sep.', irc.name, nick, new_sep)
 | 
			
		||||
        nick = normalizeNick(irc, netname, orig_nick, separator=new_sep)
 | 
			
		||||
@ -271,7 +279,7 @@ utils.add_hook(handle_squit, 'SQUIT')
 | 
			
		||||
def handle_nick(irc, numeric, command, args):
 | 
			
		||||
    for netname, user in relayusers[(irc.name, numeric)].items():
 | 
			
		||||
        remoteirc = utils.networkobjects[netname]
 | 
			
		||||
        newnick = normalizeNick(remoteirc, irc.name, args['newnick'])
 | 
			
		||||
        newnick = normalizeNick(remoteirc, irc.name, args['newnick'], uid=user)
 | 
			
		||||
        if remoteirc.users[user].nick != newnick:
 | 
			
		||||
            remoteirc.proto.nickClient(remoteirc, user, newnick)
 | 
			
		||||
utils.add_hook(handle_nick, 'NICK')
 | 
			
		||||
@ -396,9 +404,16 @@ def handle_kick(irc, source, command, args):
 | 
			
		||||
                # Join the kicked client back with its respective modes.
 | 
			
		||||
                irc.proto.sjoinServer(irc, irc.sid, channel, [(modes, target)])
 | 
			
		||||
                if kicker in irc.users:
 | 
			
		||||
                    log.info('(%s) Blocked KICK (reason %r) from %s to relay client %s/%s on %s.',
 | 
			
		||||
                             irc.name, args['text'], irc.users[source].nick,
 | 
			
		||||
                             remoteirc.users[real_target].nick, remoteirc.name, channel)
 | 
			
		||||
                    utils.msg(irc, kicker, "This channel is claimed; your kick to "
 | 
			
		||||
                                           "%s has been blocked because you are not "
 | 
			
		||||
                                           "(half)opped." % channel, notice=True)
 | 
			
		||||
                else:
 | 
			
		||||
                    log.info('(%s) Blocked KICK (reason %r) from server %s to relay client %s/%s on %s.',
 | 
			
		||||
                             irc.name, args['text'], irc.servers[source].name,
 | 
			
		||||
                             remoteirc.users[real_target].nick, remoteirc.name, channel)
 | 
			
		||||
                return
 | 
			
		||||
 | 
			
		||||
        if not real_target:
 | 
			
		||||
@ -626,10 +641,17 @@ def handle_kill(irc, numeric, command, args):
 | 
			
		||||
                client = getRemoteUser(remoteirc, irc, realuser[1])
 | 
			
		||||
                irc.proto.sjoinServer(irc, irc.sid, localchan, [(modes, client)])
 | 
			
		||||
        if userdata and numeric in irc.users:
 | 
			
		||||
            log.info('(%s) Blocked KILL (reason %r) from %s to relay client %s/%s.',
 | 
			
		||||
                     irc.name, args['text'], irc.users[numeric].nick,
 | 
			
		||||
                     remoteirc.users[realuser[1]].nick, realuser[0])
 | 
			
		||||
            utils.msg(irc, numeric, "Your kill to %s has been blocked "
 | 
			
		||||
                                    "because PyLink does not allow killing"
 | 
			
		||||
                                    " users over the relay at this time." % \
 | 
			
		||||
                                    userdata.nick, notice=True)
 | 
			
		||||
        else:
 | 
			
		||||
            log.info('(%s) Blocked KILL (reason %r) from server %s to relay client %s/%s.',
 | 
			
		||||
                     irc.name, args['text'], irc.servers[numeric].name,
 | 
			
		||||
                     remoteirc.users[realuser[1]].nick, realuser[0])
 | 
			
		||||
    # Target user was local.
 | 
			
		||||
    else:
 | 
			
		||||
        # IMPORTANT: some IRCds (charybdis) don't send explicit QUIT messages
 | 
			
		||||
@ -912,8 +934,18 @@ def handle_save(irc, numeric, command, args):
 | 
			
		||||
        remotenet, remoteuser = realuser
 | 
			
		||||
        remoteirc = utils.networkobjects[remotenet]
 | 
			
		||||
        nick = remoteirc.users[remoteuser].nick
 | 
			
		||||
        newnick = normalizeNick(irc, remotenet, nick, oldnick=args['oldnick'])
 | 
			
		||||
        irc.proto.nickClient(irc, target, newnick)
 | 
			
		||||
        # Limit how many times we can attempt to fix our nick, to prevent
 | 
			
		||||
        # floods and such.
 | 
			
		||||
        if savecache.setdefault(target, 0) <= 5:
 | 
			
		||||
            newnick = normalizeNick(irc, remotenet, nick)
 | 
			
		||||
            log.info('(%s) SAVE received for relay client %r (%s), fixing nick to %s',
 | 
			
		||||
                      irc.name, target, nick, newnick)
 | 
			
		||||
            irc.proto.nickClient(irc, target, newnick)
 | 
			
		||||
        else:
 | 
			
		||||
            log.warning('(%s) SAVE received for relay client %r (%s), not '
 | 
			
		||||
                        'fixing nick again due to 5 failed attempts in '
 | 
			
		||||
                        'the last 10 seconds!',  irc.name, target, nick)
 | 
			
		||||
        savecache[target] += 1
 | 
			
		||||
    else:
 | 
			
		||||
        # Somebody else on the network (not a PyLink client) had a nick collision;
 | 
			
		||||
        # relay this as a nick change appropriately.
 | 
			
		||||
 | 
			
		||||
@ -101,8 +101,8 @@ def sjoinServer(irc, server, channel, users, ts=None):
 | 
			
		||||
            irc.users[user].channels.add(channel)
 | 
			
		||||
        except KeyError:  # Not initialized yet?
 | 
			
		||||
            log.debug("(%s) sjoinServer: KeyError trying to add %r to %r's channel list?", irc.name, channel, user)
 | 
			
		||||
    if ts < orig_ts:
 | 
			
		||||
        # Only save our prefix modes in the channel state if our TS is lower than theirs.
 | 
			
		||||
    if ts <= orig_ts:
 | 
			
		||||
        # Only save our prefix modes in the channel state if our TS is lower than or equal to theirs.
 | 
			
		||||
        utils.applyModes(irc, channel, changedmodes)
 | 
			
		||||
    namelist = ' '.join(namelist)
 | 
			
		||||
    _send(irc, server, "FJOIN {channel} {ts} {modes} :{users}".format(
 | 
			
		||||
@ -329,8 +329,9 @@ def connect(irc):
 | 
			
		||||
    f('CAPAB START 1202')
 | 
			
		||||
    f('CAPAB CAPABILITIES :PROTOCOL=1202')
 | 
			
		||||
    f('CAPAB END')
 | 
			
		||||
    f('SERVER {host} {Pass} 0 {sid} :PyLink Service'.format(host=irc.serverdata["hostname"],
 | 
			
		||||
      Pass=irc.serverdata["sendpass"], sid=irc.sid))
 | 
			
		||||
    f('SERVER {host} {Pass} 0 {sid} :{sdesc}'.format(host=irc.serverdata["hostname"],
 | 
			
		||||
      Pass=irc.serverdata["sendpass"], sid=irc.sid,
 | 
			
		||||
      sdesc=irc.serverdata.get('serverdesc') or irc.botdata['serverdesc']))
 | 
			
		||||
    f(':%s BURST %s' % (irc.sid, ts))
 | 
			
		||||
    f(':%s ENDBURST' % (irc.sid))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -117,8 +117,8 @@ def sjoinServer(irc, server, channel, users, ts=None):
 | 
			
		||||
                ts=ts, users=namelist, channel=channel,
 | 
			
		||||
                modes=utils.joinModes(modes)))
 | 
			
		||||
        irc.channels[channel].users.update(uids)
 | 
			
		||||
    if ts < orig_ts:
 | 
			
		||||
        # Only save our prefix modes in the channel state if our TS is lower than theirs.
 | 
			
		||||
    if ts <= orig_ts:
 | 
			
		||||
       # Only save our prefix modes in the channel state if our TS is lower than or equal to theirs.
 | 
			
		||||
        utils.applyModes(irc, channel, changedmodes)
 | 
			
		||||
 | 
			
		||||
def _sendModes(irc, numeric, target, modes, ts=None):
 | 
			
		||||
@ -345,7 +345,8 @@ def connect(irc):
 | 
			
		||||
    #       and allows sending CHGHOST without ENCAP.
 | 
			
		||||
    f('CAPAB :QS ENCAP EX CHW IE KNOCK SAVE SERVICES TB EUID')
 | 
			
		||||
 | 
			
		||||
    f('SERVER %s 0 :PyLink Service' % irc.serverdata["hostname"])
 | 
			
		||||
    f('SERVER %s 0 :%s' % (irc.serverdata["hostname"],
 | 
			
		||||
                           irc.serverdata.get('serverdesc') or irc.botdata['serverdesc']))
 | 
			
		||||
 | 
			
		||||
def handle_ping(irc, source, command, args):
 | 
			
		||||
    # PING:
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user