Example #1
0
def test_set_invalid_formatting(cli: ShoulderbirdCLI, message: Mock) -> None:
    """Confirm failures based on return messages"""
    message.clean_content = "sb!set myGuild But forgot the equal sign"
    result = cli.parse_command(message)
    assert result
    assert "Error: Formatting" in result

    message.clean_content = "sb!set myGuild = "
    result = cli.parse_command(message)
    assert result
    assert "Error: Formatting" in result
Example #2
0
def test_ignore_no_target(cli: ShoulderbirdCLI, message: Mock) -> None:
    """Ignore command but nothing given"""
    message.clean_content = "sb!ignore "
    result = cli.parse_command(message)

    assert result
    assert "Error: Formatting" in result
Example #3
0
def test_help(cli: ShoulderbirdCLI, message: Mock) -> None:
    """Check for good help responses"""
    message.clean_content = "sb!help"
    result = cli.parse_command(message)

    assert result
    assert COMMAND_CONFIG["sb!help"]["help"] in result
Example #4
0
def test_all_helps(cli: ShoulderbirdCLI, message: Mock) -> None:
    """Checks all helps in COMMAND_CONFIG"""
    for key, values in COMMAND_CONFIG.items():
        message.clean_content = f"sb!help {key.replace('sb!', '')}"
        result = cli.parse_command(message)

        assert result
        assert values["help"] in result, key
Example #5
0
def test_toggle_off_guild_found(cli: ShoulderbirdCLI, message: Mock) -> None:
    """Guild found in config, turn toggle off"""
    message.clean_content = "sb!off"
    message.author.id = 101
    result = cli.parse_command(message)

    assert result
    assert "ShoulderBird now **off**" in result
Example #6
0
def test_toggle_off_guild_not_found(cli: ShoulderbirdCLI,
                                    message: Mock) -> None:
    """Guild not found in config, nothing to turn off"""
    message.clean_content = "sb!off"
    message.author.id = 901
    result = cli.parse_command(message)

    assert result
    assert "No searches found," in result
Example #7
0
def test_set_invalid_guild(cli: ShoulderbirdCLI, message: Mock) -> None:
    """Unknown guild/not in guild"""
    guilds = [Guild(10, "test"), Guild(11, "testings")]
    message.clean_content = "sb!set myGuild = test"
    with patch.object(cli, "client") as mock_discord:
        mock_discord.guilds = guilds
        result = cli.parse_command(message)

    assert result
    assert "Error: Guild not found" in result
Example #8
0
def test_ignore_user_not_found(cli: ShoulderbirdCLI, message: Mock) -> None:
    """Username not found, return helpful tips"""
    message.clean_content = "sb!ignore dave"
    users = [User(10, "test"), User(9876543210, "test_user")]
    with patch.object(cli, "client") as mock_discord:
        mock_discord.users = users
        result = cli.parse_command(message)

        assert result
        assert "'dave' not found." in result
Example #9
0
def test_set_valid_id_exists(cli: ShoulderbirdCLI, message: Mock) -> None:
    """Set a search by guild ID that does exist"""
    guilds = [Guild(101, "test"), Guild(9876543210, "testings")]
    message.clean_content = "sb!set 101 = (search|find)"
    message.author.id = 101
    with patch.object(cli, "client") as mock_discord:
        mock_discord.guilds = guilds
        result = cli.parse_command(message)
    assert result
    assert "Search set" in result

    member = cli.config.load_member("101", "101")
    assert member.regex == "(search|find)"
Example #10
0
def test_ignore_name_toggle_target(cli: ShoulderbirdCLI,
                                   message: Mock) -> None:
    """Ignore a user, confirm. Unignore user, confirm"""
    message.clean_content = "sb!ignore test_user"
    message.author.id = 901
    users = [User(10, "test"), User(9876543210, "test_user")]
    with patch.object(cli, "client") as mock_discord:
        mock_discord.users = users
        ignored_result = cli.parse_command(message)

        assert ignored_result
        assert "'test_user' added to" in ignored_result

        for member in cli.config.member_list_all("901"):
            assert "9876543210" in member.ignore, member.guild_id

        message.clean_content = "sb!unignore test_user"
        unignored_result = cli.parse_command(message)

        assert unignored_result
        assert "'test_user' removed from" in unignored_result

        for member in cli.config.member_list_all("901"):
            assert "9876543210" not in member.ignore, member.guild_id
Example #11
0
def test_no_command_found(cli: ShoulderbirdCLI, message: Mock) -> None:
    """Should fall-through"""
    message.clean_content = "sb!boop"
    assert cli.parse_command(message) is None
class ShoulderBirdParser:
    """Point of entry object for ShoulderBird module"""

    logger = logging.getLogger(__name__)

    def __init__(self, client: Client, config_file: str = DEFAULT_CONFIG) -> None:
        """Loads config"""
        self.__config = ShoulderBirdConfig(config_file)
        self.cli = ShoulderbirdCLI(self.__config, client)
        self.client = client

    def close(self) -> None:
        """Saves config state, breaks all references"""
        self.__config.save_config()
        del self.__config

    def get_matches(
        self, guild_id: str, user_id: str, clean_message: str
    ) -> List[BirdMember]:
        """Returns a list of BirdMembers whos searches match clean_message"""
        self.logger.debug(
            "get_matches: '%s', '%s', '%s'", guild_id, user_id, clean_message
        )
        match_list: List[BirdMember] = []
        guild_members = self.__config.guild_list_all(guild_id)
        for member in guild_members:
            if not (member.toggle and member.regex) or user_id in member.ignore:
                continue
            # Word bound regex search, case agnostic
            if re.search(fr"(?i)\b({member.regex})\b", clean_message):
                self.logger.debug("Match found: '%s'", member.member_id)
                match_list.append(member)
        return match_list

    @classmethod
    def __is_valid_message(cls, message: Message) -> bool:
        """Tests for valid message to process"""
        if not isinstance(message, Message):
            cls.logger.error("Unknown arg type: %s", type(message))
            return False

        if not message.content:
            cls.logger.debug("Empty message given, skipping.")
            return False

        if str(message.channel.type) not in ["text", "private"]:
            cls.logger.debug("Unsupported message type, skipping.")
            return False

        return True

    async def on_message(self, message: Message) -> None:
        """Hook for discord client, async coro"""
        if not ShoulderBirdParser.__is_valid_message(message):
            return None

        tic = time.perf_counter()
        self.logger.debug("[START] onmessage")

        # If this is a private message, branch to CLI hander and return here
        if str(message.channel.type) == "private":
            response = self.cli.parse_command(message)
            if response:
                await self.__send_dm(message.author, response)
            return None

        guild: Guild = message.guild
        channel_ids: List[str] = [str(member.id) for member in message.channel.members]

        matches = self.get_matches(
            str(message.guild.id), str(message.author.id), message.content
        )

        for match in matches:
            if match.member_id not in channel_ids:
                self.logger.debug(
                    "'%s' not in channel '%s'", match.member_id, message.channel_name
                )
                continue
            await self.__send_match_dm(match, message, guild)

        self.logger.debug(
            "[FINISH] onmessage completed: %f ms", round(time.perf_counter() - tic, 2)
        )

    async def __send_match_dm(
        self, member: BirdMember, message: Message, guild: Guild
    ) -> None:
        """Private - send DM message to match. To be replaced with actions queue"""
        try:
            target: Member = guild.get_member(int(member.member_id))
        except ValueError:
            self.logger.error("Invalid member_id to int: '%s'", member.member_id)
            return

        msg = (
            f"ShoulderBird notification, **{message.author.display_name}** "
            f"mentioned you in **{message.channel.name}** saying:\n"
            f"`{message.clean_content}`\n{message.jump_url}"
        )
        await self.__send_dm(target, msg)

    @staticmethod
    async def __send_dm(target: Member, content: str) -> None:
        """Private, sends a DM to target"""
        if target.dm_channel is None:
            await target.create_dm()
        if target.dm_channel:
            await target.dm_channel.send(content)