def display_player_name(follow_id): follow_player = SERVER.get_player_by_id(follow_id) if follow_player is not None: player_name = follow_player.n display_name = player_name if player_name.strip( ) not in config.BLACKLISTED_WORDS else "*" * len(player_name) api.exec_command(f"set player-name {display_name}", verbose=False)
def start(): """ The main gateway for fetching the server state through /svinfo_report. It runs through a loop indefinitely and attempts to extract new data only if state is not paused through the PAUSE_STATE flag. """ global STATE global PAUSE_STATE global VID_RESTARTING prev_state, curr_state = None, None initialize_state() while True: try: if PAUSE_STATE: raise Exception("Paused") # Only refresh the STATE object if new data has been read and if state is not paused while not new_report_exists( config.INITIAL_REPORT_P) and not PAUSE_STATE: time.sleep(2) if not PAUSE_STATE: api.exec_command( "varmath color2 = $chsinfo(152);" # Store inputs in color2 "silent svinfo_report serverstate.txt", verbose=False) # Write a new report elif not VID_RESTARTING: raise Exception("Paused") if new_report_exists(config.STATE_REPORT_P): # Given that a new report exists, read this new data. server_info, players, num_players = get_svinfo_report( config.STATE_REPORT_P) if bool( server_info ): # New data is not empty and valid. Update the state object. STATE.players = players STATE.update_info(server_info) STATE.num_players = num_players validate_state( ) # Check for nospec, self spec, afk, and any other problems. if STATE.current_player is not None and STATE.current_player_id != STATE.bot_id: curr_state = f"Spectating {STATE.current_player.n} on {STATE.mapname}" \ f" in server {STATE.hostname} | ip: {STATE.ip}" if curr_state != prev_state: logging.info(curr_state) prev_state = curr_state display_player_name(STATE.current_player_id) if getattr(STATE, 'vote_active', False): STATE.handle_vote() except Exception as e: if e.args[0] == 'Paused': pass else: prev_state, curr_state = None, None initialize_state( ) # Handle the first state fetch. Some extra processing needs to be done this time. logging.info(f"State failed: {e}") time.sleep(1)
async def switch_spec(direction='next', channel=None): """ Handles "smart" spec switch. Resets data relevant to old connections and players. Can move either forward (default) or backwards (used by ?prev). """ global STATE global IGNORE_IPS IGNORE_IPS = [] STATE.afk_list = [] spec_ids = STATE.spec_ids if direction == 'next' else STATE.spec_ids[:: -1] # Reverse spec_list if going backwards. if STATE.current_player_id != STATE.bot_id: # Determine the next followable id. If current id is at the last index, wrap to the beginning of the list. next_id_index = spec_ids.index(STATE.current_player_id) + 1 if next_id_index > len(spec_ids) - 1: next_id_index = 0 follow_id = spec_ids[next_id_index] if follow_id == STATE.current_player_id: # Landed on the same id (list is length 1). No other players to spec. msg = "No other players to spectate." api.display_message(f"^7{msg}") logging.info(msg) if channel is not None: await channel.send(msg) else: display_player_name(follow_id) api.exec_command(f"follow {follow_id}") # Follow this player. STATE.idle_counter = 0 # Reset idle strike flag since a followable non-bot id was found. STATE.current_player_id = follow_id # Notify the state object of the new player we are spectating. STATE.afk_counter = 0 return True
def mapdataHook(): global CURRENT_MAP while True: if serverstate.STATE.mapname != CURRENT_MAP and serverstate.STATE.mapname != None: CURRENT_MAP = serverstate.STATE.mapname logging.info("Map changed to : " + str(CURRENT_MAP)) data = MapData.load(CURRENT_MAP) cmd = f"cg_centertime 3;displaymessage 140 10 Changing settings for map: {CURRENT_MAP}" if data is not None: data = json.loads(data[2]) for key, value in SAVED_CMDS.items(): if key in data: cmd += ";" + SAVED_CMDS[key]['cmd'] + " " + str(data[key]) else: cmd += ";" + SAVED_CMDS[key]['cmd'] + " " + str(SAVED_CMDS[key]['default']) else: for key, value in SAVED_CMDS.items(): cmd += ";" + SAVED_CMDS[key]['cmd'] + " " + str(SAVED_CMDS[key]['default']) serverstate.VID_RESTARTING = True serverstate.PAUSE_STATE = True api.exec_command(cmd + "; vid_restart") time.sleep(4)
def restart_connect(ip): api.press_key_mult("{Esc}", 2) api.press_key("{Enter}") api.press_key_mult("{Tab}", 10) api.press_key("{Enter}") time.sleep(1) api.exec_command("connect " + ip, verbose=False)
def handle_vote(self): if time.time() - self.vote_time > VOTE_TALLY_TIME: logging.info("Voting tally done.") if self.vn_count > self.vy_count: api.exec_command( f"say ^3{self.vy_count} ^2f1 ^7vs. ^3{self.vn_count} ^1f2^7. Voting ^3f2^7." ) logging.info( f"{self.vy_count} f1s vs. {self.vn_count} f2s. Voting f2.") api.exec_command("vote no") elif self.vy_count > self.vn_count: api.exec_command( f"say ^3{self.vy_count} ^2f1 ^7vs. ^3{self.vn_count} ^1f2^7. Voting ^3f1^7." ) logging.info( f"{self.vy_count} f1s vs. {self.vn_count} f2s. Voting f1.") api.exec_command("vote yes") else: api.exec_command( f"say ^3{self.vy_count} ^2f1 ^7vs. ^3{self.vn_count} ^1f2^7. No action." ) logging.info( f"{self.vy_count} f1s vs. {self.vn_count} f2s. Not voting." ) self.vote_time = 0 self.voter_names = [] self.vy_count = 0 self.vn_count = 0 self.vote_active = False else: return
async def spec(ctx, author, args): follow_id = args[0] msg = serverstate.spectate_player(follow_id) await ctx.channel.send(msg) time.sleep(1) api.exec_command( f"cg_centertime 3;varcommand displaymessage 140 10 ^3{author} ^7has switched to $chsinfo(117)" )
def initialize_state(): """ Handles necessary processing on the first iteration of state retrieval. Important steps done here: - Check if there's a valid connection - Retrieve the bot's client id using the "color1" cvar and a fresh secret code """ global STATE global PAUSE_STATE global INIT_TIMEOUT global STATE_INITIALIZED try: # Create a secret code. Only "secret" for one use. secret = ''.join(random.choice('0123456789ABCDEF') for i in range(16)) server_info, bot_player, timeout_flag = None, [], 0 init_counter = 0 while server_info is None or bot_player == []: # Continue running this block until valid data and bot id found init_counter += 1 if not PAUSE_STATE: # Set color1 to secret code to determine bot's client id api.exec_command( f"seta color1 {secret};silent svinfo_report serverstate.txt", verbose=False) else: raise Exception("Paused.") if new_report_exists(config.STATE_REPORT_P): # New data detected server_info, players, num_players = get_svinfo_report( config.STATE_REPORT_P) # Read data # Select player that contains this secret as their color1, this will be the bot player. bot_player = [ player for player in players if player.c1 == secret ] # If loop hits the max iterations, the connection was not established properly if init_counter >= INIT_TIMEOUT: # Retry a connection to best server new_ip = servers.get_next_active_server(IGNORE_IPS) connect(new_ip) bot_id = bot_player[0].id # Find our own ID # Create global server object STATE = State(secret, server_info, players, bot_id) STATE.current_player_id = bot_id STATE.num_players = num_players STATE_INITIALIZED = True logging.info("State Initialized.") except: return False # if not mapdata_thread.is_alive(): # mapdata_thread.start() return True
async def connect(ctx, author, args): ip = args[0] if ip.split(':')[0] not in config.get_list("whitelist_servers"): msg = f"Server \"{ip}\" is not whitelisted. Refusing connection." api.exec_command( f"cg_centertime 5;displaymessage 140 8 ^3{author} ^1{msg};") logging.info(msg) await ctx.channel.send(msg) return serverstate.connect(ip, author)
def handle_howmany(line_data): client_id = environ['TWITCH_API']['client_id'] client_secret = environ['TWITCH_API']['client_secret'] token_url = f"https://id.twitch.tv/oauth2/token?client_id={client_id}&client_secret={client_secret}&grant_type=client_credentials" r = requests.post(token_url) token = r.json()['access_token'] stream_url = f"https://api.twitch.tv/helix/streams?user_login={'defraglive'}" headers = {"Authorization": f"Bearer {token}", "Client-Id": client_id} r = requests.get(stream_url, headers=headers) stream_data = r.json()['data'] viewer_count = stream_data[0]['viewer_count'] reply_string = f"$chsinfo(117) ^7-- you are being watched by ^3{viewer_count} ^7viewer" + ("s" if viewer_count > 0 else "") api.exec_command(f"varcommand say {reply_string}") return None
def spectate_player(follow_id): """Spectate player chosen by twich users based on their client id""" global IGNORE_IPS IGNORE_IPS = [] STATE.afk_list = [] if follow_id in STATE.spec_ids: display_player_name(follow_id) api.exec_command(f"follow {follow_id}") # Follow this player. STATE.idle_counter = 0 # Reset idle strike flag since a followable non-bot id was found. STATE.current_player_id = follow_id # Notify the state object of the new player we are spectating. STATE.afk_counter = 0 return f"Spectating {STATE.get_player_by_id(follow_id).n}" else: return f"Sorry, that player (id {follow_id}) is not available for spectating."
def handle_stonk(line_data): try: line_list = line_data['content'].split() stonk = line_list[1] region = 'US' headers = { 'x-rapidapi-key': environ['STONK_API']['key'], 'x-rapidapi-host': environ['STONK_API']['host'] } url = "https://apidojo-yahoo-finance-v1.p.rapidapi.com/auto-complete" querystring = {"q": stonk, "region": region} response = requests.request("GET", url, headers=headers, params=querystring) symbol = response.json()['quotes'][0]['symbol'] url = "https://apidojo-yahoo-finance-v1.p.rapidapi.com/stock/v2/get-summary" querystring = {"symbol": symbol, "region": region} response = requests.request("GET", url, headers=headers, params=querystring) short_name, symbol, exchange = [response.json()['quoteType'][i] for i in ('shortName', 'symbol', 'exchange')] price, change = [response.json()['price'][i]['fmt'] for i in ('regularMarketPrice', 'regularMarketChangePercent')] currency = response.json()['price']['currency'] color = "^1" if '-' in change else "^2" change = change.replace('%',' p/c') reply_string = f"^7{symbol}^3: {color}{price} {currency} ({change}) ^7{short_name} ({exchange})" except: reply_string = "Invalid input. Usage: ?stonk <symbol>" return api.exec_command(f"say {reply_string}")
async def gamma(ctx, author, args): whitelisted_twitch_users = config.get_list('whitelist_twitchusers') if USE_WHITELIST and author not in whitelisted_twitch_users and not ctx.author.is_mod: await ctx.channel.send( f"{author}, you do not have the correct permissions to use this command." f"If you wanna be whitelisted to use such a command, please contact neyo#0382 on discord." ) return value = float(args[0]) if 0.5 <= (value) <= 1.6: logging.info("i did it..") api.exec_command(f"r_gamma {value}") MapData.save(serverstate.STATE.mapname, 'gamma', value) else: await ctx.channel.send( f"{author}, the allowed values for gamma are 1.0-1.6")
def send_message(): author = request.form.get('author', None) message = request.form.get('message', None) command = request.form.get('command', None) if command is not None and command.startswith("!"): if ";" in command: # prevent q3 command injections command = command[:command.index(";")] api.exec_command(command) return jsonify(result=f"Sent mdd command {command}") else: if ";" in message: # prevent q3 command injections message = message[:message.index(";")] api.exec_command(f"say {author} ^7> ^2{message}") return jsonify(result=f"Sent {author} ^7> ^2{message}") return jsonify(result="Unknown message")
async def picmip(ctx, author, args): whitelisted_twitch_users = config.get_list('whitelist_twitchusers') if USE_WHITELIST and author not in whitelisted_twitch_users and not ctx.author.is_mod: await ctx.channel.send( f"{author}, you do not have the correct permissions to use this command." f"If you wanna be whitelisted to use such a command, please contact neyo#0382 on discord." ) return value = args[0] if value.isdigit() and (0 <= int(value) <= 6): logging.info("vid_restarting..") serverstate.VID_RESTARTING = True serverstate.PAUSE_STATE = True api.exec_command(f"r_picmip {value};vid_restart") MapData.save(serverstate.STATE.mapname, 'picmip', value) else: await ctx.channel.send( f"{author}, the allowed values for picmip are 0-5.")
def connect(ip, caller=None): """ Handles connection to a server and re-attempts if connection is not resolved. """ global PAUSE_STATE global STATE_INITIALIZED global CONNECTING global IGNORE_IPS STATE_INITIALIZED = False logging.info(f"Connecting to {ip}...") PAUSE_STATE = True CONNECTING = True STATE.idle_counter = 0 STATE.afk_counter = 0 if caller is not None: STATE.connect_msg = f"^7Brought by ^3{caller}" IGNORE_IPS = [] api.exec_command("connect " + ip, verbose=False)
def refresh_server_state(): global SERVER global STOP_STATE while not STOP_STATE.is_set(): time.sleep(1) api.exec_command("silent svinfo_report serverstate.txt", verbose=False) server_info, players = get_svinfo_report(config.STATE_REPORT_P) if bool(server_info): SERVER.players = players SERVER.update_info(server_info) if SERVER.current_player is not None: SERVER.curr_state = f"Spectating {SERVER.current_player.n} on {SERVER.mapname} in server {SERVER.hostname} | ip: {SERVER.ip}" if SERVER.curr_state != SERVER.prev_state: print(colored(SERVER.curr_state, "blue")) continue_refresh = check_status() SERVER.prev_state = SERVER.curr_state display_player_name(SERVER.current_player_id) if not continue_refresh: return
def switch_spec(direction='next'): global SERVER global STOP_STATE spec_ids = SERVER.spec_ids if direction == 'next' else SERVER.spec_ids[::-1] if SERVER.current_player_id != -1: next_id_index = spec_ids.index(SERVER.current_player_id) + 1 if next_id_index > len(spec_ids) - 1: next_id_index = 0 follow_id = spec_ids[next_id_index] if follow_id == SERVER.current_player_id: print("No other players to switch to.") api.exec_command( "displaymessage 380 10 ^1No other players to switch to.", verbose=False) else: display_player_name(follow_id) api.exec_command(f"follow {follow_id}") SERVER.idle_counter = 0 SERVER.current_player_id = follow_id
def on_ws_message(msg): message = {} if msg is None: return try: message = json.loads(msg) except Exception as e: logging.info('ERROR [on_ws_message]:', e) return # if there is no origin, exit # this function only processes messages directly from twitch console extension if 'origin' not in message: return if 'message' in message: if message['message'] is None: message['message'] = {} message_text = message['message']['content'] if ";" in message_text: # prevent q3 command injections message_text = message_text[:message_text.index(";")] if message_text.startswith("!"): # proxy mod commands (!top, !rank, etc.) logging.info("proxy command received") api.exec_command(message_text) time.sleep(1) else: author = message['message']['author'] author += ' ^7> ' author_color_num = min(ord(author[0].lower()), 9) # replace ^[a-z] with ^[0-9] message_content = message_text.lstrip('>').lstrip('<') api.exec_command(f"say ^{author_color_num}{author} ^2{message_content}")
def check_status(): global SERVER global STOP_STATE MAX_STRIKES = 10 # TODO: move this over to configurable setting by user continue_refresh = True spectating_self = SERVER.curr_dfn == 'twitchbot' spectating_nospec = SERVER.current_player_id not in SERVER.spec_ids if spectating_self or spectating_nospec: follow_id = random.choice( SERVER.spec_ids) if SERVER.spec_ids != [] else -1 if follow_id != -1: msg = "Spectating self, switching..." if spectating_self else "Switching to non-nospec player..." print(colored(msg, 'green')) display_player_name(follow_id) api.exec_command(f"follow {follow_id}") SERVER.idle_counter = 0 else: if SERVER.current_player_id != -1: api.exec_command(f"follow {follow_id}", verbose=False) SERVER.current_player_id = -1 SERVER.idle_counter += 1 print( f"Not spectating. Strike {SERVER.idle_counter}/{MAX_STRIKES}") if SERVER.idle_counter >= MAX_STRIKES: # There's been no one on the server to spec, switch servers. new_ip = servers.get_most_popular_server(ignore_ip=SERVER.ip) STOP_STATE.set() connection_success = connect(new_ip) return connection_success # if false, continue working as normal. Else, stop refresh. SERVER.current_player_id = follow_id return continue_refresh
def connect(ip): global SERVER global STOP_STATE STOP_STATE.set() # stop server refresh thread # Set a secret color cvar secret = ''.join(random.choice('0123456789ABCDEF') for i in range(16)) print(colored(f"Connecting to {ip}...", "green")) connection_time = time.time() api.exec_command("connect " + ip, verbose=False) api.exec_command(f"seta color1 {secret}", verbose=False) server_info, bot_player, timeout_flag, reconnect_tries = None, [], 0, 0 # Read report while server_info is None or bot_player == []: report_mod_time = os.path.getmtime(config.INITIAL_REPORT_P) new_report_exists = report_mod_time > connection_time if new_report_exists: server_info, players = get_svinfo_report(config.INITIAL_REPORT_P) bot_player = [player for player in players if player.c1 == secret] timeout_flag += 1 if timeout_flag >= 10: print(colored(f"Connection timed out.", "green")) if reconnect_tries < 2: print(colored(f"Retrying connection to {ip}...", "green")) connection_time = time.time() restart_connect(ip) api.exec_command(f"seta color1 {secret}", verbose=False) server_info, bot_player, timeout_flag = None, [], 0 reconnect_tries += 1 else: print(f"Could not connect to {ip}") return time.sleep(1) print(colored(f"Connected.", "green")) bot_id = bot_player[0].id # Find our own ID # Create global server object SERVER = Server(ip, secret, server_info, players, bot_id) SERVER.current_player_id = -1 check_status() STOP_STATE.clear() state_refresher = threading.Thread(target=refresh_server_state, daemon=True) state_refresher.start() return True
async def prev(ctx, author, args): await serverstate.switch_spec('prev', channel=ctx.channel) api.exec_command( f"cg_centertime 2;displaymessage 140 10 ^3{author} ^7has switched to ^3Previous player" )
async def event_message(ctx): """Activates for every message""" debounce = 1 # interval between consecutive commands and messages author = ctx.author.name message = ctx.content if ";" in message: # prevent q3 command injections message = message[:message.index(";")] # bot.py, at the bottom of event_message if message.startswith("?"): # spectator client customization and controls message = message.strip('?').lower() split_msg = message.split(' ') cmd = split_msg[0] args = split_msg[1:] if len(split_msg) > 0 else None logging.info(f"TWITCH COMMAND RECEIVED: '{cmd}' from user '{author}'") for command in TWITCH_CMDS: if cmd in command: twitch_function = getattr(twitch_commands, command[0]) await twitch_function(ctx, author, args) time.sleep(debounce) elif message.startswith(">") or message.startswith("<"): # chat bridge message = message.lstrip('>').lstrip('<').lstrip(' ') blacklisted_words = config.get_list("blacklist_chat") for word in blacklisted_words: if word in message: logging.info(f"Blacklisted word '{word}' detected in message \"{message}\" by \"{author}\". Aborting message.") return if author.lower() == 'nightbot'.lower(): # ignore twitch Nightbot's name author = '' author_color_char = 0 else: author += ' ^7> ' author_color_char = author[0] api.exec_command(f"say ^{author_color_char}{author} ^2{message}") logging.info("Chat message sent") time.sleep(debounce) elif message.startswith("**"): # team chat bridge message = message.lstrip('**') blacklisted_words = config.get_list("blacklist_chat") for word in blacklisted_words: if word in message: logging.info(f"Blacklisted word '{word}' detected in message \"{message}\" by \"{author}\". Aborting message.") return if author.lower() == 'nightbot'.lower(): # ignore twitch Nightbot's name author = '' author_color_char = 0 else: author += ' ^7> ' author_color_char = author[0] api.exec_command(f"say_team ^{author_color_char}{author} ^5{message}") logging.info("Chat message sent") time.sleep(debounce) elif message.startswith("!"): # proxy mod commands (!top, !rank, etc.) logging.info("proxy command received") api.exec_command(message) time.sleep(debounce) elif message.startswith("$"): # viewer sound commands for sound_cmd in SOUND_CMDS: if message.startswith(sound_cmd): logging.info(f"Sound command recieved ({sound_cmd})") api.play_sound(sound_cmd.replace('$', '') + '.wav') #odfe appears to only support .wav format, not mp3, so we can hardcode it time.sleep(debounce) return
async def fixchat(ctx, author, args): api.exec_command( f"cl_noprint 0;cg_centertime 3;displaymessage 140 10 ^3{author} ^7has fixed: ^3ingame chat" )
async def snaps(ctx, author, args): api.exec_command( f"toggle mdd_snap 0 3;cg_centertime 3;displaymessage 140 10 ^3{author} ^7has changed: ^3snaps hud" )
async def lagometer(ctx, author, args): api.exec_command( f"toggle cg_lagometer 0 1;cg_centertime 3;displaymessage 140 10 ^3{author} ^7has changed: ^3Lagometer" )
async def clear(ctx, author, args): api.exec_command( f"clear;cg_centertime 3;cg_centertime 3;displaymessage 140 10 ^3{author} ^1Ingame chat has been erased ^3:(" )
async def event_message(ctx): """Activates for every message""" debounce = 1 # interval between consecutive commands and messages author = ctx.author.name message = ctx.content if ";" in message: # prevent q3 command injections message = message[:message.index(";")] # bot.py, at the bottom of event_message if message.startswith("?"): # spectator client customization and controls message = message.strip('?').lower() split_msg = message.split(' ') cmd = split_msg[0] args = split_msg[1:] if len(split_msg) > 0 else None print("Command received:", cmd) if cmd == "connect": serverstate.connect(args[0]) elif cmd == "restart": connect_ip = servers.get_most_popular_server() api.press_key_mult("{Esc}", 2) api.press_key("{Enter}") api.press_key_mult("{Tab}", 10) api.press_key("{Enter}") time.sleep(1) serverstate.connect(connect_ip) elif cmd == "next": serverstate.switch_spec('next') elif cmd == "prev": serverstate.switch_spec('prev') elif cmd == "scores": api.hold_key(config.get_bind("+scores"), 3.5) elif cmd == "clear": api.press_key(config.get_bind_fuzzy("clear")) elif cmd == "triggers": api.press_key(config.get_bind_fuzzy("scr_triggers_draw")) elif cmd == "clips": api.press_key(config.get_bind_fuzzy("scr_clips_draw")) elif cmd == "snaps": api.press_key(config.get_bind_fuzzy("mdd_snap")) elif cmd == "cgaz": api.press_key(config.get_bind_fuzzy("mdd_cgaz")) elif cmd == "checkpoints": api.press_key(config.get_bind_fuzzy("df_checkpoints")) elif cmd == "nodraw": api.press_key(config.get_bind_fuzzy("df_mp_NoDrawRadius")) elif cmd == "angles": api.press_key(config.get_bind("toggle df_chs1_Info6 0 40")) elif cmd == "obs": api.press_key(config.get_bind("toggle df_chs1_Info7 0 50")) elif cmd == "clean": api.press_key(config.get_bind_fuzzy("cg_draw2D")) elif cmd == "sky": api.press_key(config.get_bind_fuzzy("r_fastsky")) elif cmd == "vote": api.press_key(config.get_bind(f"vote {args[0]}")) elif cmd == "speedinfo": api.press_key(config.get_bind("toggle df_chs1_Info5 0 1")) elif cmd == "speedorig": api.press_key(config.get_bind_fuzzy("df_drawSpeed")) elif cmd == "huds": api.press_key(config.get_bind("toggle mdd_hud 0 1")) elif cmd == "inputs": api.press_key(config.get_bind_fuzzy("df_chs0_draw")) elif cmd == "n1": api.exec_command( api.exec_command( f"varcommand say ^{author[0]}{author} ^7> ^2Nice one, $chsinfo(117) ^2!" )) # Mod commands elif cmd == "brightness": if not ctx.author.is_mod: await ctx.channel.send( f"{author}, you do not have the correct permissions to use this command." ) return value = args[0] if value.isdigit() and (0 < int(value) <= 5): api.exec_command(f"r_mapoverbrightbits {value};vid_restart") else: await ctx.channel.send( f" {author}, the valid values for brightness are 1-5.") elif cmd == "picmip": if not ctx.author.is_mod: await ctx.channel.send( f"{author}, you do not have the correct permissions to use this command." ) return value = args[0] if value.isdigit() and (0 <= int(value) <= 6): api.exec_command(f"r_picmip {value};vid_restart") else: await ctx.channel.send( f"{author}, the allowed values for picmip are 0-5.") # Currently disabled. Possibly useful for the future: # elif cmd == "cgaz": # mode = args[0] if len(args) > 0 and 0 < int(args[0]) <= 15 else "toggle" # if mode == "toggle": # api.press_key(config.get_bind("toggle mdd_cgaz 0 1")) # else: # api.exec_command(f"df_hud_cgaz {mode}") # elif cmd == "cv" and "kick" not in message: # api.exec_command(f"{message}") time.sleep(debounce) elif message.startswith(">") or message.startswith("<"): # chat bridge if author.lower() == 'nightbot'.lower( ): # ignore twitch Nightbot's name author = '' author_color_char = 0 else: author += ' ^7> ' author_color_char = author[0] message = message.lstrip('>').lstrip('<') api.exec_command(f"say ^{author_color_char}{author} ^2{message}") print("Chat message sent") time.sleep(debounce) elif message.startswith("!"): # proxy mod commands (!top, !rank, etc.) print("proxy command received") api.exec_command(message) time.sleep(debounce) return
def process_line(line): """ Processes a console line into a more useful format. Extracts type (say, announcement, print) as well as author and content if applicable. :param line: Console line to be processed :return: Data dictionary containing useful data about the line """ import serverstate line = line.strip() line_data = { "id": message_to_id(f"{time.time()}_MISC"), "type": "MISC", "command": None, "author": None, "content": line, "timestamp": time.time() } # SERVERCOMMAND try: # Don't log if it's a report if "report written to system/reports/initialstate.txt" in line or "report written to system/reports/serverstate.txt" in line: pass else: logging.info(f"[Q3] {line}") if line in {"VoteVote passed.", "RE_Shutdown( 0 )"}: if not serverstate.PAUSE_STATE: serverstate.PAUSE_STATE = True logging.info("Game is loading. Pausing state.") if 'broke the server record with' in line and is_server_msg( line, 'broke the server record with'): """ Maybe we can also add a display message with the player name and/or the record #playerName = line[:line.index(' broke the server record with')] #playerRecord = line[line.index(' broke the server record with') + len(' broke the server record with'):] #api.display_message("{playerName} broke the record with {playerRecord}") """ api.play_sound("worldrecord.wav") if 'called a vote:' in line and is_server_msg(line, 'called a vote:'): logging.info("Vote detected.") if serverstate.STATE.num_players == 2: # only bot and 1 other player in game, always f1 logging.info("1 other player in server, voting yes.") api.exec_command("vote yes") api.exec_command("say ^7Vote detected. Voted ^3f1^7.") else: logging.info( "Multiple people in server, initiating vote tally.") serverstate.STATE.init_vote() api.exec_command( "say ^7Vote detected. Should I vote yes or no? Send ^3?^7f1 for yes and ^3?^7f2 for no." ) if line.startswith('Not recording a demo.') or line.startswith( "report written to system/reports/initialstate.txt"): if serverstate.CONNECTING: time.sleep(1) serverstate.CONNECTING = False elif serverstate.VID_RESTARTING: time.sleep(1) logging.info("vid_restart done.") serverstate.PAUSE_STATE = False serverstate.VID_RESTARTING = False elif serverstate.PAUSE_STATE: time.sleep(1) serverstate.PAUSE_STATE = False logging.info("Game loaded. Continuing state.") serverstate.STATE.say_connect_msg() # sc_r = r"^\^5serverCommand:\s*(\d+?)\s*:\s*(.+?)$" # match = re.match(sc_r, line) # # sv_command_id = match.group(1) # sv_command = match.group(2) def parse_chat_message(command): # CHAT MESSAGE (BY PLAYER) # chat_message_r = r"^chat\s*\"[\x19]*\[*(.*?)[\x19]*?\]*?\x19:\s*(.*?)\".*?$" #/developer 1 chat_message_r = r"(.*)\^7: \^\d(.*)" match = re.match(chat_message_r, command) chat_name = match.group(1) chat_message = match.group(2) line_data["id"] = message_to_id(f"SAY_{chat_name}_{chat_message}") line_data["type"] = "SAY" line_data["author"] = chat_name line_data["content"] = chat_message line_data["command"] = cmd.scan_for_command(chat_message) def parse_chat_announce(command): # CHAT ANNOUNCEMENT chat_announce_r = r"^chat\s*\"(.*?)\".*?$" match = re.match(chat_announce_r, command) chat_announcement = match.group(1) line_data["id"] = message_to_id(f"ANN_{chat_announcement}") line_data["type"] = "ANNOUNCE" line_data["author"] = None line_data["content"] = chat_announcement def parse_print(command): # PRINT print_r = r"^print\s*\"(.*?)$" # Prints have their ending quotation mark on the next line, very strange match = re.match(print_r, command) print_message = match.group(1) line_data["id"] = message_to_id(f"PRINT_{print_message}") line_data["type"] = "PRINT" line_data["author"] = None line_data["content"] = print_message def parse_scores(command): # SCORES scores_r = r"^scores\s+(.*?)$" match = re.match(scores_r, command) scores = match.group(1) line_data["id"] = message_to_id(f"SCORES_{scores}") line_data["type"] = "SCORES" line_data["author"] = None line_data["content"] = scores for fun in [ parse_chat_message, parse_chat_announce, parse_print, parse_scores ]: try: # fun(sv_command) fun(line) break except: continue except: return line_data # print((line_data) return line_data
async def clips(ctx, author, args): api.exec_command( f"toggle r_renderClipBrushes 0 1;cg_centertime 3;displaymessage 140 10 ^3{author} ^7has changed: ^3Render Clips" )