Пример #1
0
    def test_localization(self):
        english_lines = localization.access_localization_data()['English']
        num_lines_total = len(english_lines) - 4
        incorrect_hashes = []
        test_text = "This text isn't in the localization files"
        meta_keys = ('name_localized', 'code', 'credits', 'notes')

        for key in english_lines:
            test_key = localization.hash_text(
                localization.access_localization_data()['English'][key])

            if key != test_key and key not in meta_keys:
                incorrect_hashes.append(
                    (key, test_key,
                     localization.access_localization_data()['English'][key]))

        self.assertEqual(incorrect_hashes, [])

        for language in localization.langs:
            localizer = localization.Localizer(language=language,
                                               persist_missing=False)
            self.assertEqual(
                repr(localizer),
                f'localization.Localizer ({language}, appending=False, 0 missing lines)'
            )

            num_equal_lines = 0
            for key_english in english_lines:
                if key_english in meta_keys:
                    continue

                line_english = localization.access_localization_data(
                )['English'][key_english]
                line_localized = localizer.text(line_english)

                try:
                    self.assertNotEqual(line_localized, "")
                    self.assertEqual(line_localized.count('{0}'),
                                     line_english.count('{0}'))
                    self.assertEqual(line_localized.count('{1}'),
                                     line_english.count('{1}'))
                except AssertionError as error:
                    raise AssertionError(
                        f"{error}\n{line_english}\n{line_localized}")

                if line_localized == line_english:
                    num_equal_lines += 1

            if language == 'English':
                self.assertEqual(num_equal_lines, num_lines_total)
            else:
                self.assertLess(num_equal_lines, num_lines_total / 4)

            self.assertEqual(localizer.text(test_text), test_text)

        db = utils.access_db()
        self.assertTrue(test_text in db['missing_localization'])
        db['missing_localization'] = []
        utils.access_db(write=db)
Пример #2
0
    def receive_update_check(
            self,
            raise_exceptions: bool = False) -> Optional[Tuple[str, str, str]]:
        import requests
        self.checked_response = True

        try:
            result = self.api_future.result()
        except (requests.Timeout, requests.exceptions.ReadTimeout):
            self.log.error(f"Update check timed out", reportable=False)

            if raise_exceptions:
                raise
        except (requests.RequestException, requests.ConnectionError):
            self.log.error(
                f"Connection error in updater: {traceback.format_exc()}",
                reportable=False)

            if raise_exceptions:
                raise
        except Exception:
            self.log.error(
                f"Non-connection based update error: {traceback.format_exc()}")
        else:
            self.log.debug(
                f"Update check took {round(result.elapsed.microseconds / 1000000, 3)} seconds"
            )
            response: dict = result.json()
            self.api_future = None

            try:
                newest_version: str = response['tag_name']
                downloads_url: str = response['html_url']
                changelog: str = response['body']
            except KeyError:
                if 'message' in response and 'API rate limit exceeded' in response[
                        'message']:
                    rate_limit_message: str = f"Github {response['message'].split('(')[0][:-1]}"
                    raise RateLimitError(rate_limit_message)
                else:
                    raise

            changelog_formatted: str = format_changelog(changelog)

            if launcher.VERSION == newest_version:
                self.log.debug(f"Up to date ({launcher.VERSION})")
            else:  # out of date
                self.log.error(
                    f"Out of date, newest version is {newest_version} (this is {launcher.VERSION})",
                    reportable=False)

                # save available version for the launcher
                db: Dict[str, Union[bool, list, str]] = utils.access_db()
                db['available_version'] = newest_version
                utils.access_db(db)

                return newest_version, downloads_url, changelog_formatted
Пример #3
0
def exc_already_reported(tb: str) -> bool:
    try:
        tb_hash: str = str(zlib.crc32(tb.encode('UTF8', errors='replace')))  # technically not a hash but w/e
        db: dict = utils.access_db()

        if tb_hash in db['tb_hashes']:
            return True
        else:
            db['tb_hashes'].append(tb_hash)
            utils.access_db(db)
            return False
    except Exception:
        return False
Пример #4
0
    def error(self, message_in: str, reportable: bool = True):
        if self.log_level_allowed('Error'):
            self.write_log('ERROR', message_in, use_errors_file=reportable)

        if reportable and settings.get('sentry_level') == 'All errors':
            db: Dict[str, Union[bool, list, str]] = utils.access_db()
            message_hash: int = zlib.adler32(message_in.encode('UTF8'))

            if message_hash not in db[
                    'error_hashes'] and message_hash not in self.local_error_hashes:
                self.local_error_hashes.append(message_hash)
                sentry_sdk.capture_message(message_in[-512:])
                db['error_hashes'].append(message_hash)
                utils.access_db(write=db)
            else:
                self.debug(
                    "Not reporting the error (has already been reported)")
Пример #5
0
def out_of_date_warning() -> str:
    try:
        available_version: str = utils.access_db()['available_version']

        if available_version:
            return f"\n\nBTW an newer version for TF2 Rich Presence is available ({available_version}), which may have fixed this crash."
        else:
            return ""
    except Exception:
        return ""
Пример #6
0
    def close_window(self):
        try:
            db: Dict[str, Union[bool, list, str]] = utils.access_db()
            # save position regardless of preserve_window_pos setting
            save_pos = get_window_center(self.master)
            db['gui_position'] = list(save_pos)
            utils.access_db(db)
            self.log.info(
                f"Closing main window and exiting program (saving pos as {save_pos}, loop body time stats are {min(self.main_loop_body_times)} min, "
                f"{statistics.median(self.main_loop_body_times)} median, {max(self.main_loop_body_times)} max)"
            )
            self.master.destroy()
        except Exception:
            pass  # we really do need the program to close now

        if self.main_controlled:
            self.alive = False  # this makes main raise SystemExit ASAP
        else:
            del self.log
            raise SystemExit
Пример #7
0
 def __init__(self,
              log: Optional[logger.Log] = None,
              language: Optional[str] = None,
              appending: bool = False,
              persist_missing: bool = True):
     self.log: Optional[logger.Log] = log
     self.language: str = language if language else settings.get('language')
     self.appending: bool = appending  # if extending localization files
     self.text.cache_clear()
     self.missing_lines: List[str] = utils.access_db(
     )['missing_localization'] if persist_missing else []
Пример #8
0
def detect_system_language(log: logger.Log):
    db: Dict[str, Union[bool, list, str]] = utils.access_db()

    if not db['has_asked_language']:
        language_codes: dict = {
            read_localization_files()[lang]['code']: lang
            for lang in langs[1:]
        }
        system_locale: str = locale.windows_locale[
            ctypes.windll.kernel32.GetUserDefaultUILanguage()]
        system_language_code: str = system_locale.split('_')[0]
        is_brazilian_port: bool = system_locale == 'pt_BR'

        if system_language_code in language_codes or is_brazilian_port:
            system_language: str = language_codes[system_language_code]
            can_localize: bool = True
        else:
            if system_language_code != 'en':
                log.error(f"System locale {system_locale} is not localizable")

            can_localize = False

        if can_localize and settings.get('language') != system_language:
            log.info(
                f"System language ({system_locale}, {system_language}) != settings language ({settings.get('language')}), asking to change"
            )
            db['has_asked_language'] = True
            utils.access_db(db)
            system_language_display: str = 'Português Brasileiro' if is_brazilian_port else system_language
            # this is intentionally not localized
            changed_language: str = messagebox.askquestion(
                f"TF2 Rich Presence {launcher.VERSION}",
                f"Change language to your system's default ({system_language_display})?"
            )
            log.debug(f"Changed language: {changed_language}")

            if changed_language == 'yes':
                settings.change('language', system_language)
Пример #9
0
    def text(self, english_text: str) -> str:
        # TODO: use manual language keys instead of text hashes (maybe)
        english_text_adler32: str = hash_text(english_text)

        if self.appending:  # only used for development
            access_localization_data(append=(english_text_adler32,
                                             english_text))
            return english_text

        if english_text_adler32 not in access_localization_data()[
                self.language]:
            if english_text not in self.missing_lines:
                self.missing_lines.append(english_text)

                db: Dict[str, Union[bool, list, str]] = utils.access_db()
                db['missing_localization'].append(english_text)
                utils.access_db(db)
                if self.log:
                    self.log.error(
                        f"\"{english_text}\" not in {self.language} localization",
                        reportable=False)

            # no available translation, so must default to the English text
            return english_text

        if self.language == 'English':
            # returns what was passed to this function, NOT what's in English.json
            # this means that that file is only used for helping with translating
            return english_text
        else:
            try:
                return access_localization_data()[
                    self.language][english_text_adler32]
            except KeyError as error:
                raise KeyError(
                    f"{error}, ({english_text_adler32}, {self.language})")
Пример #10
0
    def __init__(self, log: logger.Log, main_controlled: bool = False):
        self.main_controlled: bool = main_controlled
        self.log: logger.Log = log
        self.log.info("Initializing main GUI")
        self.loc: localization.Localizer = localization.Localizer(self.log)
        self.master: tk.Tk = tk.Tk()
        tk.Frame.__init__(self, self.master)
        self.pack(fill=tk.BOTH, expand=1, padx=0, pady=0)
        self.alive: bool = True

        self.scale: float = settings.get('gui_scale') / 100
        self.size: Tuple[int, int] = (round(500 * self.scale),
                                      round(250 * self.scale))
        self.master.geometry(f'{self.size[0]}x{self.size[1] + 20}'
                             )  # the +20 is for the menu bar
        self.master.title(
            self.loc.text("TF2 Rich Presence").format(launcher.VERSION))
        set_window_icon(self.log, self.master, False)
        self.master.resizable(False, False)  # disables resizing
        self.master.protocol('WM_DELETE_WINDOW', self.close_window)
        self.log.debug("Set up main window")

        # misc stuff that doesn't go anywhere else
        default_bg: ImageTk.PhotoImage = ImageTk.PhotoImage(
            Image.new('RGB', self.size))
        font_size: int = round(12 * self.scale)
        self.blank_image: ImageTk.PhotoImage = ImageTk.PhotoImage(
            Image.new('RGBA', (1, 1), color=(0, 0, 0, 0)))
        self.paused_image: ImageTk.PhotoImage = ImageTk.PhotoImage(
            Image.new('RGBA', self.size, (0, 0, 0, 128)))
        self.vignette: Image = self.load_image('vignette').resize(self.size)
        self.clean_console_log: bool = False
        self.text_state: Tuple[str, ...] = ('', )
        self.bg_state: Tuple[str, int, int] = ('', 0, 0)
        self.fg_state: str = ''
        self.class_state: str = ''
        self.available_update_data: Tuple[str, str, str] = ('', '', '')
        self.update_window_open: bool = False
        self.bottom_text_state: Dict[str, bool] = {
            'discord': False,
            'kataiser': False,
            'queued': False,
            'holiday': False
        }
        self.bottom_text_queue_state: str = ""
        self.holiday_text: str = ""
        self.launched_tf2_with_button: bool = False
        self.tf2_launch_cmd: Optional[Tuple[str, str]] = None
        self.main_loop_body_times: List[float] = []
        self.update_checker: updater.UpdateChecker = updater.UpdateChecker(
            self.log)
        self.console_log_path: Optional[str] = None

        menu_bar: tk.Menu = tk.Menu(self.master)
        self.file_menu = tk.Menu(menu_bar, tearoff=0)
        help_menu = tk.Menu(menu_bar, tearoff=0)
        menu_bar.add_cascade(label=self.loc.text("File"), menu=self.file_menu)
        menu_bar.add_cascade(label=self.loc.text("Help"), menu=help_menu)
        self.file_menu.add_command(label=self.loc.text("Change settings"),
                                   command=self.menu_open_settings,
                                   accelerator="Ctrl+S")
        self.file_menu.add_command(
            label=self.loc.text("Restore default settings"),
            command=self.menu_restore_defaults)
        self.file_menu.add_command(label=self.loc.text("Open console.log"),
                                   command=self.menu_open_console_log,
                                   state=tk.DISABLED)
        self.file_menu.add_command(
            label=self.loc.text("Trim/clean console.log"),
            command=self.menu_clean_console_log,
            state=tk.DISABLED)
        self.file_menu.add_command(label=self.loc.text("Open save directory"),
                                   command=self.menu_open_save_directory)
        self.file_menu.add_command(label=self.loc.text("Exit"),
                                   command=self.menu_exit,
                                   accelerator="Ctrl+Q")
        self.console_log_command_indices: Tuple[int, int] = (2, 3)
        help_menu.add_command(label=self.loc.text("Open Github page"),
                              command=self.menu_open_github)
        help_menu.add_command(label=self.loc.text("Open readme"),
                              command=self.menu_open_readme)
        help_menu.add_command(label=self.loc.text("Open changelog"),
                              command=self.menu_open_changelog)
        help_menu.add_command(label=self.loc.text("Open license"),
                              command=self.menu_open_license)
        help_menu.add_separator()
        help_menu.add_command(label=self.loc.text("Check for updates"),
                              command=self.menu_check_updates)
        help_menu.add_command(label=self.loc.text("Report bug/issue"),
                              command=self.menu_report_issue)
        help_menu.add_command(label=self.loc.text("About"),
                              command=self.menu_about,
                              accelerator="Ctrl+A")
        self.master.config(menu=menu_bar)
        self.bind_all('<Control-s>', self.menu_open_settings)
        self.bind_all('<Control-q>', self.menu_exit)
        self.bind_all('<Control-a>', self.menu_about)
        self.log.debug("Created menu bar")

        previous_gui_position: List[int] = utils.access_db()['gui_position']
        if previous_gui_position == [
                0, 0
        ] or not settings.get('preserve_window_pos'):
            # center the window on the screen
            window_x: int = round(self.winfo_screenwidth() / 2)
            window_y: int = round(self.winfo_screenheight() / 2) - 40
            pos_window_by_center(self.master, window_x, window_y)
            self.log.debug(
                f"Window position: {(window_x, window_y)} (centered)")
        else:
            # remember previous position
            window_x, window_y = previous_gui_position
            pos_window_by_center(self.master, window_x - 8, window_y - 41)
            self.log.debug(
                f"Window position: {(window_x, window_y)} (remembered)")

        # create the main drawing canvas and its elements
        self.canvas: tk.Canvas = tk.Canvas(width=self.size[0],
                                           height=self.size[1],
                                           borderwidth=0,
                                           highlightthickness=0)
        self.bg_image: int = self.canvas.create_image(0,
                                                      0,
                                                      anchor=tk.NW,
                                                      image=default_bg)
        self.bg_rect: int = self.canvas.create_image(0, 0, anchor=tk.NW)
        self.fg_shadow: int = self.canvas.create_image(65 * self.scale,
                                                       45 * self.scale,
                                                       anchor=tk.NW)
        self.fg_image: int = self.canvas.create_image(85 * self.scale,
                                                      65 * self.scale,
                                                      anchor=tk.NW)
        self.class_image: int = self.canvas.create_image(90 * self.scale,
                                                         180 * self.scale,
                                                         anchor=tk.CENTER)
        self.text_1: int = self.canvas.create_text(358 * self.scale,
                                                   110 * self.scale,
                                                   font=('TkDefaultFont',
                                                         font_size),
                                                   fill='white',
                                                   anchor=tk.CENTER)
        self.text_3_0: int = self.canvas.create_text(220 * self.scale,
                                                     105 * self.scale,
                                                     font=('TkDefaultFont',
                                                           font_size),
                                                     fill='white',
                                                     anchor=tk.W)
        self.text_3_1: int = self.canvas.create_text(220 * self.scale,
                                                     125 * self.scale,
                                                     font=('TkDefaultFont',
                                                           font_size),
                                                     fill='white',
                                                     anchor=tk.W)
        self.text_3_2: int = self.canvas.create_text(220 * self.scale,
                                                     145 * self.scale,
                                                     font=('TkDefaultFont',
                                                           font_size),
                                                     fill='gray',
                                                     anchor=tk.W)
        self.text_4_0: int = self.canvas.create_text(220 * self.scale,
                                                     95 * self.scale,
                                                     font=('TkDefaultFont',
                                                           font_size),
                                                     fill='white',
                                                     anchor=tk.W)
        self.text_4_1: int = self.canvas.create_text(220 * self.scale,
                                                     115 * self.scale,
                                                     font=('TkDefaultFont',
                                                           font_size),
                                                     fill='white',
                                                     anchor=tk.W)
        self.text_4_2: int = self.canvas.create_text(220 * self.scale,
                                                     135 * self.scale,
                                                     font=('TkDefaultFont',
                                                           font_size),
                                                     fill='white',
                                                     anchor=tk.W)
        self.text_4_3: int = self.canvas.create_text(220 * self.scale,
                                                     155 * self.scale,
                                                     font=('TkDefaultFont',
                                                           font_size),
                                                     fill='gray',
                                                     anchor=tk.W)
        self.update_text: int = self.canvas.create_text(467 * self.scale,
                                                        20 * self.scale,
                                                        font=('TkDefaultFont',
                                                              font_size),
                                                        fill='light gray',
                                                        anchor=tk.E)
        self.update_icon: int = self.canvas.create_image(492 * self.scale,
                                                         8 * self.scale,
                                                         anchor=tk.NE)
        self.paused_overlay: int = self.canvas.create_image(0, 0, anchor=tk.NW)
        self.paused_text: int = self.canvas.create_text(
            5 * self.scale,
            5 * self.scale,
            font=('TkDefaultFont', round(14 * self.scale)),
            fill='white',
            anchor=tk.NW)
        self.bottom_text: int = self.canvas.create_text(
            2 * self.scale,
            248 * self.scale,
            font=('TkDefaultFont', round(10 * self.scale)),
            fill='light gray',
            anchor=tk.SW)
        self.canvas.tag_bind(self.update_text, '<Button-1>',
                             self.show_update_menu)
        self.canvas.tag_bind(self.update_icon, '<Button-1>',
                             self.show_update_menu)
        self.canvas.place(x=0, y=0)
        self.log.debug("Created canvas elements")

        self.launch_tf2_button: tk.Button = tk.Button(
            self.master,
            text=self.loc.text("Launch TF2"),
            font=('TkDefaultFont', round(9 * self.scale)),
            command=self.launch_tf2)

        self.safe_update()
        self.window_dimensions = self.master.winfo_width(
        ), self.master.winfo_height()
        self.log.debug(
            f"Window size: {self.window_dimensions} (scale {self.scale})")

        # move window to the top (but don't keep it there)
        self.master.lift()
        self.master.attributes('-topmost', True)
        self.master.after_idle(self.master.attributes, '-topmost', False)
        self.log.debug("Finished creating GUI")
Пример #11
0
    def __init__(self, path: Optional[str] = None):
        # make sure there's actually somewhere to put the log
        if os.path.isdir('logs'):
            self.logs_path: str = 'logs'
            created_logs_dir: bool = False
        else:
            self.logs_path = os.path.join(os.getenv('APPDATA'),
                                          'TF2 Rich Presence', 'logs')

            if not os.path.isdir(self.logs_path):
                os.mkdir(
                    os.path.join(os.getenv('APPDATA'), 'TF2 Rich Presence'))
                os.mkdir(self.logs_path)
                time.sleep(0.1)  # ensure it gets created
                created_logs_dir = True
            else:
                created_logs_dir = False

        # find user's pc and account name
        user_pc_name: str = socket.gethostname()
        try:
            user_identifier: str = getpass.getuser()
        except ModuleNotFoundError:
            user_identifier = user_pc_name

        if path:
            self.filename: str = path
        else:
            existing_logs: List[str] = sorted(os.listdir(self.logs_path))
            log_index: int = 0

            while True:
                filename: str = f'TF2RP_{user_pc_name}_{user_identifier}_{launcher.VERSION}_{log_index}.log'
                log_index += 1

                if filename not in existing_logs and f'{filename}.gz' not in existing_logs:
                    break

            self.filename = os.path.join(self.logs_path, filename)

        # setup
        self.filename_errors: str = os.path.join(
            self.logs_path,
            f'TF2RP_{user_pc_name}_{user_identifier}_{launcher.VERSION}.errors.log'
        )
        self.last_log_time: float = time.perf_counter()
        self.to_stderr: bool = launcher.DEBUG
        self.force_disabled: bool = False
        self.log_levels: List[str] = [
            'Debug', 'Info', 'Error', 'Critical', 'Off'
        ]
        self.local_error_hashes: List[int] = []  # just in case DB.json breaks

        if self.enabled():
            self.log_file: TextIO = open(self.filename, 'a', encoding='UTF8')

            if created_logs_dir:
                self.debug(
                    f"Created logs folder at {os.path.abspath(self.logs_path)}"
                )

        try:
            utils.access_db(write=utils.access_db(),
                            pass_permission_error=False)
        except PermissionError:
            self.error(
                "DB.json can't be written to, due to permissions. This could cause crashes"
            )

        self.debug(f"Created {repr(self)}")