channel-worker 2.5.118 → 2.5.119

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.
@@ -370,7 +370,26 @@ class CommandPoller {
370
370
  return;
371
371
  }
372
372
 
373
+ const clickResult = await report.fetchClickReport(conn.context, {
374
+ days: payload.days || 90,
375
+ log: (message) => console.log(message),
376
+ });
377
+ if (clickResult.status === 'needs_verify') {
378
+ await this.api.updateCommand(command._id, {
379
+ status: 'done',
380
+ result: { needs_verify: true, reason: clickResult.reason, count: result.rows.length, click_count: clickResult.rows?.length || 0 },
381
+ });
382
+ return;
383
+ }
384
+ if (clickResult.status !== 'ok') {
385
+ await this.api.updateCommand(command._id, {
386
+ status: 'failed', error: String(clickResult.reason || 'Không đọc được báo cáo click Shopee').slice(0, 500),
387
+ });
388
+ return;
389
+ }
390
+
373
391
  const saved = await this.api.affiliateReportResult(result.rows, {
392
+ clicks: clickResult.rows,
374
393
  last_report_date: result.last_report_date,
375
394
  last_update_time: result.last_update_time,
376
395
  from: result.from,
@@ -383,10 +402,14 @@ class CommandPoller {
383
402
  saved: saved?.saved || 0,
384
403
  matched: saved?.matched || 0,
385
404
  unmatched: saved?.unmatched || 0,
405
+ click_count: clickResult.rows.length,
406
+ click_saved: saved?.click_saved || 0,
407
+ click_matched: saved?.click_matched || 0,
408
+ click_unmatched: saved?.click_unmatched || 0,
386
409
  last_report_date: result.last_report_date,
387
410
  },
388
411
  });
389
- console.log(`[shopee-report] đã lưu ${saved?.saved || 0}/${result.rows.length} dòng; khớp kênh ${saved?.matched || 0}`);
412
+ console.log(`[shopee-report] đã lưu ${saved?.saved || 0}/${result.rows.length} đơn, ${saved?.click_saved || 0}/${clickResult.rows.length} click`);
390
413
  } catch (err) {
391
414
  console.error(`[shopee-report] đồng bộ thất bại: ${err.message}`);
392
415
  await this.api.updateCommand(command._id, { status: 'failed', error: String(err.message || err).slice(0, 500) });
@@ -104,4 +104,83 @@ async function fetchConversionReport(ctx, {
104
104
  }
105
105
  }
106
106
 
107
- module.exports = { fetchConversionReport, reportWindow };
107
+ async function fetchClickReport(ctx, {
108
+ days = 90,
109
+ pageSize = 100,
110
+ maxPages = 200,
111
+ delayMs = 250,
112
+ timeoutMs = 60000,
113
+ log = null,
114
+ } = {}) {
115
+ let page = findAffiliatePage(ctx.pages());
116
+ let opened = false;
117
+ if (!page) {
118
+ page = await ctx.newPage();
119
+ opened = true;
120
+ }
121
+
122
+ try {
123
+ if (!page.url().includes('affiliate.shopee.vn/')) {
124
+ await page.goto('https://affiliate.shopee.vn/report/click_report', {
125
+ waitUntil: 'domcontentloaded', timeout: timeoutMs,
126
+ }).catch(() => {});
127
+ await sleep(1500);
128
+ }
129
+ if (isBlockedState(page.url(), null)) {
130
+ return { status: 'needs_verify', rows: [], reason: 'session at captcha/login — re-verify via VNC' };
131
+ }
132
+
133
+ const window = reportWindow(days);
134
+ const rows = [];
135
+ let total = null;
136
+
137
+ for (let pageNum = 1; pageNum <= maxPages; pageNum++) {
138
+ const result = await page.evaluate(async (q) => {
139
+ const url = `/api/v1/click_report/list?page_size=${q.pageSize}&page_num=${q.pageNum}`
140
+ + `&click_time_s=${q.start}&click_time_e=${q.end}`;
141
+ try {
142
+ const r = await fetch(url, { credentials: 'include' });
143
+ return { status: r.status, json: await r.json().catch(() => null) };
144
+ } catch (e) {
145
+ return { status: 0, error: String(e?.message || e) };
146
+ }
147
+ }, { pageSize, pageNum, start: window.start, end: window.end });
148
+
149
+ if (isBlockedState(page.url(), result) || result.json?.code === 90309999) {
150
+ return { status: 'needs_verify', rows, reason: `blocked while reading click report (http ${result.status}, code ${result.json?.code})` };
151
+ }
152
+ if (result.status !== 200 || result.json?.code !== 0) {
153
+ return { status: 'failed', rows, reason: result.json?.msg || result.error || `click report HTTP ${result.status}` };
154
+ }
155
+
156
+ const data = result.json?.data || {};
157
+ const batch = Array.isArray(data.list) ? data.list : [];
158
+ total = Number(data.total_count || 0);
159
+ rows.push(...batch.map((row) => ({
160
+ click_id: row.click_id,
161
+ click_time: row.click_time,
162
+ sub_id: row.sub_id,
163
+ click_region: row.click_region,
164
+ referrer: row.referrer,
165
+ direct_source: row.direct_source,
166
+ })));
167
+ if (log) log(`[shopee-report] click trang ${pageNum}: ${batch.length} dòng (${rows.length}/${total})`);
168
+ if (!batch.length || rows.length >= total || batch.length < pageSize) break;
169
+ await sleep(delayMs);
170
+ }
171
+
172
+ if (total != null && rows.length < total) {
173
+ return { status: 'failed', rows, reason: `click pagination stopped at ${rows.length}/${total} rows` };
174
+ }
175
+
176
+ return {
177
+ status: 'ok', rows, total: total ?? rows.length,
178
+ from: new Date(window.start * 1000).toISOString(),
179
+ to: new Date(window.end * 1000).toISOString(),
180
+ };
181
+ } finally {
182
+ if (opened) await page.close().catch(() => {});
183
+ }
184
+ }
185
+
186
+ module.exports = { fetchConversionReport, fetchClickReport, reportWindow };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "channel-worker",
3
- "version": "2.5.118",
3
+ "version": "2.5.119",
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": {