├── .env.example ├── .eslintrc.cjs ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ └── build.yml ├── .gitignore ├── LICENSE ├── README.md ├── backend ├── @types │ └── index.ts ├── electron-env.d.ts ├── handlers │ ├── achievements │ │ ├── data.ts │ │ ├── index.ts │ │ ├── item.ts │ │ ├── locator.ts │ │ ├── parse.ts │ │ └── watcher.ts │ ├── downloads │ │ ├── http │ │ │ ├── index.ts │ │ │ └── utils.ts │ │ ├── index.ts │ │ ├── queue │ │ │ ├── index.ts │ │ │ ├── item.ts │ │ │ └── persistence.ts │ │ └── torrent │ │ │ ├── index.ts │ │ │ └── utils.ts │ ├── events │ │ ├── achievements │ │ │ ├── getUnlocked.ts │ │ │ └── index.ts │ │ ├── app │ │ │ ├── autoLaunch.ts │ │ │ ├── close.ts │ │ │ ├── index.ts │ │ │ ├── maximize.ts │ │ │ ├── minimize.ts │ │ │ └── openExternal.ts │ │ ├── db │ │ │ ├── external_accounts │ │ │ │ ├── add-account.ts │ │ │ │ ├── delete-account.ts │ │ │ │ ├── get-account.ts │ │ │ │ ├── get-accounts.ts │ │ │ │ ├── index.ts │ │ │ │ └── update-account.ts │ │ │ ├── games │ │ │ │ ├── add-game.ts │ │ │ │ ├── delete-game.ts │ │ │ │ ├── get-all-games.ts │ │ │ │ ├── get-game-by-id.ts │ │ │ │ ├── get-game-by-igdb-id.ts │ │ │ │ ├── index.ts │ │ │ │ └── update-game.ts │ │ │ ├── index.ts │ │ │ └── lists │ │ │ │ ├── add-game-to-List.ts │ │ │ │ ├── create-list.ts │ │ │ │ ├── delete-game.ts │ │ │ │ ├── delete-list.ts │ │ │ │ ├── get-all-lists.ts │ │ │ │ ├── get-games-in-list.ts │ │ │ │ ├── index.ts │ │ │ │ └── remove-game-from-list.ts │ │ ├── downloads │ │ │ ├── addDownload.ts │ │ │ ├── cancelDownload.ts │ │ │ ├── clearCompleted.ts │ │ │ ├── getAllDownloads.ts │ │ │ ├── getDownload.ts │ │ │ ├── index.ts │ │ │ ├── pauseDownload.ts │ │ │ ├── removeDownload.ts │ │ │ ├── resumeDownload.ts │ │ │ ├── setPriority.ts │ │ │ ├── throttleDownload.ts │ │ │ └── updateConfig.ts │ │ ├── generic │ │ │ ├── get-app-info.ts │ │ │ ├── get-os.ts │ │ │ ├── index.ts │ │ │ ├── open-dialog.ts │ │ │ ├── open-downloads.ts │ │ │ ├── open-folder.ts │ │ │ └── request.ts │ │ ├── index.ts │ │ ├── launcher │ │ │ ├── index.ts │ │ │ ├── play-game.ts │ │ │ └── stop-game.ts │ │ ├── logger │ │ │ ├── clear.ts │ │ │ ├── delete.ts │ │ │ ├── filter.ts │ │ │ ├── get-all-logs.ts │ │ │ ├── get-logged-dates.ts │ │ │ ├── index.ts │ │ │ └── log.ts │ │ ├── plugins │ │ │ ├── check-for-updates.ts │ │ │ ├── delete-plugin.ts │ │ │ ├── disable-plugin.ts │ │ │ ├── enable-plugin.ts │ │ │ ├── get-mutiple-choice-download.ts │ │ │ ├── get-plugin.ts │ │ │ ├── index.ts │ │ │ ├── install-plugin.ts │ │ │ ├── list-plugins.ts │ │ │ ├── search-plugins.ts │ │ │ ├── update-all.ts │ │ │ └── update-plugin.ts │ │ ├── settings │ │ │ ├── get-all.ts │ │ │ ├── get-setting.ts │ │ │ ├── index.ts │ │ │ ├── reload.ts │ │ │ ├── reset-to-default.ts │ │ │ └── update-setting.ts │ │ ├── themes │ │ │ ├── delete-theme.ts │ │ │ ├── get-theme.ts │ │ │ ├── index.ts │ │ │ ├── install-theme.ts │ │ │ └── list-themes.ts │ │ ├── torrent │ │ │ ├── index.ts │ │ │ ├── throttleDownload.ts │ │ │ └── throttleUpload.ts │ │ ├── updater │ │ │ ├── check-for-update.ts │ │ │ ├── index.ts │ │ │ └── install.ts │ │ └── utils │ │ │ ├── index.ts │ │ │ └── registerEvent.ts │ ├── index.ts │ ├── launcher │ │ ├── gameProcessLauncher.ts │ │ ├── games_launched.ts │ │ └── utils.ts │ ├── logging │ │ └── index.ts │ ├── notifications │ │ └── index.ts │ ├── plugins │ │ ├── index.ts │ │ └── plugin.ts │ ├── themes │ │ └── index.ts │ └── updater │ │ └── index.ts ├── main.ts ├── preload.ts ├── sql │ ├── index.ts │ ├── knex.ts │ ├── knexfile.ts │ ├── queries │ │ ├── accounts.ts │ │ ├── achievements.ts │ │ ├── base.ts │ │ ├── games.ts │ │ ├── index.ts │ │ └── lists.ts │ └── utils.ts └── utils │ ├── constants.ts │ ├── index.ts │ ├── json │ └── jsonFileEditor.ts │ ├── playsound.ts │ ├── settings │ ├── constants.ts │ └── settings.ts │ ├── utils.ts │ ├── uuid.ts │ └── window.ts ├── build ├── icon.icns ├── icon.ico └── icons │ ├── 1024x1024.png │ ├── 128x128.png │ ├── 16x16.png │ ├── 24x24.png │ ├── 256x256.png │ ├── 32x32.png │ ├── 48x48.png │ ├── 512x512.png │ └── 64x64.png ├── components.json ├── electron-builder.json5 ├── flake.nix ├── index.html ├── package.json ├── postcss.config.js ├── public └── icon.png ├── resources ├── icon.png └── sounds │ ├── achievement_unlock.wav │ └── complete.wav ├── src ├── @types │ ├── accounts │ │ ├── index.ts │ │ ├── real_debrid.ts │ │ ├── torbox.ts │ │ └── types.ts │ ├── achievements │ │ ├── db .ts │ │ ├── index.ts │ │ └── types.ts │ ├── download │ │ ├── index.ts │ │ ├── queue-types.ts │ │ └── types.ts │ ├── global.d.ts │ ├── index.ts │ ├── launcher.ts │ ├── library │ │ └── types.ts │ ├── logs.ts │ ├── settings │ │ ├── index.ts │ │ └── types.ts │ ├── themes.ts │ ├── torrent │ │ ├── index.ts │ │ └── types.ts │ └── types.ts ├── App.tsx ├── assets │ ├── bg.png │ ├── icon.png │ └── protondb.png ├── components │ ├── IGDBImage.tsx │ ├── banner │ │ └── index.tsx │ ├── buttonWithIcon.tsx │ ├── cards │ │ ├── bannerCard.tsx │ │ ├── defaultCard.tsx │ │ ├── listCard │ │ │ ├── image.tsx │ │ │ ├── index.tsx │ │ │ └── overlay.tsx │ │ ├── sourcecard.tsx │ │ └── unified-plugin-card.tsx │ ├── carouselButton.tsx │ ├── confirmClose.tsx │ ├── confirmation.tsx │ ├── containers │ │ ├── index.ts │ │ ├── mainContainer.tsx │ │ ├── row.tsx │ │ └── section.tsx │ ├── data-table │ │ ├── column-header.tsx │ │ ├── column-toggle.tsx │ │ └── pagination.tsx │ ├── errorComponent │ │ └── index.tsx │ ├── folderButton.tsx │ ├── genericRow.tsx │ ├── info │ │ ├── infoBar.tsx │ │ ├── media │ │ │ ├── index.tsx │ │ │ ├── screenshots.tsx │ │ │ └── trailer.tsx │ │ ├── similar │ │ │ └── index.tsx │ │ ├── sources │ │ │ ├── index.tsx │ │ │ └── soruces.tsx │ │ ├── specs │ │ │ ├── index.tsx │ │ │ └── row.tsx │ │ ├── tabs │ │ │ ├── about.tsx │ │ │ └── selected.tsx │ │ ├── top.tsx │ │ └── topbar │ │ │ ├── addToListButton.tsx │ │ │ ├── backButton.tsx │ │ │ ├── index.tsx │ │ │ └── title.tsx │ ├── inputWithIcon.tsx │ ├── protonDbBadge │ │ └── index.tsx │ ├── skeletons │ │ ├── banner.tsx │ │ ├── defaultCard.tsx │ │ ├── genericRow.tsx │ │ ├── index.ts │ │ └── info │ │ │ ├── index.ts │ │ │ ├── top.skeleton.tsx │ │ │ └── topbar.skeleton.tsx │ ├── spinner.tsx │ ├── starts.tsx │ ├── theme-provider.tsx │ ├── titleBar │ │ ├── control.tsx │ │ ├── controlWithIcon.tsx │ │ ├── icons.tsx │ │ ├── index.tsx │ │ └── traffic-lights.tsx │ ├── trailer │ │ ├── dialogContent.tsx │ │ └── index.tsx │ └── ui │ │ ├── aspect-ratio.tsx │ │ ├── avatar.tsx │ │ ├── badge.tsx │ │ ├── button.tsx │ │ ├── card.tsx │ │ ├── carousel.tsx │ │ ├── chart.tsx │ │ ├── checkbox.tsx │ │ ├── command.tsx │ │ ├── dialog.tsx │ │ ├── dropdown-menu.tsx │ │ ├── form.tsx │ │ ├── input.tsx │ │ ├── label.tsx │ │ ├── popover.tsx │ │ ├── progress.tsx │ │ ├── radio-group.tsx │ │ ├── scroll-area.tsx │ │ ├── select.tsx │ │ ├── separator.tsx │ │ ├── sheet.tsx │ │ ├── skeleton.tsx │ │ ├── slider.tsx │ │ ├── sonner.tsx │ │ ├── switch.tsx │ │ ├── table.tsx │ │ ├── tabs.tsx │ │ ├── tooltip.tsx │ │ └── typography.tsx ├── contexts │ └── I18N.tsx ├── features │ ├── accounts │ │ └── components │ │ │ └── table │ │ │ ├── columns.tsx │ │ │ ├── data-table.tsx │ │ │ └── index.tsx │ ├── achievements │ │ ├── components │ │ │ ├── cards │ │ │ │ └── achievement.tsx │ │ │ └── container.tsx │ │ └── hooks │ │ │ ├── useGetAchievementsData.ts │ │ │ └── useGetUnlockedAchievements.ts │ ├── downloads │ │ ├── components │ │ │ ├── DownloadItem.tsx │ │ │ ├── DownloadTabs.tsx │ │ │ ├── Downloads.tsx │ │ │ ├── EmptyState.tsx │ │ │ ├── ErrorState.tsx │ │ │ ├── LoadingState.tsx │ │ │ └── index.ts │ │ └── hooks │ │ │ ├── index.ts │ │ │ ├── useDownloadActions.ts │ │ │ ├── useDownloadEvents.ts │ │ │ └── useDownloadStats.ts │ ├── home │ │ └── components │ │ │ ├── FeaturedGames.tsx │ │ │ ├── GameCategories.tsx │ │ │ ├── GameRows.tsx │ │ │ └── HeroSection.tsx │ ├── library │ │ ├── components │ │ │ ├── activeLibrary.tsx │ │ │ ├── cards │ │ │ │ ├── continuePlaying │ │ │ │ │ ├── BackgroundImage.tsx │ │ │ │ │ ├── ContinuePlayingCardOverlay │ │ │ │ │ │ ├── actions │ │ │ │ │ │ │ ├── delete.tsx │ │ │ │ │ │ │ ├── edit.tsx │ │ │ │ │ │ │ └── index.tsx │ │ │ │ │ │ └── index.tsx │ │ │ │ │ ├── PlayStopButton.tsx │ │ │ │ │ └── index.tsx │ │ │ │ └── newGame.tsx │ │ │ ├── containers │ │ │ │ ├── activeLibraryGame.tsx │ │ │ │ ├── activeLibraryList.tsx │ │ │ │ ├── games.tsx │ │ │ │ └── tabs.tsx │ │ │ ├── continuePlaying.tsx │ │ │ ├── gameFormInput.tsx │ │ │ ├── header │ │ │ │ ├── index.tsx │ │ │ │ └── listActions.tsx │ │ │ ├── mediaCarousel │ │ │ │ ├── index.tsx │ │ │ │ ├── mainMediaDisplay.tsx │ │ │ │ ├── thumbnailCarousel.tsx │ │ │ │ └── types.ts │ │ │ ├── modals │ │ │ │ └── newGame │ │ │ │ │ ├── forms │ │ │ │ │ ├── metadata.tsx │ │ │ │ │ └── settings.tsx │ │ │ │ │ ├── import.tsx │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── schema.ts │ │ │ ├── playtime.tsx │ │ │ └── updateForm.tsx │ │ └── hooks │ │ │ ├── useFormActions.tsx │ │ │ ├── useGames.ts │ │ │ └── usePlayGame.ts │ ├── lists │ │ ├── components │ │ │ ├── container │ │ │ │ ├── listContainer.tsx │ │ │ │ └── listsContainer.tsx │ │ │ ├── dropdownContent.tsx │ │ │ ├── dropdownItem.tsx │ │ │ ├── listsDropdown.tsx │ │ │ ├── newListDialogContent.tsx │ │ │ └── updateList.tsx │ │ └── hooks │ │ │ └── useLists.ts │ ├── navigation │ │ └── components │ │ │ ├── cards │ │ │ └── playing.tsx │ │ │ ├── containers │ │ │ ├── bottom.tsx │ │ │ ├── middle.tsx │ │ │ └── top.tsx │ │ │ ├── item.tsx │ │ │ └── navbar.tsx │ ├── plugins │ │ └── providers │ │ │ ├── components │ │ │ └── community-providers.tsx │ │ │ ├── hooks │ │ │ └── useProviders.ts │ │ │ └── utils │ │ │ └── api │ │ │ └── providersApi.ts │ ├── realDebrid │ │ ├── components │ │ │ └── realDebridDialogContent.tsx │ │ └── utils │ │ │ └── auth.ts │ ├── search │ │ ├── components │ │ │ ├── card.tsx │ │ │ └── search.tsx │ │ └── hooks │ │ │ └── useSearch.ts │ ├── settings │ │ ├── components │ │ │ ├── linkGroup.tsx │ │ │ ├── section.tsx │ │ │ ├── settingsItem.tsx │ │ │ ├── sidebar.tsx │ │ │ ├── tab.tsx │ │ │ ├── tabs │ │ │ │ ├── accounts │ │ │ │ │ ├── addAccountButton.tsx │ │ │ │ │ └── index.tsx │ │ │ │ ├── container.tsx │ │ │ │ ├── developer │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── settings │ │ │ │ │ │ └── logDisplay │ │ │ │ │ │ ├── index.tsx │ │ │ │ │ │ ├── logSwitch.tsx │ │ │ │ │ │ ├── logTypes │ │ │ │ │ │ ├── base.tsx │ │ │ │ │ │ ├── error.tsx │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── info.tsx │ │ │ │ │ │ └── warn.tsx │ │ │ │ │ │ └── logWindow.tsx │ │ │ │ ├── download.tsx │ │ │ │ ├── general │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── settings │ │ │ │ │ │ ├── language.tsx │ │ │ │ │ │ └── title-bar.tsx │ │ │ │ ├── miscellaneous.tsx │ │ │ │ └── plugins │ │ │ │ │ ├── addButton.tsx │ │ │ │ │ ├── addPluginModal.tsx │ │ │ │ │ ├── display.tsx │ │ │ │ │ ├── index.tsx │ │ │ │ │ ├── search.tsx │ │ │ │ │ └── sort.tsx │ │ │ └── title.tsx │ │ └── hooks │ │ │ └── useSettingsTabs.tsx │ ├── splashscreen │ │ ├── components │ │ │ └── index.tsx │ │ └── styles.css │ ├── torBox │ │ ├── components │ │ │ └── torBoxDialogContent.tsx │ │ └── utils │ │ │ └── auth.ts │ └── updater │ │ └── components │ │ └── updater.tsx ├── global.css ├── hooks │ ├── index.ts │ ├── useAppStartup.ts │ ├── useDebounce.ts │ ├── useGamepadButton.tsx │ ├── useLogger.ts │ ├── useMapState.ts │ ├── usePluginActions.ts │ ├── usePlugins.ts │ ├── useProtonDb.ts │ ├── useSetState.ts │ ├── useSettings.ts │ ├── useThemes.ts │ └── useUpdater.ts ├── i18n │ ├── index.tsx │ └── translations │ │ ├── chinese-simplified.json │ │ ├── english.json │ │ ├── french.json │ │ ├── german.json │ │ ├── hindi.json │ │ ├── indonesian.json │ │ ├── italian.json │ │ ├── japanese.json │ │ ├── korean.json │ │ ├── malay.json │ │ ├── portuguese.json │ │ ├── spanish.json │ │ ├── thai.json │ │ ├── urdu.json │ │ └── vietnamese.json ├── lib │ ├── api │ │ ├── base.ts │ │ ├── hltb │ │ │ └── types.ts │ │ ├── igdb │ │ │ ├── constants.ts │ │ │ ├── genre.ts │ │ │ ├── index.ts │ │ │ ├── theme.ts │ │ │ └── types.ts │ │ ├── index.ts │ │ ├── itad │ │ │ ├── index.ts │ │ │ └── types.ts │ │ ├── realdebrid │ │ │ ├── auth.ts │ │ │ ├── index.ts │ │ │ ├── models │ │ │ │ └── api.ts │ │ │ ├── torrents.ts │ │ │ ├── unrestrict.ts │ │ │ └── user.ts │ │ └── torbox │ │ │ ├── index.ts │ │ │ ├── models │ │ │ └── api.ts │ │ │ ├── torrents.ts │ │ │ └── user.ts │ ├── helpers │ │ ├── formatName.ts │ │ ├── helpers.ts │ │ └── index.ts │ ├── history.ts │ ├── html-parser.tsx │ ├── index.ts │ ├── mapping │ │ └── index.ts │ └── utils.ts ├── main.tsx ├── routeTree.gen.ts ├── routes │ ├── __root.tsx │ ├── downloads.lazy.tsx │ ├── genre │ │ └── $genreId.tsx │ ├── index.tsx │ ├── info │ │ └── $id.lazy.tsx │ ├── library.lazy.tsx │ ├── sections │ │ ├── mostAnticipated.lazy.tsx │ │ ├── newReleases.lazy.tsx │ │ └── topRated.tsx │ ├── settings.tsx │ └── theme │ │ └── $themeId.tsx ├── stores │ ├── account-services.tsx │ ├── downloads.tsx │ ├── games.tsx │ ├── lists.tsx │ ├── logger.tsx │ ├── plugins.tsx │ ├── settings.ts │ ├── themes.tsx │ └── updater.ts └── vite-env.d.ts ├── tsconfig.json ├── tsconfig.node.json ├── vite.config.ts └── yarn.lock /.env.example: -------------------------------------------------------------------------------- 1 | # GOTO: https://api-docs.igdb.com/#getting-started 2 | # IGDB (Twitch) client id 3 | VITE_TWITCH_CLIENT_ID= 4 | # IGDB (Twitch) client secret 5 | VITE_TWITCH_CLIENT_SECRET= 6 | 7 | # GOTO: https://docs.isthereanydeal.com/#section/Access 8 | # Itad (IsThereAnyDeal) api key 9 | VITE_ITAD_API_KEY= 10 | 11 | 12 | # Open source app RD (Real Debrid) client id found on the real debrid docs 13 | VITE_RD_CLIENT_ID="X245A4XAIBGVM" 14 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { browser: true, es2020: true }, 4 | extends: [ 5 | "eslint:recommended", 6 | "plugin:@typescript-eslint/recommended", 7 | "plugin:react-hooks/recommended", 8 | ], 9 | ignorePatterns: ["dist", ".eslintrc.cjs"], 10 | parser: "@typescript-eslint/parser", 11 | plugins: ["react-refresh"], 12 | rules: { 13 | "react-refresh/only-export-components": [ 14 | "warn", 15 | { allowConstantExport: true }, 16 | ], 17 | "@typescript-eslint/no-explicit-any": "off", 18 | "@typescript-eslint/no-unused-vars": "off", 19 | "prefer-const": "warn", 20 | }, 21 | }; 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 18 | 19 | ### Bug Description 20 | 21 | 22 | ### Steps to Reproduce 23 | 24 | 1. 25 | 2. 26 | 3. 27 | 28 | ### Expected Behavior 29 | 30 | 31 | ### Actual Behavior 32 | 33 | 34 | ### Screenshots or Logs 35 | 36 | 37 | ```plaintext 38 | Logs go here 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 15 | 16 | ### Feature Description 17 | 18 | 19 | ### Problem Statement 20 | 21 | 22 | ### Proposed Solution 23 | 24 | 25 | ### Alternatives Considered 26 | 27 | 28 | ### Additional Context 29 | 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2025, Team-Falkor 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | 2. Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | 3. Neither the name of the copyright holder nor the names of its 16 | contributors may be used to endorse or promote products derived from 17 | this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /backend/@types/index.ts: -------------------------------------------------------------------------------- 1 | export * from "../../src/@types/launcher"; 2 | export * from "../../src/@types/themes"; 3 | -------------------------------------------------------------------------------- /backend/electron-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | declare namespace NodeJS { 4 | interface ProcessEnv { 5 | APP_ROOT: string; 6 | /** /dist/ or /public/ */ 7 | VITE_PUBLIC: string; 8 | VITE_TWITCH_CLIENT_ID: string; 9 | VITE_TWITCH_CLIENT_SECRET: string; 10 | VITE_ITAD_API_KEY: string; 11 | VITE_RD_CLIENT_ID: string; 12 | VITE_STEAMGRIDDB_API_KEY?: string; 13 | FALKOR_API_BASE_URL?: string; 14 | debug?: boolean; 15 | 16 | [key: string]: string | undefined; 17 | } 18 | } 19 | 20 | // Used in Renderer process, expose in `preload.ts` 21 | interface Window { 22 | ipcRenderer: import("electron").IpcRenderer & { 23 | dialog: import("electron").Dialog; 24 | }; 25 | } 26 | -------------------------------------------------------------------------------- /backend/handlers/achievements/data.ts: -------------------------------------------------------------------------------- 1 | import { ISchemaForGame } from "@/@types"; 2 | import { settings } from "../../utils/settings/settings"; 3 | import logger from "../logging"; 4 | 5 | class AchievementData { 6 | readonly api_url: string | undefined = settings.get("api_base_url"); 7 | 8 | async get(steamId: string, lang: string = "en"): Promise { 9 | try { 10 | const url = `${this.api_url}/achievements/${steamId}?lang=${lang}`; 11 | 12 | const request = await fetch(url, { 13 | method: "GET", 14 | }); 15 | 16 | if (!request.ok) throw new Error(request.statusText); 17 | 18 | const data: ISchemaForGame = await request.json(); 19 | 20 | return data; 21 | } catch (error) { 22 | console.log(error); 23 | logger.log("error", `Error getting achievement data: ${error}`); 24 | throw error; 25 | } 26 | } 27 | } 28 | 29 | export const achievementData = new AchievementData(); 30 | -------------------------------------------------------------------------------- /backend/handlers/achievements/index.ts: -------------------------------------------------------------------------------- 1 | class Achievements {} 2 | -------------------------------------------------------------------------------- /backend/handlers/downloads/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Main export file for the downloads module 3 | * This file exports all the components of the download system 4 | */ 5 | 6 | // Export queue and handlers 7 | export { httpDownloadHandler } from "./http"; 8 | export { downloadQueue } from "./queue"; 9 | export { torrentDownloadHandler } from "./torrent"; 10 | -------------------------------------------------------------------------------- /backend/handlers/downloads/queue/item.ts: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /backend/handlers/events/achievements/getUnlocked.ts: -------------------------------------------------------------------------------- 1 | import { achievementsDB } from "../../../sql"; 2 | import { registerEvent } from "../utils"; 3 | 4 | const handler = async (_event: Electron.IpcMainInvokeEvent, gameId: string) => { 5 | try { 6 | const unlocked = await achievementsDB.getUnlockedAchievements(gameId); 7 | return unlocked; 8 | } catch (error) { 9 | console.error(error); 10 | return []; 11 | } 12 | }; 13 | 14 | registerEvent("achievements:get-unlocked", handler); 15 | -------------------------------------------------------------------------------- /backend/handlers/events/achievements/index.ts: -------------------------------------------------------------------------------- 1 | import "./getUnlocked"; 2 | -------------------------------------------------------------------------------- /backend/handlers/events/app/autoLaunch.ts: -------------------------------------------------------------------------------- 1 | import { AutoLaunchOptions } from "@/@types"; 2 | import AutoLaunch from "auto-launch"; 3 | import { app } from "electron"; 4 | import { registerEvent } from "../utils/registerEvent"; 5 | 6 | const autoLaunch = async ( 7 | _event: Electron.IpcMainInvokeEvent, 8 | { enabled, isHidden }: AutoLaunchOptions 9 | ) => { 10 | try { 11 | if (!app.isPackaged) { 12 | console.log("[AutoLaunch] App is not packaged"); 13 | return false; 14 | } 15 | 16 | const autoLauncher = new AutoLaunch({ name: app.getName(), isHidden }); 17 | 18 | if (!enabled) { 19 | autoLauncher.disable(); 20 | return true; 21 | } 22 | autoLauncher.enable(); 23 | return true; 24 | } catch (error) { 25 | console.error(error); 26 | } 27 | }; 28 | 29 | registerEvent("app:auto-launch", autoLaunch); 30 | -------------------------------------------------------------------------------- /backend/handlers/events/app/close.ts: -------------------------------------------------------------------------------- 1 | import { downloadQueue } from "../../../handlers/downloads"; 2 | import { gamesLaunched } from "../../../handlers/launcher/games_launched"; 3 | import { settings } from "../../../utils/settings/settings"; 4 | import window from "../../../utils/window"; 5 | import { registerEvent } from "../utils/registerEvent"; 6 | 7 | const close = async ( 8 | _event: Electron.IpcMainInvokeEvent, 9 | confirmed?: boolean 10 | ) => { 11 | try { 12 | const w = window.getWindow(); 13 | if (!w) return; 14 | const closeToTray = settings.get("closeToTray"); 15 | 16 | if (closeToTray) { 17 | w?.hide(); 18 | return; 19 | } 20 | 21 | if (gamesLaunched.size > 0 && !confirmed) { 22 | window.emitToFrontend("close-confirm", { message: "game's running" }); 23 | return; 24 | } 25 | 26 | const isDownloading = downloadQueue.getDownloads()?.length > 0; 27 | 28 | if (isDownloading && !confirmed) { 29 | window.emitToFrontend("close-confirm", { message: "downloading" }); 30 | return; 31 | } 32 | 33 | return window.destroy(); 34 | } catch (error) { 35 | console.error(error); 36 | return false; 37 | } 38 | }; 39 | 40 | registerEvent("app:close", close); 41 | -------------------------------------------------------------------------------- /backend/handlers/events/app/index.ts: -------------------------------------------------------------------------------- 1 | import "./autoLaunch"; 2 | import "./close"; 3 | import "./maximize"; 4 | import "./minimize"; 5 | import "./openExternal"; 6 | -------------------------------------------------------------------------------- /backend/handlers/events/app/maximize.ts: -------------------------------------------------------------------------------- 1 | import window from "../../../utils/window"; 2 | import { registerEvent } from "../utils/registerEvent"; 3 | 4 | 5 | const maximize = async (_event: Electron.IpcMainInvokeEvent) => { 6 | try { 7 | const w = window.getWindow(); 8 | if (!w) return; 9 | 10 | if (w.isMaximized()) { 11 | w?.unmaximize(); 12 | } else { 13 | w?.maximize(); 14 | } 15 | } catch (error) { 16 | console.error(error); 17 | return false; 18 | } 19 | }; 20 | 21 | registerEvent("app:maximize", maximize); 22 | -------------------------------------------------------------------------------- /backend/handlers/events/app/minimize.ts: -------------------------------------------------------------------------------- 1 | import window from "../../../utils/window"; 2 | import { registerEvent } from "../utils/registerEvent"; 3 | 4 | 5 | const minimize = async (_event: Electron.IpcMainInvokeEvent) => { 6 | try { 7 | const w = window.getWindow(); 8 | if (!w) return; 9 | w?.minimize(); 10 | } catch (error) { 11 | console.error(error); 12 | return false; 13 | } 14 | }; 15 | 16 | registerEvent("app:minimize", minimize); 17 | -------------------------------------------------------------------------------- /backend/handlers/events/app/openExternal.ts: -------------------------------------------------------------------------------- 1 | import { shell } from "electron"; 2 | import { registerEvent } from "../utils/registerEvent"; 3 | 4 | const minimize = async (_event: Electron.IpcMainInvokeEvent, url: string) => { 5 | try { 6 | return await shell.openExternal(url); 7 | } catch (error) { 8 | console.error(error); 9 | return false; 10 | } 11 | }; 12 | 13 | registerEvent("openExternal", minimize); 14 | -------------------------------------------------------------------------------- /backend/handlers/events/db/external_accounts/add-account.ts: -------------------------------------------------------------------------------- 1 | import { ExternalNewAccountInput } from "@/@types/accounts"; 2 | import { accountsDB } from "../../../../sql"; 3 | import { registerEvent } from "../../utils/registerEvent"; 4 | 5 | const addExternalAccount = async ( 6 | _event: Electron.IpcMainInvokeEvent, 7 | input: ExternalNewAccountInput 8 | ) => { 9 | try { 10 | await accountsDB.addAccount(input); 11 | return true; 12 | } catch (error) { 13 | console.error(error); 14 | return false; 15 | } 16 | }; 17 | 18 | registerEvent("external-accounts:add", addExternalAccount); 19 | -------------------------------------------------------------------------------- /backend/handlers/events/db/external_accounts/delete-account.ts: -------------------------------------------------------------------------------- 1 | import { accountsDB } from "../../../../sql"; 2 | import { registerEvent } from "../../utils/registerEvent"; 3 | 4 | const deleteExternalAccount = async ( 5 | _event: Electron.IpcMainInvokeEvent, 6 | identifier: string, 7 | type?: string 8 | ) => { 9 | try { 10 | await accountsDB.deleteAccount(identifier, type); 11 | return true; 12 | } catch (error) { 13 | console.error(error); 14 | return false; 15 | } 16 | }; 17 | 18 | registerEvent("external-accounts:delete", deleteExternalAccount); 19 | -------------------------------------------------------------------------------- /backend/handlers/events/db/external_accounts/get-account.ts: -------------------------------------------------------------------------------- 1 | import { accountsDB } from "../../../../sql"; 2 | import { registerEvent } from "../../utils/registerEvent"; 3 | 4 | const getExternalAccounts = async ( 5 | _event: Electron.IpcMainInvokeEvent, 6 | identifier: string, 7 | type?: string 8 | ) => { 9 | try { 10 | return await accountsDB.getAccount(identifier, type); 11 | } catch (error) { 12 | console.error(error); 13 | return {}; 14 | } 15 | }; 16 | 17 | registerEvent("external-accounts:get", getExternalAccounts); 18 | -------------------------------------------------------------------------------- /backend/handlers/events/db/external_accounts/get-accounts.ts: -------------------------------------------------------------------------------- 1 | import { ExternalAccount } from "@/@types/accounts"; 2 | import { accountsDB } from "../../../../sql"; 3 | import { registerEvent } from "../../utils/registerEvent"; 4 | 5 | const getExternalAccounts = async ( 6 | _event: Electron.IpcMainInvokeEvent, 7 | type?: string 8 | ): Promise> => { 9 | try { 10 | return await accountsDB.getAccounts(type); 11 | } catch (error) { 12 | console.error(error); 13 | return []; 14 | } 15 | }; 16 | 17 | registerEvent("external-accounts:get-all", getExternalAccounts); 18 | -------------------------------------------------------------------------------- /backend/handlers/events/db/external_accounts/index.ts: -------------------------------------------------------------------------------- 1 | import "./add-account"; 2 | import "./delete-account"; 3 | import "./get-account"; 4 | import "./get-accounts"; 5 | import "./update-account"; 6 | -------------------------------------------------------------------------------- /backend/handlers/events/db/external_accounts/update-account.ts: -------------------------------------------------------------------------------- 1 | import { ExternalTokenUpdateInput } from "@/@types/accounts"; 2 | import { accountsDB } from "../../../../sql"; 3 | import { registerEvent } from "../../utils/registerEvent"; 4 | 5 | const updateExternalAccount = async ( 6 | _event: Electron.IpcMainInvokeEvent, 7 | identifier: string, 8 | input: ExternalTokenUpdateInput, 9 | type?: string 10 | ) => { 11 | try { 12 | await accountsDB.updateTokens(identifier, input, type); 13 | return true; 14 | } catch (error) { 15 | console.error(error); 16 | return false; 17 | } 18 | }; 19 | 20 | registerEvent("external-accounts:update", updateExternalAccount); 21 | -------------------------------------------------------------------------------- /backend/handlers/events/db/games/add-game.ts: -------------------------------------------------------------------------------- 1 | import { NewLibraryGame } from "@/@types/library/types"; 2 | import { gamesDB } from "../../../../sql"; 3 | import { registerEvent } from "../../utils/registerEvent"; 4 | 5 | const addGameToGames = async ( 6 | _event: Electron.IpcMainInvokeEvent, 7 | game: NewLibraryGame 8 | ) => { 9 | try { 10 | return await gamesDB.addGame(game); 11 | } catch (error) { 12 | console.error(error); 13 | throw error; 14 | } 15 | }; 16 | 17 | registerEvent("games:add-game", addGameToGames); 18 | -------------------------------------------------------------------------------- /backend/handlers/events/db/games/delete-game.ts: -------------------------------------------------------------------------------- 1 | import { gamesDB } from "../../../../sql"; 2 | import { registerEvent } from "../../utils/registerEvent"; 3 | 4 | const deleteGame = async ( 5 | _event: Electron.IpcMainInvokeEvent, 6 | gameId: string 7 | ) => { 8 | try { 9 | console.log("deleteGame", gameId); 10 | return await gamesDB.deleteGame(gameId); 11 | } catch (error) { 12 | console.error(error); 13 | throw error; 14 | } 15 | }; 16 | 17 | registerEvent("games:delete-game", deleteGame); 18 | -------------------------------------------------------------------------------- /backend/handlers/events/db/games/get-all-games.ts: -------------------------------------------------------------------------------- 1 | import { gamesDB } from "../../../../sql"; 2 | import { registerEvent } from "../../utils/registerEvent"; 3 | 4 | const getAllGames = async (_event: Electron.IpcMainInvokeEvent) => { 5 | try { 6 | return await gamesDB.getAllGames(); 7 | } catch (error) { 8 | console.error(error); 9 | throw error; 10 | } 11 | }; 12 | 13 | registerEvent("games:get-all-games", getAllGames); 14 | -------------------------------------------------------------------------------- /backend/handlers/events/db/games/get-game-by-id.ts: -------------------------------------------------------------------------------- 1 | import { gamesDB } from "../../../../sql"; 2 | import { registerEvent } from "../../utils/registerEvent"; 3 | 4 | const getGameById = async ( 5 | _event: Electron.IpcMainInvokeEvent, 6 | gameId: string 7 | ) => { 8 | try { 9 | return await gamesDB.getGameById(gameId); 10 | } catch (error) { 11 | console.error(error); 12 | throw error; 13 | } 14 | }; 15 | 16 | registerEvent("games:get-game-by-id", getGameById); 17 | -------------------------------------------------------------------------------- /backend/handlers/events/db/games/get-game-by-igdb-id.ts: -------------------------------------------------------------------------------- 1 | import { gamesDB } from "../../../../sql"; 2 | import { registerEvent } from "../../utils/registerEvent"; 3 | 4 | const getGameByIgdbId = async ( 5 | _event: Electron.IpcMainInvokeEvent, 6 | gameId: string 7 | ) => { 8 | try { 9 | const game = await gamesDB.getGameByIGDBId(gameId); 10 | 11 | return game; 12 | } catch (error) { 13 | console.error(error); 14 | throw error; 15 | } 16 | }; 17 | 18 | registerEvent("games:get-game-by-igdb-id", getGameByIgdbId); 19 | -------------------------------------------------------------------------------- /backend/handlers/events/db/games/index.ts: -------------------------------------------------------------------------------- 1 | import "./add-game"; 2 | import "./delete-game"; 3 | import "./get-all-games"; 4 | import "./get-game-by-id"; 5 | import "./get-game-by-igdb-id"; 6 | import "./update-game"; 7 | -------------------------------------------------------------------------------- /backend/handlers/events/db/games/update-game.ts: -------------------------------------------------------------------------------- 1 | import { LibraryGameUpdate } from "@/@types/library/types"; 2 | import { gamesDB } from "../../../../sql"; 3 | import { registerEvent } from "../../utils/registerEvent"; 4 | 5 | const updateGame = async ( 6 | _event: Electron.IpcMainInvokeEvent, 7 | gameId: string, 8 | updates: LibraryGameUpdate 9 | ) => { 10 | try { 11 | return await gamesDB.updateGame(gameId, updates); 12 | } catch (error) { 13 | console.error(error); 14 | throw error; 15 | } 16 | }; 17 | 18 | registerEvent("games:update-game", updateGame); 19 | -------------------------------------------------------------------------------- /backend/handlers/events/db/index.ts: -------------------------------------------------------------------------------- 1 | import "./external_accounts"; 2 | import "./games"; 3 | import "./lists"; 4 | -------------------------------------------------------------------------------- /backend/handlers/events/db/lists/add-game-to-List.ts: -------------------------------------------------------------------------------- 1 | import { ListGame } from "@/@types"; 2 | import { listsDB } from "../../../../sql"; 3 | import { registerEvent } from "../../utils/registerEvent"; 4 | 5 | const addGameToList = async ( 6 | _event: Electron.IpcMainInvokeEvent, 7 | listId: number, 8 | game: ListGame 9 | ) => { 10 | try { 11 | return await listsDB.addGameToList(listId, game); 12 | } catch (error) { 13 | console.error(error); 14 | throw error; 15 | } 16 | }; 17 | 18 | registerEvent("lists:add-game-to-list", addGameToList); 19 | -------------------------------------------------------------------------------- /backend/handlers/events/db/lists/create-list.ts: -------------------------------------------------------------------------------- 1 | import { listsDB } from "../../../../sql/queries"; 2 | import { registerEvent } from "../../utils/registerEvent"; 3 | 4 | const createList = async ( 5 | _event: Electron.IpcMainInvokeEvent, 6 | name: string, 7 | description?: string 8 | ) => { 9 | try { 10 | return await listsDB.createList(name, description); 11 | } catch (error) { 12 | console.error(error); 13 | throw error; 14 | } 15 | }; 16 | 17 | registerEvent("lists:create-list", createList); 18 | -------------------------------------------------------------------------------- /backend/handlers/events/db/lists/delete-game.ts: -------------------------------------------------------------------------------- 1 | import { listsDB } from "../../../../sql/queries"; 2 | import { registerEvent } from "../../utils/registerEvent"; 3 | 4 | const deleteGame = async ( 5 | _event: Electron.IpcMainInvokeEvent, 6 | gameId: number 7 | ) => { 8 | try { 9 | return await listsDB.deleteGame(gameId); 10 | } catch (error) { 11 | console.error(error); 12 | throw error; 13 | } 14 | }; 15 | 16 | registerEvent("lists:delete-game", deleteGame); 17 | -------------------------------------------------------------------------------- /backend/handlers/events/db/lists/delete-list.ts: -------------------------------------------------------------------------------- 1 | import { listsDB } from "../../../../sql/queries"; 2 | import { registerEvent } from "../../utils/registerEvent"; 3 | 4 | const deleteList = async ( 5 | _event: Electron.IpcMainInvokeEvent, 6 | listId: number 7 | ) => { 8 | try { 9 | return await listsDB.deleteList(listId); 10 | } catch (error) { 11 | console.error(error); 12 | throw error; 13 | } 14 | }; 15 | 16 | registerEvent("lists:delete-list", deleteList); 17 | -------------------------------------------------------------------------------- /backend/handlers/events/db/lists/get-all-lists.ts: -------------------------------------------------------------------------------- 1 | import { listsDB } from "../../../../sql/queries"; 2 | import { registerEvent } from "../../utils/registerEvent"; 3 | 4 | const getAllLists = async (_event: Electron.IpcMainInvokeEvent) => { 5 | try { 6 | return await listsDB.getAllLists(); 7 | } catch (error) { 8 | console.error(error); 9 | throw error; 10 | } 11 | }; 12 | 13 | registerEvent("lists:get-all-lists", getAllLists); 14 | -------------------------------------------------------------------------------- /backend/handlers/events/db/lists/get-games-in-list.ts: -------------------------------------------------------------------------------- 1 | import { listsDB } from "../../../../sql"; 2 | import { registerEvent } from "../../utils/registerEvent"; 3 | 4 | 5 | const getGamesInList = async ( 6 | _event: Electron.IpcMainInvokeEvent, 7 | listId: number 8 | ) => { 9 | try { 10 | return await listsDB.getGamesInList(listId); 11 | } catch (error) { 12 | console.error(error); 13 | throw error; 14 | } 15 | }; 16 | 17 | registerEvent("lists:get-games-in-list", getGamesInList); 18 | -------------------------------------------------------------------------------- /backend/handlers/events/db/lists/index.ts: -------------------------------------------------------------------------------- 1 | import "./add-game-to-List"; 2 | import "./create-list"; 3 | import "./delete-game"; 4 | import "./delete-list"; 5 | import "./get-all-lists"; 6 | import "./get-games-in-list"; 7 | import "./remove-game-from-list"; 8 | -------------------------------------------------------------------------------- /backend/handlers/events/db/lists/remove-game-from-list.ts: -------------------------------------------------------------------------------- 1 | import { listsDB } from "../../../../sql/queries"; 2 | import { registerEvent } from "../../utils/registerEvent"; 3 | 4 | const removeGameFromList = async ( 5 | _event: Electron.IpcMainInvokeEvent, 6 | listId: number, 7 | gameId: number 8 | ) => { 9 | try { 10 | return await listsDB.removeGameFromList(listId, gameId); 11 | } catch (error) { 12 | console.error(error); 13 | throw error; 14 | } 15 | }; 16 | 17 | registerEvent("lists:remove-game-from-list", removeGameFromList); 18 | -------------------------------------------------------------------------------- /backend/handlers/events/downloads/addDownload.ts: -------------------------------------------------------------------------------- 1 | import { AddDownloadOptions } from "@/@types"; 2 | import { downloadQueue } from "../../../handlers/downloads"; 3 | import { registerEvent } from "../utils"; 4 | 5 | registerEvent( 6 | "download-queue:add", 7 | async (_event, options: AddDownloadOptions) => { 8 | return await downloadQueue.addDownload(options); 9 | } 10 | ); 11 | -------------------------------------------------------------------------------- /backend/handlers/events/downloads/cancelDownload.ts: -------------------------------------------------------------------------------- 1 | import { downloadQueue } from "../../../handlers/downloads"; 2 | import { registerEvent } from "../utils"; 3 | 4 | registerEvent("download-queue:cancel", async (_event, id: string) => { 5 | return downloadQueue.cancelDownload(id); 6 | }); 7 | -------------------------------------------------------------------------------- /backend/handlers/events/downloads/clearCompleted.ts: -------------------------------------------------------------------------------- 1 | import { downloadQueue } from "../../../handlers/downloads"; 2 | import { registerEvent } from "../utils"; 3 | 4 | registerEvent("download-queue:clear-completed", async () => { 5 | return downloadQueue.clearCompletedDownloads(); 6 | }); 7 | -------------------------------------------------------------------------------- /backend/handlers/events/downloads/getAllDownloads.ts: -------------------------------------------------------------------------------- 1 | import { downloadQueue } from "../../../handlers/downloads"; 2 | import { registerEvent } from "../utils"; 3 | 4 | registerEvent("download-queue:get-all", async () => { 5 | return downloadQueue.getDownloads(); 6 | }); 7 | -------------------------------------------------------------------------------- /backend/handlers/events/downloads/getDownload.ts: -------------------------------------------------------------------------------- 1 | import { downloadQueue } from "../../../handlers/downloads"; 2 | import { registerEvent } from "../utils"; 3 | 4 | registerEvent("download-queue:get", async (_event, id: string) => { 5 | return downloadQueue.getDownload(id); 6 | }); 7 | -------------------------------------------------------------------------------- /backend/handlers/events/downloads/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | downloadQueue, 3 | httpDownloadHandler, 4 | torrentDownloadHandler, 5 | } from "../../../handlers/downloads"; 6 | import "./addDownload"; 7 | import "./cancelDownload"; 8 | import "./clearCompleted"; 9 | import "./getAllDownloads"; 10 | import "./getDownload"; 11 | import "./pauseDownload"; 12 | import "./removeDownload"; 13 | import "./resumeDownload"; 14 | import "./setPriority"; 15 | import "./updateConfig"; 16 | 17 | downloadQueue.registerHandler("http", httpDownloadHandler); 18 | downloadQueue.registerHandler("torrent", torrentDownloadHandler); 19 | -------------------------------------------------------------------------------- /backend/handlers/events/downloads/pauseDownload.ts: -------------------------------------------------------------------------------- 1 | import { downloadQueue } from "../../../handlers/downloads"; 2 | import { registerEvent } from "../utils"; 3 | 4 | registerEvent("download-queue:pause", async (_event, id: string) => { 5 | return downloadQueue.pauseDownload(id); 6 | }); 7 | -------------------------------------------------------------------------------- /backend/handlers/events/downloads/removeDownload.ts: -------------------------------------------------------------------------------- 1 | import { downloadQueue } from "../../../handlers/downloads"; 2 | import { registerEvent } from "../utils"; 3 | 4 | registerEvent("download-queue:remove", async (_event, id: string) => { 5 | return downloadQueue.removeDownload(id); 6 | }); 7 | -------------------------------------------------------------------------------- /backend/handlers/events/downloads/resumeDownload.ts: -------------------------------------------------------------------------------- 1 | import { downloadQueue } from "../../../handlers/downloads"; 2 | import { registerEvent } from "../utils"; 3 | 4 | registerEvent("download-queue:resume", async (_event, id: string) => { 5 | return downloadQueue.resumeDownload(id); 6 | }); 7 | -------------------------------------------------------------------------------- /backend/handlers/events/downloads/setPriority.ts: -------------------------------------------------------------------------------- 1 | import { DownloadPriority } from "@/@types"; 2 | import { downloadQueue } from "../../../handlers/downloads"; 3 | import { registerEvent } from "../utils"; 4 | 5 | registerEvent( 6 | "download-queue:set-priority", 7 | async (_event, id: string, priority: DownloadPriority) => { 8 | return downloadQueue.setPriority(id, priority); 9 | } 10 | ); 11 | -------------------------------------------------------------------------------- /backend/handlers/events/downloads/throttleDownload.ts: -------------------------------------------------------------------------------- 1 | import { torrentDownloadHandler } from "../../../handlers/downloads"; 2 | import { registerEvent } from "../utils"; 3 | 4 | registerEvent("torrent:throttle-download", async (_event, speed: number) => { 5 | torrentDownloadHandler.updateThrottling(speed); 6 | return true; 7 | }); 8 | -------------------------------------------------------------------------------- /backend/handlers/events/downloads/updateConfig.ts: -------------------------------------------------------------------------------- 1 | import { DownloadQueueConfig } from "@/@types"; 2 | import { downloadQueue } from "../../../handlers/downloads"; 3 | import { registerEvent } from "../utils"; 4 | 5 | registerEvent( 6 | "download-queue:update-config", 7 | async (_event, config: Partial) => { 8 | downloadQueue.updateConfig(config); 9 | return true; 10 | } 11 | ); 12 | -------------------------------------------------------------------------------- /backend/handlers/events/generic/get-app-info.ts: -------------------------------------------------------------------------------- 1 | import { AppInfo } from "@/@types"; 2 | import { app } from "electron"; 3 | import { getOS } from "../../../utils"; 4 | import { registerEvent } from "../utils/registerEvent"; 5 | 6 | const getAppInfo = (_event: Electron.IpcMainInvokeEvent): AppInfo => { 7 | const app_info = { 8 | app_version: app.getVersion(), 9 | electron_version: process.versions.electron, 10 | app_name: app.getName(), 11 | app_path: app.getAppPath(), 12 | user_data_path: app.getPath("userData"), 13 | os: getOS(), 14 | }; 15 | 16 | return app_info; 17 | }; 18 | 19 | registerEvent("generic:get-app-info", getAppInfo); 20 | -------------------------------------------------------------------------------- /backend/handlers/events/generic/get-os.ts: -------------------------------------------------------------------------------- 1 | import { getOS } from "../../../utils"; 2 | import { registerEvent } from "../utils/registerEvent"; 3 | 4 | const getOSEvent = (_event: Electron.IpcMainInvokeEvent) => { 5 | return getOS(); 6 | }; 7 | 8 | registerEvent("generic:get-os", getOSEvent); 9 | -------------------------------------------------------------------------------- /backend/handlers/events/generic/index.ts: -------------------------------------------------------------------------------- 1 | import "./get-app-info"; 2 | import "./get-os"; 3 | import "./open-dialog"; 4 | import "./open-downloads"; 5 | import "./open-folder"; 6 | import "./request"; 7 | -------------------------------------------------------------------------------- /backend/handlers/events/generic/open-dialog.ts: -------------------------------------------------------------------------------- 1 | import { dialog } from "electron"; 2 | import { registerEvent } from "../utils/registerEvent"; 3 | 4 | const openDialog = async ( 5 | _event: Electron.IpcMainInvokeEvent, 6 | options: Electron.OpenDialogOptions 7 | ) => { 8 | return await dialog.showOpenDialog(options); 9 | }; 10 | 11 | registerEvent("generic:open-dialog", openDialog); 12 | -------------------------------------------------------------------------------- /backend/handlers/events/generic/open-downloads.ts: -------------------------------------------------------------------------------- 1 | import { shell } from "electron"; 2 | import { constants } from "../../../utils"; 3 | import { settings } from "../../../utils/settings/settings"; 4 | import { registerEvent } from "../utils/registerEvent"; 5 | 6 | const openDownloads = async (_event: Electron.IpcMainInvokeEvent) => { 7 | const path = settings.get("downloadsPath") ?? constants.downloadsPath; 8 | return await shell.openPath(path); 9 | }; 10 | 11 | registerEvent("generic:open-downloads", openDownloads); 12 | -------------------------------------------------------------------------------- /backend/handlers/events/generic/open-folder.ts: -------------------------------------------------------------------------------- 1 | import { shell } from "electron"; 2 | import { registerEvent } from "../utils/registerEvent"; 3 | 4 | const openFolder = async ( 5 | _event: Electron.IpcMainInvokeEvent, 6 | path: string 7 | ) => { 8 | return await shell.openPath(path); 9 | }; 10 | 11 | registerEvent("generic:open-folder", openFolder); 12 | -------------------------------------------------------------------------------- /backend/handlers/events/generic/request.ts: -------------------------------------------------------------------------------- 1 | import { registerEvent } from "../utils/registerEvent"; 2 | 3 | const request = async ( 4 | _event: Electron.IpcMainInvokeEvent, 5 | url: string, 6 | options?: RequestInit 7 | ) => { 8 | try { 9 | const response = await fetch(url, options); 10 | const data = await response.json(); 11 | return { data, success: true }; 12 | } catch (error) { 13 | return { success: false, error: (error as Error).message }; 14 | } 15 | }; 16 | 17 | registerEvent("request", request); 18 | -------------------------------------------------------------------------------- /backend/handlers/events/index.ts: -------------------------------------------------------------------------------- 1 | import { app } from "electron"; 2 | import "./achievements"; 3 | import "./app"; 4 | import "./db"; 5 | import "./downloads"; 6 | import "./generic"; 7 | import "./launcher"; 8 | import "./logger"; 9 | import "./plugins"; 10 | import "./settings"; 11 | import "./themes"; 12 | import "./torrent"; 13 | import "./updater"; 14 | 15 | if (process.platform === "win32") { 16 | app.setAppUserModelId(app.name); 17 | } 18 | -------------------------------------------------------------------------------- /backend/handlers/events/launcher/index.ts: -------------------------------------------------------------------------------- 1 | import "./play-game"; 2 | import "./stop-game"; 3 | -------------------------------------------------------------------------------- /backend/handlers/events/launcher/stop-game.ts: -------------------------------------------------------------------------------- 1 | import { gamesLaunched } from "../../launcher/games_launched"; 2 | import { registerEvent } from "../utils/registerEvent"; 3 | 4 | const stopGame = async ( 5 | _event: Electron.IpcMainInvokeEvent, 6 | game_id: string 7 | ) => { 8 | try { 9 | const launcher = gamesLaunched.get(game_id); 10 | 11 | if (!launcher) { 12 | console.log("Game not launched"); 13 | return true; 14 | } 15 | 16 | launcher.stopGame(); 17 | 18 | return true; 19 | } catch (error) { 20 | console.error("Failed to launch game:", error); 21 | return false; 22 | } 23 | }; 24 | 25 | registerEvent("launcher:stop-game", stopGame); 26 | -------------------------------------------------------------------------------- /backend/handlers/events/logger/clear.ts: -------------------------------------------------------------------------------- 1 | import logger from "../../../handlers/logging"; 2 | import { registerEvent } from "../utils/registerEvent"; 3 | 4 | const clear = async (_event: Electron.IpcMainInvokeEvent) => { 5 | try { 6 | return await logger.clear(); 7 | } catch (error) { 8 | console.error(error); 9 | return false; 10 | } 11 | }; 12 | 13 | registerEvent("logger:clear", clear); 14 | -------------------------------------------------------------------------------- /backend/handlers/events/logger/delete.ts: -------------------------------------------------------------------------------- 1 | import logger from "../../../handlers/logging"; 2 | import { registerEvent } from "../utils/registerEvent"; 3 | 4 | const deleteALog = async ( 5 | _event: Electron.IpcMainInvokeEvent, 6 | timestamp: number 7 | ) => { 8 | try { 9 | return logger.remove(timestamp); 10 | } catch (error) { 11 | console.error(error); 12 | return false; 13 | } 14 | }; 15 | 16 | registerEvent("logger:delete", deleteALog); 17 | -------------------------------------------------------------------------------- /backend/handlers/events/logger/filter.ts: -------------------------------------------------------------------------------- 1 | import { LoggerFilterOptions } from "@/@types/logs"; 2 | import logger from "../../../handlers/logging"; 3 | import { registerEvent } from "../utils/registerEvent"; 4 | 5 | const handler = async ( 6 | _event: Electron.IpcMainInvokeEvent, 7 | options: LoggerFilterOptions 8 | ) => { 9 | try { 10 | return logger.filter(options); 11 | } catch (error) { 12 | console.error(error); 13 | return []; 14 | } 15 | }; 16 | 17 | registerEvent("logger:filter", handler); 18 | -------------------------------------------------------------------------------- /backend/handlers/events/logger/get-all-logs.ts: -------------------------------------------------------------------------------- 1 | import logger from "../../../handlers/logging"; 2 | import { registerEvent } from "../utils/registerEvent"; 3 | 4 | const getAllLogs = async (_event: Electron.IpcMainInvokeEvent) => { 5 | try { 6 | return logger.read(); 7 | } catch (error) { 8 | console.error(error); 9 | return false; 10 | } 11 | }; 12 | 13 | registerEvent("logger:get-all", getAllLogs); 14 | -------------------------------------------------------------------------------- /backend/handlers/events/logger/get-logged-dates.ts: -------------------------------------------------------------------------------- 1 | import logger from "../../logging"; 2 | import { registerEvent } from "../utils/registerEvent"; 3 | 4 | const handler = async ( 5 | _event: Electron.IpcMainInvokeEvent, 6 | includeTime?: boolean 7 | ) => { 8 | try { 9 | return logger.getLoggedDates(includeTime); 10 | } catch (error) { 11 | console.error(error); 12 | return []; 13 | } 14 | }; 15 | 16 | registerEvent("logger:get-logged-dates", handler); 17 | -------------------------------------------------------------------------------- /backend/handlers/events/logger/index.ts: -------------------------------------------------------------------------------- 1 | import "./clear"; 2 | import "./delete"; 3 | import "./filter"; 4 | import "./get-all-logs"; 5 | import "./get-logged-dates"; 6 | import "./log"; 7 | -------------------------------------------------------------------------------- /backend/handlers/events/logger/log.ts: -------------------------------------------------------------------------------- 1 | import { LogEntry } from "@/@types/logs"; 2 | import logger from "../../../handlers/logging"; 3 | import { registerEvent } from "../utils/registerEvent"; 4 | 5 | const log = ( 6 | _event: Electron.IpcMainInvokeEvent, 7 | level: LogEntry["level"], 8 | message: string 9 | ): ReturnType => { 10 | try { 11 | return logger.log(level, message); 12 | } catch (error) { 13 | console.error(error); 14 | return null; 15 | } 16 | }; 17 | 18 | registerEvent("logger:log", log); 19 | -------------------------------------------------------------------------------- /backend/handlers/events/plugins/check-for-updates.ts: -------------------------------------------------------------------------------- 1 | import { PluginId, PluginSetupJSONDisabled } from "@team-falkor/shared-types"; 2 | import pluginHandler from "../../../handlers/plugins/plugin"; 3 | import { registerEvent } from "../utils/registerEvent"; 4 | 5 | const checkForUpdates = async ( 6 | _event: Electron.IpcMainInvokeEvent, 7 | pluginId?: PluginId 8 | ): Promise> => { 9 | try { 10 | const plugins = pluginId 11 | ? await pluginHandler.checkForUpdates(pluginId) 12 | : await pluginHandler.checkForUpdatesAll(); 13 | 14 | return plugins; 15 | } catch (error) { 16 | console.error(`Failed to check for updates: ${error}`); 17 | return false; 18 | } 19 | }; 20 | 21 | registerEvent("plugins:check-for-updates", checkForUpdates); 22 | -------------------------------------------------------------------------------- /backend/handlers/events/plugins/delete-plugin.ts: -------------------------------------------------------------------------------- 1 | import { PluginId } from "@team-falkor/shared-types"; 2 | import pluginHandler from "../../../handlers/plugins/plugin"; 3 | import { registerEvent } from "../utils/registerEvent"; 4 | 5 | const deletePlugin = async ( 6 | _event: Electron.IpcMainInvokeEvent, 7 | pluginId: PluginId 8 | ): Promise<{ 9 | message: string; 10 | success: boolean; 11 | }> => { 12 | try { 13 | const deleted = await pluginHandler.delete(pluginId); 14 | 15 | if (!deleted) { 16 | return { 17 | message: "Failed to deleted plugin", 18 | success: false, 19 | }; 20 | } 21 | 22 | return { 23 | message: "Plugin deleted successfully", 24 | success: true, 25 | }; 26 | } catch (error) { 27 | console.error(error); 28 | 29 | return { 30 | message: (error as Error).message, 31 | success: false, 32 | }; 33 | } 34 | }; 35 | 36 | registerEvent("plugins:delete", deletePlugin); 37 | -------------------------------------------------------------------------------- /backend/handlers/events/plugins/disable-plugin.ts: -------------------------------------------------------------------------------- 1 | import { PluginId } from "@team-falkor/shared-types"; 2 | import pluginHandler from "../../../handlers/plugins/plugin"; 3 | import { registerEvent } from "../utils/registerEvent"; 4 | 5 | const disablePlugin = async ( 6 | _event: Electron.IpcMainInvokeEvent, 7 | pluginId: PluginId 8 | ): Promise<{ 9 | message: string; 10 | success: boolean; 11 | }> => { 12 | try { 13 | const disabled = await pluginHandler.disable(pluginId); 14 | 15 | if (!disabled) { 16 | return { 17 | message: "Failed to disabled plugin", 18 | success: false, 19 | }; 20 | } 21 | 22 | return { 23 | message: "Plugin disabled successfully", 24 | success: true, 25 | }; 26 | } catch (error) { 27 | console.error(error); 28 | 29 | return { 30 | message: (error as Error).message, 31 | success: false, 32 | }; 33 | } 34 | }; 35 | 36 | registerEvent("plugins:disable", disablePlugin); 37 | -------------------------------------------------------------------------------- /backend/handlers/events/plugins/enable-plugin.ts: -------------------------------------------------------------------------------- 1 | import { PluginId } from "@team-falkor/shared-types"; 2 | import pluginHandler from "../../../handlers/plugins/plugin"; 3 | import { registerEvent } from "../utils/registerEvent"; 4 | 5 | const enablePlugin = async ( 6 | _event: Electron.IpcMainInvokeEvent, 7 | pluginId: PluginId 8 | ): Promise<{ 9 | message: string; 10 | success: boolean; 11 | }> => { 12 | try { 13 | const enabled = await pluginHandler.enable(pluginId); 14 | 15 | if (!enabled) { 16 | return { 17 | message: "Failed to enable plugin", 18 | success: false, 19 | }; 20 | } 21 | 22 | return { 23 | message: "Plugin enabled successfully", 24 | success: true, 25 | }; 26 | } catch (error) { 27 | console.error(error); 28 | 29 | return { 30 | message: (error as Error).message, 31 | success: false, 32 | }; 33 | } 34 | }; 35 | 36 | registerEvent("plugins:enable", enablePlugin); 37 | -------------------------------------------------------------------------------- /backend/handlers/events/plugins/get-mutiple-choice-download.ts: -------------------------------------------------------------------------------- 1 | import { PluginId } from "@team-falkor/shared-types"; 2 | import pluginHandler from "../../../handlers/plugins/plugin"; 3 | import { registerEvent } from "../utils/registerEvent"; 4 | 5 | const getMultipleChoiceDownload = async ( 6 | _event: Electron.IpcMainInvokeEvent, 7 | pluginId: PluginId, 8 | base64Url: string 9 | ): Promise => { 10 | try { 11 | const plugin = await pluginHandler.get(pluginId); 12 | 13 | if (!plugin) return []; 14 | 15 | const url = `${plugin.api_url}/return/${base64Url}`; 16 | 17 | const response = await fetch(url); 18 | 19 | if (!response.ok) return []; 20 | 21 | const json: string[] = await response.json(); 22 | 23 | return json; 24 | } catch (error) { 25 | console.error(error); 26 | return []; 27 | } 28 | }; 29 | 30 | registerEvent( 31 | "plugins:use:get-multiple-choice-download", 32 | getMultipleChoiceDownload 33 | ); 34 | -------------------------------------------------------------------------------- /backend/handlers/events/plugins/get-plugin.ts: -------------------------------------------------------------------------------- 1 | import { PluginId } from "@team-falkor/shared-types"; 2 | import pluginHandler from "../../../handlers/plugins/plugin"; 3 | import { registerEvent } from "../utils/registerEvent"; 4 | 5 | type getPluginResponse = 6 | | { 7 | message: string; 8 | success: boolean; 9 | } 10 | | { 11 | data: any; 12 | success: true; 13 | }; 14 | 15 | const getPlugin = async ( 16 | _event: Electron.IpcMainInvokeEvent, 17 | pluginId: PluginId 18 | ): Promise => { 19 | try { 20 | const plugin = await pluginHandler.get(pluginId); 21 | 22 | if (!plugin) { 23 | return { 24 | message: `no plugin found! with id: ${pluginId}`, 25 | success: false, 26 | }; 27 | } 28 | 29 | return { 30 | data: plugin, 31 | success: true, 32 | }; 33 | } catch (error) { 34 | console.error(error); 35 | 36 | return { 37 | message: (error as Error).message, 38 | success: false, 39 | }; 40 | } 41 | }; 42 | 43 | registerEvent("plugins:get", getPlugin); 44 | -------------------------------------------------------------------------------- /backend/handlers/events/plugins/index.ts: -------------------------------------------------------------------------------- 1 | import "./check-for-updates"; 2 | import "./delete-plugin"; 3 | import "./disable-plugin"; 4 | import "./enable-plugin"; 5 | import "./get-mutiple-choice-download"; 6 | import "./get-plugin"; 7 | import "./install-plugin"; 8 | import "./list-plugins"; 9 | import "./search-plugins"; 10 | import "./update-all"; 11 | import "./update-plugin"; 12 | -------------------------------------------------------------------------------- /backend/handlers/events/plugins/install-plugin.ts: -------------------------------------------------------------------------------- 1 | import pluginHandler from "../../../handlers/plugins/plugin"; 2 | import { registerEvent } from "../utils/registerEvent"; 3 | 4 | const installPlugin = async ( 5 | _event: Electron.IpcMainInvokeEvent, 6 | url: string 7 | ): Promise<{ 8 | message: string; 9 | success: boolean; 10 | }> => { 11 | try { 12 | const installed = await pluginHandler.install(url); 13 | 14 | if (!installed) { 15 | return { 16 | message: "Failed to install plugin", 17 | success: false, 18 | }; 19 | } 20 | 21 | return { 22 | message: "Plugin installed successfully", 23 | success: true, 24 | }; 25 | } catch (error) { 26 | console.error(error); 27 | 28 | return { 29 | message: (error as Error).message, 30 | success: false, 31 | }; 32 | } 33 | }; 34 | 35 | registerEvent("plugins:install", installPlugin); 36 | -------------------------------------------------------------------------------- /backend/handlers/events/plugins/list-plugins.ts: -------------------------------------------------------------------------------- 1 | import pluginHandler from "../../../handlers/plugins/plugin"; 2 | import { registerEvent } from "../utils/registerEvent"; 3 | 4 | // type getPluginResponse = 5 | // | { 6 | // message: string; 7 | // success: boolean; 8 | // } 9 | // | { 10 | // data: any; 11 | // success: true; 12 | // }; 13 | 14 | const getPlugin = async ( 15 | _event: Electron.IpcMainInvokeEvent, 16 | wantDisabled: boolean = false 17 | ) => { 18 | try { 19 | const plugins = await pluginHandler.list(wantDisabled); 20 | 21 | if (!plugins) { 22 | return { 23 | message: `no plugins found!`, 24 | success: false, 25 | }; 26 | } 27 | 28 | return { 29 | data: plugins, 30 | success: true, 31 | }; 32 | } catch (error) { 33 | console.error(error); 34 | 35 | return { 36 | message: (error as Error).message, 37 | success: false, 38 | }; 39 | } 40 | }; 41 | 42 | registerEvent("plugins:list", getPlugin); 43 | -------------------------------------------------------------------------------- /backend/handlers/events/plugins/update-all.ts: -------------------------------------------------------------------------------- 1 | import pluginHandler from "../../../handlers/plugins/plugin"; 2 | import { registerEvent } from "../utils/registerEvent"; 3 | 4 | const updateAll = async (_event: Electron.IpcMainInvokeEvent) => { 5 | try { 6 | const updated = await pluginHandler.updateAll(); 7 | 8 | return updated; 9 | } catch (error) { 10 | console.error(`Failed to update all plugins: ${error}`); 11 | return []; 12 | } 13 | }; 14 | 15 | registerEvent("plugins:update-all", updateAll); 16 | -------------------------------------------------------------------------------- /backend/handlers/events/plugins/update-plugin.ts: -------------------------------------------------------------------------------- 1 | import { PluginId } from "@team-falkor/shared-types"; 2 | import pluginHandler from "../../../handlers/plugins/plugin"; 3 | import { registerEvent } from "../utils/registerEvent"; 4 | 5 | const updatePlugin = async ( 6 | _event: Electron.IpcMainInvokeEvent, 7 | pluginId: PluginId 8 | ) => { 9 | try { 10 | const updated = await pluginHandler.update(pluginId); 11 | 12 | return updated; 13 | } catch (error) { 14 | console.error(`Failed to update plugin: ${error}`); 15 | return []; 16 | } 17 | }; 18 | 19 | registerEvent("plugins:update-plugin", updatePlugin); 20 | -------------------------------------------------------------------------------- /backend/handlers/events/settings/get-all.ts: -------------------------------------------------------------------------------- 1 | import { SettingsConfig } from "@/@types"; 2 | import { defaultSettings } from "../../../utils/settings/constants"; 3 | import { settings } from "../../../utils/settings/settings"; 4 | import { registerEvent } from "../utils/registerEvent"; 5 | 6 | // Handler function to return all settings 7 | const getAllSettings = ( 8 | _event: Electron.IpcMainInvokeEvent 9 | ): SettingsConfig => { 10 | try { 11 | // Use the read method to fetch settings, falling back to defaults if the file is empty or missing 12 | const currentSettings = settings.read(); 13 | return currentSettings || defaultSettings; // Return defaultSettings if no file exists or it's empty 14 | } catch (error) { 15 | console.error("Error fetching all settings:", error); 16 | // If there's an error, return default settings as a safe fallback 17 | return defaultSettings; 18 | } 19 | }; 20 | 21 | // Register the event with the handler 22 | registerEvent("settings:get-all", getAllSettings); 23 | -------------------------------------------------------------------------------- /backend/handlers/events/settings/get-setting.ts: -------------------------------------------------------------------------------- 1 | import { SettingsConfig } from "@/@types"; 2 | import { settings } from "../../../utils/settings/settings"; 3 | import { registerEvent } from "../utils/registerEvent"; 4 | 5 | const getSetting = ( 6 | _event: Electron.IpcMainInvokeEvent, 7 | key: keyof SettingsConfig 8 | ) => { 9 | try { 10 | return settings.get(key); 11 | } catch (error) { 12 | console.error(error); 13 | return null; 14 | } 15 | }; 16 | 17 | registerEvent("settings:get", getSetting); 18 | -------------------------------------------------------------------------------- /backend/handlers/events/settings/index.ts: -------------------------------------------------------------------------------- 1 | import "./get-all"; 2 | import "./get-setting"; 3 | import "./reload"; 4 | import "./update-setting"; 5 | -------------------------------------------------------------------------------- /backend/handlers/events/settings/reload.ts: -------------------------------------------------------------------------------- 1 | import { settings } from "../../../utils/settings/settings"; 2 | import { registerEvent } from "../utils/registerEvent"; 3 | 4 | const reloadSettings = (_event: Electron.IpcMainInvokeEvent) => { 5 | try { 6 | return settings.reload(); 7 | } catch (error) { 8 | console.error(error); 9 | return null; 10 | } 11 | }; 12 | 13 | registerEvent("settings:reload", reloadSettings); 14 | -------------------------------------------------------------------------------- /backend/handlers/events/settings/reset-to-default.ts: -------------------------------------------------------------------------------- 1 | import { settings } from "../../../utils/settings/settings"; 2 | import { registerEvent } from "../utils/registerEvent"; 3 | 4 | const resetToDefaultSettings = (_event: Electron.IpcMainInvokeEvent) => { 5 | try { 6 | return settings.resetToDefaults(); 7 | } catch (error) { 8 | console.error(error); 9 | return null; 10 | } 11 | }; 12 | 13 | registerEvent("settings:reset-to-default", resetToDefaultSettings); 14 | -------------------------------------------------------------------------------- /backend/handlers/events/settings/update-setting.ts: -------------------------------------------------------------------------------- 1 | import { SettingsConfig } from "@/@types"; 2 | import { settings } from "../../../utils/settings/settings"; 3 | import { registerEvent } from "../utils/registerEvent"; 4 | 5 | const updateSetting = ( 6 | _event: Electron.IpcMainInvokeEvent, 7 | key: keyof SettingsConfig, 8 | value: SettingsConfig[keyof SettingsConfig] 9 | ) => { 10 | try { 11 | settings.update(key, value); 12 | return true; 13 | } catch (error) { 14 | console.error(error); 15 | return null; 16 | } 17 | }; 18 | 19 | registerEvent("settings:update", updateSetting); 20 | -------------------------------------------------------------------------------- /backend/handlers/events/themes/delete-theme.ts: -------------------------------------------------------------------------------- 1 | import { ThemeResponse } from "../../../@types"; 2 | import { themes } from "../../../handlers/themes"; 3 | import { registerEvent } from "../utils/registerEvent"; 4 | 5 | const deleteTheme = async ( 6 | _event: Electron.IpcMainInvokeEvent, 7 | name: string 8 | ): Promise => { 9 | try { 10 | return await themes.delete(name); 11 | } catch (error) { 12 | console.error(error); 13 | 14 | return { 15 | message: (error as Error).message, 16 | success: false, 17 | }; 18 | } 19 | }; 20 | 21 | registerEvent("themes:delete", deleteTheme); 22 | -------------------------------------------------------------------------------- /backend/handlers/events/themes/get-theme.ts: -------------------------------------------------------------------------------- 1 | import { themes } from "../../../handlers/themes"; 2 | import { registerEvent } from "../utils/registerEvent"; 3 | 4 | const getTheme = async (_event: Electron.IpcMainInvokeEvent, name: string) => { 5 | try { 6 | return await themes.get(name); 7 | } catch (error) { 8 | console.error(error); 9 | return (error as Error).message; 10 | } 11 | }; 12 | 13 | registerEvent("themes:get", getTheme); 14 | -------------------------------------------------------------------------------- /backend/handlers/events/themes/index.ts: -------------------------------------------------------------------------------- 1 | import "./delete-theme"; 2 | import "./get-theme"; 3 | import "./install-theme"; 4 | import "./list-themes"; 5 | -------------------------------------------------------------------------------- /backend/handlers/events/themes/install-theme.ts: -------------------------------------------------------------------------------- 1 | import { ThemeResponse } from "../../../@types"; 2 | import { themes } from "../../../handlers/themes"; 3 | import { registerEvent } from "../utils/registerEvent"; 4 | 5 | const installTheme = async ( 6 | _event: Electron.IpcMainInvokeEvent, 7 | url: string 8 | ): Promise => { 9 | try { 10 | return await themes.install(url); 11 | } catch (error) { 12 | console.error(error); 13 | 14 | return { 15 | message: (error as Error).message, 16 | success: false, 17 | }; 18 | } 19 | }; 20 | 21 | registerEvent("themes:install", installTheme); 22 | -------------------------------------------------------------------------------- /backend/handlers/events/themes/list-themes.ts: -------------------------------------------------------------------------------- 1 | import { themes } from "../../themes"; 2 | import { registerEvent } from "../utils/registerEvent"; 3 | 4 | const getThemes = async (_event: Electron.IpcMainInvokeEvent) => { 5 | try { 6 | return await themes.list(); 7 | } catch (error) { 8 | console.error(error); 9 | 10 | return (error as Error).message; 11 | } 12 | }; 13 | 14 | registerEvent("themes:list", getThemes); 15 | -------------------------------------------------------------------------------- /backend/handlers/events/torrent/index.ts: -------------------------------------------------------------------------------- 1 | import "./throttleDownload"; 2 | import "./throttleUpload"; 3 | -------------------------------------------------------------------------------- /backend/handlers/events/torrent/throttleDownload.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-Falkor/falkor/83ccd92a990b6154655cab6cfa649fffe4e46e89/backend/handlers/events/torrent/throttleDownload.ts -------------------------------------------------------------------------------- /backend/handlers/events/torrent/throttleUpload.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-Falkor/falkor/83ccd92a990b6154655cab6cfa649fffe4e46e89/backend/handlers/events/torrent/throttleUpload.ts -------------------------------------------------------------------------------- /backend/handlers/events/updater/check-for-update.ts: -------------------------------------------------------------------------------- 1 | import updater from "../../../handlers/updater"; 2 | import { registerEvent } from "../utils"; 3 | 4 | const checkForUpdate = async (_event: Electron.IpcMainInvokeEvent) => { 5 | console.log("Checking for update..."); 6 | try { 7 | const check = await updater.checkForUpdates(); 8 | 9 | return { success: true, data: check }; 10 | } catch (error) { 11 | console.error("Error checking for updates:", error); 12 | return { success: false, error }; 13 | } 14 | }; 15 | 16 | // Register the event handler 17 | registerEvent("updater:check-for-update", checkForUpdate); 18 | -------------------------------------------------------------------------------- /backend/handlers/events/updater/index.ts: -------------------------------------------------------------------------------- 1 | import "./check-for-update"; 2 | import "./install"; 3 | -------------------------------------------------------------------------------- /backend/handlers/events/updater/install.ts: -------------------------------------------------------------------------------- 1 | import updater from "../../../handlers/updater"; 2 | import { registerEvent } from "../utils"; 3 | 4 | const installUpdate = async (_event: Electron.IpcMainInvokeEvent) => { 5 | console.log("Installing update..."); 6 | try { 7 | return await updater.update(); 8 | } catch (error) { 9 | console.error("Error installing update: ", error); 10 | return { success: false, error }; 11 | } 12 | }; 13 | 14 | // Register the event handler 15 | registerEvent("updater:install", installUpdate); 16 | -------------------------------------------------------------------------------- /backend/handlers/events/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./registerEvent"; 2 | -------------------------------------------------------------------------------- /backend/handlers/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./downloads"; 2 | export * from "./plugins"; 3 | -------------------------------------------------------------------------------- /backend/handlers/launcher/games_launched.ts: -------------------------------------------------------------------------------- 1 | import GameProcessLauncher from "./gameProcessLauncher"; 2 | 3 | export const gamesLaunched = new Map(); 4 | -------------------------------------------------------------------------------- /backend/handlers/launcher/utils.ts: -------------------------------------------------------------------------------- 1 | import child_process from "child_process"; 2 | import { shell } from "electron"; 3 | import path from "path"; 4 | 5 | export const spawnSync = ( 6 | command: string, 7 | programPath: string, 8 | args: string[], 9 | options: child_process.SpawnOptions 10 | ) => { 11 | let cmd = programPath; 12 | 13 | if (typeof options.cwd === "string") { 14 | cmd = path.join(options.cwd, programPath); 15 | } 16 | 17 | return child_process.spawn(command, [cmd, ...args], { 18 | ...options, 19 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 20 | // @ts-expect-error 21 | env: { ...process.env, WINEDEBUG: "fixme-all" }, 22 | }); 23 | }; 24 | 25 | export const getRealPath = (path: string) => { 26 | try { 27 | if (process.platform !== "win32" && !path.endsWith(".lnk")) return path; 28 | return shell.readShortcutLink(path).target; 29 | } catch (error) { 30 | return path; 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /backend/handlers/plugins/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./plugin"; 2 | -------------------------------------------------------------------------------- /backend/preload.ts: -------------------------------------------------------------------------------- 1 | import { contextBridge, dialog, ipcRenderer } from "electron"; 2 | 3 | // --------- Expose some API to the Renderer process --------- 4 | contextBridge.exposeInMainWorld("ipcRenderer", { 5 | on(...args: Parameters) { 6 | const [channel, listener] = args; 7 | return ipcRenderer.on(channel, (event, ...args) => 8 | listener(event, ...args) 9 | ); 10 | }, 11 | off(...args: Parameters) { 12 | const [channel, ...omit] = args; 13 | return ipcRenderer.off(channel, ...omit); 14 | }, 15 | send(...args: Parameters) { 16 | const [channel, ...omit] = args; 17 | return ipcRenderer.send(channel, ...omit); 18 | }, 19 | invoke(...args: Parameters) { 20 | const [channel, ...omit] = args; 21 | return ipcRenderer.invoke(channel, ...omit); 22 | }, 23 | removeAllListeners( 24 | ...args: Parameters 25 | ) { 26 | const [channel] = args; 27 | return ipcRenderer.removeAllListeners(channel); 28 | }, 29 | once(...args: Parameters) { 30 | const [channel, listener] = args; 31 | return ipcRenderer.once(channel, (event, ...args) => 32 | listener(event, ...args) 33 | ); 34 | }, 35 | 36 | // You can expose other APTs you need here. 37 | dialog, 38 | }); 39 | -------------------------------------------------------------------------------- /backend/sql/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./knex"; 2 | export * from "./queries"; 3 | export * from "./utils"; 4 | -------------------------------------------------------------------------------- /backend/sql/knex.ts: -------------------------------------------------------------------------------- 1 | import { app } from "electron"; 2 | import knexClass from "knex"; 3 | import config from "./knexfile"; 4 | 5 | // Determine environment based on app packaging status 6 | const environment = app.isPackaged ? "production" : "development"; 7 | 8 | // Create database connection with proper configuration from knexfile 9 | export const db = knexClass(config[environment]); 10 | 11 | // Add event listeners for connection issues 12 | db.on("error", (error) => { 13 | console.error("Database connection error:", error); 14 | }); 15 | 16 | // Initialize database connection 17 | db.raw("SELECT 1").then(() => { 18 | console.log(`Database connected in ${environment} mode`); 19 | }).catch((error) => { 20 | console.error("Failed to connect to database:", error); 21 | }); 22 | -------------------------------------------------------------------------------- /backend/sql/knexfile.ts: -------------------------------------------------------------------------------- 1 | import { app } from "electron"; 2 | import { constants } from "../utils"; 3 | import { Knex } from "knex"; 4 | import { Database } from "better-sqlite3"; 5 | 6 | const config: { 7 | development: Knex.Config, 8 | production: Knex.Config 9 | } = { 10 | development: { 11 | client: "better-sqlite3", 12 | connection: { 13 | filename: constants.databasePath, 14 | }, 15 | useNullAsDefault: true, 16 | pool: { 17 | min: 2, 18 | max: 10, 19 | afterCreate: (conn: Database, done: () => void) => { 20 | // Enable foreign keys support 21 | conn.pragma('foreign_keys', { 22 | simple: true, 23 | }); 24 | done(); 25 | }, 26 | }, 27 | migrations: { 28 | directory: "./migrations", 29 | tableName: "knex_migrations", 30 | }, 31 | debug: Boolean(process.env?.debug) ?? !app.isPackaged, 32 | }, 33 | production: { 34 | client: "better-sqlite3", 35 | connection: { 36 | filename: constants.databasePath, 37 | }, 38 | useNullAsDefault: true, 39 | pool: { 40 | min: 2, 41 | max: 10, 42 | afterCreate: (conn: Database, done: () => void) => { 43 | conn.pragma('foreign_keys', { 44 | simple: true, 45 | }); 46 | done(); 47 | }, 48 | }, 49 | migrations: { 50 | directory: "./migrations", 51 | tableName: "knex_migrations", 52 | }, 53 | debug: false, 54 | }, 55 | }; 56 | 57 | export default config; -------------------------------------------------------------------------------- /backend/sql/queries/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./accounts"; 2 | export * from "./achievements"; 3 | export * from "./games"; 4 | export * from "./lists"; 5 | -------------------------------------------------------------------------------- /backend/utils/constants.ts: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import { join } from "node:path"; 3 | import { homedir } from "os"; 4 | import { getSoundPath } from "./utils"; 5 | 6 | const appDataPath = join(homedir(), "moe.falkor"); 7 | const downloadsPath = join(homedir(), "Downloads"); 8 | const settingsPath = join(appDataPath, "settings.json"); 9 | const cachePath = join(appDataPath, "cache"); 10 | 11 | if (!fs.existsSync(appDataPath)) { 12 | fs.mkdirSync(appDataPath); 13 | } 14 | 15 | if (!fs.existsSync(downloadsPath)) { 16 | fs.mkdirSync(downloadsPath); 17 | } 18 | 19 | if (!fs.existsSync(cachePath)) { 20 | fs.mkdirSync(cachePath); 21 | } 22 | 23 | export const constants = { 24 | databasePath: join(appDataPath, "database.sqlite"), 25 | pluginsPath: join(appDataPath, "plugins"), 26 | themesPath: join(appDataPath, "themes"), 27 | screenshotsPath: join(appDataPath, "screenshots"), 28 | logsPath: join(appDataPath, "logs.json"), 29 | cachePath, 30 | appDataPath, 31 | settingsPath, 32 | downloadsPath, 33 | apiUrl: process.env.FALKOR_API_BASE_URL, 34 | assets: { 35 | sounds: { 36 | complete: getSoundPath("complete.wav"), 37 | }, 38 | }, 39 | }; 40 | -------------------------------------------------------------------------------- /backend/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./constants"; 2 | export * from "./utils"; 3 | export * from "./uuid"; 4 | -------------------------------------------------------------------------------- /backend/utils/settings/constants.ts: -------------------------------------------------------------------------------- 1 | import { SettingsConfig } from "@/@types"; 2 | import { constants } from ".."; 3 | 4 | export const defaultSettings: SettingsConfig = { 5 | theme: "system", 6 | language: "en", 7 | downloadsPath: constants.downloadsPath, 8 | autoCheckForUpdates: true, 9 | checkForUpdatesOnStartup: true, 10 | checkForPluginUpdatesOnStartup: true, 11 | useAccountsForDownloads: false, 12 | titleBarStyle: "icons", 13 | launchOnStartup: false, 14 | closeToTray: false, 15 | maxDownloadSpeed: -1, 16 | maxUploadSpeed: -1, 17 | notifications: true, 18 | api_base_url: constants.apiUrl ?? "https://api.falkor.moe", 19 | maxConcurrentDownloads: 1, 20 | downloadConfig: { 21 | maxConcurrentDownloads: 2, 22 | maxRetries: 3, 23 | persistQueue: true, 24 | retryDelay: 1000, 25 | }, 26 | }; 27 | -------------------------------------------------------------------------------- /build/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-Falkor/falkor/83ccd92a990b6154655cab6cfa649fffe4e46e89/build/icon.icns -------------------------------------------------------------------------------- /build/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-Falkor/falkor/83ccd92a990b6154655cab6cfa649fffe4e46e89/build/icon.ico -------------------------------------------------------------------------------- /build/icons/1024x1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-Falkor/falkor/83ccd92a990b6154655cab6cfa649fffe4e46e89/build/icons/1024x1024.png -------------------------------------------------------------------------------- /build/icons/128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-Falkor/falkor/83ccd92a990b6154655cab6cfa649fffe4e46e89/build/icons/128x128.png -------------------------------------------------------------------------------- /build/icons/16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-Falkor/falkor/83ccd92a990b6154655cab6cfa649fffe4e46e89/build/icons/16x16.png -------------------------------------------------------------------------------- /build/icons/24x24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-Falkor/falkor/83ccd92a990b6154655cab6cfa649fffe4e46e89/build/icons/24x24.png -------------------------------------------------------------------------------- /build/icons/256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-Falkor/falkor/83ccd92a990b6154655cab6cfa649fffe4e46e89/build/icons/256x256.png -------------------------------------------------------------------------------- /build/icons/32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-Falkor/falkor/83ccd92a990b6154655cab6cfa649fffe4e46e89/build/icons/32x32.png -------------------------------------------------------------------------------- /build/icons/48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-Falkor/falkor/83ccd92a990b6154655cab6cfa649fffe4e46e89/build/icons/48x48.png -------------------------------------------------------------------------------- /build/icons/512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-Falkor/falkor/83ccd92a990b6154655cab6cfa649fffe4e46e89/build/icons/512x512.png -------------------------------------------------------------------------------- /build/icons/64x64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-Falkor/falkor/83ccd92a990b6154655cab6cfa649fffe4e46e89/build/icons/64x64.png -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "new-york", 4 | "rsc": false, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.js", 8 | "css": "src/global.css", 9 | "baseColor": "zinc", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils", 16 | "ui": "@/components/ui", 17 | "lib": "@/lib", 18 | "hooks": "@/hooks" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | Falkor 17 | 18 | 19 | 20 |
21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | '@tailwindcss/postcss': {}, 4 | }, 5 | } 6 | -------------------------------------------------------------------------------- /public/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-Falkor/falkor/83ccd92a990b6154655cab6cfa649fffe4e46e89/public/icon.png -------------------------------------------------------------------------------- /resources/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-Falkor/falkor/83ccd92a990b6154655cab6cfa649fffe4e46e89/resources/icon.png -------------------------------------------------------------------------------- /resources/sounds/achievement_unlock.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-Falkor/falkor/83ccd92a990b6154655cab6cfa649fffe4e46e89/resources/sounds/achievement_unlock.wav -------------------------------------------------------------------------------- /resources/sounds/complete.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-Falkor/falkor/83ccd92a990b6154655cab6cfa649fffe4e46e89/resources/sounds/complete.wav -------------------------------------------------------------------------------- /src/@types/accounts/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./real_debrid"; 2 | export * from "./types"; 3 | export * from "./torbox"; 4 | -------------------------------------------------------------------------------- /src/@types/accounts/types.ts: -------------------------------------------------------------------------------- 1 | export type ExternalAccountType = "real-debrid" | "torbox"; 2 | 3 | export interface ExternalAccount { 4 | id: number; 5 | username: string | null; 6 | email: string | null; 7 | avatar: string | null; 8 | client_id: string | null; 9 | client_secret: string | null; 10 | access_token: string; 11 | refresh_token: string; 12 | expires_in: number; 13 | type: ExternalAccountType; 14 | } 15 | 16 | export interface ExternalNewAccountInput { 17 | username?: string; 18 | email?: string; 19 | avatar?: string; 20 | client_id?: string; 21 | client_secret?: string; 22 | access_token: string; 23 | refresh_token: string; 24 | expires_in: number; 25 | type: ExternalAccountType; 26 | } 27 | 28 | export interface ExternalTokenUpdateInput { 29 | access_token: string; 30 | refresh_token: string; 31 | expires_in: Date; 32 | } 33 | 34 | export type ExternalRefreshTokenFunction = (refresh_token: string) => Promise<{ 35 | accessToken: string; 36 | refreshToken: string; 37 | expiresIn: Date; 38 | }>; 39 | 40 | export type ExternalAccountColumn = Pick< 41 | ExternalAccount, 42 | "avatar" | "username" | "email" | "type" | "access_token" | "expires_in" 43 | >; 44 | -------------------------------------------------------------------------------- /src/@types/achievements/db .ts: -------------------------------------------------------------------------------- 1 | export interface AchievementDBItem { 2 | readonly id: number; 3 | readonly game_id: string; 4 | readonly achievement_display_name: string; 5 | readonly achievement_name: string; 6 | readonly description: string | null; 7 | readonly unlocked: boolean; 8 | readonly unlocked_at: Date | null; 9 | } 10 | 11 | export interface NewAchievementInputDBItem { 12 | game_id: string; 13 | achievement_name: string; 14 | achievement_display_name: string; 15 | achievement_description?: string; 16 | achievement_image: string; 17 | achievement_unlocked?: boolean; 18 | achievement_unlocked_at?: Date; 19 | } 20 | -------------------------------------------------------------------------------- /src/@types/achievements/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./db "; 2 | export * from "./types"; 3 | -------------------------------------------------------------------------------- /src/@types/achievements/types.ts: -------------------------------------------------------------------------------- 1 | import { Cracker } from "../types"; 2 | 3 | export interface UnlockedAchievement { 4 | name: string; 5 | unlockTime: number; 6 | } 7 | 8 | export interface AchievementFile { 9 | cracker: Cracker; 10 | path: string; 11 | } 12 | 13 | export interface AchivementStat { 14 | name: string; 15 | displayName: string; 16 | hidden: number; 17 | description?: string; 18 | icon: string; 19 | icongray: string; 20 | unlockTime: number; 21 | } 22 | 23 | export interface ISchemaForGame { 24 | game: ISchemaForGameGame; 25 | } 26 | 27 | export interface ISchemaForGameGame { 28 | gameName: string; 29 | gameVersion: string; 30 | availableGameStats: ISchemaForGameAvailableGameStats; 31 | } 32 | 33 | export interface ISchemaForGameAvailableGameStats { 34 | achievements: ISchemaForGameAchievement[]; 35 | } 36 | 37 | export interface ISchemaForGameAchievement { 38 | name: string; 39 | defaultvalue: number; 40 | displayName: string; 41 | hidden: number; 42 | icon: string; 43 | icongray: string; 44 | description?: string; 45 | } 46 | -------------------------------------------------------------------------------- /src/@types/download/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./types"; 2 | export * from "./queue-types"; -------------------------------------------------------------------------------- /src/@types/download/types.ts: -------------------------------------------------------------------------------- 1 | export type DownloadgameData = { 2 | id: number; 3 | name: string; 4 | image_id: string; 5 | banner_id?: string; 6 | }; 7 | -------------------------------------------------------------------------------- /src/@types/global.d.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-Falkor/falkor/83ccd92a990b6154655cab6cfa649fffe4e46e89/src/@types/global.d.ts -------------------------------------------------------------------------------- /src/@types/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./achievements"; 2 | export * from "./download"; 3 | export * from "./launcher"; 4 | export * from "./settings"; 5 | export * from "./themes"; 6 | export * from "./torrent"; 7 | export * from "./types"; 8 | -------------------------------------------------------------------------------- /src/@types/launcher.ts: -------------------------------------------------------------------------------- 1 | export interface LauncherExtra { 2 | args?: string; 3 | command?: string; 4 | } 5 | -------------------------------------------------------------------------------- /src/@types/library/types.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Represents a game entry in the `library_games` table. 3 | */ 4 | export interface LibraryGame { 5 | id: number; 6 | game_name: string; 7 | game_path: string; 8 | game_id: string; 9 | game_steam_id?: string | null; 10 | game_icon?: string; 11 | game_args?: string; 12 | game_command?: string; 13 | game_playtime: number; 14 | game_last_played?: Date | null; 15 | igdb_id?: number | null; 16 | wine_prefix_folder?: string | null; 17 | } 18 | 19 | /** 20 | * Type for adding a new game. 21 | */ 22 | export type NewLibraryGame = Omit< 23 | LibraryGame, 24 | "id" | "game_playtime" | "game_last_played" 25 | >; 26 | 27 | /** 28 | * Type for updating game fields. 29 | */ 30 | export type LibraryGameUpdate = Partial>; 31 | -------------------------------------------------------------------------------- /src/@types/logs.ts: -------------------------------------------------------------------------------- 1 | export interface LogEntry { 2 | level: LogLevel; 3 | message: string; 4 | timestamp: number; 5 | } 6 | 7 | export type LogLevel = "error" | "warn" | "info" | "debug" | "trace"; 8 | 9 | export interface LoggerFilterOptions { 10 | date?: Date; 11 | level?: LogLevel; 12 | } 13 | -------------------------------------------------------------------------------- /src/@types/settings/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./types"; 2 | -------------------------------------------------------------------------------- /src/@types/settings/types.ts: -------------------------------------------------------------------------------- 1 | import { ExternalAccountType } from "../accounts"; 2 | 3 | // Define a recursive type for nested settings objects 4 | export type NestedSettingsObject = { 5 | [key: string]: SettingsValue; 6 | }; 7 | 8 | // Define possible types for settings values 9 | export type SettingsValue = 10 | | string 11 | | number 12 | | boolean 13 | | null 14 | | undefined 15 | | NestedSettingsObject 16 | | Array; 17 | 18 | export interface SettingsConfig { 19 | theme: SettingsTheme; 20 | language: string; 21 | downloadsPath: string; 22 | autoCheckForUpdates: boolean; 23 | checkForUpdatesOnStartup: boolean; 24 | checkForPluginUpdatesOnStartup: boolean; 25 | launchOnStartup: launchOnStartupType; 26 | closeToTray: boolean; 27 | useAccountsForDownloads: boolean; 28 | titleBarStyle: SettingsTitleBarStyle; 29 | maxDownloadSpeed: number; 30 | maxUploadSpeed: number; 31 | notifications: boolean; 32 | api_base_url: string; 33 | preferredDebridService?: ExternalAccountType; 34 | maxConcurrentDownloads?: number; 35 | // Allow for additional nested settings objects 36 | [key: string]: SettingsValue; 37 | } 38 | 39 | export type SettingsTheme = "system" | "light" | "dark"; 40 | 41 | export type SettingsTitleBarStyle = 42 | | "icons" 43 | | "traffic-lights" 44 | | "native" 45 | | "none"; 46 | 47 | export type launchOnStartupType = true | false | "minimized"; 48 | -------------------------------------------------------------------------------- /src/@types/themes.ts: -------------------------------------------------------------------------------- 1 | export interface ThemeResponse { 2 | message: string; 3 | success: boolean; 4 | } 5 | -------------------------------------------------------------------------------- /src/@types/torrent/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./types"; 2 | -------------------------------------------------------------------------------- /src/@types/torrent/types.ts: -------------------------------------------------------------------------------- 1 | import { DownloadgameData, DownloadStatus } from "../download"; 2 | 3 | export interface ITorrent { 4 | infoHash: string; 5 | name: string; 6 | progress: number; 7 | downloadSpeed: number; 8 | uploadSpeed: number; 9 | numPeers: number; 10 | path: string; 11 | paused: boolean; 12 | status: DownloadStatus; 13 | totalSize: number; 14 | timeRemaining: number; 15 | game_data?: DownloadgameData; 16 | url?: string; 17 | } 18 | -------------------------------------------------------------------------------- /src/assets/bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-Falkor/falkor/83ccd92a990b6154655cab6cfa649fffe4e46e89/src/assets/bg.png -------------------------------------------------------------------------------- /src/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-Falkor/falkor/83ccd92a990b6154655cab6cfa649fffe4e46e89/src/assets/icon.png -------------------------------------------------------------------------------- /src/assets/protondb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-Falkor/falkor/83ccd92a990b6154655cab6cfa649fffe4e46e89/src/assets/protondb.png -------------------------------------------------------------------------------- /src/components/IGDBImage.tsx: -------------------------------------------------------------------------------- 1 | import { IGDBImageSize } from "@/@types"; 2 | import { ImgHTMLAttributes } from "react"; 3 | 4 | interface IGDBImageProps extends ImgHTMLAttributes { 5 | imageSize?: IGDBImageSize; 6 | imageId: string; 7 | alt: string; 8 | } 9 | 10 | const IGDBImage = ({ 11 | imageSize = "original", 12 | imageId, 13 | alt, 14 | ...props 15 | }: IGDBImageProps) => { 16 | const src = imageId.startsWith("http") 17 | ? imageId 18 | : `https://images.igdb.com/igdb/image/upload/t_${imageSize}/${imageId}.png`; 19 | 20 | return {alt}; 21 | }; 22 | 23 | export default IGDBImage; 24 | -------------------------------------------------------------------------------- /src/components/banner/index.tsx: -------------------------------------------------------------------------------- 1 | import { cn, igdb } from "@/lib"; 2 | import { useQuery } from "@tanstack/react-query"; 3 | import { HTMLAttributes } from "react"; 4 | import BannerCard from "../cards/bannerCard"; 5 | import BannerSkeleton from "../skeletons/banner"; 6 | import { CarouselContent, CarouselItem } from "../ui/carousel"; 7 | 8 | type Props = HTMLAttributes; 9 | 10 | const Banner = ({ className, ...props }: Props) => { 11 | const fetch = async () => { 12 | const data = await igdb.topRated(); 13 | return data; 14 | }; 15 | 16 | const { isPending, error, data } = useQuery({ 17 | queryKey: ["igdb", "Banner"], 18 | queryFn: fetch, 19 | refetchOnMount: false, 20 | refetchOnWindowFocus: false, 21 | }); 22 | 23 | console.log(data); 24 | 25 | if (isPending) return ; 26 | 27 | if (error) return
Error
; 28 | 29 | return ( 30 |
31 | 32 | {!!data?.length && 33 | data?.map((game) => ( 34 | 35 | 36 | 37 | ))} 38 | 39 |
40 | ); 41 | }; 42 | 43 | export default Banner; 44 | -------------------------------------------------------------------------------- /src/components/buttonWithIcon.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "@/lib"; 2 | import React, { ButtonHTMLAttributes, forwardRef } from "react"; 3 | import { Button } from "./ui/button"; 4 | 5 | interface Props extends ButtonHTMLAttributes { 6 | startIcon?: React.ReactElement; 7 | endIcon?: React.ReactElement; 8 | divClassName?: string; 9 | } 10 | 11 | export const ButtonWithIcon = forwardRef( 12 | ( 13 | { className, type, startIcon, endIcon, divClassName, children, ...props }, 14 | ref 15 | ) => { 16 | const StartIcon = startIcon; 17 | const EndIcon = endIcon; 18 | 19 | return ( 20 | 49 | ); 50 | } 51 | ); 52 | -------------------------------------------------------------------------------- /src/components/cards/listCard/image.tsx: -------------------------------------------------------------------------------- 1 | import IGDBImage from "@/components/IGDBImage"; 2 | 3 | interface ListCardImageProps { 4 | imageId: string; 5 | alt: string; 6 | } 7 | 8 | const ListCardImage = ({ imageId, alt }: ListCardImageProps) => ( 9 | 10 | ); 11 | 12 | export default ListCardImage; 13 | -------------------------------------------------------------------------------- /src/components/cards/listCard/index.tsx: -------------------------------------------------------------------------------- 1 | import { ListGame } from "@/@types"; 2 | import { H5 } from "@/components/ui/typography"; 3 | import { Link } from "@tanstack/react-router"; 4 | import ListCardImage from "./image"; 5 | 6 | type ListCardProps = ListGame; 7 | 8 | const ListCard: React.FC = ({ game_id, title, image }) => { 9 | const imageId = image 10 | ? `https:${image.replace("t_thumb", "t_cover_big")}` 11 | : ""; 12 | 13 | return ( 14 | 15 |
16 | {/* IMAGE */} 17 |
18 | 19 | 20 | 21 |
22 | 23 | {/* CONTENT */} 24 |
25 |
26 |
27 | {title} 28 |
29 |
30 |
31 | 32 | ); 33 | }; 34 | 35 | export default ListCard; 36 | -------------------------------------------------------------------------------- /src/components/cards/listCard/overlay.tsx: -------------------------------------------------------------------------------- 1 | import { H5 } from "@/components/ui/typography"; 2 | 3 | interface ListCardOverlayProps { 4 | title: string; 5 | } 6 | 7 | const ListCardOverlay: React.FC = ({ title }) => ( 8 |
9 |
10 |
11 |
12 | {title} 13 |
14 |
15 |
16 |
17 | ); 18 | 19 | export default ListCardOverlay; 20 | -------------------------------------------------------------------------------- /src/components/carouselButton.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "@/lib"; 2 | import { ChevronLeft, ChevronRight } from "lucide-react"; 3 | import { Button } from "./ui/button"; 4 | import { useCarousel } from "./ui/carousel"; 5 | 6 | interface CarouselButtonProps { 7 | direction: "left" | "right"; 8 | className?: string; 9 | id?: string; 10 | } 11 | 12 | const CarouselButton = ({ direction, className, id }: CarouselButtonProps) => { 13 | const { scrollNext, scrollPrev, canScrollNext, canScrollPrev } = 14 | useCarousel(); 15 | 16 | return ( 17 | 35 | ); 36 | }; 37 | 38 | export default CarouselButton; 39 | -------------------------------------------------------------------------------- /src/components/confirmClose.tsx: -------------------------------------------------------------------------------- 1 | import { invoke } from "@/lib"; 2 | import { useEffect, useState } from "react"; 3 | import Confirmation from "./confirmation"; 4 | 5 | const ConfirmClose = () => { 6 | const [open, setOpen] = useState(false); 7 | const [message, setMessage] = useState(null); 8 | 9 | const handleClose = () => { 10 | invoke("app:close", true); 11 | }; 12 | 13 | useEffect(() => { 14 | window.ipcRenderer.on( 15 | "close-confirm", 16 | (_event, { message }: { message: string }) => { 17 | setMessage(message); 18 | setOpen(true); 19 | } 20 | ); 21 | 22 | return () => { 23 | window.ipcRenderer.removeAllListeners("close-confirm"); 24 | }; 25 | }, []); 26 | 27 | if (!message) return null; 28 | return ( 29 | 36 | ); 37 | }; 38 | 39 | export default ConfirmClose; 40 | -------------------------------------------------------------------------------- /src/components/containers/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./mainContainer"; 2 | export * from "./row"; 3 | -------------------------------------------------------------------------------- /src/components/containers/mainContainer.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "@/lib"; 2 | import { HTMLAttributes, PropsWithChildren } from "react"; 3 | 4 | type Props = HTMLAttributes; 5 | 6 | const MainContainer = ({ 7 | children, 8 | className, 9 | id, 10 | }: PropsWithChildren) => { 11 | return ( 12 |
13 | {children} 14 |
15 | ); 16 | }; 17 | 18 | export default MainContainer; 19 | -------------------------------------------------------------------------------- /src/components/folderButton.tsx: -------------------------------------------------------------------------------- 1 | import { invoke } from "@/lib"; 2 | import { Folder } from "lucide-react"; 3 | import { JSX } from "react"; 4 | import { Button } from "./ui/button"; 5 | import { Tooltip, TooltipContent, TooltipTrigger } from "./ui/tooltip"; 6 | 7 | interface Props { 8 | path: string | "downloads"; 9 | icon?: JSX.Element; 10 | variant?: 11 | | "default" 12 | | "destructive" 13 | | "outline" 14 | | "secondary" 15 | | "ghost" 16 | | "link" 17 | | null; 18 | size?: "default" | "icon" | "sm" | "lg" | null; 19 | tooltip?: string; 20 | } 21 | 22 | const FolderButton = ({ 23 | path, 24 | icon, 25 | tooltip, 26 | variant, 27 | size = "icon", 28 | }: Props) => { 29 | const handleClick = () => { 30 | if (path === "downloads") { 31 | invoke("generic:open-downloads"); 32 | return; 33 | } 34 | invoke("generic:open-folder", path); 35 | }; 36 | 37 | return ( 38 | 39 | 40 | 43 | 44 | {tooltip} 45 | 46 | ); 47 | }; 48 | 49 | export default FolderButton; 50 | -------------------------------------------------------------------------------- /src/components/genericRow.tsx: -------------------------------------------------------------------------------- 1 | import { igdb } from "@/lib"; 2 | import { useQuery } from "@tanstack/react-query"; 3 | import DefaultCard from "./cards/defaultCard"; 4 | import GenericRowSkeleton from "./skeletons/genericRow"; 5 | import { CarouselContent, CarouselItem } from "./ui/carousel"; 6 | 7 | interface GenericRowProps { 8 | dataToFetch: "mostAnticipated" | "topRated" | "newReleases"; 9 | fetchKey: string[]; 10 | } 11 | 12 | const GenericRow = ({ dataToFetch, fetchKey }: GenericRowProps) => { 13 | const fetcher = async () => { 14 | const data = await igdb[dataToFetch](); 15 | return data; 16 | }; 17 | 18 | const { data, isPending, error } = useQuery({ 19 | queryKey: ["igdb", ...fetchKey], 20 | queryFn: fetcher, 21 | }); 22 | 23 | if (isPending) return ; 24 | if (error) return null; 25 | 26 | return ( 27 | 28 | {!!data?.length && 29 | data?.map((game) => ( 30 | 35 | 36 | 37 | ))} 38 | 39 | ); 40 | }; 41 | 42 | export default GenericRow; 43 | -------------------------------------------------------------------------------- /src/components/info/infoBar.tsx: -------------------------------------------------------------------------------- 1 | import { IGDBReturnDataType } from "@/lib/api/igdb/types"; 2 | import TopbarSkeleton from "../skeletons/info/topbar.skeleton"; 3 | import { Topbar } from "./topbar"; 4 | 5 | interface InfoBarProps { 6 | onBack: () => void; 7 | titleText: string; 8 | data?: IGDBReturnDataType; 9 | isPending: boolean; 10 | } 11 | 12 | export const InfoBar = ({ 13 | onBack, 14 | titleText, 15 | isPending, 16 | data, 17 | }: InfoBarProps) => { 18 | if (isPending) return ; 19 | if (!data) return null; 20 | 21 | return ( 22 |
23 | 24 |
25 | ); 26 | }; 27 | -------------------------------------------------------------------------------- /src/components/info/media/index.tsx: -------------------------------------------------------------------------------- 1 | import { useLanguageContext } from "@/contexts/I18N"; 2 | import { IGDBReturnDataType } from "@/lib/api/igdb/types"; 3 | import MediaScreenshots from "./screenshots"; 4 | import MediaTrailer from "./trailer"; 5 | import { H1 } from "@/components/ui/typography"; 6 | 7 | const GameMedia = (props: IGDBReturnDataType) => { 8 | const { t } = useLanguageContext(); 9 | const { name, screenshots, videos } = props; 10 | 11 | return ( 12 |
13 |

{t("media")}

14 | 15 | 16 | 17 | 18 |
19 | ); 20 | }; 21 | 22 | export default GameMedia; 23 | -------------------------------------------------------------------------------- /src/components/info/media/trailer.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "@/lib"; 2 | import { Video } from "@/lib/api/igdb/types"; 3 | 4 | interface MediaTrailerProps { 5 | videos: Video[] | undefined; 6 | className?: string; 7 | } 8 | 9 | const MediaTrailer = ({ videos, className }: MediaTrailerProps) => { 10 | if (!videos?.length) return null; 11 | 12 | return ( 13 |