def get_server_twitch_client_id(self): try: url = self.entries['trackerserver_url'].get() + "/tracker/api/twitchclientid/" return urllib.request.urlopen(url).read() except Exception: log_error("Couldn't get twitch client id from tracker server\n" + traceback.format_exc()) return None
def reset_options(self): """ Reset state variables affected by options """ opt = Options() font_size = int(16 * opt.size_multiplier) # Anything that gets calculated and cached based on something in options # now needs to be flushed self.text_margin_size = font_size self.show_floors = opt.show_floors and (not self.state or self.state.game_version != "Antibirth") try: self.font = pygame.font.SysFont( opt.show_font, font_size, bold=opt.bold_font ) except Exception: log_error("ERROR: Couldn't load font \"" + opt.show_font +"\", falling back to Arial\n" + traceback.format_exc()) self.font = pygame.font.SysFont( "arial", font_size, bold=opt.bold_font ) self._image_library = {} self.roll_icon = self.get_scaled_icon(self.numeric_id_to_image_path("284"), font_size * 2) self.blind_icon = self.get_scaled_icon("questionmark.png", font_size * 2) if opt.show_description or opt.show_status_message: self.text_height = self.write_message(" ") else: self.text_height = 0
def __init__(self): self.options = Options() self.root = Tk() self.root.destroy() # Our 'safe' list of fonts that should work in pygame self.fonts = ['Andalus', 'Angsana New', 'AngsanaUPC', 'Arial', 'Arial Black', 'Browallia New', 'BrowalliaUPC', 'Comic Sans MS', 'Cordia New', 'CordiaUPC', 'Courier New', 'DFKai-SB', 'David', 'DilleniaUPC', 'Estrangelo Edessa', 'FrankRuehl', 'Franklin Gothic Medium', 'Gautami', 'Georgia', 'Impact', 'IrisUPC', 'JasmineUPC', 'KodchiangUPC', 'Latha', 'LilyUPC', 'Lucida Console', 'MV Boli', 'Mangal', 'Microsoft Sans Serif', 'Miriam', 'Miriam Fixed', 'Narkisim', 'Raavi', 'Rod', 'Shruti', 'SimHei', 'Simplified Arabic', 'Simplified Arabic Fixed', 'Sylfaen', 'Tahoma', 'Times New Roman', 'Traditional Arabic', 'Trebuchet MS', 'Tunga', 'Verdana'] self.game_versions = ['Rebirth', 'Afterbirth', 'Afterbirth+', 'Antibirth'] self.network_queue = Queue() # Check if the system has the fonts installed, and remove them from the list if it doesn't try: valid_pygame_fonts = [str.lower(x.replace(" ", "")) for x in self.fonts] system_fonts = pygame.sysfont.get_fonts() to_delete = [] for index, font in enumerate(valid_pygame_fonts): if font not in system_fonts: to_delete += [index] for index in to_delete[::-1]: del self.fonts[index] except: log_error("There may have been an error detecting system fonts.\n" + traceback.print_exc())
def __init__(self): self.options = Options() # Our 'safe' list of fonts that should work in pygame self.fonts = ['Andalus', 'Angsana New', 'AngsanaUPC', 'Arial', 'Arial Black', 'Browallia New', 'BrowalliaUPC', 'Comic Sans MS', 'Cordia New', 'CordiaUPC', 'Courier New', 'DFKai-SB', 'David', 'DilleniaUPC', 'Estrangelo Edessa', 'FrankRuehl', 'Franklin Gothic Medium', 'Gautami', 'Georgia', 'Impact', 'IrisUPC', 'JasmineUPC', 'KodchiangUPC', 'Latha', 'LilyUPC', 'Lucida Console', 'MV Boli', 'Mangal', 'Microsoft Sans Serif', 'Miriam', 'Miriam Fixed', 'Narkisim', 'Raavi', 'Rod', 'Shruti', 'SimHei', 'Simplified Arabic', 'Simplified Arabic Fixed', 'Sylfaen', 'Tahoma', 'Times New Roman', 'Traditional Arabic', 'Trebuchet MS', 'Tunga', 'Verdana'] self.game_versions = ['Rebirth', 'Afterbirth', 'Afterbirth+', 'Antibirth'] self.network_queue = Queue() # Check if the system has the fonts installed, and remove them from the list if it doesn't try: valid_pygame_fonts = [lower(x.replace(" ", "")) for x in self.fonts] system_fonts = pygame.sysfont.get_fonts() to_delete = [] for index, font in enumerate(valid_pygame_fonts): if font not in system_fonts: to_delete += [index] for index in to_delete[::-1]: del self.fonts[index] except: log_error("There may have been an error detecting system fonts.\n" + traceback.print_exc())
def get_server_twitch_client_id(self): try: url = self.entries['trackerserver_url'].get() + "/tracker/api/twitchclientid/" return urllib2.urlopen(url).read() except Exception: log_error("Couldn't get twitch client id from tracker server\n" + traceback.format_exc()) return None
def reset_options(self): """ Reset state variables affected by options """ opt = Options() font_size = int(16 * opt.size_multiplier) # Anything that gets calculated and cached based on something in options # now needs to be flushed self.text_margin_size = font_size self.show_floors = opt.show_floors and ( not self.state or self.state.game_version != "Antibirth") try: self.font = pygame.font.SysFont(opt.show_font, font_size, bold=opt.bold_font) except Exception: log_error("ERROR: Couldn't load font \"" + opt.show_font + "\", falling back to Arial\n" + traceback.format_exc()) self.font = pygame.font.SysFont("arial", font_size, bold=opt.bold_font) self._image_library = {} self.roll_icon = self.get_scaled_icon( self.numeric_id_to_image_path("284"), font_size * 2) self.blind_icon = self.get_scaled_icon("questionmark.png", font_size * 2) self.jacob_icon = self.get_scaled_icon("JacobHead.png", font_size * 2) self.esau_icon = self.get_scaled_icon("EsauHead.png", font_size * 2) if opt.show_description or opt.show_status_message: self.text_height = self.write_message(" ") else: self.text_height = 0
def main(): """ Main """ try: # Pass "logging.DEBUG" in debug mode rt = IsaacTracker() rt.run() except Exception: log_error(traceback.format_exc())
def main(): """ Main """ try: # Pass "logging.DEBUG" in debug mode rt = IsaacTracker() rt.run() except Exception: log_error(traceback.format_exc())
def main(): """ Main """ try: # Pass "logging.DEBUG" in debug mode rt = IsaacTracker() rt.run() except Exception: excepthook = IsaacTracker.filter_excepthook(IsaacTracker) log_error(excepthook)
def from_valid_json(json_dic, *args): """ Create a Floor from a type-checked dic """ floor_id = json_dic['floor_id'] curse = json_dic['curse'] if (floor_id not in Floor.__floor_id_to_label or curse < Curse.No_Curse or curse > Curse.Labyrinth): log_error("ERROR: Invalid floor_id or curse (" + floor_id + ", " + curse + ")") return None return Floor(floor_id, curse)
def get_server_userlist_and_enqueue(self): try: url = self.entries['trackerserver_url'].get() + "/tracker/api/userlist/" json_state = urllib2.urlopen(url).read() users = json.loads(json_state) success = True except Exception: log_error("Problem getting userlist from tracker server\n" + traceback.format_exc()) users = [] success = False network_result = {"users": users, "success": success} self.network_queue.put(network_result)
def get_server_userlist_and_enqueue(self): try: url = self.entries['trackerserver_url'].get() + "/tracker/api/userlist/" json_state = urllib.request.urlopen(url).read() users = json.loads(json_state) success = True except Exception: log_error("Problem getting userlist from tracker server\n" + traceback.format_exc()) users = [] success = False network_result = {"users": users, "success": success} self.network_queue.put(network_result)
def check_item_keys(items_dic, filename): """ Check for unexpected keys in an item dict. if we find any, complain about them in the error log. This shouldn't actually stop the program though, because it just means some data won't be recognized, and that data is only of limited importance. """ invalid_keys = set() for item_id in items_dic: for item_info_key in items_dic[item_id]: if item_info_key not in ItemInfo.valid_key_set: invalid_keys.add(item_info_key) if len(invalid_keys) > 0: log_error("The file " + filename + " contains unexpected keys: " + ", ".join(invalid_keys))
def check_item_keys(items_dic, filename): """ Check for unexpected keys in an item dict. if we find any, complain about them in the error log. This shouldn't actually stop the program though, because it just means some data won't be recognized, and that data is only of limited importance. """ invalid_keys = set() for item_id in items_dic: for item_info_key in items_dic[item_id]: if item_info_key not in ItemInfo.valid_key_set: invalid_keys.add(item_info_key) if len(invalid_keys) > 0: log_error("The file " + filename + " contains unexpected keys: " + ", ".join(invalid_keys))
def from_valid_json(json_dic, *args): """ Create an Item from a type-checked dic and a floor_list """ floor_list = args[0] floor = next((f for f in floor_list if f.floor_id == json_dic['floor_id']), None) if not floor: log_error("ERROR: Floor id %s is not found in state list", json_dic['floor_id']) return None item_id = json_dic['item_id'] if not Item.contains_info(item_id): item_id = "NEW" flagstr = json_dic['flags'] return Item(item_id, floor, flagstr=flagstr)
def from_valid_json(json_dic, *args): """ Create an Item from a type-checked dic and a floor_list """ floor_list = args[0] floor = next((f for f in floor_list if f.floor_id == json_dic['floor_id']), None) if not floor: log_error("ERROR: Floor id %s is not found in state list", json_dic['floor_id']) return None item_id = json_dic['item_id'] if not Item.contains_info(item_id): item_id = "NEW" flagstr = json_dic['flags'] return Item(item_id, floor, flagstr=flagstr)
def from_json(cls, json_dic, *args): """ This function does some type checking on expected attributes, and then calls the derived factory method """ log = logging.getLogger("tracker") if not isinstance(json_dic, dict): log_error("ERROR: json_dic is not a dictionary") return None # Basic type check for key, value_type in cls.serialize: if key not in json_dic: log_error("ERROR: key "+ key + " not found in dictionary") return None if not isinstance(json_dic[key], value_type): log_error("ERROR: key " + key + " is not a " + value_type.__name__ + " as expected") return None return cls.from_valid_json(json_dic, *args)
def from_json(cls, json_dic, *args): """ This function does some type checking on expected attributes, and then calls the derived factory method """ log = logging.getLogger("tracker") if not isinstance(json_dic, dict): log_error("ERROR: json_dic is not a dictionary") return None # Basic type check for key, value_type in cls.serialize: if key not in json_dic: log_error("ERROR: key " + key + " not found in dictionary") return None if not isinstance(json_dic[key], value_type): log_error("ERROR: key " + key + " is not a " + value_type.__name__ + " as expected") return None return cls.from_valid_json(json_dic, *args)
def run(self): """ The main routine which controls everything """ framecount = 0 # Create drawing tool to use to draw everything - it'll create its own screen drawing_tool = DrawingTool(wdir_prefix) drawing_tool.set_window_title_info(update_notifier=(" v" + self.tracker_version)) opt = Options() parser = LogParser(wdir_prefix, self.tracker_version, LogFinder()) event_result = None state = None custom_title_enabled = opt.custom_title_enabled read_from_server = opt.read_from_server write_to_server = opt.write_to_server game_version = opt.game_version state_version = -1 twitch_username = None new_states_queue = [] screen_error_message = None retry_in = 0 update_timer = opt.log_file_check_seconds last_game_version = None while event_result != Event.DONE: # Check for events and handle them event_result = drawing_tool.handle_events() # The user checked or unchecked the "Custom Title Enabled" checkbox if opt.custom_title_enabled != custom_title_enabled: custom_title_enabled = opt.custom_title_enabled drawing_tool.update_window_title() # The user started or stopped watching someone from the server (or they started watching a new person from the server) if opt.read_from_server != read_from_server or opt.twitch_name != twitch_username: twitch_username = opt.twitch_name read_from_server = opt.read_from_server new_states_queue = [] # Also restart version count if we go back and forth from log.txt to server if read_from_server: state_version = -1 state = None # Change the delay for polling, as we probably don't want to fetch it every second update_timer_override = 2 # Show who we are watching in the title bar drawing_tool.set_window_title_info(watching=True, watching_player=twitch_username, updates_queued=len(new_states_queue)) else: drawing_tool.set_window_title_info(watching=False) update_timer_override = 0 # The user started or stopped broadcasting to the server if opt.write_to_server != write_to_server: write_to_server = opt.write_to_server drawing_tool.set_window_title_info(uploading=opt.write_to_server) if opt.game_version != game_version: parser.reset() game_version = opt.game_version # Force refresh state if we updated options or if we need to retry # to contact the server. if (event_result == Event.OPTIONS_UPDATE or (screen_error_message is not None and retry_in == 0)): # By setting the framecount to 0 we ensure we'll refresh the state right away framecount = 0 screen_error_message = None retry_in = 0 # Force updates after changing options if state is not None: state.modified = True # normally we check for updates based on how the option is set # when doing network stuff, this can be overridden update_delay = opt.log_file_check_seconds if update_timer_override != 0: update_delay = update_timer_override # Now we re-process the log file to get anything that might have loaded; # do it every update_timer seconds (making sure to truncate to an integer # or else it might never mod to 0) frames_between_checks = int(Options().framerate_limit * update_delay) if frames_between_checks <= 0: frames_between_checks = 1 if framecount % frames_between_checks == 0: if retry_in != 0: retry_in -= 1 # Let the parser do his thing and give us a state if opt.read_from_server: base_url = opt.trackerserver_url + "/tracker/api/user/" + opt.twitch_name json_dict = None try: json_version = urllib.request.urlopen(base_url + "/version").read() if int(json_version) > state_version: # FIXME better handling of 404 error ? json_state = urllib.request.urlopen(base_url).read() json_dict = json.loads(json_state, "utf-8") new_state = TrackerState.from_json(json_dict) if new_state is None: raise Exception("server gave us empty state") state_version = int(json_version) new_states_queue.append((state_version, new_state)) drawing_tool.set_window_title_info(updates_queued=len(new_states_queue)) except Exception: state = None log_error("Couldn't load state from server\n" + traceback.format_exc()) if json_dict is not None: if "tracker_version" in json_dict: their_version = json_dict["tracker_version"] else: # This is the only version that can upload to the server but doesn't include a version string their_version = "0.10-beta1" if their_version != self.tracker_version: screen_error_message = "They are using tracker version " + their_version + " but you have " + self.tracker_version else: force_draw = state and state.modified state = parser.parse() if force_draw: state.modified = True if write_to_server and not opt.trackerserver_authkey: screen_error_message = "Your authkey is blank. Get a new authkey in the options menu and paste it into the authkey text field." if state is not None and write_to_server and state.modified and screen_error_message is None: opener = urllib.request.build_opener(urllib.request.HTTPHandler) put_url = opt.trackerserver_url + "/tracker/api/update/" + opt.trackerserver_authkey json_string = json.dumps(state, cls=TrackerStateEncoder, sort_keys=True) request = urllib.request.Request(put_url, data=json_string) request.add_header('Content-Type', 'application/json') request.get_method = lambda: 'PUT' try: result = opener.open(request) result_json = json.loads(result.read()) updated_user = result_json["updated_user"] if updated_user is None: screen_error_message = "The server didn't recognize you. Try getting a new authkey in the options menu." else: screen_error_message = None except Exception as e: log_error("ERROR: Couldn't send item info to server\n" + traceback.format_exc()) screen_error_message = "ERROR: Couldn't send item info to server, check tracker_log.txt" # Retry to write the state in 10*update_timer (aka 10 sec in write mode) retry_in = 10 # Check the new state at the front of the queue to see if it's time to use it if len(new_states_queue) > 0: (state_timestamp, new_state) = new_states_queue[0] current_timestamp = int(time.time()) if current_timestamp - state_timestamp >= opt.read_delay or opt.read_delay == 0 or state is None: state = new_state new_states_queue.pop(0) drawing_tool.set_window_title_info(updates_queued=len(new_states_queue)) if state is None and screen_error_message is None: if read_from_server: screen_error_message = "Unable to read state from server. Please verify your options setup and tracker_log.txt" # Retry to read the state in 5*update_timer (aka 10 sec in read mode) retry_in = 5 else: screen_error_message = "log.txt for " + opt.game_version + " not found. Make sure you have the right game selected in the options." if screen_error_message is not None: drawing_tool.write_error_message(screen_error_message) else: # We got a state, now we draw it drawing_tool.draw_state(state) # if we're watching someone and they change their game version, it can require us to reset if state and last_game_version != state.game_version: drawing_tool.reset_options() last_game_version = state.game_version drawing_tool.tick() framecount += 1 # Main loop finished; program is exiting drawing_tool.save_window_position() Options().save_options(wdir_prefix + "options.json")
def run(self): """ The main routine which controls everything """ framecount = 0 # Create drawing tool to use to draw everything - it'll create its own screen drawing_tool = DrawingTool(wdir_prefix) drawing_tool.set_window_title_info( update_notifier=(" v" + self.tracker_version)) opt = Options() parser = LogParser(wdir_prefix, self.tracker_version, LogFinder()) event_result = None state = None custom_title_enabled = opt.custom_title_enabled read_from_server = opt.read_from_server write_to_server = opt.write_to_server game_version = opt.game_version state_version = -1 twitch_username = None new_states_queue = [] screen_error_message = None retry_in = 0 update_timer = opt.log_file_check_seconds last_game_version = None while event_result != Event.DONE: # Check for events and handle them event_result = drawing_tool.handle_events() # The user checked or unchecked the "Custom Title Enabled" checkbox if opt.custom_title_enabled != custom_title_enabled: custom_title_enabled = opt.custom_title_enabled drawing_tool.update_window_title() # The user started or stopped watching someone from the server (or they started watching a new person from the server) if opt.read_from_server != read_from_server or opt.twitch_name != twitch_username: twitch_username = opt.twitch_name read_from_server = opt.read_from_server new_states_queue = [] # Also restart version count if we go back and forth from log.txt to server if read_from_server: state_version = -1 state = None # Change the delay for polling, as we probably don't want to fetch it every second update_timer_override = 2 # Show who we are watching in the title bar drawing_tool.set_window_title_info( watching=True, watching_player=twitch_username, updates_queued=len(new_states_queue)) else: drawing_tool.set_window_title_info(watching=False) update_timer_override = 0 # The user started or stopped broadcasting to the server if opt.write_to_server != write_to_server: write_to_server = opt.write_to_server drawing_tool.set_window_title_info( uploading=opt.write_to_server) if opt.game_version != game_version: parser.reset() game_version = opt.game_version # Force refresh state if we updated options or if we need to retry # to contact the server. if (event_result == Event.OPTIONS_UPDATE or (screen_error_message is not None and retry_in == 0)): # By setting the framecount to 0 we ensure we'll refresh the state right away framecount = 0 screen_error_message = None retry_in = 0 # Force updates after changing options if state is not None: state.modified = True # normally we check for updates based on how the option is set # when doing network stuff, this can be overridden update_delay = opt.log_file_check_seconds if update_timer_override != 0: update_delay = update_timer_override # Now we re-process the log file to get anything that might have loaded; # do it every update_timer seconds (making sure to truncate to an integer # or else it might never mod to 0) frames_between_checks = int(Options().framerate_limit * update_delay) if frames_between_checks <= 0: frames_between_checks = 1 if framecount % frames_between_checks == 0: if retry_in != 0: retry_in -= 1 # Let the parser do his thing and give us a state if opt.read_from_server: base_url = opt.trackerserver_url + "/tracker/api/user/" + opt.twitch_name json_dict = None try: json_version = urllib.request.urlopen( base_url + "/version").read() if int(json_version) > state_version: # FIXME better handling of 404 error ? json_state = urllib.request.urlopen( base_url).read() json_dict = json.loads(json_state) new_state = TrackerState.from_json(json_dict) if new_state is None: raise Exception("server gave us empty state") state_version = int(json_version) new_states_queue.append((state_version, new_state)) drawing_tool.set_window_title_info( updates_queued=len(new_states_queue)) except Exception: state = None log_error("Couldn't load state from server\n" + traceback.format_exc()) if json_dict is not None: if "tracker_version" in json_dict: their_version = json_dict["tracker_version"] else: # This is the only version that can upload to the server but doesn't include a version string their_version = "0.10-beta1" if their_version != self.tracker_version: screen_error_message = "They are using tracker version " + their_version + " but you have " + self.tracker_version else: force_draw = state and state.modified state = parser.parse() if force_draw and state is not None: state.modified = True if write_to_server and not opt.trackerserver_authkey: screen_error_message = "Your authkey is blank. Get a new authkey in the options menu and paste it into the authkey text field." if state is not None and write_to_server and state.modified and screen_error_message is None: opener = urllib.request.build_opener( urllib.request.HTTPHandler) put_url = opt.trackerserver_url + "/tracker/api/update/" + opt.trackerserver_authkey json_string = json.dumps( state, cls=TrackerStateEncoder, sort_keys=True).encode("utf-8") request = urllib.request.Request(put_url, data=json_string) request.add_header('Content-Type', 'application/json') request.get_method = lambda: 'PUT' try: result = opener.open(request) result_json = json.loads(result.read()) updated_user = result_json["updated_user"] if updated_user is None: screen_error_message = "The server didn't recognize you. Try getting a new authkey in the options menu." else: screen_error_message = None except Exception as e: log_error( "ERROR: Couldn't send item info to server\n" + traceback.format_exc()) screen_error_message = "ERROR: Couldn't send item info to server, check tracker_log.txt" # Retry to write the state in 10*update_timer (aka 10 sec in write mode) retry_in = 10 # Check the new state at the front of the queue to see if it's time to use it if len(new_states_queue) > 0: (state_timestamp, new_state) = new_states_queue[0] current_timestamp = int(time.time()) if current_timestamp - state_timestamp >= opt.read_delay or opt.read_delay == 0 or state is None: state = new_state new_states_queue.pop(0) drawing_tool.set_window_title_info( updates_queued=len(new_states_queue)) if state is None and screen_error_message is None: if read_from_server: screen_error_message = "Unable to read state from server. Please verify your options setup and tracker_log.txt" # Retry to read the state in 5*update_timer (aka 10 sec in read mode) retry_in = 5 else: screen_error_message = "log.txt for " + opt.game_version + " not found. Make sure you have the right game selected in the options." if screen_error_message is not None: drawing_tool.write_error_message(screen_error_message) else: # We got a state, now we draw it drawing_tool.draw_state(state, framecount) # if we're watching someone and they change their game version, it can require us to reset if state and last_game_version != state.game_version: drawing_tool.reset_options() last_game_version = state.game_version drawing_tool.tick() framecount += 1 # Main loop finished; program is exiting drawing_tool.save_window_position() Options().save_options(wdir_prefix + "options.json")