async def _unmuting( self, ctx, member: discord.Member, *, reason='-No reason specified-' ): # TODO: Allow IDs to be unmuted (in the case of not being in the guild) if len(reason) > 990: return await ctx.send( f'{config.redTick} Unmute reason is too long, reduce it by at least {len(reason) - 990} characters' ) db = mclient.bowser.puns muteRole = ctx.guild.get_role(config.mute) action = db.find_one_and_update( { 'user': member.id, 'type': 'mute', 'active': True }, {'$set': { 'active': False }}) if not action: return await ctx.send( f'{config.redTick} Cannot unmute {member} ({member.id}), they are not currently muted' ) docID = await tools.issue_pun(member.id, ctx.author.id, 'unmute', reason, context=action['_id'], active=False) await member.remove_roles( muteRole, reason='Unmute action performed by moderator') await tools.send_modlog(self.bot, self.modLogs, 'unmute', docID, reason, user=member, moderator=ctx.author, public=True) try: await member.send(tools.format_pundm('unmute', reason, ctx.author)) except (discord.Forbidden, AttributeError): if not tools.mod_cmd_invoke_delete(ctx.channel): await ctx.send( f'{config.greenTick} {member} ({member.id}) has been successfully unmuted. I was not able to DM them about this action' ) return if tools.mod_cmd_invoke_delete(ctx.channel): return await ctx.message.delete() await ctx.send( f'{config.greenTick} {member} ({member.id}) has been successfully unmuted' )
async def _unbanning(self, ctx, user: int, *, reason='-No reason specified-'): if len(reason) > 990: return await ctx.send( f'{config.redTick} Unban reason is too long, reduce it by at least {len(reason) - 990} characters' ) db = mclient.bowser.puns userObj = discord.Object(id=user) try: await ctx.guild.fetch_ban(userObj) except discord.NotFound: return await ctx.send( f'{config.redTick} {user} is not currently banned') openAppeal = mclient.modmail.logs.find_one({ 'open': True, 'ban_appeal': True, 'recipient.id': str(user) }) if openAppeal: return await ctx.send( f'{config.redTick} You cannot use the unban command on {user} while a ban appeal is in-progress. You can accept the appeal in <#{int(openAppeal["channel_id"])}> with `!appeal accept [reason]`' ) db.find_one_and_update({ 'user': user, 'type': 'ban', 'active': True }, {'$set': { 'active': False }}) docID = await tools.issue_pun(user, ctx.author.id, 'unban', reason, active=False) await ctx.guild.unban(userObj, reason='Unban action performed by moderator') await tools.send_modlog( self.bot, self.modLogs, 'unban', docID, reason, username=str(user), userid=user, moderator=ctx.author, public=True, ) if tools.mod_cmd_invoke_delete(ctx.channel): return await ctx.message.delete() await ctx.send(f'{config.greenTick} {user} has been unbanned')
async def _kicking(self, ctx, member: discord.Member, *, reason='-No reason specified-'): if len(reason) > 990: return await ctx.send( f'{config.redTick} Kick reason is too long, reduce it by at least {len(reason) - 990} characters' ) docID = await tools.issue_pun(member.id, ctx.author.id, 'kick', reason, active=False) await tools.send_modlog(self.bot, self.modLogs, 'kick', docID, reason, user=member, moderator=ctx.author, public=True) try: await member.send(tools.format_pundm('kick', reason, ctx.author)) except (discord.Forbidden, AttributeError): if not tools.mod_cmd_invoke_delete(ctx.channel): await ctx.send( f'{config.greenTick} {member} ({member.id}) has been successfully kicked. I was not able to DM them about this action' ) await member.kick(reason='Kick action performed by moderator') return if tools.mod_cmd_invoke_delete(ctx.channel): return await ctx.message.delete() await ctx.send( f'{config.greenTick} {member} ({member.id}) has been successfully kicked' )
async def _note(self, ctx, user: ResolveUser, *, content): userid = user if (type(user) is int) else user.id if len(content) > 900: return await ctx.send( f'{config.redTick} Note is too long, reduce it by at least {len(content) - 990} characters' ) await tools.issue_pun(userid, ctx.author.id, 'note', content, active=False, public=False) if tools.mod_cmd_invoke_delete(ctx.channel): return await ctx.message.delete() return await ctx.send( f'{config.greenTick} Note successfully added to {user} ({user.id})' )
async def _strike_set(self, ctx, member: discord.Member, count: StrikeRange, *, reason): punDB = mclient.bowser.puns activeStrikes = 0 puns = punDB.find({ 'user': member.id, 'type': 'strike', 'active': True }) for pun in puns: activeStrikes += pun['active_strike_count'] if activeStrikes == count: return await ctx.send( f'{config.redTick} That user already has {activeStrikes} active strikes' ) elif ( count > activeStrikes ): # This is going to be a positive diff, lets just do the math and defer work to _strike() return await self._strike(ctx, member, count - activeStrikes, reason=reason) else: # Negative diff, we will need to reduce our strikes diff = activeStrikes - count puns = punDB.find({ 'user': member.id, 'type': 'strike', 'active': True }).sort('timestamp', 1) for pun in puns: if pun['active_strike_count'] - diff >= 0: userDB = mclient.bowser.users punDB.update_one( {'_id': pun['_id']}, { '$set': { 'active_strike_count': pun['active_strike_count'] - diff, 'active': pun['active_strike_count'] - diff > 0, } }, ) userDB.update_one({'_id': member.id}, { '$set': { 'strike_check': time.time() + (60 * 60 * 24 * 7) } }) self.taskHandles.append( self.bot.loop.call_later( 60 * 60 * 12, asyncio.create_task, self.expire_actions(pun['_id'], ctx.guild.id)) ) # Check in 12 hours, prevents time drifting # Logic to calculate the remaining (diff) strikes will simplify to 0 # new_diff = diff - removed_strikes # = diff - (old_strike_amount - new_strike_amount) # = diff - (old_strike_amount - (old_strike_amount - diff)) # = diff - old_strike_amount + old_strike_amount - diff # = 0 diff = 0 break elif pun['active_strike_count'] - diff < 0: punDB.update_one( {'_id': pun['_id']}, {'$set': { 'active_strike_count': 0, 'active': False }}) diff -= pun['active_strike_count'] if diff != 0: # Something has gone horribly wrong raise ValueError('Diff != 0 after full iteration') docID = await tools.issue_pun(member.id, ctx.author.id, 'destrike', reason=reason, active=False, strike_count=activeStrikes - count) await tools.send_modlog( self.bot, self.modLogs, 'destrike', docID, reason, user=member, moderator=ctx.author, extra_author=(activeStrikes - count), public=True, ) error = "" try: await member.send( tools.format_pundm('destrike', reason, ctx.author, details=activeStrikes - count)) except discord.Forbidden: error = 'I was not able to DM them about this action' if tools.mod_cmd_invoke_delete(ctx.channel): return await ctx.message.delete() await ctx.send( f'{member} ({member.id}) has had {activeStrikes - count} strikes removed, ' f'they now have {activeStrikes} strike{"s" if activeStrikes > 1 else ""} ' f'({activeStrikes+count} - {count}) {error}')
async def _strike(self, ctx, member: discord.Member, count: typing.Optional[StrikeRange] = 1, *, reason): if count == 0: return await ctx.send( f'{config.redTick} You cannot issue less than one strike. If you need to reset this user\'s strikes to zero instead use `{ctx.prefix}strike set`' ) if len(reason) > 990: return await ctx.send( f'{config.redTick} Strike reason is too long, reduce it by at least {len(reason) - 990} characters' ) punDB = mclient.bowser.puns userDB = mclient.bowser.users activeStrikes = 0 for pun in punDB.find({ 'user': member.id, 'type': 'strike', 'active': True }): activeStrikes += pun['active_strike_count'] activeStrikes += count if activeStrikes > 16: # Max of 16 active strikes return await ctx.send( f'{config.redTick} Striking {count} time{"s" if count > 1 else ""} would exceed the maximum of 16 strikes. The amount being issued must be lowered by at least {activeStrikes - 16} or consider banning the user instead' ) docID = await tools.issue_pun(member.id, ctx.author.id, 'strike', reason, strike_count=count, public=True) userDB.update_one( {'_id': member.id}, {'$set': { 'strike_check': time.time() + (60 * 60 * 24 * 7) }}) # 7 days self.taskHandles.append( self.bot.loop.call_later(60 * 60 * 12, asyncio.create_task, self.expire_actions(docID, ctx.guild.id)) ) # Check in 12 hours, prevents time drifting await tools.send_modlog( self.bot, self.modLogs, 'strike', docID, reason, user=member, moderator=ctx.author, extra_author=count, public=True, ) content = ( f'{config.greenTick} {member} ({member.id}) has been successfully struck, ' f'they now have {activeStrikes} strike{"s" if activeStrikes > 1 else ""} ({activeStrikes-count} + {count})' ) try: await member.send( tools.format_pundm('strike', reason, ctx.author, details=count)) except discord.Forbidden: if not tools.mod_cmd_invoke_delete(ctx.channel): content += '. I was not able to DM them about this action' if activeStrikes == 16: content += '.\n:exclamation: You may want to consider a ban' await ctx.send(content) return if tools.mod_cmd_invoke_delete(ctx.channel): return await ctx.message.delete() if activeStrikes == 16: content += '.\n:exclamation: You may want to consider a ban' await ctx.send(content)
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()
async def _banning(self, ctx, users: commands.Greedy[ResolveUser], *, reason='-No reason specified-'): if len(reason) > 990: return await ctx.send( f'{config.redTick} Ban reason is too long, reduce it by at least {len(reason) - 990} characters' ) if not users: return await ctx.send( f'{config.redTick} An invalid user was provided') banCount = 0 failedBans = 0 for user in users: userid = user if (type(user) is int) else user.id username = userid if (type(user) is int) else f'{str(user)}' user = (discord.Object(id=userid) if (type(user) is int) else user ) # If not a user, manually contruct a user object try: await ctx.guild.fetch_ban(user) if len(users) == 1: return await ctx.send( f'{config.redTick} {username} is already banned') else: # If a many-user ban, don't exit if a user is already banned failedBans += 1 continue except discord.NotFound: pass try: await user.send(tools.format_pundm('ban', reason, ctx.author)) except (discord.Forbidden, AttributeError): pass try: await ctx.guild.ban( user, reason=f'Ban action performed by moderator', delete_message_days=3) except discord.NotFound: # User does not exist if len(users) == 1: return await ctx.send( f'{config.redTick} User {userid} does not exist') failedBans += 1 continue docID = await tools.issue_pun(userid, ctx.author.id, 'ban', reason=reason) await tools.send_modlog( self.bot, self.modLogs, 'ban', docID, reason, username=username, userid=userid, moderator=ctx.author, public=True, ) banCount += 1 if tools.mod_cmd_invoke_delete(ctx.channel): return await ctx.message.delete() if len(users) == 1: await ctx.send( f'{config.greenTick} {users[0]} has been successfully banned') else: resp = f'{config.greenTick} **{banCount}** users have been successfully banned' if failedBans: resp += f'. Failed to ban **{failedBans}** from the provided list' return await ctx.send(resp)
async def _kicking(self, ctx, users: commands.Greedy[ResolveUser], *, reason='-No reason specified-'): if len(reason) > 990: return await ctx.send( f'{config.redTick} Kick reason is too long, reduce it by at least {len(reason) - 990} characters' ) if not users: return await ctx.send( f'{config.redTick} An invalid user was provided') kickCount = 0 failedKicks = 0 couldNotDM = False for user in users: userid = user if (type(user) is int) else user.id username = userid if (type(user) is int) else f'{str(user)}' user = (discord.Object(id=userid) if (type(user) is int) else user ) # If not a user, manually contruct a user object try: member = await ctx.guild.fetch_member(userid) except discord.HTTPException: # Member not in guild if len(users) == 1: return await ctx.send( f'{config.redTick} {username} is not the server!') else: # If a many-user kick, don't exit if a user is already gone failedKicks += 1 continue usr_role_pos = member.top_role.position if (usr_role_pos >= ctx.guild.me.top_role.position) or ( usr_role_pos >= ctx.author.top_role.position): if len(users) == 1: return await ctx.send( f'{config.redTick} Insufficent permissions to kick {username}' ) else: failedKicks += 1 continue try: await user.send(tools.format_pundm('kick', reason, ctx.author)) except (discord.Forbidden, AttributeError): couldNotDM = True pass try: await member.kick(reason='Kick action performed by moderator') except (discord.Forbidden): failedKicks += 1 continue docID = await tools.issue_pun(member.id, ctx.author.id, 'kick', reason, active=False) await tools.send_modlog(self.bot, self.modLogs, 'kick', docID, reason, user=member, moderator=ctx.author, public=True) kickCount += 1 if tools.mod_cmd_invoke_delete(ctx.channel): return await ctx.message.delete() if ctx.author.id != self.bot.user.id: # Non-command invoke, such as automod if len(users) == 1: resp = f'{config.greenTick} {users[0]} has been successfully kicked' if couldNotDM: resp += '. I was not able to DM them about this action' else: resp = f'{config.greenTick} **{kickCount}** users have been successfully kicked' if failedKicks: resp += f'. Failed to kick **{failedKicks}** from the provided list' return await ctx.send(resp)