async def handle_player_count_message(self, message: PlayerCount): if config.get_server(message.server_name).player_count_channel_id: player_count_string = config.get_server( message.server_name).player_count_format.format( players=message.players, slots=message.slots, queue=message.queue, time=message.time) if message.queue != '0': player_count_string += config.get_server( message.server_name).player_count_queue_format.format( players=message.players, slots=message.slots, queue=message.queue, time=message.time) if self.player_counts[message.server_name] != player_count_string: if datetime.timedelta(minutes=6) < \ datetime.datetime.now() - self.last_player_count_update[message.server_name]: # Rate limit is triggered when updating a channel name too often, so that's why we # put a hard limit on how often the player count channel gets updated channel: discord.TextChannel = self.client.get_channel( config.get_server( message.server_name).player_count_channel_id) await channel.edit(name=player_count_string) self.last_player_count_update[ message.server_name] = datetime.datetime.now() self.player_counts[ message.server_name] = player_count_string log.info( f'log {message.server_name}: Update player count: {player_count_string}' ) if config.get().log_player_count_updates: self.log_rollup[message.server_name].append( f'Update player count: {player_count_string}')
async def create_datagram_endpoint(self): self.transport, self.rcon_protocol = await asyncio.get_running_loop( ).create_datagram_endpoint( connection.ProtocolFactory(self.server_name, self.rcon_registrar).get, remote_addr=(config.get_server(self.server_name).ip, config.get_server(self.server_name).rcon_port))
async def start_server(server_name): await steam_service.get_service_manager(server_name).start() if config.get_server(server_name).rcon_password is not None: await rcon_service.get_service_manager(server_name).start() for i in range(len(config.get_server(server_name).scheduled_commands)): sm = scheduled_command.get_service_manager(server_name, i) await sm.start() if config.get_server(server_name).player_count_channel_id is not None: await player_count.get_service_manager(server_name).start()
def connection_made(self, transport: asyncio.DatagramTransport): self.transport = transport data = protocol.Packet( protocol.Login(password=config.get_server( self.server_name).rcon_password)).generate() log.info(f'{self.server_name} sending login') self.send_rcon_datagram(data)
async def query_stats(self, server_name, steam64): server_api_id = config.get_server(server_name).cf_cloud_server_api_id request = await self.locking_request('GET', f'{API}/v1/server/{server_api_id}/lookup', payload=dict(identifier=steam64)) if request.status_code != 200 or not request.json().get('status', False): return 'Lookup failed' result = request.json() cftools_id = result.get('cftools_id') request = await self.locking_request('GET', f'{API}/v1/server/{server_api_id}/player', payload=dict(cftools_id=cftools_id)) if request.status_code != 200 or not request.json().get('status', False): return 'Query failed' result = request.json() log.info(f'result: {result}') user = result.get('user', dict()) stats = user.get('stats', dict()) response = dict( playtime=str(datetime.timedelta(seconds=user.get('playtime', 0))), sessions=user.get('sessions', 0), average_engagement_distance=f'{stats.get("average_engagement_distance", 0):0.2f}m', kills=stats.get('kills', 0), deaths=stats.get('deaths', 0), longest_kill_distance=f'{stats.get("longest_kill_distance", 0)}m', longest_kill_weapon=stats.get('longest_kill_weapon', '') ) return json.dumps(response, indent=1, ensure_ascii=False)
def __init__(self, server_name, index): super().__init__() self.server_name = server_name self.index = index self.command = config.get_server( self.server_name).scheduled_commands[self.index] self.command['next'] = 'unknown'
async def query_stats(self, server_name, steam64): request = await self.locking_request('GET', f'{API}/v1/user/lookup', payload=dict( identity=steam64, identity_type='steam64')) result = request.json() if not result.get('status', False): return 'Invalid steam64' cftools_id = result.get('cftools_id') service_token = self.get_service_token(server_name) request = await self.locking_request( 'GET', f'{API}/v1/user/{service_token.token}/service', payload=dict(platform='omega', cftools_id=cftools_id)) result = request.json() user = result.get('user', dict()).get( config.get_server(server_name).cftools_service_id, dict()) stats = user.get('stats', dict()) response = dict( playtime=str(datetime.timedelta(seconds=user.get('playtime', 0))), sessions=user.get('sessions', 0), average_engagement_distance= f'{stats.get("average_engagement_distance", 0):0.2f}m', kills=stats.get('kills', 0), deaths=stats.get('deaths', 0), longest_kill_distance=f'{stats.get("longest_kill_distance", 0)}m', longest_kill_weapon=stats.get('longest_kill_weapon', '')) return json.dumps(response, indent=1)
def process_packet(self, packet): if isinstance(packet.payload, protocol.Command) or isinstance( packet.payload, protocol.SplitCommand): asyncio.create_task( self.rcon_registrar.incoming(packet.payload.sequence_number, packet)) elif isinstance(packet.payload, protocol.Message): message = packet.payload.message log.debug(f'{self.server_name} message: {message}') disconnect = re.compile(r'Player .* disconnected') if disconnect.match(message): parts = message.split() disconnect_message = ' '.join(parts[2:]) log.info( f'{self.server_name} login event {disconnect_message}') if config.get_server( self.server_name).chat_show_connect_disconnect_notices: asyncio.create_task( discord_service.get_service_manager().send_message( discord_service.Chat(self.server_name, disconnect_message))) connect = re.compile(r'Verified GUID .* of player .*') if connect.match(message): parts = message.split() name = ' '.join(parts[6:]) login_message = f'{name} connected' log.info(f'{self.server_name} login event {login_message}') if config.get_server( self.server_name).chat_show_connect_disconnect_notices: asyncio.create_task( discord_service.get_service_manager().send_message( discord_service.Chat(self.server_name, login_message))) chat = re.compile(r'^\((Global|Side)\).*:.*') if chat.match(message): _, _, content = message.partition(' ') asyncio.create_task( discord_service.get_service_manager().send_message( discord_service.Chat(self.server_name, content))) if len(message) > 0: asyncio.create_task( discord_service.get_service_manager().send_message( discord_service.Log(self.server_name, message))) return protocol.Packet( protocol.Message(packet.payload.sequence_number)).generate() return None
async def keep_alive(self): seq_number = await self.rcon_registrar.get_next_sequence_number() packet = protocol.Packet(protocol.Command(seq_number)) future = asyncio.get_running_loop().create_future() await self.rcon_registrar.register(packet.payload.sequence_number, future) self.rcon_protocol.send_rcon_datagram(packet.generate()) await future if config.get_server(self.server_name).log_rcon_keep_alive: await self.discord_log('keep alive')
async def handle_message(self, message: Message): await self.client.wait_until_ready() if isinstance(message, PlayerCount): await self.handle_player_count_message(message) elif isinstance(message, Log): log.info(f'log {message.server_name}: {message.text}') self.log_rollup[message.server_name] += f'{message.text}'.split( '\n') elif isinstance(message, Response): log.info(f'response {message.server_name}: {message.text}') self.log_rollup[message.server_name] += f'{message.text}'.split( '\n') elif isinstance(message, Chat): log.info(f'chat {message.server_name}: {message.content}') channel_id = config.get_server(message.server_name).chat_channel_id if channel_id: if config.get_server(message.server_name).chat_ignore_regex: r = re.compile( config.get_server( message.server_name).chat_ignore_regex) if r.match(message.content): log.info( f'chat ignored {message.server_name}: {message.content}' ) return channel: discord.TextChannel = self.client.get_channel( channel_id) await channel.send(embed=discord.Embed( description=message.content)) elif isinstance(message, UserResponse): log.info(f'user message {message.channel_id}: {message.text}') channel: discord.TextChannel = self.client.get_channel( message.channel_id) for m in build_formatted_fields(message.title, message.text.split('\n')): embed_dict = { 'color': get_server_color(message.title), 'fields': m } await channel.send(embed=discord.Embed.from_dict(embed_dict))
async def query_leaderboard(self, server_name, stat): cached = self.leaderboard_cache.get(server_name, stat) if cached is not None: log.debug('using cached value') return cached else: server_api_id = config.get_server(server_name).cf_cloud_server_api_id request = await self.locking_request('GET', f'{API}/v1/server/{server_api_id}/leaderboard', payload=dict(stat=stat, limit=20, order=-1)) if request.status_code != 200 or not request.json().get('status', False): return 'Query failed' result = request.json() result['leaderboard'] = [{'rank': entry['rank'], 'latest_name': entry['latest_name'], stat: entry[stat]} for entry in result.get('leaderboard', list())] self.leaderboard_cache.set(server_name, stat, result) return result
async def on_message(self, message: discord.Message): if message.author == self.user: return for server_name in config.get_server_names(): if config.get_server(server_name).rcon_password is not None: if message.channel.id == config.get_server( server_name).chat_channel_id: await arguments.process_chat(server_name, message) elif message.channel.id == config.get_server(server_name).admin_channel_id \ and message.content.startswith('--'): args = shlex.split(message.content, comments=True) try: parsed_args, remaining_args = arguments.message_parser.parse_known_args( args) except (ValueError, argparse.ArgumentError): log.info(f'invalid command {message.content}') return if remaining_args == args: log.info(f'invalid command {message.content}') asyncio.create_task( discord_service.get_service_manager().send_message( discord_service.Response( server_name, f'invalid command\n`{message.content}`'))) else: await arguments.process_message_args( server_name, parsed_args, message) if message.channel.id in config.get( ).user_channel_ids and message.content.startswith('--'): args = shlex.split(message.content, comments=True) try: parsed_args, remaining_args = arguments.user_message_parser.parse_known_args( args) if remaining_args == args: return except (ValueError, argparse.ArgumentError): log.info(f'invalid command {message.content}') asyncio.create_task( discord_service.get_service_manager().send_message( discord_service.UserResponse( message.channel.id, f'invalid command\n`{message.content}`'))) return await arguments.process_user_message_args(message.channel.id, parsed_args) for custom_command in config.get().custom_commands: custom_command: message_builder.Response = custom_command if custom_command.enabled: if message.channel.id in custom_command.channels or len( custom_command.channels) == 0: if re.match(custom_command.command, message.content): embed = custom_command.generate() if len(embed) <= 6000: await message.channel.send(embed=embed) else: await message.channel.send( f'Message longer than 6000 character limit: {len(embed)}' )
def get_service_token(self, server_name): try: return self.service_tokens[config.get_server( server_name).cftools_service_id] except IndexError: return None
async def send_rolled_log(self, server_name, fields): channel: discord.TextChannel = self.client.get_channel( config.get_server(server_name).admin_channel_id) embed_dict = {'color': get_server_color(server_name), 'fields': fields} await channel.send(embed=discord.Embed.from_dict(embed_dict))
async def process_admin_args(server_name, parsed_args, message): if parsed_args.list_priority: cf_message = omega_service.QueuePriorityList(server_name) await omega_service.get_service_manager().send_message(cf_message) try: result = await cf_message.result result = json.dumps(result, indent=1) asyncio.create_task( discord_service.get_service_manager().send_message( discord_service.Response( server_name, f'**Queue Priority**\n```{result}```'))) except asyncio.CancelledError: asyncio.create_task( discord_service.get_service_manager().send_message( discord_service.Response( server_name, f'**Queue Priority**\nquery timed out'))) if 'create_priority' in parsed_args: cftools_id, comment, days = parsed_args.create_priority try: days = int(days) if days != -1: expires_at = datetime.datetime.now( tz=datetime.timezone.utc) + datetime.timedelta(days=days) expires_at = expires_at.timestamp() else: expires_at = -1 except ValueError: asyncio.create_task( discord_service.get_service_manager().send_message( discord_service.Response(server_name, f'Invalid days: {days}'))) return cf_message = omega_service.QueuePriorityCreate(server_name, cftools_id, comment, expires_at) await omega_service.get_service_manager().send_message(cf_message) try: result = await cf_message.result asyncio.create_task( discord_service.get_service_manager().send_message( discord_service.Response( server_name, f'**Create Priority**\n{result}'))) except asyncio.CancelledError: asyncio.create_task( discord_service.get_service_manager().send_message( discord_service.Response( server_name, f'**Create Priority**\nquery timed out'))) if 'revoke_priority' in parsed_args: cftools_id = parsed_args.revoke_priority cf_message = omega_service.QueuePriorityRevoke(server_name, cftools_id) await omega_service.get_service_manager().send_message(cf_message) try: result = await cf_message.result asyncio.create_task( discord_service.get_service_manager().send_message( discord_service.Response( server_name, f'**Revoke Priority**\n{result}'))) except asyncio.CancelledError: asyncio.create_task( discord_service.get_service_manager().send_message( discord_service.Response( server_name, f'**Revoke Priority**\nquery timed out'))) if 'command' in parsed_args: if parsed_args.command is None: command = 'commands' else: command = parsed_args.command service_message = rcon_service.Command(server_name, command) await rcon_service.get_service_manager(server_name).send_message( service_message) try: result = await service_message.result asyncio.create_task(discord_service.get_service_manager( ).send_message( discord_service.Response( server_name, f'**{command}**\n{str(result) if result else "success"}'))) except asyncio.CancelledError: asyncio.create_task( discord_service.get_service_manager().send_message( discord_service.Response( server_name, f'**{command}**\nquery timed out'))) if 'shutdown' in parsed_args: if parsed_args.shutdown is not None: delay = parsed_args.shutdown else: delay = 0 service_message = rcon_service.SafeShutdown(server_name, delay) await rcon_service.get_service_manager(server_name).send_message( service_message) if parsed_args.status: commands_info = list() commands_config = config.get_server(server_name).scheduled_commands for i, command_config in enumerate(commands_config): sc = scheduled_command.get_service_manager(server_name, i) next_run = sc.command['next'] if not isinstance(next_run, str): next_run = datetime.timedelta(seconds=next_run) next_run -= datetime.timedelta( microseconds=next_run.microseconds) next_run = str(next_run) c_info = dict(index=i, command=sc.command['command'], interval=sc.command['interval'], next_run=next_run) if sc.command.get('skip', False): c_info['skip_next'] = True commands_info.append(c_info) asyncio.create_task(discord_service.get_service_manager().send_message( discord_service.Response( server_name, f'```{json.dumps(commands_info, indent=1)}```'))) if 'skip' in parsed_args: i = parsed_args.skip if not 0 <= i < len(scheduled_command.services.get( server_name, list())): asyncio.create_task( discord_service.get_service_manager().send_message( discord_service.Response(server_name, 'Invalid index'))) else: await scheduled_command.get_service_manager( server_name, i).send_message(scheduled_command.Skip(server_name)) if parsed_args.kill: sys.exit(0) if parsed_args.version: asyncio.create_task(discord_service.get_service_manager().send_message( discord_service.Response(server_name, f'{carim_discord_bot.VERSION}')))
async def service(self): while True: await asyncio.sleep( config.get_server( self.server_name).player_count_update_interval) await self.update_player_count()
async def handle_message(self, message: managed_service.Message): if isinstance(message, Query): await query.query(config.get_server(self.server_name).ip, config.get_server(self.server_name).steam_port, message.result)