agent-messenger 2.1.0 → 2.3.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.
- package/.claude-plugin/plugin.json +1 -1
- package/.env.template +35 -17
- package/README.md +7 -7
- package/bun.lock +31 -7
- package/dist/package.json +5 -3
- package/dist/src/platforms/channeltalk/commands/auth.d.ts.map +1 -1
- package/dist/src/platforms/channeltalk/commands/auth.js +35 -28
- package/dist/src/platforms/channeltalk/commands/auth.js.map +1 -1
- package/dist/src/platforms/channeltalk/ensure-auth.js +6 -6
- package/dist/src/platforms/channeltalk/ensure-auth.js.map +1 -1
- package/dist/src/platforms/channeltalk/token-extractor.d.ts +23 -1
- package/dist/src/platforms/channeltalk/token-extractor.d.ts.map +1 -1
- package/dist/src/platforms/channeltalk/token-extractor.js +299 -29
- package/dist/src/platforms/channeltalk/token-extractor.js.map +1 -1
- package/dist/src/platforms/discord/commands/auth.d.ts.map +1 -1
- package/dist/src/platforms/discord/commands/auth.js +57 -49
- package/dist/src/platforms/discord/commands/auth.js.map +1 -1
- package/dist/src/platforms/discord/ensure-auth.js +3 -3
- package/dist/src/platforms/discord/ensure-auth.js.map +1 -1
- package/dist/src/platforms/discord/token-extractor.d.ts +6 -1
- package/dist/src/platforms/discord/token-extractor.d.ts.map +1 -1
- package/dist/src/platforms/discord/token-extractor.js +167 -14
- package/dist/src/platforms/discord/token-extractor.js.map +1 -1
- package/dist/src/platforms/instagram/client.d.ts +2 -0
- package/dist/src/platforms/instagram/client.d.ts.map +1 -1
- package/dist/src/platforms/instagram/client.js +2 -2
- package/dist/src/platforms/instagram/client.js.map +1 -1
- package/dist/src/platforms/instagram/commands/auth.d.ts.map +1 -1
- package/dist/src/platforms/instagram/commands/auth.js +107 -14
- package/dist/src/platforms/instagram/commands/auth.js.map +1 -1
- package/dist/src/platforms/instagram/ensure-auth.d.ts.map +1 -1
- package/dist/src/platforms/instagram/ensure-auth.js +57 -11
- package/dist/src/platforms/instagram/ensure-auth.js.map +1 -1
- package/dist/src/platforms/instagram/index.d.ts +1 -0
- package/dist/src/platforms/instagram/index.d.ts.map +1 -1
- package/dist/src/platforms/instagram/index.js +1 -0
- package/dist/src/platforms/instagram/index.js.map +1 -1
- package/dist/src/platforms/instagram/token-extractor.d.ts +44 -0
- package/dist/src/platforms/instagram/token-extractor.d.ts.map +1 -0
- package/dist/src/platforms/instagram/token-extractor.js +407 -0
- package/dist/src/platforms/instagram/token-extractor.js.map +1 -0
- package/dist/src/platforms/kakaotalk/client.d.ts.map +1 -1
- package/dist/src/platforms/kakaotalk/client.js +2 -1
- package/dist/src/platforms/kakaotalk/client.js.map +1 -1
- package/dist/src/platforms/kakaotalk/commands/auth.d.ts.map +1 -1
- package/dist/src/platforms/kakaotalk/commands/auth.js +14 -13
- package/dist/src/platforms/kakaotalk/commands/auth.js.map +1 -1
- package/dist/src/platforms/kakaotalk/protocol/connection.d.ts.map +1 -1
- package/dist/src/platforms/kakaotalk/protocol/connection.js +2 -1
- package/dist/src/platforms/kakaotalk/protocol/connection.js.map +1 -1
- package/dist/src/platforms/line/client.d.ts.map +1 -1
- package/dist/src/platforms/line/client.js +36 -9
- package/dist/src/platforms/line/client.js.map +1 -1
- package/dist/src/platforms/line/commands/auth.d.ts.map +1 -1
- package/dist/src/platforms/line/commands/auth.js +6 -5
- package/dist/src/platforms/line/commands/auth.js.map +1 -1
- package/dist/src/platforms/slack/commands/auth.d.ts.map +1 -1
- package/dist/src/platforms/slack/commands/auth.js +11 -10
- package/dist/src/platforms/slack/commands/auth.js.map +1 -1
- package/dist/src/platforms/slack/token-extractor.d.ts +9 -0
- package/dist/src/platforms/slack/token-extractor.d.ts.map +1 -1
- package/dist/src/platforms/slack/token-extractor.js +300 -23
- package/dist/src/platforms/slack/token-extractor.js.map +1 -1
- package/dist/src/platforms/teams/commands/auth.d.ts.map +1 -1
- package/dist/src/platforms/teams/commands/auth.js +9 -8
- package/dist/src/platforms/teams/commands/auth.js.map +1 -1
- package/dist/src/platforms/teams/ensure-auth.d.ts.map +1 -1
- package/dist/src/platforms/teams/ensure-auth.js +2 -1
- package/dist/src/platforms/teams/ensure-auth.js.map +1 -1
- package/dist/src/platforms/teams/token-extractor.d.ts +5 -0
- package/dist/src/platforms/teams/token-extractor.d.ts.map +1 -1
- package/dist/src/platforms/teams/token-extractor.js +161 -29
- package/dist/src/platforms/teams/token-extractor.js.map +1 -1
- package/dist/src/platforms/telegram/client.d.ts.map +1 -1
- package/dist/src/platforms/telegram/client.js +25 -7
- package/dist/src/platforms/telegram/client.js.map +1 -1
- package/dist/src/platforms/telegram/commands/auth.d.ts.map +1 -1
- package/dist/src/platforms/telegram/commands/auth.js +6 -5
- package/dist/src/platforms/telegram/commands/auth.js.map +1 -1
- package/dist/src/platforms/webex/client.d.ts +12 -0
- package/dist/src/platforms/webex/client.d.ts.map +1 -1
- package/dist/src/platforms/webex/client.js +168 -1
- package/dist/src/platforms/webex/client.js.map +1 -1
- package/dist/src/platforms/webex/commands/auth.d.ts +4 -0
- package/dist/src/platforms/webex/commands/auth.d.ts.map +1 -1
- package/dist/src/platforms/webex/commands/auth.js +50 -4
- package/dist/src/platforms/webex/commands/auth.js.map +1 -1
- package/dist/src/platforms/webex/credential-manager.js +1 -1
- package/dist/src/platforms/webex/credential-manager.js.map +1 -1
- package/dist/src/platforms/webex/encryption.d.ts +10 -0
- package/dist/src/platforms/webex/encryption.d.ts.map +1 -0
- package/dist/src/platforms/webex/encryption.js +49 -0
- package/dist/src/platforms/webex/encryption.js.map +1 -0
- package/dist/src/platforms/webex/ensure-auth.d.ts.map +1 -1
- package/dist/src/platforms/webex/ensure-auth.js +25 -5
- package/dist/src/platforms/webex/ensure-auth.js.map +1 -1
- package/dist/src/platforms/webex/index.d.ts +2 -0
- package/dist/src/platforms/webex/index.d.ts.map +1 -1
- package/dist/src/platforms/webex/index.js +1 -0
- package/dist/src/platforms/webex/index.js.map +1 -1
- package/dist/src/platforms/webex/token-extractor.d.ts +29 -0
- package/dist/src/platforms/webex/token-extractor.d.ts.map +1 -0
- package/dist/src/platforms/webex/token-extractor.js +393 -0
- package/dist/src/platforms/webex/token-extractor.js.map +1 -0
- package/dist/src/platforms/webex/types.d.ts +8 -1
- package/dist/src/platforms/webex/types.d.ts.map +1 -1
- package/dist/src/platforms/webex/types.js +4 -1
- package/dist/src/platforms/webex/types.js.map +1 -1
- package/dist/src/platforms/whatsapp/client.d.ts.map +1 -1
- package/dist/src/platforms/whatsapp/client.js +6 -2
- package/dist/src/platforms/whatsapp/client.js.map +1 -1
- package/dist/src/shared/utils/derived-key-cache.d.ts +1 -1
- package/dist/src/shared/utils/derived-key-cache.d.ts.map +1 -1
- package/dist/src/shared/utils/error-handler.d.ts +1 -1
- package/dist/src/shared/utils/error-handler.d.ts.map +1 -1
- package/dist/src/shared/utils/error-handler.js +3 -2
- package/dist/src/shared/utils/error-handler.js.map +1 -1
- package/dist/src/shared/utils/stderr.d.ts +5 -0
- package/dist/src/shared/utils/stderr.d.ts.map +1 -0
- package/dist/src/shared/utils/stderr.js +18 -0
- package/dist/src/shared/utils/stderr.js.map +1 -0
- package/docs/content/docs/cli/channeltalk.mdx +7 -7
- package/docs/content/docs/cli/discord.mdx +3 -3
- package/docs/content/docs/cli/instagram.mdx +28 -6
- package/docs/content/docs/cli/slack.mdx +2 -2
- package/docs/content/docs/cli/teams.mdx +6 -4
- package/docs/content/docs/cli/webex.mdx +32 -11
- package/e2e/README.md +132 -8
- package/e2e/channeltalk.e2e.test.ts +2 -7
- package/e2e/channeltalkbot.e2e.test.ts +2 -6
- package/e2e/config.ts +172 -10
- package/e2e/helpers.ts +7 -0
- package/e2e/instagram.e2e.test.ts +97 -0
- package/e2e/kakaotalk.e2e.test.ts +74 -0
- package/e2e/line.e2e.test.ts +92 -0
- package/e2e/teams.e2e.test.ts +46 -1
- package/e2e/telegram.e2e.test.ts +84 -0
- package/e2e/webex.e2e.test.ts +190 -0
- package/e2e/whatsapp.e2e.test.ts +90 -0
- package/e2e/whatsappbot.e2e.test.ts +78 -0
- package/package.json +5 -3
- package/skills/agent-channeltalk/SKILL.md +9 -9
- package/skills/agent-channeltalk/references/authentication.md +21 -18
- package/skills/agent-channeltalkbot/SKILL.md +1 -1
- package/skills/agent-discord/SKILL.md +5 -5
- package/skills/agent-discord/references/authentication.md +8 -8
- package/skills/agent-discordbot/SKILL.md +1 -1
- package/skills/agent-instagram/SKILL.md +51 -9
- package/skills/agent-instagram/references/authentication.md +35 -3
- package/skills/agent-kakaotalk/SKILL.md +1 -1
- package/skills/agent-line/SKILL.md +1 -1
- package/skills/agent-slack/SKILL.md +5 -5
- package/skills/agent-slack/references/authentication.md +8 -8
- package/skills/agent-slackbot/SKILL.md +1 -1
- package/skills/agent-teams/SKILL.md +6 -6
- package/skills/agent-teams/references/authentication.md +8 -8
- package/skills/agent-telegram/SKILL.md +1 -1
- package/skills/agent-webex/SKILL.md +35 -15
- package/skills/agent-webex/references/authentication.md +63 -9
- package/skills/agent-webex/references/common-patterns.md +6 -3
- package/skills/agent-whatsapp/SKILL.md +1 -1
- package/skills/agent-whatsappbot/SKILL.md +1 -1
- package/src/platforms/channeltalk/commands/auth.test.ts +5 -5
- package/src/platforms/channeltalk/commands/auth.ts +38 -32
- package/src/platforms/channeltalk/ensure-auth.test.ts +6 -6
- package/src/platforms/channeltalk/ensure-auth.ts +6 -6
- package/src/platforms/channeltalk/token-extractor.test.ts +182 -15
- package/src/platforms/channeltalk/token-extractor.ts +344 -30
- package/src/platforms/discord/commands/auth.test.ts +3 -3
- package/src/platforms/discord/commands/auth.ts +58 -54
- package/src/platforms/discord/ensure-auth.test.ts +3 -3
- package/src/platforms/discord/ensure-auth.ts +3 -3
- package/src/platforms/discord/token-extractor.test.ts +199 -27
- package/src/platforms/discord/token-extractor.ts +190 -17
- package/src/platforms/instagram/client.ts +2 -2
- package/src/platforms/instagram/commands/auth.ts +133 -14
- package/src/platforms/instagram/ensure-auth.ts +63 -12
- package/src/platforms/instagram/index.ts +1 -0
- package/src/platforms/instagram/token-extractor.test.ts +424 -0
- package/src/platforms/instagram/token-extractor.ts +478 -0
- package/src/platforms/kakaotalk/client.ts +3 -1
- package/src/platforms/kakaotalk/commands/auth.ts +14 -13
- package/src/platforms/kakaotalk/protocol/connection.ts +3 -1
- package/src/platforms/line/client.ts +39 -14
- package/src/platforms/line/commands/auth.ts +7 -6
- package/src/platforms/slack/cli.test.ts +6 -5
- package/src/platforms/slack/commands/auth.test.ts +11 -7
- package/src/platforms/slack/commands/auth.ts +11 -10
- package/src/platforms/slack/token-extractor.test.ts +98 -1
- package/src/platforms/slack/token-extractor.ts +338 -26
- package/src/platforms/teams/commands/auth.ts +9 -8
- package/src/platforms/teams/ensure-auth.ts +3 -1
- package/src/platforms/teams/token-extractor.test.ts +136 -17
- package/src/platforms/teams/token-extractor.ts +182 -31
- package/src/platforms/telegram/client.test.ts +134 -0
- package/src/platforms/telegram/client.ts +27 -6
- package/src/platforms/telegram/commands/auth.ts +6 -5
- package/src/platforms/webex/client.test.ts +314 -0
- package/src/platforms/webex/client.ts +231 -1
- package/src/platforms/webex/commands/auth.ts +71 -4
- package/src/platforms/webex/commands/member.test.ts +10 -1
- package/src/platforms/webex/commands/message.test.ts +9 -5
- package/src/platforms/webex/commands/snapshot.test.ts +13 -4
- package/src/platforms/webex/commands/space.test.ts +12 -2
- package/src/platforms/webex/credential-manager.ts +1 -1
- package/src/platforms/webex/encryption.ts +53 -0
- package/src/platforms/webex/ensure-auth.test.ts +4 -0
- package/src/platforms/webex/ensure-auth.ts +27 -4
- package/src/platforms/webex/index.ts +2 -0
- package/src/platforms/webex/token-extractor.test.ts +327 -0
- package/src/platforms/webex/token-extractor.ts +460 -0
- package/src/platforms/webex/types.ts +8 -2
- package/src/platforms/webex/typings/node-jose.d.ts +27 -0
- package/src/platforms/whatsapp/client.ts +11 -7
- package/src/shared/utils/derived-key-cache.ts +1 -1
- package/src/shared/utils/error-handler.ts +4 -2
- package/src/shared/utils/stderr.ts +22 -0
|
@@ -34,6 +34,13 @@ interface CDPMessage {
|
|
|
34
34
|
error?: { code: number; message: string }
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
interface BrowserConfig {
|
|
38
|
+
name: string
|
|
39
|
+
darwin: string
|
|
40
|
+
linux: string
|
|
41
|
+
win32: string
|
|
42
|
+
}
|
|
43
|
+
|
|
37
44
|
const TOKEN_REGEX = /[\w-]{24,}\.[\w-]{6}\.[\w-]{25,110}/
|
|
38
45
|
const MFA_TOKEN_REGEX = /mfa\.[\w-]{84}/
|
|
39
46
|
const ENCRYPTED_PREFIX = 'dQw4w9WgXcQ:'
|
|
@@ -55,6 +62,51 @@ const DISCORD_APP_PATHS: Record<DiscordVariant, { darwin: string }> = {
|
|
|
55
62
|
ptb: { darwin: '/Applications/Discord PTB.app/Contents/MacOS/Discord PTB' },
|
|
56
63
|
}
|
|
57
64
|
|
|
65
|
+
const BROWSERS: BrowserConfig[] = [
|
|
66
|
+
{
|
|
67
|
+
name: 'Chrome',
|
|
68
|
+
darwin: join('Google', 'Chrome'),
|
|
69
|
+
linux: 'google-chrome',
|
|
70
|
+
win32: join('Google', 'Chrome', 'User Data'),
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
name: 'Chrome Canary',
|
|
74
|
+
darwin: join('Google', 'Chrome Canary'),
|
|
75
|
+
linux: 'google-chrome-unstable',
|
|
76
|
+
win32: join('Google', 'Chrome SxS', 'User Data'),
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
name: 'Edge',
|
|
80
|
+
darwin: 'Microsoft Edge',
|
|
81
|
+
linux: 'microsoft-edge',
|
|
82
|
+
win32: join('Microsoft', 'Edge', 'User Data'),
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
name: 'Arc',
|
|
86
|
+
darwin: join('Arc', 'User Data'),
|
|
87
|
+
linux: '',
|
|
88
|
+
win32: join('Arc', 'User Data'),
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
name: 'Brave',
|
|
92
|
+
darwin: join('BraveSoftware', 'Brave-Browser'),
|
|
93
|
+
linux: join('BraveSoftware', 'Brave-Browser'),
|
|
94
|
+
win32: join('BraveSoftware', 'Brave-Browser', 'User Data'),
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
name: 'Vivaldi',
|
|
98
|
+
darwin: 'Vivaldi',
|
|
99
|
+
linux: 'vivaldi',
|
|
100
|
+
win32: join('Vivaldi', 'User Data'),
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
name: 'Chromium',
|
|
104
|
+
darwin: 'Chromium',
|
|
105
|
+
linux: 'chromium',
|
|
106
|
+
win32: join('Chromium', 'User Data'),
|
|
107
|
+
},
|
|
108
|
+
]
|
|
109
|
+
|
|
58
110
|
export class DiscordTokenExtractor {
|
|
59
111
|
private platform: NodeJS.Platform
|
|
60
112
|
private startupWait: number
|
|
@@ -92,6 +144,75 @@ export class DiscordTokenExtractor {
|
|
|
92
144
|
}
|
|
93
145
|
}
|
|
94
146
|
|
|
147
|
+
getBrowserLevelDBDirs(): string[] {
|
|
148
|
+
const dirs: string[] = []
|
|
149
|
+
|
|
150
|
+
for (const browser of BROWSERS) {
|
|
151
|
+
const browserBase = this.getBrowserBasePath(browser)
|
|
152
|
+
if (!browserBase) continue
|
|
153
|
+
|
|
154
|
+
const profileDirs = this.discoverProfileDirs(browserBase)
|
|
155
|
+
for (const profileDir of profileDirs) {
|
|
156
|
+
dirs.push(join(profileDir, 'Local Storage', 'leveldb'))
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return dirs
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
private getBrowserBasePath(browser: BrowserConfig): string | null {
|
|
164
|
+
let relative: string
|
|
165
|
+
|
|
166
|
+
switch (this.platform) {
|
|
167
|
+
case 'darwin':
|
|
168
|
+
relative = browser.darwin
|
|
169
|
+
if (!relative) return null
|
|
170
|
+
return join(homedir(), 'Library', 'Application Support', relative)
|
|
171
|
+
case 'linux':
|
|
172
|
+
relative = browser.linux
|
|
173
|
+
if (!relative) return null
|
|
174
|
+
return join(homedir(), '.config', relative)
|
|
175
|
+
case 'win32':
|
|
176
|
+
relative = browser.win32
|
|
177
|
+
if (!relative) return null
|
|
178
|
+
return join(
|
|
179
|
+
process.env.LOCALAPPDATA || join(homedir(), 'AppData', 'Local'),
|
|
180
|
+
relative,
|
|
181
|
+
)
|
|
182
|
+
default:
|
|
183
|
+
return null
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
private discoverProfileDirs(browserBase: string): string[] {
|
|
188
|
+
const dirs: string[] = []
|
|
189
|
+
|
|
190
|
+
dirs.push(join(browserBase, 'Default'))
|
|
191
|
+
|
|
192
|
+
if (!existsSync(browserBase)) return dirs
|
|
193
|
+
|
|
194
|
+
try {
|
|
195
|
+
const entries = readdirSync(browserBase, { withFileTypes: true })
|
|
196
|
+
for (const entry of entries) {
|
|
197
|
+
if (!entry.isDirectory()) continue
|
|
198
|
+
if (!/^Profile \d+$/i.test(entry.name)) continue
|
|
199
|
+
dirs.push(join(browserBase, entry.name))
|
|
200
|
+
}
|
|
201
|
+
} catch {}
|
|
202
|
+
|
|
203
|
+
return dirs
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
private findBrowserBaseDirForPath(path: string): string | null {
|
|
207
|
+
const parts = path.split(/[/\\]/)
|
|
208
|
+
for (let levels = 2; levels <= 5; levels++) {
|
|
209
|
+
if (parts.length < levels) break
|
|
210
|
+
const base = parts.slice(0, parts.length - levels).join('/')
|
|
211
|
+
if (existsSync(join(base, 'Local State'))) return base
|
|
212
|
+
}
|
|
213
|
+
return null
|
|
214
|
+
}
|
|
215
|
+
|
|
95
216
|
getKeychainVariants(): KeychainVariant[] {
|
|
96
217
|
return [
|
|
97
218
|
// Modern Discord (lowercase)
|
|
@@ -102,6 +223,14 @@ export class DiscordTokenExtractor {
|
|
|
102
223
|
{ service: 'Discord Safe Storage', account: 'Discord' },
|
|
103
224
|
{ service: 'Discord Canary Safe Storage', account: 'Discord Canary' },
|
|
104
225
|
{ service: 'Discord PTB Safe Storage', account: 'Discord PTB' },
|
|
226
|
+
// Browser keychain entries for fallback
|
|
227
|
+
{ service: 'Chrome Safe Storage', account: 'Chrome' },
|
|
228
|
+
{ service: 'Chrome Canary Safe Storage', account: 'Chrome Canary' },
|
|
229
|
+
{ service: 'Microsoft Edge Safe Storage', account: 'Microsoft Edge' },
|
|
230
|
+
{ service: 'Arc Safe Storage', account: 'Arc' },
|
|
231
|
+
{ service: 'Brave Safe Storage', account: 'Brave' },
|
|
232
|
+
{ service: 'Vivaldi Safe Storage', account: 'Vivaldi' },
|
|
233
|
+
{ service: 'Chromium Safe Storage', account: 'Chromium' },
|
|
105
234
|
]
|
|
106
235
|
}
|
|
107
236
|
|
|
@@ -121,22 +250,34 @@ export class DiscordTokenExtractor {
|
|
|
121
250
|
return token.startsWith(ENCRYPTED_PREFIX)
|
|
122
251
|
}
|
|
123
252
|
|
|
124
|
-
async extract(): Promise<ExtractedDiscordToken
|
|
253
|
+
async extract(): Promise<ExtractedDiscordToken[]> {
|
|
125
254
|
await this.loadCachedKey()
|
|
126
255
|
|
|
127
|
-
const
|
|
128
|
-
|
|
129
|
-
|
|
256
|
+
const results: ExtractedDiscordToken[] = []
|
|
257
|
+
const seenTokens = new Set<string>()
|
|
258
|
+
|
|
259
|
+
for (const t of await this.extractFromLevelDB()) {
|
|
260
|
+
if (!seenTokens.has(t.token)) {
|
|
261
|
+
seenTokens.add(t.token)
|
|
262
|
+
results.push(t)
|
|
263
|
+
}
|
|
130
264
|
}
|
|
131
265
|
|
|
132
|
-
|
|
266
|
+
for (const t of await this.extractFromBrowserLevelDB()) {
|
|
267
|
+
if (!seenTokens.has(t.token)) {
|
|
268
|
+
seenTokens.add(t.token)
|
|
269
|
+
results.push(t)
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (results.length === 0 && this.platform === 'darwin') {
|
|
133
274
|
const cdpToken = await this.tryExtractViaCDP()
|
|
134
|
-
if (cdpToken) {
|
|
135
|
-
|
|
275
|
+
if (cdpToken && !seenTokens.has(cdpToken)) {
|
|
276
|
+
results.push({ token: cdpToken })
|
|
136
277
|
}
|
|
137
278
|
}
|
|
138
279
|
|
|
139
|
-
return
|
|
280
|
+
return results
|
|
140
281
|
}
|
|
141
282
|
|
|
142
283
|
private async loadCachedKey(): Promise<void> {
|
|
@@ -175,19 +316,45 @@ export class DiscordTokenExtractor {
|
|
|
175
316
|
return null
|
|
176
317
|
}
|
|
177
318
|
|
|
178
|
-
private async extractFromLevelDB(): Promise<ExtractedDiscordToken
|
|
319
|
+
private async extractFromLevelDB(): Promise<ExtractedDiscordToken[]> {
|
|
179
320
|
const dirs = this.getDiscordDirs()
|
|
321
|
+
const results: ExtractedDiscordToken[] = []
|
|
322
|
+
const seenTokens = new Set<string>()
|
|
180
323
|
|
|
181
324
|
for (const dir of dirs) {
|
|
182
325
|
if (!existsSync(dir)) continue
|
|
183
326
|
|
|
184
327
|
const token = await this.extractFromDir(dir)
|
|
185
|
-
if (token) {
|
|
186
|
-
|
|
328
|
+
if (token && !seenTokens.has(token)) {
|
|
329
|
+
seenTokens.add(token)
|
|
330
|
+
results.push({ token })
|
|
187
331
|
}
|
|
188
332
|
}
|
|
189
333
|
|
|
190
|
-
return
|
|
334
|
+
return results
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
private async extractFromBrowserLevelDB(): Promise<ExtractedDiscordToken[]> {
|
|
338
|
+
const leveldbDirs = this.getBrowserLevelDBDirs()
|
|
339
|
+
const results: ExtractedDiscordToken[] = []
|
|
340
|
+
const seenTokens = new Set<string>()
|
|
341
|
+
|
|
342
|
+
for (const leveldbDir of leveldbDirs) {
|
|
343
|
+
if (!existsSync(leveldbDir)) continue
|
|
344
|
+
|
|
345
|
+
// The browser base dir contains the Local State file needed for Windows decryption
|
|
346
|
+
const browserBaseDir = this.findBrowserBaseDirForPath(leveldbDir)
|
|
347
|
+
const tokens = this.extractTokensFromLDBFiles(leveldbDir, browserBaseDir ?? leveldbDir)
|
|
348
|
+
|
|
349
|
+
for (const token of tokens) {
|
|
350
|
+
if (!seenTokens.has(token)) {
|
|
351
|
+
seenTokens.add(token)
|
|
352
|
+
results.push({ token })
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
return results
|
|
191
358
|
}
|
|
192
359
|
|
|
193
360
|
private async extractFromDir(discordDir: string): Promise<string | null> {
|
|
@@ -196,7 +363,7 @@ export class DiscordTokenExtractor {
|
|
|
196
363
|
if (!existsSync(levelDbPath)) return null
|
|
197
364
|
|
|
198
365
|
const tokens = this.extractTokensFromLDBFiles(levelDbPath, discordDir)
|
|
199
|
-
return tokens.length > 0 ? tokens[0] : null
|
|
366
|
+
return tokens.length > 0 ? tokens[0]! : null
|
|
200
367
|
}
|
|
201
368
|
|
|
202
369
|
private extractTokensFromLDBFiles(dbPath: string, discordDir: string): string[] {
|
|
@@ -307,11 +474,12 @@ export class DiscordTokenExtractor {
|
|
|
307
474
|
private decryptMacToken(encryptedData: Buffer, discordDir: string): string | null {
|
|
308
475
|
if (this.cachedKey) {
|
|
309
476
|
const decrypted = this.decryptAESCBC(encryptedData, this.cachedKey)
|
|
310
|
-
if (decrypted) return decrypted
|
|
477
|
+
if (decrypted && this.isValidToken(decrypted)) return decrypted
|
|
311
478
|
}
|
|
312
479
|
|
|
313
480
|
const variant = this.getVariantFromPath(discordDir)
|
|
314
|
-
|
|
481
|
+
// Try Discord-specific variants first, then fall back to all browser variants
|
|
482
|
+
const discordVariants = this.getKeychainVariants().filter((v) => {
|
|
315
483
|
const lowerAccount = v.account.toLowerCase()
|
|
316
484
|
if (variant === 'stable') return lowerAccount === 'discord' || lowerAccount === 'discord key'
|
|
317
485
|
if (variant === 'canary') return lowerAccount === 'discord canary' || lowerAccount === 'discordcanary key'
|
|
@@ -319,7 +487,12 @@ export class DiscordTokenExtractor {
|
|
|
319
487
|
return false
|
|
320
488
|
})
|
|
321
489
|
|
|
322
|
-
|
|
490
|
+
const browserVariants = this.getKeychainVariants().filter((v) => {
|
|
491
|
+
const lowerService = v.service.toLowerCase()
|
|
492
|
+
return !lowerService.includes('discord')
|
|
493
|
+
})
|
|
494
|
+
|
|
495
|
+
for (const keychainVariant of [...discordVariants, ...browserVariants]) {
|
|
323
496
|
try {
|
|
324
497
|
const password = execSync(
|
|
325
498
|
`security find-generic-password -s "${keychainVariant.service}" -a "${keychainVariant.account}" -w 2>/dev/null`,
|
|
@@ -328,7 +501,7 @@ export class DiscordTokenExtractor {
|
|
|
328
501
|
|
|
329
502
|
const key = pbkdf2Sync(password, 'saltysalt', 1003, 16, 'sha1')
|
|
330
503
|
const decrypted = this.decryptAESCBC(encryptedData, key)
|
|
331
|
-
if (decrypted) {
|
|
504
|
+
if (decrypted && this.isValidToken(decrypted)) {
|
|
332
505
|
this.cachedKey = key
|
|
333
506
|
this.keyCache.set('discord', key).catch(() => {})
|
|
334
507
|
return decrypted
|
|
@@ -24,7 +24,7 @@ const DEVICE_MANUFACTURER = 'samsung'
|
|
|
24
24
|
const DEVICE_MODEL = 'SM-S911B'
|
|
25
25
|
const DEVICE_CHIPSET = 'qcom'
|
|
26
26
|
|
|
27
|
-
function generateDeviceString(): string {
|
|
27
|
+
export function generateDeviceString(): string {
|
|
28
28
|
return `${ANDROID_VERSION}/${ANDROID_RELEASE}; ${DEVICE_DPI}; ${DEVICE_RESOLUTION}; ${DEVICE_MANUFACTURER}; ${DEVICE_MODEL}; ${DEVICE_MODEL}; ${DEVICE_CHIPSET}; en_US; ${IG_VERSION_CODE}`
|
|
29
29
|
}
|
|
30
30
|
|
|
@@ -32,7 +32,7 @@ function buildUserAgent(deviceString: string): string {
|
|
|
32
32
|
return `Instagram ${IG_VERSION} Android (${deviceString})`
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
function generateAndroidDeviceId(): string {
|
|
35
|
+
export function generateAndroidDeviceId(): string {
|
|
36
36
|
return `android-${randomBytes(8).toString('hex')}`
|
|
37
37
|
}
|
|
38
38
|
|
|
@@ -1,10 +1,14 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto'
|
|
2
|
+
import { mkdir, writeFile } from 'node:fs/promises'
|
|
1
3
|
import { Writable } from 'node:stream'
|
|
2
4
|
|
|
3
5
|
import { Command } from 'commander'
|
|
4
6
|
import { handleError } from '@/shared/utils/error-handler'
|
|
5
7
|
import { formatOutput } from '@/shared/utils/output'
|
|
6
|
-
import {
|
|
8
|
+
import { info, warn, error as stderrError, debug } from '@/shared/utils/stderr'
|
|
9
|
+
import { InstagramClient, generateAndroidDeviceId, generateDeviceString } from '../client'
|
|
7
10
|
import { InstagramCredentialManager } from '../credential-manager'
|
|
11
|
+
import { InstagramTokenExtractor } from '../token-extractor'
|
|
8
12
|
import { createAccountId } from '../types'
|
|
9
13
|
|
|
10
14
|
function isInteractive(): boolean {
|
|
@@ -88,7 +92,7 @@ async function loginAction(options: LoginOptions): Promise<void> {
|
|
|
88
92
|
process.exit(1)
|
|
89
93
|
}
|
|
90
94
|
username = await promptText('Instagram username')
|
|
91
|
-
if (!username) {
|
|
95
|
+
if (!username) { stderrError('Username is required.'); process.exit(1) }
|
|
92
96
|
}
|
|
93
97
|
|
|
94
98
|
if (!password) {
|
|
@@ -99,7 +103,7 @@ async function loginAction(options: LoginOptions): Promise<void> {
|
|
|
99
103
|
process.exit(1)
|
|
100
104
|
}
|
|
101
105
|
password = await promptHidden('Password')
|
|
102
|
-
if (!password) {
|
|
106
|
+
if (!password) { stderrError('Password is required.'); process.exit(1) }
|
|
103
107
|
}
|
|
104
108
|
|
|
105
109
|
const manager = new InstagramCredentialManager()
|
|
@@ -109,7 +113,7 @@ async function loginAction(options: LoginOptions): Promise<void> {
|
|
|
109
113
|
const client = new InstagramClient(manager)
|
|
110
114
|
client.setSessionPath(paths.session_path)
|
|
111
115
|
if (options.debug) {
|
|
112
|
-
client.setDebugLog((msg) =>
|
|
116
|
+
client.setDebugLog((msg) => debug(`[debug] ${msg}`))
|
|
113
117
|
}
|
|
114
118
|
|
|
115
119
|
const result = await client.authenticate(username, password)
|
|
@@ -118,9 +122,9 @@ async function loginAction(options: LoginOptions): Promise<void> {
|
|
|
118
122
|
const twoFactorIdentifier = (result.twoFactorInfo?.['two_factor_identifier'] as string) ?? ''
|
|
119
123
|
|
|
120
124
|
if (interactive) {
|
|
121
|
-
|
|
125
|
+
info('\n Two-factor authentication required.')
|
|
122
126
|
const code = await promptText('Verification code')
|
|
123
|
-
if (!code) {
|
|
127
|
+
if (!code) { stderrError('Code is required.'); process.exit(1) }
|
|
124
128
|
|
|
125
129
|
const tfResult = await client.twoFactorLogin(username, code, twoFactorIdentifier)
|
|
126
130
|
await saveAccountAndPrint(manager, accountId, username, tfResult.userId, options.pretty)
|
|
@@ -161,24 +165,24 @@ async function handleInteractiveChallenge(
|
|
|
161
165
|
challengePath: string,
|
|
162
166
|
pretty?: boolean,
|
|
163
167
|
): Promise<void> {
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
168
|
+
info('\n Security challenge required by Instagram.')
|
|
169
|
+
info(' Choose verification method:')
|
|
170
|
+
info(' 1. Email')
|
|
171
|
+
info(' 2. SMS')
|
|
172
|
+
info('')
|
|
169
173
|
|
|
170
174
|
const choice = await promptText('Method (1/2)')
|
|
171
175
|
const method = choice === '2' ? 'sms' as const : 'email' as const
|
|
172
176
|
|
|
173
177
|
const sendResult = await client.challengeSendCode(challengePath, method)
|
|
174
178
|
if (sendResult.contactPoint) {
|
|
175
|
-
|
|
179
|
+
info(`\n Code sent to: ${sendResult.contactPoint}`)
|
|
176
180
|
} else {
|
|
177
|
-
|
|
181
|
+
info('\n Verification code sent.')
|
|
178
182
|
}
|
|
179
183
|
|
|
180
184
|
const code = await promptText('Verification code')
|
|
181
|
-
if (!code) {
|
|
185
|
+
if (!code) { stderrError('Code is required.'); process.exit(1) }
|
|
182
186
|
|
|
183
187
|
const verifyResult = await client.challengeSubmitCode(challengePath, code)
|
|
184
188
|
await saveAccountAndPrint(manager, accountId, username, verifyResult.userId, pretty)
|
|
@@ -322,6 +326,114 @@ async function useAction(accountId: string, options: { pretty?: boolean }): Prom
|
|
|
322
326
|
}
|
|
323
327
|
}
|
|
324
328
|
|
|
329
|
+
async function extractAction(options: { pretty?: boolean; debug?: boolean }): Promise<void> {
|
|
330
|
+
try {
|
|
331
|
+
const extractor = new InstagramTokenExtractor(
|
|
332
|
+
undefined,
|
|
333
|
+
options.debug ? (msg) => debug(`[debug] ${msg}`) : undefined,
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
if (options.debug) {
|
|
337
|
+
debug('[debug] Searching browser profiles for Instagram cookies...')
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const results = await extractor.extract()
|
|
341
|
+
|
|
342
|
+
if (results.length === 0) {
|
|
343
|
+
console.log(
|
|
344
|
+
formatOutput(
|
|
345
|
+
{
|
|
346
|
+
error: 'No Instagram cookies found in any browser. Make sure you are logged in to instagram.com in Chrome, Edge, Arc, or Brave.',
|
|
347
|
+
hint: 'Run "auth login --username <username>" to log in manually.',
|
|
348
|
+
},
|
|
349
|
+
options.pretty,
|
|
350
|
+
),
|
|
351
|
+
)
|
|
352
|
+
process.exit(1)
|
|
353
|
+
return
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
const manager = new InstagramCredentialManager()
|
|
357
|
+
|
|
358
|
+
for (const extracted of results) {
|
|
359
|
+
const session = {
|
|
360
|
+
cookies: [
|
|
361
|
+
`sessionid=${extracted.sessionid}`,
|
|
362
|
+
`ds_user_id=${extracted.ds_user_id}`,
|
|
363
|
+
`csrftoken=${extracted.csrftoken}`,
|
|
364
|
+
extracted.mid ? `mid=${extracted.mid}` : null,
|
|
365
|
+
extracted.ig_did ? `ig_did=${extracted.ig_did}` : null,
|
|
366
|
+
extracted.rur ? `rur=${extracted.rur}` : null,
|
|
367
|
+
]
|
|
368
|
+
.filter(Boolean)
|
|
369
|
+
.join('; '),
|
|
370
|
+
device: {
|
|
371
|
+
phone_id: randomUUID(),
|
|
372
|
+
uuid: randomUUID(),
|
|
373
|
+
android_device_id: generateAndroidDeviceId(),
|
|
374
|
+
advertising_id: randomUUID(),
|
|
375
|
+
client_session_id: randomUUID(),
|
|
376
|
+
device_string: generateDeviceString(),
|
|
377
|
+
},
|
|
378
|
+
user_id: extracted.ds_user_id,
|
|
379
|
+
mid: extracted.mid,
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
const accountId = createAccountId(extracted.ds_user_id)
|
|
383
|
+
const paths = await manager.ensureAccountPaths(accountId)
|
|
384
|
+
|
|
385
|
+
await mkdir(paths.account_dir, { recursive: true })
|
|
386
|
+
await writeFile(paths.session_path, JSON.stringify(session, null, 2), { mode: 0o600 })
|
|
387
|
+
|
|
388
|
+
const client = new InstagramClient(manager)
|
|
389
|
+
client.setSessionPath(paths.session_path)
|
|
390
|
+
if (options.debug) {
|
|
391
|
+
client.setDebugLog((msg) => debug(`[debug] ${msg}`))
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
await client.loadSession(paths.session_path)
|
|
395
|
+
|
|
396
|
+
let username = extracted.ds_user_id
|
|
397
|
+
try {
|
|
398
|
+
const { data } = await (client as any).request('GET', '/accounts/current_user/?edit=true')
|
|
399
|
+
const user = data['user'] as Record<string, unknown> | undefined
|
|
400
|
+
if (user?.['username']) {
|
|
401
|
+
username = user['username'] as string
|
|
402
|
+
}
|
|
403
|
+
await (client as any).saveSession()
|
|
404
|
+
} catch (err) {
|
|
405
|
+
if (options.debug) {
|
|
406
|
+
debug(`[debug] Session validation failed: ${err}`)
|
|
407
|
+
}
|
|
408
|
+
warn('Warning: Could not validate session. Cookies may be expired.')
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
const now = new Date().toISOString()
|
|
412
|
+
await manager.setAccount({
|
|
413
|
+
account_id: accountId,
|
|
414
|
+
username,
|
|
415
|
+
pk: extracted.ds_user_id,
|
|
416
|
+
created_at: now,
|
|
417
|
+
updated_at: now,
|
|
418
|
+
})
|
|
419
|
+
await manager.setCurrent(accountId)
|
|
420
|
+
|
|
421
|
+
console.log(
|
|
422
|
+
formatOutput(
|
|
423
|
+
{
|
|
424
|
+
authenticated: true,
|
|
425
|
+
account_id: accountId,
|
|
426
|
+
username,
|
|
427
|
+
},
|
|
428
|
+
options.pretty,
|
|
429
|
+
),
|
|
430
|
+
)
|
|
431
|
+
}
|
|
432
|
+
} catch (error) {
|
|
433
|
+
handleError(error as Error)
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
325
437
|
async function logoutAction(options: { account?: string; pretty?: boolean }): Promise<void> {
|
|
326
438
|
try {
|
|
327
439
|
const manager = new InstagramCredentialManager()
|
|
@@ -354,6 +466,13 @@ export const authCommand = new Command('auth')
|
|
|
354
466
|
.option('--debug', 'Show raw API responses')
|
|
355
467
|
.action(loginAction),
|
|
356
468
|
)
|
|
469
|
+
.addCommand(
|
|
470
|
+
new Command('extract')
|
|
471
|
+
.description('Extract Instagram cookies from browser (Chrome, Edge, Arc, Brave)')
|
|
472
|
+
.option('--pretty', 'Pretty print JSON output')
|
|
473
|
+
.option('--debug', 'Show debug output')
|
|
474
|
+
.action(extractAction),
|
|
475
|
+
)
|
|
357
476
|
.addCommand(
|
|
358
477
|
new Command('verify')
|
|
359
478
|
.description('Complete two-factor authentication (non-interactive)')
|
|
@@ -1,24 +1,75 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto'
|
|
1
2
|
import { existsSync } from 'node:fs'
|
|
3
|
+
import { mkdir, writeFile } from 'node:fs/promises'
|
|
4
|
+
|
|
2
5
|
import { formatOutput } from '@/shared/utils/output'
|
|
6
|
+
|
|
7
|
+
import { generateAndroidDeviceId, generateDeviceString } from './client'
|
|
3
8
|
import { InstagramCredentialManager } from './credential-manager'
|
|
9
|
+
import { InstagramTokenExtractor } from './token-extractor'
|
|
10
|
+
import { createAccountId } from './types'
|
|
4
11
|
|
|
5
12
|
export async function ensureInstagramAuth(): Promise<void> {
|
|
6
13
|
const manager = new InstagramCredentialManager()
|
|
7
14
|
const account = await manager.getAccount()
|
|
8
15
|
|
|
9
|
-
if (
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
16
|
+
if (account) {
|
|
17
|
+
const paths = manager.getAccountPaths(account.account_id)
|
|
18
|
+
if (existsSync(paths.session_path)) {
|
|
19
|
+
return
|
|
20
|
+
}
|
|
14
21
|
}
|
|
15
22
|
|
|
16
|
-
|
|
23
|
+
try {
|
|
24
|
+
const extractor = new InstagramTokenExtractor()
|
|
25
|
+
const results = await extractor.extract()
|
|
26
|
+
const cookies = results[0]
|
|
17
27
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
28
|
+
if (cookies) {
|
|
29
|
+
const session = {
|
|
30
|
+
cookies: [
|
|
31
|
+
`sessionid=${cookies.sessionid}`,
|
|
32
|
+
`ds_user_id=${cookies.ds_user_id}`,
|
|
33
|
+
`csrftoken=${cookies.csrftoken}`,
|
|
34
|
+
cookies.mid ? `mid=${cookies.mid}` : null,
|
|
35
|
+
cookies.ig_did ? `ig_did=${cookies.ig_did}` : null,
|
|
36
|
+
cookies.rur ? `rur=${cookies.rur}` : null,
|
|
37
|
+
]
|
|
38
|
+
.filter(Boolean)
|
|
39
|
+
.join('; '),
|
|
40
|
+
device: {
|
|
41
|
+
phone_id: randomUUID(),
|
|
42
|
+
uuid: randomUUID(),
|
|
43
|
+
android_device_id: generateAndroidDeviceId(),
|
|
44
|
+
advertising_id: randomUUID(),
|
|
45
|
+
client_session_id: randomUUID(),
|
|
46
|
+
device_string: generateDeviceString(),
|
|
47
|
+
},
|
|
48
|
+
user_id: cookies.ds_user_id,
|
|
49
|
+
mid: cookies.mid,
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const accountId = createAccountId(cookies.ds_user_id)
|
|
53
|
+
const paths = await manager.ensureAccountPaths(accountId)
|
|
54
|
+
|
|
55
|
+
await mkdir(paths.account_dir, { recursive: true })
|
|
56
|
+
await writeFile(paths.session_path, JSON.stringify(session, null, 2), { mode: 0o600 })
|
|
57
|
+
|
|
58
|
+
const now = new Date().toISOString()
|
|
59
|
+
await manager.setAccount({
|
|
60
|
+
account_id: accountId,
|
|
61
|
+
username: cookies.ds_user_id,
|
|
62
|
+
pk: cookies.ds_user_id,
|
|
63
|
+
created_at: now,
|
|
64
|
+
updated_at: now,
|
|
65
|
+
})
|
|
66
|
+
await manager.setCurrent(accountId)
|
|
67
|
+
return
|
|
68
|
+
}
|
|
69
|
+
} catch {}
|
|
70
|
+
|
|
71
|
+
console.log(formatOutput({
|
|
72
|
+
error: 'Not authenticated. Run "agent-instagram auth extract" to extract from browser, or "agent-instagram auth login --username <username>" to log in.',
|
|
73
|
+
}))
|
|
74
|
+
process.exit(1)
|
|
24
75
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export { InstagramClient } from './client'
|
|
2
2
|
export { InstagramCredentialManager } from './credential-manager'
|
|
3
3
|
export { InstagramListener, type InstagramListenerEventMap } from './listener'
|
|
4
|
+
export { InstagramTokenExtractor, type ExtractedInstagramCookies } from './token-extractor'
|
|
4
5
|
export {
|
|
5
6
|
createAccountId,
|
|
6
7
|
extractMediaUrl,
|