mirror of
https://github.com/hydralauncher/hydra.git
synced 2026-01-11 22:06:17 +00:00
Compare commits
42 Commits
fix/notifi
...
fix/friend
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c0956b1bc1 | ||
|
|
d9d443ee6d | ||
|
|
a912b57ccc | ||
|
|
447c146035 | ||
|
|
39ff44f9d1 | ||
|
|
dbe101b7df | ||
|
|
e7a62c16fa | ||
|
|
f37ccbb4c0 | ||
|
|
feb8d78e01 | ||
|
|
2ccc93ea61 | ||
|
|
7e7390885e | ||
|
|
64815f4f8d | ||
|
|
4dfdc4d798 | ||
|
|
9bbfab2aff | ||
|
|
01938f8905 | ||
|
|
f60ad5908d | ||
|
|
fe6553bcdc | ||
|
|
87895bb715 | ||
|
|
290209f372 | ||
|
|
87fcbaa56e | ||
|
|
c32ce14630 | ||
|
|
e52f10a5ff | ||
|
|
bcdbe31596 | ||
|
|
7ed514b6ef | ||
|
|
42386ae0b5 | ||
|
|
04d8f900a6 | ||
|
|
8b3bcd88b1 | ||
|
|
b2bffeb2b0 | ||
|
|
0a194eaa29 | ||
|
|
07c277c033 | ||
|
|
345696ad06 | ||
|
|
6c4e8c406f | ||
|
|
c46a1e7848 | ||
|
|
590e09a8c3 | ||
|
|
4584783f44 | ||
|
|
765ec70dd0 | ||
|
|
de483da51c | ||
|
|
2bc0266775 | ||
|
|
c9729fb3eb | ||
|
|
9a7ad148e3 | ||
|
|
d929fbaeaa | ||
|
|
8fa33119d6 |
@@ -1,6 +1,7 @@
|
||||
MAIN_VITE_API_URL=
|
||||
MAIN_VITE_AUTH_URL=
|
||||
MAIN_VITE_WS_URL=
|
||||
MAIN_VITE_NIMBUS_API_URL=
|
||||
RENDERER_VITE_REAL_DEBRID_REFERRAL_ID=
|
||||
RENDERER_VITE_TORBOX_REFERRAL_CODE=
|
||||
MAIN_VITE_LAUNCHER_SUBDOMAIN=
|
||||
|
||||
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@@ -57,6 +57,7 @@ jobs:
|
||||
MAIN_VITE_AUTH_URL: ${{ vars.MAIN_VITE_STAGING_AUTH_URL }}
|
||||
MAIN_VITE_CHECKOUT_URL: ${{ vars.MAIN_VITE_STAGING_CHECKOUT_URL }}
|
||||
MAIN_VITE_WS_URL: ${{ vars.MAIN_VITE_WS_STAGING_URL }}
|
||||
MAIN_VITE_NIMBUS_API_URL: ${{ vars.MAIN_VITE_NIMBUS_API_URL }}
|
||||
RENDERER_VITE_EXTERNAL_RESOURCES_URL: ${{ vars.EXTERNAL_RESOURCES_URL }}
|
||||
MAIN_VITE_EXTERNAL_RESOURCES_URL: ${{ vars.EXTERNAL_RESOURCES_URL }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -73,6 +74,7 @@ jobs:
|
||||
MAIN_VITE_AUTH_URL: ${{ vars.MAIN_VITE_STAGING_AUTH_URL }}
|
||||
MAIN_VITE_CHECKOUT_URL: ${{ vars.MAIN_VITE_STAGING_CHECKOUT_URL }}
|
||||
MAIN_VITE_WS_URL: ${{ vars.MAIN_VITE_WS_STAGING_URL }}
|
||||
MAIN_VITE_NIMBUS_API_URL: ${{ vars.MAIN_VITE_NIMBUS_API_URL }}
|
||||
RENDERER_VITE_EXTERNAL_RESOURCES_URL: ${{ vars.EXTERNAL_RESOURCES_URL }}
|
||||
MAIN_VITE_EXTERNAL_RESOURCES_URL: ${{ vars.EXTERNAL_RESOURCES_URL }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
6
.github/workflows/release.yml
vendored
6
.github/workflows/release.yml
vendored
@@ -54,9 +54,10 @@ jobs:
|
||||
MAIN_VITE_API_URL: ${{ vars.MAIN_VITE_API_URL }}
|
||||
MAIN_VITE_AUTH_URL: ${{ vars.MAIN_VITE_AUTH_URL }}
|
||||
MAIN_VITE_CHECKOUT_URL: ${{ vars.MAIN_VITE_CHECKOUT_URL }}
|
||||
MAIN_VITE_WS_URL: ${{ vars.MAIN_VITE_WS_URL }}
|
||||
MAIN_VITE_NIMBUS_API_URL: ${{ vars.MAIN_VITE_NIMBUS_API_URL }}
|
||||
RENDERER_VITE_EXTERNAL_RESOURCES_URL: ${{ vars.EXTERNAL_RESOURCES_URL }}
|
||||
MAIN_VITE_EXTERNAL_RESOURCES_URL: ${{ vars.EXTERNAL_RESOURCES_URL }}
|
||||
MAIN_VITE_WS_URL: ${{ vars.MAIN_VITE_WS_URL }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
RENDERER_VITE_SENTRY_DSN: ${{ vars.SENTRY_DSN }}
|
||||
@@ -71,9 +72,10 @@ jobs:
|
||||
MAIN_VITE_API_URL: ${{ vars.MAIN_VITE_API_URL }}
|
||||
MAIN_VITE_AUTH_URL: ${{ vars.MAIN_VITE_AUTH_URL }}
|
||||
MAIN_VITE_CHECKOUT_URL: ${{ vars.MAIN_VITE_CHECKOUT_URL }}
|
||||
MAIN_VITE_WS_URL: ${{ vars.MAIN_VITE_WS_URL }}
|
||||
MAIN_VITE_NIMBUS_API_URL: ${{ vars.MAIN_VITE_NIMBUS_API_URL }}
|
||||
RENDERER_VITE_EXTERNAL_RESOURCES_URL: ${{ vars.EXTERNAL_RESOURCES_URL }}
|
||||
MAIN_VITE_EXTERNAL_RESOURCES_URL: ${{ vars.EXTERNAL_RESOURCES_URL }}
|
||||
MAIN_VITE_WS_URL: ${{ vars.MAIN_VITE_WS_URL }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
RENDERER_VITE_SENTRY_DSN: ${{ vars.SENTRY_DSN }}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "hydralauncher",
|
||||
"version": "3.7.6",
|
||||
"version": "3.8.0",
|
||||
"description": "Hydra",
|
||||
"main": "./out/main/index.js",
|
||||
"author": "Los Broxas",
|
||||
@@ -91,6 +91,7 @@
|
||||
"user-agents": "^1.1.387",
|
||||
"uuid": "^13.0.0",
|
||||
"winreg": "^1.2.5",
|
||||
"workwonders-sdk": "0.0.10",
|
||||
"ws": "^8.18.1",
|
||||
"yaml": "^2.6.1",
|
||||
"yup": "^1.5.0"
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import aria2p
|
||||
from aria2p.client import ClientException as DownloadNotFound
|
||||
|
||||
class HttpDownloader:
|
||||
def __init__(self):
|
||||
@@ -11,12 +12,16 @@ class HttpDownloader:
|
||||
)
|
||||
)
|
||||
|
||||
def start_download(self, url: str, save_path: str, header: str, out: str = None):
|
||||
def start_download(self, url: str, save_path: str, header, out: str = None):
|
||||
if self.download:
|
||||
self.aria2.resume([self.download])
|
||||
else:
|
||||
downloads = self.aria2.add(url, options={"header": header, "dir": save_path, "out": out})
|
||||
|
||||
options = {"dir": save_path}
|
||||
if header:
|
||||
options["header"] = header
|
||||
if out:
|
||||
options["out"] = out
|
||||
downloads = self.aria2.add(url, options=options)
|
||||
self.download = downloads[0]
|
||||
|
||||
def pause_download(self):
|
||||
@@ -32,7 +37,11 @@ class HttpDownloader:
|
||||
if self.download == None:
|
||||
return None
|
||||
|
||||
download = self.aria2.get_download(self.download.gid)
|
||||
try:
|
||||
download = self.aria2.get_download(self.download.gid)
|
||||
except DownloadNotFound:
|
||||
self.download = None
|
||||
return None
|
||||
|
||||
response = {
|
||||
'folderName': download.name,
|
||||
|
||||
@@ -175,6 +175,7 @@
|
||||
"repacks_modal_description": "Choose the repack you want to download",
|
||||
"select_folder_hint": "To change the default folder, go to the <0>Settings</0>",
|
||||
"download_now": "Download now",
|
||||
"loading": "Loading...",
|
||||
"no_shop_details": "Could not retrieve shop details.",
|
||||
"download_options": "Download options",
|
||||
"download_path": "Download path",
|
||||
@@ -184,6 +185,12 @@
|
||||
"open_screenshot": "Open screenshot {{number}}",
|
||||
"download_settings": "Download settings",
|
||||
"downloader": "Downloader",
|
||||
"downloader_online": "Online",
|
||||
"downloader_not_configured": "Available but not configured",
|
||||
"downloader_offline": "Link is offline",
|
||||
"downloader_not_available": "Not available",
|
||||
"recommended": "Recommended",
|
||||
"go_to_settings": "Go to Settings",
|
||||
"select_executable": "Select",
|
||||
"no_executable_selected": "No executable selected",
|
||||
"open_folder": "Open folder",
|
||||
@@ -682,6 +689,7 @@
|
||||
"blocked_users": "Blocked users",
|
||||
"unblock": "Unblock",
|
||||
"no_friends_added": "You have no added friends",
|
||||
"no_friends_yet": "You haven't added any friends yet",
|
||||
"view_all": "View all",
|
||||
"load_more": "Load more",
|
||||
"loading": "Loading",
|
||||
@@ -728,7 +736,6 @@
|
||||
"game_added_to_pinned": "Game added to pinned",
|
||||
"karma": "Karma",
|
||||
"karma_count": "karma",
|
||||
"karma_description": "Earned from positive likes on reviews",
|
||||
"user_reviews": "Reviews",
|
||||
"delete_review": "Delete Review",
|
||||
"loading_reviews": "Loading reviews...",
|
||||
|
||||
@@ -182,6 +182,12 @@
|
||||
"open_screenshot": "Abrir captura número {{number}}",
|
||||
"download_settings": "Descargar ajustes",
|
||||
"downloader": "Descargador",
|
||||
"downloader_online": "En línea",
|
||||
"downloader_not_configured": "Disponible pero no configurado",
|
||||
"downloader_offline": "El enlace está fuera de línea",
|
||||
"downloader_not_available": "No disponible",
|
||||
"recommended": "Recomendado",
|
||||
"go_to_settings": "Ir a Ajustes",
|
||||
"select_executable": "Seleccionar",
|
||||
"no_executable_selected": "Sin ejecutable seleccionado",
|
||||
"open_folder": "Abrir carpeta",
|
||||
@@ -651,6 +657,7 @@
|
||||
"sending": "Enviando",
|
||||
"friend_request_sent": "Solicitud de amistad enviada",
|
||||
"friends": "Amistades",
|
||||
"badges": "Insignias",
|
||||
"friends_list": "Lista de amistades",
|
||||
"user_not_found": "Usuario no encontrado",
|
||||
"block_user": "Bloquear usuario",
|
||||
@@ -661,12 +668,16 @@
|
||||
"ignore_request": "Ignorar solicitud",
|
||||
"cancel_request": "Cancelar solicitud",
|
||||
"undo_friendship": "Deshacer amistad",
|
||||
"friendship_removed": "Amigo eliminado",
|
||||
"request_accepted": "Solicitud aceptada",
|
||||
"user_blocked_successfully": "Usuario bloqueado exitosamente",
|
||||
"user_block_modal_text": "Esto va a bloquear a {{displayName}}",
|
||||
"blocked_users": "Usuarios bloqueados",
|
||||
"unblock": "Desbloquear",
|
||||
"no_friends_added": "No tenés amistades añadidas",
|
||||
"view_all": "Ver todo",
|
||||
"load_more": "Cargar más",
|
||||
"loading": "Cargando",
|
||||
"pending": "Pendiente",
|
||||
"no_pending_invites": "No tenés invitaciones pendientes",
|
||||
"no_blocked_users": "No has bloqueado a nadie",
|
||||
@@ -690,6 +701,7 @@
|
||||
"report_reason_other": "Otros",
|
||||
"profile_reported": "Perfil reportado",
|
||||
"your_friend_code": "Tu código de amistad:",
|
||||
"copy_friend_code": "Copiar código de amistad",
|
||||
"upload_banner": "Subir banner",
|
||||
"uploading_banner": "Subiendo banner…",
|
||||
"background_image_updated": "Imagen de fondo actualizada",
|
||||
@@ -710,11 +722,13 @@
|
||||
"amount_minutes_short": "{{amount}}m",
|
||||
"karma": "Karma",
|
||||
"karma_count": "karma",
|
||||
"karma_description": "Conseguido por me gustas positivos en reseñas",
|
||||
"sort_by": "Filtrar por:",
|
||||
"game_added_to_pinned": "Juego añadido a fijados",
|
||||
"user_reviews": "Reseñas",
|
||||
"loading_reviews": "Cargando reseñas...",
|
||||
"wrapped_2025": "Wrapped 2025",
|
||||
"view_my_wrapped_button": "Ver Mi Wrapped 2025",
|
||||
"view_wrapped_button": "Ver Wrapped 2025 de {{displayName}}",
|
||||
"no_reviews": "Sin reseñas aún",
|
||||
"delete_review": "Eliminar reseña"
|
||||
},
|
||||
@@ -767,5 +781,41 @@
|
||||
"all_games": "Todos los Juegos",
|
||||
"recently_played": "Jugados Recientemente",
|
||||
"favorites": "Favoritos"
|
||||
},
|
||||
"notifications_page": {
|
||||
"title": "Notificaciones",
|
||||
"mark_all_as_read": "Marcar todo como leído",
|
||||
"clear_all": "Limpiar todo",
|
||||
"loading": "Cargando...",
|
||||
"empty_title": "Sin notificaciones",
|
||||
"empty_description": "¡Estás al día! Volvé más tarde para ver nuevas actualizaciones.",
|
||||
"empty_filter_description": "No hay notificaciones que coincidan con este filtro.",
|
||||
"filter_all": "Todas",
|
||||
"filter_unread": "No leídas",
|
||||
"filter_friends": "Amigos",
|
||||
"filter_badges": "Insignias",
|
||||
"filter_upvotes": "Votos",
|
||||
"filter_local": "Locales",
|
||||
"load_more": "Cargar más",
|
||||
"dismiss": "Descartar",
|
||||
"accept": "Aceptar",
|
||||
"refuse": "Rechazar",
|
||||
"notification": "Notificación",
|
||||
"friend_request_received_title": "¡Nueva solicitud de amistad!",
|
||||
"friend_request_received_description": "{{displayName}} quiere ser tu amigo",
|
||||
"friend_request_accepted_title": "¡Solicitud de amistad aceptada!",
|
||||
"friend_request_accepted_description": "{{displayName}} aceptó tu solicitud de amistad",
|
||||
"badge_received_title": "¡Obtuviste una nueva insignia!",
|
||||
"badge_received_description": "{{badgeName}}",
|
||||
"review_upvote_title": "¡Tu reseña de {{gameTitle}} recibió votos!",
|
||||
"review_upvote_description": "Tu reseña recibió {{count}} nuevos votos",
|
||||
"marked_all_as_read": "Todas las notificaciones marcadas como leídas",
|
||||
"failed_to_mark_as_read": "Error al marcar las notificaciones como leídas",
|
||||
"cleared_all": "Todas las notificaciones eliminadas",
|
||||
"failed_to_clear": "Error al eliminar las notificaciones",
|
||||
"failed_to_load": "Error al cargar las notificaciones",
|
||||
"failed_to_dismiss": "Error al descartar la notificación",
|
||||
"friend_request_accepted": "Solicitud de amistad aceptada",
|
||||
"friend_request_refused": "Solicitud de amistad rechazada"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -673,8 +673,7 @@
|
||||
"game_removed_from_pinned": "Peli poistettu kiinnitetyistä",
|
||||
"game_added_to_pinned": "Peli lisätty kiinnitettyihin",
|
||||
"karma": "Karma",
|
||||
"karma_count": "karmaa",
|
||||
"karma_description": "Ansittu positiivisilla arvosteluäänillä"
|
||||
"karma_count": "karmaa"
|
||||
},
|
||||
"achievement": {
|
||||
"achievement_unlocked": "Saavutus avattu",
|
||||
|
||||
@@ -718,7 +718,6 @@
|
||||
"game_added_to_pinned": "Játék hozzáadva a kitűzöttekhez",
|
||||
"karma": "Karma",
|
||||
"karma_count": "karma",
|
||||
"karma_description": "Pozitív értékelésekkel szerzett pontok",
|
||||
"user_reviews": "Vélemények",
|
||||
"delete_review": "Vélemény Törlése",
|
||||
"loading_reviews": "Vélemények betöltése..."
|
||||
|
||||
@@ -673,8 +673,7 @@
|
||||
"game_removed_from_pinned": "Spēle dzēsta no piespraustajiem",
|
||||
"game_added_to_pinned": "Spēle pievienota piespraustajiem",
|
||||
"karma": "Karma",
|
||||
"karma_count": "karma",
|
||||
"karma_description": "Nopelnīta ar pozitīviem atsauksmju vērtējumiem"
|
||||
"karma_count": "karma"
|
||||
},
|
||||
"achievement": {
|
||||
"achievement_unlocked": "Sasniegums atbloķēts",
|
||||
|
||||
@@ -172,6 +172,12 @@
|
||||
"open_screenshot": "Ver captura de tela {{number}}",
|
||||
"download_settings": "Ajustes do download",
|
||||
"downloader": "Downloader",
|
||||
"downloader_online": "Online",
|
||||
"downloader_not_configured": "Disponível mas não configurado",
|
||||
"downloader_offline": "Link está offline",
|
||||
"downloader_not_available": "Não disponível",
|
||||
"recommended": "Recomendado",
|
||||
"go_to_settings": "Ir para Configurações",
|
||||
"select_executable": "Explorar",
|
||||
"no_executable_selected": "Nenhum executável selecionado",
|
||||
"open_folder": "Abrir pasta",
|
||||
@@ -654,6 +660,7 @@
|
||||
"see_profile": "Ver perfil",
|
||||
"friend_request_sent": "Pedido de amizade enviado",
|
||||
"friends": "Amigos",
|
||||
"badges": "Insígnias",
|
||||
"add": "Adicionar",
|
||||
"sending": "Enviando",
|
||||
"friends_list": "Lista de amigos",
|
||||
@@ -666,12 +673,16 @@
|
||||
"ignore_request": "Ignorar pedido",
|
||||
"cancel_request": "Cancelar pedido",
|
||||
"undo_friendship": "Desfazer amizade",
|
||||
"friendship_removed": "Amigo removido",
|
||||
"request_accepted": "Pedido de amizade aceito",
|
||||
"user_blocked_successfully": "Usuário bloqueado com sucesso",
|
||||
"user_block_modal_text": "Bloquear {{displayName}}",
|
||||
"blocked_users": "Usuários bloqueados",
|
||||
"unblock": "Desbloquear",
|
||||
"no_friends_added": "Você ainda não possui amigos adicionados",
|
||||
"view_all": "Ver todos",
|
||||
"load_more": "Carregar mais",
|
||||
"loading": "Carregando",
|
||||
"pending": "Pendentes",
|
||||
"no_pending_invites": "Você não possui convites de amizade pendentes",
|
||||
"no_blocked_users": "Você não tem nenhum usuário bloqueado",
|
||||
@@ -695,6 +706,7 @@
|
||||
"report_reason_other": "Outro",
|
||||
"profile_reported": "Perfil reportado",
|
||||
"your_friend_code": "Seu código de amigo:",
|
||||
"copy_friend_code": "Copiar código de amigo",
|
||||
"upload_banner": "Carregar banner",
|
||||
"uploading_banner": "Carregando banner…",
|
||||
"background_image_updated": "Imagem de fundo salva",
|
||||
@@ -720,10 +732,12 @@
|
||||
"achievements_earned": "Conquistas recebidas",
|
||||
"karma": "Karma",
|
||||
"karma_count": "karma",
|
||||
"karma_description": "Ganho a partir de curtidas positivas em avaliações",
|
||||
"manual_playtime_tooltip": "Este tempo de jogo foi atualizado manualmente",
|
||||
"user_reviews": "Avaliações",
|
||||
"loading_reviews": "Carregando avaliações...",
|
||||
"wrapped_2025": "Wrapped 2025",
|
||||
"view_my_wrapped_button": "Ver Meu Wrapped 2025",
|
||||
"view_wrapped_button": "Ver Wrapped 2025 de {{displayName}}",
|
||||
"no_reviews": "Ainda não há avaliações",
|
||||
"delete_review": "Excluir avaliação"
|
||||
},
|
||||
@@ -776,5 +790,41 @@
|
||||
"all_games": "Todos os Jogos",
|
||||
"recently_played": "Jogados Recentemente",
|
||||
"favorites": "Favoritos"
|
||||
},
|
||||
"notifications_page": {
|
||||
"title": "Notificações",
|
||||
"mark_all_as_read": "Marcar todas como lidas",
|
||||
"clear_all": "Limpar todas",
|
||||
"loading": "Carregando...",
|
||||
"empty_title": "Sem notificações",
|
||||
"empty_description": "Você está em dia! Volte mais tarde para ver novas atualizações.",
|
||||
"empty_filter_description": "Nenhuma notificação corresponde a este filtro.",
|
||||
"filter_all": "Todas",
|
||||
"filter_unread": "Não lidas",
|
||||
"filter_friends": "Amigos",
|
||||
"filter_badges": "Insígnias",
|
||||
"filter_upvotes": "Votos",
|
||||
"filter_local": "Locais",
|
||||
"load_more": "Carregar mais",
|
||||
"dismiss": "Descartar",
|
||||
"accept": "Aceitar",
|
||||
"refuse": "Recusar",
|
||||
"notification": "Notificação",
|
||||
"friend_request_received_title": "Nova solicitação de amizade!",
|
||||
"friend_request_received_description": "{{displayName}} quer ser seu amigo",
|
||||
"friend_request_accepted_title": "Solicitação de amizade aceita!",
|
||||
"friend_request_accepted_description": "{{displayName}} aceitou sua solicitação de amizade",
|
||||
"badge_received_title": "Você recebeu uma nova insígnia!",
|
||||
"badge_received_description": "{{badgeName}}",
|
||||
"review_upvote_title": "Sua avaliação de {{gameTitle}} recebeu votos!",
|
||||
"review_upvote_description": "Sua avaliação recebeu {{count}} novos votos",
|
||||
"marked_all_as_read": "Todas as notificações marcadas como lidas",
|
||||
"failed_to_mark_as_read": "Falha ao marcar notificações como lidas",
|
||||
"cleared_all": "Todas as notificações limpas",
|
||||
"failed_to_clear": "Falha ao limpar notificações",
|
||||
"failed_to_load": "Falha ao carregar notificações",
|
||||
"failed_to_dismiss": "Falha ao descartar notificação",
|
||||
"friend_request_accepted": "Solicitação de amizade aceita",
|
||||
"friend_request_refused": "Solicitação de amizade recusada"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -508,7 +508,7 @@
|
||||
"show_and_compare_achievements": "Mostra e compara as tuas conquistas com as de outros utilizadores",
|
||||
"animated_profile_banner": "Banner animado no perfil",
|
||||
"cloud_saving": "Progresso dos jogos na nuvem",
|
||||
"hydra_cloud_feature_found": "Descubriste uma funcionalidade Hydra Cloud!",
|
||||
"hydra_cloud_feature_found": "Descobriste uma funcionalidade Hydra Cloud!",
|
||||
"learn_more": "Saber mais"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -182,6 +182,12 @@
|
||||
"open_screenshot": "Открыть скриншот {{number}}",
|
||||
"download_settings": "Параметры загрузки",
|
||||
"downloader": "Загрузчик",
|
||||
"downloader_online": "Онлайн",
|
||||
"downloader_not_configured": "Доступен, но не настроен",
|
||||
"downloader_offline": "Ссылка недоступна",
|
||||
"downloader_not_available": "Недоступно",
|
||||
"recommended": "Рекомендуется",
|
||||
"go_to_settings": "Перейти в настройки",
|
||||
"select_executable": "Выбрать",
|
||||
"no_executable_selected": "Файл не выбран",
|
||||
"open_folder": "Открыть папку",
|
||||
@@ -651,6 +657,7 @@
|
||||
"sending": "Отправка",
|
||||
"friend_request_sent": "Запрос в друзья отправлен",
|
||||
"friends": "Друзья",
|
||||
"badges": "Значки",
|
||||
"friends_list": "Список друзей",
|
||||
"user_not_found": "Пользователь не найден",
|
||||
"block_user": "Заблокировать пользователя",
|
||||
@@ -661,12 +668,16 @@
|
||||
"ignore_request": "Игнорировать запрос",
|
||||
"cancel_request": "Отменить запрос",
|
||||
"undo_friendship": "Удалить друга",
|
||||
"friendship_removed": "Друг удален",
|
||||
"request_accepted": "Запрос принят",
|
||||
"user_blocked_successfully": "Пользователь успешно заблокирован",
|
||||
"user_block_modal_text": "{{displayName}} будет заблокирован",
|
||||
"blocked_users": "Заблокированные пользователи",
|
||||
"unblock": "Разблокировать",
|
||||
"no_friends_added": "Вы ещё не добавили ни одного друга",
|
||||
"view_all": "Показать все",
|
||||
"load_more": "Загрузить еще",
|
||||
"loading": "Загрузка",
|
||||
"pending": "Ожидание",
|
||||
"no_pending_invites": "У вас нет запросов ожидающих ответа",
|
||||
"no_blocked_users": "Вы не заблокировали ни одного пользователя",
|
||||
@@ -690,6 +701,7 @@
|
||||
"report_reason_other": "Другое",
|
||||
"profile_reported": "Жалоба на профиль отправлена",
|
||||
"your_friend_code": "Код вашего друга:",
|
||||
"copy_friend_code": "Копировать код друга",
|
||||
"upload_banner": "Загрузить баннер",
|
||||
"uploading_banner": "Загрузка баннера...",
|
||||
"background_image_updated": "Фоновое изображение обновлено",
|
||||
@@ -709,9 +721,11 @@
|
||||
"game_added_to_pinned": "Игра добавлена в закрепленные",
|
||||
"karma": "Карма",
|
||||
"karma_count": "карма",
|
||||
"karma_description": "Заработана положительными оценками отзывов",
|
||||
"user_reviews": "Отзывы",
|
||||
"loading_reviews": "Загрузка отзывов...",
|
||||
"wrapped_2025": "Wrapped 2025",
|
||||
"view_my_wrapped_button": "Просмотреть мой Wrapped 2025",
|
||||
"view_wrapped_button": "Просмотреть Wrapped 2025 {{displayName}}",
|
||||
"no_reviews": "Пока нет отзывов",
|
||||
"delete_review": "Удалить отзыв"
|
||||
},
|
||||
@@ -764,5 +778,41 @@
|
||||
"all_games": "Все игры",
|
||||
"recently_played": "Недавно сыгранные",
|
||||
"favorites": "Избранное"
|
||||
},
|
||||
"notifications_page": {
|
||||
"title": "Уведомления",
|
||||
"mark_all_as_read": "Отметить все как прочитанные",
|
||||
"clear_all": "Очистить все",
|
||||
"loading": "Загрузка...",
|
||||
"empty_title": "Нет уведомлений",
|
||||
"empty_description": "Вы в курсе всех событий! Загляните позже за новыми обновлениями.",
|
||||
"empty_filter_description": "Нет уведомлений, соответствующих этому фильтру.",
|
||||
"filter_all": "Все",
|
||||
"filter_unread": "Непрочитанные",
|
||||
"filter_friends": "Друзья",
|
||||
"filter_badges": "Значки",
|
||||
"filter_upvotes": "Голоса",
|
||||
"filter_local": "Локальные",
|
||||
"load_more": "Загрузить еще",
|
||||
"dismiss": "Отклонить",
|
||||
"accept": "Принять",
|
||||
"refuse": "Отклонить",
|
||||
"notification": "Уведомление",
|
||||
"friend_request_received_title": "Новый запрос в друзья!",
|
||||
"friend_request_received_description": "{{displayName}} хочет добавить вас в друзья",
|
||||
"friend_request_accepted_title": "Запрос в друзья принят!",
|
||||
"friend_request_accepted_description": "{{displayName}} принял ваш запрос в друзья",
|
||||
"badge_received_title": "Вы получили новый значок!",
|
||||
"badge_received_description": "{{badgeName}}",
|
||||
"review_upvote_title": "Ваш отзыв на {{gameTitle}} получил голоса!",
|
||||
"review_upvote_description": "Ваш отзыв получил {{count}} новых голосов",
|
||||
"marked_all_as_read": "Все уведомления отмечены как прочитанные",
|
||||
"failed_to_mark_as_read": "Не удалось отметить уведомления как прочитанные",
|
||||
"cleared_all": "Все уведомления очищены",
|
||||
"failed_to_clear": "Не удалось очистить уведомления",
|
||||
"failed_to_load": "Не удалось загрузить уведомления",
|
||||
"failed_to_dismiss": "Не удалось отклонить уведомление",
|
||||
"friend_request_accepted": "Запрос в друзья принят",
|
||||
"friend_request_refused": "Запрос в друзья отклонен"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -706,7 +706,6 @@
|
||||
"game_added_to_pinned": "Oyun sabitlenmişlere eklendi",
|
||||
"karma": "Karma",
|
||||
"karma_count": "karma",
|
||||
"karma_description": "İncelemelerdeki olumlu beğenilerden kazanılır",
|
||||
"user_reviews": "İncelemeler",
|
||||
"delete_review": "İncelemeyi Sil",
|
||||
"loading_reviews": "İncelemeler yükleniyor..."
|
||||
|
||||
@@ -668,8 +668,7 @@
|
||||
"game_removed_from_pinned": "Гру видалено із закріплених",
|
||||
"game_added_to_pinned": "Гру додано до закріплених",
|
||||
"karma": "Карма",
|
||||
"karma_count": "карма",
|
||||
"karma_description": "Зароблена позитивними оцінками на відгуках"
|
||||
"karma_count": "карма"
|
||||
},
|
||||
"achievement": {
|
||||
"achievement_unlocked": "Досягнення розблоковано",
|
||||
|
||||
@@ -689,7 +689,6 @@
|
||||
"game_removed_from_pinned": "游戏已从置顶移除",
|
||||
"karma": "业力",
|
||||
"karma_count": "业力值",
|
||||
"karma_description": "通过评论获得的点赞",
|
||||
"loading_reviews": "正在加载评价...",
|
||||
"manual_playtime_tooltip": "该游戏时长已手动更新",
|
||||
"pinned": "已置顶",
|
||||
|
||||
@@ -46,7 +46,7 @@ export class SevenZip {
|
||||
onProgress?: (progress: ExtractionProgress) => void
|
||||
): Promise<ExtractionResult> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const tryPassword = (index = -1) => {
|
||||
const tryPassword = (index = 0) => {
|
||||
const password = passwords[index] ?? "";
|
||||
logger.info(
|
||||
`Trying password "${password || "(empty)"}" on ${filePath}`
|
||||
@@ -115,7 +115,7 @@ export class SevenZip {
|
||||
});
|
||||
};
|
||||
|
||||
tryPassword();
|
||||
tryPassword(0);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
DatanodesApi,
|
||||
MediafireApi,
|
||||
PixelDrainApi,
|
||||
VikingFileApi,
|
||||
} from "../hosters";
|
||||
import { PythonRPC } from "../python-rpc";
|
||||
import {
|
||||
@@ -150,14 +151,28 @@ export class DownloadManager {
|
||||
if (!isDownloadingMetadata && !isCheckingFiles) {
|
||||
if (!download) return null;
|
||||
|
||||
await downloadsSublevel.put(downloadId, {
|
||||
const updatedDownload = {
|
||||
...download,
|
||||
bytesDownloaded,
|
||||
fileSize,
|
||||
progress,
|
||||
folderName,
|
||||
status: "active",
|
||||
});
|
||||
status: "active" as const,
|
||||
};
|
||||
|
||||
await downloadsSublevel.put(downloadId, updatedDownload);
|
||||
|
||||
return {
|
||||
numPeers,
|
||||
numSeeds,
|
||||
downloadSpeed,
|
||||
timeRemaining: calculateETA(fileSize, bytesDownloaded, downloadSpeed),
|
||||
isDownloadingMetadata,
|
||||
isCheckingFiles,
|
||||
progress,
|
||||
gameId: downloadId,
|
||||
download: updatedDownload,
|
||||
} as DownloadProgress;
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -499,6 +514,29 @@ export class DownloadManager {
|
||||
allow_multiple_connections: true,
|
||||
};
|
||||
}
|
||||
case Downloader.VikingFile: {
|
||||
logger.log(
|
||||
`[DownloadManager] Processing VikingFile download for URI: ${download.uri}`
|
||||
);
|
||||
try {
|
||||
const downloadUrl = await VikingFileApi.getDownloadUrl(download.uri);
|
||||
logger.log(`[DownloadManager] VikingFile direct URL obtained`);
|
||||
return {
|
||||
action: "start",
|
||||
game_id: downloadId,
|
||||
url: downloadUrl,
|
||||
save_path: download.downloadPath,
|
||||
header:
|
||||
"User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36",
|
||||
};
|
||||
} catch (error) {
|
||||
logger.error(
|
||||
`[DownloadManager] Error processing VikingFile download:`,
|
||||
error
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import axios from "axios";
|
||||
import http from "node:http";
|
||||
import https from "node:https";
|
||||
import {
|
||||
HOSTER_USER_AGENT,
|
||||
extractHosterFilename,
|
||||
@@ -28,6 +30,12 @@ export class BuzzheavierApi {
|
||||
await axios.get(baseUrl, {
|
||||
headers: { "User-Agent": HOSTER_USER_AGENT },
|
||||
timeout: 30000,
|
||||
httpAgent: new http.Agent({
|
||||
family: 4, // Force IPv4
|
||||
}),
|
||||
httpsAgent: new https.Agent({
|
||||
family: 4, // Force IPv4
|
||||
}),
|
||||
});
|
||||
|
||||
const downloadUrl = `${baseUrl}/download`;
|
||||
@@ -43,6 +51,12 @@ export class BuzzheavierApi {
|
||||
validateStatus: (status) =>
|
||||
status === 200 || status === 204 || status === 301 || status === 302,
|
||||
timeout: 30000,
|
||||
httpAgent: new http.Agent({
|
||||
family: 4, // Force IPv4
|
||||
}),
|
||||
httpsAgent: new https.Agent({
|
||||
family: 4, // Force IPv4
|
||||
}),
|
||||
});
|
||||
|
||||
const hxRedirect = headResponse.headers["hx-redirect"];
|
||||
|
||||
@@ -5,3 +5,4 @@ export * from "./mediafire";
|
||||
export * from "./pixeldrain";
|
||||
export * from "./buzzheavier";
|
||||
export * from "./fuckingfast";
|
||||
export * from "./vikingfile";
|
||||
|
||||
46
src/main/services/hosters/vikingfile.ts
Normal file
46
src/main/services/hosters/vikingfile.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import axios from "axios";
|
||||
import { logger } from "../logger";
|
||||
|
||||
interface UnlockResponse {
|
||||
link: string;
|
||||
hoster: string;
|
||||
}
|
||||
|
||||
export class VikingFileApi {
|
||||
public static async getDownloadUrl(uri: string): Promise<string> {
|
||||
const unlockResponse = await axios.post<UnlockResponse>(
|
||||
`${import.meta.env.MAIN_VITE_NIMBUS_API_URL}/hosters/unlock`,
|
||||
{ url: uri }
|
||||
);
|
||||
|
||||
if (!unlockResponse.data.link) {
|
||||
throw new Error("Failed to unlock VikingFile URL");
|
||||
}
|
||||
|
||||
const redirectUrl = unlockResponse.data.link;
|
||||
|
||||
try {
|
||||
const redirectResponse = await axios.head(redirectUrl, {
|
||||
maxRedirects: 0,
|
||||
validateStatus: (status) =>
|
||||
status === 301 || status === 302 || status === 200,
|
||||
});
|
||||
|
||||
if (
|
||||
redirectResponse.headers.location ||
|
||||
redirectResponse.status === 301 ||
|
||||
redirectResponse.status === 302
|
||||
) {
|
||||
return redirectResponse.headers.location || redirectUrl;
|
||||
}
|
||||
|
||||
return redirectUrl;
|
||||
} catch (error) {
|
||||
logger.error(
|
||||
`[VikingFile] Error following redirect, using redirect URL:`,
|
||||
error
|
||||
);
|
||||
return redirectUrl;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import axios from "axios";
|
||||
import http from "node:http";
|
||||
|
||||
import cp from "node:child_process";
|
||||
import fs from "node:fs";
|
||||
@@ -31,6 +32,9 @@ export class PythonRPC {
|
||||
public static readonly RPC_PORT = "8084";
|
||||
public static readonly rpc = axios.create({
|
||||
baseURL: `http://localhost:${this.RPC_PORT}`,
|
||||
httpAgent: new http.Agent({
|
||||
family: 4, // Force IPv4
|
||||
}),
|
||||
});
|
||||
|
||||
private static pythonProcess: cp.ChildProcess | null = null;
|
||||
|
||||
1
src/main/vite-env.d.ts
vendored
1
src/main/vite-env.d.ts
vendored
@@ -7,6 +7,7 @@ interface ImportMetaEnv {
|
||||
readonly MAIN_VITE_CHECKOUT_URL: string;
|
||||
readonly MAIN_VITE_EXTERNAL_RESOURCES_URL: string;
|
||||
readonly MAIN_VITE_WS_URL: string;
|
||||
readonly MAIN_VITE_NIMBUS_API_URL: string;
|
||||
readonly MAIN_VITE_LAUNCHER_SUBDOMAIN: string;
|
||||
readonly ELECTRON_RENDERER_URL: string;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { Sidebar, BottomPanel, Header, Toast } from "@renderer/components";
|
||||
|
||||
import { WorkWondersSdk } from "workwonders-sdk";
|
||||
import {
|
||||
useAppDispatch,
|
||||
useAppSelector,
|
||||
@@ -52,6 +52,8 @@ export function App() {
|
||||
|
||||
const { clearDownload, setLastPacket } = useDownload();
|
||||
|
||||
const workwondersRef = useRef<WorkWondersSdk | null>(null);
|
||||
|
||||
const {
|
||||
hasActiveSubscription,
|
||||
fetchUserDetails,
|
||||
@@ -114,7 +116,30 @@ export function App() {
|
||||
return () => unsubscribe();
|
||||
}, [updateLibrary]);
|
||||
|
||||
useEffect(() => {
|
||||
const setupWorkWonders = useCallback(
|
||||
async (token?: string, locale?: string) => {
|
||||
if (workwondersRef.current) return;
|
||||
|
||||
const possibleLocales = ["en", "pt", "ru"];
|
||||
|
||||
const parsedLocale =
|
||||
possibleLocales.find((l) => l === locale?.slice(0, 2)) ?? "en";
|
||||
|
||||
workwondersRef.current = new WorkWondersSdk();
|
||||
await workwondersRef.current.init({
|
||||
organization: "hydra",
|
||||
token,
|
||||
locale: parsedLocale,
|
||||
});
|
||||
|
||||
await workwondersRef.current.initChangelogWidget();
|
||||
workwondersRef.current.initChangelogWidgetMini();
|
||||
workwondersRef.current.initFeedbackWidget();
|
||||
},
|
||||
[workwondersRef]
|
||||
);
|
||||
|
||||
const setupExternalResources = useCallback(async () => {
|
||||
const cachedUserDetails = window.localStorage.getItem("userDetails");
|
||||
|
||||
if (cachedUserDetails) {
|
||||
@@ -125,21 +150,26 @@ export function App() {
|
||||
dispatch(setProfileBackground(profileBackground));
|
||||
}
|
||||
|
||||
fetchUserDetails()
|
||||
.then((response) => {
|
||||
if (response) {
|
||||
updateUserDetails(response);
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
if (document.getElementById("external-resources")) return;
|
||||
const userPreferences = await window.electron.getUserPreferences();
|
||||
const userDetails = await fetchUserDetails().catch(() => null);
|
||||
|
||||
const $script = document.createElement("script");
|
||||
$script.id = "external-resources";
|
||||
$script.src = `${import.meta.env.RENDERER_VITE_EXTERNAL_RESOURCES_URL}/bundle.js?t=${Date.now()}`;
|
||||
document.head.appendChild($script);
|
||||
});
|
||||
}, [fetchUserDetails, updateUserDetails, dispatch]);
|
||||
if (userDetails) {
|
||||
updateUserDetails(userDetails);
|
||||
}
|
||||
|
||||
setupWorkWonders(userDetails?.workwondersJwt, userPreferences?.language);
|
||||
|
||||
if (!document.getElementById("external-resources")) {
|
||||
const $script = document.createElement("script");
|
||||
$script.id = "external-resources";
|
||||
$script.src = `${import.meta.env.RENDERER_VITE_EXTERNAL_RESOURCES_URL}/bundle.js?t=${Date.now()}`;
|
||||
document.head.appendChild($script);
|
||||
}
|
||||
}, [fetchUserDetails, updateUserDetails, dispatch, setupWorkWonders]);
|
||||
|
||||
useEffect(() => {
|
||||
setupExternalResources();
|
||||
}, [setupExternalResources]);
|
||||
|
||||
const onSignIn = useCallback(() => {
|
||||
fetchUserDetails().then((response) => {
|
||||
@@ -203,6 +233,7 @@ export function App() {
|
||||
|
||||
useEffect(() => {
|
||||
if (contentRef.current) contentRef.current.scrollTop = 0;
|
||||
workwondersRef.current?.notifyUrlChange();
|
||||
}, [location.pathname, location.search]);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Downloader } from "@shared";
|
||||
|
||||
export const VERSION_CODENAME = "Supernova";
|
||||
export const VERSION_CODENAME = "Harbinger";
|
||||
|
||||
export const DOWNLOADER_NAME = {
|
||||
[Downloader.RealDebrid]: "Real-Debrid",
|
||||
@@ -14,6 +14,7 @@ export const DOWNLOADER_NAME = {
|
||||
[Downloader.FuckingFast]: "FuckingFast",
|
||||
[Downloader.TorBox]: "TorBox",
|
||||
[Downloader.Hydra]: "Nimbus",
|
||||
[Downloader.VikingFile]: "VikingFile",
|
||||
};
|
||||
|
||||
export const MAX_MINUTES_TO_SHOW_IN_PLAYTIME = 120;
|
||||
|
||||
@@ -59,6 +59,7 @@ export function useUserDetails() {
|
||||
username: userDetails?.username || "",
|
||||
subscription: userDetails?.subscription || null,
|
||||
featurebaseJwt: userDetails?.featurebaseJwt || "",
|
||||
workwondersJwt: userDetails?.workwondersJwt || "",
|
||||
karma: userDetails?.karma || 0,
|
||||
});
|
||||
},
|
||||
@@ -111,7 +112,7 @@ export function useUserDetails() {
|
||||
);
|
||||
|
||||
const undoFriendship = (userId: string) =>
|
||||
window.electron.hydraApi.delete(`/profile/friends/${userId}`);
|
||||
window.electron.hydraApi.delete(`/profile/friend-requests/${userId}`);
|
||||
|
||||
const blockUser = (userId: string) =>
|
||||
window.electron.hydraApi.post(`/users/${userId}/block`);
|
||||
|
||||
@@ -19,23 +19,173 @@
|
||||
color: globals.$body-color;
|
||||
}
|
||||
|
||||
&__downloaders {
|
||||
display: grid;
|
||||
gap: globals.$spacing-unit;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
&__downloaders-list-wrapper {
|
||||
border: 1px solid globals.$border-color;
|
||||
overflow: hidden;
|
||||
background-color: globals.$dark-background-color;
|
||||
}
|
||||
|
||||
&__downloader-option {
|
||||
position: relative;
|
||||
&__downloaders-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0;
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
padding: 0;
|
||||
|
||||
&:only-child {
|
||||
grid-column: 1 / -1;
|
||||
&::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
}
|
||||
|
||||
&__downloader-icon {
|
||||
position: absolute;
|
||||
left: calc(globals.$spacing-unit * 2);
|
||||
&__downloader-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: calc(globals.$spacing-unit * 1.5) calc(globals.$spacing-unit * 2);
|
||||
border: 1px solid transparent;
|
||||
border-bottom: 1px solid globals.$border-color;
|
||||
border-radius: 0;
|
||||
background-color: transparent;
|
||||
cursor: pointer;
|
||||
transition:
|
||||
background-color 0.15s ease,
|
||||
border-color 0.15s ease;
|
||||
color: globals.$body-color;
|
||||
font-size: 14px;
|
||||
text-align: left;
|
||||
height: 48px;
|
||||
box-sizing: border-box;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
&--selected {
|
||||
background-color: rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
|
||||
&--last {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
cursor: default;
|
||||
|
||||
&:hover {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.download-settings-modal__downloader-name {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.download-settings-modal__availability-indicator-wrapper {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__downloader-item-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
&__check-icon {
|
||||
color: white;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
&__check-icon-wrapper {
|
||||
margin-left: auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
&__recommendation-badge {
|
||||
margin-left: auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 20px;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
|
||||
.badge {
|
||||
padding: 2px 6px;
|
||||
font-size: 10px;
|
||||
line-height: 1.2;
|
||||
height: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
&__availability-indicator-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
&__availability-indicator {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
flex-shrink: 0;
|
||||
|
||||
&--available {
|
||||
background-color: #22c55e;
|
||||
box-shadow: 0 0 6px rgba(34, 197, 94, 0.5);
|
||||
}
|
||||
|
||||
&--unavailable {
|
||||
background-color: #ef4444;
|
||||
box-shadow: 0 0 6px rgba(239, 68, 68, 0.5);
|
||||
}
|
||||
|
||||
&--not-present {
|
||||
background-color: #6b7280;
|
||||
box-shadow: 0 0 6px rgba(107, 114, 128, 0.5);
|
||||
}
|
||||
|
||||
&--warning {
|
||||
background-color: #eab308;
|
||||
box-shadow: 0 0 6px rgba(234, 179, 8, 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%,
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
50% {
|
||||
opacity: 0.7;
|
||||
transform: scale(1.1);
|
||||
}
|
||||
}
|
||||
|
||||
&__availability-indicator--pulsating {
|
||||
animation: pulse 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
&__path-error {
|
||||
@@ -49,4 +199,17 @@
|
||||
&__change-path-button {
|
||||
align-self: flex-end;
|
||||
}
|
||||
|
||||
&__loading-spinner {
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,25 @@
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
import {
|
||||
Badge,
|
||||
Button,
|
||||
CheckboxField,
|
||||
Link,
|
||||
Modal,
|
||||
TextField,
|
||||
} from "@renderer/components";
|
||||
import { CheckCircleFillIcon, DownloadIcon } from "@primer/octicons-react";
|
||||
import { Downloader, formatBytes, getDownloadersForUris } from "@shared";
|
||||
import {
|
||||
DownloadIcon,
|
||||
SyncIcon,
|
||||
CheckCircleFillIcon,
|
||||
} from "@primer/octicons-react";
|
||||
import { Downloader, formatBytes, getDownloadersForUri } from "@shared";
|
||||
import type { GameRepack } from "@types";
|
||||
import { DOWNLOADER_NAME } from "@renderer/constants";
|
||||
import { useAppSelector, useFeature, useToast } from "@renderer/hooks";
|
||||
import { motion } from "framer-motion";
|
||||
import { Tooltip } from "react-tooltip";
|
||||
import { RealDebridInfoModal } from "./real-debrid-info-modal";
|
||||
import "./download-settings-modal.scss";
|
||||
|
||||
export interface DownloadSettingsModalProps {
|
||||
@@ -51,6 +59,7 @@ export function DownloadSettingsModal({
|
||||
const [hasWritePermission, setHasWritePermission] = useState<boolean | null>(
|
||||
null
|
||||
);
|
||||
const [showRealDebridModal, setShowRealDebridModal] = useState(false);
|
||||
|
||||
const { isFeatureEnabled, Feature } = useFeature();
|
||||
|
||||
@@ -78,18 +87,89 @@ export function DownloadSettingsModal({
|
||||
}
|
||||
}, [visible, checkFolderWritePermission, selectedPath]);
|
||||
|
||||
const downloaders = useMemo(() => {
|
||||
return getDownloadersForUris(repack?.uris ?? []);
|
||||
}, [repack?.uris]);
|
||||
const downloadOptions = useMemo(() => {
|
||||
const unavailableUrisSet = new Set(repack?.unavailableUris ?? []);
|
||||
|
||||
const downloaderMap = new Map<
|
||||
Downloader,
|
||||
{ hasAvailable: boolean; hasUnavailable: boolean }
|
||||
>();
|
||||
|
||||
if (repack) {
|
||||
for (const uri of repack.uris) {
|
||||
const uriDownloaders = getDownloadersForUri(uri);
|
||||
const isAvailable = !unavailableUrisSet.has(uri);
|
||||
|
||||
for (const downloader of uriDownloaders) {
|
||||
const existing = downloaderMap.get(downloader);
|
||||
if (existing) {
|
||||
existing.hasAvailable = existing.hasAvailable || isAvailable;
|
||||
existing.hasUnavailable = existing.hasUnavailable || !isAvailable;
|
||||
} else {
|
||||
downloaderMap.set(downloader, {
|
||||
hasAvailable: isAvailable,
|
||||
hasUnavailable: !isAvailable,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const allDownloaders = Object.values(Downloader).filter(
|
||||
(value) => typeof value === "number"
|
||||
) as Downloader[];
|
||||
|
||||
const getDownloaderPriority = (option: {
|
||||
isAvailable: boolean;
|
||||
canHandle: boolean;
|
||||
isAvailableButNotConfigured: boolean;
|
||||
}) => {
|
||||
if (option.isAvailable) return 0;
|
||||
if (option.canHandle && !option.isAvailableButNotConfigured) return 1;
|
||||
if (option.isAvailableButNotConfigured) return 2;
|
||||
return 3;
|
||||
};
|
||||
|
||||
return allDownloaders
|
||||
.filter((downloader) => downloader !== Downloader.Hydra) // Temporarily comment out Nimbus
|
||||
.map((downloader) => {
|
||||
const status = downloaderMap.get(downloader);
|
||||
const canHandle = status !== undefined;
|
||||
const isAvailable = status?.hasAvailable ?? false;
|
||||
|
||||
let isConfigured = true;
|
||||
if (downloader === Downloader.RealDebrid) {
|
||||
isConfigured = !!userPreferences?.realDebridApiToken;
|
||||
} else if (downloader === Downloader.TorBox) {
|
||||
isConfigured = !!userPreferences?.torBoxApiToken;
|
||||
}
|
||||
// } else if (downloader === Downloader.Hydra) {
|
||||
// isConfigured = isFeatureEnabled(Feature.Nimbus);
|
||||
// }
|
||||
|
||||
const isAvailableButNotConfigured =
|
||||
isAvailable && !isConfigured && canHandle;
|
||||
|
||||
return {
|
||||
downloader,
|
||||
isAvailable: isAvailable && isConfigured,
|
||||
canHandle,
|
||||
isAvailableButNotConfigured,
|
||||
};
|
||||
})
|
||||
.sort((a, b) => getDownloaderPriority(a) - getDownloaderPriority(b));
|
||||
}, [
|
||||
repack,
|
||||
userPreferences?.realDebridApiToken,
|
||||
userPreferences?.torBoxApiToken,
|
||||
isFeatureEnabled,
|
||||
Feature,
|
||||
]);
|
||||
|
||||
const getDefaultDownloader = useCallback(
|
||||
(availableDownloaders: Downloader[]) => {
|
||||
if (availableDownloaders.length === 0) return null;
|
||||
|
||||
if (availableDownloaders.includes(Downloader.Hydra)) {
|
||||
return Downloader.Hydra;
|
||||
}
|
||||
|
||||
if (availableDownloaders.includes(Downloader.RealDebrid)) {
|
||||
return Downloader.RealDebrid;
|
||||
}
|
||||
@@ -112,26 +192,12 @@ export function DownloadSettingsModal({
|
||||
.then((defaultDownloadsPath) => setSelectedPath(defaultDownloadsPath));
|
||||
}
|
||||
|
||||
const filteredDownloaders = downloaders.filter((downloader) => {
|
||||
if (downloader === Downloader.RealDebrid)
|
||||
return userPreferences?.realDebridApiToken;
|
||||
if (downloader === Downloader.TorBox)
|
||||
return userPreferences?.torBoxApiToken;
|
||||
if (downloader === Downloader.Hydra)
|
||||
return isFeatureEnabled(Feature.Nimbus);
|
||||
return true;
|
||||
});
|
||||
const availableDownloaders = downloadOptions
|
||||
.filter((option) => option.isAvailable)
|
||||
.map((option) => option.downloader);
|
||||
|
||||
setSelectedDownloader(getDefaultDownloader(filteredDownloaders));
|
||||
}, [
|
||||
Feature,
|
||||
isFeatureEnabled,
|
||||
getDefaultDownloader,
|
||||
userPreferences?.downloadsPath,
|
||||
downloaders,
|
||||
userPreferences?.realDebridApiToken,
|
||||
userPreferences?.torBoxApiToken,
|
||||
]);
|
||||
setSelectedDownloader(getDefaultDownloader(availableDownloaders));
|
||||
}, [getDefaultDownloader, userPreferences?.downloadsPath, downloadOptions]);
|
||||
|
||||
const handleChooseDownloadsPath = async () => {
|
||||
const { filePaths } = await window.electron.showOpenDialog({
|
||||
@@ -186,33 +252,144 @@ export function DownloadSettingsModal({
|
||||
<div className="download-settings-modal__downloads-path-field">
|
||||
<span>{t("downloader")}</span>
|
||||
|
||||
<div className="download-settings-modal__downloaders">
|
||||
{downloaders.map((downloader) => {
|
||||
const shouldDisableButton =
|
||||
(downloader === Downloader.RealDebrid &&
|
||||
!userPreferences?.realDebridApiToken) ||
|
||||
(downloader === Downloader.TorBox &&
|
||||
!userPreferences?.torBoxApiToken) ||
|
||||
(downloader === Downloader.Hydra &&
|
||||
!isFeatureEnabled(Feature.Nimbus));
|
||||
<div className="download-settings-modal__downloaders-list-wrapper">
|
||||
<div className="download-settings-modal__downloaders-list">
|
||||
{downloadOptions.map((option, index) => {
|
||||
const isSelected = selectedDownloader === option.downloader;
|
||||
const tooltipId = `availability-indicator-${option.downloader}`;
|
||||
const isLastItem = index === downloadOptions.length - 1;
|
||||
|
||||
return (
|
||||
<Button
|
||||
key={downloader}
|
||||
className="download-settings-modal__downloader-option"
|
||||
theme={
|
||||
selectedDownloader === downloader ? "primary" : "outline"
|
||||
const Indicator = option.isAvailable ? motion.span : "span";
|
||||
|
||||
const isDisabled =
|
||||
!option.canHandle ||
|
||||
(!option.isAvailable && !option.isAvailableButNotConfigured);
|
||||
|
||||
const getAvailabilityIndicator = () => {
|
||||
if (option.isAvailable) {
|
||||
return (
|
||||
<Indicator
|
||||
className={`download-settings-modal__availability-indicator download-settings-modal__availability-indicator--available download-settings-modal__availability-indicator--pulsating`}
|
||||
animate={{
|
||||
scale: [1, 1.1, 1],
|
||||
opacity: [1, 0.7, 1],
|
||||
}}
|
||||
transition={{
|
||||
duration: 2,
|
||||
repeat: Infinity,
|
||||
ease: "easeInOut",
|
||||
}}
|
||||
data-tooltip-id={tooltipId}
|
||||
data-tooltip-content={t("downloader_online")}
|
||||
/>
|
||||
);
|
||||
}
|
||||
disabled={shouldDisableButton}
|
||||
onClick={() => setSelectedDownloader(downloader)}
|
||||
>
|
||||
{selectedDownloader === downloader && (
|
||||
<CheckCircleFillIcon className="download-settings-modal__downloader-icon" />
|
||||
)}
|
||||
{DOWNLOADER_NAME[downloader]}
|
||||
</Button>
|
||||
);
|
||||
})}
|
||||
|
||||
if (option.isAvailableButNotConfigured) {
|
||||
return (
|
||||
<span
|
||||
className={`download-settings-modal__availability-indicator download-settings-modal__availability-indicator--warning`}
|
||||
data-tooltip-id={tooltipId}
|
||||
data-tooltip-content={t("downloader_not_configured")}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (option.canHandle) {
|
||||
return (
|
||||
<span
|
||||
className={`download-settings-modal__availability-indicator download-settings-modal__availability-indicator--unavailable`}
|
||||
data-tooltip-id={tooltipId}
|
||||
data-tooltip-content={t("downloader_offline")}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<span
|
||||
className={`download-settings-modal__availability-indicator download-settings-modal__availability-indicator--not-present`}
|
||||
data-tooltip-id={tooltipId}
|
||||
data-tooltip-content={t("downloader_not_available")}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const getRightContent = () => {
|
||||
if (isSelected) {
|
||||
return (
|
||||
<motion.div
|
||||
className="download-settings-modal__check-icon-wrapper"
|
||||
initial={{ scale: 0 }}
|
||||
animate={{ scale: 1 }}
|
||||
transition={{
|
||||
type: "spring",
|
||||
stiffness: 300,
|
||||
damping: 20,
|
||||
}}
|
||||
>
|
||||
<CheckCircleFillIcon
|
||||
size={16}
|
||||
className="download-settings-modal__check-icon"
|
||||
/>
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
option.downloader === Downloader.RealDebrid &&
|
||||
option.canHandle
|
||||
) {
|
||||
return (
|
||||
<div className="download-settings-modal__recommendation-badge">
|
||||
<Badge>{t("recommended")}</Badge>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
key={option.downloader}
|
||||
className="download-settings-modal__downloader-item-wrapper"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
className={`download-settings-modal__downloader-item ${
|
||||
isSelected
|
||||
? "download-settings-modal__downloader-item--selected"
|
||||
: ""
|
||||
} ${
|
||||
isLastItem
|
||||
? "download-settings-modal__downloader-item--last"
|
||||
: ""
|
||||
}`}
|
||||
disabled={isDisabled}
|
||||
onClick={() => {
|
||||
if (
|
||||
option.downloader === Downloader.RealDebrid &&
|
||||
option.isAvailableButNotConfigured
|
||||
) {
|
||||
setShowRealDebridModal(true);
|
||||
} else {
|
||||
setSelectedDownloader(option.downloader);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<span className="download-settings-modal__downloader-name">
|
||||
{DOWNLOADER_NAME[option.downloader]}
|
||||
</span>
|
||||
<div className="download-settings-modal__availability-indicator-wrapper">
|
||||
{getAvailabilityIndicator()}
|
||||
</div>
|
||||
<Tooltip id={tooltipId} />
|
||||
{getRightContent()}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -264,13 +441,34 @@ export function DownloadSettingsModal({
|
||||
disabled={
|
||||
downloadStarting ||
|
||||
selectedDownloader === null ||
|
||||
!hasWritePermission
|
||||
!hasWritePermission ||
|
||||
downloadOptions.some(
|
||||
(option) =>
|
||||
option.downloader === selectedDownloader &&
|
||||
(option.isAvailableButNotConfigured ||
|
||||
(!option.isAvailable && option.canHandle) ||
|
||||
!option.canHandle)
|
||||
)
|
||||
}
|
||||
>
|
||||
<DownloadIcon />
|
||||
{t("download_now")}
|
||||
{downloadStarting ? (
|
||||
<>
|
||||
<SyncIcon className="download-settings-modal__loading-spinner" />
|
||||
{t("loading")}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<DownloadIcon />
|
||||
{t("download_now")}
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<RealDebridInfoModal
|
||||
visible={showRealDebridModal}
|
||||
onClose={() => setShowRealDebridModal(false)}
|
||||
/>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
@use "../../../scss/globals.scss";
|
||||
|
||||
.real-debrid-info-modal {
|
||||
&__content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: calc(globals.$spacing-unit * 2.5);
|
||||
width: 100%;
|
||||
max-width: 500px;
|
||||
}
|
||||
|
||||
&__description-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: calc(globals.$spacing-unit * 1.5);
|
||||
}
|
||||
|
||||
&__description {
|
||||
margin: 0;
|
||||
color: globals.$body-color;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
&__create-account {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: calc(globals.$spacing-unit / 2);
|
||||
color: #c0c1c7;
|
||||
text-decoration: underline;
|
||||
font-size: 14px;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { Button, Link, Modal } from "@renderer/components";
|
||||
import { LinkExternalIcon } from "@primer/octicons-react";
|
||||
import "./real-debrid-info-modal.scss";
|
||||
|
||||
const realDebridReferralId = import.meta.env
|
||||
.RENDERER_VITE_REAL_DEBRID_REFERRAL_ID;
|
||||
|
||||
const REAL_DEBRID_URL = realDebridReferralId
|
||||
? `https://real-debrid.com/?id=${realDebridReferralId}`
|
||||
: "https://real-debrid.com";
|
||||
|
||||
export interface RealDebridInfoModalProps {
|
||||
visible: boolean;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export function RealDebridInfoModal({
|
||||
visible,
|
||||
onClose,
|
||||
}: Readonly<RealDebridInfoModalProps>) {
|
||||
const { t } = useTranslation("game_details");
|
||||
const { t: tSettings } = useTranslation("settings");
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (
|
||||
<Modal
|
||||
visible={visible}
|
||||
title={tSettings("enable_real_debrid")}
|
||||
onClose={onClose}
|
||||
>
|
||||
<div className="real-debrid-info-modal__content">
|
||||
<div className="real-debrid-info-modal__description-container">
|
||||
<p className="real-debrid-info-modal__description">
|
||||
{tSettings("real_debrid_description")}
|
||||
</p>
|
||||
<Link
|
||||
to={REAL_DEBRID_URL}
|
||||
className="real-debrid-info-modal__create-account"
|
||||
>
|
||||
<LinkExternalIcon />
|
||||
{tSettings("create_real_debrid_account")}
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
onClick={() => {
|
||||
onClose();
|
||||
navigate("/settings?tab=4");
|
||||
}}
|
||||
>
|
||||
{t("go_to_settings")}
|
||||
</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
@@ -100,8 +100,10 @@
|
||||
padding: calc(globals.$spacing-unit * 1.5);
|
||||
background-color: rgba(255, 255, 255, 0.05);
|
||||
border-radius: 8px;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
transition: all ease 0.2s;
|
||||
text-align: left;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
|
||||
@@ -4,6 +4,20 @@
|
||||
&__box {
|
||||
padding: calc(globals.$spacing-unit * 2);
|
||||
position: relative;
|
||||
|
||||
&--empty {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: calc(globals.$spacing-unit * 2);
|
||||
}
|
||||
}
|
||||
|
||||
&__empty-text {
|
||||
color: globals.$muted-color;
|
||||
font-size: globals.$small-font-size;
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&__add-friend-button {
|
||||
|
||||
@@ -19,6 +19,7 @@ export function FriendsBox() {
|
||||
const [showAddFriendModal, setShowAddFriendModal] = useState(false);
|
||||
|
||||
const isMe = userDetails?.id === userProfile?.id;
|
||||
const hasFriends = userProfile?.friends && userProfile.friends.length > 0;
|
||||
|
||||
const getGameImage = (game: { iconUrl: string | null; title: string }) => {
|
||||
if (game.iconUrl) {
|
||||
@@ -35,7 +36,15 @@ export function FriendsBox() {
|
||||
return <SteamLogo width={16} height={16} />;
|
||||
};
|
||||
|
||||
if (!userProfile?.friends.length) return null;
|
||||
if (!hasFriends) {
|
||||
if (!isMe) return null;
|
||||
|
||||
return (
|
||||
<div className="friends-box__box friends-box__box--empty">
|
||||
<p className="friends-box__empty-text">{t("no_friends_yet")}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const visibleFriends = userProfile.friends.slice(0, MAX_VISIBLE_FRIENDS);
|
||||
const totalFriends = userProfile.friends.length;
|
||||
|
||||
@@ -376,7 +376,7 @@ export function ProfileContent() {
|
||||
const hasAnyGames = hasGames || hasPinnedGames;
|
||||
|
||||
const shouldShowRightContent =
|
||||
hasAnyGames || userProfile.friends.length > 0;
|
||||
hasAnyGames || userProfile.friends.length > 0 || isMe;
|
||||
|
||||
return (
|
||||
<section className="profile-content__section">
|
||||
@@ -444,7 +444,7 @@ export function ProfileContent() {
|
||||
<RecentGamesBox />
|
||||
</ProfileSection>
|
||||
)}
|
||||
{userProfile?.friends.length > 0 && (
|
||||
{(userProfile?.friends.length > 0 || isMe) && (
|
||||
<ProfileSection
|
||||
title={t("friends")}
|
||||
count={userStats?.friendsCount || userProfile.friends.length}
|
||||
|
||||
@@ -123,11 +123,6 @@ export function UserStatsBox() {
|
||||
{t("karma_count")}
|
||||
</p>
|
||||
</div>
|
||||
<div className="user-stats__karma-info">
|
||||
<small className="user-stats__karma-info-text">
|
||||
{t("karma_description")}
|
||||
</small>
|
||||
</div>
|
||||
</li>
|
||||
)}
|
||||
</ul>
|
||||
|
||||
@@ -10,6 +10,7 @@ export enum Downloader {
|
||||
Hydra,
|
||||
Buzzheavier,
|
||||
FuckingFast,
|
||||
VikingFile,
|
||||
}
|
||||
|
||||
export enum DownloadSourceStatus {
|
||||
|
||||
@@ -124,6 +124,9 @@ export const getDownloadersForUri = (uri: string) => {
|
||||
if (uri.startsWith("https://fuckingfast.co")) {
|
||||
return [Downloader.FuckingFast];
|
||||
}
|
||||
if (uri.startsWith("https://vikingfile.com")) {
|
||||
return [Downloader.VikingFile];
|
||||
}
|
||||
|
||||
if (realDebridHosts.some((host) => uri.startsWith(host)))
|
||||
return [Downloader.RealDebrid];
|
||||
|
||||
@@ -20,6 +20,7 @@ export interface GameRepack {
|
||||
title: string;
|
||||
fileSize: string | null;
|
||||
uris: string[];
|
||||
unavailableUris: string[];
|
||||
uploadDate: string | null;
|
||||
downloadSourceId: string;
|
||||
downloadSourceName: string;
|
||||
@@ -187,6 +188,7 @@ export interface UserDetails {
|
||||
profileVisibility: ProfileVisibility;
|
||||
bio: string;
|
||||
featurebaseJwt: string;
|
||||
workwondersJwt: string;
|
||||
subscription: Subscription | null;
|
||||
karma: number;
|
||||
quirks?: {
|
||||
|
||||
75
yarn.lock
75
yarn.lock
@@ -2203,14 +2203,15 @@
|
||||
tslib "^2.6.2"
|
||||
|
||||
"@smithy/config-resolver@^4.3.0", "@smithy/config-resolver@^4.3.1":
|
||||
version "4.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@smithy/config-resolver/-/config-resolver-4.3.1.tgz#f1a0ed6faa52377909440002e1632be9fc901840"
|
||||
integrity sha512-tWDwrWy37CDVGeaP8AIGZPFL2RoFtmd5Y+nTzLw5qroXNedT2S66EY2d+XzB1zxulCd6nfDXnAQu4auq90aj5Q==
|
||||
version "4.4.5"
|
||||
resolved "https://registry.yarnpkg.com/@smithy/config-resolver/-/config-resolver-4.4.5.tgz#35e792b6db00887bdd029df9b41780ca005d064b"
|
||||
integrity sha512-HAGoUAFYsUkoSckuKbCPayECeMim8pOu+yLy1zOxt1sifzEbrsRpYa+mKcMdiHKMeiqOibyPG0sFJnmaV/OGEg==
|
||||
dependencies:
|
||||
"@smithy/node-config-provider" "^4.3.1"
|
||||
"@smithy/types" "^4.7.0"
|
||||
"@smithy/node-config-provider" "^4.3.7"
|
||||
"@smithy/types" "^4.11.0"
|
||||
"@smithy/util-config-provider" "^4.2.0"
|
||||
"@smithy/util-middleware" "^4.2.1"
|
||||
"@smithy/util-endpoints" "^3.2.7"
|
||||
"@smithy/util-middleware" "^4.2.7"
|
||||
tslib "^2.6.2"
|
||||
|
||||
"@smithy/core@^3.15.0", "@smithy/core@^3.16.0":
|
||||
@@ -2421,6 +2422,16 @@
|
||||
"@smithy/types" "^4.7.0"
|
||||
tslib "^2.6.2"
|
||||
|
||||
"@smithy/node-config-provider@^4.3.7":
|
||||
version "4.3.7"
|
||||
resolved "https://registry.yarnpkg.com/@smithy/node-config-provider/-/node-config-provider-4.3.7.tgz#c023fa857b008c314f621fb5b124724c157b2fd3"
|
||||
integrity sha512-7r58wq8sdOcrwWe+klL9y3bc4GW1gnlfnFOuL7CXa7UzfhzhxKuzNdtqgzmTV+53lEp9NXh5hY/S4UgjLOzPfw==
|
||||
dependencies:
|
||||
"@smithy/property-provider" "^4.2.7"
|
||||
"@smithy/shared-ini-file-loader" "^4.4.2"
|
||||
"@smithy/types" "^4.11.0"
|
||||
tslib "^2.6.2"
|
||||
|
||||
"@smithy/node-http-handler@^4.3.0", "@smithy/node-http-handler@^4.4.0":
|
||||
version "4.4.0"
|
||||
resolved "https://registry.yarnpkg.com/@smithy/node-http-handler/-/node-http-handler-4.4.0.tgz#e1f6ae4a90cd7257699263bf8e06e653ff0e5f83"
|
||||
@@ -2440,6 +2451,14 @@
|
||||
"@smithy/types" "^4.7.0"
|
||||
tslib "^2.6.2"
|
||||
|
||||
"@smithy/property-provider@^4.2.7":
|
||||
version "4.2.7"
|
||||
resolved "https://registry.yarnpkg.com/@smithy/property-provider/-/property-provider-4.2.7.tgz#cd0044e13495cf4064b3a6ed3299e5f549ba7513"
|
||||
integrity sha512-jmNYKe9MGGPoSl/D7JDDs1C8b3dC8f/w78LbaVfoTtWy4xAd5dfjaFG9c9PWPihY4ggMQNQSMtzU77CNgAJwmA==
|
||||
dependencies:
|
||||
"@smithy/types" "^4.11.0"
|
||||
tslib "^2.6.2"
|
||||
|
||||
"@smithy/protocol-http@^5.3.0", "@smithy/protocol-http@^5.3.1":
|
||||
version "5.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@smithy/protocol-http/-/protocol-http-5.3.1.tgz#add01f73290f1e8fd49d7102b63e3fe53a5e6e18"
|
||||
@@ -2480,6 +2499,14 @@
|
||||
"@smithy/types" "^4.7.0"
|
||||
tslib "^2.6.2"
|
||||
|
||||
"@smithy/shared-ini-file-loader@^4.4.2":
|
||||
version "4.4.2"
|
||||
resolved "https://registry.yarnpkg.com/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.4.2.tgz#8fa1b459de485b11185fe8c64182e3205a280ba9"
|
||||
integrity sha512-M7iUUff/KwfNunmrgtqBfvZSzh3bmFgv/j/t1Y1dQ+8dNo34br1cqVEqy6v0mYEgi0DkGO7Xig0AnuOaEGVlcg==
|
||||
dependencies:
|
||||
"@smithy/types" "^4.11.0"
|
||||
tslib "^2.6.2"
|
||||
|
||||
"@smithy/signature-v4@^5.3.0":
|
||||
version "5.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@smithy/signature-v4/-/signature-v4-5.3.1.tgz#c3d711c29d37f3db4daf51750eea75204c4f51d4"
|
||||
@@ -2507,6 +2534,13 @@
|
||||
"@smithy/util-stream" "^4.5.1"
|
||||
tslib "^2.6.2"
|
||||
|
||||
"@smithy/types@^4.11.0":
|
||||
version "4.11.0"
|
||||
resolved "https://registry.yarnpkg.com/@smithy/types/-/types-4.11.0.tgz#c02f6184dcb47c4f0b387a32a7eca47956cc09f1"
|
||||
integrity sha512-mlrmL0DRDVe3mNrjTcVcZEgkFmufITfUAPBEA+AHYiIeYyJebso/He1qLbP3PssRe22KUzLRpQSdBPbXdgZ2VA==
|
||||
dependencies:
|
||||
tslib "^2.6.2"
|
||||
|
||||
"@smithy/types@^4.6.0", "@smithy/types@^4.7.0":
|
||||
version "4.7.0"
|
||||
resolved "https://registry.yarnpkg.com/@smithy/types/-/types-4.7.0.tgz#42d707276d9184aef705f04e04615cd1979d044f"
|
||||
@@ -2601,6 +2635,15 @@
|
||||
"@smithy/types" "^4.7.0"
|
||||
tslib "^2.6.2"
|
||||
|
||||
"@smithy/util-endpoints@^3.2.7":
|
||||
version "3.2.7"
|
||||
resolved "https://registry.yarnpkg.com/@smithy/util-endpoints/-/util-endpoints-3.2.7.tgz#78cd5dd4aac8d9977f49d256d1e3418a09cade72"
|
||||
integrity sha512-s4ILhyAvVqhMDYREeTS68R43B1V5aenV5q/V1QpRQJkCXib5BPRo4s7uNdzGtIKxaPHCfU/8YkvPAEvTpxgspg==
|
||||
dependencies:
|
||||
"@smithy/node-config-provider" "^4.3.7"
|
||||
"@smithy/types" "^4.11.0"
|
||||
tslib "^2.6.2"
|
||||
|
||||
"@smithy/util-hex-encoding@^4.2.0":
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@smithy/util-hex-encoding/-/util-hex-encoding-4.2.0.tgz#1c22ea3d1e2c3a81ff81c0a4f9c056a175068a7b"
|
||||
@@ -2616,6 +2659,14 @@
|
||||
"@smithy/types" "^4.7.0"
|
||||
tslib "^2.6.2"
|
||||
|
||||
"@smithy/util-middleware@^4.2.7":
|
||||
version "4.2.7"
|
||||
resolved "https://registry.yarnpkg.com/@smithy/util-middleware/-/util-middleware-4.2.7.tgz#1cae2c4fd0389ac858d29f7170c33b4443e83524"
|
||||
integrity sha512-i1IkpbOae6NvIKsEeLLM9/2q4X+M90KV3oCFgWQI4q0Qz+yUZvsr+gZPdAEAtFhWQhAHpTsJO8DRJPuwVyln+w==
|
||||
dependencies:
|
||||
"@smithy/types" "^4.11.0"
|
||||
tslib "^2.6.2"
|
||||
|
||||
"@smithy/util-retry@^4.2.0", "@smithy/util-retry@^4.2.1":
|
||||
version "4.2.1"
|
||||
resolved "https://registry.yarnpkg.com/@smithy/util-retry/-/util-retry-4.2.1.tgz#8336368586a458cdce86fc92d6fb11fd1db41521"
|
||||
@@ -6354,6 +6405,11 @@ keyv@^4.0.0, keyv@^4.5.3:
|
||||
dependencies:
|
||||
json-buffer "3.0.1"
|
||||
|
||||
ky@^1.11.0:
|
||||
version "1.14.1"
|
||||
resolved "https://registry.yarnpkg.com/ky/-/ky-1.14.1.tgz#16f20b3bf3939abcc04e2a9613f47360fe5f64c9"
|
||||
integrity sha512-hYje4L9JCmpEQBtudo+v52X5X8tgWXUYyPcxKSuxQNboqufecl9VMWjGiucAFH060AwPXHZuH+WB2rrqfkmafw==
|
||||
|
||||
language-subtag-registry@^0.3.20:
|
||||
version "0.3.23"
|
||||
resolved "https://registry.yarnpkg.com/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz#23529e04d9e3b74679d70142df3fd2eb6ec572e7"
|
||||
@@ -9123,6 +9179,13 @@ word-wrap@^1.2.5:
|
||||
resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34"
|
||||
integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==
|
||||
|
||||
workwonders-sdk@0.0.10:
|
||||
version "0.0.10"
|
||||
resolved "https://registry.yarnpkg.com/workwonders-sdk/-/workwonders-sdk-0.0.10.tgz#377167370a39c905c5228f8972c37c19004b7b21"
|
||||
integrity sha512-bnswhlLRz1TCiqGV8l+VEOBej7u1SAkzLMEv6A60Sp0+S4j4pnmSve92KeOts/GYtUeNDuNM7fLPwZwMKY3sAg==
|
||||
dependencies:
|
||||
ky "^1.11.0"
|
||||
|
||||
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
|
||||
|
||||
Reference in New Issue
Block a user