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, 'β')