diggylab 0.1.39 → 0.1.41
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 +16 -5
- package/cli.js +78 -11
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -113,17 +113,28 @@ If the browser says the link expired, run `diggylab login` again — each run mi
|
|
|
113
113
|
If you would rather not share a screen, log into Facebook in your own browser and import those
|
|
114
114
|
cookies instead — no Xvfb, no link, no port:
|
|
115
115
|
|
|
116
|
-
1.
|
|
117
|
-
(
|
|
116
|
+
1. Get the `facebook.com` cookies, in whichever way is easiest:
|
|
117
|
+
- **DevTools → Network** (fastest): open facebook.com, click any request, copy the whole
|
|
118
|
+
`cookie:` value from *Request Headers*.
|
|
119
|
+
- **DevTools → Application → Cookies → `https://www.facebook.com`**: select the table and copy the
|
|
120
|
+
rows.
|
|
121
|
+
- **Cookie-Editor** (Chrome, Edge, Firefox): *Export → JSON*.
|
|
118
122
|
2. On the server:
|
|
119
123
|
|
|
120
124
|
```bash
|
|
121
125
|
diggylab login --cookies cookies.json # or: diggylab login --cookies - and paste, then Ctrl-D
|
|
122
126
|
```
|
|
123
127
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
128
|
+
Only `name` and `value` are required; the agent fills in the rest (`.facebook.com`, `/`, session
|
|
129
|
+
expiry). Every `facebook.com` cookie is kept, and at least `c_user` plus `xs` must be present — those
|
|
130
|
+
two carry the session, and `datr` is worth keeping too.
|
|
131
|
+
|
|
132
|
+
Do not use `document.cookie` from the Console: Facebook's `c_user` and `xs` are HttpOnly, so they
|
|
133
|
+
never appear there. The DevTools cookie table and the extension do show them.
|
|
134
|
+
|
|
135
|
+
The agent saves the cookies encrypted and opens Facebook once to confirm the session before it
|
|
136
|
+
accepts it. Cookies exported from a different account, or expired ones, are rejected with a message
|
|
137
|
+
instead of failing during a scan.
|
|
127
138
|
|
|
128
139
|
Note: Facebook sometimes asks for a security check when a session moves to a server's IP. If that
|
|
129
140
|
happens the command says so — approve the login from your phone and import again.
|
package/cli.js
CHANGED
|
@@ -2329,6 +2329,10 @@ function parseCliArgs(argv) {
|
|
|
2329
2329
|
const spec = FLAGS[flag];
|
|
2330
2330
|
if (!spec) return { error: `Unknown option "${flag}".`, options };
|
|
2331
2331
|
const raw = inline ?? argv[index + 1];
|
|
2332
|
+
if (flag === "--cookies" && (raw === void 0 || raw.startsWith("--"))) {
|
|
2333
|
+
options.cookies = "";
|
|
2334
|
+
continue;
|
|
2335
|
+
}
|
|
2332
2336
|
if (raw === void 0 || raw.startsWith("--")) return { error: `Option "${flag}" needs a value.`, options };
|
|
2333
2337
|
if (inline === void 0) index += 1;
|
|
2334
2338
|
if (spec.type === "number") {
|
|
@@ -2340,6 +2344,10 @@ function parseCliArgs(argv) {
|
|
|
2340
2344
|
}
|
|
2341
2345
|
continue;
|
|
2342
2346
|
}
|
|
2347
|
+
if (command === "login" && token === "cookies" && options.cookies === void 0) {
|
|
2348
|
+
options.cookies = "";
|
|
2349
|
+
continue;
|
|
2350
|
+
}
|
|
2343
2351
|
if (command === void 0) {
|
|
2344
2352
|
command = token;
|
|
2345
2353
|
continue;
|
|
@@ -2386,16 +2394,30 @@ function toCookie(value) {
|
|
|
2386
2394
|
}
|
|
2387
2395
|
function parseCookieHeader(input) {
|
|
2388
2396
|
const header = input.replace(/^\s*cookie\s*:/i, "");
|
|
2389
|
-
return header.split(
|
|
2397
|
+
return header.split(/[;\n]/).map((part) => part.trim()).filter(Boolean).map((part) => {
|
|
2390
2398
|
const separator = part.indexOf("=");
|
|
2391
2399
|
if (separator <= 0) return null;
|
|
2392
2400
|
return { name: part.slice(0, separator).trim(), value: part.slice(separator + 1) };
|
|
2393
2401
|
}).filter((cookie) => cookie !== null);
|
|
2394
2402
|
}
|
|
2403
|
+
function parseCookieTable(input) {
|
|
2404
|
+
const cookies = [];
|
|
2405
|
+
for (const line of input.split("\n")) {
|
|
2406
|
+
const columns = line.split(" ");
|
|
2407
|
+
if (columns.length < 2) return [];
|
|
2408
|
+
const name = columns[0].trim();
|
|
2409
|
+
const value = columns[1];
|
|
2410
|
+
if (!name || name === "Name" || !value) continue;
|
|
2411
|
+
cookies.push({ name, value });
|
|
2412
|
+
}
|
|
2413
|
+
return cookies;
|
|
2414
|
+
}
|
|
2395
2415
|
function parseCookieInput(input) {
|
|
2396
2416
|
const trimmed = input.trim();
|
|
2397
2417
|
if (!trimmed) return [];
|
|
2398
|
-
if (!trimmed.startsWith("{") && !trimmed.startsWith("["))
|
|
2418
|
+
if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) {
|
|
2419
|
+
return trimmed.includes(" ") ? parseCookieTable(trimmed) : parseCookieHeader(trimmed);
|
|
2420
|
+
}
|
|
2399
2421
|
try {
|
|
2400
2422
|
const parsed = JSON.parse(trimmed);
|
|
2401
2423
|
if (Array.isArray(parsed)) {
|
|
@@ -2753,7 +2775,8 @@ var USAGE = `DiggyLab Desktop Agent CLI
|
|
|
2753
2775
|
Usage:
|
|
2754
2776
|
diggylab pair --code <code> [--name <device name>] [--api <https://api.diggylab.com>]
|
|
2755
2777
|
diggylab login [--display <number>] [--web-url <https://diggylab.com>]
|
|
2756
|
-
diggylab login
|
|
2778
|
+
diggylab login cookies (asks you to paste facebook.com cookies)
|
|
2779
|
+
diggylab login --cookies <file> (or --cookies 'name=value; ...')
|
|
2757
2780
|
diggylab run
|
|
2758
2781
|
diggylab status
|
|
2759
2782
|
diggylab install-browser
|
|
@@ -2883,7 +2906,7 @@ async function commandPair(runtime, options, io) {
|
|
|
2883
2906
|
}
|
|
2884
2907
|
}
|
|
2885
2908
|
async function commandLogin(runtime, options, io) {
|
|
2886
|
-
if (options.cookies) return importFacebookCookies(runtime, options.cookies, io);
|
|
2909
|
+
if (options.cookies !== void 0) return importFacebookCookies(runtime, options.cookies || "-", io);
|
|
2887
2910
|
const login = new FacebookLoginService({
|
|
2888
2911
|
browserType: runtime.browserType,
|
|
2889
2912
|
clearTimer: (timer) => clearTimeout(timer),
|
|
@@ -2908,11 +2931,12 @@ async function commandLogin(runtime, options, io) {
|
|
|
2908
2931
|
if (!needsVirtualDisplay) {
|
|
2909
2932
|
io.log(process.env.DISPLAY ? `Opening the Facebook login page on display ${process.env.DISPLAY}.` : "Opening the Facebook login page in a browser window.");
|
|
2910
2933
|
} else {
|
|
2911
|
-
const
|
|
2912
|
-
if (!
|
|
2934
|
+
const stored = await runtime.manager.load();
|
|
2935
|
+
if (!stored) {
|
|
2913
2936
|
io.error("This server is not paired yet. Run `diggylab pair --code <code>` first.");
|
|
2914
2937
|
return 1;
|
|
2915
2938
|
}
|
|
2939
|
+
const access = await runtime.manager.refresh();
|
|
2916
2940
|
const webUrl = options.webUrl ?? process.env.DIGGYLAB_WEB_URL?.trim();
|
|
2917
2941
|
display = await startVirtualDisplay({
|
|
2918
2942
|
displayNumber: options.display,
|
|
@@ -2943,6 +2967,14 @@ ${relay.url}`);
|
|
|
2943
2967
|
io.error(MISSING_DISPLAY_HINT);
|
|
2944
2968
|
return 1;
|
|
2945
2969
|
}
|
|
2970
|
+
if (/CREDENTIAL_REVOKED|not paired/i.test(message)) {
|
|
2971
|
+
io.error("This server is not paired (or was unpaired). Run `diggylab pair --code <code>` again.");
|
|
2972
|
+
return 1;
|
|
2973
|
+
}
|
|
2974
|
+
if (/NETWORK_ERROR|SERVER_UNAVAILABLE|fetch failed/i.test(message)) {
|
|
2975
|
+
io.error(`Could not reach the API: ${message}`);
|
|
2976
|
+
return 1;
|
|
2977
|
+
}
|
|
2946
2978
|
if (/virtual screen|login screen|DiggyLab relay|Xvfb exited|x11vnc exited/i.test(message)) {
|
|
2947
2979
|
io.error(`The login screen failed: ${message}`);
|
|
2948
2980
|
printDisplayLogs(join4(runtime.directory, "logs"), io);
|
|
@@ -2961,10 +2993,16 @@ ${relay.url}`);
|
|
|
2961
2993
|
}
|
|
2962
2994
|
async function importFacebookCookies(runtime, source, io) {
|
|
2963
2995
|
let input;
|
|
2964
|
-
|
|
2965
|
-
input =
|
|
2966
|
-
}
|
|
2967
|
-
|
|
2996
|
+
if (source === "-") {
|
|
2997
|
+
input = await readPastedCookies(io);
|
|
2998
|
+
} else if (existsSync2(source)) {
|
|
2999
|
+
input = readFileSync2(source, "utf8");
|
|
3000
|
+
} else if (source.includes("=")) {
|
|
3001
|
+
input = source;
|
|
3002
|
+
} else {
|
|
3003
|
+
io.error(`Could not read "${source}".`);
|
|
3004
|
+
io.error("Paste the cookies in quotes: diggylab login --cookies 'datr=...; c_user=...; xs=...'");
|
|
3005
|
+
io.error("Or save them to a file (diggylab login --cookies cookies.txt), or use --cookies - and press Ctrl-D after pasting.");
|
|
2968
3006
|
return 1;
|
|
2969
3007
|
}
|
|
2970
3008
|
const cookies = parseCookieInput(input);
|
|
@@ -2974,9 +3012,11 @@ async function importFacebookCookies(runtime, source, io) {
|
|
|
2974
3012
|
return 1;
|
|
2975
3013
|
}
|
|
2976
3014
|
const state = toFacebookStorageState(cookies);
|
|
3015
|
+
io.log(`Read ${cookies.length} cookie${cookies.length === 1 ? "" : "s"}.`);
|
|
2977
3016
|
if (!state.cookies.some((cookie) => cookie.name === "c_user")) {
|
|
2978
3017
|
io.error("Those cookies have no `c_user` value, so they are not a Facebook login.");
|
|
2979
|
-
io.error("
|
|
3018
|
+
io.error("If you pasted them on the command line, wrap the whole string in quotes so the shell keeps it together.");
|
|
3019
|
+
io.error("Open facebook.com in your browser, make sure you are logged in, then copy the `cookie:` request header again.");
|
|
2980
3020
|
return 1;
|
|
2981
3021
|
}
|
|
2982
3022
|
try {
|
|
@@ -3024,6 +3064,33 @@ function readStdin() {
|
|
|
3024
3064
|
process.stdin.on("error", reject);
|
|
3025
3065
|
});
|
|
3026
3066
|
}
|
|
3067
|
+
function readPastedCookies(io) {
|
|
3068
|
+
if (!process.stdin.isTTY) return readStdin();
|
|
3069
|
+
io.log("Paste the facebook.com cookies here, then press Enter.");
|
|
3070
|
+
io.log("In your browser: F12 \u2192 Network \u2192 click any facebook.com request \u2192 copy the whole `cookie:` value.");
|
|
3071
|
+
return new Promise((resolve) => {
|
|
3072
|
+
let value = "";
|
|
3073
|
+
let idle;
|
|
3074
|
+
const finish = () => {
|
|
3075
|
+
if (idle) clearTimeout(idle);
|
|
3076
|
+
process.stdin.off("data", handleData);
|
|
3077
|
+
process.stdin.pause();
|
|
3078
|
+
resolve(value);
|
|
3079
|
+
};
|
|
3080
|
+
const handleData = (chunk) => {
|
|
3081
|
+
value += chunk;
|
|
3082
|
+
if (idle) clearTimeout(idle);
|
|
3083
|
+
if (value.includes("\n") && value.includes("c_user")) {
|
|
3084
|
+
finish();
|
|
3085
|
+
return;
|
|
3086
|
+
}
|
|
3087
|
+
idle = setTimeout(finish, 350);
|
|
3088
|
+
};
|
|
3089
|
+
process.stdin.setEncoding("utf8");
|
|
3090
|
+
process.stdin.on("data", handleData);
|
|
3091
|
+
process.stdin.resume();
|
|
3092
|
+
});
|
|
3093
|
+
}
|
|
3027
3094
|
function playwrightCliPath() {
|
|
3028
3095
|
try {
|
|
3029
3096
|
return fileURLToPath(new URL("cli.js", import.meta.resolve("playwright")));
|