mirror of
https://github.com/hydralauncher/hydra.git
synced 2025-02-09 03:37:45 +03:00
feat: updating real-debrid translations
This commit is contained in:
parent
183b85d66a
commit
08a336b392
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,6 +1,6 @@
|
|||||||
.vscode
|
.vscode
|
||||||
node_modules
|
node_modules
|
||||||
aria2*
|
aria2/
|
||||||
fastlist.exe
|
fastlist.exe
|
||||||
__pycache__
|
__pycache__
|
||||||
dist
|
dist
|
||||||
|
@ -134,7 +134,7 @@
|
|||||||
"delete_modal_title": "هل أنت متأكد؟",
|
"delete_modal_title": "هل أنت متأكد؟",
|
||||||
"delete_modal_description": "سيؤدي هذا إلى إزالة جميع ملفات التثبيت من جهاز الكمبيوتر الخاص بك",
|
"delete_modal_description": "سيؤدي هذا إلى إزالة جميع ملفات التثبيت من جهاز الكمبيوتر الخاص بك",
|
||||||
"install": "تثبيت",
|
"install": "تثبيت",
|
||||||
"real_debrid": "Real Debrid",
|
"real_debrid": "Real-Debrid",
|
||||||
"torrent": "تورنت"
|
"torrent": "تورنت"
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
@ -145,13 +145,13 @@
|
|||||||
"enable_repack_list_notifications": "عند إضافة حزمة جديدة",
|
"enable_repack_list_notifications": "عند إضافة حزمة جديدة",
|
||||||
"telemetry": "القياس عن بعد",
|
"telemetry": "القياس عن بعد",
|
||||||
"telemetry_description": "تفعيل إحصائيات الاستخدام مجهولة المصدر",
|
"telemetry_description": "تفعيل إحصائيات الاستخدام مجهولة المصدر",
|
||||||
"real_debrid_api_token_label": "رمز واجهة برمجة التطبيقات (API) لـReal Debrid ",
|
"real_debrid_api_token_label": "رمز واجهة برمجة التطبيقات (API) لـReal-Debrid ",
|
||||||
"quit_app_instead_hiding": "إنهاء هايدرا بدلاً من التصغير الى شريط الحالة",
|
"quit_app_instead_hiding": "إنهاء هايدرا بدلاً من التصغير الى شريط الحالة",
|
||||||
"launch_with_system": "تشغيل هايدرا عند بدء تشغيل النظام",
|
"launch_with_system": "تشغيل هايدرا عند بدء تشغيل النظام",
|
||||||
"general": "عام",
|
"general": "عام",
|
||||||
"behavior": "السلوك",
|
"behavior": "السلوك",
|
||||||
"enable_real_debrid": "تفعيل Real Debrid ",
|
"enable_real_debrid": "تفعيل Real-Debrid ",
|
||||||
"real_debrid": "Real Debrid",
|
"real_debrid": "Real-Debrid",
|
||||||
"real_debrid_api_token_hint": "يمكنك الحصول على مفتاح API الخاص بك هنا.",
|
"real_debrid_api_token_hint": "يمكنك الحصول على مفتاح API الخاص بك هنا.",
|
||||||
"save_changes": "حفظ التغييرات"
|
"save_changes": "حفظ التغييرات"
|
||||||
},
|
},
|
||||||
|
@ -128,7 +128,7 @@
|
|||||||
"delete_modal_title": "Er du sikker?",
|
"delete_modal_title": "Er du sikker?",
|
||||||
"delete_modal_description": "Dette vil fjerne alle installations filerne fra din computer",
|
"delete_modal_description": "Dette vil fjerne alle installations filerne fra din computer",
|
||||||
"install": "Installér",
|
"install": "Installér",
|
||||||
"real_debrid": "Real Debrid",
|
"real_debrid": "Real-Debrid",
|
||||||
"torrent": "Torrent"
|
"torrent": "Torrent"
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
@ -139,13 +139,13 @@
|
|||||||
"enable_repack_list_notifications": "Når en ny repack bliver tilføjet",
|
"enable_repack_list_notifications": "Når en ny repack bliver tilføjet",
|
||||||
"telemetry": "Telemetri",
|
"telemetry": "Telemetri",
|
||||||
"telemetry_description": "Slå anonymt brugs statistik til",
|
"telemetry_description": "Slå anonymt brugs statistik til",
|
||||||
"real_debrid_api_token_description": "Real Debrid API token",
|
"real_debrid_api_token_description": "Real-Debrid API token",
|
||||||
"quit_app_instead_hiding": "Afslut Hydra instedet for at minimere til processlinjen",
|
"quit_app_instead_hiding": "Afslut Hydra instedet for at minimere til processlinjen",
|
||||||
"launch_with_system": "Åben Hydra ved start af systemet",
|
"launch_with_system": "Åben Hydra ved start af systemet",
|
||||||
"general": "Generelt",
|
"general": "Generelt",
|
||||||
"behavior": "Opførsel",
|
"behavior": "Opførsel",
|
||||||
"enable_real_debrid": "Slå Real Debrid til",
|
"enable_real_debrid": "Slå Real-Debrid til",
|
||||||
"real_debrid": "Real Debrid",
|
"real_debrid": "Real-Debrid",
|
||||||
"real_debrid_api_token_hint": "Du kan få din API nøgle <0>her</0>.",
|
"real_debrid_api_token_hint": "Du kan få din API nøgle <0>her</0>.",
|
||||||
"save_changes": "Gem ændringer"
|
"save_changes": "Gem ændringer"
|
||||||
},
|
},
|
||||||
|
@ -133,7 +133,7 @@
|
|||||||
"delete_modal_title": "Are you sure?",
|
"delete_modal_title": "Are you sure?",
|
||||||
"delete_modal_description": "This will remove all the installation files from your computer",
|
"delete_modal_description": "This will remove all the installation files from your computer",
|
||||||
"install": "Install",
|
"install": "Install",
|
||||||
"real_debrid": "Real Debrid",
|
"real_debrid": "Real-Debrid",
|
||||||
"torrent": "Torrent"
|
"torrent": "Torrent"
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
@ -144,14 +144,15 @@
|
|||||||
"enable_repack_list_notifications": "When a new repack is added",
|
"enable_repack_list_notifications": "When a new repack is added",
|
||||||
"telemetry": "Telemetry",
|
"telemetry": "Telemetry",
|
||||||
"telemetry_description": "Enable anonymous usage statistics",
|
"telemetry_description": "Enable anonymous usage statistics",
|
||||||
"real_debrid_api_token_label": "Real Debrid API token",
|
"real_debrid_api_token_label": "Real-Debrid API token",
|
||||||
"quit_app_instead_hiding": "Quit Hydra instead of minimizing to tray",
|
"quit_app_instead_hiding": "Quit Hydra instead of minimizing to tray",
|
||||||
"launch_with_system": "Launch Hydra on system start-up",
|
"launch_with_system": "Launch Hydra on system start-up",
|
||||||
"general": "General",
|
"general": "General",
|
||||||
"behavior": "Behavior",
|
"behavior": "Behavior",
|
||||||
"enable_real_debrid": "Enable Real Debrid",
|
"enable_real_debrid": "Enable Real-Debrid",
|
||||||
"real_debrid": "Real Debrid",
|
"real_debrid": "Real-Debrid",
|
||||||
"real_debrid_api_token_hint": "You can get your API key <0>here</0>.",
|
"real_debrid_description": "Real-Debrid is an unrestricted downloader that allows you to download files instantly and at the best of your Internet speed.",
|
||||||
|
"real_debrid_api_token_hint": "You can get your API token <0>here</0>.",
|
||||||
"save_changes": "Save changes"
|
"save_changes": "Save changes"
|
||||||
},
|
},
|
||||||
"notifications": {
|
"notifications": {
|
||||||
|
@ -134,7 +134,7 @@
|
|||||||
"delete_modal_title": "¿Estás seguro?",
|
"delete_modal_title": "¿Estás seguro?",
|
||||||
"delete_modal_description": "Esto eliminará todos los archivos de instalación de tu computadora.",
|
"delete_modal_description": "Esto eliminará todos los archivos de instalación de tu computadora.",
|
||||||
"install": "Instalar",
|
"install": "Instalar",
|
||||||
"real_debrid": "Real Debrid",
|
"real_debrid": "Real-Debrid",
|
||||||
"torrent": "Torrent"
|
"torrent": "Torrent"
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
@ -145,13 +145,13 @@
|
|||||||
"enable_repack_list_notifications": "Cuando se añade un repack nuevo",
|
"enable_repack_list_notifications": "Cuando se añade un repack nuevo",
|
||||||
"telemetry": "Telemetría",
|
"telemetry": "Telemetría",
|
||||||
"telemetry_description": "Habilitar recopilación de datos de manera anónima",
|
"telemetry_description": "Habilitar recopilación de datos de manera anónima",
|
||||||
"real_debrid_api_token_label": "Token API de Real Debrid",
|
"real_debrid_api_token_label": "Token API de Real-Debrid",
|
||||||
"quit_app_instead_hiding": "Salir de Hydra en vez de minimizar en la bandeja del sistema",
|
"quit_app_instead_hiding": "Salir de Hydra en vez de minimizar en la bandeja del sistema",
|
||||||
"launch_with_system": "Iniciar Hydra al inicio del sistema",
|
"launch_with_system": "Iniciar Hydra al inicio del sistema",
|
||||||
"general": "General",
|
"general": "General",
|
||||||
"behavior": "Otros",
|
"behavior": "Otros",
|
||||||
"enable_real_debrid": "Activar Real Debrid",
|
"enable_real_debrid": "Activar Real-Debrid",
|
||||||
"real_debrid": "Real Debrid",
|
"real_debrid": "Real-Debrid",
|
||||||
"real_debrid_api_token_hint": "Puedes obtener tu clave de API <0>aquí</0>.",
|
"real_debrid_api_token_hint": "Puedes obtener tu clave de API <0>aquí</0>.",
|
||||||
"save_changes": "Guardar cambios"
|
"save_changes": "Guardar cambios"
|
||||||
},
|
},
|
||||||
|
@ -128,7 +128,7 @@
|
|||||||
"delete_modal_title": "مطمئنی؟",
|
"delete_modal_title": "مطمئنی؟",
|
||||||
"delete_modal_description": "این کار تمام فایلهای اینستالر را از کامپیوتر شما حذف میکند",
|
"delete_modal_description": "این کار تمام فایلهای اینستالر را از کامپیوتر شما حذف میکند",
|
||||||
"install": "نصف",
|
"install": "نصف",
|
||||||
"real_debrid": "Real Debrid",
|
"real_debrid": "Real-Debrid",
|
||||||
"torrent": "تورنت"
|
"torrent": "تورنت"
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
@ -139,13 +139,13 @@
|
|||||||
"enable_repack_list_notifications": "زمانی که یک ریپک جدید اضافه شد",
|
"enable_repack_list_notifications": "زمانی که یک ریپک جدید اضافه شد",
|
||||||
"telemetry": "تلمتری",
|
"telemetry": "تلمتری",
|
||||||
"telemetry_description": "فعال کردن آمارگیری استفاده ناشناس",
|
"telemetry_description": "فعال کردن آمارگیری استفاده ناشناس",
|
||||||
"real_debrid_api_token_description": "توکن Real Debrid",
|
"real_debrid_api_token_description": "توکن Real-Debrid",
|
||||||
"quit_app_instead_hiding": "به جای کوچک کردن، از هایدرا خارج شو",
|
"quit_app_instead_hiding": "به جای کوچک کردن، از هایدرا خارج شو",
|
||||||
"launch_with_system": "زمانی که سیستم روشن میشود، هایدرا را باز کن",
|
"launch_with_system": "زمانی که سیستم روشن میشود، هایدرا را باز کن",
|
||||||
"general": "کلی",
|
"general": "کلی",
|
||||||
"behavior": "رفتار",
|
"behavior": "رفتار",
|
||||||
"enable_real_debrid": "فعالسازی Real Debrid",
|
"enable_real_debrid": "فعالسازی Real-Debrid",
|
||||||
"real_debrid": "Real Debrid",
|
"real_debrid": "Real-Debrid",
|
||||||
"real_debrid_api_token_hint": "کلید API خود را از <ب0>اینجا</0> بگیرید.",
|
"real_debrid_api_token_hint": "کلید API خود را از <ب0>اینجا</0> بگیرید.",
|
||||||
"save_changes": "ذخیره تغییرات"
|
"save_changes": "ذخیره تغییرات"
|
||||||
},
|
},
|
||||||
|
@ -128,7 +128,7 @@
|
|||||||
"delete_modal_title": "정말로 하시겠습니까?",
|
"delete_modal_title": "정말로 하시겠습니까?",
|
||||||
"delete_modal_description": "이 기기의 모든 설치 파일들이 제거될 것입니다",
|
"delete_modal_description": "이 기기의 모든 설치 파일들이 제거될 것입니다",
|
||||||
"install": "설치",
|
"install": "설치",
|
||||||
"real_debrid": "Real Debrid",
|
"real_debrid": "Real-Debrid",
|
||||||
"torrent": "Torrent"
|
"torrent": "Torrent"
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
@ -139,13 +139,13 @@
|
|||||||
"enable_repack_list_notifications": "새 리팩이 추가되었을 때",
|
"enable_repack_list_notifications": "새 리팩이 추가되었을 때",
|
||||||
"telemetry": "자동 데이터 수집",
|
"telemetry": "자동 데이터 수집",
|
||||||
"telemetry_description": "익명 사용 통계를 활성화",
|
"telemetry_description": "익명 사용 통계를 활성화",
|
||||||
"real_debrid_api_token_description": "Real Debrid API 토큰",
|
"real_debrid_api_token_description": "Real-Debrid API 토큰",
|
||||||
"quit_app_instead_hiding": "작업 표시줄 트레이로 최소화하는 대신 Hydra를 종료",
|
"quit_app_instead_hiding": "작업 표시줄 트레이로 최소화하는 대신 Hydra를 종료",
|
||||||
"launch_with_system": "컴퓨터가 시작되었을 때 Hydra 실행",
|
"launch_with_system": "컴퓨터가 시작되었을 때 Hydra 실행",
|
||||||
"general": "일반",
|
"general": "일반",
|
||||||
"behavior": "행동",
|
"behavior": "행동",
|
||||||
"enable_real_debrid": "Real Debrid 활성화",
|
"enable_real_debrid": "Real-Debrid 활성화",
|
||||||
"real_debrid": "Real Debrid",
|
"real_debrid": "Real-Debrid",
|
||||||
"real_debrid_api_token_hint": "API 키를 <0>이곳</0>에서 얻으세요.",
|
"real_debrid_api_token_hint": "API 키를 <0>이곳</0>에서 얻으세요.",
|
||||||
"save_changes": "변경 사항 저장"
|
"save_changes": "변경 사항 저장"
|
||||||
},
|
},
|
||||||
|
@ -128,7 +128,7 @@
|
|||||||
"delete_modal_title": "Weet je het zeker?",
|
"delete_modal_title": "Weet je het zeker?",
|
||||||
"delete_modal_description": "Hiermee worden alle installatiebestanden van uw computer verwijderd",
|
"delete_modal_description": "Hiermee worden alle installatiebestanden van uw computer verwijderd",
|
||||||
"install": "Installeren",
|
"install": "Installeren",
|
||||||
"real_debrid": "Real Debrid",
|
"real_debrid": "Real-Debrid",
|
||||||
"torrent": "Torrent"
|
"torrent": "Torrent"
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
@ -139,13 +139,13 @@
|
|||||||
"enable_repack_list_notifications": "Wanneer een nieuwe herverpakking wordt toegevoegd",
|
"enable_repack_list_notifications": "Wanneer een nieuwe herverpakking wordt toegevoegd",
|
||||||
"telemetry": "Telemetrie",
|
"telemetry": "Telemetrie",
|
||||||
"telemetry_description": "Schakel anonieme gebruiksstatistieken in",
|
"telemetry_description": "Schakel anonieme gebruiksstatistieken in",
|
||||||
"real_debrid_api_token_label": "Real Debrid API token",
|
"real_debrid_api_token_label": "Real-Debrid API token",
|
||||||
"quit_app_instead_hiding": "Sluit Hydra af in plaats van te minimaliseren naar de lade",
|
"quit_app_instead_hiding": "Sluit Hydra af in plaats van te minimaliseren naar de lade",
|
||||||
"launch_with_system": "Start Hydra bij het opstarten van het systeem",
|
"launch_with_system": "Start Hydra bij het opstarten van het systeem",
|
||||||
"general": "Algemeen",
|
"general": "Algemeen",
|
||||||
"behavior": "Gedrag",
|
"behavior": "Gedrag",
|
||||||
"enable_real_debrid": "Enable Real Debrid",
|
"enable_real_debrid": "Enable Real-Debrid",
|
||||||
"real_debrid": "Real Debrid",
|
"real_debrid": "Real-Debrid",
|
||||||
"real_debrid_api_token_hint": "U kunt uw API-sleutel <0>hier</0> verkrijgen.",
|
"real_debrid_api_token_hint": "U kunt uw API-sleutel <0>hier</0> verkrijgen.",
|
||||||
"save_changes": "Wijzigingen opslaan"
|
"save_changes": "Wijzigingen opslaan"
|
||||||
},
|
},
|
||||||
|
@ -134,7 +134,7 @@
|
|||||||
"delete_modal_title": "Czy na pewno?",
|
"delete_modal_title": "Czy na pewno?",
|
||||||
"delete_modal_description": "Spowoduje to usunięcie wszystkich plików instalacyjnych z komputera",
|
"delete_modal_description": "Spowoduje to usunięcie wszystkich plików instalacyjnych z komputera",
|
||||||
"install": "Instaluj",
|
"install": "Instaluj",
|
||||||
"real_debrid": "Real Debrid",
|
"real_debrid": "Real-Debrid",
|
||||||
"torrent": "Torrent"
|
"torrent": "Torrent"
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
@ -145,13 +145,13 @@
|
|||||||
"enable_repack_list_notifications": "Gdy dodawany jest nowy repack",
|
"enable_repack_list_notifications": "Gdy dodawany jest nowy repack",
|
||||||
"telemetry": "Telemetria",
|
"telemetry": "Telemetria",
|
||||||
"telemetry_description": "Włącz anonimowe statystyki użycia",
|
"telemetry_description": "Włącz anonimowe statystyki użycia",
|
||||||
"real_debrid_api_token_label": "Real Debrid API token",
|
"real_debrid_api_token_label": "Real-Debrid API token",
|
||||||
"quit_app_instead_hiding": "Zamknij Hydr zamiast minimalizować do zasobnika",
|
"quit_app_instead_hiding": "Zamknij Hydr zamiast minimalizować do zasobnika",
|
||||||
"launch_with_system": "Uruchom Hydra przy starcie systemu",
|
"launch_with_system": "Uruchom Hydra przy starcie systemu",
|
||||||
"general": "Ogólne",
|
"general": "Ogólne",
|
||||||
"behavior": "Zachowania",
|
"behavior": "Zachowania",
|
||||||
"enable_real_debrid": "Włącz Real Debrid",
|
"enable_real_debrid": "Włącz Real-Debrid",
|
||||||
"real_debrid": "Real Debrid",
|
"real_debrid": "Real-Debrid",
|
||||||
"real_debrid_api_token_hint": "Możesz uzyskać swój klucz API <0>tutaj</0>.",
|
"real_debrid_api_token_hint": "Możesz uzyskać swój klucz API <0>tutaj</0>.",
|
||||||
"save_changes": "Zapisz zmiany"
|
"save_changes": "Zapisz zmiany"
|
||||||
},
|
},
|
||||||
|
@ -131,7 +131,7 @@
|
|||||||
"deleting": "Excluindo instalador…",
|
"deleting": "Excluindo instalador…",
|
||||||
"install": "Instalar",
|
"install": "Instalar",
|
||||||
"torrent": "Torrent",
|
"torrent": "Torrent",
|
||||||
"real_debrid": "Real Debrid"
|
"real_debrid": "Real-Debrid"
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"downloads_path": "Diretório dos downloads",
|
"downloads_path": "Diretório dos downloads",
|
||||||
@ -141,13 +141,13 @@
|
|||||||
"enable_repack_list_notifications": "Quando a lista de repacks for atualizada",
|
"enable_repack_list_notifications": "Quando a lista de repacks for atualizada",
|
||||||
"telemetry": "Telemetria",
|
"telemetry": "Telemetria",
|
||||||
"telemetry_description": "Habilitar estatísticas de uso anônimas",
|
"telemetry_description": "Habilitar estatísticas de uso anônimas",
|
||||||
"real_debrid_api_token_label": "Token de API do Real Debrid",
|
"real_debrid_api_token_label": "Token de API do Real-Debrid",
|
||||||
"quit_app_instead_hiding": "Fechar o aplicativo em vez de minimizá-lo",
|
"quit_app_instead_hiding": "Fechar o aplicativo em vez de minimizá-lo",
|
||||||
"launch_with_system": "Iniciar aplicativo na inicialização do sistema",
|
"launch_with_system": "Iniciar aplicativo na inicialização do sistema",
|
||||||
"general": "Geral",
|
"general": "Geral",
|
||||||
"behavior": "Comportamento",
|
"behavior": "Comportamento",
|
||||||
"enable_real_debrid": "Habilitar Real Debrid",
|
"enable_real_debrid": "Habilitar Real-Debrid",
|
||||||
"real_debrid": "Real Debrid",
|
"real_debrid": "Real-Debrid",
|
||||||
"real_debrid_api_token_hint": "Você pode obter sua chave de API <0>aqui</0>.",
|
"real_debrid_api_token_hint": "Você pode obter sua chave de API <0>aqui</0>.",
|
||||||
"save_changes": "Salvar mudanças"
|
"save_changes": "Salvar mudanças"
|
||||||
},
|
},
|
||||||
|
@ -134,7 +134,7 @@
|
|||||||
"delete_modal_title": "Вы уверены?",
|
"delete_modal_title": "Вы уверены?",
|
||||||
"delete_modal_description": "Это удалит все установщики с вашего компьютера",
|
"delete_modal_description": "Это удалит все установщики с вашего компьютера",
|
||||||
"install": "Установить",
|
"install": "Установить",
|
||||||
"real_debrid": "Real Debrid",
|
"real_debrid": "Real-Debrid",
|
||||||
"torrent": "Torrent"
|
"torrent": "Torrent"
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
@ -145,13 +145,13 @@
|
|||||||
"enable_repack_list_notifications": "При добавлении нового репака",
|
"enable_repack_list_notifications": "При добавлении нового репака",
|
||||||
"telemetry": "Телеметрия",
|
"telemetry": "Телеметрия",
|
||||||
"telemetry_description": "Отправлять анонимную статистику использования",
|
"telemetry_description": "Отправлять анонимную статистику использования",
|
||||||
"real_debrid_api_token_label": "Real Debrid API-токен",
|
"real_debrid_api_token_label": "Real-Debrid API-токен",
|
||||||
"quit_app_instead_hiding": "Закрывать Hydra вместо того, чтобы сворачивать его в трей",
|
"quit_app_instead_hiding": "Закрывать Hydra вместо того, чтобы сворачивать его в трей",
|
||||||
"launch_with_system": "Запуск Hydra вместе с системой",
|
"launch_with_system": "Запуск Hydra вместе с системой",
|
||||||
"general": "Основные",
|
"general": "Основные",
|
||||||
"behavior": "Поведение",
|
"behavior": "Поведение",
|
||||||
"enable_real_debrid": "Включить Real Debrid",
|
"enable_real_debrid": "Включить Real-Debrid",
|
||||||
"real_debrid": "Real Debrid",
|
"real_debrid": "Real-Debrid",
|
||||||
"real_debrid_api_token_hint": "API ключ можно получить <0>здесь</0>.",
|
"real_debrid_api_token_hint": "API ключ можно получить <0>здесь</0>.",
|
||||||
"save_changes": "Сохранить изменения"
|
"save_changes": "Сохранить изменения"
|
||||||
},
|
},
|
||||||
|
@ -132,7 +132,7 @@
|
|||||||
"delete_modal_title": "您确定吗?",
|
"delete_modal_title": "您确定吗?",
|
||||||
"delete_modal_description": "这将从您的电脑上移除所有的安装文件",
|
"delete_modal_description": "这将从您的电脑上移除所有的安装文件",
|
||||||
"install": "安装",
|
"install": "安装",
|
||||||
"real_debrid": "Real Debrid",
|
"real_debrid": "Real-Debrid",
|
||||||
"torrent": "种子"
|
"torrent": "种子"
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
@ -143,13 +143,13 @@
|
|||||||
"enable_repack_list_notifications": "添加新重打包时",
|
"enable_repack_list_notifications": "添加新重打包时",
|
||||||
"telemetry": "遥测",
|
"telemetry": "遥测",
|
||||||
"telemetry_description": "启用匿名使用统计",
|
"telemetry_description": "启用匿名使用统计",
|
||||||
"real_debrid_api_token_description": "Real Debrid API密钥",
|
"real_debrid_api_token_description": "Real-Debrid API密钥",
|
||||||
"behavior": "行为",
|
"behavior": "行为",
|
||||||
"general": "常规",
|
"general": "常规",
|
||||||
"quit_app_instead_hiding": "关闭应用程序而不是最小化到托盘",
|
"quit_app_instead_hiding": "关闭应用程序而不是最小化到托盘",
|
||||||
"launch_with_system": "随系统启动时运行应用程序",
|
"launch_with_system": "随系统启动时运行应用程序",
|
||||||
"enable_real_debrid": "启用 Real Debrid",
|
"enable_real_debrid": "启用 Real-Debrid",
|
||||||
"real_debrid": "Real Debrid",
|
"real_debrid": "Real-Debrid",
|
||||||
"real_debrid_api_token_hint": "您可以从<0>这里</0>获取API密钥.",
|
"real_debrid_api_token_hint": "您可以从<0>这里</0>获取API密钥.",
|
||||||
"save_changes": "保存更改"
|
"save_changes": "保存更改"
|
||||||
},
|
},
|
||||||
|
@ -29,7 +29,7 @@ const resumeGameDownload = async (
|
|||||||
.getRepository(Game)
|
.getRepository(Game)
|
||||||
.update({ status: "active", progress: Not(1) }, { status: "paused" });
|
.update({ status: "active", progress: Not(1) }, { status: "paused" });
|
||||||
|
|
||||||
await DownloadManager.resumeDownload(gameId);
|
await DownloadManager.resumeDownload(game);
|
||||||
|
|
||||||
await transactionalEntityManager
|
await transactionalEntityManager
|
||||||
.getRepository(Game)
|
.getRepository(Game)
|
||||||
|
@ -19,6 +19,7 @@ const startGameDownload = async (
|
|||||||
where: {
|
where: {
|
||||||
objectID,
|
objectID,
|
||||||
},
|
},
|
||||||
|
relations: { repack: true },
|
||||||
}),
|
}),
|
||||||
repackRepository.findOne({
|
repackRepository.findOne({
|
||||||
where: {
|
where: {
|
||||||
@ -50,9 +51,7 @@ const startGameDownload = async (
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
await DownloadManager.startDownload(game.id);
|
return DownloadManager.startDownload(game);
|
||||||
|
|
||||||
return { ...game, stauts: "active" };
|
|
||||||
} else {
|
} else {
|
||||||
const steamGame = stateManager
|
const steamGame = stateManager
|
||||||
.getValue("steamGames")
|
.getValue("steamGames")
|
||||||
@ -62,8 +61,8 @@ const startGameDownload = async (
|
|||||||
? getSteamAppAsset("icon", objectID, steamGame.clientIcon)
|
? getSteamAppAsset("icon", objectID, steamGame.clientIcon)
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
const createdGame = await gameRepository
|
await gameRepository
|
||||||
.save({
|
.insert({
|
||||||
title,
|
title,
|
||||||
iconUrl,
|
iconUrl,
|
||||||
objectID,
|
objectID,
|
||||||
@ -83,9 +82,14 @@ const startGameDownload = async (
|
|||||||
return result;
|
return result;
|
||||||
});
|
});
|
||||||
|
|
||||||
await DownloadManager.startDownload(createdGame.id);
|
const createdGame = await gameRepository.findOne({
|
||||||
|
where: {
|
||||||
|
objectID,
|
||||||
|
},
|
||||||
|
relations: { repack: true },
|
||||||
|
});
|
||||||
|
|
||||||
return createdGame;
|
return DownloadManager.startDownload(createdGame!);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -2,23 +2,17 @@ import { userPreferencesRepository } from "@main/repository";
|
|||||||
import { registerEvent } from "../register-event";
|
import { registerEvent } from "../register-event";
|
||||||
|
|
||||||
import type { UserPreferences } from "@types";
|
import type { UserPreferences } from "@types";
|
||||||
import { RealDebridClient } from "@main/services/real-debrid";
|
|
||||||
|
|
||||||
const updateUserPreferences = async (
|
const updateUserPreferences = async (
|
||||||
_event: Electron.IpcMainInvokeEvent,
|
_event: Electron.IpcMainInvokeEvent,
|
||||||
preferences: Partial<UserPreferences>
|
preferences: Partial<UserPreferences>
|
||||||
) => {
|
) =>
|
||||||
if (preferences.realDebridApiToken) {
|
userPreferencesRepository.upsert(
|
||||||
RealDebridClient.authorize(preferences.realDebridApiToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
await userPreferencesRepository.upsert(
|
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
...preferences,
|
...preferences,
|
||||||
},
|
},
|
||||||
["id"]
|
["id"]
|
||||||
);
|
);
|
||||||
};
|
|
||||||
|
|
||||||
registerEvent("updateUserPreferences", updateUserPreferences);
|
registerEvent("updateUserPreferences", updateUserPreferences);
|
||||||
|
@ -97,7 +97,7 @@ const loadState = async (userPreferences: UserPreferences | null) => {
|
|||||||
relations: { repack: true },
|
relations: { repack: true },
|
||||||
});
|
});
|
||||||
|
|
||||||
if (game) DownloadManager.startDownload(game.id);
|
if (game) DownloadManager.startDownload(game);
|
||||||
};
|
};
|
||||||
|
|
||||||
userPreferencesRepository
|
userPreferencesRepository
|
||||||
|
27
src/main/services/aria2.ts
Normal file
27
src/main/services/aria2.ts
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import path from "node:path";
|
||||||
|
import { spawn } from "node:child_process";
|
||||||
|
import type { ChildProcessWithoutNullStreams } from "node:child_process";
|
||||||
|
import { app } from "electron";
|
||||||
|
|
||||||
|
export const startAria2 = (): Promise<ChildProcessWithoutNullStreams> => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const binaryPath = app.isPackaged
|
||||||
|
? path.join(process.resourcesPath, "aria2", "aria2c")
|
||||||
|
: path.join(__dirname, "..", "..", "aria2", "aria2c");
|
||||||
|
|
||||||
|
const cp = spawn(binaryPath, [
|
||||||
|
"--enable-rpc",
|
||||||
|
"--rpc-listen-all",
|
||||||
|
"--file-allocation=none",
|
||||||
|
"--allow-overwrite=true",
|
||||||
|
]);
|
||||||
|
|
||||||
|
cp.stdout.on("data", async (data) => {
|
||||||
|
const msg = Buffer.from(data).toString("utf-8");
|
||||||
|
|
||||||
|
if (msg.includes("IPv6 RPC: listening on TCP")) {
|
||||||
|
resolve(cp);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
@ -1,57 +1,39 @@
|
|||||||
import Aria2, { StatusResponse } from "aria2";
|
import Aria2, { StatusResponse } from "aria2";
|
||||||
import { spawn } from "node:child_process";
|
|
||||||
|
|
||||||
import { gameRepository, userPreferencesRepository } from "@main/repository";
|
import { gameRepository, userPreferencesRepository } from "@main/repository";
|
||||||
|
|
||||||
import path from "node:path";
|
|
||||||
import { WindowManager } from "./window-manager";
|
import { WindowManager } from "./window-manager";
|
||||||
import { RealDebridClient } from "./real-debrid";
|
import { RealDebridClient } from "./real-debrid";
|
||||||
import { Notification, app } from "electron";
|
import { Notification } from "electron";
|
||||||
import { t } from "i18next";
|
import { t } from "i18next";
|
||||||
import { Downloader } from "@shared";
|
import { Downloader } from "@shared";
|
||||||
import { DownloadProgress } from "@types";
|
import { DownloadProgress } from "@types";
|
||||||
import { QueryDeepPartialEntity } from "typeorm/query-builder/QueryPartialEntity";
|
import { QueryDeepPartialEntity } from "typeorm/query-builder/QueryPartialEntity";
|
||||||
import { Game } from "@main/entity";
|
import { Game } from "@main/entity";
|
||||||
|
import { startAria2 } from "./aria2";
|
||||||
|
|
||||||
export class DownloadManager {
|
export class DownloadManager {
|
||||||
private static downloads = new Map<number, string>();
|
private static downloads = new Map<number, string>();
|
||||||
|
|
||||||
private static connected = false;
|
private static connected = false;
|
||||||
private static gid: string | null = null;
|
private static gid: string | null = null;
|
||||||
private static gameId: number | null = null;
|
private static game: Game | null = null;
|
||||||
|
private static realDebridTorrentId: string | null = null;
|
||||||
|
|
||||||
private static aria2 = new Aria2({});
|
private static aria2 = new Aria2({});
|
||||||
|
|
||||||
private static connect(): Promise<boolean> {
|
private static async connect() {
|
||||||
return new Promise((resolve) => {
|
await startAria2();
|
||||||
const binaryPath = app.isPackaged
|
|
||||||
? path.join(process.resourcesPath, "aria2", "aria2c")
|
|
||||||
: path.join(__dirname, "..", "..", "aria2", "aria2c");
|
|
||||||
|
|
||||||
const cp = spawn(binaryPath, [
|
|
||||||
"--enable-rpc",
|
|
||||||
"--rpc-listen-all",
|
|
||||||
"--file-allocation=none",
|
|
||||||
"--allow-overwrite=true",
|
|
||||||
]);
|
|
||||||
|
|
||||||
cp.stdout.on("data", async (data) => {
|
|
||||||
const msg = Buffer.from(data).toString("utf-8");
|
|
||||||
|
|
||||||
if (msg.includes("IPv6 RPC: listening on TCP")) {
|
|
||||||
await this.aria2.open();
|
await this.aria2.open();
|
||||||
this.connected = true;
|
this.connected = true;
|
||||||
|
|
||||||
resolve(true);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static getETA(status: StatusResponse) {
|
private static getETA(
|
||||||
const remainingBytes =
|
totalLength: number,
|
||||||
Number(status.totalLength) - Number(status.completedLength);
|
completedLength: number,
|
||||||
const speed = Number(status.downloadSpeed);
|
speed: number
|
||||||
|
) {
|
||||||
|
const remainingBytes = totalLength - completedLength;
|
||||||
|
|
||||||
if (remainingBytes >= 0 && speed > 0) {
|
if (remainingBytes >= 0 && speed > 0) {
|
||||||
return (remainingBytes / speed) * 1000;
|
return (remainingBytes / speed) * 1000;
|
||||||
@ -65,9 +47,7 @@ export class DownloadManager {
|
|||||||
where: { id: 1 },
|
where: { id: 1 },
|
||||||
});
|
});
|
||||||
|
|
||||||
if (userPreferences?.downloadNotificationsEnabled && this.gameId) {
|
if (userPreferences?.downloadNotificationsEnabled && this.game) {
|
||||||
const game = await this.getGame(this.gameId);
|
|
||||||
|
|
||||||
new Notification({
|
new Notification({
|
||||||
title: t("download_complete", {
|
title: t("download_complete", {
|
||||||
ns: "notifications",
|
ns: "notifications",
|
||||||
@ -76,7 +56,7 @@ export class DownloadManager {
|
|||||||
body: t("game_ready_to_install", {
|
body: t("game_ready_to_install", {
|
||||||
ns: "notifications",
|
ns: "notifications",
|
||||||
lng: userPreferences.language,
|
lng: userPreferences.language,
|
||||||
title: game?.title,
|
title: this.game.title,
|
||||||
}),
|
}),
|
||||||
}).show();
|
}).show();
|
||||||
}
|
}
|
||||||
@ -87,8 +67,73 @@ export class DownloadManager {
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static async getRealDebridDownloadUrl() {
|
||||||
|
if (this.realDebridTorrentId) {
|
||||||
|
const torrentInfo = await RealDebridClient.getTorrentInfo(
|
||||||
|
this.realDebridTorrentId
|
||||||
|
);
|
||||||
|
|
||||||
|
const { status, links } = torrentInfo;
|
||||||
|
|
||||||
|
if (status === "waiting_files_selection") {
|
||||||
|
await RealDebridClient.selectAllFiles(this.realDebridTorrentId);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status === "downloaded") {
|
||||||
|
const [link] = links;
|
||||||
|
const { download } = await RealDebridClient.unrestrictLink(link);
|
||||||
|
return decodeURIComponent(download);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (WindowManager.mainWindow) {
|
||||||
|
const progress = torrentInfo.progress / 100;
|
||||||
|
const totalDownloaded = progress * torrentInfo.bytes;
|
||||||
|
|
||||||
|
WindowManager.mainWindow.setProgressBar(progress === 1 ? -1 : progress);
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
numPeers: 0,
|
||||||
|
numSeeds: torrentInfo.seeders,
|
||||||
|
downloadSpeed: torrentInfo.speed,
|
||||||
|
timeRemaining: this.getETA(
|
||||||
|
torrentInfo.bytes,
|
||||||
|
totalDownloaded,
|
||||||
|
torrentInfo.speed
|
||||||
|
),
|
||||||
|
isDownloadingMetadata: status === "magnet_conversion",
|
||||||
|
game: {
|
||||||
|
...this.game,
|
||||||
|
bytesDownloaded: progress * torrentInfo.bytes,
|
||||||
|
progress,
|
||||||
|
},
|
||||||
|
} as DownloadProgress;
|
||||||
|
|
||||||
|
WindowManager.mainWindow.webContents.send(
|
||||||
|
"on-download-progress",
|
||||||
|
JSON.parse(JSON.stringify(payload))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
public static async watchDownloads() {
|
public static async watchDownloads() {
|
||||||
if (!this.gid || !this.gameId) return;
|
if (!this.game) return;
|
||||||
|
|
||||||
|
if (!this.gid && this.realDebridTorrentId) {
|
||||||
|
const options = { dir: this.game.downloadPath! };
|
||||||
|
const downloadUrl = await this.getRealDebridDownloadUrl();
|
||||||
|
|
||||||
|
if (downloadUrl) {
|
||||||
|
this.gid = await this.aria2.call("addUri", [downloadUrl], options);
|
||||||
|
this.downloads.set(this.game.id, this.gid);
|
||||||
|
this.realDebridTorrentId = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.gid) return;
|
||||||
|
|
||||||
const status = await this.aria2.call("tellStatus", this.gid);
|
const status = await this.aria2.call("tellStatus", this.gid);
|
||||||
|
|
||||||
@ -96,7 +141,7 @@ export class DownloadManager {
|
|||||||
|
|
||||||
if (status.followedBy?.length) {
|
if (status.followedBy?.length) {
|
||||||
this.gid = status.followedBy[0];
|
this.gid = status.followedBy[0];
|
||||||
this.downloads.set(this.gameId, this.gid);
|
this.downloads.set(this.game.id, this.gid);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -113,7 +158,7 @@ export class DownloadManager {
|
|||||||
if (!isNaN(progress)) update.progress = progress;
|
if (!isNaN(progress)) update.progress = progress;
|
||||||
|
|
||||||
await gameRepository.update(
|
await gameRepository.update(
|
||||||
{ id: this.gameId },
|
{ id: this.game.id },
|
||||||
{
|
{
|
||||||
...update,
|
...update,
|
||||||
status: status.status,
|
status: status.status,
|
||||||
@ -123,17 +168,18 @@ export class DownloadManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const game = await gameRepository.findOne({
|
const game = await gameRepository.findOne({
|
||||||
where: { id: this.gameId, isDeleted: false },
|
where: { id: this.game.id, isDeleted: false },
|
||||||
relations: { repack: true },
|
relations: { repack: true },
|
||||||
});
|
});
|
||||||
|
|
||||||
if (progress === 1 && game && !isDownloadingMetadata) {
|
if (progress === 1 && this.game && !isDownloadingMetadata) {
|
||||||
await this.publishNotification();
|
await this.publishNotification();
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Only cancel bittorrent downloads to stop seeding
|
Only cancel bittorrent downloads to stop seeding
|
||||||
*/
|
*/
|
||||||
if (status.bittorrent) {
|
if (status.bittorrent) {
|
||||||
await this.cancelDownload(game.id);
|
await this.cancelDownload(this.game.id);
|
||||||
} else {
|
} else {
|
||||||
this.clearCurrentDownload();
|
this.clearCurrentDownload();
|
||||||
}
|
}
|
||||||
@ -143,13 +189,14 @@ export class DownloadManager {
|
|||||||
WindowManager.mainWindow.setProgressBar(progress === 1 ? -1 : progress);
|
WindowManager.mainWindow.setProgressBar(progress === 1 ? -1 : progress);
|
||||||
|
|
||||||
const payload = {
|
const payload = {
|
||||||
progress,
|
|
||||||
bytesDownloaded: Number(status.completedLength),
|
|
||||||
fileSize: Number(status.totalLength),
|
|
||||||
numPeers: Number(status.connections),
|
numPeers: Number(status.connections),
|
||||||
numSeeds: Number(status.numSeeders ?? 0),
|
numSeeds: Number(status.numSeeders ?? 0),
|
||||||
downloadSpeed: Number(status.downloadSpeed),
|
downloadSpeed: Number(status.downloadSpeed),
|
||||||
timeRemaining: this.getETA(status),
|
timeRemaining: this.getETA(
|
||||||
|
Number(status.totalLength),
|
||||||
|
Number(status.completedLength),
|
||||||
|
Number(status.downloadSpeed)
|
||||||
|
),
|
||||||
isDownloadingMetadata: !!isDownloadingMetadata,
|
isDownloadingMetadata: !!isDownloadingMetadata,
|
||||||
game,
|
game,
|
||||||
} as DownloadProgress;
|
} as DownloadProgress;
|
||||||
@ -161,20 +208,12 @@ export class DownloadManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static async getGame(gameId: number) {
|
|
||||||
return gameRepository.findOne({
|
|
||||||
where: { id: gameId, isDeleted: false },
|
|
||||||
relations: {
|
|
||||||
repack: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private static clearCurrentDownload() {
|
private static clearCurrentDownload() {
|
||||||
if (this.gameId) {
|
if (this.game) {
|
||||||
this.downloads.delete(this.gameId);
|
this.downloads.delete(this.game.id);
|
||||||
this.gid = null;
|
this.gid = null;
|
||||||
this.gameId = null;
|
this.game = null;
|
||||||
|
this.realDebridTorrentId = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -198,50 +237,42 @@ export class DownloadManager {
|
|||||||
if (this.gid) {
|
if (this.gid) {
|
||||||
await this.aria2.call("forcePause", this.gid);
|
await this.aria2.call("forcePause", this.gid);
|
||||||
this.gid = null;
|
this.gid = null;
|
||||||
this.gameId = null;
|
this.game = null;
|
||||||
|
this.realDebridTorrentId = null;
|
||||||
|
|
||||||
WindowManager.mainWindow?.setProgressBar(-1);
|
WindowManager.mainWindow?.setProgressBar(-1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static async resumeDownload(gameId: number) {
|
static async resumeDownload(game: Game) {
|
||||||
if (this.downloads.has(gameId)) {
|
if (this.downloads.has(game.id)) {
|
||||||
const gid = this.downloads.get(gameId)!;
|
const gid = this.downloads.get(game.id)!;
|
||||||
await this.aria2.call("unpause", gid);
|
await this.aria2.call("unpause", gid);
|
||||||
|
|
||||||
this.gid = gid;
|
this.gid = gid;
|
||||||
this.gameId = gameId;
|
this.game = game;
|
||||||
} else {
|
} else {
|
||||||
return this.startDownload(gameId);
|
return this.startDownload(game);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static async startDownload(gameId: number) {
|
static async startDownload(game: Game) {
|
||||||
if (!this.connected) await this.connect();
|
if (!this.connected) await this.connect();
|
||||||
|
|
||||||
const game = await this.getGame(gameId)!;
|
|
||||||
|
|
||||||
if (game) {
|
|
||||||
const options = {
|
const options = {
|
||||||
dir: game.downloadPath!,
|
dir: game.downloadPath!,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (game.downloader === Downloader.RealDebrid) {
|
if (game.downloader === Downloader.RealDebrid) {
|
||||||
const downloadUrl = decodeURIComponent(
|
this.realDebridTorrentId = await RealDebridClient.getTorrentId(
|
||||||
await RealDebridClient.getDownloadUrl(game)
|
game!.repack.magnet
|
||||||
);
|
);
|
||||||
|
|
||||||
this.gid = await this.aria2.call("addUri", [downloadUrl], options);
|
|
||||||
} else {
|
} else {
|
||||||
this.gid = await this.aria2.call(
|
this.gid = await this.aria2.call("addUri", [game.repack.magnet], options);
|
||||||
"addUri",
|
|
||||||
[game.repack.magnet],
|
this.downloads.set(game.id, this.gid);
|
||||||
options
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.gameId = gameId;
|
this.game = game;
|
||||||
this.downloads.set(gameId, this.gid);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { Game } from "@main/entity";
|
|
||||||
import axios, { AxiosInstance } from "axios";
|
import axios, { AxiosInstance } from "axios";
|
||||||
|
import parseTorrent from "parse-torrent";
|
||||||
import type {
|
import type {
|
||||||
RealDebridAddMagnet,
|
RealDebridAddMagnet,
|
||||||
RealDebridTorrentInfo,
|
RealDebridTorrentInfo,
|
||||||
@ -7,10 +7,18 @@ import type {
|
|||||||
RealDebridUser,
|
RealDebridUser,
|
||||||
} from "@types";
|
} from "@types";
|
||||||
|
|
||||||
const base = "https://api.real-debrid.com/rest/1.0";
|
|
||||||
|
|
||||||
export class RealDebridClient {
|
export class RealDebridClient {
|
||||||
private static instance: AxiosInstance;
|
private static instance: AxiosInstance;
|
||||||
|
private static baseURL = "https://api.real-debrid.com/rest/1.0";
|
||||||
|
|
||||||
|
static authorize(apiToken: string) {
|
||||||
|
this.instance = axios.create({
|
||||||
|
baseURL: this.baseURL,
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${apiToken}`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
static async addMagnet(magnet: string) {
|
static async addMagnet(magnet: string) {
|
||||||
const searchParams = new URLSearchParams({ magnet });
|
const searchParams = new URLSearchParams({ magnet });
|
||||||
@ -23,7 +31,7 @@ export class RealDebridClient {
|
|||||||
return response.data;
|
return response.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
static async getInfo(id: string) {
|
static async getTorrentInfo(id: string) {
|
||||||
const response = await this.instance.get<RealDebridTorrentInfo>(
|
const response = await this.instance.get<RealDebridTorrentInfo>(
|
||||||
`/torrents/info/${id}`
|
`/torrents/info/${id}`
|
||||||
);
|
);
|
||||||
@ -55,50 +63,24 @@ export class RealDebridClient {
|
|||||||
return response.data;
|
return response.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
static async getAllTorrentsFromUser() {
|
private static async getAllTorrentsFromUser() {
|
||||||
const response =
|
const response =
|
||||||
await this.instance.get<RealDebridTorrentInfo[]>("/torrents");
|
await this.instance.get<RealDebridTorrentInfo[]>("/torrents");
|
||||||
|
|
||||||
return response.data;
|
return response.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
static extractSHA1FromMagnet(magnet: string) {
|
static async getTorrentId(magnetUri: string) {
|
||||||
return magnet.match(/btih:([0-9a-fA-F]*)/)?.[1].toLowerCase();
|
const userTorrents = await RealDebridClient.getAllTorrentsFromUser();
|
||||||
}
|
|
||||||
|
|
||||||
static async getDownloadUrl(game: Game) {
|
const { infoHash } = await parseTorrent(magnetUri);
|
||||||
const torrents = await RealDebridClient.getAllTorrentsFromUser();
|
const userTorrent = userTorrents.find(
|
||||||
const hash = RealDebridClient.extractSHA1FromMagnet(game!.repack.magnet);
|
(userTorrent) => userTorrent.hash === infoHash
|
||||||
let torrent = torrents.find((t) => t.hash === hash);
|
);
|
||||||
|
|
||||||
// User haven't downloaded this torrent yet
|
if (userTorrent) return userTorrent.id;
|
||||||
if (!torrent) {
|
|
||||||
const magnet = await RealDebridClient.addMagnet(game!.repack.magnet);
|
|
||||||
|
|
||||||
if (magnet) {
|
const torrent = await RealDebridClient.addMagnet(magnetUri);
|
||||||
await RealDebridClient.selectAllFiles(magnet.id);
|
return torrent.id;
|
||||||
torrent = await RealDebridClient.getInfo(magnet.id);
|
|
||||||
|
|
||||||
const { links } = torrent;
|
|
||||||
const { download } = await RealDebridClient.unrestrictLink(links[0]);
|
|
||||||
|
|
||||||
if (!download) {
|
|
||||||
throw new Error("Torrent not cached on Real Debrid");
|
|
||||||
}
|
|
||||||
|
|
||||||
return download;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error();
|
|
||||||
}
|
|
||||||
|
|
||||||
static authorize(apiToken: string) {
|
|
||||||
this.instance = axios.create({
|
|
||||||
baseURL: base,
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${apiToken}`,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,9 +26,9 @@ globalStyle("body", {
|
|||||||
overflow: "hidden",
|
overflow: "hidden",
|
||||||
userSelect: "none",
|
userSelect: "none",
|
||||||
fontFamily: "'Fira Mono', monospace",
|
fontFamily: "'Fira Mono', monospace",
|
||||||
fontSize: vars.size.bodyFontSize,
|
fontSize: vars.size.body,
|
||||||
background: vars.color.background,
|
background: vars.color.background,
|
||||||
color: vars.color.bodyText,
|
color: vars.color.body,
|
||||||
margin: "0",
|
margin: "0",
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -68,7 +68,7 @@ globalStyle(
|
|||||||
);
|
);
|
||||||
|
|
||||||
globalStyle("label", {
|
globalStyle("label", {
|
||||||
fontSize: vars.size.bodyFontSize,
|
fontSize: vars.size.body,
|
||||||
});
|
});
|
||||||
|
|
||||||
globalStyle("input[type=number]", {
|
globalStyle("input[type=number]", {
|
||||||
|
@ -134,6 +134,11 @@ export function App() {
|
|||||||
|
|
||||||
<section ref={contentRef} className={styles.content}>
|
<section ref={contentRef} className={styles.content}>
|
||||||
<Outlet />
|
<Outlet />
|
||||||
|
</section>
|
||||||
|
</article>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<BottomPanel />
|
||||||
|
|
||||||
<Toast
|
<Toast
|
||||||
visible={toast.visible}
|
visible={toast.visible}
|
||||||
@ -141,10 +146,6 @@ export function App() {
|
|||||||
type={toast.type}
|
type={toast.type}
|
||||||
onClose={handleToastClose}
|
onClose={handleToastClose}
|
||||||
/>
|
/>
|
||||||
</section>
|
|
||||||
</article>
|
|
||||||
</main>
|
|
||||||
<BottomPanel />
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ import { SPACING_UNIT, vars } from "../../theme.css";
|
|||||||
export const bottomPanel = style({
|
export const bottomPanel = style({
|
||||||
width: "100%",
|
width: "100%",
|
||||||
borderTop: `solid 1px ${vars.color.border}`,
|
borderTop: `solid 1px ${vars.color.border}`,
|
||||||
|
backgroundColor: vars.color.background,
|
||||||
padding: `${SPACING_UNIT / 2}px ${SPACING_UNIT * 2}px`,
|
padding: `${SPACING_UNIT / 2}px ${SPACING_UNIT * 2}px`,
|
||||||
display: "flex",
|
display: "flex",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
@ -14,10 +15,10 @@ export const bottomPanel = style({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const downloadsButton = style({
|
export const downloadsButton = style({
|
||||||
color: vars.color.bodyText,
|
color: vars.color.body,
|
||||||
borderBottom: "1px solid transparent",
|
borderBottom: "1px solid transparent",
|
||||||
":hover": {
|
":hover": {
|
||||||
borderBottom: `1px solid ${vars.color.bodyText}`,
|
borderBottom: `1px solid ${vars.color.body}`,
|
||||||
cursor: "pointer",
|
cursor: "pointer",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -109,6 +109,6 @@ export const shopIcon = style({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const noDownloadsLabel = style({
|
export const noDownloadsLabel = style({
|
||||||
color: vars.color.bodyText,
|
color: vars.color.body,
|
||||||
fontWeight: "bold",
|
fontWeight: "bold",
|
||||||
});
|
});
|
||||||
|
@ -108,7 +108,7 @@ export const section = style({
|
|||||||
|
|
||||||
export const backButton = recipe({
|
export const backButton = recipe({
|
||||||
base: {
|
base: {
|
||||||
color: vars.color.bodyText,
|
color: vars.color.body,
|
||||||
cursor: "pointer",
|
cursor: "pointer",
|
||||||
WebkitAppRegion: "no-drag",
|
WebkitAppRegion: "no-drag",
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
|
@ -23,7 +23,7 @@ export const modal = recipe({
|
|||||||
backgroundColor: vars.color.background,
|
backgroundColor: vars.color.background,
|
||||||
borderRadius: "5px",
|
borderRadius: "5px",
|
||||||
maxWidth: "600px",
|
maxWidth: "600px",
|
||||||
color: vars.color.bodyText,
|
color: vars.color.body,
|
||||||
maxHeight: "100%",
|
maxHeight: "100%",
|
||||||
border: `solid 1px ${vars.color.border}`,
|
border: `solid 1px ${vars.color.border}`,
|
||||||
overflow: "hidden",
|
overflow: "hidden",
|
||||||
@ -65,5 +65,5 @@ export const closeModalButton = style({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const closeModalButtonIcon = style({
|
export const closeModalButtonIcon = style({
|
||||||
color: vars.color.bodyText,
|
color: vars.color.body,
|
||||||
});
|
});
|
||||||
|
@ -25,12 +25,13 @@ export const toast = recipe({
|
|||||||
borderRadius: "4px",
|
borderRadius: "4px",
|
||||||
border: `solid 1px ${vars.color.border}`,
|
border: `solid 1px ${vars.color.border}`,
|
||||||
left: "50%",
|
left: "50%",
|
||||||
/* Bottom panel height + spacing */
|
/* Bottom panel height + 16px */
|
||||||
bottom: `${26 + SPACING_UNIT * 2}px`,
|
bottom: `${26 + SPACING_UNIT * 2}px`,
|
||||||
overflow: "hidden",
|
overflow: "hidden",
|
||||||
display: "flex",
|
display: "flex",
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
justifyContent: "space-between",
|
justifyContent: "space-between",
|
||||||
|
zIndex: "0",
|
||||||
},
|
},
|
||||||
variants: {
|
variants: {
|
||||||
closing: {
|
closing: {
|
||||||
@ -66,7 +67,7 @@ export const progress = style({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const closeButton = style({
|
export const closeButton = style({
|
||||||
color: vars.color.bodyText,
|
color: vars.color.body,
|
||||||
cursor: "pointer",
|
cursor: "pointer",
|
||||||
padding: "0",
|
padding: "0",
|
||||||
margin: "0",
|
margin: "0",
|
||||||
|
2
src/renderer/src/declaration.d.ts
vendored
2
src/renderer/src/declaration.d.ts
vendored
@ -22,7 +22,7 @@ declare global {
|
|||||||
|
|
||||||
interface Electron {
|
interface Electron {
|
||||||
/* Torrenting */
|
/* Torrenting */
|
||||||
startGameDownload: (payload: StartGameDownloadPayload) => Promise<Game>;
|
startGameDownload: (payload: StartGameDownloadPayload) => Promise<void>;
|
||||||
cancelGameDownload: (gameId: number) => Promise<void>;
|
cancelGameDownload: (gameId: number) => Promise<void>;
|
||||||
pauseGameDownload: (gameId: number) => Promise<void>;
|
pauseGameDownload: (gameId: number) => Promise<void>;
|
||||||
resumeGameDownload: (gameId: number) => Promise<void>;
|
resumeGameDownload: (gameId: number) => Promise<void>;
|
||||||
|
@ -19,7 +19,6 @@ export const toastSlice = createSlice({
|
|||||||
initialState,
|
initialState,
|
||||||
reducers: {
|
reducers: {
|
||||||
showToast: (state, action: PayloadAction<Omit<ToastState, "visible">>) => {
|
showToast: (state, action: PayloadAction<Omit<ToastState, "visible">>) => {
|
||||||
console.log(action.payload);
|
|
||||||
state.message = action.payload.message;
|
state.message = action.payload.message;
|
||||||
state.visible = true;
|
state.visible = true;
|
||||||
},
|
},
|
||||||
|
@ -12,7 +12,7 @@ export const downloadTitleWrapper = style({
|
|||||||
export const downloadTitle = style({
|
export const downloadTitle = style({
|
||||||
fontWeight: "bold",
|
fontWeight: "bold",
|
||||||
cursor: "pointer",
|
cursor: "pointer",
|
||||||
color: vars.color.bodyText,
|
color: vars.color.body,
|
||||||
textAlign: "left",
|
textAlign: "left",
|
||||||
fontSize: "16px",
|
fontSize: "16px",
|
||||||
display: "block",
|
display: "block",
|
||||||
|
@ -174,5 +174,5 @@ globalStyle(`${description} img`, {
|
|||||||
});
|
});
|
||||||
|
|
||||||
globalStyle(`${description} a`, {
|
globalStyle(`${description} a`, {
|
||||||
color: vars.color.bodyText,
|
color: vars.color.body,
|
||||||
});
|
});
|
||||||
|
@ -13,6 +13,6 @@ export const repackButton = style({
|
|||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
alignItems: "flex-start",
|
alignItems: "flex-start",
|
||||||
gap: `${SPACING_UNIT}px`,
|
gap: `${SPACING_UNIT}px`,
|
||||||
color: vars.color.bodyText,
|
color: vars.color.body,
|
||||||
padding: `${SPACING_UNIT * 2}px`,
|
padding: `${SPACING_UNIT * 2}px`,
|
||||||
});
|
});
|
||||||
|
@ -15,7 +15,7 @@ export const downloadsPathField = style({
|
|||||||
|
|
||||||
export const hintText = style({
|
export const hintText = style({
|
||||||
fontSize: "12px",
|
fontSize: "12px",
|
||||||
color: vars.color.bodyText,
|
color: vars.color.body,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const downloaders = style({
|
export const downloaders = style({
|
||||||
|
@ -125,7 +125,7 @@ export function SelectFolderModal({
|
|||||||
{selectedDownloader === Downloader.RealDebrid && (
|
{selectedDownloader === Downloader.RealDebrid && (
|
||||||
<CheckCircleFillIcon />
|
<CheckCircleFillIcon />
|
||||||
)}
|
)}
|
||||||
Real Debrid
|
Real-Debrid
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -88,5 +88,5 @@ export const howLongToBeatCategorySkeleton = style({
|
|||||||
|
|
||||||
globalStyle(`${requirementsDetails} a`, {
|
globalStyle(`${requirementsDetails} a`, {
|
||||||
display: "flex",
|
display: "flex",
|
||||||
color: vars.color.bodyText,
|
color: vars.color.body,
|
||||||
});
|
});
|
||||||
|
@ -7,3 +7,8 @@ export const form = style({
|
|||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
gap: `${SPACING_UNIT}px`,
|
gap: `${SPACING_UNIT}px`,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const description = style({
|
||||||
|
fontFamily: "'Fira Sans', sans-serif",
|
||||||
|
marginBottom: `${SPACING_UNIT}px`,
|
||||||
|
});
|
||||||
|
@ -52,8 +52,6 @@ export function SettingsRealDebrid({
|
|||||||
form.realDebridApiToken!
|
form.realDebridApiToken!
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log(user);
|
|
||||||
|
|
||||||
if (user.type === "premium") {
|
if (user.type === "premium") {
|
||||||
dispatch(
|
dispatch(
|
||||||
showToast({
|
showToast({
|
||||||
@ -61,18 +59,22 @@ export function SettingsRealDebrid({
|
|||||||
type: "success",
|
type: "success",
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// updateUserPreferences({
|
updateUserPreferences({
|
||||||
// realDebridApiToken: form.useRealDebrid ? form.realDebridApiToken : null,
|
realDebridApiToken: form.useRealDebrid
|
||||||
// });
|
? form.realDebridApiToken
|
||||||
|
: null,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const isButtonDisabled = form.useRealDebrid && !form.realDebridApiToken;
|
const isButtonDisabled = form.useRealDebrid && !form.realDebridApiToken;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form className={styles.form} onSubmit={handleFormSubmit}>
|
<form className={styles.form} onSubmit={handleFormSubmit}>
|
||||||
|
<p className={styles.description}>{t("real_debrid_description")}</p>
|
||||||
|
|
||||||
<CheckboxField
|
<CheckboxField
|
||||||
label={t("enable_real_debrid")}
|
label={t("enable_real_debrid")}
|
||||||
checked={form.useRealDebrid}
|
checked={form.useRealDebrid}
|
||||||
@ -86,7 +88,7 @@ export function SettingsRealDebrid({
|
|||||||
|
|
||||||
{form.useRealDebrid && (
|
{form.useRealDebrid && (
|
||||||
<TextField
|
<TextField
|
||||||
label={t("real_debrid_api_token_label")}
|
label="API Private Token"
|
||||||
value={form.realDebridApiToken ?? ""}
|
value={form.realDebridApiToken ?? ""}
|
||||||
type="password"
|
type="password"
|
||||||
onChange={(event) =>
|
onChange={(event) =>
|
||||||
|
@ -7,7 +7,7 @@ export const [themeClass, vars] = createTheme({
|
|||||||
background: "#1c1c1c",
|
background: "#1c1c1c",
|
||||||
darkBackground: "#151515",
|
darkBackground: "#151515",
|
||||||
muted: "#c0c1c7",
|
muted: "#c0c1c7",
|
||||||
bodyText: "#8e919b",
|
body: "#8e919b",
|
||||||
border: "#424244",
|
border: "#424244",
|
||||||
success: "#1c9749",
|
success: "#1c9749",
|
||||||
danger: "#e11d48",
|
danger: "#e11d48",
|
||||||
@ -17,6 +17,6 @@ export const [themeClass, vars] = createTheme({
|
|||||||
active: "0.7",
|
active: "0.7",
|
||||||
},
|
},
|
||||||
size: {
|
size: {
|
||||||
bodyFontSize: "14px",
|
body: "14px",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -115,9 +115,6 @@ export interface DownloadProgress {
|
|||||||
numPeers: number;
|
numPeers: number;
|
||||||
numSeeds: number;
|
numSeeds: number;
|
||||||
isDownloadingMetadata: boolean;
|
isDownloadingMetadata: boolean;
|
||||||
progress: number;
|
|
||||||
bytesDownloaded: number;
|
|
||||||
fileSize: number;
|
|
||||||
game: Omit<Game, "repacks">;
|
game: Omit<Game, "repacks">;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -197,7 +194,18 @@ export interface RealDebridTorrentInfo {
|
|||||||
host: string;
|
host: string;
|
||||||
split: number;
|
split: number;
|
||||||
progress: number;
|
progress: number;
|
||||||
status: string;
|
status:
|
||||||
|
| "magnet_error"
|
||||||
|
| "magnet_conversion"
|
||||||
|
| "waiting_files_selection"
|
||||||
|
| "queued"
|
||||||
|
| "downloading"
|
||||||
|
| "downloaded"
|
||||||
|
| "error"
|
||||||
|
| "virus"
|
||||||
|
| "compressing"
|
||||||
|
| "uploading"
|
||||||
|
| "dead";
|
||||||
added: string;
|
added: string;
|
||||||
files: {
|
files: {
|
||||||
id: number;
|
id: number;
|
||||||
|
Loading…
Reference in New Issue
Block a user