async def lingering_secondaries(client): start_time = time() if client.is_ready(): potentials = None with concurrent.futures.ThreadPoolExecutor() as pool: potentials = await client.loop.run_in_executor(pool, get_potentials) potentials = potentials.split('\n') if potentials: potentials = set(potentials[-10000:]) # Sets apparently give better performance. Discard all but last 10k. for guild in func.get_guilds(client): settings = utils.get_serv_settings(guild) if not settings['enabled'] or not settings['auto_channels']: continue secondaries = func.get_secondaries(guild, settings=settings, include_jc=True) voice_channels = [x for x in guild.channels if isinstance(x, discord.VoiceChannel)] for v in voice_channels: if v.id not in secondaries and str(v.id) in potentials and not func.channel_is_requested(v): if v.name not in ['⌛', '⚠']: try: await v.edit(name='⚠') log("Remembering channel {}".format(v.id), guild) await func.admin_log( "⚠ Remembering channel `{}` in guild **{}**".format(v.id, guild.name), client ) except discord.errors.NotFound: pass except Exception: traceback.print_exc() end_time = time() fn_name = "lingering_secondaries" cfg.TIMINGS[fn_name] = end_time - start_time if cfg.TIMINGS[fn_name] > 5: await func.log_timings(client, fn_name)
async def check_empty(guild, settings): # Delete empty secondaries, in case they didn't get caught somehow (e.g. errors, downtime) secondaries = func.get_secondaries(guild, settings) voice_channels = [x for x in guild.channels if isinstance(x, discord.VoiceChannel)] for v in voice_channels: if v.name != "⌛": # Ignore secondary channels that are currently being created if v.id in secondaries: if not v.members: await func.delete_secondary(guild, v)
async def execute(ctx, params): params_str = ' '.join(params) guild = ctx['guild'] settings = ctx['settings'] author = ctx['message'].author bitrate = utils.strip_quotes(params_str) v = author.voice in_vc = v is not None and v.channel.id in func.get_secondaries( guild, settings) if bitrate.lower() == 'reset': try: del settings['custom_bitrates'][str(author.id)] utils.set_serv_settings(guild, settings) except KeyError: return False, "You haven't set a custom bitrate." if in_vc: await func.update_bitrate(v.channel, settings, reset=True) return True, "Your custom bitrate has been reset, the channel default will be used for you from now on." try: bitrate = float(bitrate) except ValueError: return False, "`{}` is not a number.".format(bitrate) if bitrate < 8: return False, "The bitrate must be higher than 8." if bitrate * 1000 > guild.bitrate_limit: return False, "{} is higher than the maximum bitrate in this server ({}).".format( bitrate, guild.bitrate_limit / 1000) if 'custom_bitrates' not in settings: settings['custom_bitrates'] = {} settings['custom_bitrates'][str(author.id)] = bitrate utils.set_serv_settings(guild, settings) if in_vc: await func.update_bitrate(v.channel, settings) await func.server_log( guild, "🎚 {} (`{}`) set their custom bitrate to {}kbps".format( func.user_hash(author), author.id, bitrate), 2, settings) return True, ( "Done! From now on, channels you join will have their bitrate set to {}kbps.\n" "If multiple users in the channel have set custom bitrates, the average will be used.\n\n" "Use `{}channelinfo` to check the current bitrate of your channel.". format(bitrate, ctx['print_prefix']))
async def execute(ctx, params): params_str = ctx['clean_paramstr'] guild = ctx['guild'] settings = ctx['settings'] author = ctx['message'].author new_name = params_str.replace('\n', ' ') # Can't have newlines in channel name. new_name = new_name.strip() secondaries = func.get_secondaries(guild, settings) first_word = new_name.split(' ')[0] try: cid = int(first_word) except ValueError: return False, ( "`{}` is not a valid channel ID. Please run `{}help rename` " "to learn how to use this command.".format(first_word, ctx['print_prefix'])) target_c = guild.get_channel(cid) if target_c is None: return False, "I can't find any channel with the ID `{}`.".format(cid) if cid not in secondaries: return False, "Sorry, that's not one of my channels." new_name = new_name[len(str(cid)):].strip() if new_name: return await func.custom_name(guild, target_c, author, new_name) else: return False, ( "You need to specify a new name for the channel, e.g. '{0}rename {1} <new name>'.\n" "Run '{0}help template' for a full list of variables you can use like " "`@@game_name@@`, `@@creator@@` and `@@num_others@@`.".format( ctx['print_prefix'], cid))
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 == 'ping': r = await channel.send(". . .") t1 = message.created_at t2 = r.created_at response_time = (t2 - t1).total_seconds() e = '🔴🔴🔴' if response_time > 5 else ('🟠🟠' if response_time > 1 else '🟢') await r.edit(content="**{0} {1:.1f}s**".format(e, response_time)) if cmd == 'top': top_guilds = [] for g in guilds: s = func.get_secondaries(g) top_guilds.append({"name": g.name, "size": len([m for m in g.members if not m.bot]), "num": len(s) if s is not None else 0}) 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 == 'refetch': gid = utils.strip_quotes(params_str) try: gid = int(gid) except ValueError: await func.react(message, '❌') return g = client.get_guild(gid) if g is None: await func.react(message, '❓') return utils.get_serv_settings(g, force_refetch=True) await func.react(message, '✅') return 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 == 'loop': mode = params[0] loop_name = params[1] try: loop = ctx['loops'][loop_name] modes = { # Dict of possible loop functions/attrs as [fn, arg] 'current_loop': [loop.current_loop, None], 'next_iteration': [loop.next_iteration, None], 'next_run': [loop.next_iteration, None], # Alias 'start': [loop.start, client], 'stop': [loop.stop, None], 'cancel': [loop.cancel, None], 'restart': [loop.restart, client], 'is_being_cancelled': [loop.is_being_cancelled, None], 'last_run': [loop.last_run, None], } if mode not in modes: await func.react(message, '❓') return fn, arg = modes[mode] if callable(fn): if arg is None: r = fn() else: r = fn(arg) else: r = fn if r is not None: if isinstance(r, date): r = r.astimezone(pytz.timezone(cfg.CONFIG['log_timezone'])) await channel.send(r.strftime("%Y-%m-%d %H:%M:%S")) else: await channel.send(str(r)) await func.react(message, '✅') except: await channel.send(traceback.format_exc()) await channel.send("Loops: \n{}".format('\n'.join(ctx['loops'].keys()))) 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, '❌') if cmd == 'leaveinactive': params_str = utils.strip_quotes(params_str) try: total_guilds = 0 inactive_guilds = 0 cfg.CONFIG['leave_inactive'] = [] for g in client.guilds: total_guilds += 1 if g and (not utils.guild_is_active(g) or g not in guilds): cfg.CONFIG['leave_inactive'].append(g.id) inactive_guilds += 1 if params_str == "go": try: await g.leave() except discord.errors.NotFound: pass if params_str == "go": await channel.send("Left {} of {} guilds.".format(inactive_guilds, total_guilds)) else: await channel.send("Will leave {} of {} guilds. " "Rerun command with 'go' at end to actually leave them.".format( inactive_guilds, total_guilds)) cfg.CONFIG['leave_inactive'] = [] except: await channel.send(traceback.format_exc()) await func.react(message, '❌')
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 run(c, ctx, params): if c not in commands: if 'dcnf' not in ctx['settings'] or ctx['settings']['dcnf'] is False: similar = sorted(commands, key=lambda x: SequenceMatcher(None, x, c).ratio(), reverse=True)[0] ratio = SequenceMatcher(None, similar, c).ratio() return False, "Sorry, `{}` is not a recognised command.{}".format( c, " Did you mean `{}{}`?".format(ctx['print_prefix'], similar) if ratio > 0.65 else "") else: return False, "NO RESPONSE" cmd = commands[c] if cmd.admin_required and not ctx['admin']: return False, ("Gabisa bikin channel default, cuma admin yg bisa ") restrictions = ctx['settings']['restrictions'] if 'restrictions' in ctx[ 'settings'] else {} if not ctx['admin'] and c in restrictions: roles = [r.id for r in ctx['message'].author.roles] if not any((r in roles) for r in restrictions[c]): return False, "You don't have permission to use that command." if cmd.sapphire_required and not ctx['sapphire']: return False, ( "That command is restricted to :gem: **Sapphire Patron** servers.\n" "Become a Sapphire Patron to support the development of this bot and unlock more ~~useless~~ " "amazing features: https://www.patreon.com/pixaal") elif cmd.gold_required and not ctx['gold']: return False, ( "That command is restricted to :credit_card: **Gold Patron** servers.\n" "Become a Gold Patron to support the development of this bot and unlock more ~~useless~~ " "amazing features: https://www.patreon.com/pixaal") if cmd.voice_required: v = ctx['message'].author.voice if v is not None and v.channel.id in get_secondaries( ctx['guild'], ctx['settings']): ctx['voice_channel'] = v.channel else: return False, "You need to be in one of my voice channels to use that command." if cmd.creator_only: vc = ctx[ 'voice_channel'] # all creator_only commands will also have voice_required creator_id = utils.get_creator_id(ctx['settings'], vc) ctx['creator_id'] = creator_id if not creator_id == ctx['message'].author.id and not ctx['admin']: creator_mention = None for m in vc.members: if m.id == creator_id: creator_mention = m.mention break return False, ( "Only the person who created this voice channel ({}) is allowed to do that.\n" "If you were the creator originally but then left the channel temporarily, " "the person at the top of the channel at the time became the new designated creator." "".format( creator_mention if creator_mention else "unknown member")) if len(params) < cmd.params_required: ctx['incorrect_command_usage'] = False await help_cmd.command.execute(ctx, [c]) return False, None try: r = await cmd.execute(ctx, params) # Run command except discord.errors.Forbidden: return False, "I don't have permission to do that :(" except Exception as e: error_text = "Server: `{}`\n`{}` with command `{}`, params_str: `{}`".format( ctx['guild'].id, type(e).__name__, c, ' '.join(params)) await admin_log(error_text, ctx['client']) log(error_text) import traceback error_text = traceback.format_exc() await admin_log(error_text, ctx['client']) log(error_text) return False, ( "A `{}` error occured :(\n" "Please ensure I have the correct permissions, check `{}help {}` for the correct command usage, " "and then try again. \nIf that still doesn't help, try asking in the support server: " "https://discord.gg/qhMrz6u".format( type(e).__name__, ctx['print_prefix'], c)) if r is None: # In case command didn't return success/response await admin_log( "Server: `{}`\nUnknown Error with command `{}`, params_str: {}". format(ctx['guild'].id, c, ' '.join(params)), ctx['client'], important=True) return False, ( "An unknown error occured :(\n" "Please ensure I have the correct permissions, check `{}help {}` for the correct command usage, " "and then try again. \nIf that still doesn't help, try asking in the support server: " "https://discord.gg/qhMrz6u".format(ctx['print_prefix'], c)) return r