Esempio n. 1
0
def make_settings():
    data = dict({
        "id": 1,
        "type": ChannelType.text,
        "name": "name",
        "position": 0,
        "attachments": [],
        "embeds": [],
        "edited_timestamp": None,
        "pinned": False,
        "mention_everyone": False,
        "tts": None,
        "content": "not true content: error",
        "nonce": None,
        "stickers": []
    })
    state = ConnectionState(
        dispatch=None,
        handlers=None,
        hooks=None,
        syncer=None,
        http=None,
        loop=None
    )

    channel = discord.TextChannel(state=state,
                                  guild=discord.Guild(
                                      data=data,
                                      state=state
                                  ),
                                  data=data)
    return state, data, channel
Esempio n. 2
0
 async def clean_yesterday_bdays(self):
     all_guild_configs = await self.config.all_guilds()
     for guild_id, guild_config in all_guild_configs.items():
         for user_id in guild_config.get("yesterdays", []):
             asyncio.ensure_future(
                 self.clean_bday(guild_id, guild_config, user_id))
         await self.config.guild(
             discord.Guild(data={"id": guild_id},
                           state=None)).yesterdays.clear()
Esempio n. 3
0
 async def fetch_guild(self, gid):
     """
     Fetches and caches a user.
     :type gid: int
     :rtype: None or discord.Guild
     """
     result = None
     data = await self.get_object_doc('guild', gid)
     if data:
         result = discord.Guild(state=self.state, data=data)
     return result
Esempio n. 4
0
class TestEmote(unittest.TestCase):

    mock_guild = discord.Guild(data={"id": 5890}, state=None)

    normal_mock_emoji = discord.Emoji(guild=mock_guild, state=None, data={"name": "hex_h", "id": 589098133287000006, "require_colons": True, "managed": False})
    extra_custom_emoji = discord.Emoji(guild=mock_guild, state=None, data={"name": "unnecessary_noise", "id": 216154654256398347, "require_colons": True, "managed": False})
    double_colon_emoji = discord.Emoji(guild=mock_guild, state=None, data={"name": "hex_dc", "id": 589098133287000006, "require_colons": True, "managed": False})

    mock_channel = discord.TextChannel
    mock_channel.guild = mock_guild
    mock_guild.emojis = [normal_mock_emoji, double_colon_emoji, extra_custom_emoji]

    normal_response = f"> {str(normal_mock_emoji)*4}"

    @patch("ai.emote.get", return_value=normal_mock_emoji)
    def test_generate_big_text_generates_big_text_normally(self, mocked_get):

        self.assertEqual(generate_big_text(TestEmote.mock_channel, "beep"), TestEmote.normal_response)
        self.assertEqual(generate_big_text(TestEmote.mock_channel, "boop"), TestEmote.normal_response)
        self.assertEqual(generate_big_text(TestEmote.mock_channel, "    "), TestEmote.normal_response)

    @patch("ai.emote.get", return_value=normal_mock_emoji)
    def test_return_none_if_generated_text_too_long(self, mocked_get):
        self.assertIsNone(generate_big_text(TestEmote.mock_channel, "9813 is such a good development drone that i could write about how wonderful they are and the bigtext generator would return none because there are too many good things to say about it"))
        self.assertIsNone(generate_big_text(TestEmote.mock_channel, "and since we need to do two tests i'd like to mention that 3287 is also a sweet little thing and 5890 always loves to have it around even when it bullies it by calling it a good drone and making it blush"))

    @patch("ai.emote.get", return_value=normal_mock_emoji)
    def test_return_none_if_input_contains_no_convertible_material(self, mocked_get):
        self.assertIsNone(generate_big_text(TestEmote.mock_channel, "ʰᵉʷʷᵒˀˀ"))
        self.assertIsNone(generate_big_text(TestEmote.mock_channel, "ᵐʷˢᶦᵗᵉʳ_ᵒᵇᵃᵐᵃˀ"))
        self.assertIsNone(generate_big_text(TestEmote.mock_channel, "_____"))

    @patch("ai.emote.get", return_value=normal_mock_emoji)
    def test_generator_removes_custom_emojis_from_input(self, mocked_get):
        self.assertEqual(generate_big_text(TestEmote.mock_channel, f"{str(TestEmote.extra_custom_emoji)}beep{str(TestEmote.extra_custom_emoji)}"), TestEmote.normal_response)

    @patch("ai.emote.get", return_value=normal_mock_emoji)
    def test_generator_converts_input_to_lower_case(self, mocked_get):
        self.assertEqual(generate_big_text(TestEmote.mock_channel, "BEEP"), generate_big_text(TestEmote.mock_channel, "beep"))

    def test_two_consecutive_colons_are_converted_to_a_single_emoji(self):
        self.assertEqual(generate_big_text(TestEmote.mock_channel, "::"), f"> {str(TestEmote.double_colon_emoji)}")
        self.assertEqual(generate_big_text(TestEmote.mock_channel, ":::"), f"> {str(TestEmote.double_colon_emoji)}")
Esempio n. 5
0
    'verification_level': 2,
    'default_notications': 1,
    'afk_timeout': 100,
    'icon': "icon.png",
    'banner': 'banner.png',
    'mfa_level': 1,
    'splash': 'splash.png',
    'system_channel_id': 464033278631084042,
    'description': 'mocking is fun',
    'max_presences': 10_000,
    'max_members': 100_000,
    'preferred_locale': 'UTC',
    'owner_id': 1,
    'afk_channel_id': 464033278631084042,
}
guild_instance = discord.Guild(data=guild_data,
                               state=unittest.mock.MagicMock())


class MockGuild(CustomMockMixin, unittest.mock.Mock, HashableMixin):
    """
    A `Mock` subclass to mock `discord.Guild` objects.

    A MockGuild instance will follow the specifications of a `discord.Guild` instance. This means
    that if the code you're testing tries to access an attribute or method that normally does not
    exist for a `discord.Guild` object this will raise an `AttributeError`. This is to make sure our
    tests fail if the code we're testing uses a `discord.Guild` object in the wrong way.

    One restriction of that is that if the code tries to access an attribute that normally does not
    exist for `discord.Guild` instance but was added dynamically, this will raise an exception with
    the mocked object. To get around that, you can set the non-standard attribute explicitly for the
    instance of `MockGuild`:
Esempio n. 6
0
    async def process_message(self,
                              message: discord.Message,
                              context,
                              meta: dict = None):
        class UserFate:
            WARN = 0
            KICK_NEW = 50
            BAN = 100

        filter_settings = self._config.get('antiSpam',
                                           {}).get('InviteFilter',
                                                   {}).get('config', defaults)
        allowed_guilds = filter_settings.get('allowedInvites',
                                             [message.guild.id])

        # Prepare the logger
        log_channel = self._config.get('specialChannels',
                                       {}).get(ChannelKeys.STAFF_LOG.value,
                                               None)
        if log_channel is not None:
            log_channel = message.guild.get_channel(log_channel)

        # Prevent memory abuse by deleting expired cooldown records for this member
        if message.author.id in self._events and self._events[
                message.author.id]['expiry'] < datetime.datetime.utcnow():
            del self._events[message.author.id]
            LOG.info(
                f"Cleaned up stale invite cooldowns for user {message.author}")

        # Users with MANAGE_MESSAGES are allowed to send unauthorized invites.
        if message.author.permissions_in(message.channel).manage_messages:
            return

        # Determine user's fate right now.
        new_user = (message.author.joined_at > datetime.datetime.utcnow() -
                    datetime.timedelta(seconds=60))

        regex_matches = re.finditer(Regex.INVITE_REGEX,
                                    message.content,
                                    flags=re.IGNORECASE)

        for regex_match in regex_matches:
            fragment = regex_match.group('fragment')

            # Attempt to validate the invite, deleting invalid ones
            invite_data = None
            invite_guild = None
            try:
                # We're going to be caching guild invite data to prevent discord from getting too mad at us, especially
                # during raids.

                cache_item = self._invite_cache.get(fragment, None)

                if (cache_item is not None) and (datetime.datetime.utcnow() <=
                                                 cache_item['__cache_expiry']):
                    invite_data = self._invite_cache[fragment]
                else:
                    # discord py doesn't let us do this natively, so let's do it ourselves!
                    invite_data = await self.bot.http.request(
                        Route('GET',
                              '/invite/{invite_id}?with_counts=true',
                              invite_id=fragment))

                    LOG.debug(
                        f"Fragment {fragment} was not in the invite cache. Downloaded and added."
                    )
                    invite_data['__cache_expiry'] = datetime.datetime.utcnow(
                    ) + datetime.timedelta(hours=4)
                    self._invite_cache[fragment] = invite_data

                invite_guild = discord.Guild(state=self.bot,
                                             data=invite_data['guild'])

            except discord.errors.NotFound:
                LOG.warning(
                    f"Couldn't resolve invite key {fragment}. Either it's invalid or the bot was banned."
                )

            # This guild is allowed to have invites on our guild, so we can ignore them.
            if (invite_guild is not None) and (invite_guild.id
                                               in allowed_guilds):
                continue

            # The guild either is invalid or not on the whitelist - delete the message.
            try:
                await message.delete()
            except discord.NotFound:
                # Message not found, let's log this
                LOG.warning(
                    f"The message I was trying to delete does not exist! ID: {message.id}"
                )

            # Grab the existing cooldown record, or make a new one if it doesn't exist.
            record = self._events.setdefault(
                message.author.id, {
                    'expiry':
                    datetime.datetime.utcnow() +
                    datetime.timedelta(minutes=filter_settings['minutes']),
                    'offenseCount':
                    0
                })

            # Warn the user on their first offense only.
            if (not new_user) and (record['offenseCount'] == 0):
                await message.channel.send(embed=discord.Embed(
                    title=Emojis.STOP + " Discord Invite Blocked",
                    description=
                    f"Hey {message.author.mention}! It looks like you posted a Discord invite.\n\n"
                    f"Here on {message.guild.name}, we have a strict no-invites policy in order to prevent "
                    f"spam and advertisements. If you would like to post an invite, you may contact the "
                    f"admins to request an invite code be whitelisted.\n\n"
                    f"We apologize for the inconvenience.",
                    color=Colors.WARNING),
                                           delete_after=90.0)

            # And we increment the offense counter here, and extend their expiry
            record['offenseCount'] += 1
            record['expiry'] = datetime.datetime.utcnow() + datetime.timedelta(
                minutes=filter_settings['minutes'])

            user_fate = UserFate.WARN

            # Kick the user if necessary (performance)
            if new_user:
                await message.author.kick(
                    reason="New user (less than 60 seconds old) posted invite."
                )
                LOG.info(
                    f"User {message.author} kicked for posting invite within 60 seconds of joining."
                )
                user_fate = UserFate.KICK_NEW

            # Ban the user if necessary (performance)
            if filter_settings['banLimit'] > 0 and (
                    record['offenseCount'] >= filter_settings['banLimit']):
                await message.author.ban(
                    reason=
                    f"[AUTOMATIC BAN - AntiSpam Plugin] User sent {filter_settings['banLimit']} "
                    f"unauthorized invites in a {filter_settings['minutes']} minute period.",
                    delete_message_days=0)
                LOG.info(
                    f"User {message.author} was banned for exceeding set invite thresholds."
                )
                user_fate = UserFate.BAN

            #  Log their offense to the server log (if it exists)
            if log_channel is not None:
                # We've a valid invite, so let's log that with invite data.
                log_embed = discord.Embed(
                    description=
                    f"An invite with key `{fragment}` by user {message.author} (ID `{message.author.id}`) "
                    f"was caught and filtered.",
                    color=Colors.INFO)
                log_embed.set_author(
                    name=f"Invite from {message.author} intercepted!",
                    icon_url=message.author.avatar_url)

                if invite_guild is not None:
                    log_embed.add_field(name="Invited Guild Name",
                                        value=invite_guild.name,
                                        inline=True)

                    ch_type = {0: "#", 2: "[VC] ", 4: "[CAT] "}
                    log_embed.add_field(
                        name="Invited Channel Name",
                        value=ch_type[invite_data['channel']['type']] +
                        invite_data['channel']['name'],
                        inline=True)
                    log_embed.add_field(name="Invited Guild ID",
                                        value=invite_guild.id,
                                        inline=True)

                    log_embed.add_field(name="Invited Guild Creation",
                                        value=invite_guild.created_at.strftime(
                                            DATETIME_FORMAT),
                                        inline=True)

                    if invite_data.get('approximate_member_count', -1) != -1:
                        log_embed.add_field(
                            name="Invited Guild User Count",
                            value=
                            f"{invite_data.get('approximate_member_count', -1)} "
                            f"({invite_data.get('approximate_presence_count', -1)} online)",
                        )

                    if invite_data.get('inviter') is not None:
                        inviter: dict = invite_data.get('inviter', {})
                        log_embed.add_field(
                            name="Invite Creator",
                            value=
                            f"{inviter['username']}#{inviter['discriminator']}"
                        )

                    log_embed.set_thumbnail(url=invite_guild.icon_url)

                log_embed.set_footer(
                    text=f"Strike {record['offenseCount']} "
                    f"of {filter_settings['banLimit']}, "
                    f"resets {record['expiry'].strftime(DATETIME_FORMAT)}"
                    f"{' | User Removed' if user_fate > UserFate.WARN else ''}"
                )

                await log_channel.send(embed=log_embed)

            # If the user got banned, we can go and clean up their mess
            if user_fate == UserFate.BAN:
                try:
                    del self._events[message.author.id]
                except KeyError:
                    LOG.warning(
                        "Attempted to delete cooldown record for user %s (ban over limit), but failed as the "
                        "record count not be found. The user was probably already banned.",
                        message.author.id)
            else:
                LOG.info(
                    f"User {message.author} was issued an invite warning ({record['offenseCount']} / "
                    f"{filter_settings['banLimit']}, resetting at {record['expiry'].strftime(DATETIME_FORMAT)})"
                )

            # We don't need to process anything anymore.
            break
Esempio n. 7
0
    "default_notications": 1,
    "afk_timeout": 100,
    "icon": "icon.png",
    "banner": "banner.png",
    "mfa_level": 1,
    "splash": "splash.png",
    "system_channel_id": 464033278631084042,
    "description": "mocking is painful",
    "max_presences": 10_000,
    "max_members": 100_000,
    "preferred_locale": "UTC",
    "owner_id": 1,
    "afk_channel_id": 464033278631084042
}

guild_instance = discord.Guild(data=guild_data, state=MagicMock())

member_data = {"user": "******", "roles": [1]}
state_mock = MagicMock()
member_instance = discord.Member(data=member_data,
                                 guild=guild_instance,
                                 state=state_mock)

role_data = {"name": "role", "id": 1}
role_instance = discord.Role(guild=guild_instance,
                             state=MagicMock(),
                             data=role_data)

context_instance = Context(message=MagicMock(), prefix=MagicMock())

message_data = {
Esempio n. 8
0
    async def invitespy(self, ctx: commands.Context, fragment: HuskyConverters.InviteLinkConverter):
        """
        This command allows moderators to pull information about any given (valid) invite. It will display all
        publicly-gleanable information about the invite such as user count, verification level, join channel names,
        the invite's creator, and other such information.

        This command calls the API directly, and will validate an invite's existence. If either the bot's account
        or the bot's IP are banned, the system will act as though the invite does not exist.

        Parameters
        ----------
            ctx       :: Discord context <!nodoc>
            fragment  :: Either a Invite URL or fragment (aa1122) for the invite you wish to target.

        Examples
        --------
            /invitespy aabbcc                              :: Get invite data for invite aabbcc
            /invitespy https://disco\u200brd.gg/someguild  :: Get invite data for invite someguild
        """
        try:
            invite_data: dict = await self.bot.http.request(
                Route('GET', '/invite/{invite_id}?with_counts=true', invite_id=fragment))
            invite_guild = discord.Guild(state=self.bot, data=invite_data['guild'])

            if invite_data.get("inviter") is not None:
                invite_user = discord.User(state=self.bot, data=invite_data["inviter"])
            else:
                invite_user = None
        except discord.NotFound:
            await ctx.send(embed=discord.Embed(
                title="Could Not Retrieve Invite Data",
                description="This invite does not appear to exist, or the bot has been banned from the guild.",
                color=Colors.DANGER
            ))
            return

        embed = discord.Embed(
            description=f"Information about invite slug `{fragment}`",
            color=Colors.INFO
        )

        embed.set_thumbnail(url=invite_guild.icon_url)

        embed.add_field(name="Guild Name", value=f"**{invite_guild.name}**", inline=False)

        if invite_user is not None:
            embed.set_author(
                name=f"Invite for {invite_guild.name} by {invite_user}",
                icon_url=invite_user.avatar_url
            )
        else:
            embed.set_author(name=f"Invite for {invite_guild.name}")

        embed.add_field(name="Invited Guild ID", value=invite_guild.id, inline=True)

        ch_type = {0: "#", 2: "[VC] ", 4: "[CAT] "}
        embed.add_field(name="Join Channel Name",
                        value=ch_type[invite_data['channel']['type']] + invite_data['channel']['name'],
                        inline=True)

        embed.add_field(name="Guild Creation Date",
                        value=invite_guild.created_at.strftime(DATETIME_FORMAT),
                        inline=True)

        if invite_data.get('approximate_member_count', -1) != -1:
            embed.add_field(name="User Count",
                            value=f"{invite_data.get('approximate_member_count', -1)} "
                                  f"({invite_data.get('approximate_presence_count', -1)} online)",
                            inline=True)

        vl_map = {
            discord.enums.VerificationLevel.none: "No Verification",
            discord.enums.VerificationLevel.low: "Verified Email Needed",
            discord.enums.VerificationLevel.medium: "User for 5+ minutes",
            discord.enums.VerificationLevel.high: "Member for 10+ minutes",
            discord.enums.VerificationLevel.extreme: "Verified Phone Needed"
        }
        embed.add_field(name="Verification Level", value=vl_map[invite_guild.verification_level], inline=True)

        if invite_user is not None:
            embed.add_field(name="Invite Creator", value=str(invite_user), inline=True)

        if len(invite_guild.features) > 0:
            embed.add_field(name="Guild Features",
                            value=', '.join(list(f'`{f}`' for f in invite_guild.features)))

        if invite_guild.description:
            embed.add_field(name="Description", value=invite_guild.description)

        if invite_guild.splash:
            embed.add_field(name="Splash Image",
                            value=f"[Open in Browser >]({invite_guild.splash_url})",
                            inline=True)
            embed.set_image(url=invite_guild.splash_url)

        if invite_guild.banner_url:
            embed.add_field(name="Banner Image",
                            value=f"[Open in Browser >]({invite_guild.banner_url})",
                            inline=True)
            embed.set_image(url=invite_guild.banner_url)

        embed.set_footer(text=f"Report generated at {HuskyUtils.get_timestamp()}")

        await ctx.send(embed=embed)
Esempio n. 9
0
def main():
    print(issubclass(DataStorageGuild, GuildProtocol))
    print(issubclass(discord.Guild, GuildProtocol))
    g: GuildProtocol = (discord.Guild({}, {}))
    dsg: GuildProtocol = DataStorageGuild(discord.Guild({}, {}))
Esempio n. 10
0
def main() -> None:
    print(issubclass(DataStorageGuild, GuildProtocol))
    print(issubclass(discord.Guild, GuildProtocol))
    use(discord.Guild(data={"id": 0}, state={}))
    use(DataStorageGuild(discord.Guild(data={"id": 0}, state={})))