Example #1
0
 def handle_privmsg(self, source, command, args):
     # Convert nicks to UIDs, where they exist.
     target = self._getNick(args[0])
     # We use lowercase channels internally, but uppercase UIDs.
     if utils.isChannel(target):
         target = utils.toLower(self.irc, target)
     return {'target': target, 'text': args[1]}
Example #2
0
    def handle_mode(self, numeric, command, args):
        # <- :unreal.midnight.vpn MODE #endlessvoid +bb test!*@* *!*@bad.net
        # <- :unreal.midnight.vpn MODE #endlessvoid +q GL 1444361345
        # <- :unreal.midnight.vpn MODE #endlessvoid +ntCo GL 1444361345
        # <- :unreal.midnight.vpn MODE #endlessvoid +mntClfo 5 [10t]:5  GL 1444361345
        # <- :GL MODE #services +v GL

        # This seems pretty relatively inconsistent - why do some commands have a TS at the end while others don't?
        # Answer: the first syntax (MODE sent by SERVER) is used for channel bursts - according to Unreal 3.2 docs,
        # the last argument should be interpreted as a timestamp ONLY if it is a number and the sender is a server.
        # Ban bursting does not give any TS, nor do normal users setting modes. SAMODE is special though, it will
        # send 0 as a TS argument (which should be ignored unless breaking the internal channel TS is desired).

        # Also, we need to get rid of that extra space following the +f argument. :|
        if utils.isChannel(args[0]):
            channel = utils.toLower(self.irc, args[0])
            oldobj = self.irc.channels[channel].deepcopy()
            modes = list(filter(None, args[1:]))  # normalize whitespace
            parsedmodes = utils.parseModes(self.irc, channel, modes)
            if parsedmodes:
                utils.applyModes(self.irc, channel, parsedmodes)
            if numeric in self.irc.servers and args[-1].isdigit():
                # Sender is a server AND last arg is number. Perform TS updates.
                their_ts = int(args[-1])
                if their_ts > 0:
                    self.updateTS(channel, their_ts)
            return {'target': channel, 'modes': parsedmodes, 'oldchan': oldobj}
        else:
            log.warning("(%s) received MODE for non-channel target: %r",
                        self.irc.name, args)
            raise NotImplementedError
Example #3
0
def joinclient(irc, source, args):
    """<target> <channel1>,[<channel2>], etc.

    Admin-only. Joins <target>, the nick of a PyLink client, to a comma-separated list of channels."""
    utils.checkAuthenticated(irc, source, allowOper=False)
    try:
        nick = args[0]
        clist = args[1].split(',')
        if not clist:
            raise IndexError
    except IndexError:
        irc.reply(
            "Error: Not enough arguments. Needs 2: nick, comma separated list of channels."
        )
        return
    u = utils.nickToUid(irc, nick)
    if not utils.isManipulatableClient(irc, u):
        irc.reply(
            "Error: Cannot force join a protected PyLink services client.")
        return
    for channel in clist:
        if not utils.isChannel(channel):
            irc.reply("Error: Invalid channel name %r." % channel)
            return
        irc.proto.joinClient(u, channel)
        irc.callHooks([
            u, 'PYLINK_BOTSPLUGIN_JOIN', {
                'channel': channel,
                'users': [u],
                'modes': irc.channels[channel].modes,
                'parse_as': 'JOIN'
            }
        ])
Example #4
0
def kick(irc, source, args):
    """<source> <channel> <user> [<reason>]

    Admin-only. Kicks <user> from <channel> via <source>, where <source> is the nick of a PyLink client."""
    utils.checkAuthenticated(irc, source, allowOper=False)
    try:
        nick = args[0]
        channel = args[1]
        target = args[2]
        reason = ' '.join(args[3:])
    except IndexError:
        irc.reply(
            "Error: Not enough arguments. Needs 3-4: source nick, channel, target, reason (optional)."
        )
        return
    u = utils.nickToUid(irc, nick) or nick
    targetu = utils.nickToUid(irc, target)
    if not utils.isChannel(channel):
        irc.reply("Error: Invalid channel name %r." % channel)
        return
    if utils.isInternalServer(irc, u):
        irc.proto.kickServer(u, channel, targetu, reason)
    else:
        irc.proto.kickClient(u, channel, targetu, reason)
    irc.callHooks([
        u, 'PYLINK_BOTSPLUGIN_KICK', {
            'channel': channel,
            'target': targetu,
            'text': reason,
            'parse_as': 'KICK'
        }
    ])
Example #5
0
def msg(irc, source, args):
    """<source> <target> <text>

    Admin-only. Sends message <text> from <source>, where <source> is the nick of a PyLink client."""
    utils.checkAuthenticated(irc, source, allowOper=False)
    try:
        msgsource, target, text = args[0], args[1], ' '.join(args[2:])
    except IndexError:
        irc.reply(
            'Error: Not enough arguments. Needs 3: source nick, target, text.')
        return
    sourceuid = utils.nickToUid(irc, msgsource)
    if not sourceuid:
        irc.reply('Error: Unknown user %r.' % msgsource)
        return
    if not utils.isChannel(target):
        real_target = utils.nickToUid(irc, target)
        if real_target is None:
            irc.reply('Error: Unknown user %r.' % target)
            return
    else:
        real_target = target
    if not text:
        irc.reply('Error: No text given.')
        return
    irc.proto.messageClient(sourceuid, real_target, text)
    irc.callHooks([
        sourceuid, 'PYLINK_BOTSPLUGIN_MSG', {
            'target': real_target,
            'text': text,
            'parse_as': 'PRIVMSG'
        }
    ])
Example #6
0
def identify(irc, source, args):
    """<username> <password>

    Logs in to PyLink using the configured administrator account."""
    if utils.isChannel(irc.called_by):
        irc.reply('Error: This command must be sent in private. '
                '(Would you really type a password inside a channel?)')
        return
    try:
        username, password = args[0], args[1]
    except IndexError:
        irc.msg(source, 'Error: Not enough arguments.')
        return
    # Usernames are case-insensitive, passwords are NOT.
    if username.lower() == irc.conf['login']['user'].lower() and password == irc.conf['login']['password']:
        realuser = irc.conf['login']['user']
        irc.users[source].identified = realuser
        irc.msg(source, 'Successfully logged in as %s.' % realuser)
        log.info("(%s) Successful login to %r by %s",
                 irc.name, username, utils.getHostmask(irc, source))
    else:
        irc.msg(source, 'Error: Incorrect credentials.')
        u = irc.users[source]
        log.warning("(%s) Failed login to %r from %s",
                    irc.name, username, utils.getHostmask(irc, source))
Example #7
0
def part(irc, source, args):
    """<target> <channel1>,[<channel2>],... [<reason>]

    Admin-only. Parts <target>, the nick of a PyLink client, from a comma-separated list of channels."""
    utils.checkAuthenticated(irc, source, allowOper=False)
    try:
        nick = args[0]
        clist = args[1].split(',')
        reason = ' '.join(args[2:])
    except IndexError:
        irc.reply(
            "Error: Not enough arguments. Needs 2: nick, comma separated list of channels."
        )
        return
    u = utils.nickToUid(irc, nick)
    if not utils.isManipulatableClient(irc, u):
        irc.reply(
            "Error: Cannot force part a protected PyLink services client.")
        return
    for channel in clist:
        if not utils.isChannel(channel):
            irc.reply("Error: Invalid channel name %r." % channel)
            return
        irc.proto.partClient(u, channel, reason)
    irc.callHooks([
        u, 'PYLINK_BOTSPLUGIN_PART', {
            'channels': clist,
            'text': reason,
            'parse_as': 'PART'
        }
    ])
Example #8
0
 def _sendModes(self, numeric, target, modes, ts=None):
     """Internal function to send mode changes from a PyLink client/server."""
     # -> :9PYAAAAAA FMODE #pylink 1433653951 +os 9PYAAAAAA
     # -> :9PYAAAAAA MODE 9PYAAAAAA -i+w
     log.debug('(%s) inspircd._sendModes: received %r for mode list',
               self.irc.name, modes)
     if ('+o', None) in modes and not utils.isChannel(target):
         # https://github.com/inspself.ircd/inspself.ircd/blob/master/src/modules/m_spanningtree/opertype.cpp#L26-L28
         # Servers need a special command to set umode +o on people.
         self._operUp(target)
     utils.applyModes(self.irc, target, modes)
     joinedmodes = utils.joinModes(modes)
     if utils.isChannel(target):
         ts = ts or self.irc.channels[utils.toLower(self.irc, target)].ts
         self._send(numeric, 'FMODE %s %s %s' % (target, ts, joinedmodes))
     else:
         self._send(numeric, 'MODE %s %s' % (target, joinedmodes))
Example #9
0
 def _getNick(self, target):
     """Converts a nick argument to its matching UID. This differs from utils.nickToUid()
     in that it returns the original text instead of None, if no matching nick is found."""
     target = utils.nickToUid(self.irc, target) or target
     if target not in self.irc.users and not utils.isChannel(target):
         log.debug(
             "(%s) Possible desync? Got command target %s, who "
             "isn't in our user list!", self.irc.name, target)
     return target
Example #10
0
 def knockClient(self, numeric, target, text):
     """Sends a KNOCK from a PyLink client."""
     # KNOCKs in UnrealIRCd are actually just specially formatted NOTICEs,
     # sent to all ops in a channel.
     # <- :unreal.midnight.vpn NOTICE @#test :[Knock] by GL|!gl@hidden-1C620195 (test)
     assert utils.isChannel(target), "Can only knock on channels!"
     sender = utils.clientToServer(self.irc, numeric)
     s = '[Knock] by %s (%s)' % (utils.getHostmask(self.irc, numeric), text)
     self._send(sender, 'NOTICE @%s :%s' % (target, s))
Example #11
0
 def handle_privmsg(self, source, command, args):
     """Handles incoming PRIVMSG/NOTICE."""
     # <- :70MAAAAAA PRIVMSG #dev :afasfsa
     # <- :70MAAAAAA NOTICE 0ALAAAAAA :afasfsa
     target = args[0]
     # We use lowercase channels internally, but uppercase UIDs.
     if utils.isChannel(target):
         target = utils.toLower(self.irc, target)
     return {'target': target, 'text': args[1]}
Example #12
0
def handle_fantasy(irc, source, command, args):
    """Fantasy command handler."""
    try:  # First, try to fetch the config-defined prefix.
        prefixes = [irc.botdata["prefix"]]
    except KeyError:  # Config option is missing.
        prefixes = []

    if irc.botdata.get("respondtonick"):
        # If responding to nick is enabled, add variations of the current nick
        # to the prefix list: "<nick>," and "<nick>:"
        nick = irc.pseudoclient.nick
        prefixes += [nick + ',', nick + ':']

    if not prefixes:
        # We finished with an empty prefixes list, meaning fantasy is misconfigured!
        log.warning(
            "(%s) Fantasy prefix was not set in configuration - "
            "fantasy commands will not work!", irc.name)
        return

    channel = args['target']
    text = args['text']
    for prefix in prefixes:  # Cycle through the prefixes list we finished with.
        # The following conditions must be met for an incoming message for
        # fantasy to trigger:
        #   1) The message target is a channel.
        #   2) The message starts with one of our fantasy prefixes.
        #   3) The main PyLink client is in the channel where the command was
        #      called.
        #   4) The sender is NOT a PyLink client (this prevents infinite
        #      message loops).
        if utils.isChannel(channel) and text.startswith(prefix) and \
                irc.pseudoclient.uid in irc.channels[channel].users and not \
                utils.isInternalClient(irc, source):

            # Cut off the length of the prefix from the text.
            text = text[len(prefix):]

            # Set the "place last command was called in" variable to the
            # channel in question, so that replies from fantasy-supporting
            # plugins get forwarded to it.
            irc.called_by = channel

            # Finally, call the bot command and break.
            irc.callCommand(source, text)
            break
Example #13
0
 def _sendModes(self, numeric, target, modes, ts=None):
     """Internal function to send mode changes from a PyLink client/server."""
     # <- :unreal.midnight.vpn MODE #endlessvoid +ntCo GL 1444361345
     utils.applyModes(self.irc, target, modes)
     joinedmodes = utils.joinModes(modes)
     if utils.isChannel(target):
         # The MODE command is used for channel mode changes only
         ts = ts or self.irc.channels[utils.toLower(self.irc, target)].ts
         self._send(numeric, 'MODE %s %s %s' % (target, joinedmodes, ts))
     else:
         # For user modes, the only way to set modes (for non-U:Lined servers)
         # is through UMODE2, which sets the modes on the caller.
         # U:Lines can use SVSMODE/SVS2MODE, but I won't expect people to
         # U:Line a PyLink daemon...
         if not utils.isInternalClient(self.irc, target):
             raise ProtocolError(
                 'Cannot force mode change on external clients!')
         self._send(target, 'UMODE2 %s' % joinedmodes)
Example #14
0
    def _sendModes(self, numeric, target, modes, ts=None):
        """Internal function to send mode changes from a PyLink client/server."""
        utils.applyModes(self.irc, target, modes)
        modes = list(modes)
        if utils.isChannel(target):
            ts = ts or self.irc.channels[utils.toLower(self.irc, target)].ts
            # TMODE:
            # parameters: channelTS, channel, cmode changes, opt. cmode parameters...

            # On output, at most ten cmode parameters should be sent; if there are more,
            # multiple TMODE messages should be sent.
            while modes[:9]:
                joinedmodes = utils.joinModes(modes=[
                    m for m in modes[:9] if m[0] not in self.irc.cmodes['*A']
                ])
                modes = modes[9:]
                self._send(numeric,
                           'TMODE %s %s %s' % (ts, target, joinedmodes))
        else:
            joinedmodes = utils.joinModes(modes)
            self._send(numeric, 'MODE %s %s' % (target, joinedmodes))
Example #15
0
 def testIsChannel(self):
     self.assertFalse(utils.isChannel(''))
     self.assertFalse(utils.isChannel('lol'))
     self.assertTrue(utils.isChannel('#channel'))
     self.assertTrue(utils.isChannel('##ABCD'))