async def check_votekicks(client): await client.wait_until_ready() check_votekicks.last_run = datetime.now(pytz.utc) start_time = time() if client.is_ready(): to_remove = [] votekicks = list(cfg.VOTEKICKS.keys()) for mid in votekicks: try: vk = cfg.VOTEKICKS[mid] except KeyError: print("Ignoring error:") traceback.print_exc() continue guild = vk['message'].guild guilds = func.get_guilds(client) if guild not in guilds: return in_favor = len(vk['in_favor']) log( "TESTVOTEKICK: {} ({}/{})".format(vk['offender'].display_name, in_favor, vk['required_votes']), guild) if in_favor >= vk['required_votes']: to_remove.append(mid) log( "Kicked {} from {} ({}/{})".format( vk['offender'].display_name, vk['voice_channel'].name, in_favor, len(vk['participants']) + 1), guild) try: await vk['offender'].move_to(None) # Kick except Exception as e: to_remove.append(mid) await vk['message'].edit( content= "‼ **Votekick** failed - A `{}` error was encountered." .format(type(e).__name__)) continue banned = True try: await vk['voice_channel'].set_permissions(vk['offender'], connect=False) except discord.errors.Forbidden: banned = False await vk['message'].edit(content=( "‼ **Votekick** ‼\n" "{} was **kicked** from {}'s channel{}.{}". format(vk['offender'].mention, vk['initiator'].mention, ( ", but could not be banned from the channel as I don't have the *Manage Roles* permission." if not banned else ""), ("\nReason: **{}**".format( vk['reason']) if vk['reason'] else "")))) await func.server_log( guild, "👢 {} (`{}`) has been **kicked** from {}'s channel.". format(func.user_hash(vk['offender']), vk['offender'].id, vk['initiator']), 1, utils.get_serv_settings(guild)) elif time() > vk['end_time'] + 5: to_remove.append(mid) log( "VOTEKICK TIMED OUT: {} ({}/{}) {} {}".format( vk['offender'].display_name, in_favor, vk['required_votes'], mid, type(mid)), guild) await vk['message'].edit( content= "‼ **Votekick** timed out: Insufficient votes received " "({0}/{1}), required: {2}/{1}.".format( in_favor, len(vk['participants']) + 1, vk['required_votes'])) for mid in to_remove: log( "REMOVING VOTEKICK: {} {} len:{} keys:{}".format( mid, type(mid), len(cfg.VOTEKICKS), ', '.join([ str(k) + " " + str(type(k)) for k in cfg.VOTEKICKS.keys() ])), guild) del cfg.VOTEKICKS[mid] print("... len:{} keys:{}".format( len(cfg.VOTEKICKS), ', '.join([ str(k) + " " + str(type(k)) for k in cfg.VOTEKICKS.keys() ]))) end_time = time() fn_name = "check_votekicks" cfg.TIMINGS[fn_name] = end_time - start_time if cfg.TIMINGS[fn_name] > 20: await func.log_timings(client, fn_name)
async def create_join_channels(client): await client.wait_until_ready() create_join_channels.last_run = datetime.now(pytz.utc) start_time = time() if not client.is_ready(): return to_remove = [] priv_channels = list(cfg.PRIV_CHANNELS.keys()) for pc in priv_channels: try: pcv = cfg.PRIV_CHANNELS[pc] except KeyError: print("Ignoring error:") traceback.print_exc() continue if 'request_time' in pcv and time() - pcv['request_time'] > 120: # Unable to create join channel for 120s to_remove.append(pc) await pcv['text_channel'].send( ":warning: {} For some reason I was unable to create your \"⇩ Join\" channel, please try again later. " "Your channel is still private, but there's now no way for anyone to join you. " "Use `{}public` to make it public again." "".format(pcv['creator'].mention, pcv['prefix'])) log("Failed to create join-channel, timed out.") continue guild = client.get_guild(pcv['guild_id']) if guild not in func.get_guilds(client): continue settings = utils.get_serv_settings(guild) settings_copy = deepcopy(settings) for p, pv in settings_copy['auto_channels'].items(): for s, sv in pv['secondaries'].items(): if 'priv' in sv and 'jc' not in sv: creator = pcv['creator'].display_name vc = pcv['voice_channel'] c_position = vc.position overwrites = vc.overwrites k = guild.default_role v = overwrites[ k] if k in overwrites else discord.PermissionOverwrite( ) v.update(connect=True) overwrites[k] = v try: jc = await guild.create_voice_channel( "⇩ Join {}".format(creator), # TODO creator can change category=vc.category, overwrites=overwrites) except discord.errors.Forbidden: to_remove.append(pc) try: await pcv['text_channel'].send( ":warning: {} I don't have permission to make the \"⇩ Join\" channel for you anymore." "".format(pcv['creator'].mention)) except: log("Failed to create join-channel, and failed to notify {}" .format(creator)) break break utils.permastore_secondary(jc.id) try: settings['auto_channels'][p]['secondaries'][s][ 'jc'] = jc.id except KeyError: to_remove.append(pc) break utils.set_serv_settings(guild, settings) to_remove.append(pc) try: # Set position again, sometimes create_voice_channel gets it wrong. await jc.edit(position=c_position) except discord.errors.Forbidden: # Harmless error, no idea why it sometimes throws this, seems like a bug. pass break # give the event loop some more control await asyncio.sleep(0.5) await asyncio.sleep(0.5) await asyncio.sleep(0.5) for i in to_remove: try: del cfg.PRIV_CHANNELS[i] except KeyError: # Already deleted somehow. print("Ignoring error:") traceback.print_exc() pass end_time = time() fn_name = "create_join_channels" cfg.TIMINGS[fn_name] = end_time - start_time if cfg.TIMINGS[fn_name] > 10: await func.log_timings(client, fn_name)
async def on_voice_state_update(member, before, after): if not client.is_ready(): return if before.channel == after.channel: # Ignore mute/unmute events return guild = member.guild guilds = func.get_guilds(client) if guild not in guilds: return settings = utils.get_serv_settings(guild) if not settings['enabled']: return if not settings['auto_channels']: # No channels have been set up, do nothing return secondaries = func.get_secondaries(guild, settings) join_channels = func.get_join_channels(guild, settings) if after.channel: if after.channel.id in settings['auto_channels']: await func.create_secondary(guild, after.channel, member) elif after.channel.id in secondaries: if after.channel.name != "⌛": await func.update_text_channel_role(guild, member, after.channel, "join") bitrate = await func.update_bitrate(after.channel, settings) await func.server_log( guild, "➡ {} (`{}`) joined \"**{}**\" (`{}`)".format( func.user_hash(member), member.id, after.channel.name, after.channel.id) + (" ⤏ {}kbps".format(round(bitrate / 1000)) if bitrate else ""), 3, settings) elif after.channel.id in join_channels: sv = join_channels[after.channel.id] msg_channel = guild.get_channel(sv['msgs']) vc = guild.get_channel(sv['vc']) creator = guild.get_member(sv['creator']) if msg_channel and creator and vc: try: m = await msg_channel.send( "Hey {},\n{} would like to join your private voice channel. React with:\n" "• ✅ to **allow**.\n" "• ❌ to **deny** this time.\n" "• ⛔ to deny and **block** future requests from them.". format(creator.mention, member.mention)) cfg.JOINS_IN_PROGRESS[member.id] = { "creator": creator, "requester": member, "vc": vc, "jc": after.channel, "msg": m, "mid": m.id } log( "{} ({}) requests to join {}".format( member.display_name, member.id, creator.display_name), guild) try: await m.add_reaction('✅') await m.add_reaction('❌') await m.add_reaction('⛔') except discord.errors.Forbidden: pass except Exception as e: log( "Failed to send join-request message ({})".format( type(e).__name__), guild) else: cfg.JOINS_IN_PROGRESS[member.id] if before.channel: if before.channel.id in secondaries: members = [m for m in before.channel.members if not m.bot] bitrate = None if members: await func.update_text_channel_role(guild, member, before.channel, "leave") bitrate = await func.update_bitrate(before.channel, settings, user_left=member) await func.server_log( guild, "🚪 {} (`{}`) left \"**{}**\" (`{}`)".format( func.user_hash(member), member.id, before.channel.name, before.channel.id) + (" [bitrate: {}kbps]".format(round(bitrate / 1000)) if bitrate else ""), 3, settings) if not members: await func.delete_secondary(guild, before.channel)
async def on_message(message): if not client.is_ready(): return if message.author.bot: # Don't respond to self or bots return guilds = func.get_guilds(client) admin = ADMIN admin_channels = [] if admin is not None: admin_channels = [admin.dm_channel] if 'admin_channel' in cfg.CONFIG: admin_channels.append(ADMIN_CHANNEL) if message.channel in admin_channels: split = message.content.split(' ') cmd = split[0].split('\n')[0].lower() params_str = message.content[len(cmd):].strip() params = params_str.split(' ') if cmd == 'stop': m = utils.strip_quotes(params_str) success = await reload_modules(m) await func.react(message, '⌛' if success else '❌') await asyncio.sleep(3) await func.react(message, '🌑' if success else '❌') await asyncio.sleep(1) await func.react(message, '🌓' if success else '❌') await asyncio.sleep(1) await func.react(message, '🌔' if success else '❌') await asyncio.sleep(1) await func.react(message, '✅' if success else '❌') await message.channel.send("restarting..") await client.logout() else: ctx = { 'client': client, 'admin': admin, 'message': message, 'params': params, 'params_str': params_str, 'guilds': guilds, 'LAST_COMMIT': LAST_COMMIT, } await admin_commands.admin_command(cmd, ctx) return if not message.guild: # DM if 'help' in message.content and len( message.content) <= len("@Auto Voice Channels help"): await message.channel.send( "Sorry I don't respond to commands in DMs, " "you need to type the commands in a channel in your server.\n" "If you've tried that already, then make sure I have the right permissions " "to see and reply to your commands in that channel.") else: await admin_channels[-1].send( embed=discord.Embed(title="DM from **{}** [`{}`]:".format( message.author.name, message.author.id), description=message.content)) return if message.guild not in guilds: return prefix_m = message.guild.me.mention prefix_mx = "<@!" + prefix_m[2:] if message.guild.id in cfg.PREFIXES: prefix_p = cfg.PREFIXES[message.guild.id] else: prefix_p = 'vc/' prefix = None if message.content.startswith(prefix_m): prefix = prefix_m print_prefix = "@{} ".format(message.guild.me.display_name) elif message.content.startswith(prefix_mx): prefix = prefix_mx print_prefix = "@{} ".format(message.guild.me.display_name) elif message.content.lower().startswith(prefix_p.lower()): prefix = prefix_p print_prefix = prefix_p # Commands if prefix: msg = message.content[len(prefix):].strip() # Remove prefix split = msg.split(' ') cmd = split[0].lower() params = split[1:] params_str = ' '.join(params) clean_paramstr = ' '.join( message.clean_content[len(prefix):].strip().split(' ')[1:]) guild = message.guild channel = message.channel settings = utils.get_serv_settings(guild) if channel.id not in func.get_voice_context_channel_ids( guild, settings): settings['last_channel'] = channel.id utils.set_serv_settings(guild, settings) ctx = { 'client': client, 'guild': guild, 'prefix': prefix, 'print_prefix': print_prefix, 'prefix_p': prefix_p, 'command': cmd, 'gold': func.is_gold(guild), 'sapphire': func.is_sapphire(guild), 'settings': settings, 'message': message, 'channel': channel, 'clean_paramstr': clean_paramstr, } # Restricted commands perms = message.author.permissions_in(channel) perms_required = [ perms.manage_channels, perms.manage_roles, ] ctx['admin'] = all(perms_required) success, response = await commands.run(cmd, ctx, params) if success or response != "NO RESPONSE": log("CMD {}: {}".format("Y" if success else "F", msg), guild) if success: if response: if response != "NO RESPONSE": await echo(response, channel, message.author) else: await func.react(message, '✅') else: if response != "NO RESPONSE": await func.react(message, '❌') if response: await echo(response, channel, message.author)
async def create_join_channels(client): if not client.is_ready(): return to_remove = [] priv_channels = list(cfg.PRIV_CHANNELS.keys()) for pc in priv_channels: try: pcv = cfg.PRIV_CHANNELS[pc] except KeyError: print("Ignoring error:") traceback.print_exc() continue if 'request_time' in pcv and time() - pcv['request_time'] > 120: # Unable to create join channel for 120s to_remove.append(pc) await pcv['text_channel'].send( ":warning: {} For some reason I was unable to create your \"⇧ Join\" channel, please try again later. " "Your channel is still locked, but there's now no way for anyone to join you. " "Use `{}unlock` to make it public again." "".format(pcv['creator'].mention, pcv['prefix'])) log("Failed to create join-channel, timed out.") continue guild = client.get_guild(pcv['guild_id']) if guild not in func.get_guilds(client): continue settings = utils.get_serv_settings(guild) for p, pv in settings['auto_channels'].items(): for s, sv in pv['secondaries'].items(): if 'priv' in sv and 'jc' not in sv: creator = pcv['creator'].display_name vc = pcv['voice_channel'] c_position = 0 voice_channels = [ x for x in guild.channels if isinstance(x, type(vc)) ] voice_channels.sort(key=lambda ch: ch.position) for x in voice_channels: c_position += 1 if x.id == vc.id: break overwrites = vc.overwrites k = guild.default_role v = overwrites[ k] if k in overwrites else discord.PermissionOverwrite( ) v.update(connect=True) overwrites[k] = v try: jc = await guild.create_voice_channel( "⇧ Join {}".format( creator), # TODO creator can change position=c_position, category=vc.category, overwrites=overwrites) except discord.errors.Forbidden: to_remove.append(pc) try: await pcv['text_channel'].send( ":warning: {} I don't have permission to make the \"⇧ Join\" channel for you anymore." "".format(pcv['creator'].mention)) except: log("Failed to create join-channel, and failed to notify {}" .format(creator)) break utils.permastore_secondary(jc.id) settings['auto_channels'][p]['secondaries'][s][ 'jc'] = jc.id utils.set_serv_settings(guild, settings) to_remove.append(pc) try: # Set position again, sometimes create_voice_channel gets it wrong. await jc.edit(position=c_position) except discord.errors.Forbidden: # Harmless error, no idea why it sometimes throws this, seems like a bug. pass break for i in to_remove: try: del cfg.PRIV_CHANNELS[i] except KeyError: # Already deleted somehow. print("Ignoring error:") traceback.print_exc() pass
async def on_message(message): if not client.is_ready(): return if message.author.bot: # Don't respond to self or bots return guilds = func.get_guilds(client) admin = ADMIN admin_channels = [] if admin is not None: admin_channels = [admin.dm_channel] if 'admin_channel' in cfg.CONFIG and ADMIN_CHANNEL is not None: admin_channels.append(ADMIN_CHANNEL) if message.channel in admin_channels: split = message.content.split(' ') cmd = split[0].split('\n')[0].lower() params_str = message.content[len(cmd):].strip() params = params_str.split(' ') if cmd == 'reload': m = utils.strip_quotes(params_str) success = await reload_modules(m) await func.react(message, '✅' if success else '❌') else: ctx = { 'client': client, 'admin': admin, 'message': message, 'params': params, 'params_str': params_str, 'guilds': guilds, 'LAST_COMMIT': LAST_COMMIT, 'loops': loops, } await admin_commands.admin_command(cmd, ctx) return if not message.guild: # DM if 'help' in message.content and len( message.content) <= len("@Auto Voice Channels help"): await message.channel.send( "Sorry I don't respond to commands in DMs, " "you need to type the commands in a channel in your server.\n" "If you've tried that already, then make sure I have the right permissions " "to see and reply to your commands in that channel.") elif message.content.lower().startswith("power-overwhelming"): channel = message.channel params_str = message.content[len("power-overwhelming"):].strip() if not params_str: await channel.send( "You need to specify a guild ID. " "Try typing `who am I` to get a list of guilds we're both in" ) return auth_guilds = params_str.replace(' ', '\n').split('\n') for auth_guild in auth_guilds: try: g = client.get_guild(int(auth_guild)) if g is None: await channel.send( "`{}` is not a guild I know about, " "maybe you need to invite me there first?".format( auth_guild)) return except ValueError: await channel.send( "`{}` is not a valid guild ID, try typing " "`who am I` to get a list of guilds we're both in.". format(auth_guild)) return except Exception as e: error_text = "Auth Error `{}`\nUser `{}`\nCMD `{}`".format( type(e).__name__, message.author.id, message.content) await func.admin_log(error_text, ctx['client']) log(error_text) error_text = traceback.format_exc() await func.admin_log(error_text, ctx['client']) log(error_text) return False, ( "A `{}` error occured :(\n" "An admin has been notified and will be in touch.\n" "In the meantime, try asking for help in the support server: " "https://discord.gg/qhMrz6u".format(type(e).__name__)) ctx = { 'message': message, 'channel': channel, 'client': client, } auth_guilds = [int(g) for g in auth_guilds] success, response = await func.power_overwhelming(ctx, auth_guilds) if success or response != "NO RESPONSE": log("DM CMD {}: {}".format("Y" if success else "F", message.content)) if success: if response: if response != "NO RESPONSE": await echo(response, channel, message.author) else: await func.react(message, '✅') else: if response != "NO RESPONSE": await func.react(message, '❌') if response: await echo(response, channel, message.author) elif message.content.lower() in ["who am i", "who am i?"]: in_guilds = [] for g in client.guilds: if message.author in g.members: in_guilds.append("`{}` **{}**".format(g.id, g.name)) if in_guilds: await message.channel.send( "We're both in the following guilds:\n{}".format( '\n'.join(in_guilds))) else: await message.channel.send( "I'm not in any of the same guilds as you.") else: await admin_channels[-1].send( embed=discord.Embed(title="DM from **{}** [`{}`]:".format( message.author.name, message.author.id), description=message.content)) return if message.guild not in guilds: return prefix_m = message.guild.me.mention prefix_mx = "<@!" + prefix_m[2:] if message.guild.id in cfg.PREFIXES: prefix_p = cfg.PREFIXES[message.guild.id] else: prefix_p = 'vc/' prefix = None if message.content.startswith(prefix_m): prefix = prefix_m print_prefix = "@{} ".format(message.guild.me.display_name) elif message.content.startswith(prefix_mx): prefix = prefix_mx print_prefix = "@{} ".format(message.guild.me.display_name) elif message.content.lower().startswith(prefix_p.lower()): prefix = prefix_p print_prefix = prefix_p # Commands if prefix: msg = message.content[len(prefix):].strip() # Remove prefix split = msg.split(' ') cmd = split[0].lower() params = split[1:] params_str = ' '.join(params) clean_paramstr = ' '.join( message.clean_content[len(prefix):].strip().split(' ')[1:]) guild = message.guild channel = message.channel settings = utils.get_serv_settings(guild) if channel.id not in func.get_voice_context_channel_ids( guild, settings): settings['last_channel'] = channel.id utils.set_serv_settings(guild, settings) ctx = { 'client': client, 'guild': guild, 'prefix': prefix, 'print_prefix': print_prefix, 'prefix_p': prefix_p, 'command': cmd, 'gold': func.is_gold(guild), 'sapphire': func.is_sapphire(guild), 'settings': settings, 'message': message, 'channel': channel, 'clean_paramstr': clean_paramstr, } # Restricted commands perms = message.author.permissions_in(channel) perms_required = [ perms.manage_channels, perms.manage_roles, ] ctx['admin'] = all(perms_required) success, response = await commands.run(cmd, ctx, params) if success or response != "NO RESPONSE": log("CMD {}: {}".format("Y" if success else "F", msg), guild) if success: if response: if response != "NO RESPONSE": await echo(response, channel, message.author) else: await func.react(message, '✅') else: if response != "NO RESPONSE": await func.react(message, '❌') if response: await echo(response, channel, message.author)
async def admin_command(cmd, ctx): client = ctx['client'] message = ctx['message'] channel = message.channel params = ctx['params'] params_str = ctx['params_str'] guilds = ctx['guilds'] LAST_COMMIT = ctx['LAST_COMMIT'] if cmd == 'log': logfile = "log{}.txt".format( "" if cfg.SAPPHIRE_ID is None else cfg.SAPPHIRE_ID) if not os.path.exists(logfile): await channel.send("No log file") return with open(logfile, 'r', encoding="utf8") as f: data = f.read() data = data[ -10000:] # Drop everything but the last 10k characters to make string ops quicker data = data.replace(' Creating channel for ', ' ✅') data = data.replace(' Deleting ', ' ❌') data = data.replace(' Renaming ⌛ to ', ' ⏩ ') data = data.replace(' Renaming ', ' 🔄') data = data.replace(' to ', ' ⏩ ') data = data.replace(' CMD Y: ', ' C✔ ') data = data.replace(' CMD F: ', ' C✖ ') data = data.replace(" creating channels too quickly", " creating channels too quickly❗❗") data = data.replace(" where I don't have permissions", " where I don't have permissions❗❗") data = data.replace("Traceback (most recent", "❗❗Traceback (most recent") data = data.replace("discord.errors.", "❗❗discord.errors.") data = data.replace("Remembering channel ", "❗❗Remembering ") data = data.replace("New tickrate is ", "🕐") data = data.replace(", seed interval is ", " 🕐") data = data.replace(' ', ' ') # Reduce indent to save character space today = datetime.now(pytz.timezone( cfg.CONFIG['log_timezone'])).strftime("%Y-%m-%d") data = data.replace(today, 'T') character_limit = 2000 - 17 # 17 for length of ```autohotkey\n at start and ``` at end. data = data[character_limit * -1:] data = data.split('\n', 1)[1] lines = data.split('\n') for i, l in enumerate(lines): # Fake colon (U+02D0) to prevent highlighting the line if " ⏩" in l: lines[i] = l.replace(':', 'ː') elif l.startswith('T '): if '[' in l: s = l.split('[', 1) lines[i] = s[0] + '[' + s[1].replace(':', 'ː') data = '\n'.join(lines) data = '```autohotkey\n' + data data = data + '```' await channel.send(data) if cmd == 'stats': r = await channel.send(". . .") t1 = message.created_at t2 = r.created_at response_time = (t2 - t1).total_seconds() num_users = 0 for g in guilds: num_users += len([m for m in g.members if not m.bot]) lines_of_code = 0 for f in os.listdir(cfg.SCRIPT_DIR): if f.lower().endswith('.py'): lines_of_code += utils.count_lines( os.path.join(cfg.SCRIPT_DIR, f)) elif f == "commands": for sf in os.listdir(os.path.join(cfg.SCRIPT_DIR, f)): if sf.lower().endswith('.py'): lines_of_code += utils.count_lines( os.path.join(cfg.SCRIPT_DIR, f, sf)) cpu = psutil.cpu_percent() mem = psutil.virtual_memory() disk = psutil.disk_usage('/') await r.edit(content=( "Servers: **{tot_servs}** (A:{active_servs} S:{shards}) \t " "Users: **{users}** \t Channels: **{channels}** \n" "Response time: **{rt}** \t Tick rate: **{tr}** \t Tick time: **{tt}** | **{gtt}**\n" "CPU: **{cpu}%** \t MEM: **{memg} ({memp}%)** \t DISK: **{diskg} ({diskp}%)**\n" "**Last commit:** {commit}\n" "**Lines of code:** {lines}\n" "**Timings:** \n{timings}".format( tot_servs=len(guilds), active_servs=utils.num_active_guilds(guilds), shards=utils.num_shards(guilds), users=num_users, channels=utils.num_active_channels(guilds), rt="{0:.2f}s".format(response_time), tr="{0:.1f}s".format(cfg.TICK_RATE), tt="{0:.2f}s".format(cfg.TICK_TIME), gtt="{0:.2f}s".format(cfg.G_TICK_TIME), cpu=cpu, memg="{0:.1f}GB".format(mem.used / 1024 / 1024 / 1024), memp=round(mem.percent), diskg="{0:.1f}GB".format(disk.used / 1024 / 1024 / 1024), diskp=round(disk.percent), commit=LAST_COMMIT, lines=lines_of_code, timings=utils.format_timings()))) if cmd == 'top': top_guilds = [] for g in guilds: s = func.get_secondaries(g) if s: top_guilds.append({ "name": g.name, "size": len([m for m in g.members if not m.bot]), "num": len(s) }) top_guilds = sorted(top_guilds, key=lambda x: x['num'], reverse=True)[:10] r = "**Top Guilds:**" for g in top_guilds: r += "\n`{}` {}: \t**{}**".format(g['size'], func.esc_md(g['name']), g['num']) r += "\n\n**{}**".format(utils.num_active_channels(guilds)) await channel.send(r) if cmd == 'patrons': if patreon_info is None: await channel.send(content='❌') return patrons = patreon_info.fetch_patrons(force_update=True) if not patrons: await channel.send(content='❌') return fields = {} auths = patreon_info.update_patron_servers(patrons) for p, pv in patrons.items(): pu = client.get_user(p) if pu is not None: pn = pu.name else: pn = "Unknown" gn = "" if str(p) in auths: for s in auths[str(p)]['servers']: gn += "`{}` ".format(s) if 'extra_gold' in auths[str(p)]: for s in auths[str(p)]['extra_gold']: gn += "+g`{}` ".format(s) fields["`{}` **{}** {}".format(p, pn, cfg.TIER_ICONS[pv])] = gn try: for field_chunk in utils.dict_chunks(fields, 25): e = discord.Embed(color=discord.Color.from_rgb(205, 220, 57)) e.title = "{} Patrons".format(len(field_chunk)) for f, fv in field_chunk.items(): fv = fv if fv else "None" e.add_field(name=f, value=fv) await channel.send(embed=e) except: await channel.send(traceback.format_exc()) await func.react(message, '❌') if cmd == 'sapphiredebug': if cfg.SAPPHIRE_ID is None: await channel.send(content='❌ Not a sapphire') return if patreon_info is None: await channel.send(content='❌ No patreon_info') return auths = utils.read_json( os.path.join(cfg.SCRIPT_DIR, "patron_auths.json")) initiator_id = cfg.CONFIG["sapphires"][str( cfg.SAPPHIRE_ID)]['initiator'] msg = ("Sapphire ID: {}\n" "User: `{}`\n" "Actual guilds: {}\n" "Config guilds: {}\n" "Authenticated guilds: {}\n" "get_guilds: {}".format( cfg.SAPPHIRE_ID, initiator_id, ", ".join(['`' + str(g.id) + '`' for g in client.guilds]), ", ".join([ '`' + str(g) + '`' for g in cfg.CONFIG["sapphires"][str( cfg.SAPPHIRE_ID)]['servers'] ]), ", ".join([ '`' + str(g) + '`' for g in auths[str(initiator_id)]['servers'] ]), ", ".join([ '`' + str(g.id) + '`' for g in func.get_guilds(client) ]))) await channel.send(msg) if cmd == 'status': g = utils.strip_quotes(params_str) if not g: await func.react(message, '❌') return try: await client.change_presence(activity=discord.Activity( name=g, type=discord.ActivityType.watching)) await func.react(message, '✅') except: await channel.send(traceback.format_exc()) await func.react(message, '❌') if cmd == 'settings': gid = utils.strip_quotes(params_str) try: int(gid) except ValueError: for x in guilds: if x.name == gid: gid = str(x.id) break fname = gid + '.json' fp = os.path.join(cfg.SCRIPT_DIR, "guilds", fname) if os.path.exists(fp): gid = int(gid) g = client.get_guild(gid) head = "**{}** `{}`{}".format( g.name, gid, ("✅" if g in func.get_guilds(client) else "❌")) head += "💎" if func.is_sapphire(gid) else ( "💳" if func.is_gold(gid) else "") s = head s += "\n```json\n" with open(fp, 'r') as f: file_content = f.read() s += file_content s += '```' try: await channel.send(s) except discord.errors.HTTPException: # Usually because message is over character limit haste_url = await utils.hastebin(file_content) await channel.send("{}\n{}".format(head, haste_url)) else: await func.react(message, '❌') if cmd == 'disable': try: g = client.get_guild(int(utils.strip_quotes(params_str))) settings = utils.get_serv_settings(g) settings['enabled'] = False utils.set_serv_settings(g, settings) log("Force Disabling", g) await func.react(message, '✅') except: await channel.send(traceback.format_exc()) await func.react(message, '❌') if cmd == 'enable': try: g = client.get_guild(int(utils.strip_quotes(params_str))) settings = utils.get_serv_settings(g) settings['enabled'] = True utils.set_serv_settings(g, settings) log("Force Enabling", g) await func.react(message, '✅') except: await channel.send(traceback.format_exc()) await func.react(message, '❌') if cmd == 'info': cid = utils.strip_quotes(params_str) try: c = client.get_channel(int(cid)) members = [ m.display_name + " \t {}".format(utils.debug_unicode(m.display_name)) for m in c.members ] games = [] for m in c.members: if m.activity: games.append( m.activity.name + " \t {}".format(utils.debug_unicode(m.activity.name))) s = "**__Server:__** {} `{}`\n**__Name:__** {}\n{}\n\n".format( c.guild.name, c.guild.id, c.name, utils.debug_unicode(c.name)) if c.id in cfg.ATTEMPTED_CHANNEL_NAMES: s += "**__Attempted Name:__** {}\n{}\n\n".format( cfg.ATTEMPTED_CHANNEL_NAMES[c.id], utils.debug_unicode(cfg.ATTEMPTED_CHANNEL_NAMES[c.id])) s += "**__{} Members:__**\n".format(len(members)) s += '\n'.join(members) s += '\n\n**__{} Games:__**\n'.format(len(games)) s += '\n'.join(games) s = s.replace('\n\n\n', '\n\n') await channel.send(s) except: await channel.send(traceback.format_exc()) await func.react(message, '❌') if cmd == 'whois': uid = utils.strip_quotes(params_str) try: u = client.get_user(int(uid)) in_guilds = {} for g in client.guilds: if u in g.members: m = g.get_member(int(uid)) in_guilds[g.id] = { "guild_name": func.esc_md(g.name), "guild_size": g.member_count, "patron": "💎" if func.is_sapphire(g) else ("💳" if func.is_gold(g) else ""), "user_name": func.esc_md(m.display_name), "role": m.top_role.name, } if in_guilds: s = "**{}**".format(func.user_hash(u)) s += " \t :b: :regional_indicator_o: :regional_indicator_t:" if u.bot else "" can_dm = True try: await u.create_dm() can_dm = client.user.permissions_in( u.dm_channel).send_messages except discord.errors.Forbidden: can_dm = False s += " \t Can DM: {}".format('✅' if can_dm else '❌') for gid, g in in_guilds.items(): s += "\n{}`{}` **{}** (`{}`) \t {} ({})".format( g['patron'], gid, g['guild_name'], g['guild_size'], g['user_name'], g['role']) await echo(s, channel) else: await channel.send("¯\\_(ツ)_/¯") except: await channel.send(traceback.format_exc()) await func.react(message, '❌') if cmd == 'votekicks': try: readable = {} for k, kv in cfg.VOTEKICKS.items(): readable[k] = { "initiator": kv['initiator'].display_name, "participants": [m.display_name for m in kv['participants']], "required_votes": kv['required_votes'], "offender": kv['offender'].display_name, "reason": kv['reason'], "in_favor": [m.display_name for m in kv['in_favor']], "voice_channel": kv['voice_channel'].id, "message": kv['message'].id, "end_time": datetime.fromtimestamp( kv['end_time']).strftime("%Y-%m-%d %H:%M") } s = "```json\n" + json.dumps(readable, indent=1, sort_keys=True) + "```" print(s) try: await channel.send(s) except discord.errors.HTTPException: # Usually because message is over character limit haste_url = await utils.hastebin(s) await channel.send(haste_url) except: await channel.send(traceback.format_exc()) await func.react(message, '❌') if cmd == 'exit': attempts = 0 while attempts < 100: attempts += 1 if not cfg.WRITES_IN_PROGRESS: print("Exiting!") await client.close() sys.exit() break else: print("Failed to close", cfg.WRITES_IN_PROGRESS) await func.react(message, '❌') if cmd == 'rename': try: cid = utils.strip_quotes(params[0]) c = client.get_channel(int(cid)) new_name = ' '.join(params[1:]) if not new_name: new_name = "⌛" await c.edit(name=new_name) except: await channel.send(traceback.format_exc()) await func.react(message, '❌') else: await func.react(message, '✅') log("{0} Force Renaming to {1}".format(cid[-4:], new_name), c.guild) if cmd == 'forget': try: cid = int(utils.strip_quotes(params[0])) c = client.get_channel(cid) settings = utils.get_serv_settings(c.guild) for p, pv in settings['auto_channels'].items(): tmp = settings['auto_channels'][p]['secondaries'].copy() for s, sv in pv['secondaries'].items(): if s == cid: del settings['auto_channels'][p]['secondaries'][s] break utils.set_serv_settings(c.guild, settings) except: await channel.send(traceback.format_exc()) await func.react(message, '❌') else: await func.react(message, '✅') if cmd == 'delete': try: cid = int(utils.strip_quotes(params[0])) c = client.get_channel(cid) await c.delete() except: await channel.send(traceback.format_exc()) await func.react(message, '❌') else: await func.react(message, '✅') if cmd == 'whisper': params_str = utils.strip_quotes(params_str) if '\n' not in params_str: await func.react(message, '❌') return uid, msg = params_str.split('\n', 1) try: u = await client.fetch_user(uid) except discord.errors.NotFound: await func.react(message, '❌') return if u.dm_channel is None: await u.create_dm() try: await u.dm_channel.send(msg) except: await channel.send(traceback.format_exc()) await func.react(message, '❌') else: await func.react(message, '✅') if cmd == 'cleanprimaries': try: n_primaries = 0 n_real_primaries = 0 for g in client.guilds: settings = utils.get_serv_settings(g) tmp = {} n_primaries += len(settings['auto_channels']) for p, pv in settings['auto_channels'].items(): c = g.get_channel(p) if c: tmp[p] = pv n_real_primaries += len(tmp) if len(settings['auto_channels']) != len(tmp): settings['auto_channels'] = tmp utils.set_serv_settings(g, settings) await channel.send("Cleaned {} of {} primaries".format( n_real_primaries, n_primaries)) except: await channel.send(traceback.format_exc()) await func.react(message, '❌')