channel-worker 1.2.0 → 1.3.1
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/bin/cli.js +10 -6
- package/lib/api-client.js +21 -1
- package/lib/command-poller.js +23 -0
- package/lib/daemon.js +15 -0
- package/lib/extension-updater.js +78 -0
- package/package.json +1 -1
package/bin/cli.js
CHANGED
|
@@ -6,6 +6,10 @@ const os = require('os');
|
|
|
6
6
|
|
|
7
7
|
const CONFIG_DIR = path.join(os.homedir(), '.channel-worker');
|
|
8
8
|
const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
|
|
9
|
+
const DEFAULT_API_URL = 'https://api.channel.tunasm.art';
|
|
10
|
+
const DEFAULT_EXT_PATH = process.platform === 'win32'
|
|
11
|
+
? 'C:\\agents\\channel-manager-ext'
|
|
12
|
+
: path.join(os.homedir(), 'channel-manager-ext');
|
|
9
13
|
|
|
10
14
|
function parseArgs(args) {
|
|
11
15
|
const result = {};
|
|
@@ -40,9 +44,9 @@ const cmd = args._cmd || 'start';
|
|
|
40
44
|
if (cmd === 'pair') {
|
|
41
45
|
// Pair with dashboard using one-time code
|
|
42
46
|
const code = args.code;
|
|
43
|
-
const apiUrl = args.api ||
|
|
47
|
+
const apiUrl = args.api || DEFAULT_API_URL;
|
|
44
48
|
const nstKey = args['nst-key'] || '';
|
|
45
|
-
const extensionPath = args.extension ||
|
|
49
|
+
const extensionPath = args.extension || DEFAULT_EXT_PATH;
|
|
46
50
|
const maxConcurrent = parseInt(args.concurrent || '2', 10);
|
|
47
51
|
|
|
48
52
|
if (!code) {
|
|
@@ -91,10 +95,10 @@ if (cmd === 'pair') {
|
|
|
91
95
|
} else if (cmd === 'init') {
|
|
92
96
|
const config = {
|
|
93
97
|
worker_id: args.id || `worker-${os.hostname()}`,
|
|
94
|
-
api_url: args.api ||
|
|
98
|
+
api_url: args.api || DEFAULT_API_URL,
|
|
95
99
|
max_concurrent: parseInt(args.concurrent || '2', 10),
|
|
96
100
|
nst_api_key: args['nst-key'] || '',
|
|
97
|
-
extension_path: args.extension ||
|
|
101
|
+
extension_path: args.extension || DEFAULT_EXT_PATH,
|
|
98
102
|
worker_token: args.token || '',
|
|
99
103
|
};
|
|
100
104
|
saveConfig(config);
|
|
@@ -108,10 +112,10 @@ if (cmd === 'pair') {
|
|
|
108
112
|
const saved = loadConfig();
|
|
109
113
|
const config = {
|
|
110
114
|
worker_id: args.id || saved.worker_id || `worker-${os.hostname()}`,
|
|
111
|
-
api_url: args.api || saved.api_url ||
|
|
115
|
+
api_url: args.api || saved.api_url || DEFAULT_API_URL,
|
|
112
116
|
max_concurrent: parseInt(args.concurrent || saved.max_concurrent || '2', 10),
|
|
113
117
|
nst_api_key: args['nst-key'] || saved.nst_api_key || '',
|
|
114
|
-
extension_path: args.extension || saved.extension_path ||
|
|
118
|
+
extension_path: args.extension || saved.extension_path || DEFAULT_EXT_PATH,
|
|
115
119
|
worker_token: args.token || saved.worker_token || '',
|
|
116
120
|
verbose: !!args.verbose,
|
|
117
121
|
};
|
package/lib/api-client.js
CHANGED
|
@@ -62,12 +62,32 @@ class ApiClient {
|
|
|
62
62
|
|
|
63
63
|
// Commands
|
|
64
64
|
async getNextCommand(workerId) {
|
|
65
|
-
|
|
65
|
+
const workerTypes = 'launch_profile,close_profile,save_file,set_thumbnail,set_tags,set_file_input,click_and_upload,type_text,verify_logins,update_extension';
|
|
66
|
+
return this.request('GET', `/workers/commands?worker_id=${workerId}&types=${encodeURIComponent(workerTypes)}`);
|
|
66
67
|
}
|
|
67
68
|
|
|
68
69
|
async updateCommand(commandId, data) {
|
|
69
70
|
return this.request('PUT', `/workers/commands/${commandId}`, data);
|
|
70
71
|
}
|
|
72
|
+
|
|
73
|
+
// Extension download
|
|
74
|
+
async getExtensionVersion() {
|
|
75
|
+
const data = await this.request('GET', '/extension-download/version');
|
|
76
|
+
return data.version;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async downloadExtension(destPath) {
|
|
80
|
+
const url = `${this.baseUrl}/extension-download/zip`;
|
|
81
|
+
const res = await fetch(url, {
|
|
82
|
+
headers: { 'x-worker-token': this.workerToken },
|
|
83
|
+
});
|
|
84
|
+
if (!res.ok) throw new Error(`Download failed: ${res.status}`);
|
|
85
|
+
|
|
86
|
+
const fs = require('fs');
|
|
87
|
+
const buffer = Buffer.from(await res.arrayBuffer());
|
|
88
|
+
fs.writeFileSync(destPath, buffer);
|
|
89
|
+
return destPath;
|
|
90
|
+
}
|
|
71
91
|
}
|
|
72
92
|
|
|
73
93
|
module.exports = { ApiClient };
|
package/lib/command-poller.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const { NstManager } = require('./nst-manager');
|
|
2
|
+
const { checkAndUpdateExtension } = require('./extension-updater');
|
|
2
3
|
|
|
3
4
|
class CommandPoller {
|
|
4
5
|
constructor(api, config) {
|
|
@@ -64,6 +65,9 @@ class CommandPoller {
|
|
|
64
65
|
case 'type_text':
|
|
65
66
|
await this.handleTypeText(command);
|
|
66
67
|
break;
|
|
68
|
+
case 'update_extension':
|
|
69
|
+
await this.handleUpdateExtension(command);
|
|
70
|
+
break;
|
|
67
71
|
default:
|
|
68
72
|
// Other commands (scan_facebook_pages, etc.) handled by extension
|
|
69
73
|
console.log(`[commands] Skipping ${command.type} — handled by extension`);
|
|
@@ -827,6 +831,25 @@ class CommandPoller {
|
|
|
827
831
|
await this.api.updateCommand(command._id, { status: 'done' });
|
|
828
832
|
}
|
|
829
833
|
|
|
834
|
+
async handleUpdateExtension(command) {
|
|
835
|
+
console.log('[commands] Updating extension...');
|
|
836
|
+
try {
|
|
837
|
+
if (!this.config.extension_path) {
|
|
838
|
+
throw new Error('No extension_path configured');
|
|
839
|
+
}
|
|
840
|
+
const result = await checkAndUpdateExtension(this.api, this.config.extension_path);
|
|
841
|
+
if (result.updated) {
|
|
842
|
+
console.log(`[commands] Extension updated: ${result.from || 'none'} → ${result.to}`);
|
|
843
|
+
} else {
|
|
844
|
+
console.log(`[commands] Extension already up to date (v${result.local})`);
|
|
845
|
+
}
|
|
846
|
+
await this.api.updateCommand(command._id, { status: 'done', result });
|
|
847
|
+
} catch (err) {
|
|
848
|
+
console.error(`[commands] Extension update failed: ${err.message}`);
|
|
849
|
+
await this.api.updateCommand(command._id, { status: 'failed', error: err.message });
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
|
|
830
853
|
async handleVerifyLogins(command) {
|
|
831
854
|
const { profile_id, channel_id } = command.payload || {};
|
|
832
855
|
console.log(`[commands] Verifying logins for profile: ${profile_id}`);
|
package/lib/daemon.js
CHANGED
|
@@ -3,6 +3,7 @@ const { Heartbeat } = require('./heartbeat');
|
|
|
3
3
|
const { JobPoller } = require('./job-poller');
|
|
4
4
|
const { CommandPoller } = require('./command-poller');
|
|
5
5
|
const { UpdateChecker, getLocalVersion } = require('./updater');
|
|
6
|
+
const { checkAndUpdateExtension } = require('./extension-updater');
|
|
6
7
|
|
|
7
8
|
class Daemon {
|
|
8
9
|
constructor(config) {
|
|
@@ -34,6 +35,20 @@ class Daemon {
|
|
|
34
35
|
max_concurrent: this.config.max_concurrent,
|
|
35
36
|
});
|
|
36
37
|
console.log('[daemon] Registered with dashboard ✓');
|
|
38
|
+
|
|
39
|
+
// Auto-update extension on startup
|
|
40
|
+
if (this.config.extension_path) {
|
|
41
|
+
try {
|
|
42
|
+
const result = await checkAndUpdateExtension(this.api, this.config.extension_path);
|
|
43
|
+
if (result.updated) {
|
|
44
|
+
console.log(`[daemon] Extension updated: ${result.from || 'none'} → ${result.to}`);
|
|
45
|
+
} else {
|
|
46
|
+
console.log(`[daemon] Extension up to date (v${result.local})`);
|
|
47
|
+
}
|
|
48
|
+
} catch (err) {
|
|
49
|
+
console.warn(`[daemon] Extension update check failed: ${err.message}`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
37
52
|
} catch (err) {
|
|
38
53
|
console.error(`[daemon] Failed to register: ${err.message}`);
|
|
39
54
|
console.error('[daemon] Is the dashboard API running? Retrying in 10s...');
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const os = require('os');
|
|
4
|
+
const { execSync } = require('child_process');
|
|
5
|
+
|
|
6
|
+
function getLocalExtensionVersion(extensionPath) {
|
|
7
|
+
try {
|
|
8
|
+
const manifest = JSON.parse(fs.readFileSync(path.join(extensionPath, 'manifest.json'), 'utf8'));
|
|
9
|
+
return manifest.version || null;
|
|
10
|
+
} catch {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function isNewer(remote, local) {
|
|
16
|
+
const r = remote.split('.').map(Number);
|
|
17
|
+
const l = local.split('.').map(Number);
|
|
18
|
+
for (let i = 0; i < 3; i++) {
|
|
19
|
+
if ((r[i] || 0) > (l[i] || 0)) return true;
|
|
20
|
+
if ((r[i] || 0) < (l[i] || 0)) return false;
|
|
21
|
+
}
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async function checkAndUpdateExtension(api, extensionPath) {
|
|
26
|
+
const local = getLocalExtensionVersion(extensionPath);
|
|
27
|
+
const remote = await api.getExtensionVersion();
|
|
28
|
+
|
|
29
|
+
if (local && !isNewer(remote, local)) {
|
|
30
|
+
return { updated: false, local, remote };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
console.log(`[ext-updater] ${local ? `Update available: ${local} → ${remote}` : `Downloading extension v${remote}...`}`);
|
|
34
|
+
|
|
35
|
+
// Download to temp
|
|
36
|
+
const tmpArchive = path.join(os.tmpdir(), `channel-manager-ext-${Date.now()}.tar.gz`);
|
|
37
|
+
const tmpExtract = path.join(os.tmpdir(), `channel-manager-ext-new-${Date.now()}`);
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
await api.downloadExtension(tmpArchive);
|
|
41
|
+
|
|
42
|
+
// Extract
|
|
43
|
+
fs.mkdirSync(tmpExtract, { recursive: true });
|
|
44
|
+
execSync(`tar -xzf "${tmpArchive}" -C "${tmpExtract}"`, { timeout: 30000 });
|
|
45
|
+
|
|
46
|
+
// tar extracts into a subfolder named "channel-manager-ext"
|
|
47
|
+
const extracted = path.join(tmpExtract, 'channel-manager-ext');
|
|
48
|
+
const sourceDir = fs.existsSync(extracted) ? extracted : tmpExtract;
|
|
49
|
+
|
|
50
|
+
// Replace existing extension dir
|
|
51
|
+
if (fs.existsSync(extensionPath)) {
|
|
52
|
+
fs.rmSync(extensionPath, { recursive: true, force: true });
|
|
53
|
+
}
|
|
54
|
+
fs.mkdirSync(extensionPath, { recursive: true });
|
|
55
|
+
|
|
56
|
+
// Copy files
|
|
57
|
+
const files = fs.readdirSync(sourceDir);
|
|
58
|
+
for (const f of files) {
|
|
59
|
+
const src = path.join(sourceDir, f);
|
|
60
|
+
const dest = path.join(extensionPath, f);
|
|
61
|
+
const stat = fs.statSync(src);
|
|
62
|
+
if (stat.isFile()) {
|
|
63
|
+
fs.copyFileSync(src, dest);
|
|
64
|
+
} else if (stat.isDirectory()) {
|
|
65
|
+
fs.cpSync(src, dest, { recursive: true });
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
console.log(`[ext-updater] Extension updated to v${remote}`);
|
|
70
|
+
return { updated: true, from: local, to: remote };
|
|
71
|
+
} finally {
|
|
72
|
+
// Cleanup temp files
|
|
73
|
+
try { fs.unlinkSync(tmpArchive); } catch {}
|
|
74
|
+
try { fs.rmSync(tmpExtract, { recursive: true, force: true }); } catch {}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
module.exports = { checkAndUpdateExtension, getLocalExtensionVersion };
|