agent-messenger 2.1.0 → 2.2.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 +6 -6
- package/dist/package.json +2 -2
- 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/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 +10 -0
- package/dist/src/platforms/webex/client.d.ts.map +1 -1
- package/dist/src/platforms/webex/client.js +124 -0
- 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 +46 -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/ensure-auth.d.ts.map +1 -1
- package/dist/src/platforms/webex/ensure-auth.js +21 -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 +28 -0
- package/dist/src/platforms/webex/token-extractor.d.ts.map +1 -0
- package/dist/src/platforms/webex/token-extractor.js +344 -0
- package/dist/src/platforms/webex/token-extractor.js.map +1 -0
- package/dist/src/platforms/webex/types.d.ts +4 -1
- package/dist/src/platforms/webex/types.d.ts.map +1 -1
- package/dist/src/platforms/webex/types.js +2 -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 +30 -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 +2 -2
- 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 +62 -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/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 +158 -0
- package/src/platforms/webex/commands/auth.ts +67 -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/ensure-auth.test.ts +4 -0
- package/src/platforms/webex/ensure-auth.ts +23 -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 +393 -0
- package/src/platforms/webex/types.ts +4 -2
- 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
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
import {
|
|
2
|
+
copyFileSync,
|
|
3
|
+
existsSync,
|
|
4
|
+
mkdirSync,
|
|
5
|
+
readdirSync,
|
|
6
|
+
readFileSync,
|
|
7
|
+
rmSync,
|
|
8
|
+
statSync,
|
|
9
|
+
} from 'node:fs'
|
|
10
|
+
import { homedir, tmpdir } from 'node:os'
|
|
11
|
+
import { join } from 'node:path'
|
|
12
|
+
|
|
13
|
+
import { ClassicLevel } from 'classic-level'
|
|
14
|
+
|
|
15
|
+
export interface ExtractedWebexToken {
|
|
16
|
+
accessToken: string
|
|
17
|
+
refreshToken?: string
|
|
18
|
+
expiresAt?: number
|
|
19
|
+
deviceUrl?: string
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface BrowserConfig {
|
|
23
|
+
name: string
|
|
24
|
+
darwin: string
|
|
25
|
+
linux: string
|
|
26
|
+
win32: string
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const BROWSERS: BrowserConfig[] = [
|
|
30
|
+
{
|
|
31
|
+
name: 'Chrome',
|
|
32
|
+
darwin: join('Google', 'Chrome'),
|
|
33
|
+
linux: 'google-chrome',
|
|
34
|
+
win32: join('Google', 'Chrome', 'User Data'),
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
name: 'Chrome Canary',
|
|
38
|
+
darwin: join('Google', 'Chrome Canary'),
|
|
39
|
+
linux: 'google-chrome-unstable',
|
|
40
|
+
win32: join('Google', 'Chrome SxS', 'User Data'),
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
name: 'Edge',
|
|
44
|
+
darwin: join('Microsoft Edge'),
|
|
45
|
+
linux: 'microsoft-edge',
|
|
46
|
+
win32: join('Microsoft', 'Edge', 'User Data'),
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
name: 'Arc',
|
|
50
|
+
darwin: join('Arc', 'User Data'),
|
|
51
|
+
linux: '',
|
|
52
|
+
win32: join('Arc', 'User Data'),
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
name: 'Brave',
|
|
56
|
+
darwin: join('BraveSoftware', 'Brave-Browser'),
|
|
57
|
+
linux: join('BraveSoftware', 'Brave-Browser'),
|
|
58
|
+
win32: join('BraveSoftware', 'Brave-Browser', 'User Data'),
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
name: 'Vivaldi',
|
|
62
|
+
darwin: 'Vivaldi',
|
|
63
|
+
linux: 'vivaldi',
|
|
64
|
+
win32: join('Vivaldi', 'User Data'),
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
name: 'Chromium',
|
|
68
|
+
darwin: 'Chromium',
|
|
69
|
+
linux: 'chromium',
|
|
70
|
+
win32: join('Chromium', 'User Data'),
|
|
71
|
+
},
|
|
72
|
+
]
|
|
73
|
+
|
|
74
|
+
const WEBEX_STORAGE_KEY = '_https://web.webex.com\x00\x01webex-web-client-bounded'
|
|
75
|
+
|
|
76
|
+
export class WebexTokenExtractor {
|
|
77
|
+
private platform: NodeJS.Platform
|
|
78
|
+
private baseDir: string | null
|
|
79
|
+
private debugLog: ((message: string) => void) | null
|
|
80
|
+
|
|
81
|
+
constructor(platform?: NodeJS.Platform, debugLog?: (message: string) => void, baseDir?: string) {
|
|
82
|
+
this.platform = platform ?? process.platform
|
|
83
|
+
this.debugLog = debugLog ?? null
|
|
84
|
+
this.baseDir = baseDir ?? null
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
private debug(message: string): void {
|
|
88
|
+
this.debugLog?.(message)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
getBrowserProfileDirs(): string[] {
|
|
92
|
+
if (this.baseDir) {
|
|
93
|
+
return this.discoverProfileDirs(this.baseDir)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const dirs: string[] = []
|
|
97
|
+
|
|
98
|
+
for (const browser of BROWSERS) {
|
|
99
|
+
const browserBase = this.getBrowserBasePath(browser)
|
|
100
|
+
if (!browserBase) continue
|
|
101
|
+
|
|
102
|
+
const profileDirs = this.discoverProfileDirs(browserBase)
|
|
103
|
+
dirs.push(...profileDirs)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return dirs
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
private getBrowserBasePath(browser: BrowserConfig): string | null {
|
|
110
|
+
let relative: string
|
|
111
|
+
|
|
112
|
+
switch (this.platform) {
|
|
113
|
+
case 'darwin':
|
|
114
|
+
relative = browser.darwin
|
|
115
|
+
if (!relative) return null
|
|
116
|
+
return join(homedir(), 'Library', 'Application Support', relative)
|
|
117
|
+
case 'linux':
|
|
118
|
+
relative = browser.linux
|
|
119
|
+
if (!relative) return null
|
|
120
|
+
return join(homedir(), '.config', relative)
|
|
121
|
+
case 'win32':
|
|
122
|
+
relative = browser.win32
|
|
123
|
+
if (!relative) return null
|
|
124
|
+
return join(
|
|
125
|
+
process.env.LOCALAPPDATA || join(homedir(), 'AppData', 'Local'),
|
|
126
|
+
relative,
|
|
127
|
+
)
|
|
128
|
+
default:
|
|
129
|
+
return null
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
private discoverProfileDirs(browserBase: string): string[] {
|
|
134
|
+
const dirs: string[] = []
|
|
135
|
+
|
|
136
|
+
if (!existsSync(browserBase)) return dirs
|
|
137
|
+
|
|
138
|
+
const defaultLeveldb = join(browserBase, 'Default', 'Local Storage', 'leveldb')
|
|
139
|
+
if (existsSync(defaultLeveldb)) {
|
|
140
|
+
dirs.push(defaultLeveldb)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
try {
|
|
144
|
+
const entries = readdirSync(browserBase, { withFileTypes: true })
|
|
145
|
+
for (const entry of entries) {
|
|
146
|
+
if (!entry.isDirectory()) continue
|
|
147
|
+
if (!/^Profile \d+$/i.test(entry.name)) continue
|
|
148
|
+
|
|
149
|
+
const leveldb = join(browserBase, entry.name, 'Local Storage', 'leveldb')
|
|
150
|
+
if (existsSync(leveldb)) {
|
|
151
|
+
dirs.push(leveldb)
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
} catch {
|
|
155
|
+
// Ignore read errors
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return dirs
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async extract(): Promise<ExtractedWebexToken | null> {
|
|
162
|
+
const profileDirs = this.getBrowserProfileDirs()
|
|
163
|
+
|
|
164
|
+
if (profileDirs.length === 0) {
|
|
165
|
+
this.debug('No browser profile directories found')
|
|
166
|
+
return null
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
for (const leveldbDir of profileDirs) {
|
|
170
|
+
this.debug(`Scanning: ${leveldbDir}`)
|
|
171
|
+
|
|
172
|
+
const token = await this.extractViaClassicLevelCopy(leveldbDir)
|
|
173
|
+
?? this.extractFromRawFiles(leveldbDir)
|
|
174
|
+
|
|
175
|
+
if (token) {
|
|
176
|
+
this.debug(`Found token in: ${leveldbDir}`)
|
|
177
|
+
return token
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
this.debug('No Webex tokens found in any browser profile')
|
|
182
|
+
return null
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
private async extractViaClassicLevelCopy(dbPath: string): Promise<ExtractedWebexToken | null> {
|
|
186
|
+
const tempDir = join(tmpdir(), `webex-leveldb-${Date.now()}-${Math.random().toString(36).slice(2)}`)
|
|
187
|
+
|
|
188
|
+
try {
|
|
189
|
+
mkdirSync(tempDir, { recursive: true })
|
|
190
|
+
|
|
191
|
+
const files = readdirSync(dbPath)
|
|
192
|
+
for (const file of files) {
|
|
193
|
+
if (file === 'LOCK') continue
|
|
194
|
+
const src = join(dbPath, file)
|
|
195
|
+
try {
|
|
196
|
+
if (statSync(src).isFile()) {
|
|
197
|
+
copyFileSync(src, join(tempDir, file))
|
|
198
|
+
}
|
|
199
|
+
} catch {}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return await this.extractViaClassicLevel(tempDir)
|
|
203
|
+
} catch {
|
|
204
|
+
return null
|
|
205
|
+
} finally {
|
|
206
|
+
try {
|
|
207
|
+
rmSync(tempDir, { recursive: true, force: true })
|
|
208
|
+
} catch {}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
private async extractViaClassicLevel(dbPath: string): Promise<ExtractedWebexToken | null> {
|
|
213
|
+
let db: ClassicLevel<string, Buffer> | null = null
|
|
214
|
+
try {
|
|
215
|
+
db = new ClassicLevel(dbPath, { keyEncoding: 'utf8', valueEncoding: 'buffer' })
|
|
216
|
+
|
|
217
|
+
for await (const [key, value] of db.iterator()) {
|
|
218
|
+
if (!key.includes('web.webex.com')) continue
|
|
219
|
+
|
|
220
|
+
const decoded = this.decodeLevelDBValue(value)
|
|
221
|
+
if (!decoded.includes('"supertoken"') && !decoded.includes('"Credentials"')) continue
|
|
222
|
+
|
|
223
|
+
const token = this.extractTokenFromString(decoded)
|
|
224
|
+
if (token) return token
|
|
225
|
+
}
|
|
226
|
+
} catch (e) {
|
|
227
|
+
this.debug(`ClassicLevel failed: ${e instanceof Error ? e.message : String(e)}`)
|
|
228
|
+
} finally {
|
|
229
|
+
if (db) {
|
|
230
|
+
try {
|
|
231
|
+
await db.close()
|
|
232
|
+
} catch {}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return null
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
private decodeLevelDBValue(buf: Buffer): string {
|
|
239
|
+
if (buf.length < 2) return buf.toString('utf8')
|
|
240
|
+
// Chromium localStorage: 0x00 prefix = UTF-16LE, 0x01 prefix = Latin1/UTF-8
|
|
241
|
+
if (buf[0] === 0x00 && (buf.length - 1) % 2 === 0) {
|
|
242
|
+
return buf.subarray(1).toString('utf16le')
|
|
243
|
+
}
|
|
244
|
+
if (buf[0] === 0x01) {
|
|
245
|
+
return buf.subarray(1).toString('utf8')
|
|
246
|
+
}
|
|
247
|
+
return buf.toString('utf8')
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
private extractFromRawFiles(leveldbDir: string): ExtractedWebexToken | null {
|
|
251
|
+
try {
|
|
252
|
+
const files = readdirSync(leveldbDir)
|
|
253
|
+
|
|
254
|
+
const sorted = [...files]
|
|
255
|
+
.filter((f) => f.endsWith('.log') || f.endsWith('.ldb'))
|
|
256
|
+
.sort((a, b) => {
|
|
257
|
+
const aIsLog = a.endsWith('.log') ? 0 : 1
|
|
258
|
+
const bIsLog = b.endsWith('.log') ? 0 : 1
|
|
259
|
+
if (aIsLog !== bIsLog) return aIsLog - bIsLog
|
|
260
|
+
try {
|
|
261
|
+
return statSync(join(leveldbDir, b)).mtimeMs - statSync(join(leveldbDir, a)).mtimeMs
|
|
262
|
+
} catch {
|
|
263
|
+
return 0
|
|
264
|
+
}
|
|
265
|
+
})
|
|
266
|
+
|
|
267
|
+
for (const file of sorted) {
|
|
268
|
+
const token = this.extractFromFile(join(leveldbDir, file))
|
|
269
|
+
if (token) return token
|
|
270
|
+
}
|
|
271
|
+
} catch {
|
|
272
|
+
this.debug(`Failed to read directory: ${leveldbDir}`)
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return null
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
private extractFromFile(filePath: string): ExtractedWebexToken | null {
|
|
279
|
+
try {
|
|
280
|
+
const stat = statSync(filePath)
|
|
281
|
+
if (stat.size > 50 * 1024 * 1024) return null
|
|
282
|
+
|
|
283
|
+
const buffer = readFileSync(filePath)
|
|
284
|
+
return this.extractTokenFromBuffer(buffer)
|
|
285
|
+
} catch {
|
|
286
|
+
return null
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
private extractTokenFromBuffer(buffer: Buffer): ExtractedWebexToken | null {
|
|
291
|
+
// Try UTF-8 first, then strip null bytes for Chromium's UTF-16LE localStorage encoding
|
|
292
|
+
return this.extractTokenFromString(buffer.toString('utf8'))
|
|
293
|
+
?? this.extractTokenFromString(this.stripNullBytes(buffer))
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
private stripNullBytes(buffer: Buffer): string {
|
|
297
|
+
const bytes: number[] = []
|
|
298
|
+
for (let i = 0; i < buffer.length; i++) {
|
|
299
|
+
if (buffer[i] !== 0) bytes.push(buffer[i]!)
|
|
300
|
+
}
|
|
301
|
+
return Buffer.from(bytes).toString('utf8')
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
private extractTokenFromString(content: string): ExtractedWebexToken | null {
|
|
305
|
+
const outerObjectMarkerIdx = content.indexOf('"Credentials"')
|
|
306
|
+
const innermostMarkerIdx = content.indexOf('"supertoken"')
|
|
307
|
+
|
|
308
|
+
const markerIdx =
|
|
309
|
+
outerObjectMarkerIdx !== -1 ? outerObjectMarkerIdx
|
|
310
|
+
: innermostMarkerIdx !== -1 ? innermostMarkerIdx
|
|
311
|
+
: -1
|
|
312
|
+
|
|
313
|
+
if (markerIdx === -1) return null
|
|
314
|
+
|
|
315
|
+
const json = this.extractJsonAroundIndex(content, markerIdx)
|
|
316
|
+
if (!json) return null
|
|
317
|
+
|
|
318
|
+
return this.parseWebexStorage(json)
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
private extractJsonAroundIndex(content: string, markerIdx: number): string | null {
|
|
322
|
+
let depth = 0
|
|
323
|
+
let start = -1
|
|
324
|
+
for (let i = markerIdx; i >= 0; i--) {
|
|
325
|
+
if (content[i] === '}') depth++
|
|
326
|
+
if (content[i] === '{') {
|
|
327
|
+
if (depth === 0) {
|
|
328
|
+
start = i
|
|
329
|
+
break
|
|
330
|
+
}
|
|
331
|
+
depth--
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
if (start === -1) return null
|
|
335
|
+
|
|
336
|
+
depth = 0
|
|
337
|
+
let end = -1
|
|
338
|
+
for (let i = start; i < content.length; i++) {
|
|
339
|
+
if (content[i] === '{') depth++
|
|
340
|
+
if (content[i] === '}') {
|
|
341
|
+
depth--
|
|
342
|
+
if (depth === 0) {
|
|
343
|
+
end = i + 1
|
|
344
|
+
break
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
if (end === -1) return null
|
|
349
|
+
|
|
350
|
+
return content.substring(start, end)
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
parseWebexStorage(jsonStr: string): ExtractedWebexToken | null {
|
|
354
|
+
try {
|
|
355
|
+
const data = JSON.parse(jsonStr)
|
|
356
|
+
|
|
357
|
+
const supertoken =
|
|
358
|
+
data?.Credentials?.['@']?.supertoken ??
|
|
359
|
+
data?.['@']?.supertoken ??
|
|
360
|
+
data?.supertoken ??
|
|
361
|
+
null
|
|
362
|
+
|
|
363
|
+
if (!supertoken?.access_token) return null
|
|
364
|
+
|
|
365
|
+
const accessToken = String(supertoken.access_token)
|
|
366
|
+
if (accessToken.length < 20) return null
|
|
367
|
+
|
|
368
|
+
const result: ExtractedWebexToken = { accessToken }
|
|
369
|
+
|
|
370
|
+
if (supertoken.refresh_token) {
|
|
371
|
+
result.refreshToken = String(supertoken.refresh_token)
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
if (typeof supertoken.expires === 'number') {
|
|
375
|
+
result.expiresAt = supertoken.expires
|
|
376
|
+
} else if (typeof supertoken.expires_in === 'number') {
|
|
377
|
+
result.expiresAt = Date.now() + supertoken.expires_in * 1000
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
const deviceUrl =
|
|
381
|
+
data?.Device?.['@']?.url ??
|
|
382
|
+
data?.['@']?.deviceUrl ??
|
|
383
|
+
null
|
|
384
|
+
if (deviceUrl && typeof deviceUrl === 'string') {
|
|
385
|
+
result.deviceUrl = deviceUrl
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
return result
|
|
389
|
+
} catch {
|
|
390
|
+
return null
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
@@ -55,7 +55,8 @@ export interface WebexConfig {
|
|
|
55
55
|
expiresAt: number
|
|
56
56
|
clientId?: string
|
|
57
57
|
clientSecret?: string
|
|
58
|
-
tokenType?: 'oauth' | 'manual'
|
|
58
|
+
tokenType?: 'oauth' | 'manual' | 'extracted'
|
|
59
|
+
deviceUrl?: string
|
|
59
60
|
}
|
|
60
61
|
|
|
61
62
|
export class WebexError extends Error {
|
|
@@ -123,5 +124,6 @@ export const WebexConfigSchema = z.object({
|
|
|
123
124
|
expiresAt: z.number(),
|
|
124
125
|
clientId: z.string().optional(),
|
|
125
126
|
clientSecret: z.string().optional(),
|
|
126
|
-
tokenType: z.enum(['oauth', 'manual']).optional(),
|
|
127
|
+
tokenType: z.enum(['oauth', 'manual', 'extracted']).optional(),
|
|
128
|
+
deviceUrl: z.string().optional(),
|
|
127
129
|
})
|
|
@@ -25,10 +25,14 @@ import {
|
|
|
25
25
|
|
|
26
26
|
const MAX_MESSAGES_PER_CHAT = 500
|
|
27
27
|
|
|
28
|
-
function toTimestampMs(ts:
|
|
28
|
+
function toTimestampMs(ts: unknown): number {
|
|
29
29
|
if (ts == null) return 0
|
|
30
|
-
if (typeof ts === '
|
|
31
|
-
|
|
30
|
+
if (typeof ts === 'number') return ts * 1000
|
|
31
|
+
if (typeof ts === 'object' && ts !== null && 'toNumber' in ts && typeof (ts as Record<string, unknown>).toNumber === 'function') {
|
|
32
|
+
return (ts as { toNumber(): number }).toNumber() * 1000
|
|
33
|
+
}
|
|
34
|
+
const n = Number(ts)
|
|
35
|
+
return Number.isNaN(n) ? 0 : n * 1000
|
|
32
36
|
}
|
|
33
37
|
|
|
34
38
|
function resolveJid(input: string): string {
|
|
@@ -443,8 +447,8 @@ export class WhatsAppClient {
|
|
|
443
447
|
const sorted = chats.sort((a, b) => {
|
|
444
448
|
const aTime = a.conversationTimestamp
|
|
445
449
|
const bTime = b.conversationTimestamp
|
|
446
|
-
const aMs = toTimestampMs(aTime
|
|
447
|
-
const bMs = toTimestampMs(bTime
|
|
450
|
+
const aMs = toTimestampMs(aTime)
|
|
451
|
+
const bMs = toTimestampMs(bTime)
|
|
448
452
|
return bMs - aMs
|
|
449
453
|
})
|
|
450
454
|
|
|
@@ -474,8 +478,8 @@ export class WhatsAppClient {
|
|
|
474
478
|
|
|
475
479
|
const msgs = this.messages.get(resolvedJid) ?? []
|
|
476
480
|
const sorted = [...msgs].sort((a, b) => {
|
|
477
|
-
const aMs = toTimestampMs(a.messageTimestamp
|
|
478
|
-
const bMs = toTimestampMs(b.messageTimestamp
|
|
481
|
+
const aMs = toTimestampMs(a.messageTimestamp)
|
|
482
|
+
const bMs = toTimestampMs(b.messageTimestamp)
|
|
479
483
|
return aMs - bMs
|
|
480
484
|
})
|
|
481
485
|
|
|
@@ -3,7 +3,7 @@ import { mkdir, readFile, rm, writeFile } from 'node:fs/promises'
|
|
|
3
3
|
import { homedir } from 'node:os'
|
|
4
4
|
import { join } from 'node:path'
|
|
5
5
|
|
|
6
|
-
export type Platform = 'slack' | 'discord' | 'teams'
|
|
6
|
+
export type Platform = 'slack' | 'discord' | 'teams' | 'webex'
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Caches derived encryption keys to avoid repeated macOS Keychain prompts.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
const isTTY = process.stderr.isTTY ?? false
|
|
2
|
+
|
|
3
|
+
const RESET = isTTY ? '\x1b[0m' : ''
|
|
4
|
+
const RED = isTTY ? '\x1b[31m' : ''
|
|
5
|
+
const YELLOW = isTTY ? '\x1b[33m' : ''
|
|
6
|
+
const DIM = isTTY ? '\x1b[2m' : ''
|
|
7
|
+
|
|
8
|
+
export function info(message: string): void {
|
|
9
|
+
process.stderr.write(`${message}\n`)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function warn(message: string): void {
|
|
13
|
+
process.stderr.write(`${YELLOW}${message}${RESET}\n`)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function error(message: string): void {
|
|
17
|
+
process.stderr.write(`${RED}${message}${RESET}\n`)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function debug(message: string): void {
|
|
21
|
+
process.stderr.write(`${DIM}${message}${RESET}\n`)
|
|
22
|
+
}
|