async def delete_instance(self, ctx): instance_id = self.bot.cache._get_selected_instance(ctx.author.id) inst = Instance(instance_id) embed = base_embed( inst.id, title="Are you sure you want to permanently delete this instance?", description= "Deleting an instance will break the connection, remove all permissions and clear all the logs. This can NOT be reverted.\n\nReact with 🗑️ to confirm and delete this instance." ) try: msg = await ctx.author.send(embed=embed) except: msg = await ctx.send(embed=embed) await msg.add_reaction('🗑️') def check(reaction, user): return reaction.message.id == msg.id and str( reaction.emoji ) == "🗑️" and user.id == ctx.author.id and not user.bot try: await self.bot.wait_for('reaction_add', timeout=120.0, check=check) except: if isinstance(msg.channel, discord.TextChannel): await msg.clear_reactions() embed.description = "You took too long to respond. Execute the command again to retry." await msg.edit(embed=embed) else: if isinstance(msg.channel, discord.TextChannel): await msg.clear_reactions() embed = base_embed(inst.id, title="Instance deleted") self.bot.cache.delete_instance(instance_id) await msg.edit(embed=embed)
async def instance_config(self, ctx, key: str = None, value=None): instance_id = self.bot.cache._get_selected_instance(ctx.author.id) instance = Instance(instance_id) if key: key = key.lower() if key == None or key not in instance.config.keys(): embed = base_embed(instance.id, title='Config values') for key, value in instance.config.items(): value = str(value) if str(value) else "NULL" embed.add_field(name=key, value=value) embed = add_empty_fields(embed) await ctx.send(embed=embed) elif value == None: embed = base_embed( instance.id, title='Config values', description= f"> **Current value:**\n> {key} = {instance.config[key]}") desc = CONFIG_DESC[key] if key in CONFIG_DESC.keys( ) else f"**{key}**\nNo description found." embed.description = embed.description + "\n\n" + desc await ctx.send(embed=embed) else: old_value = instance.config[key] try: value = type(old_value)(value) except: raise commands.BadArgument( '%s should be %s, not %s' % (value, type(old_value).__name__, type(value).__name__)) else: if key == "guild_id": guild = self.bot.get_guild(int(value)) if not guild: raise commands.BadArgument( 'Unable to find a guild with ID %s' % value) member = guild.get_member(ctx.author.id) if not member: raise commands.BadArgument( 'You haven\'t joined that guild yourself') if not member.guild_permissions.administrator: raise commands.BadArgument( 'You need to have administrator permissions in that guild' ) instance.config[key] = value instance.store_config() if not old_value: old_value = "None" embed = base_embed(instance.id, title='Updated config') embed.add_field(name="Old Value", value=str(old_value)) embed.add_field(name="New value", value=str(value)) await ctx.send(embed=embed)
async def disconnect_instance(self, ctx): instance_id = self.bot.cache._get_selected_instance(ctx.author.id) inst = Instance(instance_id) self.bot.cache.instances[instance_id] = None embed = base_embed(instance_id, title="Instance was shut down") await ctx.send(embed=embed)
async def kick_from_squad(self, ctx, name_or_id: str, *, reason: str = None): inst = self.bot.cache.instance(ctx.author, ctx.guild.id).update() player = inst.get_player(name_or_id) if not player: raise commands.BadArgument("Player %s isn't online at the moment" % name_or_id) res = inst.rcon.remove_from_squad(player.steam_id) if "is not in a squad" in res: raise RconCommandError(res) warn_reason = "An Admin removed you from your squad." if reason: warn_reason = warn_reason + " Reason: " + reason inst.rcon.warn(player.steam_id, warn_reason) embed = base_embed(inst.id, title="Player removed from squad", description=res) await ctx.send(embed=embed) ServerLogs(inst.id).add( 'rcon', f'{ctx.author.name}#{ctx.author.discriminator} squad-kicked {player.name} for "{reason}"' )
def setup_embed(question): embed = discord.Embed( title=question, description= "Type your answer down below. You can change your answers later." ) if operation == 0: embed = discord.Embed( title=question, description= "Type your answer down below. You can change your answers later." ) embed.set_author(name="Creating your instance...") elif operation == 1: embed = base_embed( inst, title=question, description= "Type your answer down below. You can change your answers later." ) embed._author['name'] = f"Editing {inst.name}..." embed.set_footer( text= "This action will be canceled if the channel remains inactive for 2 minutes during its creation." ) return embed
async def maprotation(self, ctx): inst = self.bot.cache.instance(ctx.author.id, ctx.guild.id) embed = base_embed(inst.id, title="Custom Map Rotation") embed.description = "`r!rotation upload` - Upload a new custom rotation\n`r!rotation enable` - Enable the custom rotation\n`r!rotation disable` - Disable custom rotation\n`r!rotation download` - Download your custom rotation" if os.path.exists(Path(f'rotations/{str(inst.id)}.json')): if inst.map_rotation: embed.color = discord.Color.green() embed.title += " | Status: Enabled" else: embed.color = discord.Color.red() embed.title += " | Status: Disabled" try: maps = sorted( set([ str(entry) for entry in inst.map_rotation.get_entries() ])) except Exception as e: maps = ["Failed to fetch maps"] raise e embed.add_field(name="Maps in rotation:", value="\n".join(maps)) else: embed.title += " | Status: Unconfigured" await ctx.send(embed=embed)
async def enable(self, ctx): inst = self.bot.cache.instance(ctx.author.id, ctx.guild.id) if inst.map_rotation: await ctx.send( ':no_entry_sign: Custom Map Rotation is already enabled!') return path = Path(f'rotations/{str(inst.id)}.json') if not os.path.exists(path): await ctx.send( ':no_entry_sign: Upload a custom rotation first using `r!rotation upload`!' ) return inst.import_rotation(fp=path) Instance(inst.id).set_uses_custom_rotation(1) embed = base_embed(inst.id, title="Enabled Custom Map Rotation", color=discord.Color.green()) embed.description = "`r!rotation upload` - Upload a new custom rotation\n`r!rotation enable` - Enable the custom rotation\n`r!rotation disable` - Disable custom rotation\n`r!rotation download` - Download your custom rotation" try: maps = sorted( set([str(entry) for entry in inst.map_rotation.get_entries()])) except: maps = ["Failed to fetch maps"] embed.add_field(name="Maps in rotation:", value="\n".join(maps)) await ctx.send(embed=embed)
async def disband_squad(self, ctx, team_id: int, squad_id: int, *, reason: str = None): if team_id not in [1, 2]: raise commands.BadArgument('team_id needs to be either 1 or 2') inst = self.bot.cache.instance(ctx.author, ctx.guild.id).update() if team_id == 1: team = inst.team1 elif team_id == 2: team = inst.team2 squads = team.squads try: squad = [squad for squad in squads if squad.id == squad_id][0] except: raise commands.BadArgument('No squad found with this ID') players = inst.select(team_id=team_id, squad_id=squad_id) res = inst.rcon.disband_squad(team_id, squad_id) warn_reason = "An Admin disbanded the squad you were in." if reason: warn_reason = warn_reason + " Reason: " + reason for player in players: inst.rcon.warn(player.steam_id, warn_reason) embed = base_embed(inst.id, title="Squad disbanded", description=res) await ctx.send(embed=embed) ServerLogs(inst.id).add( 'rcon', f'{ctx.author.name}#{ctx.author.discriminator} disbanded {team.faction_short}/Squad{squad_id} for "{reason}"' )
async def set_max_player_limit(self, ctx, limit: int): inst = self.bot.cache.instance(ctx.author.id, ctx.guild.id) res = inst.rcon.set_max_player_limit(limit) embed = base_embed(inst.id, title="Max player limit changed", description=res) await ctx.send(embed=embed)
async def upload(self, ctx): if not ctx.message.attachments: await ctx.send( f":no_entry_sign: Please include your custom rotation as an attachment!" ) return attachment = ctx.message.attachments[0] if attachment.size > 1000000: await ctx.send( f":no_entry_sign: Invalid attachment!\n`File too big! Maximum is 1000000 bytes but received {str(attachment.size)} bytes`" ) return if not attachment.filename.endswith(".json"): extension = "." + attachment.filename.split(".")[-1] await ctx.send( f":no_entry_sign: Invalid attachment!\n`Invalid file extension! Expected .json but received {extension}`" ) return inst = self.bot.cache.instance(ctx.author.id, ctx.guild.id) content = str(await attachment.read(), 'utf-8') content = json.loads(content) inst.import_rotation(content=content) with open(Path(f'rotations/{str(inst.id)}.json'), 'w+') as f: f.write(json.dumps(content, indent=2)) Instance(inst.id).set_uses_custom_rotation(1) game = Instance(inst.id).game.upper() if game == 'SQUAD': valid_maps = MAPS_SQUAD elif game == 'BTW': valid_maps = MAPS_BTW elif game == 'PS': valid_maps = MAPS_PS else: valid_maps = [] embed = base_embed(inst.id, title="Uploaded and enabled Custom Map Rotation", color=discord.Color.green()) embed.description = "`r!rotation upload` - Upload a new custom rotation\n`r!rotation enable` - Enable the custom rotation\n`r!rotation disable` - Disable custom rotation\n`r!rotation download` - Download your custom rotation" try: maps = sorted( set([str(entry) for entry in inst.map_rotation.get_entries()])) for m in maps: if m not in valid_maps: maps[maps.index(m)] += " ⚠️" except: maps = ["Failed to fetch maps"] embed.add_field(name="Maps in rotation:", value="\n".join(maps)) if " ⚠️" in "\n".join(maps): embed.add_field( name='⚠️ Warning ⚠️', value= "Some maps are not recognized and could be invalid. Please verify that the marked layers are correct." ) await ctx.send(embed=embed)
async def connect_instance(self, ctx): instance_id = self.bot.cache._get_selected_instance(ctx.author) inst = Instance(instance_id) res = self.bot.cache._connect_instance(inst, return_exception=True) if isinstance(res, Exception): # Instance could not be connected raise res else: # Instance was successfully connected embed = base_embed(instance_id, title="Instance was successfully (re)connected") await ctx.send(embed=embed)
async def slomo(self, ctx, percentage: str): try: if percentage.endswith("%"): percentage = float(percentage[:-1]) / 100 else: percentage = float(percentage) except ValueError: raise commands.BadArgument('%s needs to be a percentage' % percentage) inst = self.bot.cache.instance(ctx.author, ctx.guild.id) res = inst.rcon.set_clockspeed(percentage) embed = base_embed(inst.id, title="Server clockspeed adjusted", description=res) await ctx.send(embed=embed)
async def instance_command(self, ctx, *, instance_id=None): if instance_id: await self.select_instance.__call__(ctx, instance_id=instance_id) else: current_instance = self.bot.cache._get_selected_instance(ctx.author) try: embed = base_embed(current_instance) except: embed = discord.Embed() embed.set_author(name="No instance selected") embed.set_footer(text='Use "r!inst help" to view a list of options') await ctx.send(embed=embed)
async def set_next_map(self, ctx, *, map_name: str): inst = self.bot.cache.instance(ctx.author.id, ctx.guild.id) res = inst.rcon.set_next_map(map_name) inst.next_map = map_name embed = base_embed(self.bot.cache.instance(ctx.author.id, ctx.guild.id).id, title="Queued the next map", description=res) await ctx.send(embed=embed) ServerLogs(inst.id).add( 'rcon', f'{ctx.author.name}#{ctx.author.discriminator} queued {map_name}')
async def broadcast(self, ctx, *, message: str): inst = self.bot.cache.instance(ctx.author, ctx.guild.id) res = inst.rcon.broadcast(message) embed = base_embed(self.bot.cache.instance(ctx.author, ctx.guild.id).id, title="Message broadcasted", description=res) await ctx.send(embed=embed) ServerLogs(inst.id).add( 'rcon', f'{ctx.author.name}#{ctx.author.discriminator} broadcasted "{message}"' )
async def restart_match(self, ctx): inst = self.bot.cache.instance(ctx.author, ctx.guild.id) res = inst.rcon.restart_match() embed = base_embed(self.bot.cache.instance(ctx.author, ctx.guild.id).id, title="Restarted the current match", description=res) await ctx.send(embed=embed) ServerLogs(inst.id).add( 'rcon', f'{ctx.author.name}#{ctx.author.discriminator} restarted the current match' )
async def warn(self, ctx, name_or_id: str, *, reason: str): inst = self.bot.cache.instance(ctx.author, ctx.guild.id).update() player = inst.get_player(name_or_id) if not player: raise commands.BadArgument("Player %s isn't online at the moment" % name_or_id) res = inst.rcon.warn(player.steam_id, reason) embed = base_embed(inst.id, title="Player warned", description=res) await ctx.send(embed=embed) ServerLogs(inst.id).add( 'rcon', f'{ctx.author.name}#{ctx.author.discriminator} warned {player.name} for "{reason}"' )
async def disable(self, ctx): inst = self.bot.cache.instance(ctx.author, ctx.guild.id) if inst.map_rotation == None: await ctx.send(':no_entry_sign: Custom Map Rotation is already disabled!') return inst.map_rotation = None Instance(inst.id).set_uses_custom_rotation(0) embed = base_embed(inst.id, title="Disabled Custom Map Rotation", color=discord.Color.red()) embed.description = "`r!rotation upload` - Upload a new custom rotation\n`r!rotation enable` - Enable the custom rotation\n`r!rotation disable` - Disable custom rotation\n`r!rotation download` - Download your custom rotation" await ctx.send(embed=embed)
def create_server_embed(self, inst): playercount = len(inst.players) if playercount >= 100: color = discord.Color.dark_red() elif playercount >= 80: color = discord.Color.red() elif playercount >= 50: color = discord.Color.orange() elif playercount >= 20: color = discord.Color.gold() elif playercount >= 10: color = discord.Color.green() elif playercount >= 3: color = discord.Color.dark_green() else: color = discord.Embed.Empty embed = base_embed(inst.id, color=color) embed.add_field(name="Players", value=f"{str(playercount)}/100") embed.add_field(name="Current Map", value=inst.current_map) embed.add_field(name="Next Map", value=inst.next_map) return embed
async def execute(self, ctx, *, cmd): inst = self.bot.cache.instance(ctx.author, ctx.guild.id) res = inst.rcon.exec_command(cmd) res = literal_eval(res) if not res: # An empty list was received res = "Empty response received" else: res = res[0].strip(b'\x00\x01').decode('utf-8') if len(res) > 1018: # too big to be sent in one embed field res = res[:1015] + "..." embed = base_embed(inst.id, title="Command executed") embed.add_field(name="Request", value=f"`{cmd}`", inline=False) embed.add_field(name="Response", value=f"```{res}```") await ctx.send(embed=embed)
async def map(self, ctx): inst = self.bot.cache.instance(ctx.author.id, ctx.guild.id) current_map = inst.current_map next_map = inst.next_map try: time_since_map_change = int( (datetime.now() - inst.last_map_change).total_seconds() / 60) except: time_since_map_change = -1 embed = base_embed( inst.id, title=f"Current map is {current_map}", description= f"Next map will be **{next_map}**\nMap last changed {str(time_since_map_change)}m ago" ) await ctx.send(embed=embed)
async def skip_match(self, ctx, *, map_name: str = ""): inst = self.bot.cache.instance(ctx.author.id, ctx.guild.id) if map_name: res = inst.rcon.switch_to_map(map_name) else: res = inst.rcon.end_match() embed = base_embed(self.bot.cache.instance(ctx.author.id, ctx.guild.id).id, title="Skipped the current match", description=res) await ctx.send(embed=embed) ServerLogs(inst.id).add( 'rcon', f'{ctx.author.name}#{ctx.author.discriminator} skipped the current match' )
async def switch_team(self, ctx, name_or_id: str, *, reason: str = None): inst = self.bot.cache.instance(ctx.author, ctx.guild.id).update() player = inst.get_player(name_or_id) if not player: raise commands.BadArgument("Player %s isn't online at the moment" % name_or_id) res = inst.rcon.change_team(player.steam_id) warn_reason = "An Admin switched your team." if reason: warn_reason = warn_reason + " Reason: " + reason inst.rcon.warn(player.steam_id, warn_reason) embed = base_embed(inst.id, title="Player switched", description=res) await ctx.send(embed=embed) ServerLogs(inst.id).add( 'rcon', f'{ctx.author.name}#{ctx.author.discriminator} team-switched {player.name} for "{reason}"' )
async def kick(self, ctx, name_or_id: str, *, reason: str = None): inst = self.bot.cache.instance(ctx.author.id, ctx.guild.id).update() player = inst.get_player(name_or_id) if not player: raise commands.BadArgument("Player %s isn't online at the moment" % name_or_id) if not reason: reason = "Kicked by a moderator" res = inst.rcon.kick(player.steam_id, reason) self.bot.cache.instance(ctx.author.id, ctx.guild.id).disconnect_player(player) embed = base_embed(inst.id, title="Player kicked", description=res) await ctx.send(embed=embed) ServerLogs(inst.id).add( 'rcon', f'{ctx.author.name}#{ctx.author.discriminator} kicked {player.name} for "{reason}"' )
async def squad(self, ctx, team_id: int, squad_id: int): if team_id not in [1, 2]: raise commands.BadArgument('team_id needs to be either 1 or 2') inst = self.bot.cache.instance(ctx.author, ctx.guild.id).update() if team_id == 1: team = inst.team1 elif team_id == 2: team = inst.team2 squad = None for itersquad in team.squads: if itersquad.id == squad_id: squad = itersquad break if not squad: raise commands.BadArgument( 'No squad was found with the given squad id') creator = squad.creator_name if squad.creator_name else inst.get_player( squad.creator) if not creator: creator = str(squad.creator) else: creator = f'{creator} ({squad.creator})' embed = base_embed( inst.id, title=squad.name, description= f"> Size: {str(len(squad))}\n> Squad ID: {str(squad_id)}\n> Team: {team.faction}\n> Created by: {creator}" ) players = [ inst.get_player(player_id) for player_id in squad.player_ids ] for player_num, player in enumerate(players): player_num += 1 if player_num == 1: player_num = "SQL" else: player_num = str(player_num) embed.add_field( name=f"{player_num} | {player.name}", value= f"*{str(player.steam_id)}*\n> Player ID: {str(player.player_id)}\n> Online For: {str(player.online_time())}m" ) # Add empty fields if needed to make the embed look nicer embed = add_empty_fields(embed) await ctx.send(embed=embed)
async def switch_squad(self, ctx, team_id: int, squad_id: int, *, reason: str = None): if team_id not in [1, 2]: raise commands.BadArgument('team_id needs to be either 1 or 2') inst = self.bot.cache.instance(ctx.author, ctx.guild.id).update() if team_id == 1: team = inst.team1 team_other = inst.team2 elif team_id == 2: team = inst.team2 team_other = inst.team1 squads = team.squads try: squad = [squad for squad in squads if squad.id == squad_id][0] except: raise commands.BadArgument('No squad found with this ID') players = inst.select(team_id=team_id, squad_id=squad_id) warn_reason = "An Admin switched your team." if reason: warn_reason = warn_reason + " Reason: " + reason for player in players: try: inst.rcon.change_team(player.steam_id) inst.rcon.warn(player.steam_id, warn_reason) except RconCommandError: pass embed = base_embed( inst.id, title=f"Switched {str(len(players))} players", description=f"from {team.faction} to {team_other.faction}") await ctx.send(embed=embed) ServerLogs(inst.id).add( 'rcon', f'{ctx.author.name}#{ctx.author.discriminator} team-switched {", ".join([player.name for player in players])} for "{reason}"' )
async def set_permissions(self, ctx, object, *, perms): object, object_type = await self.get_perms_object(ctx, object) perms = Permissions(**{p: True for p in re.split(', |,\n|,| |\n', perms)}) instance_id = ctx.bot.cache._get_selected_instance(ctx.author) instance = Instance(instance_id) embed = base_embed(instance) if object_type == 'default': old_perms = Permissions.from_int(instance.default_perms) instance.set_default_perms(int(perms)) embed.title = "Updated default permissions" else: old_perms = get_perms_entry(instance_id, object.id) set_perms(object.id, instance_id, int(perms), object_type) embed.title = f"Updated permissions for {object_type} {object.name}" embed.add_field(name='Old permissions', value=f'> {old_perms}') embed.add_field(name='New permissions', value=f'> {perms}') await ctx.send(embed=embed)
async def reset_permissions(self, ctx, object): object, object_type = await self.get_perms_object(ctx, object) instance_id = ctx.bot.cache._get_selected_instance(ctx.author) instance = Instance(instance_id) embed = base_embed(instance) new_perms = None if object_type == 'default': old_perms = Permissions.from_int(instance.default_perms) instance.set_default_perms(int(new_perms)) embed.title = "Removed default permissions" else: old_perms = get_perms_entry(instance_id, object.id) reset_perms(object.id, instance_id) embed.title = f"Removed permissions for {object_type} {object.name}" embed.add_field(name='Old permissions', value=f'> {old_perms}') embed.add_field(name='New permissions', value=f'> {new_perms}') await ctx.send(embed=embed)
async def warn_all(self, ctx, *, reason: str): inst = self.bot.cache.instance(ctx.author, ctx.guild.id).update() amount = 0 for player in inst.players: try: inst.rcon.warn(player.steam_id, reason) except: pass else: amount += 1 res = f"Warned {str(amount)}/{str(len(inst.players))} players for '{reason}'" embed = base_embed(inst.id, title="All players warned", description=res) await ctx.send(embed=embed) ServerLogs(inst.id).add( 'rcon', f'{ctx.author.name}#{ctx.author.discriminator} warned {str(amount)} players for "{reason}"' )
async def set_next_map(self, ctx, *, map_name: str): inst = self.bot.cache.instance(ctx.author, ctx.guild.id) instance_details = Instance( ctx.bot.cache._get_selected_instance(ctx.author, ctx.guild.id)) # check current game for instance select - squad uses another command if instance_details.game == 'squad': res = inst.rcon.set_next_layer(map_name) else: res = inst.rcon.set_next_map(map_name) inst.next_map = Map(map_name) embed = base_embed(self.bot.cache.instance(ctx.author, ctx.guild.id).id, title="Queued the next map", description=res) await ctx.send(embed=embed) ServerLogs(inst.id).add( 'rcon', f'{ctx.author.name}#{ctx.author.discriminator} queued {map_name}')