Exemple #1
0
    async def do_removal(self,
                         ctx,
                         limit,
                         predicate,
                         *,
                         before=None,
                         after=None):
        if limit > 2000:
            return await ctx.send(
                f'Too many messages to search given ({limit}/2000)')

        if before is None:
            before = ctx.message
        else:
            before = disnake.Object(id=before)

        if after is not None:
            after = disnake.Object(id=after)

        try:
            deleted = await ctx.channel.purge(limit=limit,
                                              before=before,
                                              after=after,
                                              check=predicate)
        except disnake.Forbidden as e:
            return await ctx.send(
                'I do not have permissions to delete messages.')
        except disnake.HTTPException as e:
            return await ctx.send(f'Error: {e} (try a smaller search?)')

        spammers = Counter(m.author.display_name for m in deleted)
        deleted = len(deleted)
        messages = [
            f'{deleted} message{" was" if deleted == 1 else "s were"} removed.'
        ]
        if deleted:
            messages.append('')
            spammers = sorted(spammers.items(),
                              key=lambda t: t[1],
                              reverse=True)
            messages.extend(f'**{name}**: {count}' for name, count in spammers)

        to_send = '\n'.join(messages)

        if len(to_send) > 2000:
            await ctx.send(f'Successfully removed {deleted} messages.',
                           delete_after=10)
        else:
            await ctx.send(to_send, delete_after=10)
Exemple #2
0
async def convert_argument(ctx, converters: list, args: Any, param) -> Any:
    for converter in converters:
        # Enums
        if issubclass(converter, Enum):
            if isinstance(args, converter):
                return args
            if isinstance(args, str):
                return getattr(converter, args)
            return converter(int(args))

        try:
            p = disnake.Object(id=0)
            p.name = param

            converted = await _actual_conversion(ctx, converter, str(args), p)

            return converted

        except BadArgument:
            pass

        except ConversionError:
            pass

    raise BadArgument(
        f'Converting {args} to "{" or ".join([c.__name__ for c in converters])}" failed for parameter "{param}".'
    )
Exemple #3
0
    async def get(self, ctx: Context, cog_only=False) -> Config:
        await self._bot.wait_until_ready()

        if not isinstance(ctx, Context):
            message = disnake.Object(id=0)
            message.guild = self._bot.get_guild(ctx)
            message._state = message.guild._state
            ctx = Context(bot=self._bot,
                          message=message,
                          prefix=None,
                          view=None)

        document = await self._bot.config.guild(ctx.guild.id)
        cog_doc = document.get(self._template.__key__, {})

        if not cog_only:
            items = self._bot.config.general_context._template.items + self._template.items
            doc = {
                **cog_doc,
                **document.get(
                    self._bot.config.general_context._template.__key__, {})
            }
        else:
            items = self._template.items
            doc = cog_doc

        log.debug(
            f'Creating config {self._template} for guild {ctx.guild.name} ({ctx.guild.id})'
        )

        return await Config.from_doc(self._template, ctx, doc, items)
Exemple #4
0
async def pack_messages(messages, guild_id):
    out = ""
    for message in messages:
        name = await Utils.username(message.author, clean=False)
        reply = ""
        if message.reply_to is not None:
            reply = f" | In reply to https://discord.com/channels/{message.server}/{message.channel}/{message.reply_to}"
        timestamp = datetime.datetime.strftime(disnake.Object(message.messageid).created_at.astimezone(pytz.timezone(Configuration.get_var(guild_id, 'GENERAL', 'TIMEZONE'))),'%H:%M:%S')
        out += f"{timestamp} {message.server} - {message.channel} - {message.messageid} | {name} ({message.author}) | {message.content}{reply} | {(', '.join(Utils.assemble_attachment(message.channel, attachment.id, attachment.name) for attachment in message.attachments))}\r\n"
    return out
Exemple #5
0
    async def handle_emoji_suggestion_message_edit(
            self, message: disnake.RawMessageUpdateEvent):
        if message.channel_id == EMOJI_SUGGESTIONS_CHAN_ID:
            channel = self.bot.get_channel(EMOJI_SUGGESTIONS_CHAN_ID)
            if channel is None:
                return

            try:
                await channel.delete_messages(
                    [disnake.Object(message.message_id)])
            except disnake.HTTPException:
                pass
Exemple #6
0
class ModQueueItem(object):
    __slots__ = ('_bot', '_collection', 'id', 'type', 'author_id', 'guild_id',
                 'message', 'matches', 'timestamp', 'edits', 'action',
                 'mod_id', 'action_timestamp', 'deleted_at', 'deleted_by_id')

    @classmethod
    def from_doc(cls, doc, bot: commands.Bot, collection):
        self = cls()
        self._bot = bot
        self._collection = collection
        self._from_doc(doc)

        return self

    def _from_doc(self, doc):
        self.id = doc.get('id')
        self.type = ModQueueItem.Type(doc['type'])
        self.author_id = doc['author_id']
        self.guild_id = doc['guild_id']
        self.message = ModQueueItem.Message.from_doc(doc, self._bot)
        self.matches = doc['matches']
        self.timestamp = doc['timestamp']

        self.deleted_at = doc.get('deleted_at', False)
        self.deleted_by_id = doc.get('deleted_by_id', None)

        self.edits = None
        if 'edits' in doc:
            self.edits = [ModQueueItem.Edit.from_doc(d) for d in doc['edits']]

        self.action = None
        if 'action' in doc:
            self.action = Action(doc['action'])

        self.mod_id = doc.get('mod_id', None)
        self.action_timestamp = doc.get('action_timestamp', None)

    @property
    def author(self) -> disnake.User:
        if author := self._bot.get_user(self.author_id):
            return author

        # if the author can't be loaded for some reason, we will build a fake one
        author = disnake.Object(self.author_id)
        author.name = '[USER NOT FOUND]'
        author.discriminator = '0000'
        author.display_avatar = 'https://cdn.discordapp.com/embed/avatars/0.png'
        author.mention = f'<@{self.author_id}>'
        return author
Exemple #7
0
    async def ios_body(self, channel=disnake.Object(id='534431057001316362')):
        process = subprocess.Popen(["ssh", "merlin"], stdout=subprocess.PIPE)
        output, _ = process.communicate()
        memory, rest = output.decode('utf-8').split("semafory:\n")
        semaphores, processes = rest.split("procesy:\n")
        try:
            parsed_memory = parse_memory(memory)
            parsed_semaphores, parsed_files = parse_semaphores(semaphores)
            parsed_processes = parse_processes(processes)
            parsed_resources = {
                RESOURCE_TYPE.MEMORY: parsed_memory,
                RESOURCE_TYPE.SEMAPHORE: parsed_semaphores,
                RESOURCE_TYPE.FILE: parsed_files,
                RESOURCE_TYPE.PROCESS: parsed_processes,
            }
            await print_output(self.bot, channel, "merlinovi",
                               filter_year(parsed_resources))
        except IndexError:
            await channel.send("Toastere, máš bordel v parsování.")

        process = subprocess.Popen(["ssh", "eva"], stdout=subprocess.PIPE)
        output, _ = process.communicate()

        memory, rest = output.decode('utf-8').split("semafory:\n")
        semaphores, processes = rest.split("procesy:\n")
        # remove unwanted processes
        processes = filter_processes(processes)
        try:
            parsed_memory = parse_memory(memory)
            parsed_semaphores, _ = parse_semaphores(semaphores)
            parsed_processes = parse_processes(processes)
            parsed_resources = {
                RESOURCE_TYPE.MEMORY: parsed_memory,
                RESOURCE_TYPE.SEMAPHORE: parsed_semaphores,
                RESOURCE_TYPE.PROCESS: parsed_processes,
            }
            await print_output(self.bot, channel, "evě",
                               filter_year(parsed_resources))
        except IndexError:
            await channel.send("Toastere, máš bordel v parsování.")
        # eva doesn't seem to have /dev/shm
        await channel.send("Pokud nevíte jak po sobě uklidit, checkněte: " +
                           "https://discordapp.com/channels/" +
                           "461541385204400138/534431057001316362/" +
                           "698701631495340033")
Exemple #8
0
    async def on_tempban_timer_complete(self, timer):
        guild_id, mod_id, member_id = timer.args

        guild = self.bot.get_guild(guild_id)
        if guild is None:
            # RIP
            return

        moderator = guild.get_member(mod_id)
        if moderator is None:
            try:
                moderator = await self.bot.fetch_user(mod_id)
            except:
                # request failed somehow
                moderator = f'Mod ID {mod_id}'
            else:
                moderator = f'{moderator} (ID: {mod_id})'
        else:
            moderator = f'{moderator} (ID: {mod_id})'

        reason = f'Automatic unban from timer made on {timer.created_at} by {moderator}.'
        await guild.unban(disnake.Object(id=member_id), reason=reason)
Exemple #9
0
    async def softban(self,
                      ctx,
                      member: MemberID,
                      *,
                      reason: ActionReason = None):
        """Soft bans a member from the server.

        A softban is basically banning the member from the server but
        then unbanning the member as well. This allows you to essentially
        kick the member while removing their messages.

        In order for this to work, the bot must have Ban Member permissions.

        To use this command you must have Kick Members permissions.
        """

        if reason is None:
            reason = f'Action done by {ctx.author} (ID: {ctx.author.id})'

        obj = disnake.Object(id=member)
        await ctx.guild.ban(obj, reason=reason)
        await ctx.guild.unban(obj, reason=reason)
        await ctx.send('\N{OK HAND SIGN}')
Exemple #10
0
    async def purge(self, ctx, *, args: str = None):
        '''Advanced purge command. Do `help purge` for usage examples and argument list.

		Arguments are parsed as command line arguments.

		Examples:
		Delete all messages within the last 200 containing the word "spam": `purge --check 200 --contains "spam"`
		Delete all messages within the last 100 from two members: `purge --user @runie @dave`
		Delete maximum 6 messages within the last 400 starting with "ham": `purge --check 400 --max 6 --starts "ham"`

		List of arguments:
		```
		--check <int>
		Amount of messages the bot will check for deletion.

		--max <int>
		Maximum amount of messages the bot will delete.

		--bot
		Only delete messages from bots.

		--user member [...]
		Only delete messages from these members.

		--after message_id
		Start deleting after this message id.

		--before message_id
		Delete, at most, up until this message id.

		--contains <string> [...]
		Delete messages containing this string(s).

		--starts <string> [...]
		Delete messages starting with this string(s).

		--ends <string> [...]
		Delete messages ending with this string(s).```'''

        parser = NoExitArgumentParser(prog='purge',
                                      add_help=False,
                                      allow_abbrev=False)

        parser.add_argument(
            '-c',
            '--check',
            type=int,
            metavar='message_count',
            help='Total amount of messages checked for deletion.')
        parser.add_argument(
            '-m',
            '--max',
            type=int,
            metavar='message_count',
            help='Total amount of messages the bot will delete.')
        parser.add_argument('--bot',
                            action='store_true',
                            help='Only delete messages from bots.')
        parser.add_argument('-u',
                            '--user',
                            nargs='+',
                            metavar='user',
                            help='Only delete messages from this member(s).')
        parser.add_argument('-a',
                            '--after',
                            type=int,
                            metavar='id',
                            help='Start deleting after this message id.')
        parser.add_argument('-b',
                            '--before',
                            type=int,
                            metavar='id',
                            help='Delete, at most, up until this message id.')
        parser.add_argument('--contains',
                            nargs='+',
                            metavar='text',
                            help='Delete messages containing this string(s).')
        parser.add_argument(
            '--starts',
            nargs='+',
            metavar='text',
            help='Delete messages starting with this string(s).')
        parser.add_argument('--ends',
                            nargs='+',
                            metavar='text',
                            help='Delete messages ending with this string(s).')

        if args is None:
            await ctx.send('```\n{0}\n```'.format(parser.format_help()))
            return

        try:
            args = parser.parse_args(shlex.split(args))
        except Exception as e:
            raise commands.CommandError(str(e).partition('error: ')[2])

        preds = [
            lambda m: m.id != ctx.message.id, lambda m: m.id != RULES_MSG_ID
        ]

        if args.user:
            converter = MaybeMemberConverter()
            members = []

            for id in args.user:
                try:
                    member = await converter.convert(ctx, id)
                    members.append(member)
                except commands.CommandError:
                    raise commands.CommandError(
                        'Unknown user: "******"'.format(id))

            # yes, if both objects were disnake.Member I could do m.author in members,
            # but since member can be FakeUser I need to do an explicit id comparison
            preds.append(
                lambda m: any(m.author.id == member.id for member in members))

        if args.contains:
            preds.append(lambda m: any(
                (s.lower() in m.content.lower()) for s in args.contains))

        if args.bot:
            preds.append(lambda m: m.author.bot)

        if args.starts:
            preds.append(lambda m: any(m.content.lower().startswith(s.lower())
                                       for s in args.starts))

        if args.ends:
            preds.append(lambda m: any(m.content.lower().endswith(s.lower())
                                       for s in args.ends))

        count = args.max
        deleted = 0

        def predicate(message):
            nonlocal deleted

            if count is not None and deleted >= count:
                return False

            if all(pred(message) for pred in preds):
                deleted += 1
                return True

        # limit is 100 be default
        limit = 100
        after = None
        before = None

        # set to 512 if after flag is set
        if args.after:
            after = disnake.Object(id=args.after)
            limit = PURGE_LIMIT

        if args.before:
            before = disnake.Object(id=args.before)

        # if we actually want to manually specify it doe
        if args.check is not None:
            limit = max(0, min(PURGE_LIMIT, args.check))

        try:
            deleted_messages = await ctx.channel.purge(limit=limit,
                                                       check=predicate,
                                                       before=before,
                                                       after=after)
        except disnake.HTTPException:
            raise commands.CommandError(
                'Error occurred when deleting messages.')

        deleted_count = len(deleted_messages)

        log.info('%s purged %s messages in %s', po(ctx.author), deleted_count,
                 po(ctx.guild))

        await ctx.send('{0} messages deleted.'.format(deleted_count),
                       delete_after=10)
Exemple #11
0
 def get_guild_prefixes(self, guild, *, local_inject=_prefix_callable):
     proxy_msg = disnake.Object(id=0)
     proxy_msg.guild = guild
     return local_inject(self, proxy_msg)
Exemple #12
0
    async def massban(self, ctx, *, args):
        """Mass bans multiple members from the server.
        This command has a powerful "command line" syntax. To use this command
        you and the bot must both have Ban Members permission. **Every option is optional.**
        Users are only banned **if and only if** all conditions are met.
        The following options are valid.
        `--channel` or `-c`: Channel to search for message history.
        `--reason` or `-r`: The reason for the ban.
        `--regex`: Regex that usernames must match.
        `--created`: Matches users whose accounts were created less than specified minutes ago.
        `--joined`: Matches users that joined less than specified minutes ago.
        `--joined-before`: Matches users who joined before the member ID given.
        `--joined-after`: Matches users who joined after the member ID given.
        `--no-avatar`: Matches users who have no avatar. (no arguments)
        `--no-roles`: Matches users that have no role. (no arguments)
        `--show`: Show members instead of banning them (no arguments).
        Message history filters (Requires `--channel`):
        `--contains`: A substring to search for in the message.
        `--starts`: A substring to search if the message starts with.
        `--ends`: A substring to search if the message ends with.
        `--match`: A regex to match the message content to.
        `--search`: How many messages to search. Default 100. Max 2000.
        `--after`: Messages must come after this message ID.
        `--before`: Messages must come before this message ID.
        `--files`: Checks if the message has attachments (no arguments).
        `--embeds`: Checks if the message has embeds (no arguments).
        """

        # For some reason there are cases due to caching that ctx.author
        # can be a User even in a guild only context
        # Rather than trying to work out the kink with it
        # Just upgrade the member itself.
        if not isinstance(ctx.author, disnake.Member):
            try:
                author = await ctx.guild.fetch_member(ctx.author.id)
            except disnake.HTTPException:
                return await ctx.send(
                    'Somehow, Discord does not seem to think you are in this server.'
                )
        else:
            author = ctx.author

        parser = Arguments(add_help=False, allow_abbrev=False)
        parser.add_argument('--channel', '-c')
        parser.add_argument('--reason', '-r')
        parser.add_argument('--search', type=int, default=100)
        parser.add_argument('--regex')
        parser.add_argument('--no-avatar', action='store_true')
        parser.add_argument('--no-roles', action='store_true')
        parser.add_argument('--created', type=int)
        parser.add_argument('--joined', type=int)
        parser.add_argument('--joined-before', type=int)
        parser.add_argument('--joined-after', type=int)
        parser.add_argument('--contains')
        parser.add_argument('--starts')
        parser.add_argument('--ends')
        parser.add_argument('--match')
        parser.add_argument('--show', action='store_true')
        parser.add_argument('--embeds',
                            action='store_const',
                            const=lambda m: len(m.embeds))
        parser.add_argument('--files',
                            action='store_const',
                            const=lambda m: len(m.attachments))
        parser.add_argument('--after', type=int)
        parser.add_argument('--before', type=int)

        try:
            args = parser.parse_args(shlex.split(args))
        except Exception as e:
            return await ctx.send(str(e))

        members = []

        if args.channel:
            channel = await commands.TextChannelConverter().convert(
                ctx, args.channel)
            before = args.before and disnake.Object(id=args.before)
            after = args.after and disnake.Object(id=args.after)
            predicates = []
            if args.contains:
                predicates.append(lambda m: args.contains in m.content)
            if args.starts:
                predicates.append(lambda m: m.content.startswith(args.starts))
            if args.ends:
                predicates.append(lambda m: m.content.endswith(args.ends))
            if args.match:
                try:
                    _match = re.compile(args.match)
                except re.error as e:
                    return await ctx.send(
                        f'Invalid regex passed to `--match`: {e}')
                else:
                    predicates.append(lambda m, x=_match: x.match(m.content))
            if args.embeds:
                predicates.append(args.embeds)
            if args.files:
                predicates.append(args.files)

            async for message in channel.history(limit=min(
                    max(1, args.search), 2000),
                                                 before=before,
                                                 after=after):
                if all(p(message) for p in predicates):
                    members.append(message.author)
        else:
            if ctx.guild.chunked:
                members = ctx.guild.members
            else:
                async with ctx.typing():
                    await ctx.guild.chunk(cache=True)
                members = ctx.guild.members

        # member filters
        predicates = [
            lambda m: isinstance(m, disnake.Member) and can_execute_action(
                ctx, author, m),  # Only if applicable
            lambda m: not m.bot,  # No bots
            lambda m: m.discriminator != '0000',  # No deleted users
        ]

        converter = commands.MemberConverter()

        if args.regex:
            try:
                _regex = re.compile(args.regex)
            except re.error as e:
                return await ctx.send(f'Invalid regex passed to `--regex`: {e}'
                                      )
            else:
                predicates.append(lambda m, x=_regex: x.match(m.name))

        if args.no_avatar:
            predicates.append(lambda m: m.avatar is None)
        if args.no_roles:
            predicates.append(lambda m: len(getattr(m, 'roles', [])) <= 1)

        now = disnake.utils.utcnow()
        if args.created:

            def created(member,
                        *,
                        offset=now - datetime.timedelta(minutes=args.created)):
                return member.created_at > offset

            predicates.append(created)
        if args.joined:

            def joined(member,
                       *,
                       offset=now - datetime.timedelta(minutes=args.joined)):
                if isinstance(member, disnake.User):
                    # If the member is a user then they left already
                    return True
                return member.joined_at and member.joined_at > offset

            predicates.append(joined)
        if args.joined_after:
            _joined_after_member = await converter.convert(
                ctx, str(args.joined_after))

            def joined_after(member, *, _other=_joined_after_member):
                return member.joined_at and _other.joined_at and member.joined_at > _other.joined_at

            predicates.append(joined_after)
        if args.joined_before:
            _joined_before_member = await converter.convert(
                ctx, str(args.joined_before))

            def joined_before(member, *, _other=_joined_before_member):
                return member.joined_at and _other.joined_at and member.joined_at < _other.joined_at

            predicates.append(joined_before)

        members = {m for m in members if all(p(m) for p in predicates)}
        if len(members) == 0:
            return await ctx.send('No members found matching criteria.')

        if args.show:
            members = sorted(members, key=lambda m: m.joined_at or now)
            fmt = "\n".join(
                f'{m.id}\tJoined: {m.joined_at}\tCreated: {m.created_at}\t{m}'
                for m in members)
            content = f'Current Time: {disnake.utils.utcnow()}\nTotal members: {len(members)}\n{fmt}'
            file = disnake.File(io.BytesIO(content.encode('utf-8')),
                                filename='members.txt')
            return await ctx.send(file=file)

        if args.reason is None:
            return await ctx.send('--reason flag is required.')
        else:
            reason = await ActionReason().convert(ctx, args.reason)

        confirm = await ctx.prompt(
            f'This will ban **{plural(len(members)):member}**. Are you sure?')
        if not confirm:
            return await ctx.send('Aborting.')

        count = 0
        for member in members:
            try:
                await ctx.guild.ban(member, reason=reason)
            except disnake.HTTPException:
                pass
            else:
                count += 1

        await ctx.send(f'Banned {count}/{len(members)}')
Exemple #13
0
    async def censor_invite(self, member, message_id, channel, code,
                            server_name, content, edit, reply, attachments):
        # Allow for users with a trusted role, or trusted users, to post invite links
        if Configuration.get_var(member.guild.id, "CENSORING",
                                 "ALLOW_TRUSTED_BYPASS"
                                 ) and Permissioncheckers.is_trusted(member):
            return

        e = '_edit' if edit else ''

        self.bot.deleted_messages.append(message_id)
        clean_message = await Utils.clean(content, member.guild)
        clean_name = Utils.clean_user(member)
        reply_str = ""
        if reply is not None:
            reply_str = f"\n**{Translator.translate('in_reply_to', member.guild.id)}: **<{assemble_jumplink(member.guild.id, channel.id, reply)}>"

        if attachments is None:
            attachments = await LoggedAttachment.filter(message=message_id)

        if len(attachments) > 0:
            attachments_str = f"**{Translator.translate('attachments', member.guild.id, count=len(attachments))}:** "
            attachments_str += ', '.join(
                Utils.assemble_attachment(
                    channel.id, attachment.id, attachment.filename if hasattr(
                        attachment, "filename") else attachment.name)
                for attachment in attachments)
        else:
            attachments_str = ""
        clean_message = Utils.trim_message(
            clean_message, 1600 - len(attachments_str) - len(reply_str))
        try:
            if channel.permissions_for(channel.guild.me).manage_messages:
                await channel.delete_messages([disnake.Object(message_id)])
                GearbotLogging.log_key(member.guild.id,
                                       f'censored_invite{e}',
                                       user=clean_name,
                                       code=code,
                                       message=clean_message,
                                       server_name=server_name,
                                       user_id=member.id,
                                       channel=channel.mention,
                                       attachments=attachments_str,
                                       reply=reply_str)
            else:
                GearbotLogging.log_key(member.guild.id,
                                       f'invite_censor_forbidden{e}',
                                       user=clean_name,
                                       code=code,
                                       message=clean_message,
                                       server_name=server_name,
                                       user_id=member.id,
                                       channel=channel.mention,
                                       attachments=attachments_str,
                                       reply=reply_str)
                if message_id in self.bot.deleted_messages:
                    self.bot.deleted_messages.remove(message_id)
        except disnake.NotFound:
            # we failed? guess we lost the race, log anyways
            GearbotLogging.log_key(member.guild.id,
                                   f'invite_censor_fail{e}',
                                   user=clean_name,
                                   code=code,
                                   message=clean_message,
                                   server_name=server_name,
                                   user_id=member.id,
                                   channel=channel.mention,
                                   attachments=attachments_str,
                                   reply=reply_str)
            if message_id in self.bot.deleted_messages:
                self.bot.deleted_messages.remove(message_id)

        self.bot.dispatch(
            "user_censored",
            messageholder(message_id, member, channel, channel.guild))
Exemple #14
0
    async def censor_message(self,
                             message_id,
                             content,
                             channel,
                             member,
                             bad,
                             key="",
                             edit=False,
                             reply="",
                             attachments=""):
        if Configuration.get_var(member.guild.id, "CENSORING",
                                 "ALLOW_TRUSTED_CENSOR_BYPASS"
                                 ) and Permissioncheckers.is_trusted(member):
            return
        e = '_edit' if edit else ''
        clean_message = await Utils.clean(content,
                                          channel.guild,
                                          markdown=False)
        reply_str = ""
        if reply is not None:
            reply_str = f"\n**{Translator.translate('in_reply_to', member.guild.id)}: **<{assemble_jumplink(member.guild.id, channel.id, reply)}>"

        if attachments is None:
            attachments = await LoggedAttachment.filter(message=message_id)

        if len(attachments) > 0:
            attachments_str = f"**{Translator.translate('attachments', member.guild.id, count=len(attachments))}:** "
            attachments_str += ', '.join(
                Utils.assemble_attachment(
                    channel.id, attachment.id, attachment.filename if hasattr(
                        attachment, "filename") else attachment.name)
                for attachment in attachments)
        else:
            attachments_str = ""
        clean_message = Utils.trim_message(
            clean_message, 1600 - len(attachments_str) - len(reply_str))
        p = channel.permissions_for(channel.guild.me)
        if p.manage_messages or p.administrator:
            try:
                self.bot.deleted_messages.append(message_id)
                await channel.delete_messages([disnake.Object(message_id)])
            except disnake.NotFound as ex:
                pass
            else:
                GearbotLogging.log_key(channel.guild.id,
                                       f'censored_message{key}{e}',
                                       user=member,
                                       user_id=member.id,
                                       message=clean_message,
                                       sequence=bad,
                                       channel=channel.mention,
                                       reply=reply_str,
                                       attachments=attachments_str)
        else:
            GearbotLogging.log_key(
                channel.guild.id,
                f'censored_message_failed{key}{e}',
                user=member,
                user_id=member.id,
                message=clean_message,
                sequence=bad,
                link='https://discord.com/channels/{0}/{1}/{2}'.format(
                    channel.guild.id, channel.id, message_id),
                reply=reply_str,
                attachments=attachments_str)
        self.bot.dispatch(
            "user_censored",
            messageholder(message_id, member, channel, channel.guild))