コード例 #1
0
ファイル: functions.py プロジェクト: rdococ/lykos
def match_role(var,
               role: str,
               remove_spaces: bool = False,
               allow_extra: bool = False,
               allow_special: bool = True,
               scope: Optional[Iterable[str]] = None) -> Match[LocalRole]:
    """ Match a partial role or alias name into the internal role key.

    :param var: Game state
    :param role: Partial role to match on
    :param remove_spaces: Whether or not to remove all spaces before matching.
        This is meant for contexts where we truly cannot allow spaces somewhere; otherwise we should
        prefer that the user matches including spaces where possible for friendlier-looking commands.
    :param allow_extra: Whether to allow keys that are defined in the translation file but do not exist in the bot.
        Typically these are roles that were previously removed.
    :param allow_special: Whether to allow special keys (lover, vg activated, etc.).
        If scope is set, this parameter is ignored.
    :param scope: Limit matched roles to these explicitly passed-in roles (iterable of internal role names).
    :return: Match object with all matches (see src.match.match_all)
    """
    role = role.lower()
    if remove_spaces:
        role = role.replace(" ", "")

    role_map = messages.get_role_mapping(reverse=True,
                                         remove_spaces=remove_spaces)

    special_keys = set()
    if scope is None and allow_special:
        evt = Event("get_role_metadata", {})
        evt.dispatch(var, "special_keys")
        special_keys = functools.reduce(lambda x, y: x | y, evt.data.values(),
                                        special_keys)

    matches = match_all(role, role_map.keys())

    # strip matches that don't refer to actual roles or special keys (i.e. refer to team names)
    filtered_matches = set()
    if scope is not None:
        allowed = set(scope)
    elif allow_extra:
        allowed = set(role_map.values()) | special_keys
    else:
        allowed = All.roles | special_keys

    for match in matches:
        if role_map[match] in allowed:
            filtered_matches.add(LocalRole(role_map[match], match))

    return Match(filtered_matches)
コード例 #2
0
def on_transition_night_end(evt, var):
    succubi = get_all_players(("succubus", ))
    role_map = messages.get_role_mapping()
    for succubus in succubi:
        pl = get_players()
        random.shuffle(pl)
        pl.remove(succubus)
        succ = []
        for p in pl:
            if p in succubi:
                succ.append("{0} ({1})".format(p, role_map["succubus"]))
            else:
                succ.append(p.nick)
        succubus.send(messages["succubus_notify"],
                      messages["players_list"].format(succ),
                      sep="\n")
コード例 #3
0
ファイル: utilities.py プロジェクト: svdermant/lykos
def complete_role(var,
                  role: str,
                  remove_spaces: bool = False,
                  allow_special: bool = True) -> List[str]:
    """ Match a partial role or alias name into the internal role key.

    :param var: Game state
    :param role: Partial role to match on
    :param remove_spaces: Whether or not to remove all spaces before matching.
        This is meant for contexts where we truly cannot allow spaces somewhere; otherwise we should
        prefer that the user matches including spaces where possible for friendlier-looking commands.
    :param allow_special: Whether to allow special keys (lover, vg activated, etc.)
    :return: A list of 0 elements if the role didn't match anything.
        A list with 1 element containing the internal role key if the role matched unambiguously.
        A list with 2 or more elements containing localized role or alias names if the role had ambiguous matches.
    """
    from src.cats import ROLES  # FIXME: should this be moved into cats? ROLES isn't declared in cats.__all__

    role = role.lower()
    if remove_spaces:
        role = role.replace(" ", "")

    role_map = messages.get_role_mapping(reverse=True,
                                         remove_spaces=remove_spaces)

    special_keys = set()
    if allow_special:
        evt = Event("get_role_metadata", {})
        evt.dispatch(var, "special_keys")
        special_keys = functools.reduce(lambda x, y: x | y, evt.data.values(),
                                        special_keys)

    matches = complete_match(role, role_map.keys())
    if not matches:
        return []

    # strip matches that don't refer to actual roles or special keys (i.e. refer to team names)
    filtered_matches = []
    allowed = ROLES.keys() | special_keys
    for match in matches:
        if role_map[match] in allowed:
            filtered_matches.append(match)

    if len(filtered_matches) == 1:
        return [role_map[filtered_matches[0]]]
    return filtered_matches
コード例 #4
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)