@palmyr/cli 1.9.0 → 1.10.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/README.md +46 -0
- package/dist/cli.js +467 -29
- package/dist/cli.js.map +1 -1
- package/dist/credential-store.js +3 -3
- package/dist/credential-store.js.map +1 -1
- package/dist/sdk.d.ts +10 -0
- package/dist/sdk.js +10 -0
- package/dist/sdk.js.map +1 -1
- package/dist/social-analytics.d.ts +47 -0
- package/dist/social-analytics.js +102 -0
- package/dist/social-analytics.js.map +1 -0
- package/dist/social-drafts.d.ts +43 -0
- package/dist/social-drafts.js +92 -0
- package/dist/social-drafts.js.map +1 -0
- package/dist/social-monitor.d.ts +37 -0
- package/dist/social-monitor.js +147 -0
- package/dist/social-monitor.js.map +1 -0
- package/dist/social-vault.d.ts +13 -1
- package/dist/social-vault.js +20 -2
- package/dist/social-vault.js.map +1 -1
- package/dist/tiktok-connect.d.ts +58 -0
- package/dist/tiktok-connect.js +518 -0
- package/dist/tiktok-connect.js.map +1 -0
- package/dist/wallet-daemon.js +20 -4
- package/dist/wallet-daemon.js.map +1 -1
- package/dist/wallet-doctor.js +8 -2
- package/dist/wallet-doctor.js.map +1 -1
- package/dist/wallet-trading.d.ts +2 -0
- package/dist/wallet-trading.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,518 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local real-browser TikTok login — the agent-smooth alternative to fighting
|
|
3
|
+
* TikTok's anti-bot from a headless VPS session (the old `tiktok login` form
|
|
4
|
+
* driver perma-spun ~70% of the time).
|
|
5
|
+
*
|
|
6
|
+
* The idea (borrowed from Hermes Agent's `/browser connect`): don't emulate a
|
|
7
|
+
* human, *be* one. We launch the operator's real Chrome/Edge/Brave — real
|
|
8
|
+
* fingerprint, real home IP — point it at TikTok's login page, and let the
|
|
9
|
+
* human solve login + captcha + 2FA themselves (free, and unbeatable by any
|
|
10
|
+
* solver). The moment a `sessionid` cookie appears we harvest the full jar and
|
|
11
|
+
* hand it back; the caller drops it into the same encrypted session cache that
|
|
12
|
+
* `post`/`follow`/`like` already read, so every downstream op is unchanged.
|
|
13
|
+
*
|
|
14
|
+
* Why CDP and not Playwright: TikTok's `sessionid` is HttpOnly, so a
|
|
15
|
+
* `document.cookie` scrape can never see it. CDP's `Network.getAllCookies`
|
|
16
|
+
* reads HttpOnly cookies directly — and it rides the `ws` dependency the CLI
|
|
17
|
+
* already ships, so this adds zero install weight.
|
|
18
|
+
*
|
|
19
|
+
* Agent contract: this never blocks forever (bounded by `timeoutMs`) and never
|
|
20
|
+
* needs a keystroke (it auto-detects the session). The caller gets a single
|
|
21
|
+
* structured result it can branch on.
|
|
22
|
+
*/
|
|
23
|
+
import { spawn } from "child_process";
|
|
24
|
+
import { existsSync, mkdtempSync, rmSync, writeFileSync } from "fs";
|
|
25
|
+
import { tmpdir } from "os";
|
|
26
|
+
import { join } from "path";
|
|
27
|
+
import { createServer } from "net";
|
|
28
|
+
import http from "http";
|
|
29
|
+
import WebSocket from "ws";
|
|
30
|
+
/**
|
|
31
|
+
* Find a Chromium-family browser. Only returns paths that exist on disk, so a
|
|
32
|
+
* null result is a reliable "no browser here" signal (→ graceful handoff to the
|
|
33
|
+
* manual import path rather than a doomed launch).
|
|
34
|
+
*/
|
|
35
|
+
function findBrowser(explicit) {
|
|
36
|
+
const tryList = [];
|
|
37
|
+
const override = explicit || process.env.PALMYR_BROWSER_PATH;
|
|
38
|
+
if (override)
|
|
39
|
+
tryList.push({ path: override, name: "custom" });
|
|
40
|
+
const plat = process.platform;
|
|
41
|
+
if (plat === "win32") {
|
|
42
|
+
const pf = process.env["PROGRAMFILES"] || "C:\\Program Files";
|
|
43
|
+
const pf86 = process.env["PROGRAMFILES(X86)"] || "C:\\Program Files (x86)";
|
|
44
|
+
const local = process.env["LOCALAPPDATA"];
|
|
45
|
+
tryList.push({ path: join(pf, "Google\\Chrome\\Application\\chrome.exe"), name: "chrome" }, { path: join(pf86, "Google\\Chrome\\Application\\chrome.exe"), name: "chrome" }, ...(local ? [{ path: join(local, "Google\\Chrome\\Application\\chrome.exe"), name: "chrome" }] : []), { path: join(pf86, "Microsoft\\Edge\\Application\\msedge.exe"), name: "edge" }, { path: join(pf, "Microsoft\\Edge\\Application\\msedge.exe"), name: "edge" }, ...(local ? [{ path: join(local, "BraveSoftware\\Brave-Browser\\Application\\brave.exe"), name: "brave" }] : []));
|
|
46
|
+
}
|
|
47
|
+
else if (plat === "darwin") {
|
|
48
|
+
tryList.push({ path: "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome", name: "chrome" }, { path: "/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge", name: "edge" }, { path: "/Applications/Brave Browser.app/Contents/MacOS/Brave Browser", name: "brave" }, { path: "/Applications/Chromium.app/Contents/MacOS/Chromium", name: "chromium" });
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
// Linux — enumerate common absolute locations across distros/snap.
|
|
52
|
+
for (const dir of ["/usr/bin", "/usr/local/bin", "/snap/bin", "/opt/google/chrome"]) {
|
|
53
|
+
tryList.push({ path: join(dir, "google-chrome"), name: "chrome" }, { path: join(dir, "google-chrome-stable"), name: "chrome" }, { path: join(dir, "chromium"), name: "chromium" }, { path: join(dir, "chromium-browser"), name: "chromium" }, { path: join(dir, "microsoft-edge"), name: "edge" }, { path: join(dir, "brave-browser"), name: "brave" }, { path: join(dir, "chrome"), name: "chrome" });
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
for (const c of tryList) {
|
|
57
|
+
try {
|
|
58
|
+
if (existsSync(c.path))
|
|
59
|
+
return c;
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
/* keep looking */
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
/* ─── Small helpers ──────────────────────────────────────────────────────── */
|
|
68
|
+
function getFreePort() {
|
|
69
|
+
return new Promise((resolve, reject) => {
|
|
70
|
+
const srv = createServer();
|
|
71
|
+
srv.on("error", reject);
|
|
72
|
+
srv.listen(0, "127.0.0.1", () => {
|
|
73
|
+
const addr = srv.address();
|
|
74
|
+
const port = typeof addr === "object" && addr ? addr.port : 0;
|
|
75
|
+
srv.close(() => (port ? resolve(port) : reject(new Error("could not allocate a port"))));
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
function httpGetJson(url, timeoutMs = 2000) {
|
|
80
|
+
return new Promise((resolve, reject) => {
|
|
81
|
+
const req = http.get(url, (res) => {
|
|
82
|
+
let body = "";
|
|
83
|
+
res.on("data", (d) => (body += d));
|
|
84
|
+
res.on("end", () => {
|
|
85
|
+
try {
|
|
86
|
+
resolve(JSON.parse(body));
|
|
87
|
+
}
|
|
88
|
+
catch (e) {
|
|
89
|
+
reject(e);
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
req.on("error", reject);
|
|
94
|
+
req.setTimeout(timeoutMs, () => req.destroy(new Error("timeout")));
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
98
|
+
/** CDP `sameSite` → Playwright's accepted set; omit anything unexpected. */
|
|
99
|
+
function normalizeSameSite(v) {
|
|
100
|
+
if (v === "Strict" || v === "Lax" || v === "None")
|
|
101
|
+
return v;
|
|
102
|
+
return undefined;
|
|
103
|
+
}
|
|
104
|
+
/** CDP cookie → the clean Playwright-injectable shape (drops size/priority/etc.). */
|
|
105
|
+
function normalizeCookie(c) {
|
|
106
|
+
const out = {
|
|
107
|
+
name: c.name,
|
|
108
|
+
value: c.value,
|
|
109
|
+
domain: c.domain,
|
|
110
|
+
path: c.path || "/",
|
|
111
|
+
httpOnly: !!c.httpOnly,
|
|
112
|
+
secure: !!c.secure,
|
|
113
|
+
};
|
|
114
|
+
// CDP gives expires in Unix seconds, or -1 for a session cookie — omit those.
|
|
115
|
+
if (typeof c.expires === "number" && c.expires > 0)
|
|
116
|
+
out.expires = Math.floor(c.expires);
|
|
117
|
+
const ss = normalizeSameSite(c.sameSite);
|
|
118
|
+
if (ss)
|
|
119
|
+
out.sameSite = ss;
|
|
120
|
+
return out;
|
|
121
|
+
}
|
|
122
|
+
function isTikTokCookie(domain) {
|
|
123
|
+
return domain.replace(/^\./, "").toLowerCase().endsWith("tiktok.com");
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Timezone → ISO country, mirroring the server's COUNTRY_PROFILES set (plus the
|
|
127
|
+
* extra US/CA/AU zones a single capital-city entry misses). Only consulted when
|
|
128
|
+
* the browser locale has no region subtag, so it doesn't need to be exhaustive.
|
|
129
|
+
*/
|
|
130
|
+
const TZ_COUNTRY = {
|
|
131
|
+
"America/New_York": "us", "America/Chicago": "us", "America/Denver": "us",
|
|
132
|
+
"America/Los_Angeles": "us", "America/Phoenix": "us", "America/Anchorage": "us",
|
|
133
|
+
"America/Toronto": "ca", "America/Vancouver": "ca", "America/Edmonton": "ca",
|
|
134
|
+
"Europe/London": "gb", "Europe/Dublin": "ie",
|
|
135
|
+
"Australia/Sydney": "au", "Australia/Melbourne": "au", "Australia/Perth": "au", "Australia/Brisbane": "au",
|
|
136
|
+
"Pacific/Auckland": "nz",
|
|
137
|
+
"Europe/Berlin": "de", "Europe/Vienna": "at", "Europe/Zurich": "ch",
|
|
138
|
+
"Europe/Paris": "fr", "Europe/Brussels": "be", "Europe/Amsterdam": "nl",
|
|
139
|
+
"Europe/Madrid": "es", "Europe/Rome": "it", "Europe/Lisbon": "pt",
|
|
140
|
+
"Europe/Warsaw": "pl", "Europe/Prague": "cz", "Europe/Stockholm": "se",
|
|
141
|
+
"Europe/Oslo": "no", "Europe/Copenhagen": "dk", "Europe/Helsinki": "fi",
|
|
142
|
+
"Europe/Athens": "gr", "Europe/Bucharest": "ro", "Europe/Budapest": "hu",
|
|
143
|
+
"Europe/Istanbul": "tr", "Europe/Moscow": "ru", "Europe/Kyiv": "ua",
|
|
144
|
+
"America/Sao_Paulo": "br", "America/Mexico_City": "mx", "America/Argentina/Buenos_Aires": "ar",
|
|
145
|
+
"Asia/Tokyo": "jp", "Asia/Seoul": "kr", "Asia/Kolkata": "in", "Asia/Jakarta": "id",
|
|
146
|
+
"Asia/Manila": "ph", "Asia/Bangkok": "th", "Asia/Ho_Chi_Minh": "vn", "Asia/Singapore": "sg",
|
|
147
|
+
"Asia/Kuala_Lumpur": "my", "Asia/Dubai": "ae", "Asia/Riyadh": "sa", "Asia/Jerusalem": "il",
|
|
148
|
+
"Africa/Johannesburg": "za",
|
|
149
|
+
};
|
|
150
|
+
/**
|
|
151
|
+
* Infer the account's country from the live browser. Locale region subtag is
|
|
152
|
+
* the primary signal (e.g. `en-US` → us, `pt-BR` → br) since modern browsers
|
|
153
|
+
* almost always include it; timezone is the fallback when they don't.
|
|
154
|
+
*/
|
|
155
|
+
function countryFromBrowser(locale, timezone) {
|
|
156
|
+
if (locale) {
|
|
157
|
+
const m = locale.match(/[-_]([A-Za-z]{2})(?:$|[-_])/);
|
|
158
|
+
if (m)
|
|
159
|
+
return m[1].toLowerCase();
|
|
160
|
+
}
|
|
161
|
+
if (timezone && TZ_COUNTRY[timezone])
|
|
162
|
+
return TZ_COUNTRY[timezone];
|
|
163
|
+
return undefined;
|
|
164
|
+
}
|
|
165
|
+
/* ─── Minimal CDP client over a single page target ───────────────────────── */
|
|
166
|
+
class CdpSession {
|
|
167
|
+
ws;
|
|
168
|
+
nextId = 1;
|
|
169
|
+
pending = new Map();
|
|
170
|
+
closed = false;
|
|
171
|
+
constructor(ws) {
|
|
172
|
+
this.ws = ws;
|
|
173
|
+
ws.on("message", (data) => {
|
|
174
|
+
let msg;
|
|
175
|
+
try {
|
|
176
|
+
msg = JSON.parse(data.toString());
|
|
177
|
+
}
|
|
178
|
+
catch {
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
if (typeof msg.id === "number" && this.pending.has(msg.id)) {
|
|
182
|
+
const p = this.pending.get(msg.id);
|
|
183
|
+
this.pending.delete(msg.id);
|
|
184
|
+
if (msg.error)
|
|
185
|
+
p.reject(new Error(msg.error.message || "CDP error"));
|
|
186
|
+
else
|
|
187
|
+
p.resolve(msg.result);
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
ws.on("close", () => {
|
|
191
|
+
this.closed = true;
|
|
192
|
+
for (const p of this.pending.values())
|
|
193
|
+
p.reject(new Error("CDP socket closed"));
|
|
194
|
+
this.pending.clear();
|
|
195
|
+
});
|
|
196
|
+
ws.on("error", () => {
|
|
197
|
+
/* surfaced via close */
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
static connect(wsUrl) {
|
|
201
|
+
return new Promise((resolve, reject) => {
|
|
202
|
+
const ws = new WebSocket(wsUrl);
|
|
203
|
+
const onErr = (e) => reject(e);
|
|
204
|
+
ws.once("error", onErr);
|
|
205
|
+
ws.once("open", () => {
|
|
206
|
+
ws.removeListener("error", onErr);
|
|
207
|
+
resolve(new CdpSession(ws));
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
send(method, params = {}) {
|
|
212
|
+
if (this.closed)
|
|
213
|
+
return Promise.reject(new Error("CDP socket closed"));
|
|
214
|
+
const id = this.nextId++;
|
|
215
|
+
return new Promise((resolve, reject) => {
|
|
216
|
+
this.pending.set(id, { resolve, reject });
|
|
217
|
+
this.ws.send(JSON.stringify({ id, method, params }), (e) => {
|
|
218
|
+
if (e) {
|
|
219
|
+
this.pending.delete(id);
|
|
220
|
+
reject(e);
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
close() {
|
|
226
|
+
try {
|
|
227
|
+
this.ws.close();
|
|
228
|
+
}
|
|
229
|
+
catch {
|
|
230
|
+
/* noop */
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
/** Pick the first inspectable page target from CDP's target list. */
|
|
235
|
+
async function findPageTarget(port) {
|
|
236
|
+
try {
|
|
237
|
+
const targets = await httpGetJson(`http://127.0.0.1:${port}/json`);
|
|
238
|
+
if (!Array.isArray(targets))
|
|
239
|
+
return null;
|
|
240
|
+
const page = targets.find((t) => t.type === "page" && typeof t.webSocketDebuggerUrl === "string");
|
|
241
|
+
return page?.webSocketDebuggerUrl || null;
|
|
242
|
+
}
|
|
243
|
+
catch {
|
|
244
|
+
return null;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Extract TikTok's login QR from `/login/qrcode`. The QR is a same-origin
|
|
249
|
+
* <canvas> inside [data-e2e="qr-code"] (verified against the live page), so
|
|
250
|
+
* `canvas.toDataURL()` works; fall back to a CDP element screenshot if the
|
|
251
|
+
* canvas is ever tainted or swapped for an <img>. Polls until it renders.
|
|
252
|
+
*/
|
|
253
|
+
async function extractTikTokQr(cdp) {
|
|
254
|
+
for (let i = 0; i < 12; i++) {
|
|
255
|
+
try {
|
|
256
|
+
const ev = await cdp.send("Runtime.evaluate", {
|
|
257
|
+
expression: `(()=>{const c=document.querySelector('[data-e2e="qr-code"] canvas')||document.querySelector('canvas');` +
|
|
258
|
+
`if(!c||!c.width)return '';try{return c.toDataURL('image/png');}catch(e){return 'TAINTED';}})()`,
|
|
259
|
+
returnByValue: true,
|
|
260
|
+
});
|
|
261
|
+
const v = ev?.result?.value;
|
|
262
|
+
if (typeof v === "string" && v.startsWith("data:image"))
|
|
263
|
+
return { dataUrl: v };
|
|
264
|
+
}
|
|
265
|
+
catch {
|
|
266
|
+
/* retry */
|
|
267
|
+
}
|
|
268
|
+
await sleep(1000);
|
|
269
|
+
}
|
|
270
|
+
// Fallback: screenshot the QR element region.
|
|
271
|
+
try {
|
|
272
|
+
const boxEv = await cdp.send("Runtime.evaluate", {
|
|
273
|
+
expression: `(()=>{const e=document.querySelector('[data-e2e="qr-code"]')||document.querySelector('canvas');` +
|
|
274
|
+
`if(!e)return '';const r=e.getBoundingClientRect();return JSON.stringify({x:r.x,y:r.y,w:r.width,h:r.height});})()`,
|
|
275
|
+
returnByValue: true,
|
|
276
|
+
});
|
|
277
|
+
const v = boxEv?.result?.value;
|
|
278
|
+
if (typeof v === "string" && v) {
|
|
279
|
+
const b = JSON.parse(v);
|
|
280
|
+
if (b.w > 0) {
|
|
281
|
+
const shot = await cdp.send("Page.captureScreenshot", {
|
|
282
|
+
format: "png",
|
|
283
|
+
clip: { x: b.x, y: b.y, width: b.w, height: b.h, scale: 1 },
|
|
284
|
+
});
|
|
285
|
+
if (shot?.data)
|
|
286
|
+
return { dataUrl: "data:image/png;base64," + shot.data };
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
catch {
|
|
291
|
+
/* give up — caller reports no QR */
|
|
292
|
+
}
|
|
293
|
+
return {};
|
|
294
|
+
}
|
|
295
|
+
function saveQrPng(dataUrl) {
|
|
296
|
+
const b64 = dataUrl.split(",")[1] || "";
|
|
297
|
+
const path = join(tmpdir(), `palmyr-tiktok-qr-${Date.now()}.png`);
|
|
298
|
+
writeFileSync(path, Buffer.from(b64, "base64"));
|
|
299
|
+
return path;
|
|
300
|
+
}
|
|
301
|
+
/* ─── Main entry ─────────────────────────────────────────────────────────── */
|
|
302
|
+
export async function connectTikTok(opts = {}) {
|
|
303
|
+
const timeoutMs = opts.timeoutMs ?? 300_000;
|
|
304
|
+
const progress = opts.onProgress ?? (() => { });
|
|
305
|
+
const browser = findBrowser(opts.browserPath);
|
|
306
|
+
if (!browser) {
|
|
307
|
+
return {
|
|
308
|
+
success: false,
|
|
309
|
+
reason: "no_local_browser",
|
|
310
|
+
error: "No Chrome/Edge/Brave found. Install one, pass --browser-path, or import cookies manually from DevTools.",
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
if (opts.browserPath && !existsSync(opts.browserPath)) {
|
|
314
|
+
return { success: false, reason: "no_local_browser", error: `--browser-path not found: ${opts.browserPath}` };
|
|
315
|
+
}
|
|
316
|
+
let port;
|
|
317
|
+
try {
|
|
318
|
+
port = await getFreePort();
|
|
319
|
+
}
|
|
320
|
+
catch (e) {
|
|
321
|
+
return { success: false, reason: "launch_failed", error: e.message };
|
|
322
|
+
}
|
|
323
|
+
// Fresh, throwaway profile dir — this is what forces a brand-new browser
|
|
324
|
+
// process that actually owns the debug port. Launching against the user's
|
|
325
|
+
// normal profile while their browser is already open would just open a tab
|
|
326
|
+
// in the existing process and ignore --remote-debugging-port entirely.
|
|
327
|
+
let userDataDir;
|
|
328
|
+
try {
|
|
329
|
+
userDataDir = mkdtempSync(join(tmpdir(), "palmyr-tiktok-"));
|
|
330
|
+
}
|
|
331
|
+
catch (e) {
|
|
332
|
+
return { success: false, reason: "launch_failed", error: `could not create temp profile: ${e.message}` };
|
|
333
|
+
}
|
|
334
|
+
// Chromium won't start its sandbox as root and exits immediately — and many
|
|
335
|
+
// container / CI / agent shells run as root. Detect that (or an explicit
|
|
336
|
+
// opt-in) and drop the sandbox so the browser actually launches. On a normal
|
|
337
|
+
// non-root desktop the sandbox stays on.
|
|
338
|
+
const isRootPosix = process.platform !== "win32" && typeof process.getuid === "function" && process.getuid() === 0;
|
|
339
|
+
const noSandbox = opts.noSandbox === true || isRootPosix;
|
|
340
|
+
// QR mode lands on TikTok's QR-login page; typed login on the form page.
|
|
341
|
+
const landingUrl = opts.qr
|
|
342
|
+
? "https://www.tiktok.com/login/qrcode"
|
|
343
|
+
: "https://www.tiktok.com/login/phone-or-email/email";
|
|
344
|
+
// Always run HEADED. TikTok refuses to authorize a login into a headless
|
|
345
|
+
// session — headless Chrome's UA literally contains "HeadlessChrome", which it
|
|
346
|
+
// detects and rejects ("Couldn't log in. Try another login method"). A headed
|
|
347
|
+
// window presents a normal Chrome UA with no automation tells. On a server,
|
|
348
|
+
// run under a virtual display (xvfb). --disable-blink-features keeps
|
|
349
|
+
// navigator.webdriver clean across Chrome versions.
|
|
350
|
+
const args = [
|
|
351
|
+
`--remote-debugging-port=${port}`,
|
|
352
|
+
`--user-data-dir=${userDataDir}`,
|
|
353
|
+
"--no-first-run",
|
|
354
|
+
"--no-default-browser-check",
|
|
355
|
+
"--new-window",
|
|
356
|
+
"--disable-blink-features=AutomationControlled",
|
|
357
|
+
"--window-size=1280,800", // realistic, non-zero geometry (a headless tell)
|
|
358
|
+
...(noSandbox ? ["--no-sandbox", "--disable-dev-shm-usage"] : []),
|
|
359
|
+
landingUrl,
|
|
360
|
+
];
|
|
361
|
+
let child;
|
|
362
|
+
try {
|
|
363
|
+
child = spawn(browser.path, args, { stdio: "ignore", detached: false });
|
|
364
|
+
}
|
|
365
|
+
catch (e) {
|
|
366
|
+
cleanupDir(userDataDir);
|
|
367
|
+
return { success: false, reason: "launch_failed", error: `${browser.name} launch failed: ${e.message}` };
|
|
368
|
+
}
|
|
369
|
+
let childExited = false;
|
|
370
|
+
child.on("exit", () => (childExited = true));
|
|
371
|
+
if (noSandbox)
|
|
372
|
+
progress("launched with --no-sandbox (root / headless environment detected).");
|
|
373
|
+
if (opts.qr) {
|
|
374
|
+
progress(`opened ${browser.name} — fetching TikTok's login QR (a window opens; scan from it or the saved image)…`);
|
|
375
|
+
}
|
|
376
|
+
else {
|
|
377
|
+
progress(`opened ${browser.name} — log in to TikTok (solve any captcha / 2FA yourself).`);
|
|
378
|
+
progress("capturing automatically the moment you're signed in… (window will close on success)");
|
|
379
|
+
}
|
|
380
|
+
const deadline = Date.now() + timeoutMs;
|
|
381
|
+
let cdp = null;
|
|
382
|
+
let qrPngPath;
|
|
383
|
+
let qrDataUrl;
|
|
384
|
+
let result = { success: false, reason: "login_timeout", error: "timed out waiting for login", browser: browser.name };
|
|
385
|
+
try {
|
|
386
|
+
while (Date.now() < deadline) {
|
|
387
|
+
// The human closed the window before finishing → don't hang out the clock.
|
|
388
|
+
if (childExited) {
|
|
389
|
+
result = {
|
|
390
|
+
success: false,
|
|
391
|
+
reason: "aborted",
|
|
392
|
+
error: "browser closed before a session was captured. On a headless / root box the browser can exit instantly — ensure a display is available (e.g. xvfb) and the sandbox is off (auto on root Linux, or pass --no-sandbox).",
|
|
393
|
+
browser: browser.name,
|
|
394
|
+
};
|
|
395
|
+
break;
|
|
396
|
+
}
|
|
397
|
+
// (Re)attach if we have no live CDP session — covers the brief startup
|
|
398
|
+
// window and the case where the user navigated in a way that recycled
|
|
399
|
+
// the page target.
|
|
400
|
+
if (!cdp || cdp.closed) {
|
|
401
|
+
const wsUrl = await findPageTarget(port);
|
|
402
|
+
if (wsUrl) {
|
|
403
|
+
try {
|
|
404
|
+
cdp = await CdpSession.connect(wsUrl);
|
|
405
|
+
await cdp.send("Network.enable");
|
|
406
|
+
if (opts.qr) {
|
|
407
|
+
try {
|
|
408
|
+
await cdp.send("Page.enable");
|
|
409
|
+
}
|
|
410
|
+
catch { /* screenshot fallback only */ }
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
catch {
|
|
414
|
+
cdp = null;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
if (cdp && !cdp.closed) {
|
|
419
|
+
// QR mode: surface the QR once, as soon as it renders, so the agent can
|
|
420
|
+
// forward it to a human while we keep polling for the scan.
|
|
421
|
+
if (opts.qr && !qrDataUrl) {
|
|
422
|
+
const { dataUrl } = await extractTikTokQr(cdp);
|
|
423
|
+
if (dataUrl) {
|
|
424
|
+
qrDataUrl = dataUrl;
|
|
425
|
+
try {
|
|
426
|
+
qrPngPath = saveQrPng(dataUrl);
|
|
427
|
+
}
|
|
428
|
+
catch { /* path optional */ }
|
|
429
|
+
progress("QR ready — forward it to a human to scan with the TikTok app; I capture the session automatically once they confirm.");
|
|
430
|
+
if (qrPngPath)
|
|
431
|
+
progress(`QR image saved: ${qrPngPath}`);
|
|
432
|
+
try {
|
|
433
|
+
await opts.onQr?.(dataUrl);
|
|
434
|
+
}
|
|
435
|
+
catch { /* hosting is optional; PNG/data-URL still work */ }
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
try {
|
|
439
|
+
const { cookies } = await cdp.send("Network.getAllCookies");
|
|
440
|
+
const ttCookies = (cookies || []).filter((c) => isTikTokCookie(c.domain));
|
|
441
|
+
const sessionid = ttCookies.find((c) => c.name === "sessionid" && typeof c.value === "string" && c.value.length > 10);
|
|
442
|
+
if (sessionid) {
|
|
443
|
+
const harvested = ttCookies.map(normalizeCookie);
|
|
444
|
+
// Infer the account's country from the real browser env so the
|
|
445
|
+
// operator/agent never has to supply --country. Best-effort: if the
|
|
446
|
+
// eval fails, the caller falls back to its own default.
|
|
447
|
+
let detectedLocale;
|
|
448
|
+
let detectedTimezone;
|
|
449
|
+
try {
|
|
450
|
+
const ev = await cdp.send("Runtime.evaluate", {
|
|
451
|
+
expression: "JSON.stringify({l:navigator.language,t:Intl.DateTimeFormat().resolvedOptions().timeZone})",
|
|
452
|
+
returnByValue: true,
|
|
453
|
+
});
|
|
454
|
+
const v = ev?.result?.value;
|
|
455
|
+
if (typeof v === "string") {
|
|
456
|
+
const parsed = JSON.parse(v);
|
|
457
|
+
detectedLocale = typeof parsed.l === "string" ? parsed.l : undefined;
|
|
458
|
+
detectedTimezone = typeof parsed.t === "string" ? parsed.t : undefined;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
catch {
|
|
462
|
+
/* detection is best-effort */
|
|
463
|
+
}
|
|
464
|
+
result = {
|
|
465
|
+
success: true,
|
|
466
|
+
cookies: harvested,
|
|
467
|
+
cookiesCaptured: harvested.length,
|
|
468
|
+
sessionidPresent: true,
|
|
469
|
+
browser: browser.name,
|
|
470
|
+
detectedLocale,
|
|
471
|
+
detectedTimezone,
|
|
472
|
+
detectedCountry: countryFromBrowser(detectedLocale, detectedTimezone),
|
|
473
|
+
};
|
|
474
|
+
progress(`captured ${harvested.length} cookies (sessionid present)` +
|
|
475
|
+
(result.detectedCountry ? `; detected country: ${result.detectedCountry}.` : "."));
|
|
476
|
+
break;
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
catch {
|
|
480
|
+
// Transient — socket may have closed mid-poll; loop re-attaches.
|
|
481
|
+
cdp = null;
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
await sleep(2000);
|
|
485
|
+
}
|
|
486
|
+
// Carry the QR artifacts onto whatever we return (incl. timeout) so the
|
|
487
|
+
// caller always has the QR that was generated.
|
|
488
|
+
if (qrPngPath && !result.qrPngPath)
|
|
489
|
+
result.qrPngPath = qrPngPath;
|
|
490
|
+
if (qrDataUrl && !result.qrDataUrl)
|
|
491
|
+
result.qrDataUrl = qrDataUrl;
|
|
492
|
+
}
|
|
493
|
+
finally {
|
|
494
|
+
if (cdp)
|
|
495
|
+
cdp.close();
|
|
496
|
+
if (!childExited) {
|
|
497
|
+
try {
|
|
498
|
+
child.kill();
|
|
499
|
+
}
|
|
500
|
+
catch {
|
|
501
|
+
/* noop */
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
cleanupDir(userDataDir);
|
|
505
|
+
}
|
|
506
|
+
return result;
|
|
507
|
+
}
|
|
508
|
+
function cleanupDir(dir) {
|
|
509
|
+
// Chrome may briefly hold a lock on the profile dir after kill; a failed
|
|
510
|
+
// cleanup is harmless (OS temp reclaims it), so swallow errors.
|
|
511
|
+
try {
|
|
512
|
+
rmSync(dir, { recursive: true, force: true });
|
|
513
|
+
}
|
|
514
|
+
catch {
|
|
515
|
+
/* noop */
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
//# sourceMappingURL=tiktok-connect.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tiktok-connect.js","sourceRoot":"","sources":["../tiktok-connect.ts"],"names":[],"mappings":"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"}
|
package/dist/wallet-daemon.js
CHANGED
|
@@ -125,19 +125,24 @@ export function evaluateTriggers(p) {
|
|
|
125
125
|
proposedAction: 'sell-100',
|
|
126
126
|
autoExecuted: false,
|
|
127
127
|
};
|
|
128
|
+
// Without auto-execute the position stays 'open', so a threshold that's met
|
|
129
|
+
// keeps re-firing every tick — spamming pending.jsonl + the trade log. Gate
|
|
130
|
+
// each trigger on a fire-once watermark (mirrors lastThesisFiredAt below).
|
|
131
|
+
const fired = p.monitorState?.firedTriggers ?? {};
|
|
128
132
|
const cutThreshold = parsePctString(p.exitPlan.cut);
|
|
129
|
-
if (cutThreshold !== null && currentPct <= cutThreshold) {
|
|
133
|
+
if (cutThreshold !== null && currentPct <= cutThreshold && !fired.cut) {
|
|
130
134
|
out.push({ ...base, trigger: 'cut', thresholdPct: cutThreshold });
|
|
131
135
|
}
|
|
132
136
|
const tpThreshold = parsePctString(p.exitPlan.takeProfit);
|
|
133
|
-
if (tpThreshold !== null && currentPct >= tpThreshold) {
|
|
137
|
+
if (tpThreshold !== null && currentPct >= tpThreshold && !fired.takeProfit) {
|
|
134
138
|
out.push({ ...base, trigger: 'takeProfit', thresholdPct: tpThreshold });
|
|
135
139
|
}
|
|
136
140
|
const trailPct = parsePctString(p.exitPlan.trailingStop);
|
|
137
141
|
if (trailPct !== null &&
|
|
138
142
|
p.monitorState &&
|
|
139
143
|
p.monitorState.peakUnrealizedPct > 0 &&
|
|
140
|
-
p.monitorState.peakUnrealizedPct - currentPct >= trailPct
|
|
144
|
+
p.monitorState.peakUnrealizedPct - currentPct >= trailPct &&
|
|
145
|
+
!fired.trailingStop) {
|
|
141
146
|
const peak = p.monitorState.peakUnrealizedPct;
|
|
142
147
|
out.push({
|
|
143
148
|
...base,
|
|
@@ -150,7 +155,7 @@ export function evaluateTriggers(p) {
|
|
|
150
155
|
const limitMs = parseDurationToMs(p.exitPlan.timeLimit);
|
|
151
156
|
if (limitMs !== null) {
|
|
152
157
|
const elapsedMs = Date.now() - new Date(p.entry.time).getTime();
|
|
153
|
-
if (elapsedMs >= limitMs) {
|
|
158
|
+
if (elapsedMs >= limitMs && !fired.timeLimit) {
|
|
154
159
|
out.push({
|
|
155
160
|
...base,
|
|
156
161
|
trigger: 'timeLimit',
|
|
@@ -372,6 +377,17 @@ async function processPositionFires(p, opts) {
|
|
|
372
377
|
p.monitorState.lastThesisFiredAt = fire.ts;
|
|
373
378
|
writePosition(p);
|
|
374
379
|
}
|
|
380
|
+
else if (fire.trigger === 'cut' || fire.trigger === 'takeProfit' ||
|
|
381
|
+
fire.trigger === 'trailingStop' || fire.trigger === 'timeLimit') {
|
|
382
|
+
// Stamp the watermark so this trigger doesn't re-append every tick when
|
|
383
|
+
// auto-execute is off (auto-exec closes the position, making this a no-op).
|
|
384
|
+
if (!p.monitorState)
|
|
385
|
+
p.monitorState = { peakUnrealizedPct: p.pnl.unrealizedPct, peakAt: fire.ts };
|
|
386
|
+
if (!p.monitorState.firedTriggers)
|
|
387
|
+
p.monitorState.firedTriggers = {};
|
|
388
|
+
p.monitorState.firedTriggers[fire.trigger] = fire.ts;
|
|
389
|
+
writePosition(p);
|
|
390
|
+
}
|
|
375
391
|
out.push(fire);
|
|
376
392
|
}
|
|
377
393
|
return out;
|