async def gamerword_debug(ctx, target): if not common.isDiscordRegulator(ctx.message.author): return target_id = discord.get_targeted_id(ctx) # get the forgiveness value and get how far back we should look cutoff = int(time.time() - config.cfg['automod']['forgiveness_days'] * 86400) database.cursor.execute( 'SELECT score_final,score_regex,score_prev_infractions,multiply_age_member,multiply_age_account,action from automod_gamerwords WHERE id_targeted=%s AND time>%s', (target_id, cutoff)) dbres = database.cursor.fetchall() if len(dbres) == 0: await discord.bot.say( "{0.message.author.mention} no actions found in the last {1[automod][forgiveness_days]} days." .format(ctx, config.cfg)) else: table_result = common.dbtable_to_strtable(dbres) if table_result: await discord.bot.say( "Last {0[automod][forgiveness_days]} days of automoderation against ID:{1} ```{2}```" .format(config.cfg, target_id, table_result)) else: await discord.bot.say( "{0.message.author.mention} unable to retrieve table, result too big or something broke." .format(ctx))
async def unsilence(ctx, target): if common.isDiscordVoiceRegulator(ctx.message.author) != True: return target_id = discord.get_targeted_id(ctx) target_member = discord.default_server.get_member(target_id) if allow_regulation(ctx, target_id) is False: await discord.bot.say( "тЪая╕П{} Permissions denied to regulate user".format( ctx.message.author.mention)) return if target_member is not None: await discord.bot.server_voice_state(target_member, mute=0) journal.update_journal_regulator(invoker=ctx.message.author.id, target=target_id, eventdata="None", action="VOICE_UNSILENCE", messageid=ctx.message.id) await discord.bot.say( "тЬФя╕П {} was unsilenced by {} in voice chat!".format( target_member.mention, ctx.message.author.mention)) else: await discord.bot.say( "ЁЯЫС {} unable to moderate user. Target did not resolve to a valid Discord Member." .format(ctx.message.author.mention))
async def cleanup(ctx, target=None, history=100): start = analytics.getEventTime() if common.isDiscordRegulator(ctx.message.author) != True: return #make user input something usable first, conform outlandish commands to be sane if target is None and history is None: await discord.bot.say("Syntax: `!cleanup @mention <1-1000>` where # is last x chat messages to check per channel. Defaults to 100.") return elif target is None: await discord.bot.say("Error: You must target a user by `@mention` or raw ID.") return elif history > 1000: await discord.bot.say("Warning: This command only supports up to the last 1000 channel messages. Limiting your request to 1000.") history = 1000 else: pass #get the raw mention or just pass the ID depending on what the use supplied. member only needed for protection target_id = discord.get_targeted_id(ctx) target_member = discord.default_server.get_member(target_id) # Only use the Member lookup for Reg/Admin protection #if xiphirx = soy if target_id == discord.bot.user.id: await discord.bot.say("⚠️{0.message.author.mention} you are not allowed to cleanup after the bot!".format(ctx)) return #dont allow this to be called on regulators/administrators if target_member is not None: if common.isDiscordRegulator(target_member): logger.warn("cleanup() called and is exiting: ID:{0.message.author.id} attempted to call cleanup on ID:{1} but was denied by: IS_REGULATOR_PROTECTED".format(ctx, target_id)) await discord.bot.say("⚠️{0.message.author.mention} you are not allowed to call that on other admins/regulators!".format(ctx)) return #while this runs lets not flood the mod channel with messages deleted.IGNORE_IDS.append(target_id) await discord.bot.say("{0.message.author.mention} starting now ID:`{1}` and will @ you when task completed.".format(ctx, target_id)) logger.info("cleanup() {0.message.author.name} started deletion on ID:{1}. Checking all channels last {2} messages.".format(ctx, target_id, history)) for channel in ctx.message.server.channels: if channel.type == discord.discord.ChannelType.text: logger.info("cleanup() {0.message.author.name} started deletion on channel ID: {0.message.channel.id} for user ID:{1}".format(ctx, target_id)) await delete_messages_from_channel(ctx, channel, target_id, history) #no need to keep the id around deleted.IGNORE_IDS.remove(target_id) #create both a journal entry for the user targeted journal.update_journal_event(module=__name__, event="MESSAGE_CLEANUP_MODERATOR", userid=target_id, contents="Invoked by: {0.author.name} in channel {0.channel}".format(ctx.message)) journal.update_journal_regulator(invoker=ctx.message.author.id, target=target_id, eventdata=history, action="MESSAGE_CLEANUP_MODERATOR", messageid=ctx.message.id) secs_taken = int(analytics.getEventTime() - start) await discord.bot.say("{0.message.author.mention} cleanup on ID `{1}` completed! Took ~{2} seconds".format(ctx, target_id, secs_taken))
async def warn(ctx, target, *, reason): if common.isDiscordRegulator(ctx.message.author) != True: return # convert the target into a usable ID target_id = discord.get_targeted_id(ctx) target_member = await discord.get_user_info(target_id) if target_member is None: await discord.bot.say( "{} тЪая╕П I was not able to lookup that user ID.".format( ctx.message.author.mention)) return # warn reason has to have at least a word in it if len(reason) < 4: await discord.bot.say( "тЪая╕П Warning reason must be longer than 4 characters. `!warn @username reason goes here`" ) return # warn reason has to be a reasonable length if len(reason) > 200: await discord.bot.say( "тЪая╕П Reason too long - please limit to 200 characters or less.") return if allow_regulation(ctx, target_id): # return what is happening in the same channel to alert the user await discord.bot.say( "тЬФя╕П {} is warning {} with the reason of `{}`.".format( ctx.message.author.mention, target_member.mention, reason)) # update the internal bot log journal.update_journal_regulator(invoker=ctx.message.author.id, target=target_id, eventdata=reason, action="WARN_MEMBER", messageid=ctx.message.id) # update the internal user log journal.update_journal_event(module=__name__, event="ON_MEMBER_WARN", userid=target_id, contents="{0.name}#{0.discriminator} " "warned by {1.name}#{1.discriminator} " "with reason '{2}'".format( target_member, ctx.message.author, reason)) else: await discord.bot.say( "ЁЯЫС {} unable to moderate user. Either you failed to tag the user properly or the member is protected from regulators." .format(ctx.message.author.mention))
async def kick(ctx, target, *, reason): if common.isDiscordRegulator(ctx.message.author) != True: return #convert the target into a usable ID target_id = discord.get_targeted_id(ctx) target_member = discord.default_server.get_member(target_id) if target_member is None: await discord.bot.say( "{} тЪая╕П I was not able to lookup that user ID.".format( ctx.message.author.mention)) return #kick reason has to have at least a word in it if len(reason) < 4: await discord.bot.say( "тЪая╕П Kick reason must be longer than 4 characters. `!kick @username reason goes here`" ) return # kick reason has to be a reasonable length if len(reason) > 200: await discord.bot.say( "тЪая╕П Reason too long - please limit to 200 characters or less.") return if allow_regulation(ctx, target_id): #return what is happening in the same channel to alert the user, wait 5 seconds and fire the kick command await discord.bot.say( "тЬФя╕П {} is kicking {} with the reason of `{}`.".format( ctx.message.author.mention, target_member.mention, reason)) await asyncio.sleep(5) await discord.bot.kick(target_member) #update the internal bot log, since we cant send kick reasons via this api version #TODO: in rewrite add that journal.update_journal_regulator(invoker=ctx.message.author.id, target=target_id, eventdata=reason, action="KICK_MEMBER", messageid=ctx.message.id) else: await discord.bot.say( "ЁЯЫС {} unable to moderate user. Either you failed to tag the user properly or the member is protected from regulators." .format(ctx.message.author.mention))
async def silence(ctx, target, *, reason): if common.isDiscordVoiceRegulator(ctx.message.author) != True: return target_id = discord.get_targeted_id(ctx) target_member = discord.default_server.get_member(target_id) # silence reason has to have at least a word in it if len(reason) < 4: await discord.bot.say( "тЪая╕П Silence reason must be longer than 4 characters. `!silence @username reason goes here`" ) return # silence reason has to have at least a word in it if len(reason) > 200: await discord.bot.say( "тЪая╕П Reason too long - please limit to 200 characters or less.") return if allow_regulation(ctx, target_id) is False: await discord.bot.say( "тЪая╕П{} Permissions denied to regulate user".format( ctx.message.author.mention)) return if target_member is not None: await discord.bot.server_voice_state(target_member, mute=1) journal.update_journal_regulator(invoker=ctx.message.author.id, target=target_id, eventdata=reason, action="VOICE_SILENCE", messageid=ctx.message.id) await discord.bot.say( "тЬФя╕П {} was silenced by {} in voice chat".format( target_member.mention, ctx.message.author.mention)) else: await discord.bot.say( "ЁЯЫС {} unable to moderate user. Target did not resolve to a valid Discord Member." .format(ctx.message.author.mention))
async def journal(ctx, user: str): if common.isDiscordRegulator(ctx.message.author) is False: return target_id = discord.get_targeted_id(ctx) target_member = await discord.get_user_info(target_id) if target_id is None: database.cursor.execute( 'SELECT * from journal_events WHERE userid=%s ORDER BY time DESC LIMIT 5', (target_id, )) emd = discord.embeds.Embed( title='Last 5 events for ({0})'.format(user), color=0xE79015) else: database.cursor.execute( 'SELECT * from journal_events WHERE userid=%s ORDER BY time DESC LIMIT 5', (target_id, )) emd = discord.embeds.Embed( title='Last 5 events for "{0.name}" ({0.id})'.format( target_member), color=0xE79015) dbres = database.cursor.fetchall() if len(dbres) == 0: await discord.bot.say("Not found in my journal :(") return else: for row in dbres: emd.add_field( inline=False, name="{}".format( datetime.fromtimestamp(float(row['time'])).isoformat()), value="{0[event]} ({0[module]}) Result: {0[contents]}\n". format(row)) await discord.bot.say(embed=emd)
async def rapsheet(ctx): if common.isDiscordRegulator(ctx.message.author) is False: return # Get the member target_id = discord.get_targeted_id(ctx) target_member = await discord.get_user_info(target_id) # Pull all their regulator actions, ignoring un-silence events. database.cursor.execute( 'SELECT * from journal_regulators WHERE id_targeted=%s AND NOT action = \'VOICE_UNSILENCE\' ORDER BY time DESC', (target_id, )) dbres = database.cursor.fetchall() # Get the first event in the logs for the user as a "first seen" date database.cursor.execute( 'SELECT MIN(time) AS \'time\' FROM journal_events WHERE userid = %s', (target_id, )) first_seen = database.cursor.fetchall() # If no events are returned, generate and send a clean rapsheet if len(dbres) == 0: emd = discord.embeds.Embed(title="__Moderation Statistics__", colour=discord.discord.Color.green(), timestamp=datetime.utcnow()) emd.set_author(name="{0} (ID: {1})".format(target_member.name, target_member.id), icon_url=target_member.avatar_url) emd.set_thumbnail(url=target_member.avatar_url) if first_seen[0]['time'] is None: emd.add_field( name="No previous moderation actions found for user.", value="First Seen - Unknown") else: emd.add_field( name="No previous moderation actions found for user.", value="First Seen - {0}".format( datetime.fromtimestamp(first_seen[0]['time']).strftime( '%Y-%m-%d @ %H:%M:%S'))) await discord.bot.say(embed=emd) return # Otherwise, total up all the events, generate, and send a dirty rapsheet else: emd = discord.embeds.Embed(title="__Moderation Statistics__", colour=discord.discord.Color.red(), timestamp=datetime.utcnow()) emd.set_author(name="{0} (ID: {1})".format(target_member.name, target_member.id), icon_url=target_member.avatar_url) emd.set_thumbnail(url=target_member.avatar_url) warnCount, muteCount, kickCount, banCount = 0, 0, 0, 0 warns, mutes, kicks, bans = "", "", "", "" for row in dbres: invoker = await discord.get_user_info(row['id_invoker']) if row['action'] == "WARN_MEMBER": warnCount += 1 warns += "{0} - {1} --- *{2}*\n".format( datetime.fromtimestamp(float( row['time'])).date().isoformat(), invoker.mention, row['id_message']) elif row['action'] == "VOICE_SILENCE": muteCount += 1 mutes += "{0} - {1} --- *{2}*\n".format( datetime.fromtimestamp(float( row['time'])).date().isoformat(), invoker.mention, row['id_message']) elif row['action'] == "KICK_MEMBER": kickCount += 1 kicks += "{0} - {1} --- *{2}*\n".format( datetime.fromtimestamp(float( row['time'])).date().isoformat(), invoker.mention, row['id_message']) elif row['action'] == "BAN_MEMBER": banCount += 1 bans += "{0} - {1} --- *{2}*\n".format( datetime.fromtimestamp(float( row['time'])).date().isoformat(), invoker.mention, row['id_message']) if warns == "": emd.add_field(name="Warnings: {0}".format(warnCount), value="N/A", inline=False) else: emd.add_field(name="Warnings: {0}".format(warnCount), value=warns, inline=False) if mutes == "": emd.add_field(name="Mutes: {0}".format(muteCount), value="N/A", inline=False) else: emd.add_field(name="Mutes: {0}".format(muteCount), value=mutes, inline=False) if kicks == "": emd.add_field(name="Kicks: {0}".format(kickCount), value="N/A", inline=False) else: emd.add_field(name="Kicks: {0}".format(kickCount), value=kicks, inline=False) if bans == "": emd.add_field(name="Bans: {0}".format(banCount), value="N/A", inline=False) else: emd.add_field(name="Bans: {0}".format(banCount), value=bans, inline=False) emd.add_field( name="First Seen", value=datetime.fromtimestamp( first_seen[0]['time']).strftime('%Y-%m-%d @ %H:%M:%S'), inline=True) emd.add_field( name="Time Since Moderation", value=str(datetime.now() - datetime.fromtimestamp(float(dbres[0]['time']))).split( '.')[0], inline=True) await discord.bot.say(embed=emd)
async def unban(ctx, target): target_id = discord.get_targeted_id(ctx) target_member = discord.default_server.get_member(target_id) if common.isDiscordAdministrator(ctx.message.author): #if the user is an admin, let them blindly call IDs, unban blindly from discord unban_status_discord = await common.unban_id(target_id) #also check the retroactive table, this can be useful if the user was retro banned but never rejoied database.cursor.execute('SELECT * FROM bans_retroactive WHERE id=%s', (target_id, )) unban_status_retroactive = database.cursor.fetchone() if unban_status_retroactive is None: unban_status_retroactive = False else: database.cursor.execute('DELETE FROM bans_retroactive WHERE id=%s', (target_id, )) database.connection.commit() #unban message logic if unban_status_discord is False and unban_status_retroactive is False: await discord.bot.say( "тЪая╕П Unable to find ID in either Discord or Retroactive table. Try again?" ) elif unban_status_discord is True and unban_status_retroactive is False: await discord.bot.say( "тЬФя╕П User was found banned on Discord. Removed ban.") elif unban_status_discord is False and unban_status_retroactive is True: await discord.bot.say( "тЬФя╕П User was found banned in Retroactive table. Removed from database." ) else: await discord.bot.say( "тЬФя╕П User was found banned on Discord and Retroactive ban table. Removed ban and database entry." ) elif common.isDiscordRegulator(ctx.message.author): #regulators have a slightly different logic to follow to allow. Use internal audit log to make decisions database.cursor.execute( 'SELECT * FROM journal_regulators WHERE id_targeted=%s AND id_invoker=%s AND action="BAN_MEMBER" ORDER BY time ASC', (target_id, ctx.message.author.id)) regulator_target_history = database.cursor.fetchone() #if there is nothing in the database there is no reason to even try with the logic if regulator_target_history is None: logger.warn( "{0} attempted unban of {1} ID but failed due to reason: DB_RETURNED_NONE" .format(ctx.message.author.id, target_id)) await discord.bot.say( "тЪая╕П {0.message.author.mention} Unable to find any record of you banning ID `{1}` (ever). You can only unban people you yourself banned." .format(ctx, target_id)) return #get how many mins ago the ban was to see if they are able to be unbanned banned_mins_ago = int( (time.time() - regulator_target_history['time']) / 60) #handle if we should process the unban_id() if banned_mins_ago > config.cfg['regulators']['unban_grace_mins']: over_grace_mins = int(banned_mins_ago - config.cfg['regulators']['unban_grace_mins']) logger.warn( "{0} attempted unban of {1} ID but failed due to reason: OVER_GRACE_PERIOD by {2} minutes" .format(ctx.message.author.id, target_id, over_grace_mins)) await discord.bot.say( "тЪая╕П {0.message.author.mention} The user ID `{1}` is over the grace period for regulator unbans by `{2}` minutes. Contact an Administrator." .format(ctx, target_id, over_grace_mins)) else: unban_status_discord = await common.unban_id(target_id) if unban_status_discord is False: logger.warn( "{0} attempted unban of {1} ID but failed due to reason: DISCORD_RETURNED_NOT_BANNED" .format(ctx.message.author.id, target_id)) await discord.bot.say( "тЪая╕П {0.message.author.mention} Discord returned no ban found for ID `{1}` (ever). The user was already unbanned or something is wrong :/" .format(ctx, target_id)) else: logger.info( "{0} unbanned {1}. Time elapsed since ban {2}".format( ctx.message.author.id, target_id, banned_mins_ago)) await discord.bot.say( "тЬФя╕П {0.message.author.mention} ID `{1}` has been unbanned!" .format(ctx, target)) journal.update_journal_regulator(invoker=ctx.message.author.id, target=target_id, eventdata=banned_mins_ago, action="UNBAN_MEMBER_GRACE", messageid=ctx.message.id)
async def timedban(ctx, target, duration, *, reason): if common.isDiscordRegulator(ctx.message.author) != True: return #convert the target into a usable ID target_id = discord.get_targeted_id(ctx) target_member = discord.default_server.get_member(target_id) #ban reason has to have at least a word in it if len(reason) < 4: await discord.bot.say( "тЪая╕П {0.message.author.mention} ban reason must be longer than 4 characters. `!ban @username 1d reason goes here`" .format(ctx)) return #if the user cannot be banned if allow_regulation(ctx, target_id) is False: await discord.bot.say( "ЁЯЫС {0.message.author.mention} unable to moderate user. (no permissions)" .format(ctx)) return #verify the ban duration, and get the durations in seconds duration_length = scheduler.user_duration_to_seconds(duration) if duration_length is False: await discord.bot.say( "тЪая╕П{0.message.author.mention} unable to decode message length. Should be `!ban @username 60s|1m|7d could not stop using gamer words`" .format(ctx)) return elif duration_length is None: await discord.bot.say( "тЪая╕П{0.message.author.mention} unable to decode message length. allowed: seconds (s), minutes (m), hours (h), days (d), weeks (w), years (y)" .format(ctx)) return duration_inrange = scheduler.verify_reminder_duration_length( duration_length) if duration_inrange is False: await discord.bot.say( "тЪая╕П{0.message.author.mention} Ban duration is out of range. Have some mercy! Current range {1}m to {2}d" .format(ctx, config.cfg['scheduler']['time_range_min_mins'], int(config.cfg['scheduler']['time_range_max_mins'] / 1440)) ) return #if we have a Member object, it is not retroactive and can be run live against discord if target_member is not None: #get the time to unban, make a db entry for unban now = int(time.time()) time_to_fire = now + duration_length when_nice = time.strftime("%A, %B %d %Y %H:%M %Z", time.localtime(time_to_fire)) scheduler.set_future_task(invoker=ctx.message.author.id, target=target_member.id, channel=ctx.message.channel.id, timestamp=time_to_fire, event="UNBAN_MEMBER", msg=reason) await discord.bot.say( "тЬФя╕П {0.message.author.mention} is **BANNING** {1} with the reason of `{2}`. (Duration: {3}, Unban at:`{4}`)" .format(ctx, target_member.mention, reason, duration, when_nice)) await asyncio.sleep(5) #ship the ban off to discord res = await common.ban_verbose( target_member, "{0.message.author.name} - '{1}'".format(ctx, reason)) if res is False: await discord.bot.say( "тЭУ Something's f****d! Unable to issue ban to Discord API. Bother <@{}>" .format(config.cfg['bernard']['owner'])) else: journal.update_journal_regulator(invoker=ctx.message.author.id, target=target_id, eventdata=reason, action="BAN_MEMBER", messageid=ctx.message.id) #if a raw mention exists but not the Member object, handle it as retroactive elif target_id is not None and target_member is None: await discord.bot.say( "тЪая╕П {0.message.author.mention} You cannot issue retroactive timed bans. Please use normal !ban." .format(ctx)) else: await discord.bot.say( "ЁЯЫС {0.message.author.mention} unable to moderate user. Target did not resolve to a valid Discord Member." .format(ctx))
async def ban(ctx, target, *, reason): if common.isDiscordRegulator(ctx.message.author) != True: return #convert the target into a usable ID target_id = discord.get_targeted_id(ctx) target_member = discord.default_server.get_member(target_id) #ban reason has to have at least a word in it if len(reason) < 4: await discord.bot.say( "тЪая╕П ban reason must be longer than 4 characters. `!ban @username reason goes here`" ) return # ban reason has to be a reasonable length if len(reason) > 200: await discord.bot.say( "тЪая╕П Reason too long - please limit to 200 characters or less.") return #if the user cannot be banned if allow_regulation(ctx, target_id) is False: await discord.bot.say( "ЁЯЫС {} unable to moderate user. (no permissions)".format( ctx.message.author.mention)) return #if we have a Member object, it is not retroactive and can be run live against discord if target_member is not None: await discord.bot.say( "тЬФя╕П {} is **BANNING** {} with the reason of `{}`.".format( ctx.message.author.mention, target_member.mention, reason)) await asyncio.sleep(5) res = await common.ban_verbose( target_member, "{} - '{}'".format(ctx.message.author.name, reason)) if res is False: await discord.bot.say( "тЭУ Something's f****d! Unable to issue ban to Discord API. Bother <@{}>" .format(config.cfg['bernard']['owner'])) else: journal.update_journal_regulator(invoker=ctx.message.author.id, target=target_id, eventdata=reason, action="BAN_MEMBER", messageid=ctx.message.id) #if a raw mention exists but not the Member object, handle it as retroactive and log who did it. elif target_id is not None and target_member is None: await discord.bot.say( "тЬФя╕П {} is (retroactive) **BANNING** userid `{}` with the reason of `{}`." .format(ctx.message.author.mention, target_id, reason)) journal.update_journal_regulator(invoker=ctx.message.author.id, target=target_id, eventdata=reason, action="BAN_MEMBER_RETROACTIVE", messageid=ctx.message.id) retroactive_reason = "VIA_RETROACTIVE: {} - '{}'".format( ctx.message.author.name, reason) database.cursor.execute( 'INSERT INTO bans_retroactive (id, name, reason) VALUES (%s,%s,%s)', (target_id, "N/A", retroactive_reason)) database.connection.commit() else: await discord.bot.say( "ЁЯЫС {} unable to moderate user. Target did not resolve to a valid Discord Member." .format(ctx.message.author.mention))
async def getid(ctx, target): if common.isDiscordRegulator(ctx.message.author): id = discord.get_targeted_id(ctx) await discord.bot.say(id)