Beispiel #1
0
    async def _update(self, ctx, sub, *args):
        if sub == 'pfp':
            if not ctx.message.attachments:
                return await ctx.send(
                    ':warning: An attachment to change the picture to was not provided'
                )

            else:
                attachment = await ctx.message.attachments[0].read()
                await self.bot.user.edit(avatar=attachment)

            return await ctx.send('Done.')

        elif sub == 'name':
            username = ''
            for x in args:
                username += f'{x} '

            if len(username[:-1]) >= 32:
                return await ctx.send(':warning: That username is too long.')

            await self.bot.user.edit(username=username)

        elif sub == 'servermsgcache':
            funcStart = time.time()
            logging.info('[Core] Starting db message sync')
            await ctx.send(
                'Starting syncronization of db for all messages in server. This will take a conciderable amount of time.'
            )
            for channel in ctx.guild.channels:
                if channel.type != discord.ChannelType.text:
                    continue

                await ctx.send(f'Starting syncronization for <#{channel.id}>')

                try:
                    x, y = await self.store_message_cache(channel)
                    await ctx.send(
                        f'Syncronized <#{channel.id}>. Processed {x} messages and recorded meta data for {y} messages'
                    )

                except (discord.Forbidden, discord.HTTPException):
                    await ctx.send(f'Failed to syncronize <#{channel.id}>')

            timeToComplete = tools.humanize_duration(
                tools.resolve_duration(f'{int(time.time() - funcStart)}s'))
            return await ctx.send(
                f'<@{ctx.author.id}> Syncronization completed. Took {timeToComplete}'
            )

        else:
            return await ctx.send('Invalid sub command')
Beispiel #2
0
    async def on_member_join(self, member):
        db = mclient.bowser.users
        doc = db.find_one({'_id': member.id})
        roleList = []
        restored = False

        if not doc:
            await tools.store_user(member)
            doc = db.find_one({'_id': member.id})

        else:
            db.update_one(
                {'_id': member.id},
                {
                    '$push': {
                        'joins': (datetime.datetime.utcnow() - datetime.datetime.utcfromtimestamp(0)).total_seconds()
                    }
                },
            )

        new = (
            ':new: ' if (datetime.datetime.utcnow() - member.created_at).total_seconds() <= 60 * 60 * 24 * 14 else ''
        )  # Two weeks

        # log = f':inbox_tray: {new} User **{str(member)}** ({member.id}) joined'

        embed = discord.Embed(color=0x417505, timestamp=datetime.datetime.utcnow())
        embed.set_author(name=f'{member} ({member.id})', icon_url=member.avatar_url)
        created_at = member.created_at.strftime(f'{new}%B %d, %Y %H:%M:%S UTC')
        created_at += '' if not new else '\n' + tools.humanize_duration(member.created_at)
        embed.add_field(name='Created at', value=created_at)
        embed.add_field(name='Mention', value=f'<@{member.id}>')

        await self.serverLogs.send(':inbox_tray: User joined', embed=embed)

        if doc and doc['roles']:
            for x in doc['roles']:
                if x == member.guild.id:
                    continue

                restored = True
                role = member.guild.get_role(x)
                if role:
                    roleList.append(role)

            await member.edit(roles=roleList, reason='Automatic role restore action')

        if restored:
            # roleText = ', '.split(x.name for x in roleList)

            # logRestore = f':shield: Roles have been restored for returning member **{str(member)}** ({member.id}):\n{roleText}'
            punTypes = {
                'mute': 'Mute',
                'blacklist': 'Channel Blacklist ({})',
                'tier1': 'Tier 1 Warning',
                'tier2': 'Tier 2 Warning',
                'tier3': 'Tier 3 Warning',
            }
            puns = mclient.bowser.puns.find({'user': member.id, 'active': True})
            restoredPuns = []
            if puns.count():
                for x in puns:
                    if x['type'] == 'blacklist':
                        restoredPuns.append(punTypes[x['type']].format(x['context']))

                    elif x['type'] in ['kick', 'ban']:
                        continue  # These are not punishments being "restored", instead only status is being tracked

                    elif x['type'] == 'mute':
                        if (
                            x['expiry'] < time.time()
                        ):  # If the member is rejoining after mute has expired, the task has already quit. Restart it
                            mod = self.bot.get_cog('Moderation Commands')
                            await mod.expire_actions(x['_id'], member.guild.id)

                        restoredPuns.append(punTypes[x['type']])

                    else:
                        restoredPuns.append(punTypes[x['type']])

            embed = discord.Embed(color=0x4A90E2, timestamp=datetime.datetime.utcnow())
            embed.set_author(name=f'{member} ({member.id})', icon_url=member.avatar_url)
            embed.add_field(name='Restored roles', value=', '.join(x.name for x in roleList))
            if restoredPuns:
                embed.add_field(name='Restored punishments', value=', '.join(restoredPuns))
            embed.add_field(name='Mention', value=f'<@{member.id}>')
            await self.serverLogs.send(':shield: Member restored', embed=embed)

        if (
            'migrate_unnotified' in doc.keys() and doc['migrate_unnotified'] == True
        ):  # Migration of warnings to strikes for returning members
            for pun in mclient.bowser.puns.find(
                {'active': True, 'type': {'$in': ['tier1', 'tier2', 'tier3']}, 'user': member.id}
            ):  # Should only be one, it's mutually exclusive
                strikeCount = int(pun['type'][-1:]) * 4

                mclient.bowser.puns.update_one({'_id': pun['_id']}, {'$set': {'active': False}})
                docID = await tools.issue_pun(
                    member.id,
                    self.bot.user.id,
                    'strike',
                    f'[Migrated] {pun["reason"]}',
                    strike_count=strikeCount,
                    context='strike-migration',
                    public=False,
                )
                db.update_one(
                    {'_id': member.id},
                    {'$set': {'migrate_unnotified': False, 'strike_check': time.time() + (60 * 60 * 24 * 7)}},
                )  # Setting the next expiry check time
                mod = self.bot.get_cog('Moderation Commands')
                await mod.expire_actions(docID, member.guild.id)

                explanation = (
                    'Hello there **{}**,\nI am letting you know of a change in status for your active level {} warning issued on {}.\n\n'
                    'The **/r/NintendoSwitch** Discord server is moving to a strike-based system for infractions. Here is what you need to know:\n'
                    '\* Your warning level will be converted to **{}** strikes.\n'
                    '\* __Your strikes will decay at a equivalent rate as warnings previously did__. Each warning tier is equivalent to four strikes, where one strike decays once per week instead of one warn level per four weeks\n'
                    '\* You will no longer have any permission restrictions you previously had with this warning. Moderators will instead restrict features as needed to enforce the rules on a case-by-case basis.\n\n'
                    'Strikes will allow the moderation team to weigh rule-breaking behavior better and serve as a reminder to users who may need to review our rules. You may also now view your infraction history '
                    'by using the `!history` command in <#{}>. Please feel free to send a modmail to @Parakarry (<@{}>) if you have any questions or concerns.'
                ).format(
                    str(member),  # Username
                    pun['type'][-1:],  # Tier type
                    datetime.datetime.utcfromtimestamp(pun['timestamp']).strftime('%B %d, %Y'),  # Date of warn
                    strikeCount,  # How many strikes will replace tier,
                    config.commandsChannel,  # Commands channel can only be used for the command
                    config.parakarry,  # Parakarry mention for DM
                )

                try:
                    await member.send(explanation)

                except:
                    pass
Beispiel #3
0
    def __init__(self, bot):
        self.bot = bot
        self.serverLogs = self.bot.get_channel(config.logChannel)
        self.modLogs = self.bot.get_channel(config.modChannel)
        self.publicModLogs = self.bot.get_channel(config.publicModChannel)
        self.taskHandles = []
        self.NS = self.bot.get_guild(config.nintendoswitch)
        self.roles = {'mute': self.NS.get_role(config.mute)}

        # Publish all unposted/pending public modlogs on cog load
        db = mclient.bowser.puns
        pendingLogs = db.find({
            'public': True,
            'public_log_message': None,
            'type': {
                '$ne': 'note'
            }
        })
        loop = bot.loop
        for log in pendingLogs:
            if log['type'] == 'mute':
                expires = tools.humanize_duration(
                    datetime.datetime.utcfromtimestamp(log['expiry']))

            else:
                expires = None

            loop.create_task(
                tools.send_public_modlog(bot, log['_id'], self.publicModLogs,
                                         expires))

        # Run expiration tasks
        userDB = mclient.bowser.users
        pendingPuns = db.find({
            'active': True,
            'type': {
                '$in': ['strike', 'mute']
            }
        })
        twelveHr = 60 * 60 * 12
        trackedStrikes = []  # List of unique users
        for pun in pendingPuns:
            if pun['type'] == 'strike':
                if pun['user'] in trackedStrikes:
                    continue  # We don't want to create many tasks when we only remove one
                user = userDB.find_one({'_id': pun['user']})
                trackedStrikes.append(pun['user'])
                if user['strike_check'] > time.time():  # In the future
                    tryTime = (twelveHr
                               if user['strike_check'] - time.time() > twelveHr
                               else user['strike_check'] - time.time())
                    self.taskHandles.append(
                        self.bot.loop.call_later(
                            tryTime, asyncio.create_task,
                            self.expire_actions(pun['_id'],
                                                config.nintendoswitch)))

                else:  # In the past
                    self.taskHandles.append(
                        self.bot.loop.call_soon(
                            asyncio.create_task,
                            self.expire_actions(pun['_id'],
                                                config.nintendoswitch)))

            elif pun['type'] == 'mute':
                tryTime = twelveHr if pun['expiry'] - time.time(
                ) > twelveHr else pun['expiry'] - time.time()
                logging.info(f'using {tryTime} for mute')
                self.taskHandles.append(
                    self.bot.loop.call_later(
                        tryTime, asyncio.create_task,
                        self.expire_actions(pun['_id'],
                                            config.nintendoswitch)))
Beispiel #4
0
    async def _muting(self,
                      ctx,
                      member: discord.Member,
                      duration,
                      *,
                      reason='-No reason specified-'):
        if len(reason) > 990:
            return await ctx.send(
                f'{config.redTick} Mute reason is too long, reduce it by at least {len(reason) - 990} characters'
            )
        db = mclient.bowser.puns
        if db.find_one({'user': member.id, 'type': 'mute', 'active': True}):
            return await ctx.send(
                f'{config.redTick} {member} ({member.id}) is already muted')

        muteRole = ctx.guild.get_role(config.mute)
        try:
            _duration = tools.resolve_duration(duration)
            try:
                if int(duration):
                    raise TypeError

            except ValueError:
                pass

        except (KeyError, TypeError):
            return await ctx.send(f'{config.redTick} Invalid duration passed')

        docID = await tools.issue_pun(member.id, ctx.author.id, 'mute', reason,
                                      int(_duration.timestamp()))
        await member.add_roles(muteRole,
                               reason='Mute action performed by moderator')
        await tools.send_modlog(
            self.bot,
            self.modLogs,
            'mute',
            docID,
            reason,
            user=member,
            moderator=ctx.author,
            expires=
            f'{_duration.strftime("%B %d, %Y %H:%M:%S UTC")} ({tools.humanize_duration(_duration)})',
            public=True,
        )
        error = ""
        try:
            await member.send(
                tools.format_pundm('mute', reason, ctx.author,
                                   tools.humanize_duration(_duration)))

        except (discord.Forbidden, AttributeError):
            error = '. I was not able to DM them about this action'

        if not tools.mod_cmd_invoke_delete(ctx.channel):
            await ctx.send(
                f'{config.greenTick} {member} ({member.id}) has been successfully muted{error}'
            )

        twelveHr = 60 * 60 * 12
        expireTime = time.mktime(_duration.timetuple())
        logging.info(f'using {expireTime}')
        tryTime = twelveHr if expireTime - time.time(
        ) > twelveHr else expireTime - time.time()
        self.taskHandles.append(
            self.bot.loop.call_later(tryTime, asyncio.create_task,
                                     self.expire_actions(docID, ctx.guild.id)))
        if tools.mod_cmd_invoke_delete(ctx.channel):
            return await ctx.message.delete()
Beispiel #5
0
    async def _infraction_editing(self,
                                  ctx,
                                  infraction,
                                  reason,
                                  duration=None):
        db = mclient.bowser.puns
        doc = db.find_one({'_id': infraction})
        if not doc:
            return await ctx.send(
                f'{config.redTick} An invalid infraction id was provided')

        if not doc['active'] and duration:
            return await ctx.send(
                f'{config.redTick} That infraction has already expired and the duration cannot be edited'
            )

        if duration and doc[
                'type'] != 'mute':  # TODO: Should we support strikes in the future?
            return ctx.send(
                f'{config.redTick} Setting durations is not supported for {doc["type"]}'
            )

        user = await self.bot.fetch_user(doc['user'])
        if duration:
            try:
                _duration = tools.resolve_duration(duration)
                humanized = tools.humanize_duration(_duration)
                expireStr = f'{_duration.strftime("%B %d, %Y %H:%M:%S UTC")} ({humanized})'
                stamp = _duration.timestamp()
                try:
                    if int(duration):
                        raise TypeError

                except ValueError:
                    pass

            except (KeyError, TypeError):
                return await ctx.send(
                    f'{config.redTick} Invalid duration passed')

            if stamp - time.time() < 60:  # Less than a minute
                return await ctx.send(
                    f'{config.redTick} Cannot set the new duration to be less than one minute'
                )

            db.update_one({'_id': infraction},
                          {'$set': {
                              'expiry': int(stamp)
                          }})
            await tools.send_modlog(
                self.bot,
                self.modLogs,
                'duration-update',
                doc['_id'],
                reason,
                user=user,
                moderator=ctx.author,
                expires=expireStr,
                extra_author=doc['type'].capitalize(),
            )

        else:
            db.update_one({'_id': infraction}, {'$set': {'reason': reason}})
            await tools.send_modlog(
                self.bot,
                self.modLogs,
                'reason-update',
                doc['_id'],
                reason,
                user=user,
                moderator=ctx.author,
                extra_author=doc['type'].capitalize(),
                updated=doc['reason'],
            )

        try:
            pubChannel = self.bot.get_channel(doc['public_log_channel'])
            pubMessage = await pubChannel.fetch_message(
                doc['public_log_message'])
            embed = pubMessage.embeds[0]
            embedDict = embed.to_dict()
            newEmbedDict = copy.deepcopy(embedDict)
            listIndex = 0
            for field in embedDict['fields']:
                # We are working with the dict because some logs can have `reason` at different indexes and we should not assume index position
                if duration and field['name'] == 'Expires':
                    # This is subject to a breaking change if `name` updated, but I'll take the risk
                    newEmbedDict['fields'][listIndex]['value'] = expireStr
                    break

                elif not duration and field['name'] == 'Reason':
                    newEmbedDict['fields'][listIndex]['value'] = reason
                    break

                listIndex += 1

            assert (
                embedDict['fields'] != newEmbedDict['fields']
            )  # Will fail if message was unchanged, this is likely because of a breaking change upstream in the pun flow
            newEmbed = discord.Embed.from_dict(newEmbedDict)
            await pubMessage.edit(embed=newEmbed)

        except Exception as e:
            logging.error(f'[Moderation] _infraction_duration: {e}')

        error = ''
        try:
            member = await ctx.guild.fetch_member(doc['user'])
            if duration:
                await member.send(
                    tools.format_pundm('duration-update',
                                       reason,
                                       details=(doc['type'], expireStr)))

            else:
                await member.send(
                    tools.format_pundm(
                        'reason-update',
                        reason,
                        details=(
                            doc['type'],
                            datetime.datetime.utcfromtimestamp(
                                doc['timestamp']).strftime(
                                    "%B %d, %Y %H:%M:%S UTC"),
                        ),
                    ))

        except (discord.NotFound, discord.Forbidden, AttributeError):
            error = '. I was not able to DM them about this action'

        await ctx.send(
            f'{config.greenTick} The {doc["type"]} {"duration" if duration else "reason"} has been successfully updated for {user} ({user.id}){error}'
        )