示例#1
0
    def update_account_data(self, command: str, callback: Callable):
        """Refresh stale account data on networks that don't support certain features.

        :param command: Command name that prompted the call to update_account_data.
            Used to handle cases where the user executes multiple commands before
            account data can be updated, so they can all be queued. If the same command
            is given multiple times, we honor the most recent one given. The command
            has a unique suffix so that distinct commands that share names (e.g. wolf kill
            and vg kill) do not cause collisions.
        :param callback: Callback to execute when account data is fully updated,
            passed in the updated user with an accurate account
        """

        # Nothing to update for fake nicks
        if self.is_fake:
            callback(self)
            return

        if self.account is not None and Features.get("account-notify", False):
            # account-notify is enabled, so we're already up to date on our account name
            callback(self)
            return

        if self.account is not None and self.account_timestamp > time.time(
        ) - 900:
            # account data is less than 15 minutes old, use existing data instead of refreshing
            callback(self)
            return

        if self not in _pending_account_updates:
            _pending_account_updates[self] = CheckedDict(
                "users.User.update_account_data")

        _pending_account_updates[self][command] = callback

        if len(_pending_account_updates[self].keys()) > 1:
            # already have a pending WHO/WHOIS for this user, don't send multiple to the server to avoid hitting
            # rate limits (if we are ratelimited, that can be handled by re-sending the request at a lower layer)
            return

        if Features.get("WHOX", False):
            # A WHOX query performs less network noise than WHOIS, so use that if available
            self.who()
        else:
            # Fallback to WHOIS
            self.client.send("WHOIS {0}".format(self))
示例#2
0
from src.match import Match

import botconfig

__all__ = [
    "Bot", "predicate", "get", "add", "users", "disconnected",
    "complete_match", "parse_rawnick", "parse_rawnick_as_dict", "User",
    "FakeUser", "BotUser"
]

Bot = None  # bot instance

_users = CheckedSet("users._users")  # type: CheckedSet[User]
_ghosts = CheckedSet("users._ghosts")  # type: CheckedSet[User]
_pending_account_updates = CheckedDict(
    "users._pending_account_updates"
)  # type: CheckedDict[User, CheckedDict[str, Callable]]

_arg_msg = "(user={0:for_tb}, allow_bot={1})"

# This is used to tell if this is a fake nick or not. If this function
# returns a true value, then it's a fake nick. This is useful for
# testing, where we might want everyone to be fake nicks.
predicate = re.compile(r"^[0-9]+$").search


def get(nick=None,
        ident=None,
        host=None,
        account=None,
        *,
示例#3
0
    def __new__(cls, cli, nick, ident, host, account):
        self = super().__new__(cls)
        super(__class__, self).__init__(nick, cli)

        self._ident = ident
        self._host = host
        self._account = account
        self.channels = CheckedDict("users.User.channels")
        self.timestamp = time.time()
        self.sets = []
        self.lists = []
        self.dict_keys = []
        self.dict_values = []
        self.account_timestamp = time.time()

        if Bot is not None and nick is not None and Bot.nick.rstrip(
                "_") == nick.rstrip("_") and None in {Bot.ident, Bot.host}:
            # Bot ident/host being None means that this user isn't hashable, so it cannot be in any containers
            # which store by hash. As such, mutating the properties is safe.
            self = Bot
            self.name = nick
            if ident is not None:
                self._ident = ident
            if host is not None:
                self._host = host
            self._account = account
            self.timestamp = time.time()
            self.account_timestamp = time.time()

        elif (cls.__name__ == "User" and Bot is not None and Bot.nick == nick
              and ident is not None and host is not None and Bot.ident != ident
              and Bot.host == host and Bot.account == account):
            # Messages sent in early init may give us an incorrect ident (such as because we're waiting for
            # a response from identd, or there is some *line which overrides the ident for the bot).
            # Since Bot.ident attempts to create a new BotUser, we guard against recursion by only following this
            # branch if we're constructing a top-level User object (such as via users.get)
            Bot.ident = ident
            self = Bot

        elif nick is not None and ident is not None and host is not None:
            users = set(_users)
            users.add(Bot)
            if self in users:
                for user in users:
                    if self == user:
                        self = user
                        break

        else:
            # This takes a different code path because of slightly different
            # conditions; in the above case, the ident and host are both known,
            # and so the instance is hashable. Being hashable, it can be checked
            # for set containment, and exactly one instance in that set will be
            # equal (since the hash is based off of the ident and host, and the
            # comparisons check for all those two attributes among others, two
            # instances cannot possibly be equal while having a different hash).
            #
            # In this case, however, at least the ident or the host is missing,
            # and so the hash cannot be calculated. This means that two instances
            # may compare equal and hash to different values (since only non-None
            # attributes are compared), so we need to run through the entire set
            # no matter what to make sure that one - and only one - instance in
            # the set compares equal with the new one. We can't know in advance
            # whether or not there is an instance that compares equal to this one
            # in the set, or if multiple instances are going to compare equal to
            # this one.
            #
            # The code paths, while similar in functionality, fulfill two distinct
            # purposes; the first path is usually for when new users are created
            # from a WHO reply, with all the information. This is the most common
            # case. This path, on the other hand, is for the less common cases,
            # where only the nick is known (for example, a KICK target), and where
            # the user may or may not already exist. In that case, it's easier and
            # better to just try to create a new user, which this code can then
            # implicitly replace with the equivalent user (instead of trying to get
            # an existing user or creating a new one if that fails). This is also
            # used as a short-circuit for get().
            #
            # Please don't merge these two code paths for the sake of simplicity,
            # and instead opt for the sake of clarity that this separation provides.

            potential = None
            users = set(_users)
            users.add(Bot)
            for user in users:
                if self.partial_match(user):
                    if potential is None:
                        potential = user
                    else:
                        break  # too many possibilities
            else:
                if potential is not None:
                    self = potential

        return self
示例#4
0
import time

from enum import Enum

from src.context import IRCContext, Features, lower
from src.events import Event
from src import settings as var
from src import users, stream
from src.debug import CheckedSet, CheckedDict

Main = None  # main channel
Dummy = None  # fake channel
Dev = None  # dev channel

_channels = CheckedDict(
    "channels._channels")  # type: CheckedDict[str, Channel]


class _States(Enum):
    NotJoined = "not yet joined"
    PendingJoin = "pending join"
    Joined = "joined"
    PendingLeave = "pending leave"
    Left = "left channel"

    Cleared = "cleared"


# This is used to tell if this is a fake channel or not. If this
# function returns a true value, then it's a fake channel. This is
# useful for testing, where we might want the users in fake channels.