Beispiel #1
0
def join_chan(cli, rawnick, chan, account=None, realname=None):
    """Handle a user joining a channel, which may be the bot itself.

    Ordering and meaning of arguments for a channel JOIN:

    0 - The IRCClient instance (like everywhere else)
    1 - The raw nick (nick!ident@host) of the user joining the channel
    2 - The channel the user joined

    The following two arguments are optional and only present if the
    server supports the extended-join capability (we will have requested
    it when we connected if it was supported):

    3 - The account the user is identified to, or "*" if none
    4 - The realname (gecos) of the user, or "" if none

    """

    if account == "*":
        account = None

    if realname == "":
        realname = None

    ch = channels.add(chan, cli)

    user = users.get(nick=rawnick,
                     account=account,
                     allow_bot=True,
                     allow_none=True,
                     allow_ghosts=True)
    if user is None:
        user = users.add(cli, nick=rawnick, account=account)
    if account:
        # ensure we work for the case when user left, changed accounts, then rejoined as a different account
        user.account = account
        user = users.get(nick=rawnick, account=account)
    ch.users.add(user)
    user.channels[ch] = set()
    # mark the user as here, in case they used to be connected before but left
    user.disconnected = False

    Event("chan_join", {}).dispatch(ch, user)

    if user is users.Bot:
        ch.mode()
        ch.mode(Features["CHANMODES"][0])
        ch.who()
Beispiel #2
0
def on_whois_user(cli, bot_server, bot_nick, nick, ident, host, sep, realname):
    """Set up the user for a WHOIS reply.

    Ordering and meaning of arguments for a WHOIS user reply:

    0 - The IRCClient instance (like everywhere else)
    1 - The server the requester (i.e. the bot) is on
    2 - The nickname of the requester (i.e. the bot)
    3 - The nickname of the target
    4 - The ident of the target
    5 - The host of the target
    6 - The literal character '*'
    7 - The realname of the target

    This does not fire an event by itself, but sets up the proper data
    for the "endofwhois" listener to fire an event.

    """

    user = users.get(nick, ident, host, allow_bot=True, allow_none=True)
    if user is None:
        user = users.add(cli, nick=nick, ident=ident, host=host)
    _whois_pending[nick] = {
        "user": user,
        "account": None,
        "away": False,
        "channels": set()
    }
Beispiel #3
0
def mode_change(cli, rawnick, chan, mode, *targets):
    """Update the channel and user modes whenever a mode change occurs.

    Ordering and meaning of arguments for a MODE change:

    0 - The IRCClient instance (like everywhere else)
    1 - The raw nick of the mode setter/actor
    2 - The channel (target) of the mode change
    3 - The mode changes
    * - The targets of the modes (if any)

    This takes care of properly updating all relevant users and the
    channel modes to make sure we remain internally consistent.

    """

    if chan == users.Bot.nick:  # we only see user modes set to ourselves
        users.Bot.modes.update(mode)
        return

    if "!" not in rawnick:
        # Only sync modes if a server changed modes because
        # 1) human ops probably know better
        # 2) other bots might start a fight over modes
        # 3) recursion; we see our own mode changes.
        evt = Event("sync_modes", {})
        evt.dispatch()
        return

    actor = users.get(rawnick, allow_none=True)
    target = channels.add(chan, cli)
    target.queue("mode_change", {
        "mode": mode,
        "targets": targets
    }, (actor, target))
Beispiel #4
0
def end_who(cli, bot_server, bot_nick, target, rest):
    """Handle the end of WHO/WHOX responses from the server.

    Ordering and meaning of arguments for the end of a WHO/WHOX request:

    0 - The IRCClient instance (like everywhere else)
    1 - The server the requester (i.e. the bot) is on
    2 - The nickname of the requester (i.e. the bot)
    3 - The target the request was made against
    4 - A string containing some information; traditionally "End of /WHO list."

    This fires off the "who_end" event, and dispatches it with one
    argument: The channel or user the request was made to, or None
    if it could not be resolved.

    """

    try:
        target = channels.get(target)
    except KeyError:
        try:
            target = users.get(target)
        except KeyError:
            target = None
    else:
        target.dispatch_queue()

    old = _who_old.get(target.name, target)
    _who_old.clear()
    Event("who_end", {}, old=old).dispatch(target)
Beispiel #5
0
def on_chghost(cli, rawnick, ident, host):
    """Handle a user changing host without a quit.

    Ordering and meaning of arguments for CHGHOST:

    0 - The IRCClient instance (like everywhere else)
    1 - The raw nick (nick!ident@host) of the user switching
    2 - The new ident for the user (or same if unchanged)
    3 - The new host for the user (or same if unchanged)

    """

    user = users.get(rawnick, allow_bot=True)
    old_ident = user.ident
    old_host = user.host
    # we avoid multiple swaps if we change the rawnick instead of ident and host separately
    new_rawnick = "{0}!{1}@{2}".format(user.nick, ident, host)
    user.rawnick = new_rawnick
    new_user = users.get(new_rawnick, allow_bot=True)

    Event("host_change", {}, old=user).dispatch(new_user, old_ident, old_host)
Beispiel #6
0
def on_nick_change(cli, old_rawnick, nick):
    """Handle a user changing nicks, which may be the bot itself.

    Ordering and meaning of arguments for a NICK change:

    0 - The IRCClient instance (like everywhere else)
    1 - The old (raw) nickname the user changed from
    2 - The new nickname the user changed to

    """

    user = users.get(old_rawnick, allow_bot=True)
    old_nick = user.nick
    user.nick = nick
    new_user = users.get(nick,
                         user.ident,
                         user.host,
                         user.account,
                         allow_bot=True)

    Event("nick_change", {}, old=user).dispatch(new_user, old_nick)
Beispiel #7
0
def kicked_from_chan(cli, rawnick, chan, target, reason):
    """Handle a user being kicked from a channel.

    Ordering and meaning of arguments for a channel KICK:

    0 - The IRCClient instance (like everywhere else)
    1 - The raw nick (nick!ident@host) of the user performing the kick
    2 - The channel the kick was performed on
    3 - The target of the kick
    4 - The reason given for the kick (always present)

    """

    ch = channels.add(chan, cli)
    actor = users.get(rawnick, allow_none=True)
    user = users.get(target, allow_bot=True)
    Event("chan_kick", {}).dispatch(ch, actor, user, reason)

    if user is users.Bot:
        ch.clear()
    else:
        ch.remove_user(user)
Beispiel #8
0
def on_account_change(cli, rawnick, account):
    """Handle a user changing accounts, if enabled.

    Ordering and meaning of arguments for an ACCOUNT change:

    0 - The IRCClient instance (like everywhere else)
    1 - The raw nick (nick!ident@host) of the user changing accounts
    2 - The account the user changed to

    We don't see our own account changes, so be careful!

    """

    user = users.get(rawnick)
    old_account = user.account
    user.account = account
    new_user = users.get(user.nick,
                         user.ident,
                         user.host,
                         account,
                         allow_bot=True)

    Event("account_change", {}, old=user).dispatch(new_user, old_account)
Beispiel #9
0
def who_reply(cli, bot_server, bot_nick, chan, ident, host, server, nick,
              status, hopcount_gecos):
    """Handle WHO replies for servers without WHOX support.

    Ordering and meaning of arguments for a bare WHO response:

    0 - The IRCClient instance (like everywhere else)
    1 - The server the requester (i.e. the bot) is on
    2 - The nickname of the requester (i.e. the bot)
    3 - The channel the request was made on
    4 - The ident of the user in this reply
    5 - The hostname of the user in this reply
    6 - The server the user in this reply is on
    7 - The nickname of the user in this reply
    8 - The status (H = Not away, G = Away, * = IRC operator, @ = Opped in the channel in 4, + = Voiced in the channel in 4)
    9 - The hop count and realname (gecos)

    This fires off the "who_result" event, and dispatches it with two
    arguments, a Channel and a User. Less important attributes can be
    accessed via the event.params namespace.

    """

    hop, realname = hopcount_gecos.split(" ", 1)
    # We throw away the information about the operness of the user, but we probably don't need to care about that
    # We also don't directly pass which modes they have, since that's already on the channel/user
    is_away = ("G" in status)

    modes = {Features["PREFIX"].get(s) for s in status} - {None}

    user = users.get(nick, ident, host, allow_bot=True, allow_none=True)
    if user is None:
        user = users.add(cli, nick=nick, ident=ident, host=host)

    ch = channels.get(chan, allow_none=True)
    if ch is not None and ch not in user.channels:
        user.channels[ch] = modes
        ch.users.add(user)
        for mode in modes:
            if mode not in ch.modes:
                ch.modes[mode] = set()
            ch.modes[mode].add(user)

    _who_old[user.nick] = user
    event = Event("who_result", {}, away=is_away, data=0, old=user)
    event.dispatch(ch, user)
Beispiel #10
0
def on_privmsg(cli, rawnick, chan, msg, *, notice=False):
    if notice and "!" not in rawnick or not rawnick:  # server notice; we don't care about those
        return

    _ignore_locals_ = False
    if var.USER_DATA_LEVEL == 0 or var.CHANNEL_DATA_LEVEL == 0:
        _ignore_locals_ = True  # don't expose in tb if we're trying to anonymize stuff

    user = users.get(rawnick, allow_none=True)

    ch = chan.lstrip("".join(Features["PREFIX"]))

    if users.equals(chan, users.Bot.nick):  # PM
        target = users.Bot
    else:
        target = channels.get(ch, allow_none=True)

    if user is None or target is None:
        return

    wrapper = MessageDispatcher(user, target)

    if wrapper.public and botconfig.IGNORE_HIDDEN_COMMANDS and not chan.startswith(
            tuple(Features["CHANTYPES"])):
        return

    if (notice and
        ((wrapper.public and not botconfig.ALLOW_NOTICE_COMMANDS) or
         (wrapper.private and not botconfig.ALLOW_PRIVATE_NOTICE_COMMANDS))):
        return  # not allowed in settings

    for fn in decorators.COMMANDS[""]:
        fn.caller(var, wrapper, msg)

    parts = msg.split(sep=" ", maxsplit=1)
    key = parts[0].lower()
    if len(parts) > 1:
        message = parts[1].strip()
    else:
        message = ""

    if wrapper.public and not key.startswith(botconfig.CMD_CHAR):
        return  # channel message but no prefix; ignore
    parse_and_dispatch(var, wrapper, key, message)
Beispiel #11
0
def on_quit(cli, rawnick, reason):
    """Handle a user quitting the IRC server.

    Ordering and meaning of arguments for a server QUIT:

    0 - The IRCClient instance (like everywhere else)
    1 - The raw nick (nick!ident@host) of the user quitting
    2 - The reason for the quit (always present)

    """

    user = users.get(rawnick, allow_bot=True)
    Event("server_quit", {}).dispatch(user, reason)

    for chan in set(user.channels):
        if user is users.Bot:
            chan._clear()
        else:
            chan.remove_user(user)
Beispiel #12
0
def part_chan(cli, rawnick, chan, reason=""):
    """Handle a user leaving a channel, which may be the bot itself.

    Ordering and meaning of arguments for a channel PART:

    0 - The IRCClient instance (like everywhere else)
    1 - The raw nick (nick!ident@host) of the user leaving the channel
    2 - The channel being left

    The following argument may or may not be present:

    3 - The reason the user gave for parting (if any)

    """

    ch = channels.add(chan, cli)
    user = users.get(rawnick, allow_bot=True)
    Event("chan_part", {}).dispatch(ch, user, reason)

    if user is users.Bot:  # oh snap! we're no longer in the channel!
        ch.clear()
    else:
        ch.remove_user(user)
Beispiel #13
0
def on_whois_end(cli, bot_server, bot_nick, nick, message):
    """Handle the end of WHOIS and fire events.

    Ordering and meaning of arguments for an end of WHOIS reply:

    0 - The IRCClient instance (like everywhere else)
    1 - The server the requester (i.e. the bot) is on
    2 - The nickname of the requester (i.e. the bot)
    3 - The nickname of the target
    4 - A human-friendly message, usually "End of /WHOIS list."

    This uses data accumulated from the above WHOIS listeners, and
    fires the "who_result" event (once per shared channel with the bot)
    and the "who_end" event with the relevant User instance as the arg.

    """

    values = _whois_pending.pop(nick)
    # check for account change
    new_user = user = values["user"]
    if {user.account, values["account"]} != {None} and not context.equals(
            user.account, values["account"]):
        # first check tests if both are None, and skips over this if so
        old_account = user.account
        user.account = values["account"]
        new_user = users.get(user.nick,
                             user.ident,
                             user.host,
                             values["account"],
                             allow_bot=True)
        Event("account_change", {}, old=user).dispatch(new_user, old_account)

    event = Event("who_result", {}, away=values["away"], data=0, old=user)
    for chan in values["channels"]:
        event.dispatch(chan, new_user)
    Event("who_end", {}, old=user).dispatch(new_user)
Beispiel #14
0
def extended_who_reply(cli, bot_server, bot_nick, data, chan, ident,
                       ip_address, host, server, nick, status, hop, idle,
                       account, realname):
    """Handle WHOX responses for servers that support it.

    An extended WHO (WHOX) is characterised by a second parameter to the request
    That parameter must be '%' followed by at least one of 'tcuihsnfdlar'
    If the 't' specifier is present, the specifiers must be followed by a comma and at most 3 bytes
    This is the ordering if all parameters are present, but not all of them are required
    If a parameter depends on a specifier, it will be stated at the front
    If a specifier is not given, the parameter will be omitted in the reply

    Ordering and meaning of arguments for an extended WHO (WHOX) response:

    0  -   - The IRCClient instance (like everywhere else)
    1  -   - The server the requester (i.e. the bot) is on
    2  -   - The nickname of the requester (i.e. the bot)
    3  - t - The data sent alongside the request
    4  - c - The channel the request was made on
    5  - u - The ident of the user in this reply
    6  - i - The IP address of the user in this reply
    7  - h - The hostname of the user in this reply
    8  - s - The server the user in this reply is on
    9  - n - The nickname of the user in this reply
    10 - f - Status (H = Not away, G = Away, * = IRC operator, @ = Opped in the channel in 5, + = Voiced in the channel in 5)
    11 - d - The hop count
    12 - l - The idle time (or 0 for users on other servers)
    13 - a - The services account name (or 0 if none/not logged in)
    14 - r - The realname (gecos)

    This fires off the "who_result" event, and dispatches it with two
    arguments, a Channel and a User. Less important attributes can be
    accessed via the event.params namespace.

    """

    if account == "0":
        account = None

    is_away = ("G" in status)

    data = int.from_bytes(data.encode(Features["CHARSET"]), "little")

    modes = {Features["PREFIX"].get(s) for s in status} - {None}

    # WHOX may be issued to retrieve updated account info so exclude account from users.get()
    # we handle the account change differently below and don't want to add duplicate users
    user = users.get(nick, ident, host, allow_bot=True, allow_none=True)
    if user is None:
        user = users.add(cli,
                         nick=nick,
                         ident=ident,
                         host=host,
                         account=account)

    new_user = user
    if {user.account, account} != {None} and not context.equals(
            user.account, account):
        # first check tests if both are None, and skips over this if so
        old_account = user.account
        user.account = account
        new_user = users.get(nick, ident, host, account, allow_bot=True)
        Event("account_change", {}, old=user).dispatch(new_user, old_account)

    ch = channels.get(chan, allow_none=True)
    if ch is not None and ch not in user.channels:
        user.channels[ch] = modes
        ch.users.add(user)
        for mode in modes:
            if mode not in ch.modes:
                ch.modes[mode] = set()
            ch.modes[mode].add(user)

    _who_old[new_user.nick] = user
    event = Event("who_result", {}, away=is_away, data=data, old=user)
    event.dispatch(ch, new_user)
Beispiel #15
0
    def update_modes(self, actor, mode, targets):
        """Update the channel's mode registry with the new modes.

        This is called whenever a MODE event is received. All of the
        modes are kept up-to-date in the channel, even if we don't need
        it. For instance, banlists are updated properly when the bot
        receives them. We don't need all the mode information, but it's
        better to have everything stored than only some parts.

        """

        set_time = int(time.time())  # for list modes timestamp
        list_modes, all_set, only_set, no_set = Features["CHANMODES"]
        status_modes = Features["PREFIX"].values()
        all_modes = list_modes + all_set + only_set + no_set + "".join(
            status_modes)
        if self.state is not _States.Joined:  # not joined, modes won't have the value
            no_set += all_set + only_set
            only_set = ""
            all_set = ""

        i = 0
        prefix = None
        for c in mode:
            if c in ("+", "-"):
                prefix = c
                continue
            elif c not in all_modes:
                # some broken ircds have modes without telling us about them in ISUPPORT
                # ignore such modes but emit a warning
                stream("Broken ircd detected: unrecognized channel mode +{}".
                       format(c),
                       level="warning")
                continue

            if prefix == "+":
                if c in status_modes:  # op/voice status; keep it here and update the user's registry too
                    if c not in self.modes:
                        self.modes[c] = set()
                    user = users.get(targets[i], allow_bot=True)
                    self.modes[c].add(user)
                    user.channels[self].add(c)
                    if user in var.OLD_MODES:
                        var.OLD_MODES[user].discard(c)
                    i += 1

                elif c in list_modes:  # stuff like bans, quiets, and ban and invite exempts
                    if c not in self.modes:
                        self.modes[c] = {}
                    self.modes[c][targets[i]] = ((actor.rawnick if actor
                                                  is not None else None),
                                                 set_time)
                    i += 1

                else:
                    if c in no_set:  # everything else; e.g. +m, +i, etc.
                        targ = None
                    else:  # +f, +l, +j, +k
                        targ = targets[i]
                        i += 1
                    if c in only_set and targ.isdigit():  # +l/+j
                        targ = int(targ)
                    self.modes[c] = targ

            elif prefix == "-":
                if c in status_modes:
                    if c in self.modes:
                        user = users.get(targets[i], allow_bot=True)
                        self.modes[c].discard(user)
                        user.channels[self].discard(c)
                        if not self.modes[c]:
                            del self.modes[c]
                    i += 1

                elif c in list_modes:
                    if c in self.modes:
                        self.modes[c].pop(targets[i], None)
                        if not self.modes[c]:
                            del self.modes[c]
                    i += 1

                else:
                    if c in all_set:
                        i += 1  # -k needs a target, but we don't care about it
                    del self.modes[c]

        if "k" in mode:
            self._key = self.modes.get("k", "")
Beispiel #16
0
def check_settings():
    if settings.GA_ORDER_NAME is None:
        utils.logError('settings.GA_ORDER_NAME cannot be None.')
        return False

    if settings.GA_ADVERTISER_NAME is None:
        utils.logError('settings.GA_ADVERTISER_NAME cannot be None.')
        return False

    if settings.GA_USER_EMAIL is None:
        utils.logError('settings.GA_USER_EMAIL cannot be None.')
        return False

    if settings.GA_CREATIVE_ID is None:
        utils.logError('settings.GA_CREATIVE_ID cannot be None.')
        return False

    if not settings.GA_USE_EXISTING_ORDER:
        order = orders.get(settings.GA_ORDER_NAME)
        if order is not None:
            utils.logError(
                'Order with name "%s" already exists. You must use a different name.'
                % order['name'])
            return False

    advertiser = advertisers.get(settings.GA_ADVERTISER_NAME)
    if (advertiser is None):
        utils.logError('Advertiser with id "%s" does not exists.' %
                       settings.GA_ADVERTISER_NAME)

    user = users.get(settings.GA_USER_EMAIL)
    if (user is None):
        utils.logError('Advertiser with email "%s" does not exists.' %
                       settings.GA_USER_EMAIL)

    creative = creatives.get(settings.GA_CREATIVE_ID)
    if (creative is None):
        utils.logError(
            'Creative with id "%s" does not exists. You must specify an existing creative id.'
            % settings.GA_CREATIVE_ID)

    # check data structure of PREBID_PRICE_BUCKETS
    if settings.PREBID_PRICE_BUCKETS == 'auto':
        settings.PREBID_PRICE_BUCKETS = [{
            'min': 0,
            'max': 5,
            'increment': 0.05,
            'precision': 2,
        }, {
            'min': 5,
            'max': 10,
            'increment': 0.1,
            'precision': 2,
        }, {
            'min': 10,
            'max': 20,
            'increment': 0.5,
            'precision': 2,
        }]
    else:
        for bucket in settings.PREBID_PRICE_BUCKETS:
            if 'min' not in bucket or 'max' not in bucket or 'increment' not in bucket:
                utils.logError(
                    'settings.PREBID_PRICE_BUCKETS invalid format. Requires "min", "max", and "increment"'
                )
                return False
            if 'precision' not in bucket:
                bucket['precision'] = 2
            for name in ['min', 'max', 'increment', 'precision']:
                if not isinstance(bucket[name], numbers.Number):
                    utils.logError(
                        '"%s" must be a number in settings.PREBID_PRICE_BUCKETS'
                        % name)
                    return False

    return True