diggylab 0.1.45 → 0.1.47
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 +13 -0
- package/cli.js +382 -68
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -52,6 +52,19 @@ npm publish apps/desktop-agent/dist-npm --access public
|
|
|
52
52
|
|
|
53
53
|
`CLI_PACKAGE_VERSION=0.1.31` works as well.
|
|
54
54
|
|
|
55
|
+
## Local development
|
|
56
|
+
|
|
57
|
+
Run the CLI from source against the local API without touching a real installation:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
pnpm dev:cli # prints status against http://localhost:4000 and ~/.diggylab-dev
|
|
61
|
+
pnpm dev:cli pair # forwards any command to the CLI
|
|
62
|
+
pnpm dev:cli --build run # rebuilds dist-cli first, then runs the worker
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
The wrapper points `DIGGYLAB_API_URL` at `http://localhost:4000` and `DIGGYLAB_AGENT_DIR` at
|
|
66
|
+
`~/.diggylab-dev`; set either variable to override the defaults.
|
|
67
|
+
|
|
55
68
|
## Commands
|
|
56
69
|
|
|
57
70
|
| Command | What it does |
|
package/cli.js
CHANGED
|
@@ -5,7 +5,7 @@ import { randomUUID as randomUUID3 } from "node:crypto";
|
|
|
5
5
|
import { spawn } from "node:child_process";
|
|
6
6
|
import { existsSync as existsSync2, readFileSync as readFileSync2, realpathSync, rmSync, writeFileSync as writeFileSync2 } from "node:fs";
|
|
7
7
|
import { hostname, homedir } from "node:os";
|
|
8
|
-
import { dirname as dirname3, join as
|
|
8
|
+
import { dirname as dirname3, join as join4 } from "node:path";
|
|
9
9
|
import { fileURLToPath } from "node:url";
|
|
10
10
|
|
|
11
11
|
// ../../packages/facebook-scanner/dist/parser.js
|
|
@@ -48,11 +48,25 @@ var commentAuthorPaths = [
|
|
|
48
48
|
["author_name"],
|
|
49
49
|
["author", "short_name"]
|
|
50
50
|
];
|
|
51
|
+
var commentAvatarPaths = [
|
|
52
|
+
["author", "profile_picture_depth_0", "uri"],
|
|
53
|
+
["author", "profile_picture_depth_1", "uri"],
|
|
54
|
+
["author", "profile_picture_depth_0_increased", "uri"],
|
|
55
|
+
["author", "profile_picture", "uri"]
|
|
56
|
+
];
|
|
51
57
|
var commentTimePaths = [
|
|
52
58
|
["created_time"],
|
|
53
59
|
["creation_time"],
|
|
54
60
|
["publish_time"]
|
|
55
61
|
];
|
|
62
|
+
var commentCountPaths = [
|
|
63
|
+
["feedback", "comment_count", "total_count"],
|
|
64
|
+
["feedback", "comments", "total_count"],
|
|
65
|
+
["feedback", "total_comment_count"],
|
|
66
|
+
["comment_count", "total_count"],
|
|
67
|
+
["comments", "total_count"],
|
|
68
|
+
["total_comment_count"]
|
|
69
|
+
];
|
|
56
70
|
var facebookHosts = /* @__PURE__ */ new Set([
|
|
57
71
|
"facebook.com",
|
|
58
72
|
"fb.com",
|
|
@@ -214,6 +228,55 @@ function getTextFormat(node) {
|
|
|
214
228
|
};
|
|
215
229
|
return Object.values(textFormat).some(Boolean) ? textFormat : void 0;
|
|
216
230
|
}
|
|
231
|
+
function getCommentCount(node) {
|
|
232
|
+
return firstCount(node, commentCountPaths) ?? sweepCommentCount(node, 0);
|
|
233
|
+
}
|
|
234
|
+
function firstCount(root, candidates) {
|
|
235
|
+
for (const path of candidates) {
|
|
236
|
+
const count = toCount(get(root, path));
|
|
237
|
+
if (count !== void 0)
|
|
238
|
+
return count;
|
|
239
|
+
}
|
|
240
|
+
return void 0;
|
|
241
|
+
}
|
|
242
|
+
function sweepCommentCount(value, depth) {
|
|
243
|
+
if (!value || depth > 10)
|
|
244
|
+
return void 0;
|
|
245
|
+
if (Array.isArray(value)) {
|
|
246
|
+
for (const item of value) {
|
|
247
|
+
const count = sweepCommentCount(item, depth + 1);
|
|
248
|
+
if (count !== void 0)
|
|
249
|
+
return count;
|
|
250
|
+
}
|
|
251
|
+
return void 0;
|
|
252
|
+
}
|
|
253
|
+
if (!isData(value))
|
|
254
|
+
return void 0;
|
|
255
|
+
if ("comment_id" in value || "parent_comment" in value)
|
|
256
|
+
return void 0;
|
|
257
|
+
const typename = typeof value.__typename === "string" ? value.__typename : void 0;
|
|
258
|
+
if (typename && /comment$/i.test(typename))
|
|
259
|
+
return void 0;
|
|
260
|
+
const direct = toCount(value.comment_count) ?? toCount(value.total_comment_count) ?? toCount(value.comments);
|
|
261
|
+
if (direct !== void 0)
|
|
262
|
+
return direct;
|
|
263
|
+
for (const child of Object.values(value)) {
|
|
264
|
+
const count = sweepCommentCount(child, depth + 1);
|
|
265
|
+
if (count !== void 0)
|
|
266
|
+
return count;
|
|
267
|
+
}
|
|
268
|
+
return void 0;
|
|
269
|
+
}
|
|
270
|
+
function toCount(value) {
|
|
271
|
+
if (typeof value === "number") {
|
|
272
|
+
return Number.isSafeInteger(value) && value >= 0 ? value : void 0;
|
|
273
|
+
}
|
|
274
|
+
if (typeof value === "string" && /^\d{1,9}$/.test(value))
|
|
275
|
+
return Number(value);
|
|
276
|
+
if (isData(value))
|
|
277
|
+
return toCount(value.total_count);
|
|
278
|
+
return void 0;
|
|
279
|
+
}
|
|
217
280
|
function collectImageUrls(value, urls, depth) {
|
|
218
281
|
if (!value || depth > 12 || urls.size >= 12)
|
|
219
282
|
return;
|
|
@@ -275,12 +338,14 @@ function toItem(node, requestedGroup) {
|
|
|
275
338
|
attachmentImages = dedupeImageUrls([...fallbackUrls]);
|
|
276
339
|
}
|
|
277
340
|
const textFormat = getTextFormat(node);
|
|
341
|
+
const commentCount = getCommentCount(node);
|
|
278
342
|
return {
|
|
279
343
|
text,
|
|
280
344
|
url,
|
|
281
345
|
authorName: firstString(node, paths.author),
|
|
282
346
|
...authorAvatarUrl ? { authorAvatarUrl } : {},
|
|
283
347
|
...attachmentImages.length ? { attachmentImages } : {},
|
|
348
|
+
...commentCount !== void 0 ? { commentCount } : {},
|
|
284
349
|
...textFormat ? { textFormat } : {},
|
|
285
350
|
publishedAt: toIso(firstString(node, paths.time)),
|
|
286
351
|
groupName: firstString(node, paths.group)
|
|
@@ -315,6 +380,9 @@ function mergeFacebookScanItems(items, limit) {
|
|
|
315
380
|
if (!existing.authorAvatarUrl && item.authorAvatarUrl) {
|
|
316
381
|
existing.authorAvatarUrl = item.authorAvatarUrl;
|
|
317
382
|
}
|
|
383
|
+
if (existing.commentCount === void 0 && item.commentCount !== void 0) {
|
|
384
|
+
existing.commentCount = item.commentCount;
|
|
385
|
+
}
|
|
318
386
|
const attachmentImages = dedupeImageUrls([
|
|
319
387
|
...existing.attachmentImages ?? [],
|
|
320
388
|
...item.attachmentImages ?? []
|
|
@@ -350,13 +418,25 @@ function toComment(node) {
|
|
|
350
418
|
if (!text || typename && !/comment$/i.test(typename))
|
|
351
419
|
return void 0;
|
|
352
420
|
const authorName = firstString(node, commentAuthorPaths);
|
|
421
|
+
const authorAvatarUrl = firstString(node, commentAvatarPaths, false);
|
|
422
|
+
const depth = toDepth(node);
|
|
353
423
|
const publishedAt = toIso(firstString(node, commentTimePaths));
|
|
424
|
+
const id = firstString(node, [["id"]], false);
|
|
425
|
+
const parentId = firstString(node, [["comment_direct_parent", "id"]], false);
|
|
354
426
|
return {
|
|
355
427
|
text: text.trim(),
|
|
356
428
|
...authorName ? { authorName } : {},
|
|
429
|
+
...authorAvatarUrl ? { authorAvatarUrl } : {},
|
|
430
|
+
...depth !== void 0 ? { depth } : {},
|
|
431
|
+
...id ? { id } : {},
|
|
432
|
+
...parentId ? { parentId } : {},
|
|
357
433
|
...publishedAt ? { publishedAt } : {}
|
|
358
434
|
};
|
|
359
435
|
}
|
|
436
|
+
function toDepth(node) {
|
|
437
|
+
const value = node.depth;
|
|
438
|
+
return typeof value === "number" && Number.isInteger(value) && value >= 0 && value <= 5 ? value : void 0;
|
|
439
|
+
}
|
|
360
440
|
function visitComments(value, items) {
|
|
361
441
|
if (Array.isArray(value)) {
|
|
362
442
|
for (const item of value)
|
|
@@ -384,27 +464,81 @@ function parseFacebookCommentBodies(body) {
|
|
|
384
464
|
}
|
|
385
465
|
return mergeFacebookComments(items, Number.POSITIVE_INFINITY);
|
|
386
466
|
}
|
|
467
|
+
function parseFacebookCommentJson(value) {
|
|
468
|
+
const items = [];
|
|
469
|
+
visitComments(value, items);
|
|
470
|
+
return mergeFacebookComments(items, Number.POSITIVE_INFINITY);
|
|
471
|
+
}
|
|
387
472
|
function mergeFacebookComments(items, limit) {
|
|
388
|
-
const
|
|
389
|
-
const merged = [];
|
|
473
|
+
const merged = /* @__PURE__ */ new Map();
|
|
390
474
|
for (const item of items) {
|
|
391
475
|
const text = item.text.trim();
|
|
392
476
|
if (!text)
|
|
393
477
|
continue;
|
|
394
478
|
const authorName = item.authorName?.trim();
|
|
395
|
-
const key = `${authorName ?? ""}\0${text}
|
|
396
|
-
|
|
479
|
+
const key = `${authorName ?? ""}\0${commentKeyText(text)}`;
|
|
480
|
+
const existing = merged.get(key);
|
|
481
|
+
if (existing) {
|
|
482
|
+
if (!existing.publishedAt && item.publishedAt)
|
|
483
|
+
existing.publishedAt = item.publishedAt;
|
|
484
|
+
if (!existing.authorAvatarUrl && item.authorAvatarUrl) {
|
|
485
|
+
existing.authorAvatarUrl = item.authorAvatarUrl;
|
|
486
|
+
}
|
|
487
|
+
if (!existing.id && item.id)
|
|
488
|
+
existing.id = item.id;
|
|
489
|
+
if (!existing.parentId && item.parentId)
|
|
490
|
+
existing.parentId = item.parentId;
|
|
491
|
+
if (item.depth !== void 0) {
|
|
492
|
+
existing.depth = existing.depth === void 0 ? item.depth : Math.min(existing.depth, item.depth);
|
|
493
|
+
}
|
|
397
494
|
continue;
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
merged.
|
|
495
|
+
}
|
|
496
|
+
if (merged.size >= limit)
|
|
497
|
+
continue;
|
|
498
|
+
merged.set(key, {
|
|
402
499
|
text: text.slice(0, COMMENT_TEXT_LIMIT),
|
|
403
500
|
...authorName ? { authorName: authorName.slice(0, COMMENT_AUTHOR_LIMIT) } : {},
|
|
501
|
+
...item.authorAvatarUrl ? { authorAvatarUrl: item.authorAvatarUrl } : {},
|
|
502
|
+
...item.depth !== void 0 ? { depth: item.depth } : {},
|
|
503
|
+
...item.id ? { id: item.id } : {},
|
|
504
|
+
...item.parentId ? { parentId: item.parentId } : {},
|
|
404
505
|
...item.publishedAt ? { publishedAt: item.publishedAt } : {}
|
|
405
506
|
});
|
|
406
507
|
}
|
|
407
|
-
return merged;
|
|
508
|
+
return orderFacebookCommentThreads([...merged.values()]);
|
|
509
|
+
}
|
|
510
|
+
function orderFacebookCommentThreads(items) {
|
|
511
|
+
const byId = /* @__PURE__ */ new Map();
|
|
512
|
+
for (const item of items) {
|
|
513
|
+
if (item.id && !byId.has(item.id))
|
|
514
|
+
byId.set(item.id, item);
|
|
515
|
+
}
|
|
516
|
+
const children = /* @__PURE__ */ new Map();
|
|
517
|
+
const roots = [];
|
|
518
|
+
for (const item of items) {
|
|
519
|
+
if (item.parentId && byId.has(item.parentId)) {
|
|
520
|
+
children.set(item.parentId, [...children.get(item.parentId) ?? [], item]);
|
|
521
|
+
} else {
|
|
522
|
+
roots.push(item);
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
const ordered = [];
|
|
526
|
+
const visited = /* @__PURE__ */ new Set();
|
|
527
|
+
const append = (item, depth) => {
|
|
528
|
+
if (visited.has(item))
|
|
529
|
+
return;
|
|
530
|
+
visited.add(item);
|
|
531
|
+
ordered.push({ ...item, depth: Math.max(depth, item.depth ?? 0) });
|
|
532
|
+
for (const child of item.id ? children.get(item.id) ?? [] : [])
|
|
533
|
+
append(child, depth + 1);
|
|
534
|
+
};
|
|
535
|
+
for (const root of roots)
|
|
536
|
+
append(root, 0);
|
|
537
|
+
for (const item of items) {
|
|
538
|
+
if (!visited.has(item))
|
|
539
|
+
append(item, item.depth ?? 0);
|
|
540
|
+
}
|
|
541
|
+
return ordered;
|
|
408
542
|
}
|
|
409
543
|
async function extractFacebookDomItems(page, groupUrl) {
|
|
410
544
|
const requestedGroup = requestedGroupIdentifier(groupUrl);
|
|
@@ -427,12 +561,16 @@ async function extractFacebookDomItems(page, groupUrl) {
|
|
|
427
561
|
const message = Array.from(article.querySelectorAll('[data-ad-preview="message"]')).find((element) => belongsToArticle(element) && element.checkVisibility());
|
|
428
562
|
const author = Array.from(article.querySelectorAll("h2 a, h3 a, strong a")).find((element) => belongsToArticle(element) && element.checkVisibility());
|
|
429
563
|
const time = Array.from(article.querySelectorAll("time[datetime], abbr[data-utime]")).find((element) => belongsToArticle(element) && element.checkVisibility());
|
|
564
|
+
const countText = Array.from(article.querySelectorAll('a, [role="button"]')).filter((element) => belongsToArticle(element) && element.checkVisibility()).map((element) => element.innerText.trim()).find((text) => /(?:^|\s)\d+\s*(?:bình luận|comments?)$/i.test(text));
|
|
565
|
+
const countMatch = countText ? /(\d+)\s*(?:bình luận|comments?)$/i.exec(countText) : null;
|
|
566
|
+
const commentCount = countMatch ? Number(countMatch[1]) : void 0;
|
|
430
567
|
const attachmentImages = Array.from(article.querySelectorAll("img")).filter((image) => belongsToArticle(image) && image.naturalWidth >= 200).map((image) => image.currentSrc || image.src).filter((src) => /^https?:\/\//i.test(src) && /(scontent\.|fbcdn\.net)/i.test(src));
|
|
431
568
|
return {
|
|
432
569
|
text: message?.innerText ?? "",
|
|
433
570
|
urls,
|
|
434
571
|
authorName: author?.innerText,
|
|
435
572
|
publishedAt: time?.getAttribute("datetime") ?? time?.getAttribute("data-utime") ?? void 0,
|
|
573
|
+
...commentCount !== void 0 ? { commentCount } : {},
|
|
436
574
|
...attachmentImages.length ? { attachmentImages } : {}
|
|
437
575
|
};
|
|
438
576
|
}));
|
|
@@ -443,6 +581,7 @@ async function extractFacebookDomItems(page, groupUrl) {
|
|
|
443
581
|
url,
|
|
444
582
|
authorName: item.authorName,
|
|
445
583
|
publishedAt: item.publishedAt,
|
|
584
|
+
...item.commentCount !== void 0 ? { commentCount: item.commentCount } : {},
|
|
446
585
|
...item.attachmentImages?.length ? { attachmentImages: dedupeImageUrls(item.attachmentImages) } : {}
|
|
447
586
|
}] : [];
|
|
448
587
|
}), Number.POSITIVE_INFINITY);
|
|
@@ -454,20 +593,67 @@ async function extractFacebookDomComments(page) {
|
|
|
454
593
|
return void 0;
|
|
455
594
|
}
|
|
456
595
|
const belongsToArticle = (element) => element.closest('[role="article"]') === article;
|
|
457
|
-
const
|
|
458
|
-
|
|
596
|
+
const readText = (element) => {
|
|
597
|
+
let text = "";
|
|
598
|
+
for (const node of Array.from(element.childNodes)) {
|
|
599
|
+
if (node.nodeType === Node.TEXT_NODE) {
|
|
600
|
+
text += node.textContent ?? "";
|
|
601
|
+
} else if (node instanceof HTMLImageElement) {
|
|
602
|
+
text += node.getAttribute("alt") ?? "";
|
|
603
|
+
} else if (node instanceof HTMLElement) {
|
|
604
|
+
text += readText(node);
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
return text.trim();
|
|
608
|
+
};
|
|
609
|
+
const texts = Array.from(article.querySelectorAll('[dir="auto"]')).filter((element) => belongsToArticle(element) && element.checkVisibility()).map((element) => readText(element)).filter(Boolean);
|
|
459
610
|
const time = Array.from(article.querySelectorAll("time[datetime], abbr[data-utime]")).find((element) => belongsToArticle(element) && element.checkVisibility());
|
|
460
611
|
return {
|
|
461
|
-
|
|
462
|
-
|
|
612
|
+
texts: Array.from(new Set(texts)),
|
|
613
|
+
authorLabel: label,
|
|
463
614
|
publishedAt: time?.getAttribute("datetime") ?? time?.getAttribute("data-utime") ?? void 0
|
|
464
615
|
};
|
|
465
616
|
}));
|
|
466
|
-
return mergeFacebookComments(raw.flatMap((item) =>
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
617
|
+
return mergeFacebookComments(raw.flatMap((item) => {
|
|
618
|
+
if (!item)
|
|
619
|
+
return [];
|
|
620
|
+
const authorName = parseCommentAuthor(item.authorLabel);
|
|
621
|
+
const text = pickCommentText(item.texts, authorName);
|
|
622
|
+
if (!text)
|
|
623
|
+
return [];
|
|
624
|
+
const depth = parseCommentDepth(item.authorLabel);
|
|
625
|
+
return [{
|
|
626
|
+
text,
|
|
627
|
+
...authorName ? { authorName } : {},
|
|
628
|
+
...depth !== void 0 ? { depth } : {},
|
|
629
|
+
...toIso(item.publishedAt) ? { publishedAt: toIso(item.publishedAt) } : {}
|
|
630
|
+
}];
|
|
631
|
+
}), Number.POSITIVE_INFINITY);
|
|
632
|
+
}
|
|
633
|
+
function pickCommentText(texts, authorName) {
|
|
634
|
+
if (!Array.isArray(texts) || !texts.length)
|
|
635
|
+
return "";
|
|
636
|
+
const candidates = texts.filter((text) => text !== authorName && !COMMENT_UI_LABELS.test(text));
|
|
637
|
+
return candidates.reduce((longest, text) => text.length > longest.length ? text : longest, "");
|
|
638
|
+
}
|
|
639
|
+
var COMMENT_UI_LABELS = /^(?:follow|theo dõi|like|thích|reply|trả lời|share|chia sẻ|see more|xem thêm|hide|ẩn|report|báo cáo)$/i;
|
|
640
|
+
function commentKeyText(text) {
|
|
641
|
+
const normalized = text.replace(/[\p{Extended_Pictographic}\u200d\uFE0F]/gu, "").replace(/[:;=8xX]-?[)(\]\[dDpP/\\|]+/g, "").replace(/\s+/g, "");
|
|
642
|
+
return normalized || text.replace(/\s+/g, "");
|
|
643
|
+
}
|
|
644
|
+
function parseCommentAuthor(label) {
|
|
645
|
+
const match = /^(?:bình luận|comment|trả lời|reply)\s+(?:dưới tên|by)\s+(.+)$/i.exec(label.trim());
|
|
646
|
+
if (!match)
|
|
647
|
+
return void 0;
|
|
648
|
+
const name = match[1].replace(/\s+vào\s+.*$/i, "").replace(/\s+on\s+.*$/i, "").replace(/\s+to\s+.*$/i, "").replace(/\s+a\s+few\s+(?:seconds?|minutes?|hours?|days?)\s+ago\s*$/i, "").replace(/\s+(?:just\s+now|vừa\s+xong)\s*$/i, "").replace(/\s+(?:\d+|a|an|one|một|vài|few|several)\s+(?:giây|phút|giờ|ngày|tuần|tháng|năm|seconds?|minutes?|hours?|days?|weeks?|months?|years?)\s+(?:ago|trước)\s*$/i, "").trim();
|
|
649
|
+
return name || void 0;
|
|
650
|
+
}
|
|
651
|
+
function parseCommentDepth(label) {
|
|
652
|
+
const trimmed = label.trim();
|
|
653
|
+
if (/^(?:bình luận|comment)\b/i.test(trimmed))
|
|
654
|
+
return 0;
|
|
655
|
+
const parentPart = trimmed.split(/\s+(?:to|về|của)\s+/i)[1] ?? "";
|
|
656
|
+
return /\brepl(?:y|ies)\b|trả lời|phản hồi/i.test(parentPart) ? 2 : 1;
|
|
471
657
|
}
|
|
472
658
|
|
|
473
659
|
// ../../packages/facebook-scanner/dist/session.js
|
|
@@ -507,6 +693,7 @@ var FacebookScanner = class {
|
|
|
507
693
|
extractDomComments;
|
|
508
694
|
parseGraphqlBody;
|
|
509
695
|
parseGraphqlComments;
|
|
696
|
+
parseCommentJson;
|
|
510
697
|
graphqlResponseTimeoutMs;
|
|
511
698
|
running = false;
|
|
512
699
|
constructor(options) {
|
|
@@ -515,6 +702,7 @@ var FacebookScanner = class {
|
|
|
515
702
|
this.extractDomComments = options.extractDomComments ?? extractFacebookDomComments;
|
|
516
703
|
this.parseGraphqlBody = options.parseGraphqlBody ?? parseFacebookGraphqlBody;
|
|
517
704
|
this.parseGraphqlComments = options.parseGraphqlComments ?? parseFacebookCommentBodies;
|
|
705
|
+
this.parseCommentJson = options.parseCommentJson ?? parseFacebookCommentJson;
|
|
518
706
|
this.graphqlResponseTimeoutMs = options.graphqlResponseTimeoutMs ?? 1e4;
|
|
519
707
|
}
|
|
520
708
|
async scanMany(options) {
|
|
@@ -569,6 +757,7 @@ var FacebookScanner = class {
|
|
|
569
757
|
if (condition !== "READY")
|
|
570
758
|
throw new FacebookScannerError(condition);
|
|
571
759
|
await options.onProgress?.({ phase: "FETCHING", commentCount: 0 });
|
|
760
|
+
const embeddedComments = await readEmbeddedComments(page, this.parseCommentJson);
|
|
572
761
|
let comments = [];
|
|
573
762
|
let idleRounds = 0;
|
|
574
763
|
let rounds = 0;
|
|
@@ -579,19 +768,21 @@ var FacebookScanner = class {
|
|
|
579
768
|
await page.waitForTimeout(1200);
|
|
580
769
|
await drain(responseTasks, this.graphqlResponseTimeoutMs);
|
|
581
770
|
const domComments2 = await this.extractDomComments(page);
|
|
582
|
-
comments = mergeFacebookComments([...graphqlComments, ...domComments2], options.limit);
|
|
771
|
+
comments = mergeFacebookComments([...embeddedComments, ...graphqlComments, ...domComments2], options.limit);
|
|
583
772
|
if (comments.length !== previousCount) {
|
|
584
773
|
await options.onProgress?.({ phase: "FETCHING", commentCount: comments.length });
|
|
585
774
|
}
|
|
586
775
|
const expanded = await clickCommentExpanders(page);
|
|
587
776
|
idleRounds = comments.length === previousCount && !expanded ? idleRounds + 1 : 0;
|
|
588
|
-
if (comments.length < options.limit)
|
|
777
|
+
if (comments.length < options.limit) {
|
|
778
|
+
await scrollCommentArea(page);
|
|
589
779
|
await page.mouse.wheel(0, 2e3);
|
|
780
|
+
}
|
|
590
781
|
}
|
|
591
782
|
assertNotAborted(options.signal);
|
|
592
783
|
await drain(responseTasks, this.graphqlResponseTimeoutMs);
|
|
593
784
|
const domComments = await this.extractDomComments(page);
|
|
594
|
-
comments = mergeFacebookComments([...graphqlComments, ...domComments], options.limit);
|
|
785
|
+
comments = mergeFacebookComments([...embeddedComments, ...graphqlComments, ...domComments], options.limit);
|
|
595
786
|
await options.onProgress?.({ phase: "COMPLETE", commentCount: comments.length });
|
|
596
787
|
return comments;
|
|
597
788
|
} finally {
|
|
@@ -685,29 +876,58 @@ function withTimeout(promise, timeoutMs) {
|
|
|
685
876
|
});
|
|
686
877
|
return Promise.race([promise, timeout]).finally(() => clearTimeout(timer));
|
|
687
878
|
}
|
|
688
|
-
var
|
|
689
|
-
|
|
690
|
-
/^xem \d+ bình luận$/i,
|
|
691
|
-
/^xem các bình luận khác$/i,
|
|
692
|
-
/^view more comments$/i,
|
|
693
|
-
/^view \d+ more comments$/i,
|
|
694
|
-
/^xem thêm câu trả lời$/i,
|
|
695
|
-
/^view more replies$/i
|
|
696
|
-
];
|
|
879
|
+
var COMMENT_EXPANDER_PATTERN = /^(?:xem|view)\b.*(?:bình luận|comments?|câu trả lời|repl(?:y|ies))/i;
|
|
880
|
+
var MAX_EXPANDER_CLICKS = 4;
|
|
697
881
|
async function clickCommentExpanders(page) {
|
|
698
882
|
let clicked = 0;
|
|
699
|
-
for (
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
883
|
+
for (let attempt = 0; attempt < MAX_EXPANDER_CLICKS; attempt += 1) {
|
|
884
|
+
const buttons = page.getByRole("button", { name: COMMENT_EXPANDER_PATTERN });
|
|
885
|
+
let count = 0;
|
|
886
|
+
try {
|
|
887
|
+
count = await buttons.count();
|
|
888
|
+
} catch {
|
|
889
|
+
break;
|
|
890
|
+
}
|
|
891
|
+
if (!count)
|
|
892
|
+
break;
|
|
893
|
+
try {
|
|
894
|
+
await buttons.first().click({ timeout: 1e3 });
|
|
895
|
+
clicked += 1;
|
|
896
|
+
} catch {
|
|
897
|
+
break;
|
|
707
898
|
}
|
|
708
899
|
}
|
|
709
900
|
return clicked;
|
|
710
901
|
}
|
|
902
|
+
async function scrollCommentArea(page) {
|
|
903
|
+
await page.evaluate(() => {
|
|
904
|
+
const article = document.querySelector('[role="article"][aria-label]');
|
|
905
|
+
let node = article instanceof HTMLElement ? article.parentElement : null;
|
|
906
|
+
while (node) {
|
|
907
|
+
if (node.scrollHeight > node.clientHeight + 80) {
|
|
908
|
+
node.scrollTop = node.scrollHeight;
|
|
909
|
+
return;
|
|
910
|
+
}
|
|
911
|
+
node = node.parentElement;
|
|
912
|
+
}
|
|
913
|
+
window.scrollTo(0, document.body.scrollHeight);
|
|
914
|
+
}).catch(() => void 0);
|
|
915
|
+
}
|
|
916
|
+
async function readEmbeddedComments(page, parse) {
|
|
917
|
+
const blocks = await page.evaluate(() => Array.from(document.querySelectorAll('script[type="application/json"]')).map((script) => script.textContent ?? "").filter((text) => text.includes('"Comment"'))).catch(() => []);
|
|
918
|
+
if (!Array.isArray(blocks))
|
|
919
|
+
return [];
|
|
920
|
+
const items = [];
|
|
921
|
+
for (const block of blocks) {
|
|
922
|
+
if (typeof block !== "string")
|
|
923
|
+
continue;
|
|
924
|
+
try {
|
|
925
|
+
items.push(...parse(JSON.parse(block)));
|
|
926
|
+
} catch {
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
return mergeFacebookComments(items, Number.POSITIVE_INFINITY);
|
|
930
|
+
}
|
|
711
931
|
|
|
712
932
|
// src/cli/cli.ts
|
|
713
933
|
import { chromium } from "playwright";
|
|
@@ -823,7 +1043,7 @@ function sanitize(value) {
|
|
|
823
1043
|
return value.replace(/Bearer\s+\S+/gi, "Bearer [REDACTED]").replace(/\b(authorization|cookie|set-cookie|token|access[ _-]?token|refresh[ _-]?token|api[ _-]?key|private[ _-]?key|signature|pairing[ _-]?code|storage[ _-]?state)\b\s*[:=]\s*[^\s;]+/gi, "$1=[REDACTED]").replace(/https?:\/\/[^\s/@:]+:[^\s/@]+@/gi, "https://[REDACTED]@").replace(/\b[A-F0-9]{32}\b/gi, "[REDACTED]").replace(/[\r\n\t]+/g, " ").trim();
|
|
824
1044
|
}
|
|
825
1045
|
function isEntry(value) {
|
|
826
|
-
return typeof value?.id === "string" && typeof value.timestamp === "string" && ["INFO", "WARN", "ERROR"].includes(value.level) && ["CONNECTION", "PAIRING", "FACEBOOK", "SCAN", "UPLOAD", "SYSTEM"].includes(value.category) && typeof value.code === "string" && typeof value.message === "string";
|
|
1046
|
+
return typeof value?.id === "string" && typeof value.timestamp === "string" && ["INFO", "WARN", "ERROR"].includes(value.level) && ["CONNECTION", "PAIRING", "FACEBOOK", "SCAN", "COMMENTS", "UPLOAD", "SYSTEM"].includes(value.category) && typeof value.code === "string" && typeof value.message === "string";
|
|
827
1047
|
}
|
|
828
1048
|
|
|
829
1049
|
// src/main/browser-runtime.ts
|
|
@@ -1124,6 +1344,7 @@ import { randomUUID } from "node:crypto";
|
|
|
1124
1344
|
|
|
1125
1345
|
// ../../packages/shared/src/index.ts
|
|
1126
1346
|
var DESKTOP_AGENT_PROTOCOL_VERSION = 1;
|
|
1347
|
+
var DESKTOP_POST_COMMENTS_MAX_LIMIT = 200;
|
|
1127
1348
|
var DESKTOP_POST_COMMENTS_REQUEST_TIMEOUT_MS = 3 * 60 * 1e3;
|
|
1128
1349
|
|
|
1129
1350
|
// src/main/desktop-agent-session.ts
|
|
@@ -1291,7 +1512,7 @@ var DesktopAgentSession = class {
|
|
|
1291
1512
|
state: this.reportedState,
|
|
1292
1513
|
facebookReady: this.options.getFacebookReady(),
|
|
1293
1514
|
currentJobId: this.reportedJobId,
|
|
1294
|
-
capabilities: ["POST_COMMENTS"]
|
|
1515
|
+
capabilities: ["POST_COMMENTS", "POST_COMMENTS_MORE"]
|
|
1295
1516
|
});
|
|
1296
1517
|
this.handshake = "WELCOME";
|
|
1297
1518
|
return;
|
|
@@ -1490,7 +1711,7 @@ function describeEnvelopeProblem(envelope, deviceId, replayIds) {
|
|
|
1490
1711
|
function isPostCommentsFetch(value) {
|
|
1491
1712
|
if (!value || typeof value !== "object") return false;
|
|
1492
1713
|
const payload = value;
|
|
1493
|
-
return typeof payload.requestId === "string" && /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(payload.requestId) && typeof payload.postUrl === "string" && payload.postUrl.length <= 2e3 && /^https:\/\/[\w.-]*facebook\.com\//i.test(payload.postUrl) && typeof payload.limit === "number" && Number.isInteger(payload.limit) && payload.limit > 0 && payload.limit <=
|
|
1714
|
+
return typeof payload.requestId === "string" && /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(payload.requestId) && typeof payload.postUrl === "string" && payload.postUrl.length <= 2e3 && /^https:\/\/[\w.-]*facebook\.com\//i.test(payload.postUrl) && typeof payload.limit === "number" && Number.isInteger(payload.limit) && payload.limit > 0 && payload.limit <= DESKTOP_POST_COMMENTS_MAX_LIMIT;
|
|
1494
1715
|
}
|
|
1495
1716
|
function emptySnapshot(state) {
|
|
1496
1717
|
return {
|
|
@@ -1527,6 +1748,7 @@ var DEFAULT_MAX_QUEUE = 5;
|
|
|
1527
1748
|
var DesktopCommentRunner = class {
|
|
1528
1749
|
options;
|
|
1529
1750
|
queue = [];
|
|
1751
|
+
listeners = /* @__PURE__ */ new Set();
|
|
1530
1752
|
stopController = new AbortController();
|
|
1531
1753
|
active = null;
|
|
1532
1754
|
currentController = null;
|
|
@@ -1550,13 +1772,17 @@ var DesktopCommentRunner = class {
|
|
|
1550
1772
|
isRunning() {
|
|
1551
1773
|
return this.active !== null;
|
|
1552
1774
|
}
|
|
1775
|
+
subscribe(listener) {
|
|
1776
|
+
this.listeners.add(listener);
|
|
1777
|
+
return () => this.listeners.delete(listener);
|
|
1778
|
+
}
|
|
1553
1779
|
async whenIdle() {
|
|
1554
1780
|
await this.active;
|
|
1555
1781
|
}
|
|
1556
1782
|
async handleEvent(event) {
|
|
1557
1783
|
if (event.type !== "COMMENTS" || this.stopped) return;
|
|
1558
1784
|
if (this.queue.length >= (this.options.maxQueue ?? DEFAULT_MAX_QUEUE)) {
|
|
1559
|
-
await this.
|
|
1785
|
+
await this.fail(event, "QUEUE_FULL");
|
|
1560
1786
|
return;
|
|
1561
1787
|
}
|
|
1562
1788
|
this.queue.push(event);
|
|
@@ -1583,18 +1809,19 @@ var DesktopCommentRunner = class {
|
|
|
1583
1809
|
this.options.fetchTimeoutMs ?? DEFAULT_FETCH_TIMEOUT_MS
|
|
1584
1810
|
);
|
|
1585
1811
|
this.currentController = controller;
|
|
1812
|
+
this.emit({ type: "STARTED", requestId: request.requestId, postUrl: request.postUrl });
|
|
1586
1813
|
try {
|
|
1587
1814
|
if (this.options.isScanActive()) {
|
|
1588
|
-
await this.
|
|
1815
|
+
await this.fail(request, "SCANNER_BUSY");
|
|
1589
1816
|
return;
|
|
1590
1817
|
}
|
|
1591
1818
|
if (this.options.isLoginActive()) {
|
|
1592
|
-
await this.
|
|
1819
|
+
await this.fail(request, "LOGIN_ACTIVE");
|
|
1593
1820
|
return;
|
|
1594
1821
|
}
|
|
1595
1822
|
const storageState = await this.options.facebookState.load();
|
|
1596
1823
|
if (!storageState) {
|
|
1597
|
-
await this.
|
|
1824
|
+
await this.fail(request, "FACEBOOK_SESSION_MISSING");
|
|
1598
1825
|
return;
|
|
1599
1826
|
}
|
|
1600
1827
|
let comments;
|
|
@@ -1606,28 +1833,56 @@ var DesktopCommentRunner = class {
|
|
|
1606
1833
|
storageState
|
|
1607
1834
|
});
|
|
1608
1835
|
} catch (error) {
|
|
1609
|
-
await this.
|
|
1836
|
+
await this.fail(request, readErrorCode2(error));
|
|
1837
|
+
return;
|
|
1838
|
+
}
|
|
1839
|
+
if (!await this.upload(request.requestId, { comments })) {
|
|
1840
|
+
if (!this.stopped) {
|
|
1841
|
+
this.emit({
|
|
1842
|
+
type: "FAILED",
|
|
1843
|
+
requestId: request.requestId,
|
|
1844
|
+
postUrl: request.postUrl,
|
|
1845
|
+
errorCode: "UPLOAD_FAILED"
|
|
1846
|
+
});
|
|
1847
|
+
}
|
|
1610
1848
|
return;
|
|
1611
1849
|
}
|
|
1612
|
-
|
|
1850
|
+
this.emit({
|
|
1851
|
+
type: "SUCCEEDED",
|
|
1852
|
+
requestId: request.requestId,
|
|
1853
|
+
postUrl: request.postUrl,
|
|
1854
|
+
commentCount: comments.length
|
|
1855
|
+
});
|
|
1613
1856
|
} finally {
|
|
1614
1857
|
this.options.clearTimer(timeout);
|
|
1615
1858
|
if (this.currentController === controller) this.currentController = null;
|
|
1616
1859
|
}
|
|
1617
1860
|
}
|
|
1861
|
+
async fail(request, errorCode) {
|
|
1862
|
+
await this.upload(request.requestId, { comments: [], errorCode });
|
|
1863
|
+
this.emit({
|
|
1864
|
+
type: "FAILED",
|
|
1865
|
+
requestId: request.requestId,
|
|
1866
|
+
postUrl: request.postUrl,
|
|
1867
|
+
errorCode
|
|
1868
|
+
});
|
|
1869
|
+
}
|
|
1870
|
+
emit(event) {
|
|
1871
|
+
for (const listener of this.listeners) listener(event);
|
|
1872
|
+
}
|
|
1618
1873
|
async upload(requestId, payload) {
|
|
1619
1874
|
const signal = this.stopController.signal;
|
|
1620
|
-
if (signal.aborted) return;
|
|
1875
|
+
if (signal.aborted) return false;
|
|
1621
1876
|
for (let attempt = 0; ; attempt += 1) {
|
|
1622
1877
|
try {
|
|
1623
1878
|
await this.options.http.uploadComments(requestId, payload, signal);
|
|
1624
|
-
return;
|
|
1879
|
+
return true;
|
|
1625
1880
|
} catch (error) {
|
|
1626
|
-
if (!isRecoverable(error) || attempt >= 2 || signal.aborted) return;
|
|
1881
|
+
if (!isRecoverable(error) || attempt >= 2 || signal.aborted) return false;
|
|
1627
1882
|
try {
|
|
1628
1883
|
await this.options.wait(1e3 * 2 ** attempt, signal);
|
|
1629
1884
|
} catch {
|
|
1630
|
-
return;
|
|
1885
|
+
return false;
|
|
1631
1886
|
}
|
|
1632
1887
|
}
|
|
1633
1888
|
}
|
|
@@ -1765,10 +2020,12 @@ function normalizeBatch(payload) {
|
|
|
1765
2020
|
const authorAvatarUrl = normalizeMediaUrl(item.authorAvatarUrl);
|
|
1766
2021
|
const attachmentImages = item.attachmentImages?.map(normalizeMediaUrl).filter((url) => Boolean(url)).slice(0, 6);
|
|
1767
2022
|
const groupName = typeof item.groupName === "string" && item.groupName.trim() ? item.groupName.trim().slice(0, 200) : void 0;
|
|
2023
|
+
const commentCount = normalizeCount(item.commentCount);
|
|
1768
2024
|
const textFormat = normalizeTextFormat(item.textFormat);
|
|
1769
2025
|
return {
|
|
1770
2026
|
...item,
|
|
1771
2027
|
...groupName ? { groupName } : {},
|
|
2028
|
+
...commentCount !== void 0 ? { commentCount } : {},
|
|
1772
2029
|
sourceUrl: normalizeFacebookPostUrl(item.sourceUrl),
|
|
1773
2030
|
authorAvatarUrl,
|
|
1774
2031
|
attachmentImages: attachmentImages?.length ? attachmentImages : void 0,
|
|
@@ -1825,12 +2082,21 @@ function normalizeFormatToken(value) {
|
|
|
1825
2082
|
const normalized = value?.trim();
|
|
1826
2083
|
return normalized && normalized.length <= 50 ? normalized : void 0;
|
|
1827
2084
|
}
|
|
2085
|
+
function normalizeCount(value) {
|
|
2086
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 && value <= 1e7 ? value : void 0;
|
|
2087
|
+
}
|
|
1828
2088
|
function normalizeComments(payload) {
|
|
1829
|
-
const comments = payload.comments.slice(0,
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
2089
|
+
const comments = payload.comments.slice(0, DESKTOP_POST_COMMENTS_MAX_LIMIT).map((comment) => {
|
|
2090
|
+
const authorAvatarUrl = normalizeMediaUrl(comment.authorAvatarUrl);
|
|
2091
|
+
const depth = normalizeCount(comment.depth);
|
|
2092
|
+
return {
|
|
2093
|
+
text: comment.text.trim().slice(0, 2e3),
|
|
2094
|
+
...comment.authorName?.trim() ? { authorName: comment.authorName.trim().slice(0, 200) } : {},
|
|
2095
|
+
...authorAvatarUrl ? { authorAvatarUrl } : {},
|
|
2096
|
+
...depth !== void 0 && depth <= 5 ? { depth } : {},
|
|
2097
|
+
...comment.publishedAt ? { publishedAt: comment.publishedAt } : {}
|
|
2098
|
+
};
|
|
2099
|
+
}).filter((comment) => comment.text.length > 0);
|
|
1834
2100
|
const errorCode = payload.errorCode?.replace(/[^A-Z0-9_]/g, "_").slice(0, 100);
|
|
1835
2101
|
return { comments, ...errorCode ? { errorCode } : {} };
|
|
1836
2102
|
}
|
|
@@ -2267,6 +2533,7 @@ function toDesktopPost(item) {
|
|
|
2267
2533
|
...item.authorName ? { authorName: item.authorName } : {},
|
|
2268
2534
|
...item.authorAvatarUrl ? { authorAvatarUrl: item.authorAvatarUrl } : {},
|
|
2269
2535
|
...item.attachmentImages?.length ? { attachmentImages: item.attachmentImages } : {},
|
|
2536
|
+
...item.commentCount !== void 0 ? { commentCount: item.commentCount } : {},
|
|
2270
2537
|
...item.textFormat ? { textFormat: item.textFormat } : {},
|
|
2271
2538
|
...item.publishedAt ? { publishedAt: item.publishedAt } : {},
|
|
2272
2539
|
...item.groupName ? { groupName: item.groupName } : {}
|
|
@@ -2655,6 +2922,21 @@ function getFacebookAccountId(state) {
|
|
|
2655
2922
|
return value && /^\d{1,32}$/.test(value) ? value : null;
|
|
2656
2923
|
}
|
|
2657
2924
|
|
|
2925
|
+
// src/main/graphql-dump.ts
|
|
2926
|
+
import { writeFile as writeFile4 } from "node:fs/promises";
|
|
2927
|
+
import { join as join3 } from "node:path";
|
|
2928
|
+
function createGraphqlDumpParser(directory) {
|
|
2929
|
+
if (!directory) return void 0;
|
|
2930
|
+
return (body, groupUrl) => {
|
|
2931
|
+
void writeFile4(
|
|
2932
|
+
join3(directory, `graphql-${Date.now()}-${Math.random().toString(36).slice(2, 8)}.txt`),
|
|
2933
|
+
`// group: ${groupUrl}
|
|
2934
|
+
${body}`
|
|
2935
|
+
).catch(() => void 0);
|
|
2936
|
+
return parseFacebookGraphqlBody(body, groupUrl);
|
|
2937
|
+
};
|
|
2938
|
+
}
|
|
2939
|
+
|
|
2658
2940
|
// src/main/reconnect-backoff.ts
|
|
2659
2941
|
var BASE_DELAY_MS = 1e3;
|
|
2660
2942
|
var MAX_DELAY_MS = 3e4;
|
|
@@ -2925,7 +3207,7 @@ var MISSING_BROWSER_HINT = "Chromium for Playwright is not installed yet. Run `d
|
|
|
2925
3207
|
function agentVersion() {
|
|
2926
3208
|
let directory = fileURLToPath(new URL(".", import.meta.url));
|
|
2927
3209
|
for (let depth = 0; depth < 4; depth += 1) {
|
|
2928
|
-
const manifest =
|
|
3210
|
+
const manifest = join4(directory, "package.json");
|
|
2929
3211
|
if (existsSync2(manifest)) {
|
|
2930
3212
|
try {
|
|
2931
3213
|
const version = JSON.parse(readFileSync2(manifest, "utf8")).version;
|
|
@@ -2942,7 +3224,7 @@ function resolvePlatform() {
|
|
|
2942
3224
|
return process.platform === "linux" ? "LINUX" : "WINDOWS";
|
|
2943
3225
|
}
|
|
2944
3226
|
function resolveConfigDirectory(options) {
|
|
2945
|
-
return options.config?.trim() || process.env.DIGGYLAB_AGENT_DIR?.trim() ||
|
|
3227
|
+
return options.config?.trim() || process.env.DIGGYLAB_AGENT_DIR?.trim() || join4(homedir(), CONFIG_DIRECTORY);
|
|
2946
3228
|
}
|
|
2947
3229
|
function resolveEndpoint(options) {
|
|
2948
3230
|
const configured = (options.api ?? process.env.DIGGYLAB_API_URL)?.trim() || "https://api.diggylab.com";
|
|
@@ -2952,12 +3234,12 @@ function resolveEndpoint(options) {
|
|
|
2952
3234
|
function createRuntime(options) {
|
|
2953
3235
|
const directory = resolveConfigDirectory(options);
|
|
2954
3236
|
const endpoint = resolveEndpoint(options);
|
|
2955
|
-
const crypto2 = createFileCrypto(
|
|
2956
|
-
const credentials = new CredentialStore(
|
|
3237
|
+
const crypto2 = createFileCrypto(join4(directory, "key"));
|
|
3238
|
+
const credentials = new CredentialStore(join4(directory, "credentials.json"), crypto2);
|
|
2957
3239
|
const sessions = new DeviceSessionStore(credentials);
|
|
2958
3240
|
const facebook = new FacebookSessionStore(credentials);
|
|
2959
|
-
const spool = new EncryptedScanSpool(
|
|
2960
|
-
const activity = new ActivityLog({ directory:
|
|
3241
|
+
const spool = new EncryptedScanSpool(join4(directory, "scan-spool"), crypto2);
|
|
3242
|
+
const activity = new ActivityLog({ directory: join4(directory, "activity") });
|
|
2961
3243
|
const http = new DesktopAgentHttpClient(endpoint.httpOrigin);
|
|
2962
3244
|
const manager = new DeviceCredentialManager(sessions, http, createDeviceIdentity);
|
|
2963
3245
|
return { activity, browserType: createBrowserLauncher(chromium), directory, endpoint, facebook, manager, sessions, spool };
|
|
@@ -3288,8 +3570,13 @@ async function commandRun(runtime, io) {
|
|
|
3288
3570
|
io.error("This agent is not paired yet. Run `diggylab pair --code <code>` first.");
|
|
3289
3571
|
return 2;
|
|
3290
3572
|
}
|
|
3291
|
-
const recordActivity = (message, code) => {
|
|
3292
|
-
void runtime.activity.append({
|
|
3573
|
+
const recordActivity = (message, code, options = {}) => {
|
|
3574
|
+
void runtime.activity.append({
|
|
3575
|
+
category: options.category ?? "CONNECTION",
|
|
3576
|
+
code,
|
|
3577
|
+
level: options.level ?? "INFO",
|
|
3578
|
+
message
|
|
3579
|
+
}).catch(() => void 0);
|
|
3293
3580
|
};
|
|
3294
3581
|
const facebookReady = await runtime.facebook.isReady();
|
|
3295
3582
|
const session = new DesktopAgentSession({
|
|
@@ -3326,7 +3613,10 @@ async function commandRun(runtime, io) {
|
|
|
3326
3613
|
},
|
|
3327
3614
|
origin: runtime.endpoint.httpOrigin
|
|
3328
3615
|
});
|
|
3329
|
-
const scanner = new FacebookScanner({
|
|
3616
|
+
const scanner = new FacebookScanner({
|
|
3617
|
+
browserType: runtime.browserType,
|
|
3618
|
+
parseGraphqlBody: createGraphqlDumpParser(process.env.FACEBOOK_SCAN_DUMP_DIR)
|
|
3619
|
+
});
|
|
3330
3620
|
const commentRunner = new DesktopCommentRunner({
|
|
3331
3621
|
clearTimer: (timer) => clearTimeout(timer),
|
|
3332
3622
|
facebookState: runtime.facebook,
|
|
@@ -3382,6 +3672,29 @@ async function commandRun(runtime, io) {
|
|
|
3382
3672
|
});
|
|
3383
3673
|
scanRunner.start();
|
|
3384
3674
|
commentRunner.start();
|
|
3675
|
+
const unsubscribeComments = commentRunner.subscribe((event) => {
|
|
3676
|
+
if (event.type === "STARTED") {
|
|
3677
|
+
io.log(`${stamp()} comments: fetching ${event.postUrl}`);
|
|
3678
|
+
recordActivity(`Fetching comments for ${event.postUrl}.`, "COMMENT_FETCH_STARTED", {
|
|
3679
|
+
category: "COMMENTS"
|
|
3680
|
+
});
|
|
3681
|
+
return;
|
|
3682
|
+
}
|
|
3683
|
+
if (event.type === "SUCCEEDED") {
|
|
3684
|
+
io.log(`${stamp()} comments: fetched ${event.commentCount} comment(s)`);
|
|
3685
|
+
recordActivity(
|
|
3686
|
+
`Fetched ${event.commentCount} comment(s) for ${event.postUrl}.`,
|
|
3687
|
+
"COMMENT_FETCH_SUCCEEDED",
|
|
3688
|
+
{ category: "COMMENTS" }
|
|
3689
|
+
);
|
|
3690
|
+
return;
|
|
3691
|
+
}
|
|
3692
|
+
io.log(`${stamp()} comments: failed (${event.errorCode})`);
|
|
3693
|
+
recordActivity(`Comment fetch failed (${event.errorCode}) for ${event.postUrl}.`, event.errorCode, {
|
|
3694
|
+
category: "COMMENTS",
|
|
3695
|
+
level: "WARN"
|
|
3696
|
+
});
|
|
3697
|
+
});
|
|
3385
3698
|
await scanRunner.recover();
|
|
3386
3699
|
await session.start();
|
|
3387
3700
|
io.log(`Worker running as "${stored.device.name}" (API ${runtime.endpoint.httpOrigin}). Press Ctrl+C to stop.`);
|
|
@@ -3398,6 +3711,7 @@ async function commandRun(runtime, io) {
|
|
|
3398
3711
|
clearInterval(keepAlive);
|
|
3399
3712
|
session.stop();
|
|
3400
3713
|
commentRunner.stop();
|
|
3714
|
+
unsubscribeComments();
|
|
3401
3715
|
scanRunner.stop();
|
|
3402
3716
|
}
|
|
3403
3717
|
return 0;
|
|
@@ -3428,7 +3742,7 @@ function runCommand(command, args) {
|
|
|
3428
3742
|
});
|
|
3429
3743
|
}
|
|
3430
3744
|
function resolveServiceExecStart() {
|
|
3431
|
-
const candidates = [
|
|
3745
|
+
const candidates = [join4(dirname3(process.execPath), "diggylab"), "/usr/local/bin/diggylab", "/usr/bin/diggylab"];
|
|
3432
3746
|
const link = candidates.find((candidate) => existsSync2(candidate));
|
|
3433
3747
|
return link ? [link] : [process.execPath, fileURLToPath(import.meta.url)];
|
|
3434
3748
|
}
|
|
@@ -3454,7 +3768,7 @@ async function commandInstallService(runtime, io) {
|
|
|
3454
3768
|
}
|
|
3455
3769
|
io.log(`Wrote ${SERVICE_PATH}.`);
|
|
3456
3770
|
io.log(`State directory: ${runtime.directory}`);
|
|
3457
|
-
if (process.env.SUDO_USER && process.env.SUDO_USER !== "root" && runtime.directory ===
|
|
3771
|
+
if (process.env.SUDO_USER && process.env.SUDO_USER !== "root" && runtime.directory === join4(homedir(), CONFIG_DIRECTORY)) {
|
|
3458
3772
|
io.error(`Heads up: this ran as root, so the service will use ${runtime.directory} (root's home).`);
|
|
3459
3773
|
io.error(`If you paired as "${process.env.SUDO_USER}", pass --config to point at that user's ~/${CONFIG_DIRECTORY} instead.`);
|
|
3460
3774
|
}
|