channel-worker 1.6.2 → 1.6.4
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/command-poller.js +80 -0
- package/lib/nst-manager.js +4 -3
- package/lib/stats-syncer.js +9 -1
- package/package.json +1 -1
package/lib/command-poller.js
CHANGED
|
@@ -75,6 +75,9 @@ class CommandPoller {
|
|
|
75
75
|
case 'restart_worker':
|
|
76
76
|
await this.handleRestartWorker(command);
|
|
77
77
|
break;
|
|
78
|
+
case 'launch_veo3_profile':
|
|
79
|
+
await this.handleLaunchVeo3Profile(command);
|
|
80
|
+
break;
|
|
78
81
|
default:
|
|
79
82
|
// Other commands (scan_facebook_pages, etc.) handled by extension
|
|
80
83
|
console.log(`[commands] Skipping ${command.type} — handled by extension`);
|
|
@@ -167,6 +170,83 @@ class CommandPoller {
|
|
|
167
170
|
}
|
|
168
171
|
}
|
|
169
172
|
|
|
173
|
+
async handleLaunchVeo3Profile(command) {
|
|
174
|
+
const { nst_profile_id, name, veo3_worker_id, os, proxy } = command.payload || {};
|
|
175
|
+
console.log(`[commands] Launching Veo3 profile: ${nst_profile_id} (${name}) os=${os || 'windows'}`);
|
|
176
|
+
|
|
177
|
+
try {
|
|
178
|
+
if (!this.nst) {
|
|
179
|
+
const apiKey = await this.api.getSetting('nst_api_key');
|
|
180
|
+
if (apiKey) {
|
|
181
|
+
this.nst = new NstManager(apiKey);
|
|
182
|
+
} else {
|
|
183
|
+
throw new Error('Nstbrowser API key not configured.');
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Auto-create Nstbrowser profile if not exists
|
|
188
|
+
await this.nst.ensureProfile(nst_profile_id, { os: os || 'windows' });
|
|
189
|
+
|
|
190
|
+
// Use Content Creator extension path (separate from channel-manager-ext)
|
|
191
|
+
const baseExtPath = this.config.content_creator_ext_path || '';
|
|
192
|
+
if (!baseExtPath) throw new Error('content_creator_ext_path not configured in worker config.');
|
|
193
|
+
|
|
194
|
+
let extensionPath = baseExtPath;
|
|
195
|
+
|
|
196
|
+
// Create unique extension dir per profile
|
|
197
|
+
if (nst_profile_id) {
|
|
198
|
+
const fs = require('fs');
|
|
199
|
+
const path = require('path');
|
|
200
|
+
const ts = Date.now();
|
|
201
|
+
const uniqueExtPath = baseExtPath + '-' + nst_profile_id + '-' + ts;
|
|
202
|
+
try {
|
|
203
|
+
fs.mkdirSync(uniqueExtPath, { recursive: true });
|
|
204
|
+
const files = fs.readdirSync(baseExtPath);
|
|
205
|
+
for (const f of files) {
|
|
206
|
+
const src = path.join(baseExtPath, f);
|
|
207
|
+
if (fs.statSync(src).isFile()) fs.copyFileSync(src, path.join(uniqueExtPath, f));
|
|
208
|
+
}
|
|
209
|
+
// Write Veo3-specific config.json
|
|
210
|
+
fs.writeFileSync(path.join(uniqueExtPath, 'config.json'), JSON.stringify({
|
|
211
|
+
channelManagerApi: 'https://api.channel.tunasm.art',
|
|
212
|
+
profileId: nst_profile_id,
|
|
213
|
+
workerToken: this.config.worker_token || '',
|
|
214
|
+
workerType: 'veo3',
|
|
215
|
+
}));
|
|
216
|
+
extensionPath = uniqueExtPath;
|
|
217
|
+
console.log(`[commands] Veo3 ext dir: ${uniqueExtPath}`);
|
|
218
|
+
|
|
219
|
+
// Cleanup old dirs
|
|
220
|
+
const parent = path.dirname(baseExtPath);
|
|
221
|
+
const baseName = path.basename(baseExtPath);
|
|
222
|
+
const oldDirs = fs.readdirSync(parent)
|
|
223
|
+
.filter(d => d.startsWith(baseName + '-' + nst_profile_id) && d !== path.basename(uniqueExtPath))
|
|
224
|
+
.map(d => path.join(parent, d))
|
|
225
|
+
.filter(d => { try { return fs.statSync(d).isDirectory(); } catch { return false; } });
|
|
226
|
+
for (const d of oldDirs) {
|
|
227
|
+
try { fs.rmSync(d, { recursive: true }); } catch {}
|
|
228
|
+
}
|
|
229
|
+
} catch (e) {
|
|
230
|
+
console.warn(`[commands] Unique Veo3 ext dir failed: ${e.message}, using base`);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const result = await this.nst.launchProfile(nst_profile_id, { proxy: proxy || null, extensionPath });
|
|
235
|
+
console.log(`[commands] Veo3 profile ${nst_profile_id} launched${proxy ? ' (proxy)' : ''}`);
|
|
236
|
+
|
|
237
|
+
await this.api.updateCommand(command._id, {
|
|
238
|
+
status: 'done',
|
|
239
|
+
result: { profile_id: result.profileId, launched_at: new Date().toISOString() },
|
|
240
|
+
});
|
|
241
|
+
} catch (err) {
|
|
242
|
+
console.error(`[commands] Failed to launch Veo3 profile: ${err.message}`);
|
|
243
|
+
await this.api.updateCommand(command._id, {
|
|
244
|
+
status: 'failed',
|
|
245
|
+
error: err.message,
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
170
250
|
async handleScanFacebookPages(command) {
|
|
171
251
|
const { profile_id } = command.payload || {};
|
|
172
252
|
console.log(`[commands] Scan Facebook pages — launching profile: ${profile_id}`);
|
package/lib/nst-manager.js
CHANGED
|
@@ -65,19 +65,20 @@ class NstManager {
|
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
// Create profile if not exists, return profileId
|
|
68
|
-
async ensureProfile(name) {
|
|
68
|
+
async ensureProfile(name, options = {}) {
|
|
69
69
|
const existing = await this.findProfile(name);
|
|
70
70
|
if (existing) {
|
|
71
71
|
console.log(`[nst] Profile "${name}" exists: ${existing}`);
|
|
72
72
|
return existing;
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
-
|
|
75
|
+
const platform = (options.os || 'windows').toLowerCase() === 'mac' ? 'MacOS' : 'Windows';
|
|
76
|
+
console.log(`[nst] WARNING: Profile "${name}" NOT FOUND — creating new profile (${platform})...`);
|
|
76
77
|
const res = await this.api('/profiles', {
|
|
77
78
|
method: 'POST',
|
|
78
79
|
body: JSON.stringify({
|
|
79
80
|
name,
|
|
80
|
-
platform
|
|
81
|
+
platform,
|
|
81
82
|
kernelMilestone: '132',
|
|
82
83
|
fingerprint: {
|
|
83
84
|
flags: {
|
package/lib/stats-syncer.js
CHANGED
|
@@ -34,6 +34,7 @@ class StatsSyncer {
|
|
|
34
34
|
subscribers: stats.subscribers,
|
|
35
35
|
total_views: stats.total_views,
|
|
36
36
|
video_count: stats.video_count,
|
|
37
|
+
avatar_url: stats.avatar_url,
|
|
37
38
|
});
|
|
38
39
|
|
|
39
40
|
console.log(`[stats] "${channel.name}" stats saved to API`);
|
|
@@ -176,6 +177,12 @@ class StatsSyncer {
|
|
|
176
177
|
}
|
|
177
178
|
});
|
|
178
179
|
|
|
180
|
+
// Get channel avatar
|
|
181
|
+
var avatarEl = document.querySelector('#channel-header-container img, yt-decorated-avatar img, yt-avatar-shape img');
|
|
182
|
+
if (avatarEl && avatarEl.src) {
|
|
183
|
+
stats.avatar_url = avatarEl.src;
|
|
184
|
+
}
|
|
185
|
+
|
|
179
186
|
stats.debug = texts.join(' | ');
|
|
180
187
|
return JSON.stringify(stats);
|
|
181
188
|
})()
|
|
@@ -183,13 +190,14 @@ class StatsSyncer {
|
|
|
183
190
|
returnByValue: true,
|
|
184
191
|
});
|
|
185
192
|
|
|
186
|
-
var stats = { subscribers: 0, total_views: 0, video_count: 0 };
|
|
193
|
+
var stats = { subscribers: 0, total_views: 0, video_count: 0, avatar_url: null };
|
|
187
194
|
try {
|
|
188
195
|
var parsed = JSON.parse(result?.result?.value || '{}');
|
|
189
196
|
console.log('[stats] About page data:', parsed.debug);
|
|
190
197
|
if (parsed.subscribers >= 0) stats.subscribers = parsed.subscribers;
|
|
191
198
|
if (parsed.total_views >= 0) stats.total_views = parsed.total_views;
|
|
192
199
|
if (parsed.video_count >= 0) stats.video_count = parsed.video_count;
|
|
200
|
+
if (parsed.avatar_url) stats.avatar_url = parsed.avatar_url;
|
|
193
201
|
} catch (e) {
|
|
194
202
|
console.error('[stats] Parse error:', e.message);
|
|
195
203
|
}
|