added donor screen

This commit is contained in:
tapframe 2026-01-25 14:37:42 +05:30
parent 4f6a150592
commit 36b2375db0
19 changed files with 601 additions and 123 deletions

1
.gitignore vendored
View file

@ -105,3 +105,4 @@ LibTorrent/
iTorrent/
simkl-docss
downloader.md
server

27
Transaction_All.csv Normal file
View file

@ -0,0 +1,27 @@
DateTime (UTC),"From","Message","Item","Received","Given","Currency","TransactionType","TransactionId","Reference","SalesTax","SalesTaxPercentage","SalesTaxIncludesShipping","BuyerCountry","BuyerStateOrProvince","BuyerEmail","PaymentProvider","DiscordUsername"
"06/24/2025 22:48","Nunie","This is from Taz just showing some love for your work","Ko-fi Support","10.00","0","USD","Tip","d23c58ab-8165-472f-9af5-af76b99fe196","T-Q5Q61GZKB0","","","","","","nunie661@gmail.com","PayPal",""
"07/03/2025 16:17","wildkazoos","Hope this helps get the testflight going! Good luck!","Ko-fi Support","5.00","0","USD","Tip","cb61f5a7-7cbe-41b2-9456-4ec3a7fdcc5a","T-A0A21HG9NY","","","","","","kazoos.wildcat.7y@icloud.com","PayPal","wildkazoos#0"
"07/06/2025 20:01","Nunie","Love what your doing to the community keep it up and looking forward to android tv","Ko-fi Support","10.00","0","USD","Tip","7a1dd5fd-6c42-428d-a150-600ceecd0bb9","T-G2G71HMJW5","","","","","","nunie661@gmail.com","PayPal",""
"09/11/2025 14:03","ak061","Great app","Ko-fi Support","10.00","0","USD","Tip","c4b10e74-9c3c-49ab-b32e-9b002c34ea58","T-C0C31L3IB8","","","","","","promoemails0501@gmail.com","PayPal",""
"09/16/2025 15:32","Gesti","","Ko-fi Support","10.00","0","USD","Tip","eb47a9a4-87ce-48bb-b78d-e2013e220b03","T-H2H61LCN1Y","","","","","","gestii97@gmail.com","PayPal",""
"09/18/2025 23:17","MK","Keep up the good work! Much love and support!","Ko-fi Support","50.00","0","USD","Tip","0d6d63e8-312b-48e1-ad45-a9f6bf375fda","T-G2G01LH03X","","","","","","mk90@windowslive.com","PayPal","mk90.#0"
"09/21/2025 02:43","Din","","Ko-fi Support","10.00","0","USD","Tip","751dd75f-1088-46c8-9abe-777117c105e7","T-Z8Z61LL2OW","","","","","","greksoft@ymail.com","PayPal","dinny.#0"
"09/23/2025 15:28","TheAdamsFamily","","Ko-fi Support","5.00","0","USD","Tip","fcfee7b8-cace-4190-bf9e-6cd776a58e02","T-F1A51LPUXP","","","","","","adam.gee@live.co.uk","PayPal","Not connected"
"10/15/2025 00:54","Capillary9835","","Ko-fi Support","5.00","0","USD","Tip","815ce820-4294-413e-9b4c-6999cf2e6de3","T-E1E41MU2KD","","","","","","07yq1ws7@duck.com","PayPal","capillary9835#0"
"10/19/2025 14:07","oma ","thank you for the app you've made it's absolutely amazing and the fact it's free <3","Ko-fi Support","10.00","0","USD","Tip","bfa6404c-4ac3-431c-bcb1-164d049b7c8c","T-K3K11N2ANR","","","","","","omar@amgu.com","PayPal",""
"10/30/2025 03:23","Rob 🐶","i like to support all stremio app devs, thank you!","Ko-fi Support","5.00","0","USD","Tip","29887b4c-80f6-471a-9649-b33766579a3f","T-G2G01NLYW5","","","","","","robert.koteski@hotmail.com","PayPal",""
"11/11/2025 16:54","Ko-fi Supporter","Keep going...","Ko-fi Support","10.00","0","USD","Tip","1a96aad4-e3b5-4c83-bb08-ff797038ea09","T-F1F71OA1C7","","","","","","deepcity88@duck.com","PayPal",""
"11/14/2025 07:43","EvanderMegaton","I want to support every developer who does something related to my favorite hobby. Thank you! There is only one problem: there is no Android TV version, and therefore, $5 is for everything you have done so far, and $5 is my investment, stimulation for the TV version of the app :)","Ko-fi Support","10.00","0","USD","Tip","c8623031-36b5-4288-8171-0bc4fbf9ee54","T-C0C21OF41Y","","","","","","velimir.saban@gmail.com","PayPal",""
"11/19/2025 17:23","Jx","","Ko-fi Support","5.00","0","USD","Tip","fca4c0e4-cfaa-4488-8f40-84f1cd97ec8e","T-P5P11ORGGH","","","","","","xavier.bento@gmail.com","PayPal","jxb0532_89330#0"
"12/09/2025 20:09","Marcojaco","Thanks ","Ko-fi Support","5.00","0","USD","Tip","e4fec6ba-e4db-4d7a-b580-bd8941acf52f","T-U6U71PZLCX","","","","","","marcojaco32@gmail.com","PayPal",""
"12/15/2025 22:07","Razz","Best movies app I've seen to date!! Amazing work and long may it continue! Appreciated 👍🏻","Ko-fi Support","7.00","0","USD","Tip","b4a59c25-3eda-4219-b8ea-f0eea8048bf6","T-O4O51QCM3T","","","","","","russellcausier@gmail.com","PayPal","razzthekid82#0"
"12/16/2025 18:58","Disc ~ george.epub","","Ko-fi Support","20.00","0","USD","Tip","870c51de-c26f-4e4c-80ae-28202325f861","T-P5P11QECFI","","","","","","georgegaines7@gmail.com","PayPal","Not connected"
"12/24/2025 13:45","LexTutor","","Ko-fi Support","10.00","0","USD","Tip","043da538-8f03-4126-83d8-5148ecacf25b","T-U7U31QVQ69","","","","","","arditmemollathe@gmail.com","PayPal","ardit6991#0"
"12/25/2025 13:22","Ko-fi Supporter","","Ko-fi Support","5.00","0","USD","Tip","700b1d65-3137-4984-8fc0-86921dccd008","T-F1F11QY1G7","","","","","","watsonzach12@gmail.com","PayPal",""
"12/25/2025 22:58","Ko-fi Supporter","","Ko-fi Support","5.00","0","USD","Tip","c5935dfc-2606-47e4-a3de-e84c20ba816c","T-U7U11QZ4NK","","","","","","jason@jasonboshears.com","PayPal","Not connected"
"12/29/2025 09:11","Grape Juice","","Ko-fi Support","5.00","0","USD","Tip","b04ac2de-282b-40a8-8f8e-561008b1b624","T-L3L31R7L7U","","","","","","grapejuice897@gmail.com","PayPal","grapedjuice#0"
"12/30/2025 01:26","Orlando","","Ko-fi Support","5.00","0","USD","Tip","a8e12e23-2e8a-4aae-b88c-5b84a91e0525","T-M4M71R9DFY","","","","","","orlandodewalt5@gmail.com","PayPal","Not connected"
"12/30/2025 13:22","Elyasa' Sidek","","Ko-fi Support","10.00","0","USD","Tip","6e07f94c-c7ba-42e4-8e6c-c50e7879b6aa","T-S6S11RAFG2","","","","","","elyasasidek@gmail.com","PayPal","elyasa0201#0"
"01/14/2026 14:58","Tedz ","Thank you Bro","Ko-fi Support","5.00","0","USD","Tip","a7f04654-8f37-4f74-98af-c97b4978c20a","T-R6R61SA5CK","","","","","","long_and_wide@yahoo.com","PayPal",""
"01/16/2026 21:34","Ko-fi Supporter","Appreciate you","Ko-fi Support","15.00","0","USD","Tip","236ecb15-ce1e-4a74-bb71-96a34b7994a7","T-M4M41SF6NB","","","","","","maingikenny@gmail.com","PayPal","Not connected"
"01/18/2026 11:16","Sovilor","","Ko-fi Support","5.00","0","USD","Tip","31d645b5-3098-465a-94f5-40f83d73adb7","T-S6S51SIYXB","","","","","","sovilor@gmail.com","PayPal","Not connected"
1 DateTime (UTC) From Message Item Received Given Currency TransactionType TransactionId Reference SalesTax SalesTaxPercentage SalesTaxIncludesShipping BuyerCountry BuyerStateOrProvince BuyerEmail PaymentProvider DiscordUsername
2 06/24/2025 22:48 Nunie This is from Taz just showing some love for your work Ko-fi Support 10.00 0 USD Tip d23c58ab-8165-472f-9af5-af76b99fe196 T-Q5Q61GZKB0 nunie661@gmail.com PayPal
3 07/03/2025 16:17 wildkazoos Hope this helps get the testflight going! Good luck! Ko-fi Support 5.00 0 USD Tip cb61f5a7-7cbe-41b2-9456-4ec3a7fdcc5a T-A0A21HG9NY kazoos.wildcat.7y@icloud.com PayPal wildkazoos#0
4 07/06/2025 20:01 Nunie Love what your doing to the community keep it up and looking forward to android tv Ko-fi Support 10.00 0 USD Tip 7a1dd5fd-6c42-428d-a150-600ceecd0bb9 T-G2G71HMJW5 nunie661@gmail.com PayPal
5 09/11/2025 14:03 ak061 Great app Ko-fi Support 10.00 0 USD Tip c4b10e74-9c3c-49ab-b32e-9b002c34ea58 T-C0C31L3IB8 promoemails0501@gmail.com PayPal
6 09/16/2025 15:32 Gesti Ko-fi Support 10.00 0 USD Tip eb47a9a4-87ce-48bb-b78d-e2013e220b03 T-H2H61LCN1Y gestii97@gmail.com PayPal
7 09/18/2025 23:17 MK Keep up the good work! Much love and support! Ko-fi Support 50.00 0 USD Tip 0d6d63e8-312b-48e1-ad45-a9f6bf375fda T-G2G01LH03X mk90@windowslive.com PayPal mk90.#0
8 09/21/2025 02:43 Din Ko-fi Support 10.00 0 USD Tip 751dd75f-1088-46c8-9abe-777117c105e7 T-Z8Z61LL2OW greksoft@ymail.com PayPal dinny.#0
9 09/23/2025 15:28 TheAdamsFamily Ko-fi Support 5.00 0 USD Tip fcfee7b8-cace-4190-bf9e-6cd776a58e02 T-F1A51LPUXP adam.gee@live.co.uk PayPal Not connected
10 10/15/2025 00:54 Capillary9835 Ko-fi Support 5.00 0 USD Tip 815ce820-4294-413e-9b4c-6999cf2e6de3 T-E1E41MU2KD 07yq1ws7@duck.com PayPal capillary9835#0
11 10/19/2025 14:07 oma thank you for the app you've made it's absolutely amazing and the fact it's free <3 Ko-fi Support 10.00 0 USD Tip bfa6404c-4ac3-431c-bcb1-164d049b7c8c T-K3K11N2ANR omar@amgu.com PayPal
12 10/30/2025 03:23 Rob 🐶 i like to support all stremio app devs, thank you! Ko-fi Support 5.00 0 USD Tip 29887b4c-80f6-471a-9649-b33766579a3f T-G2G01NLYW5 robert.koteski@hotmail.com PayPal
13 11/11/2025 16:54 Ko-fi Supporter Keep going... Ko-fi Support 10.00 0 USD Tip 1a96aad4-e3b5-4c83-bb08-ff797038ea09 T-F1F71OA1C7 deepcity88@duck.com PayPal
14 11/14/2025 07:43 EvanderMegaton I want to support every developer who does something related to my favorite hobby. Thank you! There is only one problem: there is no Android TV version, and therefore, $5 is for everything you have done so far, and $5 is my investment, stimulation for the TV version of the app :) Ko-fi Support 10.00 0 USD Tip c8623031-36b5-4288-8171-0bc4fbf9ee54 T-C0C21OF41Y velimir.saban@gmail.com PayPal
15 11/19/2025 17:23 Jx Ko-fi Support 5.00 0 USD Tip fca4c0e4-cfaa-4488-8f40-84f1cd97ec8e T-P5P11ORGGH xavier.bento@gmail.com PayPal jxb0532_89330#0
16 12/09/2025 20:09 Marcojaco Thanks Ko-fi Support 5.00 0 USD Tip e4fec6ba-e4db-4d7a-b580-bd8941acf52f T-U6U71PZLCX marcojaco32@gmail.com PayPal
17 12/15/2025 22:07 Razz Best movies app I've seen to date!! Amazing work and long may it continue! Appreciated 👍🏻 Ko-fi Support 7.00 0 USD Tip b4a59c25-3eda-4219-b8ea-f0eea8048bf6 T-O4O51QCM3T russellcausier@gmail.com PayPal razzthekid82#0
18 12/16/2025 18:58 Disc ~ george.epub Ko-fi Support 20.00 0 USD Tip 870c51de-c26f-4e4c-80ae-28202325f861 T-P5P11QECFI georgegaines7@gmail.com PayPal Not connected
19 12/24/2025 13:45 LexTutor Ko-fi Support 10.00 0 USD Tip 043da538-8f03-4126-83d8-5148ecacf25b T-U7U31QVQ69 arditmemollathe@gmail.com PayPal ardit6991#0
20 12/25/2025 13:22 Ko-fi Supporter Ko-fi Support 5.00 0 USD Tip 700b1d65-3137-4984-8fc0-86921dccd008 T-F1F11QY1G7 watsonzach12@gmail.com PayPal
21 12/25/2025 22:58 Ko-fi Supporter Ko-fi Support 5.00 0 USD Tip c5935dfc-2606-47e4-a3de-e84c20ba816c T-U7U11QZ4NK jason@jasonboshears.com PayPal Not connected
22 12/29/2025 09:11 Grape Juice Ko-fi Support 5.00 0 USD Tip b04ac2de-282b-40a8-8f8e-561008b1b624 T-L3L31R7L7U grapejuice897@gmail.com PayPal grapedjuice#0
23 12/30/2025 01:26 Orlando Ko-fi Support 5.00 0 USD Tip a8e12e23-2e8a-4aae-b88c-5b84a91e0525 T-M4M71R9DFY orlandodewalt5@gmail.com PayPal Not connected
24 12/30/2025 13:22 Elyasa' Sidek Ko-fi Support 10.00 0 USD Tip 6e07f94c-c7ba-42e4-8e6c-c50e7879b6aa T-S6S11RAFG2 elyasasidek@gmail.com PayPal elyasa0201#0
25 01/14/2026 14:58 Tedz Thank you Bro Ko-fi Support 5.00 0 USD Tip a7f04654-8f37-4f74-98af-c97b4978c20a T-R6R61SA5CK long_and_wide@yahoo.com PayPal
26 01/16/2026 21:34 Ko-fi Supporter Appreciate you Ko-fi Support 15.00 0 USD Tip 236ecb15-ce1e-4a74-bb71-96a34b7994a7 T-M4M41SF6NB maingikenny@gmail.com PayPal Not connected
27 01/18/2026 11:16 Sovilor Ko-fi Support 5.00 0 USD Tip 31d645b5-3098-465a-94f5-40f83d73adb7 T-S6S51SIYXB sovilor@gmail.com PayPal Not connected

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -820,6 +820,7 @@
"special_mentions": "ذكر خاص",
"tab_contributors": "المساهمون",
"tab_special": "ذكر خاص",
"tab_donors": "المانحون",
"manager_role": "مدير المجتمع",
"manager_desc": "يدير مجتمعات Discord و Reddit الخاصة بـ Nuvio",
"sponsor_role": "راعي السيرفر",
@ -833,6 +834,11 @@
"gratitude_desc": "كل سطر برمجي، بلاغ عن خطأ، واقتراح يساعد في جعل Nuvio أفضل للجميع",
"special_thanks_title": "شكر خاص",
"special_thanks_desc": "هؤلاء الأشخاص الرائعون يساعدون في الحفاظ على مجتمع Nuvio وتشغيل السيرفرات",
"donors_desc": "شكراً لإيمانك بما نقوم ببناؤه. دعمك يحافظ على Nuvio مجاناً وفي تحسن مستمر.",
"latest_donations": "الأحدث",
"leaderboard": "الترتيب",
"loading_donors": "جاري تحميل المانحين…",
"no_donors": "لا يوجد مانحون حتى الآن",
"error_rate_limit": "تم تجاوز حد معدل GitHub API. يرجى المحاولة لاحقاً أو التمرير للتحديث.",
"error_failed": "فشل تحميل المساهمين. يرجى التحقق من اتصالك بالإنترنت.",
"retry": "حاول مرة أخرى",

View file

@ -833,6 +833,12 @@
"gratitude_desc": "Jede Zeile Code hilft",
"special_thanks_title": "Besonderer Dank",
"special_thanks_desc": "Diese erstaunlichen Menschen helfen",
"donors_desc": "Danke, dass Sie an das glauben, was wir aufbauen. Ihre Unterstützung hält Nuvio kostenlos und ständig verbessert.",
"tab_donors": "Spender",
"latest_donations": "Aktuell",
"leaderboard": "Bestenliste",
"loading_donors": "Spender werden geladen…",
"no_donors": "Noch keine Spender",
"error_rate_limit": "GitHub API Rate Limit überschritten.",
"error_failed": "Fehler beim Laden der Mitwirkenden.",
"retry": "Erneut versuchen",

View file

@ -820,6 +820,7 @@
"special_mentions": "Special Mentions",
"tab_contributors": "Contributors",
"tab_special": "Special Mentions",
"tab_donors": "Donors",
"manager_role": "Community Manager",
"manager_desc": "Manages the Discord & Reddit communities for Nuvio",
"sponsor_role": "Server Sponsor",
@ -833,6 +834,11 @@
"gratitude_desc": "Each line of code, bug report, and suggestion helps make Nuvio better for everyone",
"special_thanks_title": "Special Thanks",
"special_thanks_desc": "These amazing people help keep the Nuvio community running and the servers online",
"donors_desc": "Thank you for believing in what we're building. Your support keeps Nuvio free and constantly improving.",
"latest_donations": "Latest",
"leaderboard": "Leaderboard",
"loading_donors": "Loading donors…",
"no_donors": "No donors yet",
"error_rate_limit": "GitHub API rate limit exceeded. Please try again later or pull to refresh.",
"error_failed": "Failed to load contributors. Please check your internet connection.",
"retry": "Try Again",

View file

@ -820,6 +820,7 @@
"special_mentions": "Menciones especiales",
"tab_contributors": "Colaboradores",
"tab_special": "Menciones especiales",
"tab_donors": "Donantes",
"manager_role": "Community Manager",
"manager_desc": "Gestiona las comunidades de Discord y Reddit para Nuvio",
"sponsor_role": "Patrocinador del servidor",
@ -833,6 +834,11 @@
"gratitude_desc": "Cada línea de código, informe de fallo y sugerencia ayuda a mejorar Nuvio para todos",
"special_thanks_title": "Agradecimientos especiales",
"special_thanks_desc": "Estas personas increíbles ayudan a mantener la comunidad de Nuvio en marcha y los servidores online",
"donors_desc": "Gracias por creer en lo que estamos construyendo. Tu apoyo mantiene Nuvio gratis y en constante mejora.",
"latest_donations": "Recientes",
"leaderboard": "Clasificación",
"loading_donors": "Cargando donantes…",
"no_donors": "Sin donantes aún",
"error_rate_limit": "Se superó el límite de la API de GitHub. Inténtalo de nuevo más tarde o desliza para actualizar.",
"error_failed": "Error al cargar los colaboradores. Comprueba tu conexión a internet.",
"retry": "Reintentar",

View file

@ -820,6 +820,7 @@
"special_mentions": "Mentions spéciales",
"tab_contributors": "Contributeurs",
"tab_special": "Mentions spéciales",
"tab_donors": "Donateurs",
"manager_role": "Responsable de communauté",
"manager_desc": "Gère les communautés Discord et Reddit pour Nuvio",
"sponsor_role": "Sponsor serveur",
@ -833,6 +834,11 @@
"gratitude_desc": "Chaque ligne de code, rapport de bug et suggestion aide à rendre Nuvio meilleur pour tous",
"special_thanks_title": "Remerciements spéciaux",
"special_thanks_desc": "Ces personnes formidables aident à faire fonctionner la communauté Nuvio et à maintenir les serveurs en ligne",
"donors_desc": "Merci de croire en ce que nous construisons. Votre soutien garde Nuvio gratuit et en constant progrès.",
"latest_donations": "Récents",
"leaderboard": "Classement",
"loading_donors": "Chargement des donateurs…",
"no_donors": "Pas encore de donateurs",
"error_rate_limit": "Limite de débit de l'API GitHub dépassée. Veuillez réessayer plus tard ou faire glisser pour actualiser.",
"error_failed": "Échec du chargement des contributeurs. Veuillez vérifier votre connexion Internet.",
"retry": "Réessayer",

View file

@ -820,6 +820,7 @@
"special_mentions": "Posebna priznanja",
"tab_contributors": "Doprinositelji",
"tab_special": "Posebna priznanja",
"tab_donors": "Donatori",
"manager_role": "Community Manager",
"manager_desc": "Upravlja Discord i Reddit zajednicama za Nuvio",
"sponsor_role": "Sponzor poslužitelja",
@ -833,6 +834,11 @@
"gratitude_desc": "Svaka linija koda, prijava greške i prijedlog pomaže učiniti Nuvio boljim za sve",
"special_thanks_title": "Posebna zahvala",
"special_thanks_desc": "Ovi nevjerojatni ljudi pomažu održavati Nuvio zajednicu aktivnom i poslužitelje online",
"donors_desc": "Hvala što vjerujete u ono što gradimo. Vaša podrška čini Nuvio besplatnim i neprestano poboljšanim.",
"latest_donations": "Nedavno",
"leaderboard": "Ljestvica",
"loading_donors": "Učitavanje donatora…",
"no_donors": "Još nema donatora",
"error_rate_limit": "Prekoračeno ograničenje GitHub API-ja. Molimo pokušajte ponovo kasnije ili povucite za osvježavanje.",
"error_failed": "Učitavanje doprinositelja nije uspjelo. Molimo provjerite svoju internetsku vezu.",
"retry": "Pokušaj ponovo",

View file

@ -820,6 +820,7 @@
"special_mentions": "Menzioni speciali",
"tab_contributors": "Collaboratori",
"tab_special": "Menzioni speciali",
"tab_donors": "Donatori",
"manager_role": "Community Manager",
"manager_desc": "Gestisce le community Discord e Reddit per Nuvio",
"sponsor_role": "Sponsor del Server",
@ -833,6 +834,11 @@
"gratitude_desc": "Ogni riga di codice, segnalazione di bug e suggerimento aiuta a rendere Nuvio migliore per tutti",
"special_thanks_title": "Ringraziamenti speciali",
"special_thanks_desc": "Queste persone fantastiche aiutano a mantenere attiva la community di Nuvio e i server online",
"donors_desc": "Grazie per credere in quello che stiamo costruendo. Il vostro supporto mantiene Nuvio gratuito e in continuo miglioramento.",
"latest_donations": "Recenti",
"leaderboard": "Classifica",
"loading_donors": "Caricamento donatori…",
"no_donors": "Nessun donatore ancora",
"error_rate_limit": "Limite di frequenza API GitHub superato. Riprova più tardi o trascina per aggiornare.",
"error_failed": "Impossibile caricare i collaboratori. Controlla la tua connessione internet.",
"retry": "Riprova",

View file

@ -834,6 +834,7 @@
"special_mentions": "Menções Especiais",
"tab_contributors": "Contribuidores",
"tab_special": "Menções Especiais",
"tab_donors": "Doadores",
"manager_role": "Gerente da Comunidade",
"manager_desc": "Gerencia as comunidades do Discord e Reddit",
"sponsor_role": "Patrocinador do Servidor",
@ -847,6 +848,11 @@
"gratitude_desc": "Cada linha de código, relatório de bug e sugestão ajuda a tornar o Nuvio melhor para todos",
"special_thanks_title": "Agradecimentos Especiais",
"special_thanks_desc": "Essas pessoas incríveis ajudam a manter a comunidade Nuvio funcionando e os servidores online",
"donors_desc": "Obrigado por acreditar no que estamos construindo. Seu apoio mantém o Nuvio gratuito e continuamente melhorando.",
"latest_donations": "Recentes",
"leaderboard": "Placar",
"loading_donors": "Carregando doadores…",
"no_donors": "Sem doadores ainda",
"error_rate_limit": "Limite de taxa da API do GitHub excedido. Tente novamente mais tarde.",
"error_failed": "Falha ao carregar colaboradores. Verifique sua conexão com a internet.",
"retry": "Tentar Novamente",

View file

@ -834,6 +834,7 @@
"special_mentions": "Menções Especiais",
"tab_contributors": "Contribuidores",
"tab_special": "Menções Especiais",
"tab_donors": "Doadores",
"manager_role": "Gestor da Comunidade",
"manager_desc": "Gere as comunidades do Discord e Reddit",
"sponsor_role": "Patrocinador do Servidor",
@ -847,6 +848,11 @@
"gratitude_desc": "Cada linha de código, relatório de bug e sugestão ajuda a tornar o Nuvio melhor para todos",
"special_thanks_title": "Agradecimentos Especiais",
"special_thanks_desc": "Essas pessoas incríveis ajudam a manter a comunidade Nuvio a funcionar e os servidores online",
"donors_desc": "Obrigado por acreditar no que estamos a construir. O seu apoio mantém o Nuvio gratuito e continuamente a melhorar.",
"latest_donations": "Recentes",
"leaderboard": "Placar",
"loading_donors": "A carregar doadores…",
"no_donors": "Sem doadores ainda",
"error_rate_limit": "Limite de taxa da API do GitHub excedido. Tenta novamente mais tarde.",
"error_failed": "Falha ao carregar colaboradores. Verifica a tua conexão com a internet.",
"retry": "Tentar Novamente",

View file

@ -20,11 +20,13 @@ import { useNavigation } from '@react-navigation/native';
import { NavigationProp } from '@react-navigation/native';
import FastImage from '@d11/react-native-fast-image';
import { Feather, FontAwesome5 } from '@expo/vector-icons';
import LottieView from 'lottie-react-native';
import { useTheme } from '../contexts/ThemeContext';
import { useTranslation } from 'react-i18next';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { fetchContributors, GitHubContributor } from '../services/githubReleaseService';
import { RootStackParamList } from '../navigation/AppNavigator';
import { Donation, getDonationsWithCache } from '../services/donationsService';
const { width, height } = Dimensions.get('window');
const isTablet = width >= 768;
@ -77,7 +79,10 @@ const getSpecialMentionsConfig = (t: any) => [
},
];
type TabType = 'contributors' | 'special';
type TabType = 'contributors' | 'special' | 'donors';
type DonorsTabType = 'latest' | 'leaderboard';
interface ContributorCardProps {
contributor: GitHubContributor;
@ -237,6 +242,65 @@ const SpecialMentionCard: React.FC<SpecialMentionCardProps> = ({ mention, curren
);
};
interface DonorCardProps {
donor: Donation;
currentTheme: any;
isTablet: boolean;
}
const DonorCard: React.FC<DonorCardProps> = ({ donor, currentTheme, isTablet }) => {
const formatDonationDate = (dateString?: string) => {
if (!dateString) return '';
try {
const date = new Date(dateString);
if (isNaN(date.getTime())) return dateString;
return date.toLocaleDateString(undefined, {
year: 'numeric',
month: 'short',
day: 'numeric',
});
} catch {
return dateString;
}
};
return (
<View
style={[
styles.contributorCard,
{ backgroundColor: currentTheme.colors.elevation1 },
isTablet && styles.tabletContributorCard
]}
>
<View style={styles.donorAvatar}>
<Text style={[styles.donorAvatarText, { color: currentTheme.colors.white }]}>$</Text>
</View>
<View style={styles.contributorInfo}>
<Text style={[
styles.username,
{ color: currentTheme.colors.highEmphasis },
isTablet && styles.tabletUsername
]}>
{donor.name}
</Text>
<Text style={[
styles.donorAmount,
{ color: currentTheme.colors.mediumEmphasis },
isTablet && styles.tabletContributions
]}>
{donor.amount.toFixed(2)} {donor.currency} · {formatDonationDate(donor.date)}
</Text>
{donor.message ? (
<Text style={[styles.donorMessage, { color: currentTheme.colors.mediumEmphasis }]}>
{donor.message}
</Text>
) : null}
</View>
</View>
);
};
const ContributorsScreen: React.FC = () => {
const { t } = useTranslation();
const navigation = useNavigation<NavigationProp<RootStackParamList>>();
@ -245,13 +309,105 @@ const ContributorsScreen: React.FC = () => {
const SPECIAL_MENTIONS_CONFIG = getSpecialMentionsConfig(t);
const [activeTab, setActiveTab] = useState<TabType>('contributors');
const [activeTab, setActiveTab] = useState<TabType>('donors');
const [contributors, setContributors] = useState<GitHubContributor[]>([]);
const [loading, setLoading] = useState(true);
const [refreshing, setRefreshing] = useState(false);
const [error, setError] = useState<string | null>(null);
const [specialMentions, setSpecialMentions] = useState<SpecialMention[]>([]);
const [specialMentionsLoading, setSpecialMentionsLoading] = useState(true);
const [donations, setDonations] = useState<Donation[]>([]);
const [donationsLoading, setDonationsLoading] = useState(false);
const [donationsRefreshing, setDonationsRefreshing] = useState(false);
const [donationsError, setDonationsError] = useState<string | null>(null);
const [donorsTab, setDonorsTab] = useState<DonorsTabType>('leaderboard');
const getDonationTs = useCallback((dateValue?: string) => {
if (!dateValue) return 0;
const ts = Date.parse(dateValue);
return Number.isFinite(ts) ? ts : 0;
}, []);
const formatDonationDate = useCallback((dateString?: string) => {
if (!dateString) return '';
try {
const date = new Date(dateString);
if (isNaN(date.getTime())) return dateString;
// Use locale-aware formatting
return date.toLocaleDateString(undefined, {
year: 'numeric',
month: 'short',
day: 'numeric',
});
} catch {
return dateString;
}
}, []);
const latestDonations = React.useMemo(() => {
return donations
.slice()
.sort((a, b) => getDonationTs(b.date) - getDonationTs(a.date));
}, [donations, getDonationTs]);
const leaderboardDonations = React.useMemo(() => {
const map = new Map<string, { name: string; currency: string; total: number; count: number; lastDate: string }>();
for (const d of donations) {
const name = (d.name || 'Supporter').trim() || 'Supporter';
const currency = d.currency || 'USD';
const key = `${name}__${currency}`;
const existing = map.get(key);
const amount = Number.isFinite(d.amount) ? d.amount : 0;
const ts = getDonationTs(d.date);
if (!existing) {
map.set(key, {
name,
currency,
total: amount,
count: 1,
lastDate: d.date,
});
} else {
const existingTs = getDonationTs(existing.lastDate);
map.set(key, {
...existing,
total: existing.total + amount,
count: existing.count + 1,
lastDate: ts > existingTs ? d.date : existing.lastDate,
});
}
}
const sorted = Array.from(map.values()).sort((a, b) => b.total - a.total);
let lastTotal: number | null = null;
let lastRank = 0;
return sorted.map((entry, index) => {
const rank = lastTotal !== null && entry.total === lastTotal ? lastRank : index + 1;
lastTotal = entry.total;
lastRank = rank;
return {
...entry,
rank,
};
});
}, [donations, getDonationTs]);
const getInitials = useCallback((name: string) => {
const parts = name.trim().split(/\s+/).filter(Boolean);
if (parts.length === 0) return 'U';
if (parts.length === 1) return parts[0].slice(0, 2).toUpperCase();
return `${parts[0][0]}${parts[1][0]}`.toUpperCase();
}, []);
const getRankAnimation = useCallback((rank: number) => {
if (rank === 1) return require('../../assets/lottie/ranking/gold.json');
if (rank === 2) return require('../../assets/lottie/ranking/silver.json');
if (rank === 3) return require('../../assets/lottie/ranking/bronze.json');
return null;
}, []);
// Fetch Discord user data for special mentions
const loadSpecialMentions = useCallback(async () => {
@ -309,6 +465,33 @@ const ContributorsScreen: React.FC = () => {
}
}, [activeTab, specialMentions.length, loadSpecialMentions]);
const loadDonations = useCallback(async (isRefresh = false) => {
try {
setDonationsError(null);
if (isRefresh) {
setDonationsRefreshing(true);
} else {
setDonationsLoading(true);
}
const data = await getDonationsWithCache(isRefresh);
setDonations(Array.isArray(data) ? data : []);
} catch (e) {
if (__DEV__) console.error('Error loading donations:', e);
setDonationsError('Failed to load donors.');
setDonations([]);
} finally {
setDonationsLoading(false);
setDonationsRefreshing(false);
}
}, []);
useEffect(() => {
if (activeTab === 'donors') {
// Force refresh on tab open so new donations appear quickly
loadDonations(true);
}
}, [activeTab, loadDonations]);
const loadContributors = useCallback(async (isRefresh = false) => {
try {
if (isRefresh) {
@ -486,6 +669,23 @@ const ContributorsScreen: React.FC = () => {
{ backgroundColor: currentTheme.colors.elevation1 },
isTablet && styles.tabletTabSwitcher
]}>
<TouchableOpacity
style={[
styles.tab,
activeTab === 'donors' && { backgroundColor: currentTheme.colors.primary },
isTablet && styles.tabletTab
]}
onPress={() => setActiveTab('donors')}
activeOpacity={0.7}
>
<Text style={[
styles.tabText,
{ color: activeTab === 'donors' ? currentTheme.colors.white : currentTheme.colors.mediumEmphasis },
isTablet && styles.tabletTabText
]}>
{t('contributors.tab_donors')}
</Text>
</TouchableOpacity>
<TouchableOpacity
style={[
styles.tab,
@ -524,7 +724,168 @@ const ContributorsScreen: React.FC = () => {
<View style={styles.content}>
<View style={[styles.contentContainer, isTablet && styles.tabletContentContainer]}>
{activeTab === 'contributors' ? (
{activeTab === 'donors' ? (
// Donors Tab
<ScrollView
style={styles.scrollView}
contentContainerStyle={[
styles.listContent,
isTablet && styles.tabletListContent
]}
refreshControl={
<RefreshControl
refreshing={donationsRefreshing}
onRefresh={() => loadDonations(true)}
tintColor={currentTheme.colors.primary}
colors={[currentTheme.colors.primary]}
/>
}
showsVerticalScrollIndicator={false}
>
<View style={[
styles.gratitudeCard,
{ backgroundColor: currentTheme.colors.elevation1 },
isTablet && styles.tabletGratitudeCard
]}>
<View style={styles.gratitudeContent}>
<Feather name="gift" size={isTablet ? 32 : 24} color={currentTheme.colors.primary} />
<Text style={[
styles.gratitudeText,
{ color: currentTheme.colors.highEmphasis },
isTablet && styles.tabletGratitudeText
]}>
{t('contributors.tab_donors')}
</Text>
<Text style={[
styles.gratitudeSubtext,
{ color: currentTheme.colors.mediumEmphasis },
isTablet && styles.tabletGratitudeSubtext
]}>
{t('contributors.donors_desc')}
</Text>
</View>
</View>
{/* Donors sub-tabs */}
<View style={[
styles.subTabSwitcher,
{ backgroundColor: currentTheme.colors.elevation1 }
]}>
<TouchableOpacity
style={[
styles.subTab,
donorsTab === 'leaderboard' && { backgroundColor: currentTheme.colors.primary }
]}
onPress={() => setDonorsTab('leaderboard')}
activeOpacity={0.7}
>
<Text style={[
styles.subTabText,
{ color: donorsTab === 'leaderboard' ? currentTheme.colors.white : currentTheme.colors.mediumEmphasis }
]}>
{t('contributors.leaderboard')}
</Text>
</TouchableOpacity>
<TouchableOpacity
style={[
styles.subTab,
donorsTab === 'latest' && { backgroundColor: currentTheme.colors.primary }
]}
onPress={() => setDonorsTab('latest')}
activeOpacity={0.7}
>
<Text style={[
styles.subTabText,
{ color: donorsTab === 'latest' ? currentTheme.colors.white : currentTheme.colors.mediumEmphasis }
]}>
{t('contributors.latest_donations')}
</Text>
</TouchableOpacity>
</View>
{donationsLoading ? (
<View style={styles.loadingContainer}>
<ActivityIndicator size="large" color={currentTheme.colors.primary} />
<Text style={[styles.loadingText, { color: currentTheme.colors.mediumEmphasis }]}>{t('contributors.loading_donors')}</Text>
</View>
) : donationsError ? (
<View style={styles.errorContainer}>
<Feather name="alert-circle" size={48} color={currentTheme.colors.mediumEmphasis} />
<Text style={[styles.errorText, { color: currentTheme.colors.mediumEmphasis }]}>
{donationsError}
</Text>
<TouchableOpacity
style={[styles.retryButton, { backgroundColor: currentTheme.colors.primary }]}
onPress={() => loadDonations(true)}
>
<Text style={[styles.retryText, { color: currentTheme.colors.white }]}>{t('common.retry')}</Text>
</TouchableOpacity>
</View>
) : donations.length === 0 ? (
<View style={styles.emptyContainer}>
<Feather name="gift" size={48} color={currentTheme.colors.mediumEmphasis} />
<Text style={[styles.emptyText, { color: currentTheme.colors.mediumEmphasis }]}>{t('contributors.no_donors')}</Text>
</View>
) : (
donorsTab === 'latest' ? (
latestDonations.map((donor, index) => (
<DonorCard
key={`${donor.name}-${donor.date}-${index}`}
donor={donor}
currentTheme={currentTheme}
isTablet={isTablet}
/>
))
) : (
leaderboardDonations.map((entry, index) => (
<View
key={`${entry.name}-${entry.currency}-${index}`}
style={[
styles.leaderboardCard,
{ backgroundColor: currentTheme.colors.elevation1 },
isTablet && styles.tabletContributorCard
]}
>
<View style={styles.leaderboardAvatar}>
{getRankAnimation(entry.rank) ? (
<View style={styles.leaderboardBadge}>
<Text style={[styles.leaderboardRankText, { color: currentTheme.colors.white }]}>{entry.rank}</Text>
<LottieView
source={getRankAnimation(entry.rank)}
autoPlay
loop={false}
style={styles.leaderboardLottie}
/>
</View>
) : (
<Text style={[styles.leaderboardRankText, { color: currentTheme.colors.white }]}>{entry.rank}</Text>
)}
</View>
<View style={styles.contributorInfo}>
<Text style={[
styles.username,
{ color: currentTheme.colors.highEmphasis },
isTablet && styles.tabletUsername
]}>
{entry.name}
</Text>
<Text style={[
styles.donorAmount,
{ color: currentTheme.colors.mediumEmphasis },
isTablet && styles.tabletContributions
]}>
{entry.total.toFixed(2)} {entry.currency} · {entry.count} {entry.count === 1 ? 'donation' : 'donations'}
</Text>
<Text style={[styles.donorMessage, { color: currentTheme.colors.mediumEmphasis }]}>
Rank #{entry.rank} · Last: {formatDonationDate(entry.lastDate)}
</Text>
</View>
</View>
))
)
)}
</ScrollView>
) : activeTab === 'contributors' ? (
// Contributors Tab
<>
{error ? (
@ -606,7 +967,7 @@ const ContributorsScreen: React.FC = () => {
</ScrollView>
)}
</>
) : (
) : activeTab === 'special' ? (
// Special Mentions Tab
<ScrollView
style={styles.scrollView}
@ -650,7 +1011,7 @@ const ContributorsScreen: React.FC = () => {
/>
))}
</ScrollView>
)}
) : null}
</View>
</View>
</View>
@ -931,6 +1292,81 @@ const styles = StyleSheet.create({
tabletTabText: {
fontSize: 16,
},
subTabSwitcher: {
flexDirection: 'row',
marginBottom: 16,
padding: 4,
borderRadius: 12,
},
subTab: {
flex: 1,
paddingVertical: 10,
paddingHorizontal: 12,
borderRadius: 10,
alignItems: 'center',
justifyContent: 'center',
},
subTabText: {
fontSize: 13,
fontWeight: '600',
},
leaderboardCard: {
flexDirection: 'row',
alignItems: 'center',
padding: 16,
marginBottom: 12,
borderRadius: 16,
borderWidth: 1,
borderColor: 'rgba(255,255,255,0.06)',
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.12,
shadowRadius: 6,
elevation: 4,
},
leaderboardAvatar: {
width: 100,
height: 100,
marginRight: 16,
alignItems: 'center',
justifyContent: 'center',
},
leaderboardBadge: {
width: 100,
height: 100,
alignItems: 'center',
justifyContent: 'center',
},
leaderboardLottie: {
width: 100,
height: 100,
},
leaderboardRankText: {
position: 'absolute',
fontSize: 18,
fontWeight: '800',
},
donorAvatar: {
width: 60,
height: 60,
borderRadius: 30,
marginRight: 16,
backgroundColor: DISCORD_BRAND_COLOR,
alignItems: 'center',
justifyContent: 'center',
},
donorAvatarText: {
fontSize: 22,
fontWeight: '800',
},
donorAmount: {
fontSize: 14,
marginBottom: 4,
},
donorMessage: {
fontSize: 13,
lineHeight: 18,
},
});
export default ContributorsScreen;

View file

@ -807,6 +807,13 @@ const SettingsScreen: React.FC = () => {
{/* About */}
<SettingsCard title={t('settings.about').toUpperCase()}>
<SettingItem
title={t('settings.items.contributors')}
description={t('settings.items.view_contributors')}
icon="users"
renderControl={() => <ChevronRight />}
onPress={() => navigation.navigate('Contributors')}
/>
<SettingItem
title={t('settings.about_nuvio')}
description={getDisplayedAppVersion()}

View file

@ -211,15 +211,7 @@ export const AboutSettingsContent: React.FC<AboutSettingsContentProps> = ({
onPress={handleVersionTap}
isTablet={isTablet}
/>
<SettingItem
title={t('settings.items.contributors')}
description={t('settings.items.view_contributors')}
icon="users"
renderControl={() => <ChevronRight />}
onPress={() => navigation.navigate('Contributors')}
isLast={!developerModeEnabled}
isTablet={isTablet}
/>
{developerModeEnabled && (
<SettingItem
title={t('settings.developer_mode.title', 'Developer Mode')}

View file

@ -0,0 +1,53 @@
import { mmkvStorage } from './mmkvStorage';
export type Donation = {
name: string;
amount: number;
currency: string;
date: string;
message?: string;
};
const DONATIONS_API_URL = process.env.EXPO_PUBLIC_DONATIONS_API_URL || '';
export async function fetchDonations(): Promise<Donation[]> {
if (!DONATIONS_API_URL) return [];
const res = await fetch(`${DONATIONS_API_URL.replace(/\/$/, '')}/api/donations?limit=200`);
if (!res.ok) throw new Error(`Donations API failed: ${res.status}`);
const json = await res.json();
const donations = json?.donations;
if (!Array.isArray(donations)) return [];
return donations;
}
export async function getDonationsWithCache(forceRefresh = false): Promise<Donation[]> {
const CACHE_KEY = 'donations_cache_v1';
const TS_KEY = 'donations_cache_ts_v1';
const TTL_MS = 10 * 60 * 1000; // 10 minutes
if (!forceRefresh) {
try {
const cached = await mmkvStorage.getItem(CACHE_KEY);
const ts = await mmkvStorage.getItem(TS_KEY);
if (cached && ts) {
const age = Date.now() - parseInt(ts, 10);
if (Number.isFinite(age) && age < TTL_MS) {
const parsed = JSON.parse(cached);
if (Array.isArray(parsed)) return parsed;
}
}
} catch {
// ignore cache failures
}
}
const donations = await fetchDonations();
try {
await mmkvStorage.setItem(CACHE_KEY, JSON.stringify(donations));
await mmkvStorage.setItem(TS_KEY, Date.now().toString());
} catch {
// ignore cache failures
}
return donations;
}

View file

@ -11,13 +11,10 @@ export class TrailerService {
private static readonly ENV_LOCAL_BASE = process.env.EXPO_PUBLIC_TRAILER_LOCAL_BASE || 'http://46.62.173.157:3001';
private static readonly ENV_LOCAL_TRAILER_PATH = process.env.EXPO_PUBLIC_TRAILER_LOCAL_TRAILER_PATH || '/trailer';
private static readonly ENV_LOCAL_SEARCH_PATH = process.env.EXPO_PUBLIC_TRAILER_LOCAL_SEARCH_PATH || '/search-trailer';
private static readonly ENV_XPRIME_URL = process.env.EXPO_PUBLIC_XPRIME_URL || 'https://db.xprime.tv/trailers';
private static readonly XPRIME_URL = TrailerService.ENV_XPRIME_URL;
private static readonly LOCAL_SERVER_URL = `${TrailerService.ENV_LOCAL_BASE}${TrailerService.ENV_LOCAL_TRAILER_PATH}`;
private static readonly AUTO_SEARCH_URL = `${TrailerService.ENV_LOCAL_BASE}${TrailerService.ENV_LOCAL_SEARCH_PATH}`;
private static readonly TIMEOUT = 20000; // 20 seconds
private static readonly USE_LOCAL_SERVER = true; // Toggle between local and XPrime
/**
* Fetches trailer URL for a given title and year
@ -28,20 +25,8 @@ export class TrailerService {
* @returns Promise<string | null> - The trailer URL or null if not found
*/
static async getTrailerUrl(title: string, year: number, tmdbId?: string, type?: 'movie' | 'tv'): Promise<string | null> {
logger.info('TrailerService', `getTrailerUrl requested: title="${title}", year=${year}, tmdbId=${tmdbId || 'n/a'}, type=${type || 'n/a'}, useLocal=${this.USE_LOCAL_SERVER}`);
if (this.USE_LOCAL_SERVER) {
// Try local server first, fallback to XPrime if it fails
const localResult = await this.getTrailerFromLocalServer(title, year, tmdbId, type);
if (localResult) {
// logger.info('TrailerService', 'Returning trailer URL from local server');
return localResult;
}
logger.info('TrailerService', `Local server failed, falling back to XPrime for: ${title} (${year})`);
return this.getTrailerFromXPrime(title, year);
} else {
return this.getTrailerFromXPrime(title, year);
}
logger.info('TrailerService', `getTrailerUrl requested: title="${title}", year=${year}, tmdbId=${tmdbId || 'n/a'}, type=${type || 'n/a'}`);
return this.getTrailerFromLocalServer(title, year, tmdbId, type);
}
/**
@ -150,68 +135,10 @@ export class TrailerService {
url: url
});
}
return null; // Return null to trigger XPrime fallback
}
}
/**
* Fetches trailer from XPrime API (original method)
* @param title - The movie/series title
* @param year - The release year
* @returns Promise<string | null> - The trailer URL or null if not found
*/
private static async getTrailerFromXPrime(title: string, year: number): Promise<string | null> {
try {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), this.TIMEOUT);
const url = `${this.XPRIME_URL}?title=${encodeURIComponent(title)}&year=${year}`;
logger.info('TrailerService', `Fetching trailer from XPrime for: ${title} (${year})`);
logger.info('TrailerService', `XPrime request URL: ${url}`);
logger.info('TrailerService', `XPrime timeout set to ${this.TIMEOUT}ms`);
const response = await fetch(url, {
method: 'GET',
headers: {
'Accept': 'text/plain',
'User-Agent': 'Nuvio/1.0',
},
signal: controller.signal,
});
clearTimeout(timeoutId);
logger.info('TrailerService', `XPrime response: status=${response.status} ok=${response.ok}`);
if (!response.ok) {
logger.warn('TrailerService', `XPrime failed: ${response.status} ${response.statusText}`);
return null;
}
const trailerUrl = await response.text();
logger.info('TrailerService', `XPrime raw URL length: ${trailerUrl ? trailerUrl.length : 0}`);
if (!trailerUrl || !this.isValidTrailerUrl(trailerUrl.trim())) {
logger.warn('TrailerService', `Invalid trailer URL from XPrime: ${trailerUrl}`);
return null;
}
const cleanUrl = trailerUrl.trim();
logger.info('TrailerService', `Successfully fetched trailer from XPrime: ${cleanUrl}`);
return cleanUrl;
} catch (error) {
if (error instanceof Error && error.name === 'AbortError') {
logger.warn('TrailerService', `XPrime request timed out after ${this.TIMEOUT}ms`);
} else {
const msg = error instanceof Error ? `${error.name}: ${error.message}` : String(error);
logger.error('TrailerService', `Error fetching from XPrime: ${msg}`);
}
return null;
}
}
/**
* Validates if the provided string is a valid trailer URL
* @param url - The URL to validate
@ -389,42 +316,39 @@ export class TrailerService {
}
/**
* Switch between local server and XPrime API
* @param useLocal - true for local server, false for XPrime
* Switch between local server (deprecated - always uses local server now)
* @param useLocal - true for local server (always true now)
*/
static setUseLocalServer(useLocal: boolean): void {
(this as any).USE_LOCAL_SERVER = useLocal;
logger.info('TrailerService', `Switched to ${useLocal ? 'local server' : 'XPrime API'}`);
if (!useLocal) {
logger.warn('TrailerService', 'XPrime API is no longer supported. Always using local server.');
}
logger.info('TrailerService', 'Using local server');
}
/**
* Get current server status
* @returns object with server information
*/
static getServerStatus(): { usingLocal: boolean; localUrl: string; xprimeUrl: string; fallbackEnabled: boolean } {
static getServerStatus(): { usingLocal: boolean; localUrl: string } {
return {
usingLocal: this.USE_LOCAL_SERVER,
usingLocal: true,
localUrl: this.LOCAL_SERVER_URL,
xprimeUrl: this.XPRIME_URL,
fallbackEnabled: true // Always enabled now
};
}
/**
* Test both servers and return their status
* Test local server and return its status
* @returns Promise with server status information
*/
static async testServers(): Promise<{
localServer: { status: 'online' | 'offline'; responseTime?: number };
xprimeServer: { status: 'online' | 'offline'; responseTime?: number };
}> {
logger.info('TrailerService', 'Testing servers (local and XPrime)');
logger.info('TrailerService', 'Testing local server');
const results: {
localServer: { status: 'online' | 'offline'; responseTime?: number };
xprimeServer: { status: 'online' | 'offline'; responseTime?: number };
} = {
localServer: { status: 'offline' },
xprimeServer: { status: 'offline' }
localServer: { status: 'offline' }
};
// Test local server
@ -446,26 +370,7 @@ export class TrailerService {
logger.warn('TrailerService', `Local server test failed: ${msg}`);
}
// Test XPrime server
try {
const startTime = Date.now();
const response = await fetch(`${this.XPRIME_URL}?title=test&year=2023`, {
method: 'GET',
signal: AbortSignal.timeout(5000) // 5 second timeout
});
if (response.ok || response.status === 404) { // 404 is ok, means server is running
results.xprimeServer = {
status: 'online',
responseTime: Date.now() - startTime
};
logger.info('TrailerService', `XPrime server online. Response time: ${results.xprimeServer.responseTime}ms`);
}
} catch (error) {
const msg = error instanceof Error ? `${error.name}: ${error.message}` : String(error);
logger.warn('TrailerService', `XPrime server test failed: ${msg}`);
}
logger.info('TrailerService', `Server test results -> local: ${results.localServer.status}, xprime: ${results.xprimeServer.status}`);
logger.info('TrailerService', `Server test results -> local: ${results.localServer.status}`);
return results;
}
}