channel-worker 2.5.70 → 2.5.72
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/lib/daemon.js +19 -3
- package/lib/nst-manager.js +25 -4
- package/package.json +1 -1
- package/scripts/upload_facebook.js +64 -7
package/lib/daemon.js
CHANGED
|
@@ -14,6 +14,12 @@ class Daemon {
|
|
|
14
14
|
// Nstbrowser agent of THIS Windows session. On a multi-user box the second
|
|
15
15
|
// session's agent binds 8849+ because 8848 is taken machine-wide.
|
|
16
16
|
if (config.nst_api_address) process.env.NST_API_ADDRESS = config.nst_api_address;
|
|
17
|
+
// Tell every NstManager we are pinned to one Windows user's agent, so an
|
|
18
|
+
// unresolved address REFUSES commands instead of defaulting to 8848
|
|
19
|
+
// (= the other session's agent on a multi-user box).
|
|
20
|
+
if (config.nst_agent_user && !config.nst_api_address) {
|
|
21
|
+
process.env.NST_AGENT_PINNED_USER = config.nst_agent_user;
|
|
22
|
+
}
|
|
17
23
|
this.api = new ApiClient(config.api_url, config.worker_token);
|
|
18
24
|
this.heartbeat = new Heartbeat(this.api, config.worker_id, 30000, config);
|
|
19
25
|
this.poller = new JobPoller(this.api, config);
|
|
@@ -46,9 +52,16 @@ class Daemon {
|
|
|
46
52
|
return;
|
|
47
53
|
}
|
|
48
54
|
if (!found) {
|
|
49
|
-
|
|
55
|
+
// The pinned user's agent is GONE. Drop a previously resolved address —
|
|
56
|
+
// after a relog the same port can belong to the other session's agent,
|
|
57
|
+
// and driving it opens blank profiles. Fail-closed until it re-appears.
|
|
58
|
+
if (process.env.NST_API_ADDRESS) {
|
|
59
|
+
console.warn(`[daemon] NST agent for "${user}" disappeared `
|
|
60
|
+
+ `(was ${process.env.NST_API_ADDRESS}) — refusing NST commands until it is back.`);
|
|
61
|
+
delete process.env.NST_API_ADDRESS;
|
|
62
|
+
} else if (!quiet) {
|
|
50
63
|
console.warn(`[daemon] No Nstbrowser agent for Windows user "${user}" — `
|
|
51
|
-
+ 'NST commands
|
|
64
|
+
+ 'NST commands are refused (fail-closed) until that user is logged in.');
|
|
52
65
|
}
|
|
53
66
|
return;
|
|
54
67
|
}
|
|
@@ -73,7 +86,10 @@ class Daemon {
|
|
|
73
86
|
╚══════════════════════════════════════╝
|
|
74
87
|
`);
|
|
75
88
|
console.log(`[daemon] cache server port: ${this.config.cache_port || 8849}`);
|
|
76
|
-
console.log(`[daemon] NST agent: ${process.env.NST_API_ADDRESS
|
|
89
|
+
console.log(`[daemon] NST agent: ${process.env.NST_API_ADDRESS
|
|
90
|
+
|| (this.config.nst_agent_user
|
|
91
|
+
? `UNRESOLVED — refusing NST commands until agent of "${this.config.nst_agent_user}" is found`
|
|
92
|
+
: 'http://localhost:8848/api/v2 (default)')}`);
|
|
77
93
|
|
|
78
94
|
// Start local cache server (extension shares this across all profiles)
|
|
79
95
|
try {
|
package/lib/nst-manager.js
CHANGED
|
@@ -24,10 +24,31 @@ function nstLaunchError(response, { profileId = '', runningCount = 0 } = {}) {
|
|
|
24
24
|
class NstManager {
|
|
25
25
|
constructor(apiKey, options = {}) {
|
|
26
26
|
this.apiKey = apiKey;
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
27
|
+
this._apiAddress = options.apiAddress || null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Multi-user Windows: each logged-in session runs its OWN Nstbrowser agent,
|
|
31
|
+
// and the second one lands on 8849+ because 8848 is already taken machine-wide.
|
|
32
|
+
// Resolved at CALL time, not construct time: the daemon's agent watcher
|
|
33
|
+
// rewrites NST_API_ADDRESS whenever ports move, and an instance built before
|
|
34
|
+
// the first resolution must not keep a stale target forever.
|
|
35
|
+
//
|
|
36
|
+
// Fail-closed: when the daemon is pinned to a Windows user
|
|
37
|
+
// (NST_AGENT_PINNED_USER) and that user's agent hasn't been located, REFUSE
|
|
38
|
+
// instead of falling back to 8848 — on a two-session box 8848 is the OTHER
|
|
39
|
+
// user's agent, and opening a profile there starts a blank Chrome: FB shows
|
|
40
|
+
// a login form and fb-guard wrongly flags the account as logged out
|
|
41
|
+
// (35 channels hit 2026-08-31…09-02).
|
|
42
|
+
get baseUrl() {
|
|
43
|
+
if (this._apiAddress) return this._apiAddress;
|
|
44
|
+
if (process.env.NST_API_ADDRESS) return process.env.NST_API_ADDRESS;
|
|
45
|
+
const pinned = process.env.NST_AGENT_PINNED_USER;
|
|
46
|
+
if (pinned) {
|
|
47
|
+
throw new Error(`NST_AGENT_UNRESOLVED: chưa thấy Nstbrowser agent của Windows user "${pinned}" `
|
|
48
|
+
+ '— không fallback :8848 (agent của session khác → profile trắng → báo logout oan). '
|
|
49
|
+
+ 'Lệnh CHƯA chạm nền tảng; sẽ tự chạy lại khi agent của user đó lên.');
|
|
50
|
+
}
|
|
51
|
+
return 'http://localhost:8848/api/v2';
|
|
31
52
|
}
|
|
32
53
|
|
|
33
54
|
async api(path, options = {}) {
|
package/package.json
CHANGED
|
@@ -574,6 +574,30 @@ async function isThumbnailEditorVisible(page) {
|
|
|
574
574
|
}, THUMBNAIL_EDITOR_TEXT_MARKERS).catch(() => false);
|
|
575
575
|
}
|
|
576
576
|
|
|
577
|
+
// Bằng chứng "thumb tuỳ chỉnh ĐANG được áp" không thể nhầm: nút "Gỡ hình thu
|
|
578
|
+
// nhỏ tùy chỉnh khỏi thước phim" chỉ tồn tại khi đã có thumb tuỳ chỉnh. Cần
|
|
579
|
+
// cho biến thể INLINE (bắt 03/09, idea 6a965a32 fail 5 lượt liên tiếp):
|
|
580
|
+
// editor "nested" nằm HẲN trong bước "Cài đặt thước phim" và không bao giờ
|
|
581
|
+
// "đóng" — marker "Chỉnh sửa hình thu nhỏ" thường trực trong innerText của
|
|
582
|
+
// dialog nên isThumbnailEditorVisible kẹt true, vòng chờ đóng cạn 60s rồi
|
|
583
|
+
// ESC+Hủy phá lượt dù thumb đã dán xong (diag 158386: Gỡ/Lưu/Hủy + Đăng
|
|
584
|
+
// cùng hiện trên một bước).
|
|
585
|
+
async function isCustomThumbApplied(page) {
|
|
586
|
+
return page.evaluate(() => {
|
|
587
|
+
const vis = (el) => {
|
|
588
|
+
const r = el.getBoundingClientRect();
|
|
589
|
+
if (r.width < 8 || r.height < 8) return false;
|
|
590
|
+
const cs = getComputedStyle(el);
|
|
591
|
+
return cs.visibility !== 'hidden' && cs.display !== 'none' && cs.opacity !== '0';
|
|
592
|
+
};
|
|
593
|
+
for (const el of document.querySelectorAll('[aria-label]')) {
|
|
594
|
+
const al = el.getAttribute('aria-label') || '';
|
|
595
|
+
if (/Gỡ hình thu nhỏ tùy chỉnh|Remove( your)? custom thumbnail/i.test(al) && vis(el)) return true;
|
|
596
|
+
}
|
|
597
|
+
return false;
|
|
598
|
+
}).catch(() => false);
|
|
599
|
+
}
|
|
600
|
+
|
|
577
601
|
const SAFE_RETRY_COMPOSER_CLOSED = 'FB_SAFE_RETRY_COMPOSER_CLOSED';
|
|
578
602
|
|
|
579
603
|
function safeComposerRetryError(message) {
|
|
@@ -1654,6 +1678,7 @@ async function runOnce({ page, payload, log }) {
|
|
|
1654
1678
|
const closeDeadline = Date.now() + 60_000;
|
|
1655
1679
|
let modalClosed = false;
|
|
1656
1680
|
let closedPolls = 0;
|
|
1681
|
+
let appliedPolls = 0;
|
|
1657
1682
|
let retryCount = 0;
|
|
1658
1683
|
const RETRY_AFTER_MS = 12_000;
|
|
1659
1684
|
let lastRetryAt = Date.now();
|
|
@@ -1663,6 +1688,23 @@ async function runOnce({ page, payload, log }) {
|
|
|
1663
1688
|
// nested form (no own dialog — its upload button, aria
|
|
1664
1689
|
// "Tải hình thu nhỏ tùy chỉnh lên…", only exists while the
|
|
1665
1690
|
// editor is showing).
|
|
1691
|
+
// Biến thể inline: panel không bao giờ đóng nhưng nút "Gỡ hình
|
|
1692
|
+
// thu nhỏ tùy chỉnh" đã hiện = thumb ĐÃ áp. Chỉ tin ở nhánh
|
|
1693
|
+
// nested (modal thật vẫn phải đóng sau Lưu như cũ) và đòi 2
|
|
1694
|
+
// mẫu liên tiếp như luật closedPolls; bấm Lưu chốt một nhịp
|
|
1695
|
+
// cuối phòng cohort nào đó vẫn cần Lưu để giữ trạng thái.
|
|
1696
|
+
if (thumbEditorVia === 'nested' && await isCustomThumbApplied(page)) {
|
|
1697
|
+
if (++appliedPolls >= 2) {
|
|
1698
|
+
for (const sel of saveCandidates) {
|
|
1699
|
+
const btn = await firstVisible(page.locator(sel), 1);
|
|
1700
|
+
if (btn) { await btn.click({ timeout: 1500 }).catch(() => {}); break; }
|
|
1701
|
+
}
|
|
1702
|
+
log('info', '[fb-pw] page-wall thumb — panel inline không đóng nhưng nút "Gỡ hình thu nhỏ" đang hiện → coi như dán xong');
|
|
1703
|
+
modalClosed = true; break;
|
|
1704
|
+
}
|
|
1705
|
+
} else {
|
|
1706
|
+
appliedPolls = 0;
|
|
1707
|
+
}
|
|
1666
1708
|
const still = await isThumbnailEditorVisible(page);
|
|
1667
1709
|
if (!still) {
|
|
1668
1710
|
// Require two consecutive closed samples. Facebook briefly
|
|
@@ -1702,6 +1744,11 @@ async function runOnce({ page, payload, log }) {
|
|
|
1702
1744
|
}
|
|
1703
1745
|
thumbApplied = true;
|
|
1704
1746
|
log('info', `[fb-pw] page-wall thumb — modal closed after save (retries=${retryCount})`);
|
|
1747
|
+
} else if (thumbEditorVia === 'nested' && await isCustomThumbApplied(page)) {
|
|
1748
|
+
// Hết giờ chờ đóng nhưng bằng chứng áp thumb còn đó — tuyệt
|
|
1749
|
+
// đối không ESC/Hủy (Hủy trên panel inline là GỠ thumb).
|
|
1750
|
+
log('info', '[fb-pw] page-wall thumb — hết giờ chờ đóng nhưng nút "Gỡ hình thu nhỏ" đang hiện → giữ panel, coi như dán xong');
|
|
1751
|
+
thumbApplied = true;
|
|
1705
1752
|
} else {
|
|
1706
1753
|
log('warn', '[fb-pw] page-wall thumb — modal still open 60s after Lưu click; trying ESC + click Hủy fallback');
|
|
1707
1754
|
await page.keyboard.press('Escape').catch(() => {});
|
|
@@ -2077,13 +2124,22 @@ async function runOnce({ page, payload, log }) {
|
|
|
2077
2124
|
// output. (Observed on reel 1506614811005729: step 1 → click Tiếp
|
|
2078
2125
|
// → publish, the thumb-edit pill never appeared.)
|
|
2079
2126
|
if (thumbPath && !customThumbDone) {
|
|
2080
|
-
//
|
|
2081
|
-
//
|
|
2082
|
-
//
|
|
2083
|
-
//
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2127
|
+
// Cứu theo BẰNG CHỨNG trước khi chặn: biến thể inline có thể làm cờ
|
|
2128
|
+
// customThumbDone lỡ false trong khi nút "Gỡ hình thu nhỏ tùy chỉnh"
|
|
2129
|
+
// ngay trên bước này chứng minh thumb ĐANG được áp — cả 5 lượt fail
|
|
2130
|
+
// 02-03/09 của idea 6a965a32 đều ở trạng thái này (diag 158386).
|
|
2131
|
+
if (await isCustomThumbApplied(page)) {
|
|
2132
|
+
log('info', '[fb-pw] pre-publish: nút "Gỡ hình thu nhỏ tùy chỉnh" đang hiện — thumb đã áp dù cờ chưa bật, tiếp tục đăng');
|
|
2133
|
+
customThumbDone = true;
|
|
2134
|
+
} else {
|
|
2135
|
+
// Defensive: we normally fail at the caption step (before any Tiếp)
|
|
2136
|
+
// when a required custom thumb can't be applied. If we somehow reach
|
|
2137
|
+
// here without it, refuse to publish — but BEFORE clicking Đăng, so
|
|
2138
|
+
// nothing ships and a retry can't double-post. (The v2.5.14 double-post
|
|
2139
|
+
// came from failing AFTER publish; this throw is pre-publish.)
|
|
2140
|
+
await dumpFailure(page, 'thumb-required-at-publish', log);
|
|
2141
|
+
throw new Error('Custom thumbnail required but not applied — refusing to publish with FB auto-thumb');
|
|
2142
|
+
}
|
|
2087
2143
|
}
|
|
2088
2144
|
log('info', `[fb-pw] click publish "${pub.verb}" via "${pub.sel}" (step ${step + 1})`);
|
|
2089
2145
|
// Snapshot the captured-IDs list RIGHT BEFORE the publish click. The
|
|
@@ -2850,6 +2906,7 @@ module.exports.__testables = {
|
|
|
2850
2906
|
PUBLISH_ENABLE_TIMEOUT_MS,
|
|
2851
2907
|
PUBLISH_CTA_ABSENT_GRACE_MS,
|
|
2852
2908
|
THUMBNAIL_EDITOR_TEXT_MARKERS,
|
|
2909
|
+
isCustomThumbApplied,
|
|
2853
2910
|
REEL_CREATE_SELECTORS,
|
|
2854
2911
|
REEL_ENTRY_SELECTORS,
|
|
2855
2912
|
ADD_VIDEO_SELECTORS,
|