async def execute(ctx, params): params_str = ' '.join(params) guild = ctx['guild'] settings = ctx['settings'] limit = utils.strip_quotes(params_str) author = ctx['message'].author vc = ctx['voice_channel'] if limit: try: limit = abs(int(limit)) except ValueError: return False, "`{}` is not a number.".format(limit) else: limit = len(vc.members) if limit > 99: return False, "The user limit cannot be higher than 99." await vc.edit(user_limit=limit) if limit != 0: log_msg = "👪 {} (`{}`) set the user limit of \"**{}**\" (`{}`) to {}".format( func.user_hash(author), author.id, func.esc_md(vc.name), vc.id, limit) else: log_msg = "👨👩👧👦 {} (`{}`) removed the user limit of \"**{}**\" (`{}`)".format( func.user_hash(author), author.id, func.esc_md(vc.name), vc.id) await func.server_log(guild, log_msg, 2, settings) return True, None
async def execute(ctx, params): params_str = ' '.join(params) guild = ctx['guild'] settings = ctx['settings'] author = ctx['message'].author new_word = params_str.replace('\n', ' ') # Can't have newlines in channel name. new_word = utils.strip_quotes(new_word) previous_word = ("text" if 'text_channel_name' not in settings else func.esc_md(settings['text_channel_name'])) if not new_word: return False, ( "You need to define a new name, e.g. `{}textchannelname links` to make " "**links** shown instead of **{}**.".format( ctx['print_prefix'], previous_word)) settings['text_channel_name'] = new_word utils.set_serv_settings(guild, settings) e_new_word = func.esc_md(new_word) await func.server_log( guild, "💬 {} (`{}`) définissez le nom \"text\" du serveur sur **{}**". format(func.user_hash(author), author.id, e_new_word), 2, settings) for p, pv in settings['auto_channels'].items(): for s, sv in pv['secondaries'].items(): if 'tc' in sv: tc = guild.get_channel(sv['tc']) try: await tc.edit(name=utils.nice_cname(new_word)) except discord.errors.Forbidden: pass return True, ( "Done! From now on I'll use **{}** instead of **{}**.".format( e_new_word, previous_word))
async def execute(ctx, params): guild = ctx['guild'] settings = ctx['settings'] r = "Name: **{}** \tID: `{}`\n".format(func.esc_md(guild.name), guild.id) members = [m for m in guild.members if not m.bot] num_members = len(members) percent_members_online = len([m for m in members if m.status != discord.Status.offline]) / num_members * 100 r += "**{}** non-bot members, {}% currently online\n".format(num_members, round(percent_members_online)) r += "Server is a Gold Patron: **{}**\n".format("Yes" if func.is_gold(guild) else "No") r += "Server is a Sapphire Patron: {}\n".format( ("**Yes** +private bot" if cfg.SAPPHIRE_ID is not None else "**Yes**") if func.is_sapphire(guild) else "**No**" ) r += "\n**Known Channels:**\n" for p in settings['auto_channels']: pc = guild.get_channel(p) if pc: r += "{} (`{}`)".format(func.esc_md(pc.name), pc.id) if pc.category: r += " in category \"{}\"".format(func.esc_md(pc.category.name)) r += permission_checks(pc, guild.me) secondaries = settings['auto_channels'][p]['secondaries'] r += "\t {} sub-channel{}".format(len(secondaries), "s" if len(secondaries) != 1 else "") r += "\n" for s, v in secondaries.items(): sc = guild.get_channel(s) scc = guild.get_member(v['creator']) if sc: r += "\t ⮡ \t\"{}\" (`{}`)\t Created by: \"{}\" (\"{}\", `{}`){}\n".format( func.esc_md(sc.name), sc.id, func.esc_md(scc.display_name), func.user_hash(scc), scc.id, permission_checks(sc, guild.me) ) r = r.replace('➕', '+') # Make the default plus sign more visible return True, r
async def execute(ctx, params): params_str = ' '.join(params) guild = ctx['guild'] settings = ctx['settings'] author = ctx['message'].author new_word = params_str.replace('\n', ' ') # Can't have newlines in channel name. new_word = utils.strip_quotes(new_word) previous_word = "General" if 'general' not in settings else func.esc_md( settings['general']) if not new_word: return False, ( "You need to define a new word, e.g. `{}general Lounge` to make " "**Lounge** shown instead of **{}**.".format( ctx['print_prefix'], previous_word)) settings['general'] = new_word utils.set_serv_settings(guild, settings) e_new_word = func.esc_md(new_word) await func.server_log( guild, "🎮 {} (`{}`) set the server's \"General\" word to **{}**".format( func.user_hash(author), author.id, e_new_word), 2, settings) return True, ( "Done! From now on I'll use **{}** instead of **{}**.".format( e_new_word, previous_word))
async def on_guild_remove(guild): num_members = len([m for m in guild.members if not m.bot]) if cfg.SAPPHIRE_ID is None: settings = utils.get_serv_settings(guild) settings['left'] = datetime.now( pytz.timezone( cfg.CONFIG['log_timezone'])).strftime("%Y-%m-%d %H:%M") utils.set_serv_settings(guild, settings) log("Left guild {} `{}` with {} members".format( guild.name, guild.id, num_members)) await func.admin_log( ":new_moon: Left: **{}** (`{}`) - **{}** members".format( func.esc_md(guild.name), guild.id, num_members), client)
async def on_guild_join(guild): num_members = len([m for m in guild.members if not m.bot]) important = num_members > 5000 if cfg.SAPPHIRE_ID is None: settings = utils.get_serv_settings(guild) settings['left'] = False utils.set_serv_settings(guild, settings) log("Joined guild {} `{}` with {} members".format( guild.name, guild.id, num_members)) await func.admin_log( ":bell:{} Joined: **{}** (`{}`) - **{}** members".format( utils.guild_size_icon(num_members), func.esc_md(guild.name), guild.id, num_members), client, important=important)
async def execute(ctx, params): guild = ctx['guild'] settings = ctx['settings'] author = ctx['message'].author vc = ctx['voice_channel'] for p, pv in settings['auto_channels'].items(): for s, sv in pv['secondaries'].items(): if s == vc.id: if 'priv' in sv and sv['priv']: return False, ( "Your channel is already private. " "Use `{}public` to make it public again.".format( ctx['print_prefix'])) try: await vc.set_permissions(author, connect=True) await vc.set_permissions(guild.default_role, connect=False) except discord.errors.Forbidden: return False, ( "I don't have permission to do that." "Please make sure I have the *Manage Roles* permission in this server and category." ) settings['auto_channels'][p]['secondaries'][s]['priv'] = True settings['auto_channels'][p]['secondaries'][s]['msgs'] = ctx[ 'channel'].id utils.set_serv_settings(guild, settings) cfg.PRIV_CHANNELS[s] = { 'creator': author, 'voice_channel': vc, 'primary_id': p, 'text_channel': ctx['channel'], 'guild_id': guild.id, 'request_time': time(), 'prefix': ctx['print_prefix'], } return True, ( "Your channel is now private!\n" "A \"**⇧ Join {}**\" channel will appear below your one shortly. " "When someone enters this channel to request to join you, " "I'll send a message here asking you to approve or deny their request.\n" "Use `{}public` to make it public again." "".format(func.esc_md(author.display_name), ctx['print_prefix'])) return False, "It doesn't seem like you're in a voice channel anymore."
async def execute(ctx, params): guild = ctx['guild'] settings = ctx['settings'] author = ctx['message'].author vc = ctx['voice_channel'] for p, pv in settings['auto_channels'].items(): for s, sv in pv['secondaries'].items(): if s == vc.id: if 'priv' in sv and sv['priv']: return False, ( "Votre chaîne est déjà privée." "Utilisez `{}public` pour le rendre à nouveau public.". format(ctx['print_prefix'])) try: await vc.set_permissions(author, connect=True) await vc.set_permissions(guild.default_role, connect=False) except discord.errors.Forbidden: return False, ( "I don't have permission to do that." "Please make sure I have the *Manage Roles* permission in this server and category." ) settings['auto_channels'][p]['secondaries'][s]['priv'] = True settings['auto_channels'][p]['secondaries'][s]['msgs'] = ctx[ 'channel'].id utils.set_serv_settings(guild, settings) cfg.PRIV_CHANNELS[s] = { 'creator': author, 'voice_channel': vc, 'primary_id': p, 'text_channel': ctx['channel'], 'guild_id': guild.id, 'request_time': time(), 'prefix': ctx['print_prefix'], } return True, ( "Votre canal est désormais privée!\n" "Un canal \"**⇩ Rejoignez {}**\" chaîne apparaîtra au-dessus de votre chaîne sous peu. " "Lorsque quelqu'un entre sur ce canal pour demander à vous rejoindre, " "Je vais envoyer un message ici vous demandant d'approuver ou de refuser leur demande.\n" "Utilisez `{}public` pour le rendre à nouveau public." "".format(func.esc_md(author.display_name), ctx['print_prefix'])) return False, "It doesn't seem like you're in a voice channel anymore."
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_reaction_add(reaction, user): if not client.is_ready(): return if user.bot: return guild = reaction.message.guild guilds = func.get_guilds(client) if guild not in guilds: return if reaction.message.id in cfg.VOTEKICKS: if reaction.emoji == '✅': vk = cfg.VOTEKICKS[reaction.message.id] if time() < vk['end_time']: if user not in vk['in_favor'] and user in vk['participants']: vk['in_favor'].append(user) log( "{} voted to kick {}".format( user.display_name, vk['offender'].display_name), guild) return to_delete = [] joins_in_progress = list(cfg.JOINS_IN_PROGRESS.keys()) for uid in joins_in_progress: try: j = cfg.JOINS_IN_PROGRESS[uid] except KeyError: print("Ignoring error:") traceback.print_exc() continue if reaction.message.id == j['mid'] and user.id == j['creator'].id: reacted = False if reaction.emoji == '✅': reacted = True try: await j['vc'].set_permissions(j['requester'], connect=True) await j['requester'].move_to(j['vc']) except discord.errors.Forbidden: await j['msg'].edit( content= ":warning: I don't have permission to move {} to **{}** :(" .format(j['requester'].mention, func.esc_md(j['vc'].name))) except discord.errors.HTTPException as e: await j['msg'].edit( content= ":warning: Unable to move {} to {}'s channel ({})". format(j['requester'].mention, j['creator'].mention, e.text)) else: await j['msg'].delete(delay=5) elif reaction.emoji in ['❌', '⛔']: reacted = True try: await j['requester'].move_to(None) except discord.errors.Forbidden: pass except discord.errors.HTTPException: pass else: await j['msg'].edit( content="Sorry {}, your request to join {} was denied." .format(j['requester'].mention, j['creator'].mention)) if reaction.emoji == '⛔': try: await j['jc'].set_permissions(j['requester'], connect=False) except Exception as e: await j['msg'].edit( content="{}\nFailed to block user ({}).".format( j['msg'].content, type(e).__name__)) if reacted: to_delete.append(uid) try: await j['msg'].remove_reaction('✅', guild.me) await j['msg'].remove_reaction('❌', guild.me) await j['msg'].remove_reaction('⛔', guild.me) except discord.errors.Forbidden: # Shouldn't have an issue removing your own reactions, but apparently sometimes you do. pass except discord.errors.NotFound: pass for uid in to_delete: try: del cfg.JOINS_IN_PROGRESS[uid] except KeyError: pass # Already deleted
async def execute(ctx, params): channel = ctx['channel'] author = ctx['message'].author if not params: support_server_id = 601015720200896512 if not ctx['admin'] and ctx['guild'].id != support_server_id: e = discord.Embed(color=discord.Color.from_rgb(205, 220, 57)) e.title = "Auto Voice Channels" e.description = ( "I'm a bot that allows you to dynamically and infinitely create voice channels as you need them, " "and automatically delete them as soon as they are no longer used.\n\n" "Simply join a voice channel of mine and I'll create a new one for you and move you to it." ) text = ( " · **<PREFIX>lock** - " "Lock the user limit of your voice channel so no more people can join. " "Use **<PREFIX>unlock** to remove the limit.\n\n" " · **<PREFIX>limit `N`** - " "Set the user limit of your channel to a particular number. " "Use **<PREFIX>unlock** to remove the limit.\n\n" " · **<PREFIX>private** - " "Make your voice channel private, preventing anyone from joining you directly. " "Creates a \"⇩ Join {}\" channel above yours so people can request to join you.\n\n" " · **<PREFIX>kick `@USER`** - " "Start a votekick to remove someone from your channel.\n\n" " · **<PREFIX>transfer `@USER`** - " "Transfer ownership of your channel to someone else.\n\n". format(esc_md(author.display_name))) if ctx['gold']: text += ( " · **<PREFIX>name** - Change the name of your voice channel.\n\n" " · **<PREFIX>nick** - Set what channels that show the creator's name will call you.\n\n" " · **<PREFIX>bitrate** - Set a server-wide custom bitrate (in kbps) for yourself that will be " "used for any channels you join.\n\n") text += ( " · **<PREFIX>invite** - Invite me to another server!\n\n" " · **<PREFIX>help `command`** - Get more info about a particular command." ) text = text.replace('<PREFIX>', ctx['print_prefix']) e.add_field(name="Commands:", value=text) try: await channel.send(embed=e) except discord.errors.Forbidden: log("Forbidden to echo", channel.guild) await dm_user( author, "I don't have permission to send messages in the " "`#{}` channel of **{}**.".format(channel.name, channel.guild.name)) return False, "NO RESPONSE" return True, "NO RESPONSE" can_embed = channel.permissions_for(ctx['guild'].me).embed_links with open("docs.md", 'r', encoding='utf8') as f: docs = f.read() sections = docs.split('** **') for i, s in enumerate(sections): s = s.replace("@Auto Voice Channels ", "@{} ".format(ctx['message'].guild.me.display_name)) s = s.replace("vc/", esc_md(ctx['prefix_p'])) s = s.replace("@pixaal", author.mention) s = s.replace(" :)", " :slight_smile:") s = s.replace("**Gold Patron**", ":credit_card: **Gold Patron**") s = s.replace("Change the prefix of the bot (default is", "Change the prefix of the bot (currently") s = s.replace("<https://www.patreon.com/pixaal>", "https://www.patreon.com/pixaal") # Always embed if s.startswith("\n**-- Commands --**\n") and can_embed: lines = [l for l in s.split('\n') if l != ""] parts = [] title = [] end_of_title = False cmds = [] for l in lines: if not l.startswith('`'): if end_of_title: parts.append(["** **\n" + '\n'.join(title), cmds]) title = [] cmds = [] end_of_title = False title.append(l) else: end_of_title = True cmds.append(l) parts.append(["** **\n" + '\n'.join(title), cmds]) for j, p in enumerate(parts): embed = discord.Embed( color=discord.Color.from_rgb(205, 220, 57)) for c in p[1]: cmd_name, cmd_desc = c.split(" - ", 1) embed.add_field(name=" · " + cmd_name, value=cmd_desc) try: await channel.send(content=p[0].replace( "Commands --**", "Commands --**\n"), embed=embed) except discord.errors.Forbidden: log("Forbidden to echo", channel.guild) await dm_user( author, "I don't have permission to send messages in the " "`#{}` channel of **{}**.".format( channel.name, channel.guild.name)) return False, "NO RESPONSE" continue if i == 0: s = '\n'.join( s.strip('\n').split('\n') [:-1]) # Remove last line of first section (gfycat embed) s += "\nhttps://gfycat.com/latemealyhoneyeater" else: s = '** **' + s echo_success = await echo(s, channel, author) if not echo_success: return False, "NO RESPONSE" return True, "NO RESPONSE" else: from commands import commands c = params[0] if c in commands: replacements = { "<PREFIX>": ctx['print_prefix'], "<COMMAND>": c, "<USER>": author.mention, } help_text = commands[c].help_text for part in help_text: e = discord.Embed(color=discord.Color.from_rgb(205, 220, 57)) content = None if 'incorrect_command_usage' in ctx and part == help_text[0]: content = "Incorrect command usage, here's some info about the `{}` command:".format( c) if part == help_text[-1]: e.set_footer( text= "More help: discord.io/DotsBotsSupport \nSupport me: patreon.com/pixaal", icon_url=ctx['guild'].me.avatar_url_as(size=32)) for i, p in enumerate(part): t = ("⠀\n" + p[0] ) if i != 0 and not p[0].startswith(" · ") else p[0] d = ( p[1] + "\n⠀" ) if i == len(part) - 1 and part == help_text[-1] else p[1] if t == 'title': e.title = d else: for r, rv in replacements.items(): t = t.replace(r, rv) d = d.replace(r, rv) e.add_field(name=t, value=d, inline=False) try: await channel.send(content=content, embed=e) except discord.errors.Forbidden: log("Forbidden to echo", channel.guild) await dm_user( author, "I don't have permission to send messages in the " "`#{}` channel of **{}**.".format( channel.name, channel.guild.name)) return False, "NO RESPONSE" except Exception: log("Failed to echo", channel.guild) print(traceback.format_exc()) return False, "NO RESPONSE" if commands[c].sapphire_required and not ctx['sapphire']: await channel.send( "**Note:** This 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 commands[c].gold_required and not ctx['gold']: await channel.send( "**Note:** This 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>") return True, "NO RESPONSE" elif c == 'expressions': e = discord.Embed(color=discord.Color.from_rgb(205, 220, 57)) e.title = "Template Expressions" e.description = ( "Expressions are a powerful way to set the channel name based on certain conditions, such as whether " "or not the creator has a particular role, what game is being played, and the party size.\n\n" "Expressions must be in the following form:\n" "```" "{{CONDITION ?? TRUE // FALSE}}" "```\n" "If the `CONDITION` part is met, whatever you wrote in the `TRUE` part will be added to the channel " "name, otherwise the `FALSE` part will be used instead. The `FALSE` part is optional and can be " "left out (e.g. `{{CONDITION ?? TRUE}}`).\n\n" "Anything at all can be written inside the `TRUE`/`FALSE` parts, including other template variables " "like `@@num@@` or `@@game_name@@`, or even other nested expressions, " "however only a certain things may be used as the `CONDITION`:\n\n" ) e.add_field( name=" · `ROLE:role id`", value= "Check whether or not the creator has a particular role.\n\n") e.add_field( name=" · `GAME=game name`", value= "Check if the game that users in the channel are playing (the same one that " "`@@game_name@@` returns, including aliases) matches **exactly** the text provided.\n" "You can also use `!=` instead of `=` to match anything **other** than exactly the text provided, " "or `:` to match anything that **contains** the text provided. " "E.g. `GAME:Call of Duty` will match with *\"Call of Duty: Modern Warfare\"*, " "but `GAME=Call of Duty` will not.\n\n") e.add_field( name=" · `LIVE`", value= "Whether or not the creator of the channel is streaming. Use `LIVE_DISCORD` to only detect " "discord's \"Go Live\" streams, or `LIVE_EXTERNAL` for Twitch. `LIVE` will include both.\n\n" ) e.add_field( name=" · `PLAYERS>number`", value= "💎 [*patrons only.*](https://patreon.com/pixaal) Check if the number of players in your game " "(determined either by Discord Rich Presence or the game activity statuses of members in the channel) " "is greater than the number provided. You can also use `<`, `<=`, `>=`, `=` and `!=`.\n\n" ) e.add_field( name=" · `MAX>number`", value= "💎 [*patrons only.*](https://patreon.com/pixaal) Check if the maximum number of players " "allowed in your game (determined by Discord Rich Presence or the channel limit) is greater than the " "number provided. You can also use `<`, `<=`, `>=`, `=` and `!=`.\n\n" ) e.add_field( name=" · `RICH`", value= "💎 [*patrons only.*](https://patreon.com/pixaal) Whether or not the current game uses " "Discord Rich Presence, which means `@@num_playing@@`, `@@party_size@@`, `@@party_state@@`, and " "`@@party_details@@` should have reliable values.\n\n") try: await channel.send(embed=e) except discord.errors.Forbidden: log("Forbidden to echo", channel.guild) await dm_user( author, "I don't have permission to send messages in the " "`#{}` channel of **{}**.".format(channel.name, channel.guild.name)) return False, "NO RESPONSE" e = discord.Embed(color=discord.Color.from_rgb(205, 220, 57)) e.title = "Examples" e.description = ( "```{{GAME:Left 4 Dead ?? [@@num_playing@@/4]}}```" "```{{LIVE??🔴 @@stream_name@@}}```" "```{{PLAYERS=1 ?? LFG}}```" "```{{PLAYERS<=20 ?? ## [@@game_name@@] // It's a party!}}```" "```{{MAX=@@num_playing@@ ?? (Full) // (@@num_playing@@)}}```" "```{{RICH??@@party_details@@{{MAX>1?? (@@num_playing@@/@@party_size@@)}}}}```" "```{{ROLE:601025860614750229 ?? {{ROLE:615086491235909643??[UK] // {{ROLE:607913610139664444??[DE] // " "[EU]}}}}}}```\n" "The spaces around the `??` and `//` improve readability but may not be desired if you do not want any " "spaces around the result.\n\n" "If you have a question or need any help setting up an expression, " "please ask me in the [support server](https://discord.io/DotsBotsSupport). " "I'd be happy to add any extra variables you need.") await channel.send(embed=e) return True, "NO RESPONSE" else: if 'dcnf' not in ctx[ 'settings'] or ctx['settings']['dcnf'] is False: return False, ( "`{}` is not a recognized command. Run '**{}help**' " "to get a list of commands".format(c, ctx['print_prefix'])) else: return False, "NO RESPONSE"
async def execute(ctx, params): settings = ctx['settings'] asip = settings['asip'] if 'asip' in settings else False c = ctx['voice_channel'] template = settings['channel_name_template'] for p, v in settings['auto_channels'].items(): for s in v['secondaries']: if s == c.id: if 'template' in v: template = v['template'] break r = "Current channel name: **{}**\n".format(func.esc_md(c.name)) r += "Channel name template: **{}**\n\n".format(func.esc_md(template)) r += "Bitrate: **{0:.2f}** kbps\n".format(c.bitrate / 1000) r += "Custom bitrates:" any_bitrates = False for m in c.members: if 'custom_bitrates' not in settings: break if str(m.id) in settings['custom_bitrates']: any_bitrates = True r += "\n:white_small_square: {}: **{}**".format( func.esc_md(m.display_name), settings['custom_bitrates'][str(m.id)]) if not any_bitrates: r += " **None**" r += "\n\n" r += "Game activity:" games = [] for m in c.members: if m.activity and m.activity.name and not m.bot: act = m.activity games.append(act.name) r += "\n:white_small_square: {}: **{}**".format( func.esc_md(m.display_name), func.esc_md(act.name)) r += '\t' if hasattr(act, 'state') and act.state: r += " {}".format(func.esc_md(act.state)) if hasattr(act, 'details') and act.details: r += " ({})".format(func.esc_md(act.details)) if hasattr(act, 'party') and act.party: r += " \tParty: " r += "" if 'id' not in act.party else "`{}` ".format( act.party['id']) r += "" if 'size' not in act.party else ('/'.join( str(v) for v in act.party['size'])) if not games: r += " **None**" games = func.get_channel_games(c) for g in games: party = func.get_party_info(c, g, asip, default="None") if party: r += "\n\n**{}**\n".format(g) r += "Size: **{}**".format(party['size']) r += " *(includes {} user{} with no status)*\n".format( party['sneakies'], '' if party['sneakies'] == 1 else 's') if party['sneakies'] else "\n" r += "State: **{}**\n".format(party['state']) r += "Details: **{}**".format(party['details']) aliases = {} for g in games: if g in settings['aliases']: aliases[g] = settings['aliases'][g] if aliases: r += "\n\n" r += "Aliases for these games:" for g, a in aliases.items(): r += "\n:white_small_square: {}: **{}**".format( func.esc_md(g), func.esc_md(a)) return True, r