@valoranchi/riot-client 0.4.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (156) hide show
  1. package/README.md +15 -0
  2. package/dist/RiotClient.d.ts +4 -1
  3. package/dist/RiotClient.d.ts.map +1 -1
  4. package/dist/RiotClient.js +7 -1
  5. package/dist/RiotClient.js.map +1 -1
  6. package/dist/analysis/wishlist.d.ts +5 -0
  7. package/dist/analysis/wishlist.d.ts.map +1 -0
  8. package/dist/analysis/wishlist.js +122 -0
  9. package/dist/analysis/wishlist.js.map +1 -0
  10. package/dist/catalogue/Catalogue.d.ts +1 -0
  11. package/dist/catalogue/Catalogue.d.ts.map +1 -1
  12. package/dist/catalogue/Catalogue.js +11 -0
  13. package/dist/catalogue/Catalogue.js.map +1 -1
  14. package/dist/cli.d.ts +10 -9
  15. package/dist/cli.d.ts.map +1 -1
  16. package/dist/cli.js +205 -36
  17. package/dist/cli.js.map +1 -1
  18. package/dist/client/AccountService.d.ts.map +1 -1
  19. package/dist/client/AccountService.js.map +1 -1
  20. package/dist/client/ClientContext.d.ts +4 -0
  21. package/dist/client/ClientContext.d.ts.map +1 -1
  22. package/dist/client/ClientContext.js.map +1 -1
  23. package/dist/client/OfficialService.d.ts +35 -0
  24. package/dist/client/OfficialService.d.ts.map +1 -0
  25. package/dist/client/OfficialService.js +141 -0
  26. package/dist/client/OfficialService.js.map +1 -0
  27. package/dist/client/StoreService.d.ts +6 -1
  28. package/dist/client/StoreService.d.ts.map +1 -1
  29. package/dist/client/StoreService.js +87 -5
  30. package/dist/client/StoreService.js.map +1 -1
  31. package/dist/client/api.d.ts +32 -1
  32. package/dist/client/api.d.ts.map +1 -1
  33. package/dist/dashboardLauncher.d.ts +12 -0
  34. package/dist/dashboardLauncher.d.ts.map +1 -0
  35. package/dist/dashboardLauncher.js +94 -0
  36. package/dist/dashboardLauncher.js.map +1 -0
  37. package/dist/errors.d.ts +12 -2
  38. package/dist/errors.d.ts.map +1 -1
  39. package/dist/errors.js +20 -3
  40. package/dist/errors.js.map +1 -1
  41. package/dist/formatError.d.ts +10 -0
  42. package/dist/formatError.d.ts.map +1 -0
  43. package/dist/formatError.js +29 -0
  44. package/dist/formatError.js.map +1 -0
  45. package/dist/index.d.ts +7 -5
  46. package/dist/index.d.ts.map +1 -1
  47. package/dist/index.js +5 -3
  48. package/dist/index.js.map +1 -1
  49. package/dist/mcp/McpServer.d.ts +21 -0
  50. package/dist/mcp/McpServer.d.ts.map +1 -0
  51. package/dist/mcp/McpServer.js +135 -0
  52. package/dist/mcp/McpServer.js.map +1 -0
  53. package/dist/mcp/index.d.ts +4 -0
  54. package/dist/mcp/index.d.ts.map +1 -0
  55. package/dist/mcp/index.js +3 -0
  56. package/dist/mcp/index.js.map +1 -0
  57. package/dist/mcp/tools.d.ts +9 -0
  58. package/dist/mcp/tools.d.ts.map +1 -0
  59. package/dist/mcp/tools.js +83 -0
  60. package/dist/mcp/tools.js.map +1 -0
  61. package/dist/mcp/types.d.ts +46 -0
  62. package/dist/mcp/types.d.ts.map +1 -0
  63. package/dist/mcp/types.js +2 -0
  64. package/dist/mcp/types.js.map +1 -0
  65. package/dist/model/index.d.ts +45 -0
  66. package/dist/model/index.d.ts.map +1 -1
  67. package/dist/official/OfficialApi.d.ts +35 -0
  68. package/dist/official/OfficialApi.d.ts.map +1 -0
  69. package/dist/official/OfficialApi.js +148 -0
  70. package/dist/official/OfficialApi.js.map +1 -0
  71. package/dist/official/OfficialMatchAdapter.d.ts +11 -0
  72. package/dist/official/OfficialMatchAdapter.d.ts.map +1 -0
  73. package/dist/official/OfficialMatchAdapter.js +157 -0
  74. package/dist/official/OfficialMatchAdapter.js.map +1 -0
  75. package/dist/official/RateLimiter.d.ts +20 -0
  76. package/dist/official/RateLimiter.d.ts.map +1 -0
  77. package/dist/official/RateLimiter.js +56 -0
  78. package/dist/official/RateLimiter.js.map +1 -0
  79. package/dist/official/types.d.ts +214 -0
  80. package/dist/official/types.d.ts.map +1 -0
  81. package/dist/official/types.js +2 -0
  82. package/dist/official/types.js.map +1 -0
  83. package/dist/raw.d.ts +4 -0
  84. package/dist/raw.d.ts.map +1 -1
  85. package/dist/raw.js +4 -0
  86. package/dist/raw.js.map +1 -1
  87. package/dist/riot/HttpGateway.d.ts.map +1 -1
  88. package/dist/riot/HttpGateway.js +9 -2
  89. package/dist/riot/HttpGateway.js.map +1 -1
  90. package/dist/riot/ResponseCache.d.ts +2 -0
  91. package/dist/riot/ResponseCache.d.ts.map +1 -1
  92. package/dist/riot/ResponseCache.js +8 -2
  93. package/dist/riot/ResponseCache.js.map +1 -1
  94. package/dist/serve/dashboard/dashboardCss.d.ts +2 -0
  95. package/dist/serve/dashboard/dashboardCss.d.ts.map +1 -0
  96. package/dist/serve/dashboard/dashboardCss.js +634 -0
  97. package/dist/serve/dashboard/dashboardCss.js.map +1 -0
  98. package/dist/serve/dashboard/dashboardHtml.d.ts +2 -0
  99. package/dist/serve/dashboard/dashboardHtml.d.ts.map +1 -0
  100. package/dist/serve/dashboard/dashboardHtml.js +95 -0
  101. package/dist/serve/dashboard/dashboardHtml.js.map +1 -0
  102. package/dist/serve/dashboard/dashboardJs.d.ts +2 -0
  103. package/dist/serve/dashboard/dashboardJs.d.ts.map +1 -0
  104. package/dist/serve/dashboard/dashboardJs.js +668 -0
  105. package/dist/serve/dashboard/dashboardJs.js.map +1 -0
  106. package/dist/serve/dashboard/index.d.ts +4 -0
  107. package/dist/serve/dashboard/index.d.ts.map +1 -0
  108. package/dist/serve/dashboard/index.js +4 -0
  109. package/dist/serve/dashboard/index.js.map +1 -0
  110. package/dist/serve/index.d.ts +2 -1
  111. package/dist/serve/index.d.ts.map +1 -1
  112. package/dist/serve/index.js +1 -0
  113. package/dist/serve/index.js.map +1 -1
  114. package/dist/serve/indexHtml.d.ts.map +1 -1
  115. package/dist/serve/indexHtml.js +1 -0
  116. package/dist/serve/indexHtml.js.map +1 -1
  117. package/dist/serve/openapi.d.ts.map +1 -1
  118. package/dist/serve/openapi.js +11 -0
  119. package/dist/serve/openapi.js.map +1 -1
  120. package/dist/serve/routes.d.ts +4 -2
  121. package/dist/serve/routes.d.ts.map +1 -1
  122. package/dist/serve/routes.js +434 -0
  123. package/dist/serve/routes.js.map +1 -1
  124. package/dist/serve/security.d.ts +6 -0
  125. package/dist/serve/security.d.ts.map +1 -0
  126. package/dist/serve/security.js +65 -0
  127. package/dist/serve/security.js.map +1 -0
  128. package/dist/serve/server.d.ts.map +1 -1
  129. package/dist/serve/server.js +38 -5
  130. package/dist/serve/server.js.map +1 -1
  131. package/dist/serve/types.d.ts +8 -0
  132. package/dist/serve/types.d.ts.map +1 -1
  133. package/dist/version.d.ts +2 -0
  134. package/dist/version.d.ts.map +1 -0
  135. package/dist/version.js +12 -0
  136. package/dist/version.js.map +1 -0
  137. package/dist/watch/StoreWatcher.d.ts +27 -0
  138. package/dist/watch/StoreWatcher.d.ts.map +1 -0
  139. package/dist/watch/StoreWatcher.js +116 -0
  140. package/dist/watch/StoreWatcher.js.map +1 -0
  141. package/dist/watch/WebhookNotifier.d.ts +8 -0
  142. package/dist/watch/WebhookNotifier.d.ts.map +1 -0
  143. package/dist/watch/WebhookNotifier.js +42 -0
  144. package/dist/watch/WebhookNotifier.js.map +1 -0
  145. package/dist/watch/index.d.ts +9 -3
  146. package/dist/watch/index.d.ts.map +1 -1
  147. package/dist/watch/index.js +12 -1
  148. package/dist/watch/index.js.map +1 -1
  149. package/dist/watch/types.d.ts +17 -1
  150. package/dist/watch/types.d.ts.map +1 -1
  151. package/package.json +1 -1
  152. package/schema/OfficialAccount.json +25 -0
  153. package/schema/OfficialProfile.json +354 -0
  154. package/schema/Wishlist.json +35 -0
  155. package/schema/WishlistCheck.json +67 -0
  156. package/schema/WishlistHit.json +51 -0
@@ -0,0 +1,668 @@
1
+ export function renderDashboardJs() {
2
+ return `(function () {
3
+ const DICT = {
4
+ en: {
5
+ home: "Home",
6
+ store: "Store",
7
+ wishlist: "Wishlist",
8
+ matches: "Matches",
9
+ friends: "Friends",
10
+ offlineTitle: "VALORANT is not running",
11
+ offlineMsg: "Open VALORANT and this page will update by itself",
12
+ retryingIn: "Retrying in {s}s...",
13
+ level: "Level",
14
+ rank: "Rank",
15
+ unranked: "Unranked",
16
+ rr: "RR",
17
+ wallet: "Wallet",
18
+ vp: "VP",
19
+ rad: "Radianite",
20
+ kc: "Kingdom Credits",
21
+ fit: "Rank Fit",
22
+ dailyOffers: "Daily Offers",
23
+ featuredBundles: "Featured Bundles",
24
+ nightMarket: "Night Market",
25
+ resetsIn: "Resets in",
26
+ addToWishlist: "Add to Wishlist",
27
+ searchPlaceholder: "Search skin to add...",
28
+ inStoreToday: "IN STORE TODAY!",
29
+ remove: "Remove",
30
+ victory: "VICTORY",
31
+ defeat: "DEFEAT",
32
+ draw: "DRAW",
33
+ kda: "K/D/A",
34
+ score: "Score",
35
+ noMatches: "No recent matches found",
36
+ noFriends: "No friends currently online",
37
+ onlineFriends: "Online Friends",
38
+ addedToWishlist: "Added to wishlist",
39
+ removedFromWishlist: "Removed from wishlist",
40
+ emptyWishlist: "Your wishlist is empty. Add weapon skins to track store availability.",
41
+ errorLoading: "Failed to load {section}",
42
+ },
43
+ es: {
44
+ home: "Inicio",
45
+ store: "Tienda",
46
+ wishlist: "Lista de deseos",
47
+ matches: "Partidas",
48
+ friends: "Amigos",
49
+ offlineTitle: "VALORANT no se está ejecutando",
50
+ offlineMsg: "Abrí VALORANT y esta página se actualizará sola",
51
+ retryingIn: "Reintentando en {s}s...",
52
+ level: "Nivel",
53
+ rank: "Rango",
54
+ unranked: "Sin rango",
55
+ rr: "RR",
56
+ wallet: "Billetera",
57
+ vp: "VP",
58
+ rad: "Radianita",
59
+ kc: "Créditos Kingdom",
60
+ fit: "Ajuste de rango",
61
+ dailyOffers: "Ofertas del día",
62
+ featuredBundles: "Colecciones destacadas",
63
+ nightMarket: "Mercado Nocturno",
64
+ resetsIn: "Se reinicia en",
65
+ addToWishlist: "Agregar a lista de deseos",
66
+ searchPlaceholder: "Buscar skin para agregar...",
67
+ inStoreToday: "¡EN LA TIENDA HOY!",
68
+ remove: "Eliminar",
69
+ victory: "VICTORIA",
70
+ defeat: "DERROTA",
71
+ draw: "EMPATE",
72
+ kda: "A/M/A",
73
+ score: "Resultado",
74
+ noMatches: "No se encontraron partidas recientes",
75
+ noFriends: "No hay amigos conectados en este momento",
76
+ onlineFriends: "Amigos conectados",
77
+ addedToWishlist: "Agregado a la lista de deseos",
78
+ removedFromWishlist: "Eliminado de la lista de deseos",
79
+ emptyWishlist: "Tu lista de deseos está vacía. Agregá skins de armas para recibir alertas en la tienda.",
80
+ errorLoading: "Error al cargar {section}",
81
+ },
82
+ };
83
+
84
+ const isSpanish = Boolean(navigator.language && navigator.language.toLowerCase().startsWith("es"));
85
+ const currentLang = isSpanish ? "es" : "en";
86
+
87
+ function esc(value) {
88
+ return String(value ?? "").replace(/[&<>"']/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[c]);
89
+ }
90
+
91
+ function t(key, params) {
92
+ let str = DICT[currentLang][key] || DICT.en[key] || key;
93
+ if (params) {
94
+ for (const [k, v] of Object.entries(params)) {
95
+ str = str.replace("{" + k + "}", v);
96
+ }
97
+ }
98
+ return str;
99
+ }
100
+
101
+ let isRiotOffline = false;
102
+ let offlineRetryTimer = null;
103
+ let retrySecondsLeft = 10;
104
+ let currentActiveTab = "home";
105
+ let storeCountdownTimer = null;
106
+ let cachedSkins = [];
107
+
108
+ function updateStaticTexts() {
109
+ const tabHome = document.getElementById("tab-btn-home");
110
+ const tabStore = document.getElementById("tab-btn-store");
111
+ const tabWishlist = document.getElementById("tab-btn-wishlist");
112
+ const tabMatches = document.getElementById("tab-btn-matches");
113
+ const tabFriends = document.getElementById("tab-btn-friends");
114
+ if (tabHome) tabHome.textContent = t("home");
115
+ if (tabStore) tabStore.textContent = t("store");
116
+ if (tabWishlist) tabWishlist.textContent = t("wishlist");
117
+ if (tabMatches) tabMatches.textContent = t("matches");
118
+ if (tabFriends) tabFriends.textContent = t("friends");
119
+
120
+ const bannerTitle = document.getElementById("banner-title");
121
+ const bannerMsg = document.getElementById("banner-msg");
122
+ const wishlistInput = document.getElementById("wishlist-input");
123
+ const wishlistBtn = document.getElementById("wishlist-add-btn");
124
+ if (bannerTitle) bannerTitle.textContent = t("offlineTitle");
125
+ if (bannerMsg) bannerMsg.textContent = t("offlineMsg");
126
+ if (wishlistInput) wishlistInput.placeholder = t("searchPlaceholder");
127
+ if (wishlistBtn) wishlistBtn.textContent = t("addToWishlist");
128
+ }
129
+
130
+ function handleRiotOffline() {
131
+ if (isRiotOffline) return;
132
+ isRiotOffline = true;
133
+ const banner = document.getElementById("riot-offline-banner");
134
+ if (banner) banner.classList.remove("hidden");
135
+
136
+ retrySecondsLeft = 10;
137
+ const retryEl = document.getElementById("banner-retry");
138
+ if (retryEl) retryEl.textContent = t("retryingIn", { s: retrySecondsLeft });
139
+
140
+ if (offlineRetryTimer) clearInterval(offlineRetryTimer);
141
+ offlineRetryTimer = setInterval(async () => {
142
+ retrySecondsLeft -= 1;
143
+ if (retryEl) retryEl.textContent = t("retryingIn", { s: Math.max(0, retrySecondsLeft) });
144
+ if (retrySecondsLeft <= 0) {
145
+ clearInterval(offlineRetryTimer);
146
+ offlineRetryTimer = null;
147
+ try {
148
+ await apiGet("/api/account/whoami");
149
+ handleRiotOnline();
150
+ } catch {
151
+ isRiotOffline = false;
152
+ handleRiotOffline();
153
+ }
154
+ }
155
+ }, 1000);
156
+ }
157
+
158
+ function handleRiotOnline() {
159
+ if (!isRiotOffline) return;
160
+ isRiotOffline = false;
161
+ if (offlineRetryTimer) {
162
+ clearInterval(offlineRetryTimer);
163
+ offlineRetryTimer = null;
164
+ }
165
+ const banner = document.getElementById("riot-offline-banner");
166
+ if (banner) banner.classList.add("hidden");
167
+ loadTab(currentActiveTab);
168
+ }
169
+
170
+ async function apiGet(endpoint) {
171
+ const res = await fetch(endpoint);
172
+ const data = await res.json().catch(() => ({}));
173
+ if (!res.ok) {
174
+ const code = data?.error?.code;
175
+ if (res.status === 503 && (code === "RIOT_CLIENT_NOT_RUNNING" || code === "RIOT_CLIENT_NOT_READY")) {
176
+ handleRiotOffline();
177
+ }
178
+ throw new Error(data?.error?.message || ("HTTP " + res.status));
179
+ }
180
+ if (isRiotOffline) handleRiotOnline();
181
+ return data;
182
+ }
183
+
184
+ async function apiPost(endpoint, body) {
185
+ const res = await fetch(endpoint, {
186
+ method: "POST",
187
+ headers: { "Content-Type": "application/json" },
188
+ body: JSON.stringify(body),
189
+ });
190
+ const data = await res.json().catch(() => ({}));
191
+ if (!res.ok) {
192
+ const code = data?.error?.code;
193
+ if (res.status === 503 && (code === "RIOT_CLIENT_NOT_RUNNING" || code === "RIOT_CLIENT_NOT_READY")) {
194
+ handleRiotOffline();
195
+ }
196
+ throw new Error(data?.error?.message || ("HTTP " + res.status));
197
+ }
198
+ if (isRiotOffline) handleRiotOnline();
199
+ return data;
200
+ }
201
+
202
+ function formatTimeRemaining(endsAt) {
203
+ if (!endsAt) return "";
204
+ const diff = new Date(endsAt).getTime() - Date.now();
205
+ if (diff <= 0) return "00:00:00";
206
+ const hours = Math.floor(diff / (1000 * 60 * 60));
207
+ const mins = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60));
208
+ const secs = Math.floor((diff % (1000 * 60)) / 1000);
209
+ return [
210
+ String(hours).padStart(2, "0"),
211
+ String(mins).padStart(2, "0"),
212
+ String(secs).padStart(2, "0"),
213
+ ].join(":");
214
+ }
215
+
216
+ async function loadHome() {
217
+ const container = document.getElementById("home-content");
218
+ if (!container) return;
219
+ try {
220
+ const [player, loadout, mmr, wallet] = await Promise.all([
221
+ apiGet("/api/account/whoami"),
222
+ apiGet("/api/account/loadout").catch(() => null),
223
+ apiGet("/api/matches/mmr").catch(() => null),
224
+ apiGet("/api/account/wallet").catch(() => null),
225
+ ]);
226
+
227
+ const cardArt = loadout?.card?.large || loadout?.card?.wide || loadout?.card?.small || "";
228
+ const rank = mmr?.current;
229
+ const rankName = rank?.name || t("unranked");
230
+ const rankIcon = rank?.icon ? '<img src="' + esc(rank.icon) + '" class="rank-icon" alt="' + esc(rankName) + '">' : "";
231
+ const rr = rank?.rating !== null && rank?.rating !== undefined ? rank.rating : 0;
232
+ const fit = mmr?.fit;
233
+ const fitText = fit?.verdict ? (t("fit") + ": " + fit.verdict + (fit.expected?.name ? " (" + fit.expected.name + ")" : "")) : "";
234
+
235
+ const vp = wallet?.valorantPoints ?? 0;
236
+ const rad = wallet?.radianite ?? 0;
237
+ const kc = wallet?.kingdomCredits ?? 0;
238
+
239
+ container.innerHTML = [
240
+ '<div class="home-grid">',
241
+ ' <div class="home-card profile-card">',
242
+ ' <div class="profile-header">',
243
+ cardArt ? ' <img src="' + esc(cardArt) + '" class="profile-card-art" alt="Card">' : '',
244
+ ' <div class="profile-info">',
245
+ ' <h2>' + esc(player.gameName) + '<span class="tag">#' + esc(player.tagLine) + '</span></h2>',
246
+ ' <span class="level-badge">' + t("level") + ' ' + esc(player.accountLevel) + '</span>',
247
+ ' </div>',
248
+ ' </div>',
249
+ ' </div>',
250
+ ' <div class="home-card rank-card">',
251
+ ' <div class="section-title">' + t("rank") + '</div>',
252
+ ' <div class="rank-display">',
253
+ rankIcon,
254
+ ' <div class="rank-details">',
255
+ ' <h3>' + esc(rankName) + '</h3>',
256
+ ' <div class="rr-text">' + esc(rr) + ' ' + t("rr") + '</div>',
257
+ fitText ? ' <span class="fit-badge">' + esc(fitText) + '</span>' : '',
258
+ ' </div>',
259
+ ' </div>',
260
+ ' </div>',
261
+ ' <div class="home-card wallet-card">',
262
+ ' <div class="section-title">' + t("wallet") + '</div>',
263
+ ' <div class="wallet-chips">',
264
+ ' <div class="wallet-chip vp"><span class="wallet-val">' + esc(vp.toLocaleString()) + '</span><span class="wallet-label">' + t("vp") + '</span></div>',
265
+ ' <div class="wallet-chip rad"><span class="wallet-val">' + esc(rad.toLocaleString()) + '</span><span class="wallet-label">' + t("rad") + '</span></div>',
266
+ ' <div class="wallet-chip kc"><span class="wallet-val">' + esc(kc.toLocaleString()) + '</span><span class="wallet-label">' + t("kc") + '</span></div>',
267
+ ' </div>',
268
+ ' </div>',
269
+ '</div>',
270
+ ].join("\\n");
271
+ } catch (err) {
272
+ if (!isRiotOffline) {
273
+ container.innerHTML = '<div class="inline-error">' + t("errorLoading", { section: t("home") }) + ': ' + esc(err.message) + '</div>';
274
+ }
275
+ }
276
+ }
277
+
278
+ async function loadStore() {
279
+ const container = document.getElementById("store-content");
280
+ if (!container) return;
281
+ try {
282
+ const store = await apiGet("/api/store/current");
283
+ if (storeCountdownTimer) clearInterval(storeCountdownTimer);
284
+
285
+ const daily = store?.daily;
286
+ const offers = daily?.offers || [];
287
+ const bundles = store?.bundles?.items || [];
288
+ const nightMarket = store?.nightMarket?.offers || [];
289
+
290
+ let html = '';
291
+
292
+ if (daily) {
293
+ const remaining = formatTimeRemaining(daily.endsAt);
294
+ html += '<div class="section-title"><span>' + t("dailyOffers") + '</span><span class="countdown-timer" id="store-daily-timer">' + t("resetsIn") + ' ' + esc(remaining) + '</span></div>';
295
+ html += '<div class="store-grid">';
296
+ for (const offer of offers) {
297
+ const item = offer.item;
298
+ const icon = item.icon ? '<img src="' + esc(item.icon) + '" class="skin-img" alt="' + esc(item.name) + '">' : '';
299
+ const tierIcon = item.tier?.icon ? '<img src="' + esc(item.tier.icon) + '" class="skin-tier-icon" alt="' + esc(item.tier.name) + '">' : '';
300
+ html += [
301
+ '<div class="skin-card">',
302
+ ' <div class="skin-img-wrap">' + icon + '</div>',
303
+ ' <div class="skin-title">' + esc(item.name) + '</div>',
304
+ ' <div class="skin-weapon">' + (item.weapon || '') + '</div>',
305
+ ' <div class="skin-meta">',
306
+ ' <span class="skin-price">' + esc(offer.cost.amount.toLocaleString()) + ' VP</span>',
307
+ tierIcon,
308
+ ' </div>',
309
+ '</div>',
310
+ ].join("");
311
+ }
312
+ html += '</div>';
313
+
314
+ storeCountdownTimer = setInterval(() => {
315
+ const timerEl = document.getElementById("store-daily-timer");
316
+ if (timerEl) {
317
+ timerEl.textContent = t("resetsIn") + ' ' + formatTimeRemaining(daily.endsAt);
318
+ }
319
+ }, 1000);
320
+ }
321
+
322
+ if (bundles.length > 0) {
323
+ html += '<div class="section-title">' + t("featuredBundles") + '</div>';
324
+ for (const b of bundles) {
325
+ const img = b.promoImage || b.icon;
326
+ const banner = img ? '<img src="' + esc(img) + '" class="bundle-banner" alt="' + esc(b.name) + '">' : '';
327
+ const price = b.totalDiscounted || b.totalBase || 0;
328
+ html += [
329
+ '<div class="bundle-card">',
330
+ banner,
331
+ ' <div class="bundle-info">',
332
+ ' <div><h3>' + esc(b.name) + '</h3>' + (b.description && b.description !== b.name ? '<p class="bundle-desc">' + esc(b.description) + '</p>' : '') + '</div>',
333
+ ' <div class="skin-price">' + esc(price.toLocaleString()) + ' VP</div>',
334
+ ' </div>',
335
+ '</div>',
336
+ ].join("");
337
+ }
338
+ }
339
+
340
+ if (nightMarket.length > 0) {
341
+ html += '<div class="section-title">' + t("nightMarket") + '</div>';
342
+ html += '<div class="store-grid">';
343
+ for (const nm of nightMarket) {
344
+ const item = nm.item;
345
+ const icon = item.icon ? '<img src="' + esc(item.icon) + '" class="skin-img" alt="' + esc(item.name) + '">' : '';
346
+ html += [
347
+ '<div class="skin-card">',
348
+ ' <div class="skin-img-wrap">' + icon + '</div>',
349
+ ' <div class="skin-title">' + esc(item.name) + '</div>',
350
+ ' <div class="skin-weapon">' + (item.weapon || '') + '</div>',
351
+ ' <div class="skin-meta">',
352
+ ' <div><span class="price-strike">' + esc(nm.cost.amount) + '</span><span class="skin-price">' + esc(nm.discountedCost.amount.toLocaleString()) + ' VP</span></div>',
353
+ ' <span class="hit-badge hit-badge-inline">-' + esc(nm.discountPercent) + '%</span>',
354
+ ' </div>',
355
+ '</div>',
356
+ ].join("");
357
+ }
358
+ html += '</div>';
359
+ }
360
+
361
+ container.innerHTML = html;
362
+ } catch (err) {
363
+ if (!isRiotOffline) {
364
+ container.innerHTML = '<div class="inline-error">' + t("errorLoading", { section: t("store") }) + ': ' + esc(err.message) + '</div>';
365
+ }
366
+ }
367
+ }
368
+
369
+ async function loadWishlist() {
370
+ const container = document.getElementById("wishlist-content");
371
+ if (!container) return;
372
+ try {
373
+ const [wishlist, check] = await Promise.all([
374
+ apiGet("/api/store/wishlist"),
375
+ apiGet("/api/store/wishlistCheck").catch(() => ({ hits: [] })),
376
+ ]);
377
+
378
+ const hitUuids = new Set((check?.hits || []).map((h) => (h.item?.uuid || "").toLowerCase()));
379
+ const skins = wishlist?.skins || [];
380
+
381
+ if (skins.length === 0) {
382
+ container.innerHTML = '<div class="loading-placeholder">' + t("emptyWishlist") + '</div>';
383
+ return;
384
+ }
385
+
386
+ let html = '<div class="store-grid">';
387
+ for (const s of skins) {
388
+ const isHit = hitUuids.has((s.uuid || "").toLowerCase());
389
+ const hitBadge = isHit ? '<span class="hit-badge">' + t("inStoreToday") + '</span>' : '';
390
+ const catSkin = cachedSkins.find((cs) => cs.uuid.toLowerCase() === (s.uuid || "").toLowerCase());
391
+ const icon = catSkin?.icon ? '<img src="' + esc(catSkin.icon) + '" class="skin-img" alt="' + esc(s.name) + '">' : '';
392
+ const weapon = catSkin?.weapon || "";
393
+ const tierIcon = catSkin?.tier?.icon ? '<img src="' + esc(catSkin.tier.icon) + '" class="skin-tier-icon" alt="">' : '';
394
+
395
+ html += [
396
+ '<div class="skin-card' + (isHit ? ' in-store' : '') + '">',
397
+ hitBadge,
398
+ ' <div class="skin-img-wrap">' + icon + '</div>',
399
+ ' <div class="skin-title">' + esc(s.name) + '</div>',
400
+ ' <div class="skin-weapon">' + esc(weapon) + '</div>',
401
+ ' <div class="skin-meta">',
402
+ tierIcon,
403
+ ' <button class="btn-remove" data-remove-skin="' + esc(s.uuid) + '">' + t("remove") + '</button>',
404
+ ' </div>',
405
+ '</div>',
406
+ ].join("");
407
+ }
408
+ html += '</div>';
409
+
410
+ container.innerHTML = html;
411
+
412
+ const removeBtns = container.querySelectorAll("[data-remove-skin]");
413
+ removeBtns.forEach((btn) => {
414
+ btn.addEventListener("click", async (e) => {
415
+ const skinUuid = e.currentTarget.getAttribute("data-remove-skin");
416
+ try {
417
+ await apiPost("/api/store/wishlistRemove", { skin: skinUuid });
418
+ loadWishlist();
419
+ } catch (err) {
420
+ alert(err.message);
421
+ }
422
+ });
423
+ });
424
+ } catch (err) {
425
+ if (!isRiotOffline) {
426
+ container.innerHTML = '<div class="inline-error">' + t("errorLoading", { section: t("wishlist") }) + ': ' + esc(err.message) + '</div>';
427
+ }
428
+ }
429
+ }
430
+
431
+ async function loadMatches() {
432
+ const container = document.getElementById("matches-content");
433
+ if (!container) return;
434
+ try {
435
+ const [summaries, history, player] = await Promise.all([
436
+ apiGet("/api/matches/list?count=10"),
437
+ apiGet("/api/matches/rankHistory?count=10").catch(() => []),
438
+ apiGet("/api/account/whoami").catch(() => null),
439
+ ]);
440
+
441
+ const myPuuid = player?.puuid;
442
+ const rankUpdates = Array.isArray(history) ? history : [];
443
+ const matchSummaries = Array.isArray(summaries) ? summaries.slice(0, 10) : [];
444
+
445
+ if (matchSummaries.length === 0) {
446
+ container.innerHTML = '<div class="loading-placeholder">' + t("noMatches") + '</div>';
447
+ return;
448
+ }
449
+
450
+ const matchDetails = await Promise.all(
451
+ matchSummaries.map((m) => apiGet("/api/matches/get?id=" + m.id).catch(() => null))
452
+ );
453
+
454
+ let html = '<div class="matches-list">';
455
+ for (let i = 0; i < matchSummaries.length; i++) {
456
+ const summary = matchSummaries[i];
457
+ const match = matchDetails[i];
458
+ const matchId = summary.id;
459
+ const rrUpdate = rankUpdates.find((u) => u.matchId === matchId);
460
+
461
+ let resultClass = "draw";
462
+ let resultText = t("draw");
463
+ let scoreText = "-";
464
+ let agentName = "";
465
+ let agentIcon = "";
466
+ let kdaText = "-";
467
+
468
+ if (match) {
469
+ const me = match.players?.find((p) => p.puuid === myPuuid) || match.players?.[0];
470
+ if (me) {
471
+ agentName = me.agent?.name || "";
472
+ agentIcon = me.agent?.icon ? '<img src="' + esc(me.agent.icon) + '" class="agent-icon" alt="' + esc(agentName) + '">' : '';
473
+ if (me.stats) {
474
+ kdaText = me.stats.kills + ' / ' + me.stats.deaths + ' / ' + me.stats.assists;
475
+ }
476
+ if (match.self) {
477
+ if (match.self.won === true) {
478
+ resultClass = "win";
479
+ resultText = t("victory");
480
+ } else if (match.self.won === false) {
481
+ resultClass = "loss";
482
+ resultText = t("defeat");
483
+ }
484
+ }
485
+ }
486
+ if (match.teams && match.teams.length >= 2) {
487
+ const blue = match.teams.find((tm) => tm.id === "Blue")?.roundsWon ?? 0;
488
+ const red = match.teams.find((tm) => tm.id === "Red")?.roundsWon ?? 0;
489
+ scoreText = blue + ' - ' + red;
490
+ }
491
+ }
492
+
493
+ let rrHtml = '';
494
+ if (rrUpdate && rrUpdate.earned !== undefined && rrUpdate.earned !== null) {
495
+ const earned = rrUpdate.earned;
496
+ const isPlus = earned > 0;
497
+ rrHtml = '<span class="rr-change ' + (isPlus ? 'plus' : 'minus') + '">' + (isPlus ? '+' : '') + earned + ' ' + t("rr") + '</span>';
498
+ }
499
+
500
+ const mapName = summary.map?.name || "Valorant";
501
+
502
+ html += [
503
+ '<div class="match-item ' + esc(resultClass) + '">',
504
+ ' <div><span class="match-badge ' + esc(resultClass) + '">' + esc(resultText) + '</span></div>',
505
+ ' <div class="match-agent">' + agentIcon + '<div><strong>' + esc(agentName) + '</strong><div class="muted-small">' + esc(mapName) + '</div></div></div>',
506
+ ' <div class="match-score">' + esc(scoreText) + '</div>',
507
+ ' <div class="match-kda"><div class="muted-tiny">' + t("kda") + '</div>' + esc(kdaText) + '</div>',
508
+ ' <div>' + rrHtml + '</div>',
509
+ '</div>',
510
+ ].join("");
511
+ }
512
+ html += '</div>';
513
+
514
+ container.innerHTML = html;
515
+ } catch (err) {
516
+ if (!isRiotOffline) {
517
+ container.innerHTML = '<div class="inline-error">' + t("errorLoading", { section: t("matches") }) + ': ' + esc(err.message) + '</div>';
518
+ }
519
+ }
520
+ }
521
+
522
+ async function loadFriends() {
523
+ const container = document.getElementById("friends-content");
524
+ if (!container) return;
525
+ try {
526
+ const friends = await apiGet("/api/social/friends");
527
+ const list = Array.isArray(friends) ? friends : [];
528
+
529
+ const onlineFriends = list.filter((f) => {
530
+ const st = f.presence?.state;
531
+ return st && st !== "offline";
532
+ });
533
+
534
+ if (onlineFriends.length === 0) {
535
+ container.innerHTML = '<div class="loading-placeholder">' + t("noFriends") + '</div>';
536
+ return;
537
+ }
538
+
539
+ let html = '<div class="section-title">' + t("onlineFriends") + ' (' + esc(onlineFriends.length) + ')</div>';
540
+ html += '<div class="friends-list">';
541
+ for (const f of onlineFriends) {
542
+ const presence = f.presence || {};
543
+ const state = presence.state || "online";
544
+ let stateClass = "online";
545
+ if (state === "ingame") stateClass = "in-game";
546
+ if (state === "away" || state === "mobile") stateClass = "away";
547
+
548
+ let activity = state;
549
+ if (state === "ingame") {
550
+ const map = presence.map?.name || "";
551
+ const score = presence.score ? ' (' + presence.score.ally + '-' + presence.score.enemy + ')' : '';
552
+ activity = "In Match" + (map ? ' · ' + map : '') + score;
553
+ } else if (state === "menus") {
554
+ activity = "In Menus";
555
+ } else if (state === "pregame") {
556
+ activity = "In Agent Select";
557
+ }
558
+
559
+ html += [
560
+ '<div class="friend-card">',
561
+ ' <span class="presence-dot ' + esc(stateClass) + '"></span>',
562
+ ' <div class="friend-info">',
563
+ ' <span class="friend-name">' + esc(f.gameName) + '<span class="tag">#' + esc(f.tagLine) + '</span></span>',
564
+ ' <span class="friend-activity">' + esc(activity) + '</span>',
565
+ ' </div>',
566
+ '</div>',
567
+ ].join("");
568
+ }
569
+ html += '</div>';
570
+
571
+ container.innerHTML = html;
572
+ } catch (err) {
573
+ if (!isRiotOffline) {
574
+ container.innerHTML = '<div class="inline-error">' + t("errorLoading", { section: t("friends") }) + ': ' + esc(err.message) + '</div>';
575
+ }
576
+ }
577
+ }
578
+
579
+ function loadTab(tabName) {
580
+ currentActiveTab = tabName;
581
+ document.querySelectorAll(".tab-btn").forEach((btn) => {
582
+ btn.classList.toggle("active", btn.getAttribute("data-tab") === tabName);
583
+ });
584
+ document.querySelectorAll(".tab-pane").forEach((pane) => {
585
+ pane.classList.toggle("active", pane.id === "tab-" + tabName);
586
+ });
587
+
588
+ if (tabName === "home") loadHome();
589
+ if (tabName === "store") loadStore();
590
+ if (tabName === "wishlist") loadWishlist();
591
+ if (tabName === "matches") loadMatches();
592
+ if (tabName === "friends") loadFriends();
593
+ }
594
+
595
+ async function initCatalogueAutocomplete() {
596
+ try {
597
+ const skins = await apiGet("/api/store/skins");
598
+ if (Array.isArray(skins)) {
599
+ cachedSkins = skins;
600
+ const datalist = document.getElementById("skins-datalist");
601
+ if (datalist) {
602
+ datalist.innerHTML = skins
603
+ .map((s) => '<option value="' + esc(s.name) + '">' + esc(s.weapon) + ' · ' + esc(s.name) + '</option>')
604
+ .join("");
605
+ }
606
+ }
607
+ } catch {
608
+ // Non-fatal if catalogue cannot be preloaded
609
+ }
610
+ }
611
+
612
+ function initWishlistAdd() {
613
+ const btn = document.getElementById("wishlist-add-btn");
614
+ const input = document.getElementById("wishlist-input");
615
+ const status = document.getElementById("wishlist-status");
616
+ if (!btn || !input) return;
617
+
618
+ btn.addEventListener("click", async () => {
619
+ const val = input.value.trim();
620
+ if (!val) return;
621
+ btn.disabled = true;
622
+ if (status) status.textContent = "";
623
+
624
+ try {
625
+ await apiPost("/api/store/wishlistAdd", { skin: val });
626
+ input.value = "";
627
+ if (status) {
628
+ status.style.color = "var(--success)";
629
+ status.textContent = t("addedToWishlist");
630
+ }
631
+ loadWishlist();
632
+ } catch (err) {
633
+ if (status) {
634
+ status.style.color = "var(--danger)";
635
+ status.textContent = err.message;
636
+ }
637
+ } finally {
638
+ btn.disabled = false;
639
+ }
640
+ });
641
+
642
+ input.addEventListener("keydown", (e) => {
643
+ if (e.key === "Enter") {
644
+ btn.click();
645
+ }
646
+ });
647
+ }
648
+
649
+ function initTabs() {
650
+ document.querySelectorAll(".tab-btn").forEach((btn) => {
651
+ btn.addEventListener("click", () => {
652
+ const tab = btn.getAttribute("data-tab");
653
+ if (tab) loadTab(tab);
654
+ });
655
+ });
656
+ }
657
+
658
+ document.addEventListener("DOMContentLoaded", () => {
659
+ updateStaticTexts();
660
+ initTabs();
661
+ initWishlistAdd();
662
+ initCatalogueAutocomplete();
663
+ loadTab("home");
664
+ });
665
+ })();
666
+ `;
667
+ }
668
+ //# sourceMappingURL=dashboardJs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dashboardJs.js","sourceRoot":"","sources":["../../../src/serve/dashboard/dashboardJs.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,iBAAiB;IAC/B,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAwpBR,CAAC;AACF,CAAC"}
@@ -0,0 +1,4 @@
1
+ export { renderDashboardHtml } from "./dashboardHtml.js";
2
+ export { renderDashboardCss } from "./dashboardCss.js";
3
+ export { renderDashboardJs } from "./dashboardJs.js";
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/serve/dashboard/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC"}