@kaikybrofc/omnizap-system 2.2.3 → 2.2.4

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.
@@ -0,0 +1,367 @@
1
+ <!doctype html>
2
+ <html lang="pt-BR">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>OmniZap System | Minha Conta</title>
7
+ <meta name="description" content="Painel de conta do OmniZap System com dados do usuário vinculado ao WhatsApp." />
8
+ <meta name="robots" content="noindex, nofollow" />
9
+ <meta name="theme-color" content="#0b1020" />
10
+ <link rel="canonical" href="https://omnizap.shop/user/" />
11
+ <link rel="icon" type="image/jpeg" href="https://iili.io/FC3FABe.jpg" />
12
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
13
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
14
+ <link
15
+ href="https://fonts.googleapis.com/css2?family=Manrope:wght@400;500;600;700;800&family=Sora:wght@500;600;700&display=swap"
16
+ rel="stylesheet"
17
+ />
18
+ <style>
19
+ :root {
20
+ --bg: #0b1020;
21
+ --bg-2: #121a2f;
22
+ --card: #121f39cf;
23
+ --line: #2f466f;
24
+ --text: #e6edf7;
25
+ --muted: #9bb0cf;
26
+ --primary: #22c55e;
27
+ --radius: 18px;
28
+ }
29
+
30
+ * {
31
+ box-sizing: border-box;
32
+ }
33
+
34
+ html,
35
+ body {
36
+ margin: 0;
37
+ padding: 0;
38
+ min-height: 100%;
39
+ }
40
+
41
+ body {
42
+ color: var(--text);
43
+ font-family: "Manrope", system-ui, sans-serif;
44
+ background:
45
+ radial-gradient(55rem 22rem at -10% -10%, #1fce6f30, transparent 60%),
46
+ radial-gradient(55rem 22rem at 110% -8%, #43b8ff22, transparent 56%),
47
+ linear-gradient(160deg, var(--bg), var(--bg-2));
48
+ min-height: 100vh;
49
+ display: flex;
50
+ align-items: center;
51
+ justify-content: center;
52
+ padding: 24px 14px;
53
+ }
54
+
55
+ .page {
56
+ width: min(840px, 100%);
57
+ border: 1px solid #314b74;
58
+ border-radius: 24px;
59
+ background: linear-gradient(150deg, #0f1a31e8, #0f1b34ee);
60
+ box-shadow: 0 18px 44px #0209166e, inset 0 1px 0 #95c1ff1a;
61
+ overflow: hidden;
62
+ }
63
+
64
+ .head {
65
+ border-bottom: 1px solid #2a3f63;
66
+ padding: 20px 22px;
67
+ display: flex;
68
+ align-items: center;
69
+ justify-content: space-between;
70
+ gap: 14px;
71
+ flex-wrap: wrap;
72
+ }
73
+
74
+ .brand {
75
+ display: inline-flex;
76
+ align-items: center;
77
+ gap: 10px;
78
+ font-family: "Sora", sans-serif;
79
+ font-weight: 700;
80
+ letter-spacing: 0.2px;
81
+ text-decoration: none;
82
+ color: var(--text);
83
+ }
84
+
85
+ .brand img {
86
+ width: 30px;
87
+ height: 30px;
88
+ border-radius: 50%;
89
+ border: 1px solid #33507e;
90
+ object-fit: cover;
91
+ }
92
+
93
+ .head-actions {
94
+ display: flex;
95
+ flex-wrap: wrap;
96
+ gap: 8px;
97
+ }
98
+
99
+ .btn {
100
+ border: 1px solid var(--line);
101
+ border-radius: 12px;
102
+ background: #101a2f;
103
+ color: var(--text);
104
+ text-decoration: none;
105
+ padding: 9px 12px;
106
+ font-size: 14px;
107
+ font-weight: 600;
108
+ transition: transform .2s ease, border-color .2s ease, box-shadow .2s ease;
109
+ cursor: pointer;
110
+ }
111
+
112
+ .btn:hover {
113
+ transform: translateY(-1px);
114
+ border-color: #4e6ea1;
115
+ box-shadow: 0 10px 20px #02091650;
116
+ }
117
+
118
+ .btn.primary {
119
+ border-color: transparent;
120
+ background: linear-gradient(90deg, var(--primary), #16a34a);
121
+ color: #052412;
122
+ box-shadow: 0 10px 22px #22c55e30;
123
+ }
124
+
125
+ .content {
126
+ padding: 24px 22px;
127
+ display: grid;
128
+ gap: 14px;
129
+ }
130
+
131
+ h1 {
132
+ margin: 0;
133
+ font-family: "Sora", sans-serif;
134
+ font-size: clamp(26px, 3.6vw, 36px);
135
+ line-height: 1.08;
136
+ letter-spacing: -0.02em;
137
+ background: linear-gradient(90deg, #eef5ff 0%, #8fd7ff 46%, #6ce8b5 100%);
138
+ -webkit-background-clip: text;
139
+ background-clip: text;
140
+ color: transparent;
141
+ }
142
+
143
+ .lead {
144
+ margin: 0;
145
+ color: #bfd1ea;
146
+ line-height: 1.6;
147
+ font-size: 16px;
148
+ }
149
+
150
+ .card {
151
+ border: 1px solid #334f7bc7;
152
+ border-radius: var(--radius);
153
+ background: var(--card);
154
+ padding: 16px;
155
+ display: grid;
156
+ gap: 12px;
157
+ backdrop-filter: blur(8px);
158
+ }
159
+
160
+ .status {
161
+ margin: 0;
162
+ color: #dce8fa;
163
+ font-size: 15px;
164
+ line-height: 1.5;
165
+ }
166
+
167
+ .error {
168
+ margin: 0;
169
+ border: 1px solid #a74949;
170
+ border-radius: 12px;
171
+ background: #3b181899;
172
+ color: #ffd7d7;
173
+ padding: 9px 10px;
174
+ font-size: 14px;
175
+ }
176
+
177
+ .profile {
178
+ display: grid;
179
+ grid-template-columns: auto 1fr;
180
+ gap: 12px;
181
+ align-items: center;
182
+ border: 1px solid #31517fb5;
183
+ border-radius: 14px;
184
+ background: #0f1a30bf;
185
+ padding: 12px;
186
+ }
187
+
188
+ .avatar {
189
+ width: 66px;
190
+ height: 66px;
191
+ border-radius: 16px;
192
+ border: 1px solid #3c5a89;
193
+ object-fit: cover;
194
+ background: #0b1323;
195
+ }
196
+
197
+ .profile-meta h2 {
198
+ margin: 0 0 4px;
199
+ font-size: 20px;
200
+ line-height: 1.2;
201
+ }
202
+
203
+ .profile-meta p {
204
+ margin: 0;
205
+ color: var(--muted);
206
+ font-size: 14px;
207
+ line-height: 1.45;
208
+ word-break: break-word;
209
+ }
210
+
211
+ .grid {
212
+ display: grid;
213
+ grid-template-columns: repeat(4, minmax(0, 1fr));
214
+ gap: 8px;
215
+ }
216
+
217
+ .metric {
218
+ border: 1px solid #31517fb5;
219
+ border-radius: 12px;
220
+ background: #0f1a30bf;
221
+ padding: 10px;
222
+ }
223
+
224
+ .metric-label {
225
+ margin: 0 0 4px;
226
+ color: #9bb0cf;
227
+ font-size: 12px;
228
+ }
229
+
230
+ .metric-value {
231
+ margin: 0;
232
+ color: #e8f1ff;
233
+ font-size: 19px;
234
+ font-weight: 800;
235
+ letter-spacing: -0.01em;
236
+ }
237
+
238
+ .summary {
239
+ border: 1px solid #2b8b5d9c;
240
+ border-radius: 12px;
241
+ background: #112d2094;
242
+ padding: 10px;
243
+ display: grid;
244
+ gap: 6px;
245
+ color: #dfffe8;
246
+ font-size: 14px;
247
+ }
248
+
249
+ .summary strong {
250
+ color: #f4fff8;
251
+ }
252
+
253
+ .actions {
254
+ display: flex;
255
+ flex-wrap: wrap;
256
+ gap: 8px;
257
+ }
258
+
259
+ .footer {
260
+ margin: 0;
261
+ color: #7f94b8;
262
+ font-size: 12px;
263
+ }
264
+
265
+ @media (max-width: 760px) {
266
+ .grid {
267
+ grid-template-columns: repeat(2, minmax(0, 1fr));
268
+ }
269
+ }
270
+
271
+ @media (max-width: 620px) {
272
+ .head {
273
+ padding: 16px;
274
+ }
275
+
276
+ .content {
277
+ padding: 18px 16px;
278
+ }
279
+
280
+ .profile {
281
+ grid-template-columns: 1fr;
282
+ text-align: center;
283
+ }
284
+
285
+ .avatar {
286
+ margin-inline: auto;
287
+ }
288
+
289
+ .actions .btn {
290
+ width: 100%;
291
+ text-align: center;
292
+ }
293
+ }
294
+ </style>
295
+ </head>
296
+ <body>
297
+ <main id="user-app-root" class="page" data-api-base-path="/api/sticker-packs" data-login-path="/login" data-stickers-path="/stickers">
298
+ <header class="head">
299
+ <a href="/" class="brand">
300
+ <img src="https://iili.io/FC3FABe.jpg" alt="OmniZap" loading="lazy" decoding="async" />
301
+ <span>OmniZap System</span>
302
+ </a>
303
+ <div class="head-actions">
304
+ <a class="btn" href="/">Home</a>
305
+ <a id="user-manage-head-link" class="btn" href="/stickers/perfil">Gerenciar Stickers</a>
306
+ </div>
307
+ </header>
308
+
309
+ <section class="content">
310
+ <h1>Minha Conta</h1>
311
+ <p class="lead">Informações da sua conta vinculada ao OmniZap e atalhos rápidos de acesso.</p>
312
+
313
+ <article class="card">
314
+ <p id="user-status" class="status">Carregando dados da conta...</p>
315
+ <p id="user-error" class="error" hidden></p>
316
+
317
+ <div id="user-profile" class="profile" hidden>
318
+ <img id="user-avatar" class="avatar" src="https://iili.io/FC3FABe.jpg" alt="Avatar do usuário" />
319
+ <div class="profile-meta">
320
+ <h2 id="user-name">Conta</h2>
321
+ <p id="user-email"></p>
322
+ <p id="user-whatsapp"></p>
323
+ </div>
324
+ </div>
325
+
326
+ <div id="user-grid" class="grid" hidden>
327
+ <article class="metric">
328
+ <p class="metric-label">Packs</p>
329
+ <p id="metric-packs" class="metric-value">0</p>
330
+ </article>
331
+ <article class="metric">
332
+ <p class="metric-label">Stickers</p>
333
+ <p id="metric-stickers" class="metric-value">0</p>
334
+ </article>
335
+ <article class="metric">
336
+ <p class="metric-label">Downloads</p>
337
+ <p id="metric-downloads" class="metric-value">0</p>
338
+ </article>
339
+ <article class="metric">
340
+ <p class="metric-label">Likes</p>
341
+ <p id="metric-likes" class="metric-value">0</p>
342
+ </article>
343
+ </div>
344
+
345
+ <div id="user-summary" class="summary" hidden>
346
+ <div><strong>Owner JID:</strong> <span id="user-owner-jid"></span></div>
347
+ <div><strong>Google ID:</strong> <span id="user-google-sub"></span></div>
348
+ <div><strong>Sessão expira:</strong> <span id="user-expires-at"></span></div>
349
+ </div>
350
+
351
+ <div id="user-actions" class="actions" hidden>
352
+ <a id="user-chat-link" class="btn primary" href="https://api.whatsapp.com/send/?text=%2Fmenu&type=custom_url&app_absent=0" target="_blank" rel="noreferrer noopener">
353
+ Voltar ao chat do bot (/menu)
354
+ </a>
355
+ <a id="user-manage-main-link" class="btn" href="/stickers/perfil">Gerenciar meus stickers</a>
356
+ <a class="btn" href="/">Voltar para Home</a>
357
+ <button id="user-logout-btn" type="button" class="btn">Encerrar sessão</button>
358
+ </div>
359
+ </article>
360
+
361
+ <p class="footer">OmniZap System · <span id="user-current-year"></span></p>
362
+ </section>
363
+ </main>
364
+
365
+ <script type="module" src="/js/apps/userApp.js?v=20260228-user-page-v1"></script>
366
+ </body>
367
+ </html>
@@ -2,6 +2,23 @@
2
2
 
3
3
  import fs from 'node:fs/promises';
4
4
  import path from 'node:path';
5
+ import { URLSearchParams } from 'node:url';
6
+
7
+ const TARGET_SOURCE_EXTENSIONS = new Set(['.html', '.js', '.mjs', '.css']);
8
+ const ASSET_SUFFIX_PATTERN =
9
+ String.raw`\.(?:js|mjs|cjs|css|png|jpe?g|gif|svg|webp|ico|json|map|woff2?|ttf|eot)(?:\?[^"'#\s)]*)?(?:#[^"' \s)]*)?`;
10
+ const HTML_ATTRIBUTE_ASSET_PATTERN = new RegExp(
11
+ String.raw`((?:src|href|poster)=["'])([^"']+?${ASSET_SUFFIX_PATTERN})(["'])`,
12
+ 'gi',
13
+ );
14
+ const QUOTED_LOCAL_ASSET_PATTERN = new RegExp(
15
+ String.raw`(["'])((?:\/|\.{1,2}\/)[^"'\s]+?${ASSET_SUFFIX_PATTERN})\1`,
16
+ 'gi',
17
+ );
18
+ const CSS_URL_ASSET_PATTERN = new RegExp(
19
+ String.raw`(url\(\s*["']?)([^"')\s]+?${ASSET_SUFFIX_PATTERN})(["']?\s*\))`,
20
+ 'gi',
21
+ );
5
22
 
6
23
  const usage = () => {
7
24
  console.error('Uso: node scripts/cache-bust.mjs --dir <diretorio> --version <build_id>');
@@ -30,7 +47,7 @@ const parseArgs = (argv) => {
30
47
  return options;
31
48
  };
32
49
 
33
- const listHtmlFiles = async (rootDir) => {
50
+ const listSourceFiles = async (rootDir) => {
34
51
  const output = [];
35
52
  const stack = [rootDir];
36
53
 
@@ -44,7 +61,9 @@ const listHtmlFiles = async (rootDir) => {
44
61
  stack.push(absolutePath);
45
62
  continue;
46
63
  }
47
- if (entry.isFile() && absolutePath.toLowerCase().endsWith('.html')) {
64
+ if (!entry.isFile()) continue;
65
+ const extension = path.extname(absolutePath).toLowerCase();
66
+ if (TARGET_SOURCE_EXTENSIONS.has(extension)) {
48
67
  output.push(absolutePath);
49
68
  }
50
69
  }
@@ -53,6 +72,27 @@ const listHtmlFiles = async (rootDir) => {
53
72
  return output;
54
73
  };
55
74
 
75
+ const isLocalAssetPath = (assetPath) => {
76
+ const value = String(assetPath || '').trim();
77
+ if (!value) return false;
78
+
79
+ const lower = value.toLowerCase();
80
+ if (
81
+ lower.startsWith('http://') ||
82
+ lower.startsWith('https://') ||
83
+ lower.startsWith('//') ||
84
+ lower.startsWith('data:') ||
85
+ lower.startsWith('mailto:') ||
86
+ lower.startsWith('tel:') ||
87
+ lower.startsWith('javascript:') ||
88
+ lower.startsWith('#')
89
+ ) {
90
+ return false;
91
+ }
92
+
93
+ return value.startsWith('/') || value.startsWith('./') || value.startsWith('../');
94
+ };
95
+
56
96
  const withVersion = (assetPath, version) => {
57
97
  const [withoutHash, hash = ''] = assetPath.split('#', 2);
58
98
  const [pathname, query = ''] = withoutHash.split('?', 2);
@@ -62,13 +102,27 @@ const withVersion = (assetPath, version) => {
62
102
  return `${pathname}?${queryString}${hash ? `#${hash}` : ''}`;
63
103
  };
64
104
 
65
- const applyCacheBustToHtml = (html, version) => {
66
- const pattern = /((?:src|href)=["'])(\/(?:js|css)\/[^"']+)(["'])/gi;
105
+ const applyCacheBustToSource = (source, version) => {
67
106
  let referencesUpdated = 0;
68
-
69
- const output = html.replace(pattern, (fullMatch, prefix, assetPath, suffix) => {
107
+ const rewrite = (assetPath) => {
108
+ if (!isLocalAssetPath(assetPath)) return assetPath;
70
109
  const nextPath = withVersion(assetPath, version);
71
110
  if (nextPath !== assetPath) referencesUpdated += 1;
111
+ return nextPath;
112
+ };
113
+
114
+ let output = source.replace(HTML_ATTRIBUTE_ASSET_PATTERN, (fullMatch, prefix, assetPath, suffix) => {
115
+ const nextPath = rewrite(assetPath);
116
+ return `${prefix}${nextPath}${suffix}`;
117
+ });
118
+
119
+ output = output.replace(QUOTED_LOCAL_ASSET_PATTERN, (fullMatch, quote, assetPath) => {
120
+ const nextPath = rewrite(assetPath);
121
+ return `${quote}${nextPath}${quote}`;
122
+ });
123
+
124
+ output = output.replace(CSS_URL_ASSET_PATTERN, (fullMatch, prefix, assetPath, suffix) => {
125
+ const nextPath = rewrite(assetPath);
72
126
  return `${prefix}${nextPath}${suffix}`;
73
127
  });
74
128
 
@@ -84,21 +138,21 @@ const main = async () => {
84
138
  }
85
139
 
86
140
  const targetDir = path.resolve(options.dir);
87
- const htmlFiles = await listHtmlFiles(targetDir);
141
+ const sourceFiles = await listSourceFiles(targetDir);
88
142
  let filesUpdated = 0;
89
143
  let referencesUpdated = 0;
90
144
 
91
- for (const filePath of htmlFiles) {
145
+ for (const filePath of sourceFiles) {
92
146
  const current = await fs.readFile(filePath, 'utf8');
93
- const { output, referencesUpdated: fileRefs } = applyCacheBustToHtml(current, options.version);
147
+ const { output, referencesUpdated: fileRefs } = applyCacheBustToSource(current, options.version);
94
148
  if (output !== current) {
95
149
  await fs.writeFile(filePath, output, 'utf8');
96
150
  filesUpdated += 1;
97
- referencesUpdated += fileRefs;
98
151
  }
152
+ referencesUpdated += fileRefs;
99
153
  }
100
154
 
101
- console.log(`[cache-bust] version=${options.version} files=${filesUpdated} refs=${referencesUpdated}`);
155
+ console.log(`[cache-bust] version=${options.version} scanned=${sourceFiles.length} files=${filesUpdated} refs=${referencesUpdated}`);
102
156
  };
103
157
 
104
158
  main().catch((error) => {