diggylab 0.1.47 → 0.1.49
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 +14 -1
- package/cli.js +151 -8
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -28,6 +28,19 @@ node apps/desktop-agent/dist-cli/cli/cli.js --help
|
|
|
28
28
|
|
|
29
29
|
State lives in `~/.diggylab` (`--config <directory>` or `DIGGYLAB_AGENT_DIR` overrides it).
|
|
30
30
|
|
|
31
|
+
## Update
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
diggylab update
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
It asks npm for the newest release and upgrades the global install in place; when you are already
|
|
38
|
+
current it says so and changes nothing. `diggylab run` checks once a day in the background and
|
|
39
|
+
prints `A newer version is available: ...` when there is one, and every other command repeats the
|
|
40
|
+
last known notice for free — so an old worker does not go unnoticed. A systemd service needs no
|
|
41
|
+
reinstall — the unit points at npm's symlink, so restart it to switch versions (`systemctl restart
|
|
42
|
+
diggylab`).
|
|
43
|
+
|
|
31
44
|
## Publishing the npm package
|
|
32
45
|
|
|
33
46
|
```bash
|
|
@@ -176,7 +189,7 @@ journalctl -u diggylab -f # follow the worker log
|
|
|
176
189
|
|
|
177
190
|
The unit keeps the same state directory and API origin you are using, restarts the worker if it
|
|
178
191
|
crashes, and starts it on boot. It points at npm's `diggylab` symlink, so upgrading the package is
|
|
179
|
-
enough — no need to reinstall the service. Run it as root (or with `sudo`) to write the unit.
|
|
192
|
+
enough — no need to reinstall the service (`diggylab update`, then `systemctl restart diggylab`). Run it as root (or with `sudo`) to write the unit.
|
|
180
193
|
|
|
181
194
|
<details>
|
|
182
195
|
<summary>Equivalent unit, if you prefer to write it yourself</summary>
|
package/cli.js
CHANGED
|
@@ -180,8 +180,14 @@ function dedupeImageUrls(urls) {
|
|
|
180
180
|
return order.map((pathname) => best.get(pathname).url).slice(0, 6);
|
|
181
181
|
}
|
|
182
182
|
function imageQuality(url) {
|
|
183
|
-
|
|
184
|
-
|
|
183
|
+
if (/_fb30/i.test(url))
|
|
184
|
+
return 0;
|
|
185
|
+
const dimensions = [
|
|
186
|
+
...url.matchAll(/(?:[?&](?:stp|cstp|ctp)=[^&]*?_s|_p)(\d+)x(\d+)/gi)
|
|
187
|
+
];
|
|
188
|
+
if (!dimensions.length)
|
|
189
|
+
return Number.MAX_SAFE_INTEGER;
|
|
190
|
+
return Math.max(...dimensions.map((match) => Number(match[1]) * Number(match[2])));
|
|
185
191
|
}
|
|
186
192
|
function collectFallbackImageUrls(value, urls, depth) {
|
|
187
193
|
if (!value || depth > 12 || urls.size >= 12)
|
|
@@ -423,10 +429,12 @@ function toComment(node) {
|
|
|
423
429
|
const publishedAt = toIso(firstString(node, commentTimePaths));
|
|
424
430
|
const id = firstString(node, [["id"]], false);
|
|
425
431
|
const parentId = firstString(node, [["comment_direct_parent", "id"]], false);
|
|
432
|
+
const attachmentImages = getAttachmentImages(node);
|
|
426
433
|
return {
|
|
427
434
|
text: text.trim(),
|
|
428
435
|
...authorName ? { authorName } : {},
|
|
429
436
|
...authorAvatarUrl ? { authorAvatarUrl } : {},
|
|
437
|
+
...attachmentImages.length ? { attachmentImages } : {},
|
|
430
438
|
...depth !== void 0 ? { depth } : {},
|
|
431
439
|
...id ? { id } : {},
|
|
432
440
|
...parentId ? { parentId } : {},
|
|
@@ -488,6 +496,12 @@ function mergeFacebookComments(items, limit) {
|
|
|
488
496
|
existing.id = item.id;
|
|
489
497
|
if (!existing.parentId && item.parentId)
|
|
490
498
|
existing.parentId = item.parentId;
|
|
499
|
+
const attachmentImages = dedupeImageUrls([
|
|
500
|
+
...existing.attachmentImages ?? [],
|
|
501
|
+
...item.attachmentImages ?? []
|
|
502
|
+
]);
|
|
503
|
+
if (attachmentImages.length)
|
|
504
|
+
existing.attachmentImages = attachmentImages;
|
|
491
505
|
if (item.depth !== void 0) {
|
|
492
506
|
existing.depth = existing.depth === void 0 ? item.depth : Math.min(existing.depth, item.depth);
|
|
493
507
|
}
|
|
@@ -499,6 +513,7 @@ function mergeFacebookComments(items, limit) {
|
|
|
499
513
|
text: text.slice(0, COMMENT_TEXT_LIMIT),
|
|
500
514
|
...authorName ? { authorName: authorName.slice(0, COMMENT_AUTHOR_LIMIT) } : {},
|
|
501
515
|
...item.authorAvatarUrl ? { authorAvatarUrl: item.authorAvatarUrl } : {},
|
|
516
|
+
...item.attachmentImages?.length ? { attachmentImages: item.attachmentImages } : {},
|
|
502
517
|
...item.depth !== void 0 ? { depth: item.depth } : {},
|
|
503
518
|
...item.id ? { id: item.id } : {},
|
|
504
519
|
...item.parentId ? { parentId: item.parentId } : {},
|
|
@@ -608,10 +623,12 @@ async function extractFacebookDomComments(page) {
|
|
|
608
623
|
};
|
|
609
624
|
const texts = Array.from(article.querySelectorAll('[dir="auto"]')).filter((element) => belongsToArticle(element) && element.checkVisibility()).map((element) => readText(element)).filter(Boolean);
|
|
610
625
|
const time = Array.from(article.querySelectorAll("time[datetime], abbr[data-utime]")).find((element) => belongsToArticle(element) && element.checkVisibility());
|
|
626
|
+
const attachmentImages = Array.from(article.querySelectorAll("img")).filter((image) => belongsToArticle(image) && image.checkVisibility()).map((image) => image.currentSrc || image.getAttribute("src") || "").filter((src) => /^https?:\/\//i.test(src) && /(scontent\.|fbcdn\.net)/i.test(src)).filter((src) => !/static\.xx\.fbcdn\.net|emoji\.php|rsrc\.php/i.test(src)).filter((src) => !/\/v\/t1\.30497-1\//i.test(src) && !/[?&]stp=cp0_/i.test(src));
|
|
611
627
|
return {
|
|
612
628
|
texts: Array.from(new Set(texts)),
|
|
613
629
|
authorLabel: label,
|
|
614
|
-
publishedAt: time?.getAttribute("datetime") ?? time?.getAttribute("data-utime") ?? void 0
|
|
630
|
+
publishedAt: time?.getAttribute("datetime") ?? time?.getAttribute("data-utime") ?? void 0,
|
|
631
|
+
...attachmentImages.length ? { attachmentImages } : {}
|
|
615
632
|
};
|
|
616
633
|
}));
|
|
617
634
|
return mergeFacebookComments(raw.flatMap((item) => {
|
|
@@ -622,9 +639,11 @@ async function extractFacebookDomComments(page) {
|
|
|
622
639
|
if (!text)
|
|
623
640
|
return [];
|
|
624
641
|
const depth = parseCommentDepth(item.authorLabel);
|
|
642
|
+
const attachmentImages = dedupeImageUrls(item.attachmentImages ?? []);
|
|
625
643
|
return [{
|
|
626
644
|
text,
|
|
627
645
|
...authorName ? { authorName } : {},
|
|
646
|
+
...attachmentImages.length ? { attachmentImages } : {},
|
|
628
647
|
...depth !== void 0 ? { depth } : {},
|
|
629
648
|
...toIso(item.publishedAt) ? { publishedAt: toIso(item.publishedAt) } : {}
|
|
630
649
|
}];
|
|
@@ -2088,11 +2107,13 @@ function normalizeCount(value) {
|
|
|
2088
2107
|
function normalizeComments(payload) {
|
|
2089
2108
|
const comments = payload.comments.slice(0, DESKTOP_POST_COMMENTS_MAX_LIMIT).map((comment) => {
|
|
2090
2109
|
const authorAvatarUrl = normalizeMediaUrl(comment.authorAvatarUrl);
|
|
2110
|
+
const attachmentImages = (comment.attachmentImages ?? []).map(normalizeMediaUrl).filter((url) => Boolean(url)).slice(0, 6);
|
|
2091
2111
|
const depth = normalizeCount(comment.depth);
|
|
2092
2112
|
return {
|
|
2093
2113
|
text: comment.text.trim().slice(0, 2e3),
|
|
2094
2114
|
...comment.authorName?.trim() ? { authorName: comment.authorName.trim().slice(0, 200) } : {},
|
|
2095
2115
|
...authorAvatarUrl ? { authorAvatarUrl } : {},
|
|
2116
|
+
...attachmentImages.length ? { attachmentImages } : {},
|
|
2096
2117
|
...depth !== void 0 && depth <= 5 ? { depth } : {},
|
|
2097
2118
|
...comment.publishedAt ? { publishedAt: comment.publishedAt } : {}
|
|
2098
2119
|
};
|
|
@@ -3197,6 +3218,7 @@ Usage:
|
|
|
3197
3218
|
diggylab install-service (systemd: start "run" on boot)
|
|
3198
3219
|
diggylab uninstall-service
|
|
3199
3220
|
diggylab status
|
|
3221
|
+
diggylab update (upgrades the installed npm package)
|
|
3200
3222
|
diggylab install-browser
|
|
3201
3223
|
diggylab unpair
|
|
3202
3224
|
|
|
@@ -3204,20 +3226,25 @@ Global options:
|
|
|
3204
3226
|
--config <directory> Agent state directory (default ~/.diggylab)
|
|
3205
3227
|
--api <origin> API origin (default $DIGGYLAB_API_URL or https://api.diggylab.com)`;
|
|
3206
3228
|
var MISSING_BROWSER_HINT = "Chromium for Playwright is not installed yet. Run `diggylab install-browser` once.";
|
|
3207
|
-
function
|
|
3229
|
+
function readAgentManifest() {
|
|
3208
3230
|
let directory = fileURLToPath(new URL(".", import.meta.url));
|
|
3209
3231
|
for (let depth = 0; depth < 4; depth += 1) {
|
|
3210
3232
|
const manifest = join4(directory, "package.json");
|
|
3211
3233
|
if (existsSync2(manifest)) {
|
|
3212
3234
|
try {
|
|
3213
|
-
const
|
|
3214
|
-
if (typeof version === "string" && version)
|
|
3235
|
+
const parsed = JSON.parse(readFileSync2(manifest, "utf8"));
|
|
3236
|
+
if (typeof parsed.version === "string" && parsed.version) {
|
|
3237
|
+
return { name: typeof parsed.name === "string" ? parsed.name : "", version: parsed.version };
|
|
3238
|
+
}
|
|
3215
3239
|
} catch {
|
|
3216
3240
|
}
|
|
3217
3241
|
}
|
|
3218
3242
|
directory = dirname3(directory);
|
|
3219
3243
|
}
|
|
3220
|
-
return "0.0.0";
|
|
3244
|
+
return { name: "", version: "0.0.0" };
|
|
3245
|
+
}
|
|
3246
|
+
function agentVersion() {
|
|
3247
|
+
return readAgentManifest().version;
|
|
3221
3248
|
}
|
|
3222
3249
|
function resolvePlatform() {
|
|
3223
3250
|
if (process.platform === "darwin") return "MACOS";
|
|
@@ -3570,6 +3597,7 @@ async function commandRun(runtime, io) {
|
|
|
3570
3597
|
io.error("This agent is not paired yet. Run `diggylab pair --code <code>` first.");
|
|
3571
3598
|
return 2;
|
|
3572
3599
|
}
|
|
3600
|
+
startUpdateRefresh(runtime.directory, io);
|
|
3573
3601
|
const recordActivity = (message, code, options = {}) => {
|
|
3574
3602
|
void runtime.activity.append({
|
|
3575
3603
|
category: options.category ?? "CONNECTION",
|
|
@@ -3727,6 +3755,119 @@ async function commandStatus(runtime, io) {
|
|
|
3727
3755
|
io.log(`Facebook: ${hasUser ? "connected" : "missing \u2014 run `diggylab login`"}`);
|
|
3728
3756
|
return 0;
|
|
3729
3757
|
}
|
|
3758
|
+
function npmExecutable() {
|
|
3759
|
+
return process.platform === "win32" ? "npm.cmd" : "npm";
|
|
3760
|
+
}
|
|
3761
|
+
function runNpm(args, capture) {
|
|
3762
|
+
return new Promise((resolve, reject) => {
|
|
3763
|
+
const child = spawn(npmExecutable(), args, {
|
|
3764
|
+
shell: process.platform === "win32",
|
|
3765
|
+
stdio: capture ? ["ignore", "pipe", "pipe"] : "inherit"
|
|
3766
|
+
});
|
|
3767
|
+
let stderr = "";
|
|
3768
|
+
let stdout = "";
|
|
3769
|
+
child.stdout?.on("data", (chunk) => {
|
|
3770
|
+
stdout += String(chunk);
|
|
3771
|
+
});
|
|
3772
|
+
child.stderr?.on("data", (chunk) => {
|
|
3773
|
+
stderr += String(chunk);
|
|
3774
|
+
});
|
|
3775
|
+
child.once("error", reject);
|
|
3776
|
+
child.once("exit", (value) => resolve({ code: value ?? 1, stderr, stdout }));
|
|
3777
|
+
});
|
|
3778
|
+
}
|
|
3779
|
+
function compareVersions(current, latest) {
|
|
3780
|
+
const currentParts = current.split(".").map((part) => Number.parseInt(part, 10) || 0);
|
|
3781
|
+
const latestParts = latest.split(".").map((part) => Number.parseInt(part, 10) || 0);
|
|
3782
|
+
for (let index = 0; index < 3; index += 1) {
|
|
3783
|
+
const difference = (currentParts[index] ?? 0) - (latestParts[index] ?? 0);
|
|
3784
|
+
if (difference !== 0) return difference;
|
|
3785
|
+
}
|
|
3786
|
+
return 0;
|
|
3787
|
+
}
|
|
3788
|
+
async function commandUpdate(options, io) {
|
|
3789
|
+
const manifest = readAgentManifest();
|
|
3790
|
+
if (manifest.name !== "diggylab") {
|
|
3791
|
+
io.error("This is a development build; update the repository instead (git pull && pnpm install).");
|
|
3792
|
+
return 1;
|
|
3793
|
+
}
|
|
3794
|
+
const directory = resolveConfigDirectory(options);
|
|
3795
|
+
let latest = "";
|
|
3796
|
+
try {
|
|
3797
|
+
const check = await runNpm(["view", "diggylab", "version", "--json"], true);
|
|
3798
|
+
if (check.code !== 0) {
|
|
3799
|
+
io.error("Could not ask npm for the latest version. Check the network and the npm registry, then try again.");
|
|
3800
|
+
if (check.stderr.trim()) io.error(check.stderr.trim());
|
|
3801
|
+
return 1;
|
|
3802
|
+
}
|
|
3803
|
+
latest = check.stdout.trim().replace(/^"(.*)"$/, "$1");
|
|
3804
|
+
} catch (error) {
|
|
3805
|
+
io.error(`Could not run npm: ${error instanceof Error ? error.message : String(error)}`);
|
|
3806
|
+
return 1;
|
|
3807
|
+
}
|
|
3808
|
+
if (!/^\d+\.\d+\.\d+/.test(latest)) {
|
|
3809
|
+
io.error(`npm returned an unexpected version "${latest.slice(0, 40)}".`);
|
|
3810
|
+
return 1;
|
|
3811
|
+
}
|
|
3812
|
+
writeUpdateCache(directory, latest);
|
|
3813
|
+
if (compareVersions(manifest.version, latest) >= 0) {
|
|
3814
|
+
io.log(`Already on the latest version (${manifest.version}).`);
|
|
3815
|
+
return 0;
|
|
3816
|
+
}
|
|
3817
|
+
io.log(`Updating diggylab ${manifest.version} -> ${latest}...`);
|
|
3818
|
+
try {
|
|
3819
|
+
const install = await runNpm(["install", "-g", "diggylab@latest"], false);
|
|
3820
|
+
if (install.code !== 0) {
|
|
3821
|
+
io.error("The npm upgrade failed. If npm reported a permissions error, fix the global prefix or run the command with sudo.");
|
|
3822
|
+
return 1;
|
|
3823
|
+
}
|
|
3824
|
+
} catch (error) {
|
|
3825
|
+
io.error(`Could not run npm: ${error instanceof Error ? error.message : String(error)}`);
|
|
3826
|
+
return 1;
|
|
3827
|
+
}
|
|
3828
|
+
io.log(`Updated to ${latest}. Start a new diggylab command to use it; a service picks it up on its next restart.`);
|
|
3829
|
+
return 0;
|
|
3830
|
+
}
|
|
3831
|
+
var UPDATE_CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
3832
|
+
function readUpdateCache(directory) {
|
|
3833
|
+
try {
|
|
3834
|
+
const parsed = JSON.parse(readFileSync2(join4(directory, "update-check.json"), "utf8"));
|
|
3835
|
+
if (typeof parsed.latest === "string" && /^\d+\.\d+\.\d+/.test(parsed.latest) && typeof parsed.checkedAt === "number") {
|
|
3836
|
+
return { checkedAt: parsed.checkedAt, latest: parsed.latest };
|
|
3837
|
+
}
|
|
3838
|
+
} catch {
|
|
3839
|
+
}
|
|
3840
|
+
return void 0;
|
|
3841
|
+
}
|
|
3842
|
+
function writeUpdateCache(directory, latest) {
|
|
3843
|
+
try {
|
|
3844
|
+
writeFileSync2(join4(directory, "update-check.json"), `${JSON.stringify({ checkedAt: Date.now(), latest })}
|
|
3845
|
+
`);
|
|
3846
|
+
} catch {
|
|
3847
|
+
}
|
|
3848
|
+
}
|
|
3849
|
+
function logUpdateNotice(io, current, latest) {
|
|
3850
|
+
io.log(`A newer version is available: ${current} -> ${latest} (run \`diggylab update\`).`);
|
|
3851
|
+
}
|
|
3852
|
+
function logCachedUpdateNotice(directory, io) {
|
|
3853
|
+
const manifest = readAgentManifest();
|
|
3854
|
+
if (manifest.name !== "diggylab") return;
|
|
3855
|
+
const cache = readUpdateCache(directory);
|
|
3856
|
+
if (cache && compareVersions(manifest.version, cache.latest) < 0) logUpdateNotice(io, manifest.version, cache.latest);
|
|
3857
|
+
}
|
|
3858
|
+
function startUpdateRefresh(directory, io) {
|
|
3859
|
+
const manifest = readAgentManifest();
|
|
3860
|
+
if (manifest.name !== "diggylab") return;
|
|
3861
|
+
const cache = readUpdateCache(directory);
|
|
3862
|
+
if (cache && Date.now() - cache.checkedAt < UPDATE_CHECK_INTERVAL_MS) return;
|
|
3863
|
+
void runNpm(["view", "diggylab", "version", "--json"], true).then((check) => {
|
|
3864
|
+
if (check.code !== 0) return;
|
|
3865
|
+
const latest = check.stdout.trim().replace(/^"(.*)"$/, "$1");
|
|
3866
|
+
if (!/^\d+\.\d+\.\d+/.test(latest)) return;
|
|
3867
|
+
writeUpdateCache(directory, latest);
|
|
3868
|
+
if (compareVersions(manifest.version, latest) < 0) logUpdateNotice(io, manifest.version, latest);
|
|
3869
|
+
}).catch(() => void 0);
|
|
3870
|
+
}
|
|
3730
3871
|
async function commandUnpair(runtime, io) {
|
|
3731
3872
|
await runtime.manager.clear();
|
|
3732
3873
|
await runtime.facebook.clear();
|
|
@@ -3815,12 +3956,14 @@ async function main(argv, io = console) {
|
|
|
3815
3956
|
io.log(USAGE);
|
|
3816
3957
|
return 0;
|
|
3817
3958
|
}
|
|
3818
|
-
if (!["install-browser", "install-service", "login", "logout", "pair", "rename", "run", "status", "uninstall-service", "unpair"].includes(command)) {
|
|
3959
|
+
if (!["install-browser", "install-service", "login", "logout", "pair", "rename", "run", "status", "uninstall-service", "unpair", "update"].includes(command)) {
|
|
3819
3960
|
io.error(`Unknown command "${command}".`);
|
|
3820
3961
|
io.error(USAGE);
|
|
3821
3962
|
return 2;
|
|
3822
3963
|
}
|
|
3823
3964
|
if (command === "install-browser") return commandInstallBrowser(io);
|
|
3965
|
+
if (command === "update") return commandUpdate(parsed.options, io);
|
|
3966
|
+
logCachedUpdateNotice(resolveConfigDirectory(parsed.options), io);
|
|
3824
3967
|
const runtime = createRuntime(parsed.options);
|
|
3825
3968
|
switch (command) {
|
|
3826
3969
|
case "pair":
|