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 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 logs(self, ctx, category: str = None): inst = self.bot.cache.instance(ctx.author, ctx.guild.id) await self._query(inst) if category != None: category = category.lower() if category in ["export", "file"]: inst_name = Instance(inst.id).name log_file = discord.File(fp=ServerLogs(inst.id).export(), filename=f"{inst_name}_log.txt") await ctx.send(file=log_file) else: logs = ServerLogs(inst.id).get_logs(category) if not logs: await ctx.send( f"{config.get('error_message_emoji')} No logs could be found!" ) return output = "" for log in logs[::-1]: message = format_log(log) + "\n" if len(message + output) + 12 > 2000: break else: output = message + output output = "```json\n" + output + "```" await ctx.send(output)
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 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 list_instances(self, ctx): instances = get_available_instances(ctx.author.id, ctx.guild.id) current_instance = self.bot.cache._get_selected_instance(ctx.author.id) try: current_instance = Instance(current_instance).name except: current_instance = "None" embed = discord.Embed( title=f"Available Instances ({str(len(instances))})", description=f'> Currently selected: {current_instance}') embed.set_author(name=str(ctx.author), icon_url=ctx.author.avatar_url) for i, (inst, perms) in enumerate(instances): try: self.bot.cache.instance(inst.id, by_inst_id=True) except: availability = "\🔴" else: availability = "\🟢" perms = ", ".join( [perm for perm, val in perms_to_dict(perms).items() if val]) embed.add_field(name=f"{str(i+1)} | {inst.name} {availability}", value=f"> **Perms:** {perms}") embed = add_empty_fields(embed) await ctx.send(embed=embed)
def base_embed(instance, title: str = None, description: str = None, color=discord.Embed.Empty): if isinstance(instance, int): instance = Instance(instance) embed = EmbedMenu(title=title, description=description, color=color) embed.set_author(name=instance.name, icon_url=GAME_IMAGES[instance.game]) return embed
async def connect_instance(self, ctx): instance_id = self.bot.cache._get_selected_instance(ctx.author.id) 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 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 map_changed(self, new_map): self._decrease_cooldown() self.cooldowns[str(new_map)] = 0 if str(new_map) == str(self.next_map) or ( self.next_map and not self.next_map.validate(len( self.players))) or self.is_transitioning: self.next_map = self._get_next_map() if self.next_map: instance_details = Instance(self.id) if instance_details.game == 'squad': self.rcon.set_next_layer(self.next_map) else: self.rcon.set_next_map(self.next_map) return self.next_map
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}')
async def skip_match(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)) if map_name: # check current game for instance select - squad uses another command if instance_details.game == 'squad': res = inst.rcon.switch_to_layer(map_name) else: res = inst.rcon.switch_to_map(map_name) else: res = inst.rcon.end_match() embed = base_embed(self.bot.cache.instance(ctx.author, 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 check_server(self): for inst in self.bot.cache.instances.values(): if inst: await self._query(inst) try: max_id = self.last_seen_id[inst.id] except KeyError: max_id = ServerLogs(inst.id)._get_max_log_id() new_max_id = max_id new_logs = [] else: new_max_id, new_logs = ServerLogs( inst.id).get_logs_after(max_id) config = Instance(inst.id).config guild = self.bot.get_guild(config['guild_id']) if guild and new_logs: # Note: Chat logs are handled alongside the triggers channel_joins = guild.get_channel( config['channel_log_joins']) channel_match = guild.get_channel( config['channel_log_match']) channel_rcon = guild.get_channel( config['channel_log_rcon']) if channel_rcon: default_embed = base_embed(inst.id) logs = [ log for log in new_logs if log['category'] == 'rcon' ] for log in logs: embed = default_embed embed.color = discord.Color.teal() embed.title = log['message'] embed.set_footer( text= f"Recorded at {log['timestamp'].strftime('%a, %b %d, %Y %I:%M %p')}" ) await channel_match.send(embed=embed) if channel_joins: default_embed = base_embed(inst.id) logs = [ log for log in new_logs if log['category'] == 'joins' ] if logs: joins = [ log['message'] for log in logs if log['message'].endswith(' connected') ] leaves = [ log['message'] for log in logs if not log['message'].endswith(' connected') ] embed = default_embed embed.set_footer( text= f"Recorded at {logs[-1]['timestamp'].strftime('%a, %b %d, %Y %I:%M %p')}" ) if joins: embed.color = discord.Color.dark_green() embed.description = "\n".join(joins) await channel_match.send(embed=embed) if leaves: embed.color = discord.Embed.Empty embed.description = "\n".join(leaves) await channel_match.send(embed=embed) if channel_match: default_embed = base_embed(inst.id) logs = [ log for log in new_logs if log['category'] == 'match' ] for log in logs: embed = default_embed embed.color = discord.Color.from_rgb(255, 255, 255) embed.title = log['message'] embed.set_footer( text= f"Recorded at {log['timestamp'].strftime('%a, %b %d, %Y %I:%M %p')}" ) await channel_match.send(embed=embed) self.last_seen_id[inst.id] = new_max_id
async def config_menu(self, ctx, config_title, config_key, option: str = None, value=None): inst_id = self.bot.cache._get_selected_instance( ctx.author.id, ctx.channel.id) inst = Instance(inst_id) CONFIG_KEY = str(config_key) CONFIG_TITLE = str(config_title) if option: option = option.replace(CONFIG_KEY, "") key = CONFIG_KEY + option def update_value(option, value): option = option.replace(CONFIG_KEY, "") key = CONFIG_KEY + option if key not in inst.config.keys(): raise KeyError("Config option %s does not exist" % key) old_value = inst.config[key] try: value = type(inst.config[key])(value) except ValueError: raise commands.BadArgument( "Value should be %s, not %s" % (type(inst.config[key]).__name__, type(value).__name__)) inst.config[CONFIG_KEY + option] = value inst.store_config() return old_value, value if not option: embed = base_embed( inst, title=f"{CONFIG_TITLE} Configuration", description= f"To edit values, react with the emojis below or type `r!{ctx.command.name} <option> <value>`" ) for k, v in inst.config.items(): if k.startswith(CONFIG_KEY): try: option_info = CONFIGS[k] except KeyError: continue value = inst.config[k] if inst.config[k] else "None" embed.add_option( option_info["emoji"], title=option_info['name'], description= f"ID: {k.replace(CONFIG_KEY, '')}\nValue: `{value}`\n\n*{option_info['short_desc']}*" ) reaction = await embed.run(ctx) if reaction: (key, info) = [(k, v) for k, v in CONFIGS.items() if v['emoji'] == str(reaction.emoji)][0] option = key.replace(CONFIG_KEY, "") embed = base_embed( inst, title=f"Editing value {key}... ({info['name']})", description= f"{get_name(ctx.author)}, what should the new value be? To cancel, type \"cancel\"." ) msg = await ctx.send(embed=embed) def check(m): return m.author == ctx.author and m.channel == ctx.channel try: m = await self.bot.wait_for('message', timeout=120, check=check) except: await msg.edit(embed=base_embed( inst, description= f"{get_name(ctx.author)}, you took too long to respond." )) else: if m.content.lower() == "cancel": embed.description = "You cancelled the action." embed.color = discord.Color.dark_red() await msg.edit(embed=embed) return value = m.content old_value, value = update_value(option, value) embed = base_embed(inst, title=f'Updated {CONFIGS[key]["name"]}') embed.add_field(name="Old Value", value=str(old_value)) embed.add_field(name="New value", value=str(value)) await ctx.send(embed=embed) elif value == None: embed = base_embed( inst, title=f'Chat Alerts: {option}', description=f"> **Current value:**\n> {inst.config[key]}") desc = CONFIGS[key]['long_desc'] if key in CONFIGS.keys( ) and CONFIGS[key][ 'long_desc'] else f"**{key}**\nNo description found." embed.description = embed.description + "\n\n" + desc await ctx.send(embed=embed) else: old_value, value = update_value(option, value) embed = base_embed(inst, title=f'Updated {CONFIGS[key]["name"]}') 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 guild_permissions(self, ctx, operation: str = '', value: int = None): # List guild permissions if operation.lower() in ['', 'list', 'view', 'show']: instances = get_guild_instances(ctx.guild.id) if instances: embed = discord.Embed(title="Standard guild permissions") embed.set_author(icon_url=ctx.guild.icon_url, name=ctx.guild.name) for i, (instance, perms) in enumerate(instances): perms = ", ".join([ perm for perm, val in perms_to_dict(perms).items() if val ]) embed.add_field(name=f"{str(i+1)} | {instance.name}", value=f"> **Perms:** {perms}") embed = add_empty_fields(embed) else: embed = discord.Embed( title="Standard guild permissions", description= "There aren't any instances assigned to this guild just yet." ) embed.set_author(icon_url=ctx.guild.icon_url, name=ctx.guild.name) await ctx.send(embed=embed) # Set guild permissions for the selected instance elif operation.lower() in ['set']: instance = Instance( self.bot.cache._get_selected_instance(ctx.author.id)) # Update the guild permissions for the selected instance if value != None and int(value) >= 0 and int(value) <= 31: old_value = instance.default_perms instance.set_default_perms(value) embed = base_embed( instance.id, title=f"Changed guild permissions for {ctx.guild.name}") old_perms = ", ".join([ perm for perm, val in perms_to_dict(old_value).items() if val ]) new_perms = ", ".join([ perm for perm, val in perms_to_dict(value).items() if val ]) embed.add_field(name="Old Perms", value=f"> {old_perms}") embed.add_field(name="New Perms", value=f"> {new_perms}") await ctx.send(embed=embed) # Error else: raise commands.BadArgument("Permissions value out of range") # Unknown operation else: raise commands.BadArgument( 'Operation needs to be either "list" or "set", not "%s"' % operation)
async def permissions_group(self, ctx, user: discord.Member = None, operation: str = "", value: int = None): instance_id = self.bot.cache._get_selected_instance(ctx.author.id) instance = Instance(instance_id) # Limit command usage when missing permissions if not has_perms(ctx, instance=True): user = ctx.author operation = "" value = None # Default user to message author if not user: user = ctx.author # List all instances this user has access to here if operation.lower() == '': instances = get_available_instances(user.id, ctx.guild.id) if instances: embed = discord.Embed(title=f"Permissions in {ctx.guild.name}") embed.set_author(icon_url=user.avatar_url, name=f"{user.name}#{user.discriminator}") for i, (instance, perms) in enumerate(instances): perms = ", ".join([ perm for perm, val in perms_to_dict(perms).items() if val ]) embed.add_field(name=f"{str(i+1)} | {instance.name}", value=f"> **Perms:** {perms}") embed = add_empty_fields(embed) else: embed = discord.Embed( title=f"Permissions in {ctx.guild.name}", description= "You don't have access to any instances yet!\n\nInstances can be created by server owners, assuming they are an administrator of that guild.\n`r!instance create" ) embed.set_author(icon_url=user.avatar_url, name=f"{user.name}#{user.discriminator}") await ctx.send(embed=embed) # List user permissions for this user elif operation.lower() in ['list', 'view', 'show']: perms = get_perms(user.id, -1, instance_id, is_dict=False) perms_dict = perms_to_dict(perms) perms_str = ", ".join( [perm for perm, val in perms_dict.items() if val]) embed = base_embed( instance.id, title= f'Permission overwrites for {user.name}#{user.discriminator}', description= f"> Current value: {str(perms)}\n> Perms: {perms_str}") await ctx.send(embed=embed) # Set user permissions for the selected instance elif operation.lower() in ['set']: # Update the user permissions for the selected instance if value != None and int(value) >= 0 and int(value) <= 31: old_perms = ", ".join([ perm for perm, val in get_perms(user.id, -1, instance_id).items() if val ]) new_perms = ", ".join([ perm for perm, val in perms_to_dict(value).items() if val ]) set_player_perms(user.id, instance_id, value) embed = base_embed( instance.id, title= f"Changed permission overwrites for {user.name}#{user.discriminator}" ) embed.add_field(name="Old Perms", value=f"> {old_perms}") embed.add_field(name="New Perms", value=f"> {new_perms}") await ctx.send(embed=embed) # Error else: raise commands.BadArgument("Permissions value out of range") # Reset user permissions for the selected instance elif operation.lower() in ['reset']: reset_player_perms(user.id, instance_id) embed = base_embed( instance.id, title= f"Removed permission overwrites for {user.name}#{user.discriminator}" ) await ctx.send(embed=embed) # Unknown operation else: raise commands.BadArgument( 'Operation needs to be either "list" or "set" or "reset", not "%s"' % operation)
async def ask_server_info(self, ctx, operation=0): try: await ctx.author.send(ctx.author.mention) except: await ctx.send( f":no_entry_sign: {ctx.author.mention}, please enable DMs") return if operation not in [0, 1]: operation = 0 if operation == 1: inst = Instance( self.bot.cache._get_selected_instance(ctx.author.id, ctx.guild.id)) # Create embed 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 ask_game(): embed = setup_embed( "What game is your server for, Squad, PS or BTW?") msg = await ctx.author.send(embed=embed) def check(message): return message.channel == msg.channel and message.author == ctx.author answer = "" while answer not in ['squad', 'ps', 'btw']: answer = await self.bot.wait_for('message', check=check, timeout=120.0) answer = answer.content.lower() if answer not in ['squad', 'ps', 'btw']: await ctx.author.send( f'Send either "Squad", "PS" or "BTW", not "{answer}"') return answer async def ask_address_and_port(): async def ask_address(): embed = setup_embed("What is your server's IP address?") msg = await ctx.author.send(embed=embed) def check(message): return message.channel == msg.channel and message.author == ctx.author answer = await self.bot.wait_for('message', check=check, timeout=120.0) return answer.content async def ask_port(): embed = setup_embed("What is your server's RCON port?") msg = await ctx.author.send(embed=embed) def check(message): return message.channel == msg.channel and message.author == ctx.author answer = await self.bot.wait_for('message', check=check, timeout=120.0) return answer.content address = "" port = 0 while not address or not port: address = await ask_address() if len(address.split(":", 1)) == 2: address, port = address.split(":", 1) else: port = await ask_port() try: port = int(port) except: port = 0 await ctx.author.send( "Invalid port! Please insert your address and port again." ) return address, port async def ask_password(): embed = setup_embed("What is your server's RCON password?") embed.description += "\n\nNote: Passwords are stored by the bot, but will NEVER be shared." msg = await ctx.author.send(embed=embed) def check(message): return message.channel == msg.channel and message.author == ctx.author answer = await self.bot.wait_for('message', check=check, timeout=120.0) return answer.content async def ask_name(): embed = setup_embed("What should your instance be called?") msg = await ctx.author.send(embed=embed) def check(message): return message.channel == msg.channel and message.author == ctx.author answer = await self.bot.wait_for('message', check=check, timeout=120.0) return answer.content async def ticket_confirmation(values): embed = discord.Embed( title="That was everything!", description= "If you need to change something, select one of the fields by reacting to this message. If all information is correct, react with ✅. To cancel, react with 🗑️.", color=discord.Color.gold()) embed.add_field(name="1⃣ Game", value=values[0], inline=True) embed.add_field(name="2⃣ Address:Port", value=values[1] + ":" + str(values[2]), inline=True) embed.add_field(name="3⃣ Password", value=values[3], inline=True) embed.add_field(name="4⃣ Instance Name", value=values[4], inline=False) msg = await ctx.author.send(embed=embed) for emoji in ["✅", "1⃣", "2⃣", "3⃣", "4⃣", "🗑️"]: await msg.add_reaction(emoji) def check(reaction, user): return reaction.message.channel == msg.channel and user == ctx.author answer = await self.bot.wait_for('reaction_add', check=check, timeout=300.0) await msg.delete() return str(answer[0].emoji) try: game = await ask_game() address, port = await ask_address_and_port() password = await ask_password() name = await ask_name() values = [game, address, port, password, name] confirmation = "" while confirmation != "✅": confirmation = await ticket_confirmation(values) if confirmation == "1⃣": game = await ask_game() elif confirmation == "2⃣": address, port = await ask_address_and_port() elif confirmation == "3⃣": password = await ask_password() elif confirmation == "4⃣": name = await ask_name() elif confirmation == "🗑️": if operation == 0: await ctx.author.send("Instance creation cancelled.") elif operation == 0: await ctx.author.send("Instance editing cancelled.") return values = [game, address, port, password, name] if confirmation == "✅": msg = await ctx.author.send( "Trying to establish a connection...") try: if operation == 0: inst = add_instance(game=values[0], address=values[1], port=values[2], password=values[3], name=values[4], owner_id=ctx.author.id) elif operation == 1: inst = edit_instance(inst.id, game=values[0], address=values[1], port=values[2], password=values[3], name=values[4]) except RconAuthError as e: await ctx.author.send( f"Unable to connect to the server: {str(e)}") await asyncio.sleep(3) confirmation = "" except commands.BadArgument as e: await ctx.author.send( f"An instance using this address already exists!") await asyncio.sleep(3) confirmation = "" else: await msg.edit(content="Connection established!") self.bot.cache._connect_instance(inst) embed = base_embed( inst.id, description= "You can now customize your instance further by setting permissions and changing config options. See `r!inst help` for more information.", color=discord.Color.green()) if operation == 0: embed.title = "Instance created!" elif operation == 1: embed.title = "Instance edited!" await ctx.author.send(embed=embed) except asyncio.TimeoutError: await ctx.author.send( "You took too long to respond. Execute the command again to retry." ) return
async def check_server(self): try: for inst in self.bot.cache.instances.values(): if inst: try: await self._query(inst) try: max_id = self.last_seen_id[inst.id] except KeyError: max_id = ServerLogs(inst.id)._get_max_log_id() new_max_id = max_id new_logs = [] else: new_max_id, new_logs = ServerLogs( inst.id).get_logs_after(max_id) config = Instance(inst.id).config guild = self.bot.get_guild(config['guild_id']) if guild and new_logs: # Note: Chat logs are handled alongside the triggers channel_joins = guild.get_channel( config['channel_log_joins']) channel_match = guild.get_channel( config['channel_log_match']) channel_rcon = guild.get_channel( config['channel_log_rcon']) channel_teamkills = guild.get_channel( config['channel_log_teamkills']) if channel_rcon: default_embed = base_embed(inst.id) logs = [ log for log in new_logs if log['category'] == 'rcon' ] for log in logs: embed = default_embed embed.color = discord.Color.teal() embed.title = log['message'] embed.set_footer( text= f"Recorded at {log['timestamp'].strftime('%a, %b %d, %Y %I:%M %p')}" ) await channel_rcon.send(embed=embed) if channel_joins: default_embed = base_embed(inst.id) logs = [ log for log in new_logs if log['category'] == 'joins' ] if logs: joins = [ log['message'] for log in logs if log['message'].endswith(' connected') ] leaves = [ log['message'] for log in logs if not log['message'].endswith( ' connected') ] embed = default_embed embed.set_footer( text= f"Recorded at {logs[-1]['timestamp'].strftime('%a, %b %d, %Y %I:%M %p')}" ) if joins: embed.color = discord.Color.dark_green( ) embed.description = "\n".join(joins) await channel_joins.send(embed=embed) if leaves: embed.color = discord.Embed.Empty embed.description = "\n".join(leaves) await channel_joins.send(embed=embed) if channel_match: default_embed = base_embed(inst.id) logs = [ log for log in new_logs if log['category'] == 'match' ] for log in logs: embed = default_embed embed.color = discord.Color.from_rgb( 255, 255, 255) embed.title = log['message'] embed.set_footer( text= f"Recorded at {log['timestamp'].strftime('%a, %b %d, %Y %I:%M %p')}" ) await channel_match.send(embed=embed) if channel_teamkills: default_embed = base_embed(inst.id) logs = [ log for log in new_logs if log['category'] == 'teamkill' ] if logs: embed = default_embed embed.set_footer( text= f"Recorded at {logs[-1]['timestamp'].strftime('%a, %b %d, %Y %I:%M %p')}" ) embed.color = discord.Color.dark_red() embed.description = "\n".join( [log['message'] for log in logs]) await channel_teamkills.send(embed=embed) self.last_seen_id[inst.id] = new_max_id except Exception as e: logging.exception( 'Inst %s: Unhandled exception whilst updating: %s: %s', inst.id, e.__class__.__name__, e) if isinstance(e, (RconAuthError, ConnectionLost)) and ( datetime.now() - timedelta(minutes=30)) > inst.last_updated: self.bot.cache.instances[inst.id] = None except Exception as e: logging.critical( 'Unhandled exception in instance update cycle: %s: %s', e.__class__.__name__, e)
async def _query(self, inst): # Execute a command to receive new chat packets. # We can use this opportunity to update the cache, # though we don't want to overdo this. if (datetime.now() - inst.last_updated ).total_seconds() > SECONDS_BETWEEN_CACHE_REFRESH: inst.update() else: inst.rcon.exec_command("a") # Grab all the new chat messages new_chat_messages = inst.rcon.get_player_chat() inst.rcon.clear_player_chat() # Parse incoming messages # '[ChatAll] [SteamID:12345678901234567] [FP] Clan Member 1 : Hello world!' # '[ChatAdmin] ASQKillDeathRuleset : Player S.T.A.L.K.E.R%s Team Killed Player NUKE' # '[SteamID:76561198129591637] (WTH) Dylan has possessed admin camera.' for message in new_chat_messages: raw_data = {} if message.startswith('[ChatAdmin] ASQKillDeathRuleset'): # The message is a logged teamkill p1_name, p2_name = re.search( r'\[ChatAdmin\] ASQKillDeathRuleset : Player (.*)%s Team Killed Player (.*)', message).groups() p1 = inst.get_player(p1_name) p2 = inst.get_player(p2_name) p1_output = f"{p1_name} ({p1.steam_id})" if p1 else p1_name p2_output = f"{p2_name} ({p2.steam_id})" if p2 else p2_name message = f"{p1_output} team killed {p2_output}" ServerLogs(inst.id).add("teamkill", message) continue else: try: raw_data['channel'], raw_data['steam_id'], raw_data[ 'name'], raw_data['message'] = re.search( r'\[(.+)\] \[SteamID:(\d{17})\] (.*) : (.*)', message).groups() except: continue player = inst.get_player(int(raw_data['steam_id'])) if player: faction = inst.team1.faction_short if player.team_id == 1 else inst.team2.faction_short squad = player.squad_id if raw_data['channel'] == "ChatAdmin": continue if raw_data['channel'] == "ChatAll": channel = "All" elif raw_data['channel'] == "ChatTeam": channel = faction elif raw_data['channel'] == "ChatSquad": channel = f"{faction}/Squad{str(squad)}" else: channel = raw_data['channel'] else: channel = "Unknown" name = raw_data['name'] text = raw_data['message'] message = f"[{channel}] {name}: {text}" ServerLogs(inst.id).add("chat", message) # Do stuff with new messages instance = Instance(inst.id) config = instance.config guild = self.bot.get_guild(config["guild_id"]) if guild: # Trigger words trigger_channel = guild.get_channel( config["chat_trigger_channel_id"]) trigger_words = config["chat_trigger_words"].split(",") if trigger_channel and trigger_words: trigger_mentions = config["chat_trigger_mentions"] for word in trigger_words: word = word.strip().lower() if word in text.lower(): try: last_attempt = self.trigger_cooldowns[ player.steam_id] cooldown = int( config['chat_trigger_cooldown'] - (datetime.now() - last_attempt).total_seconds()) except KeyError: cooldown = -1 if cooldown == 0: # The player sent multiple admin reports in the same period. # Let's not ping the admins twice, because why would we? trigger_mentions = "" if cooldown > 0: # The player tried sending admin reports too fast and is still in cooldown inst.rcon.warn( player.steam_id, f"You're sending reports too fast! Please wait {str(cooldown)}s and try again." ) description = f"{name}: {discord.utils.escape_markdown(text)}" embed = base_embed(inst.id, description=description) embed.set_footer( text= f"Cooldown was active for this player ({str(cooldown)}s). He was asked to try again." ) await trigger_channel.send(embed=embed) elif len(text.split( )) < 3 and config["chat_trigger_require_reason"]: # The player didn't include a reason, which is required inst.rcon.warn( player.steam_id, f"Please include a reason in your '{word}' report and try again." ) description = f"{name}: {discord.utils.escape_markdown(text)}" embed = base_embed(inst.id, description=description) embed.set_footer( text= "No reason was included in this report. The player was asked to try again." ) await trigger_channel.send(embed=embed) else: self.trigger_cooldowns[ player.steam_id] = datetime.now() description = f"{name}: {discord.utils.escape_markdown(text)}" embed = base_embed(inst.id, title="New Report", description=description, color=discord.Color.red()) if player: embed.set_footer( text= "Team ID: %s • Squad ID: %s • Player ID: %s" % (player.team_id, player.squad_id, player.player_id)) await trigger_channel.send(trigger_mentions, embed=embed) if config['chat_trigger_confirmation']: inst.rcon.warn( player.steam_id, config['chat_trigger_confirmation']) break # Auto log chat_channel = guild.get_channel(config["channel_log_chat"]) if chat_channel: embed = base_embed(instance) title = "{: <20} {}".format("[" + channel + "]", name) embed.add_field(name=title, value=discord.utils.escape_markdown(text)) embed.set_footer( text= f"Recorded at {datetime.now().strftime('%a, %b %d, %Y %I:%M %p')}" ) if not player or channel == "Unknown": embed.color = discord.Embed.Empty elif raw_data['channel'] == "ChatAll": embed.color = discord.Color.dark_gray() elif raw_data['channel'] == "ChatTeam": if player.team_id == 1: embed.color = discord.Color.from_rgb(66, 194, 245) else: embed.color = discord.Color.from_rgb(240, 36, 53) elif raw_data['channel'] == "ChatSquad": if player.team_id == 1: embed.color = discord.Color.from_rgb(0, 100, 255) else: embed.color = discord.Color.from_rgb(201, 4, 24) await chat_channel.send(embed=embed)