Ejemplo n.º 1
0
def get_role(p):
    # FIXME: make the arg a user instead of a nick
    from src import users
    from src.functions import get_participants
    user = users._get(p)
    role = var.MAIN_ROLES.get(user, None)
    if role is not None:
        return role
    # not found in player list, see if they're a special participant
    if user in get_participants():
        evt = Event("get_participant_role", {"role": None})
        evt.dispatch(var, user)
        role = evt.data["role"]
    if role is None:
        raise ValueError(
            "User {0} isn't playing and has no defined participant role".
            format(user))
    return role
Ejemplo n.º 2
0
def on_privmsg(cli, rawnick, chan, msg, *, notice=False, force_role=None):
    if notice and "!" not in rawnick or not rawnick: # server notice; we don't care about those
        return

    user = users._get(rawnick, allow_none=True) # FIXME

    if users.equals(chan, users.Bot.nick): # PM
        target = users.Bot
    else:
        target = channels.get(chan, 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(hooks.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

    if force_role is None: # if force_role isn't None, that indicates recursion; don't fire these off twice
        for fn in decorators.COMMANDS[""]:
            fn.caller(cli, rawnick, chan, msg)

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

    if wrapper.public and not key.startswith(botconfig.CMD_CHAR):
        return # channel message but no prefix; ignore

    if key.startswith(botconfig.CMD_CHAR):
        key = key[len(botconfig.CMD_CHAR):]

    if not key: # empty key ("") already handled above
        return

    # Don't change this into decorators.COMMANDS[key] even though it's a defaultdict,
    # as we don't want to insert bogus command keys into the dict.
    cmds = []
    phase = var.PHASE
    if user in get_participants():
        roles = get_all_roles(user)
        # A user can be a participant but not have a role, for example, dead vengeful ghost
        has_roles = len(roles) != 0
        if force_role is not None:
            roles &= {force_role} # only fire off role commands for the forced role

        common_roles = set(roles) # roles shared by every eligible role command
        have_role_cmd = False
        for fn in decorators.COMMANDS.get(key, []):
            if not fn.roles:
                cmds.append(fn)
                continue
            if roles.intersection(fn.roles):
                have_role_cmd = True
                cmds.append(fn)
                common_roles.intersection_update(fn.roles)

        if force_role is not None and not have_role_cmd:
            # Trying to force a non-role command with a role.
            # We allow non-role commands to execute if a role is forced if a role
            # command is also executed, as this would allow (for example) a bot admin
            # to add extra effects to all "kill" commands without needing to continually
            # update the list of roles which can use "kill". However, we don't want to
            # allow things like "wolf pstats" because that just doesn't make sense.
            return

        if has_roles and not common_roles:
            # getting here means that at least one of the role_cmds is disjoint
            # from the others. For example, augur see vs seer see when a bare see
            # is executed. In this event, display a helpful error message instructing
            # the user to resolve the ambiguity.
            common_roles = set(roles)
            info = [0,0]
            for fn in cmds:
                fn_roles = roles.intersection(fn.roles)
                if not fn_roles:
                    continue
                for role1 in common_roles:
                    info[0] = role1
                    break
                for role2 in fn_roles:
                    info[1] = role2
                    break
                common_roles &= fn_roles
                if not common_roles:
                    break
            wrapper.pm(messages["ambiguous_command"].format(key, info[0], info[1]))
            return
    elif force_role is None:
        cmds = decorators.COMMANDS.get(key, [])

    for fn in cmds:
        if phase == var.PHASE:
            # FIXME: pass in var, wrapper, message instead of cli, rawnick, chan, message
            fn.caller(cli, rawnick, chan, message)
Ejemplo n.º 3
0
def parse_and_dispatch(var,
                       wrapper: MessageDispatcher,
                       key: str,
                       message: str,
                       role: Optional[str] = None,
                       force: Optional[User] = None) -> None:
    """ Parses a command key and dispatches it should it match a valid command.

    :param var: Game state
    :param wrapper: Information about who is executing command and where command is being executed
    :param key: Command name. May be prefixed with command character and role name.
    :param message: Parameters to the command.
    :param role: Only dispatch a role command for the specified role. Even if a role name is specified in key,
        this will take precedence if set.
    :param force: Force the command to execute as the specified user instead of the current user.
        Admin and owner commands cannot be forced this way. When forcing a command, we set the appropriate context
        (channel vs PM) automatically as well.
    :return:
    """
    _ignore_locals_ = True
    if key.startswith(botconfig.CMD_CHAR):
        key = key[len(botconfig.CMD_CHAR):]

    # check for role prefix
    parts = key.split(sep=":", maxsplit=1)
    if len(parts) > 1 and len(parts[0]):
        key = parts[1]
        role_prefix = parts[0]
    else:
        key = parts[0]
        role_prefix = None

    if role:
        role_prefix = role

    if not key:
        return

    if force:
        context = MessageDispatcher(force, wrapper.target)
    else:
        context = wrapper

    if role_prefix is not None:
        # match a role prefix to a role. Multi-word roles are supported by stripping the spaces
        matches = complete_role(var, role_prefix, remove_spaces=True)
        if len(matches) == 1:
            role_prefix = matches[0]
        elif len(matches) > 1:
            wrapper.pm(messages["ambiguous_role"].format(matches))
            return
        else:
            wrapper.pm(messages["no_such_role"].format(role_prefix))
            return

    # Don't change this into decorators.COMMANDS[key] even though it's a defaultdict,
    # as we don't want to insert bogus command keys into the dict.
    cmds = []
    phase = var.PHASE
    if context.source in get_participants():
        roles = get_all_roles(context.source)
        common_roles = set(
            roles)  # roles shared by every eligible role command
        # A user can be a participant but not have a role, for example, dead vengeful ghost
        has_roles = len(roles) != 0
        if role_prefix is not None:
            roles &= {
                role_prefix
            }  # only fire off role commands for the user-specified role
    else:
        roles = set()
        common_roles = set()
        has_roles = False

    for fn in decorators.COMMANDS.get(key, []):
        if not fn.roles:
            cmds.append(fn)
        elif roles.intersection(fn.roles):
            cmds.append(fn)
            common_roles.intersection_update(fn.roles)

    if has_roles and not common_roles:
        # getting here means that at least one of the role_cmds is disjoint
        # from the others. For example, augur see vs seer see when a bare see
        # is executed. In this event, display a helpful error message instructing
        # the user to resolve the ambiguity.
        common_roles = set(roles)
        info = [0, 0]
        role_map = messages.get_role_mapping()
        for fn in cmds:
            fn_roles = roles.intersection(fn.roles)
            if not fn_roles:
                continue
            for role1 in common_roles:
                info[0] = role_map[role1].replace(" ", "")
                break
            for role2 in fn_roles:
                info[1] = role_map[role2].replace(" ", "")
                break
            common_roles &= fn_roles
            if not common_roles:
                break
        wrapper.pm(messages["ambiguous_command"].format(key, info[0], info[1]))
        return

    for fn in cmds:
        if force:
            if fn.owner_only or fn.flag:
                wrapper.pm(messages["no_force_admin"])
                return
            if fn.chan:
                context.target = channels.Main
            else:
                context.target = users.Bot
        if phase == var.PHASE:  # don't call any more commands if one we just called executed a phase transition
            fn.caller(var, context, message)
Ejemplo n.º 4
0
def on_privmsg(cli, rawnick, chan, msg, *, notice=False, force_role=None):
    if notice and "!" not in rawnick or not rawnick: # server notice; we don't care about those
        return

    user = users._get(rawnick, allow_none=True) # FIXME

    ch = chan.lstrip("".join(hooks.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(hooks.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

    if force_role is None: # if force_role isn't None, that indicates recursion; don't fire these off twice
        for fn in decorators.COMMANDS[""]:
            fn.caller(cli, rawnick, ch, msg)

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

    if wrapper.public and not key.startswith(botconfig.CMD_CHAR):
        return # channel message but no prefix; ignore

    if key.startswith(botconfig.CMD_CHAR):
        key = key[len(botconfig.CMD_CHAR):]

    if not key: # empty key ("") already handled above
        return

    # Don't change this into decorators.COMMANDS[key] even though it's a defaultdict,
    # as we don't want to insert bogus command keys into the dict.
    cmds = []
    phase = var.PHASE
    if user in get_participants():
        roles = get_all_roles(user)
        # A user can be a participant but not have a role, for example, dead vengeful ghost
        has_roles = len(roles) != 0
        if force_role is not None:
            roles &= {force_role} # only fire off role commands for the forced role

        common_roles = set(roles) # roles shared by every eligible role command
        have_role_cmd = False
        for fn in decorators.COMMANDS.get(key, []):
            if not fn.roles:
                cmds.append(fn)
                continue
            if roles.intersection(fn.roles):
                have_role_cmd = True
                cmds.append(fn)
                common_roles.intersection_update(fn.roles)

        if force_role is not None and not have_role_cmd:
            # Trying to force a non-role command with a role.
            # We allow non-role commands to execute if a role is forced if a role
            # command is also executed, as this would allow (for example) a bot admin
            # to add extra effects to all "kill" commands without needing to continually
            # update the list of roles which can use "kill". However, we don't want to
            # allow things like "wolf pstats" because that just doesn't make sense.
            return

        if has_roles and not common_roles:
            # getting here means that at least one of the role_cmds is disjoint
            # from the others. For example, augur see vs seer see when a bare see
            # is executed. In this event, display a helpful error message instructing
            # the user to resolve the ambiguity.
            common_roles = set(roles)
            info = [0,0]
            for fn in cmds:
                fn_roles = roles.intersection(fn.roles)
                if not fn_roles:
                    continue
                for role1 in common_roles:
                    info[0] = role1
                    break
                for role2 in fn_roles:
                    info[1] = role2
                    break
                common_roles &= fn_roles
                if not common_roles:
                    break
            wrapper.pm(messages["ambiguous_command"].format(key, info[0], info[1]))
            return
    elif force_role is None:
        cmds = decorators.COMMANDS.get(key, [])

    for fn in cmds:
        if phase == var.PHASE:
            # FIXME: pass in var, wrapper, message instead of cli, rawnick, chan, message
            fn.caller(cli, rawnick, ch, message)