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.
@@ -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}`);
@@ -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
- console.log(`[nst] WARNING: Profile "${name}" NOT FOUND creating new profile...`);
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: 'Windows',
81
+ platform,
81
82
  kernelMilestone: '132',
82
83
  fingerprint: {
83
84
  flags: {
@@ -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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "channel-worker",
3
- "version": "1.6.2",
3
+ "version": "1.6.4",
4
4
  "description": "Channel Manager worker daemon — runs on remote machines to execute video pipeline jobs",
5
5
  "main": "lib/daemon.js",
6
6
  "bin": {