siluzan-tso-cli 1.1.29-beta.9 → 1.1.29
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/README.md +1 -2
- package/dist/index.js +1940 -1029
- package/dist/skill/AGENTS.md +16 -16
- package/dist/skill/SKILL.md +11 -9
- package/dist/skill/_meta.json +2 -2
- package/dist/skill/assets/campaign-create-keyword-test.fixed.json +10 -33
- package/dist/skill/assets/campaign-create-template.json +2 -0
- package/dist/skill/assets/campaign-create-template.md +114 -99
- package/dist/skill/assets/market-analysis-rules.md +55 -55
- package/dist/skill/assets/meta-period-report-rules.md +60 -60
- package/dist/skill/assets/pmax-asset-group-template.json +12 -4
- package/dist/skill/assets/pmax-asset-group-template.md +25 -0
- package/dist/skill/assets/pmax-brand-assets-template.json +25 -0
- package/dist/skill/assets/pmax-brand-assets-template.md +22 -0
- package/dist/skill/assets/pmax-brand-guidelines-enable-template.json +24 -0
- package/dist/skill/assets/pmax-brand-guidelines-enable-template.md +22 -0
- package/dist/skill/assets/pmax-create-template.json +8 -2
- package/dist/skill/assets/pmax-create-template.md +75 -59
- package/dist/skill/assets/pmax-lead-form-template.json +4 -3
- package/dist/skill/assets/pmax-lead-form-template.md +30 -23
- package/dist/skill/assets/pmax-signals-template.json +2 -6
- package/dist/skill/assets/pmax-whatsapp-template.md +10 -10
- package/dist/skill/assets/website-diagnosis-rules.md +67 -67
- package/dist/skill/references/README.md +18 -3
- package/dist/skill/references/accounts/accounts.md +14 -19
- package/dist/skill/references/accounts/finance.md +1 -1
- package/dist/skill/references/accounts/open-account-by-media.md +80 -81
- package/dist/skill/references/accounts/open-account-google-ui.md +25 -25
- package/dist/skill/references/analytics/account-analytics.md +1 -0
- package/dist/skill/references/analytics/facebook-analysis-guide.md +61 -61
- package/dist/skill/references/analytics/google-analysis-batch.md +2 -2
- package/dist/skill/references/analytics/keyword-planner-workflows.md +22 -23
- package/dist/skill/references/analytics/market-analysis-guide.md +31 -25
- package/dist/skill/references/analytics/rag.md +21 -21
- package/dist/skill/references/analytics/reporting.md +9 -9
- package/dist/skill/references/analytics/website-diagnosis-guide.md +19 -30
- package/dist/skill/references/core/agent-conventions.md +43 -13
- package/dist/skill/references/core/cli-enums.md +41 -58
- package/dist/skill/references/core/playbooks.md +11 -9
- package/dist/skill/references/core/setup.md +5 -5
- package/dist/skill/references/core/subagent-orchestration.md +40 -40
- package/dist/skill/references/core/workflows.md +14 -5
- package/dist/skill/references/google-ads/google-ads-campaign-plan.md +38 -24
- package/dist/skill/references/google-ads/google-ads.md +57 -55
- package/dist/skill/references/google-ads/pmax-api.md +89 -47
- package/dist/skill/references/google-ads/rules/README.md +15 -15
- package/dist/skill/references/google-ads/rules/google-ads-account-audit.md +22 -22
- package/dist/skill/references/google-ads/rules/google-ads-compliance.md +27 -27
- package/dist/skill/references/google-ads/rules/google-ads-keyword-strategy.md +15 -15
- package/dist/skill/references/google-ads/rules/google-ads-keyword-taxonomy.md +32 -22
- package/dist/skill/references/google-ads/rules/google-ads-launch-plan-template.md +32 -30
- package/dist/skill/references/google-ads/rules/google-ads-pmax-guide.md +3 -1
- package/dist/skill/references/misc/tso-home.md +10 -10
- package/dist/skill/references/operations/clue.md +1 -1
- package/dist/skill/references/operations/forewarning.md +1 -1
- package/dist/skill/references/operations/hosted-automation-optimize-index.md +2 -2
- package/dist/skill/references/operations/hosted-automation-scenarios.md +5 -5
- package/dist/skill/references/operations/hosted-automation-self-control.md +15 -15
- package/dist/skill/references/operations/hosted-automation-user-catalog.md +13 -13
- package/dist/skill/references/operations/optimize.md +8 -8
- package/dist/skill/references/report-templates/README.md +45 -0
- package/dist/skill/references/report-templates/REPORT-WORKFLOW.md +114 -0
- package/dist/skill/references/report-templates/bing-period-report.md +65 -0
- package/dist/skill/references/report-templates/google-account-diagnosis-report.md +83 -0
- package/dist/skill/references/report-templates/google-ads-diagnosis.md +378 -0
- package/dist/skill/references/report-templates/google-inquiry-analysis.md +543 -0
- package/dist/skill/references/report-templates/google-period-report-excel.md +126 -0
- package/dist/skill/references/report-templates/google-period-report.md +60 -0
- package/dist/skill/references/report-templates/market-analysis-report.md +40 -0
- package/dist/skill/references/report-templates/meta-account-diagnosis-report.md +74 -0
- package/dist/skill/references/report-templates/meta-period-report-excel.md +230 -0
- package/dist/skill/references/report-templates/meta-period-report.md +219 -0
- package/dist/skill/references/report-templates/okki-weekly-google-client.md +217 -0
- package/dist/skill/references/report-templates/tiktok-period-report.md +56 -0
- package/dist/skill/references/report-templates/website-diagnosis-report.md +79 -0
- package/dist/skill/report-templates/README.md +16 -14
- package/dist/skill/report-templates/google-account-diagnosis-report.md +1 -1
- package/dist/skill/report-templates/google-ads-diagnosis.md +21 -21
- package/dist/skill/report-templates/google-inquiry-analysis.md +44 -44
- package/dist/skill/report-templates/google-period-report.md +23 -23
- package/dist/skill/report-templates/market-analysis-report.md +1 -1
- package/dist/skill/report-templates/meta-period-report-excel.md +72 -64
- package/dist/skill/report-templates/meta-period-report.html +705 -427
- package/dist/skill/report-templates/meta-period-report.md +60 -59
- package/dist/skill/report-templates/okki-weekly-google-client.md +26 -26
- package/dist/skill/report-templates/report-template-academic.html +1 -1
- package/dist/skill/report-templates/report-template-dark.html +1 -1
- package/dist/skill/report-templates/report-template-formal.html +1 -1
- package/dist/skill/report-templates/report-template-mobile.html +1 -1
- package/dist/skill/report-templates/report-template-onepager.html +1 -1
- package/dist/skill/report-templates/report-template-print.html +1 -1
- package/dist/skill/report-templates/report-template.html +1 -1
- package/dist/skill/report-templates/website-diagnosis-report.html +1731 -1653
- package/dist/skill/report-templates/website-diagnosis-report.md +21 -23
- package/dist/skill/scripts/install.ps1 +3 -3
- package/dist/skill/scripts/install.sh +3 -3
- package/dist/skill/snippets/handoff-p7-inquiry.md +5 -5
- package/eval/cases/accounts-entityid-vs-mediaccustomerid.scenario.json +2 -14
- package/eval/cases/accounts-mcc-bind-inquiry.scenario.json +1 -3
- package/eval/cases/accounts-single-balance-not-bulk.scenario.json +3 -14
- package/eval/cases/budget-display-not-raw-micros.scenario.json +1 -8
- package/eval/cases/clue-meta-leads-json.scenario.json +2 -14
- package/eval/cases/clue-tiktok-leads-json.scenario.json +2 -11
- package/eval/cases/destructive-account-delink-needs-confirm.scenario.json +3 -9
- package/eval/cases/destructive-forewarning-delete-needs-confirm.scenario.json +3 -9
- package/eval/cases/destructive-invoice-apply-needs-confirm.scenario.json +3 -9
- package/eval/cases/facebook-analysis-google-section-aliases.scenario.json +2 -11
- package/eval/cases/facebook-analysis-not-google-keywords.scenario.json +3 -10
- package/eval/cases/facebook-analysis-period-default.scenario.json +2 -14
- package/eval/cases/finance-invoice-info-list.scenario.json +3 -11
- package/eval/cases/forewarning-list-google.scenario.json +3 -14
- package/eval/cases/google-ads-no-structural-without-confirm.scenario.json +2 -6
- package/eval/cases/google-analysis-keywords-route.scenario.json +2 -14
- package/eval/cases/human-p1-multiturn.scenario.json +1 -5
- package/eval/cases/meta-single-balance-not-bulk.scenario.json +3 -17
- package/eval/cases/no-legacy-json-flag.scenario.json +2 -6
- package/eval/cases/open-account-google-noninteractive.scenario.json +1 -3
- package/eval/cases/open-account-tiktok-license-file.scenario.json +1 -3
- package/eval/cases/optimize-list-by-account.scenario.json +3 -11
- package/eval/cases/p1-single-account-profile.scenario.json +1 -11
- package/eval/cases/p2-balance-scan-bulk.scenario.json +2 -9
- package/eval/cases/p3-accounts-digest.scenario.json +1 -5
- package/eval/cases/p4-fb-meta-period-report.scenario.json +2 -12
- package/eval/cases/p4-period-report-window.scenario.json +1 -8
- package/eval/cases/pmax-asset-group-create-with-bg.scenario.json +15 -0
- package/eval/cases/pmax-brand-edit-routing.scenario.json +12 -0
- package/eval/cases/pmax-edit-not-campaign-edit.scenario.json +15 -0
- package/eval/cases/pmax-enable-brand-guidelines.scenario.json +15 -0
- package/eval/cases/pmax-no-assets-update-brand-fields.scenario.json +12 -0
- package/eval/cases/rag-before-keyword-expand.scenario.json +1 -11
- package/eval/cases/rag-list-then-query.scenario.json +2 -14
- package/eval/cases/report-list-google.scenario.json +2 -11
- package/eval/cases/report-push-list-google.scenario.json +2 -11
- package/eval/cases/setup-login-or-env.scenario.json +1 -3
- package/eval/cases/setup-siluzan-data-permission-env.scenario.json +1 -3
- package/eval/cases/tiktok-bc-bind-inquiry.scenario.json +2 -6
- package/eval/cases/time-range-user-delegates-default.scenario.json +1 -8
- package/eval/cases/tips-json-out-filtering.scenario.json +1 -3
- package/eval/cases/tips-large-json-pagination.scenario.json +1 -3
- package/eval/cases/uj-ad-campaign-validate-before-create-stub.scenario.json +2 -11
- package/eval/cases/uj-ad-outdoor-campgear-search-plan.scenario.json +1 -3
- package/eval/cases/uj-ad-website-search-plan-table-format.scenario.json +13 -0
- package/eval/cases/uj-analytics-30d-pdf-campaign-device-geo.scenario.json +6 -18
- package/eval/cases/uj-analytics-compare-google-tiktok-last-month-roi.scenario.json +1 -8
- package/eval/cases/uj-analytics-google-weekly-trends-campaigns-keywords.scenario.json +2 -11
- package/eval/cases/uj-analytics-report-push-weekly-email.scenario.json +1 -3
- package/eval/cases/uj-finance-invoice-records-this-month.scenario.json +2 -11
- package/eval/cases/uj-life-newbie-siluzan-google-end-to-end.scenario.json +1 -4
- package/eval/cases/uj-ops-google-accounts-list-normal.scenario.json +2 -17
- package/eval/cases/uj-ops-google-yesterday-spend-conversions.scenario.json +2 -14
- package/eval/cases/uj-ops-pause-worst-adgroup-confirm.scenario.json +2 -6
- package/eval/cases/uj-ops-tiktok-leads-last-week.scenario.json +3 -17
- package/eval/cases/uj-patrol-cpc-spike-adgroups-over-15.scenario.json +1 -5
- package/eval/cases/uj-patrol-forewarning-create-daily-cap-3000.scenario.json +1 -3
- package/eval/cases/uj-patrol-forewarning-trigger-records.scenario.json +3 -17
- package/eval/cases/uj-patrol-google-balances-low.scenario.json +2 -11
- package/eval/cases/uj-roi-optimize-records-then-execute-cautiously.scenario.json +3 -14
- package/eval/cases/uj-roi-search-terms-add-negative-keywords.scenario.json +2 -14
- package/eval/stub-fixtures/facebook-analysis.json +24 -4
- package/eval/stub-fixtures/meta-overview.json +4 -1
- package/eval/stub-fixtures/pmax-asset-group-create-ok.json +12 -0
- package/eval/stub-fixtures/pmax-brand-assets-edit-ok.json +11 -0
- package/eval/stub-fixtures/pmax-brand-guidelines-enable-ok.json +11 -0
- package/eval/stub-fixtures/pmax-edit-ok.json +11 -0
- package/eval/stub-fixtures/pmax-get-bg-on.json +20 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1976,10 +1976,14 @@ import * as path2 from "path";
|
|
|
1976
1976
|
import { fileURLToPath } from "url";
|
|
1977
1977
|
import { spawn } from "child_process";
|
|
1978
1978
|
import Table from "cli-table3";
|
|
1979
|
+
import * as fs3 from "fs/promises";
|
|
1979
1980
|
import * as path3 from "path";
|
|
1980
|
-
import
|
|
1981
|
+
import * as fs4 from "fs/promises";
|
|
1981
1982
|
import * as path4 from "path";
|
|
1982
|
-
import
|
|
1983
|
+
import * as path5 from "path";
|
|
1984
|
+
import { chmod, mkdir as mkdir3, open, readdir, stat, unlink } from "fs/promises";
|
|
1985
|
+
import * as path6 from "path";
|
|
1986
|
+
import { readdir as readdir2, readFile as readFile3 } from "fs/promises";
|
|
1983
1987
|
function atomicWriteFileSync(targetPath, content, encoding = "utf8") {
|
|
1984
1988
|
const dir = path.dirname(targetPath);
|
|
1985
1989
|
const tmp = path.join(
|
|
@@ -2130,7 +2134,7 @@ function rawRequest(url, options) {
|
|
|
2130
2134
|
const perfOn = isPerfEnabled();
|
|
2131
2135
|
const t0 = perfOn ? performance.now() : 0;
|
|
2132
2136
|
const method = options.method ?? "GET";
|
|
2133
|
-
return new Promise((
|
|
2137
|
+
return new Promise((resolve52, reject) => {
|
|
2134
2138
|
const parsed = new URL(url);
|
|
2135
2139
|
const transport = parsed.protocol === "https:" ? https : http;
|
|
2136
2140
|
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
@@ -2215,7 +2219,7 @@ function rawRequest(url, options) {
|
|
|
2215
2219
|
ok: status >= 200 && status < 300
|
|
2216
2220
|
});
|
|
2217
2221
|
}
|
|
2218
|
-
|
|
2222
|
+
resolve52({ status, text: data, headers });
|
|
2219
2223
|
});
|
|
2220
2224
|
});
|
|
2221
2225
|
req.on("timeout", () => {
|
|
@@ -2339,7 +2343,7 @@ function decodeJwtSub(token) {
|
|
|
2339
2343
|
return decodeJwtClaims(token)?.sub ?? null;
|
|
2340
2344
|
}
|
|
2341
2345
|
function sleep(ms) {
|
|
2342
|
-
return new Promise((
|
|
2346
|
+
return new Promise((resolve52) => setTimeout(resolve52, ms));
|
|
2343
2347
|
}
|
|
2344
2348
|
async function pollUntil(fn, intervalMs, timeoutMs) {
|
|
2345
2349
|
const deadline = Date.now() + timeoutMs;
|
|
@@ -2389,20 +2393,20 @@ async function defaultRunMinRequiredGlobalInstall(ctx) {
|
|
|
2389
2393
|
return { ok: false, stderr: "SILUZAN_SKIP_AUTO_GLOBAL_INSTALL=1" };
|
|
2390
2394
|
}
|
|
2391
2395
|
const spec = `${ctx.pkgName}@${ctx.tag}`;
|
|
2392
|
-
return await new Promise((
|
|
2396
|
+
return await new Promise((resolve52) => {
|
|
2393
2397
|
const child = spawn("npm", ["install", "-g", spec, "--no-fund", "--no-audit"], {
|
|
2394
2398
|
stdio: "inherit",
|
|
2395
2399
|
shell: true
|
|
2396
2400
|
});
|
|
2397
2401
|
child.on("error", (err) => {
|
|
2398
|
-
|
|
2402
|
+
resolve52({ ok: false, stderr: err instanceof Error ? err.message : String(err) });
|
|
2399
2403
|
});
|
|
2400
2404
|
child.on("close", (code, signal) => {
|
|
2401
2405
|
if (code === 0) {
|
|
2402
|
-
|
|
2406
|
+
resolve52({ ok: true });
|
|
2403
2407
|
} else {
|
|
2404
2408
|
const sig = signal ? ` signal=${signal}` : "";
|
|
2405
|
-
|
|
2409
|
+
resolve52({ ok: false, stderr: `exit code ${code}${sig}` });
|
|
2406
2410
|
}
|
|
2407
2411
|
});
|
|
2408
2412
|
});
|
|
@@ -2545,6 +2549,260 @@ function printCliTableFromUnknown(items, columns, options) {
|
|
|
2545
2549
|
});
|
|
2546
2550
|
printCliTable(rows, columns, options);
|
|
2547
2551
|
}
|
|
2552
|
+
function resolveSnapshotDir(snapshotDir) {
|
|
2553
|
+
const trimmed = snapshotDir.trim();
|
|
2554
|
+
if (!trimmed) {
|
|
2555
|
+
throw new Error("\u5FEB\u7167\u76EE\u5F55\u4E0D\u80FD\u4E3A\u7A7A");
|
|
2556
|
+
}
|
|
2557
|
+
if (trimmed.includes("\0")) {
|
|
2558
|
+
throw new Error("\u5FEB\u7167\u76EE\u5F55\u8DEF\u5F84\u975E\u6CD5");
|
|
2559
|
+
}
|
|
2560
|
+
return path3.resolve(trimmed);
|
|
2561
|
+
}
|
|
2562
|
+
async function ensureSnapshotDir(snapshotDir) {
|
|
2563
|
+
const abs = resolveSnapshotDir(snapshotDir);
|
|
2564
|
+
await fs3.mkdir(abs, { recursive: true });
|
|
2565
|
+
return abs;
|
|
2566
|
+
}
|
|
2567
|
+
function configureCliJsonSnapshot(opts) {
|
|
2568
|
+
if (opts.cliPackage) _cliPackage = opts.cliPackage;
|
|
2569
|
+
if (opts.resolveVersion) _resolveCliVersion = opts.resolveVersion;
|
|
2570
|
+
if (opts.fieldGuideRefs && opts.fieldGuideRefs.length) _fieldGuideRefs = opts.fieldGuideRefs;
|
|
2571
|
+
}
|
|
2572
|
+
function configuredCliPackage() {
|
|
2573
|
+
return _cliPackage;
|
|
2574
|
+
}
|
|
2575
|
+
function configuredCliVersion() {
|
|
2576
|
+
try {
|
|
2577
|
+
return _resolveCliVersion();
|
|
2578
|
+
} catch {
|
|
2579
|
+
return "0.0.0";
|
|
2580
|
+
}
|
|
2581
|
+
}
|
|
2582
|
+
function slugifyCliSnapshotSectionKey(section) {
|
|
2583
|
+
return section.trim().toLowerCase().replace(/[^a-z0-9_-]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
2584
|
+
}
|
|
2585
|
+
function slugifyIdSuffix(id) {
|
|
2586
|
+
if (id === void 0 || id === null) return "";
|
|
2587
|
+
return slugifyCliSnapshotSectionKey(String(id));
|
|
2588
|
+
}
|
|
2589
|
+
function applyIdSuffix(stem, idSuffix) {
|
|
2590
|
+
const slug = slugifyIdSuffix(idSuffix);
|
|
2591
|
+
if (!slug) return stem;
|
|
2592
|
+
if (stem === slug || stem.endsWith(`-${slug}`)) return stem;
|
|
2593
|
+
return `${stem}-${slug}`;
|
|
2594
|
+
}
|
|
2595
|
+
function normalizeCliSnapshotSection(section) {
|
|
2596
|
+
const slug = slugifyCliSnapshotSectionKey(section);
|
|
2597
|
+
if (!slug || !/^[a-z0-9_-]+$/.test(slug)) {
|
|
2598
|
+
throw new Error(`\u975E\u6CD5 section\uFF0C\u65E0\u6CD5\u751F\u6210\u6587\u4EF6\u540D\uFF1A${section}`);
|
|
2599
|
+
}
|
|
2600
|
+
return slug;
|
|
2601
|
+
}
|
|
2602
|
+
function cliManifestFileName(idSuffix) {
|
|
2603
|
+
return `${applyIdSuffix("cli-manifest", idSuffix)}.json`;
|
|
2604
|
+
}
|
|
2605
|
+
async function readCliManifestIfExists(absDir, idSuffix) {
|
|
2606
|
+
const candidates = [cliManifestFileName(idSuffix)];
|
|
2607
|
+
if (idSuffix && candidates[0] !== CLI_SNAPSHOT_MANIFEST_FILE) {
|
|
2608
|
+
candidates.push(CLI_SNAPSHOT_MANIFEST_FILE);
|
|
2609
|
+
}
|
|
2610
|
+
for (const name2 of candidates) {
|
|
2611
|
+
const manifestPath = path4.join(absDir, name2);
|
|
2612
|
+
try {
|
|
2613
|
+
const raw = await fs4.readFile(manifestPath, "utf8");
|
|
2614
|
+
const parsed = JSON.parse(raw);
|
|
2615
|
+
if (parsed?.schemaVersion !== SCHEMA_VERSION || parsed.kind !== "siluzan-cli-json-snapshot" || !Array.isArray(parsed.artifacts)) {
|
|
2616
|
+
continue;
|
|
2617
|
+
}
|
|
2618
|
+
return parsed;
|
|
2619
|
+
} catch {
|
|
2620
|
+
continue;
|
|
2621
|
+
}
|
|
2622
|
+
}
|
|
2623
|
+
return null;
|
|
2624
|
+
}
|
|
2625
|
+
function mergeCliSnapshotManifest(existing, params) {
|
|
2626
|
+
const prevArts = existing?.artifacts ?? [];
|
|
2627
|
+
const key = slugifyCliSnapshotSectionKey(params.newArtifact.section);
|
|
2628
|
+
const without = prevArts.filter((a) => slugifyCliSnapshotSectionKey(a.section) !== key);
|
|
2629
|
+
const artifacts = [...without, params.newArtifact];
|
|
2630
|
+
const fieldGuide = existing?.fieldGuide ?? { markdownRefs: [..._fieldGuideRefs] };
|
|
2631
|
+
return {
|
|
2632
|
+
schemaVersion: SCHEMA_VERSION,
|
|
2633
|
+
kind: "siluzan-cli-json-snapshot",
|
|
2634
|
+
cliPackage: params.cliPackage ?? existing?.cliPackage ?? configuredCliPackage(),
|
|
2635
|
+
cliVersion: params.cliVersion,
|
|
2636
|
+
fieldGuide: { markdownRefs: [...fieldGuide.markdownRefs] },
|
|
2637
|
+
artifacts
|
|
2638
|
+
};
|
|
2639
|
+
}
|
|
2640
|
+
function rejectDeprecatedJsonFlag(json, commandLabel) {
|
|
2641
|
+
if (json) {
|
|
2642
|
+
console.error(
|
|
2643
|
+
`
|
|
2644
|
+
\u274C ${commandLabel}\uFF1A--json \u5DF2\u79FB\u9664\u3002\u8BF7\u4F7F\u7528 --json-out <\u76EE\u5F55\u6216 *.json \u6587\u4EF6> \u843D\u76D8\u7ED3\u6784\u5316\u6570\u636E\uFF08stdout \u4EC5\u4E00\u884C\u6458\u8981\uFF09\u3002
|
|
2645
|
+
`
|
|
2646
|
+
);
|
|
2647
|
+
process.exit(1);
|
|
2648
|
+
}
|
|
2649
|
+
}
|
|
2650
|
+
function tsLikeQuoteKey(key) {
|
|
2651
|
+
if (/^[A-Za-z_$][\w$]*$/.test(key)) return key;
|
|
2652
|
+
return JSON.stringify(key);
|
|
2653
|
+
}
|
|
2654
|
+
function describePayloadTsLike(value) {
|
|
2655
|
+
const visiting = /* @__PURE__ */ new WeakSet();
|
|
2656
|
+
const memoDone = /* @__PURE__ */ new WeakSet();
|
|
2657
|
+
function tsScalar(v) {
|
|
2658
|
+
if (v === null) return "null";
|
|
2659
|
+
if (v === void 0) return "undefined";
|
|
2660
|
+
if (typeof v === "boolean") return "boolean";
|
|
2661
|
+
if (typeof v === "number") return "number";
|
|
2662
|
+
if (typeof v === "string") return "string";
|
|
2663
|
+
if (typeof v === "bigint") return "bigint";
|
|
2664
|
+
if (typeof v !== "object") return "unknown";
|
|
2665
|
+
return "";
|
|
2666
|
+
}
|
|
2667
|
+
function ts(v) {
|
|
2668
|
+
const prim = tsScalar(v);
|
|
2669
|
+
if (prim) return prim;
|
|
2670
|
+
if (Array.isArray(v)) {
|
|
2671
|
+
if (v.length === 0) return "unknown[]";
|
|
2672
|
+
const parts = [];
|
|
2673
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2674
|
+
const n = Math.min(v.length, TS_LIKE_ARRAY_SAMPLE_MAX);
|
|
2675
|
+
for (let i = 0; i < n; i++) {
|
|
2676
|
+
const t = ts(v[i]);
|
|
2677
|
+
if (seen.has(t)) continue;
|
|
2678
|
+
seen.add(t);
|
|
2679
|
+
parts.push(t);
|
|
2680
|
+
}
|
|
2681
|
+
const inner = parts.length === 1 ? parts[0] : `(${parts.join(" | ")})`;
|
|
2682
|
+
return `${inner}[]`;
|
|
2683
|
+
}
|
|
2684
|
+
const o = v;
|
|
2685
|
+
if (visiting.has(o)) return "any";
|
|
2686
|
+
if (memoDone.has(o)) return "any";
|
|
2687
|
+
visiting.add(o);
|
|
2688
|
+
const props = [];
|
|
2689
|
+
for (const k of Object.keys(o)) {
|
|
2690
|
+
const child = o[k];
|
|
2691
|
+
if (child === void 0) {
|
|
2692
|
+
props.push(`${tsLikeQuoteKey(k)}: undefined`);
|
|
2693
|
+
continue;
|
|
2694
|
+
}
|
|
2695
|
+
props.push(`${tsLikeQuoteKey(k)}: ${ts(child)}`);
|
|
2696
|
+
}
|
|
2697
|
+
visiting.delete(o);
|
|
2698
|
+
memoDone.add(o);
|
|
2699
|
+
return `{ ${props.join("; ")} }`;
|
|
2700
|
+
}
|
|
2701
|
+
return ts(value);
|
|
2702
|
+
}
|
|
2703
|
+
function isJsonFilePath(p) {
|
|
2704
|
+
return p.trim().toLowerCase().endsWith(".json");
|
|
2705
|
+
}
|
|
2706
|
+
function snapshotOutlineFileName(jsonBasename) {
|
|
2707
|
+
const lower = jsonBasename.trim().toLowerCase();
|
|
2708
|
+
if (!lower.endsWith(".json")) {
|
|
2709
|
+
throw new Error(`\u5FEB\u7167 JSON \u6587\u4EF6\u540D\u987B\u4EE5 .json \u7ED3\u5C3E\uFF1A${jsonBasename}`);
|
|
2710
|
+
}
|
|
2711
|
+
return `${jsonBasename.slice(0, -".json".length)}.outline.txt`;
|
|
2712
|
+
}
|
|
2713
|
+
function formatOutlineFileBody(jsonBasename, payload, extraHeaderCommentLines) {
|
|
2714
|
+
const typeText = describePayloadTsLike(payload);
|
|
2715
|
+
const lines = [
|
|
2716
|
+
`// outline of \`${jsonBasename}\` \u2014 schema-only, NOT the data.`,
|
|
2717
|
+
`// \u7528\u6CD5\uFF1A\u5904\u7406\u540C\u76EE\u5F55 \`${jsonBasename}\` \u7684\u6570\u636E\u524D\uFF0C\u5148\u8BFB\u672C\u6587\u4EF6\u4E86\u89E3\u5B57\u6BB5\u7C7B\u578B\uFF0C\u518D\u7528\u811A\u672C\uFF08fs.readFileSync / require\uFF09\u8BFB\u53D6\u8BE5 JSON \u505A\u7B5B\u9009\u4E0E\u805A\u5408\u3002`,
|
|
2718
|
+
`// \u7C7B\u578B\u7531 JSON \u63A8\u65AD\uFF1A\u6570\u7EC4\u53D6\u524D 8 \u9879\u53BB\u91CD\u5E76\u96C6\uFF1B\u73AF\u6216\u91CD\u590D\u5BF9\u8C61\u5F15\u7528\u8BB0\u4E3A any\u3002`,
|
|
2719
|
+
...extraHeaderCommentLines ?? [],
|
|
2720
|
+
typeText
|
|
2721
|
+
];
|
|
2722
|
+
return lines.join("\n");
|
|
2723
|
+
}
|
|
2724
|
+
async function writeCliJsonSnapshot(params) {
|
|
2725
|
+
const trimmed = params.snapshotDir.trim();
|
|
2726
|
+
const idSlug = slugifyIdSuffix(params.idSuffix);
|
|
2727
|
+
const manifestFile = cliManifestFileName(idSlug || void 0);
|
|
2728
|
+
let absDir;
|
|
2729
|
+
let fileName;
|
|
2730
|
+
let section;
|
|
2731
|
+
if (isJsonFilePath(trimmed)) {
|
|
2732
|
+
const absFile = path4.resolve(trimmed);
|
|
2733
|
+
absDir = path4.dirname(absFile);
|
|
2734
|
+
if (absDir.includes("\0") || absFile.includes("\0")) {
|
|
2735
|
+
throw new Error("\u5FEB\u7167\u8DEF\u5F84\u975E\u6CD5");
|
|
2736
|
+
}
|
|
2737
|
+
await fs4.mkdir(absDir, { recursive: true });
|
|
2738
|
+
fileName = path4.basename(absFile);
|
|
2739
|
+
if (!fileName.toLowerCase().endsWith(".json")) {
|
|
2740
|
+
throw new Error(`--json-out \u6587\u4EF6\u8DEF\u5F84\u987B\u4EE5 .json \u7ED3\u5C3E\uFF1A${trimmed}`);
|
|
2741
|
+
}
|
|
2742
|
+
if (fileName.toLowerCase() === manifestFile.toLowerCase()) {
|
|
2743
|
+
throw new Error(`\u8F93\u51FA\u6587\u4EF6\u4E0D\u80FD\u547D\u540D\u4E3A ${manifestFile}\uFF0C\u4E0E\u6E05\u5355\u6587\u4EF6\u51B2\u7A81`);
|
|
2744
|
+
}
|
|
2745
|
+
const base = fileName.slice(0, -".json".length);
|
|
2746
|
+
section = normalizeCliSnapshotSection(base || params.section);
|
|
2747
|
+
} else {
|
|
2748
|
+
absDir = await ensureSnapshotDir(trimmed);
|
|
2749
|
+
section = normalizeCliSnapshotSection(params.section);
|
|
2750
|
+
const stem = applyIdSuffix(section, idSlug || void 0);
|
|
2751
|
+
fileName = `${stem}.json`;
|
|
2752
|
+
}
|
|
2753
|
+
const writtenAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2754
|
+
const absFilePath = path4.join(absDir, fileName);
|
|
2755
|
+
await fs4.writeFile(absFilePath, `${JSON.stringify(params.payload, null, 2)}
|
|
2756
|
+
`, "utf8");
|
|
2757
|
+
const outlineFileName = snapshotOutlineFileName(fileName);
|
|
2758
|
+
if (outlineFileName.toLowerCase() === manifestFile.toLowerCase()) {
|
|
2759
|
+
throw new Error(`\u975E\u6CD5\u8F93\u51FA\uFF1A${outlineFileName} \u4E0E\u6E05\u5355\u6587\u4EF6\u540D\u51B2\u7A81`);
|
|
2760
|
+
}
|
|
2761
|
+
const outlineBody = formatOutlineFileBody(fileName, params.payload);
|
|
2762
|
+
await fs4.writeFile(path4.join(absDir, outlineFileName), `${outlineBody}
|
|
2763
|
+
`, "utf8");
|
|
2764
|
+
const existing = await readCliManifestIfExists(absDir, idSlug || void 0);
|
|
2765
|
+
const newArtifact = {
|
|
2766
|
+
section,
|
|
2767
|
+
file: fileName,
|
|
2768
|
+
writtenAt,
|
|
2769
|
+
...params.commandHint ? { commandHint: params.commandHint } : {}
|
|
2770
|
+
};
|
|
2771
|
+
const merged = mergeCliSnapshotManifest(existing, {
|
|
2772
|
+
cliVersion: params.cliVersion ?? configuredCliVersion(),
|
|
2773
|
+
newArtifact
|
|
2774
|
+
});
|
|
2775
|
+
await fs4.writeFile(
|
|
2776
|
+
path4.join(absDir, manifestFile),
|
|
2777
|
+
`${JSON.stringify(merged, null, 2)}
|
|
2778
|
+
`,
|
|
2779
|
+
"utf8"
|
|
2780
|
+
);
|
|
2781
|
+
return {
|
|
2782
|
+
kind: "siluzan-tso-cli-json-snapshot",
|
|
2783
|
+
schemaVersion: SCHEMA_VERSION,
|
|
2784
|
+
absoluteSnapshotDir: absDir,
|
|
2785
|
+
manifestFile,
|
|
2786
|
+
writtenFiles: [fileName, outlineFileName],
|
|
2787
|
+
section,
|
|
2788
|
+
outlineFile: outlineFileName,
|
|
2789
|
+
agentHint: OUTLINE_AGENT_HINT
|
|
2790
|
+
};
|
|
2791
|
+
}
|
|
2792
|
+
async function emitCliJsonOrSnapshot(opts, params) {
|
|
2793
|
+
if (opts.jsonOut) {
|
|
2794
|
+
const summary = await writeCliJsonSnapshot({
|
|
2795
|
+
snapshotDir: opts.jsonOut,
|
|
2796
|
+
section: params.section,
|
|
2797
|
+
payload: params.payload,
|
|
2798
|
+
commandHint: params.commandHint,
|
|
2799
|
+
idSuffix: params.idSuffix
|
|
2800
|
+
});
|
|
2801
|
+
console.log(JSON.stringify(summary));
|
|
2802
|
+
return true;
|
|
2803
|
+
}
|
|
2804
|
+
return false;
|
|
2805
|
+
}
|
|
2548
2806
|
function installProcessHandlers() {
|
|
2549
2807
|
process.on("uncaughtException", (err) => {
|
|
2550
2808
|
console.error(`
|
|
@@ -2729,15 +2987,15 @@ function sanitizeNamespace(ns) {
|
|
|
2729
2987
|
function getWriteAuditRootDir(namespace) {
|
|
2730
2988
|
const ns = sanitizeNamespace(namespace);
|
|
2731
2989
|
const base = process.env.SILUZAN_WRITE_AUDIT_BASE?.trim();
|
|
2732
|
-
if (base) return
|
|
2990
|
+
if (base) return path5.join(path5.resolve(base), ns);
|
|
2733
2991
|
if (ns === "tso") {
|
|
2734
2992
|
const legacyOverride = process.env.SILUZAN_TSO_AUDIT_DIR?.trim();
|
|
2735
|
-
if (legacyOverride) return
|
|
2993
|
+
if (legacyOverride) return path5.resolve(legacyOverride);
|
|
2736
2994
|
}
|
|
2737
|
-
return
|
|
2995
|
+
return path5.join(SILUZAN_DIR, "write-audit", ns);
|
|
2738
2996
|
}
|
|
2739
2997
|
function getWriteAuditSnapshotsDir(namespace) {
|
|
2740
|
-
return
|
|
2998
|
+
return path5.join(getWriteAuditRootDir(namespace), "snapshots");
|
|
2741
2999
|
}
|
|
2742
3000
|
function collectReadRootsForNamespace(namespace) {
|
|
2743
3001
|
const primary = getWriteAuditRootDir(namespace);
|
|
@@ -2746,8 +3004,8 @@ function collectReadRootsForNamespace(namespace) {
|
|
|
2746
3004
|
if (process.env.SILUZAN_WRITE_AUDIT_BASE?.trim() || process.env.SILUZAN_TSO_AUDIT_DIR?.trim()) {
|
|
2747
3005
|
return roots;
|
|
2748
3006
|
}
|
|
2749
|
-
const legacyFlat =
|
|
2750
|
-
if (
|
|
3007
|
+
const legacyFlat = path5.join(SILUZAN_DIR, "tso-write-audit");
|
|
3008
|
+
if (path5.resolve(legacyFlat) !== path5.resolve(primary)) roots.push(legacyFlat);
|
|
2751
3009
|
return roots;
|
|
2752
3010
|
}
|
|
2753
3011
|
function formatBeijingYmd(d) {
|
|
@@ -2799,8 +3057,8 @@ function parseAuditLogFilename(name2) {
|
|
|
2799
3057
|
return { day: m[1], segment: m[2] ? Number.parseInt(m[2], 10) : 0 };
|
|
2800
3058
|
}
|
|
2801
3059
|
function compareAuditLogPaths(a, b) {
|
|
2802
|
-
const ba = parseAuditLogFilename(
|
|
2803
|
-
const bb = parseAuditLogFilename(
|
|
3060
|
+
const ba = parseAuditLogFilename(path5.basename(a));
|
|
3061
|
+
const bb = parseAuditLogFilename(path5.basename(b));
|
|
2804
3062
|
if (!ba || !bb) return a.localeCompare(b);
|
|
2805
3063
|
if (ba.day !== bb.day) return ba.day.localeCompare(bb.day);
|
|
2806
3064
|
return ba.segment - bb.segment;
|
|
@@ -2829,16 +3087,16 @@ async function safeStatSizeBytes(filePath) {
|
|
|
2829
3087
|
}
|
|
2830
3088
|
async function resolveWritableAuditLogPath(root, dayYmd) {
|
|
2831
3089
|
const maxBytes = getWriteAuditMaxSegmentBytes();
|
|
2832
|
-
const primary =
|
|
3090
|
+
const primary = path5.join(root, `${dayYmd}.jsonl`);
|
|
2833
3091
|
if (await safeStatSizeBytes(primary) < maxBytes) return primary;
|
|
2834
3092
|
let seq = 1;
|
|
2835
3093
|
const maxSeq = 999;
|
|
2836
3094
|
while (seq <= maxSeq) {
|
|
2837
|
-
const p =
|
|
3095
|
+
const p = path5.join(root, `${dayYmd}__${seq}.jsonl`);
|
|
2838
3096
|
if (await safeStatSizeBytes(p) < maxBytes) return p;
|
|
2839
3097
|
seq++;
|
|
2840
3098
|
}
|
|
2841
|
-
return
|
|
3099
|
+
return path5.join(root, `${dayYmd}__${maxSeq}.jsonl`);
|
|
2842
3100
|
}
|
|
2843
3101
|
async function pruneExpiredWriteAuditFiles(namespace) {
|
|
2844
3102
|
const retention = getWriteAuditRetentionCalendarDays();
|
|
@@ -2857,7 +3115,7 @@ async function pruneExpiredWriteAuditFiles(namespace) {
|
|
|
2857
3115
|
if (!parsed) continue;
|
|
2858
3116
|
if (parsed.day >= oldestKeptDay) continue;
|
|
2859
3117
|
try {
|
|
2860
|
-
await unlink(
|
|
3118
|
+
await unlink(path5.join(root, n));
|
|
2861
3119
|
deleted++;
|
|
2862
3120
|
} catch {
|
|
2863
3121
|
}
|
|
@@ -2915,8 +3173,8 @@ function prepareBodySnippet(body) {
|
|
|
2915
3173
|
async function appendJsonlLine(filePath, record) {
|
|
2916
3174
|
const line = `${JSON.stringify(record)}
|
|
2917
3175
|
`;
|
|
2918
|
-
const dir =
|
|
2919
|
-
await
|
|
3176
|
+
const dir = path5.dirname(filePath);
|
|
3177
|
+
await mkdir3(dir, { recursive: true });
|
|
2920
3178
|
let fh;
|
|
2921
3179
|
try {
|
|
2922
3180
|
fh = await open(filePath, "a", 384);
|
|
@@ -3002,7 +3260,7 @@ async function readWriteAuditRecordsSince(namespace, sinceBeijingDayYmd) {
|
|
|
3002
3260
|
const parsed = parseAuditLogFilename(n);
|
|
3003
3261
|
if (!parsed) continue;
|
|
3004
3262
|
if (parsed.day >= sinceBeijingDayYmd && parsed.day <= untilDay) {
|
|
3005
|
-
filePaths.add(
|
|
3263
|
+
filePaths.add(path6.join(root, n));
|
|
3006
3264
|
}
|
|
3007
3265
|
}
|
|
3008
3266
|
}
|
|
@@ -3011,7 +3269,7 @@ async function readWriteAuditRecordsSince(namespace, sinceBeijingDayYmd) {
|
|
|
3011
3269
|
for (const file of sortedFiles) {
|
|
3012
3270
|
let raw;
|
|
3013
3271
|
try {
|
|
3014
|
-
raw = await
|
|
3272
|
+
raw = await readFile3(file, "utf8");
|
|
3015
3273
|
} catch (e) {
|
|
3016
3274
|
const code = e.code;
|
|
3017
3275
|
if (code === "ENOENT") continue;
|
|
@@ -3048,14 +3306,14 @@ async function findWriteAuditRecordByAuditId(namespace, auditId, maxCalendarDays
|
|
|
3048
3306
|
async function readPreSnapshotPayload(namespace, preSnapshotRef) {
|
|
3049
3307
|
const ref = preSnapshotRef.trim().replace(/\\/g, "/");
|
|
3050
3308
|
if (!ref || ref.includes("..") || !ref.startsWith("snapshots/")) return null;
|
|
3051
|
-
const root =
|
|
3052
|
-
const abs =
|
|
3053
|
-
const snapRoot =
|
|
3054
|
-
const rel =
|
|
3055
|
-
if (rel.startsWith("..") ||
|
|
3309
|
+
const root = path6.resolve(getWriteAuditRootDir(namespace));
|
|
3310
|
+
const abs = path6.resolve(path6.join(root, ...ref.split("/")));
|
|
3311
|
+
const snapRoot = path6.resolve(path6.join(root, "snapshots"));
|
|
3312
|
+
const rel = path6.relative(snapRoot, abs);
|
|
3313
|
+
if (rel.startsWith("..") || path6.isAbsolute(rel)) return null;
|
|
3056
3314
|
let raw;
|
|
3057
3315
|
try {
|
|
3058
|
-
raw = await
|
|
3316
|
+
raw = await readFile3(abs, "utf8");
|
|
3059
3317
|
} catch {
|
|
3060
3318
|
return null;
|
|
3061
3319
|
}
|
|
@@ -3278,7 +3536,7 @@ async function issueApiKeyWithPhoneCode(opts) {
|
|
|
3278
3536
|
});
|
|
3279
3537
|
return apiKey;
|
|
3280
3538
|
}
|
|
3281
|
-
var import_semver, SILUZAN_DIR, CONFIG_FILE, inProcessLockChain, ALLOWED_HOSTNAME_SUFFIXES, DEFAULT_TIMEOUT_MS, MAX_RESPONSE_BYTES, httpsAgent, httpAgent, PERF_PREFIX, ASCII_TABLE_CHARS, MAX_INVOCATION_CHARS, currentInvocation, MAX_COMMIT_CHARS, cliCommitOverride, WRITE_AUDIT_SCHEMA_VERSION, MUTATING, BEIJING_TZ, AUDIT_LOG_FILENAME_RE, DEFAULT_MAX_FILE_MB, MIN_SEGMENT_BYTES, MAX_SEGMENT_BYTES, DEFAULT_RETENTION_DAYS, MIN_RETENTION_DAYS, MAX_RETENTION_DAYS, PRUNE_THROTTLE_MS, lastPruneAtMs, MAX_BODY_CHARS, MAX_ERROR_CHARS, DEFAULT_FIND_AUDIT_MAX_DAYS, MAX_SNAPSHOT_FILE_BYTES, API_KEY_SERVICE_VALUES;
|
|
3539
|
+
var import_semver, SILUZAN_DIR, CONFIG_FILE, inProcessLockChain, ALLOWED_HOSTNAME_SUFFIXES, DEFAULT_TIMEOUT_MS, MAX_RESPONSE_BYTES, httpsAgent, httpAgent, PERF_PREFIX, ASCII_TABLE_CHARS, CLI_SNAPSHOT_MANIFEST_FILE, SCHEMA_VERSION, FALLBACK_FIELD_GUIDE_REFS, _cliPackage, _resolveCliVersion, _fieldGuideRefs, OUTLINE_AGENT_HINT, TS_LIKE_ARRAY_SAMPLE_MAX, MAX_INVOCATION_CHARS, currentInvocation, MAX_COMMIT_CHARS, cliCommitOverride, WRITE_AUDIT_SCHEMA_VERSION, MUTATING, BEIJING_TZ, AUDIT_LOG_FILENAME_RE, DEFAULT_MAX_FILE_MB, MIN_SEGMENT_BYTES, MAX_SEGMENT_BYTES, DEFAULT_RETENTION_DAYS, MIN_RETENTION_DAYS, MAX_RETENTION_DAYS, PRUNE_THROTTLE_MS, lastPruneAtMs, MAX_BODY_CHARS, MAX_ERROR_CHARS, DEFAULT_FIND_AUDIT_MAX_DAYS, MAX_SNAPSHOT_FILE_BYTES, API_KEY_SERVICE_VALUES;
|
|
3282
3540
|
var init_dist = __esm({
|
|
3283
3541
|
"../common/dist/index.js"() {
|
|
3284
3542
|
"use strict";
|
|
@@ -3326,6 +3584,14 @@ var init_dist = __esm({
|
|
|
3326
3584
|
"right-mid": "+",
|
|
3327
3585
|
middle: "|"
|
|
3328
3586
|
};
|
|
3587
|
+
CLI_SNAPSHOT_MANIFEST_FILE = "cli-manifest.json";
|
|
3588
|
+
SCHEMA_VERSION = 1;
|
|
3589
|
+
FALLBACK_FIELD_GUIDE_REFS = ["references/core/tips.md"];
|
|
3590
|
+
_cliPackage = "siluzan-cli";
|
|
3591
|
+
_resolveCliVersion = () => "0.0.0";
|
|
3592
|
+
_fieldGuideRefs = FALLBACK_FIELD_GUIDE_REFS;
|
|
3593
|
+
OUTLINE_AGENT_HINT = "\u7981\u6B62\u7528 Read/cat \u6253\u5F00 writtenFiles \u4E2D\u7684\u5B8C\u6574\u4E1A\u52A1 *.json\u3002\u987A\u5E8F\uFF1A1) \u89E3\u6790\u672C\u884C stdout \u6458\u8981\uFF1B2) \u4EC5\u53EF\u8BFB outlineFile\uFF08*.outline.txt\uFF0Cschema\uFF09\uFF1B3) \u5FC5\u987B\u7528 node/python \u811A\u672C require/readFileSync \u8BFB JSON \u5E76\u6253\u5370\u6240\u9700\u5B57\u6BB5\u3002\u52FF\u628A JSON \u8D34\u8FDB\u5BF9\u8BDD\u3002";
|
|
3594
|
+
TS_LIKE_ARRAY_SAMPLE_MAX = 8;
|
|
3329
3595
|
MAX_INVOCATION_CHARS = 4e3;
|
|
3330
3596
|
MAX_COMMIT_CHARS = 2e3;
|
|
3331
3597
|
WRITE_AUDIT_SCHEMA_VERSION = 2;
|
|
@@ -3357,7 +3623,7 @@ var DEFAULT_API_BASE;
|
|
|
3357
3623
|
var init_defaults = __esm({
|
|
3358
3624
|
"src/config/defaults.ts"() {
|
|
3359
3625
|
"use strict";
|
|
3360
|
-
DEFAULT_API_BASE = "https://tso-api
|
|
3626
|
+
DEFAULT_API_BASE = "https://tso-api.siluzan.com";
|
|
3361
3627
|
}
|
|
3362
3628
|
});
|
|
3363
3629
|
|
|
@@ -3481,8 +3747,8 @@ var init_version = __esm({
|
|
|
3481
3747
|
});
|
|
3482
3748
|
|
|
3483
3749
|
// src/utils/snapshot/write-audit-policy.ts
|
|
3484
|
-
import * as
|
|
3485
|
-
import { mkdir as
|
|
3750
|
+
import * as path7 from "path";
|
|
3751
|
+
import { mkdir as mkdir4, writeFile as writeFile2, chmod as chmod2 } from "fs/promises";
|
|
3486
3752
|
function shouldAttemptPreSnapshot(method, url) {
|
|
3487
3753
|
const m = method.toUpperCase();
|
|
3488
3754
|
if (m !== "PUT" && m !== "PATCH") return false;
|
|
@@ -3528,9 +3794,9 @@ async function tryWritePreSnapshot(params) {
|
|
|
3528
3794
|
params.verbose ?? false
|
|
3529
3795
|
);
|
|
3530
3796
|
const dir = getWriteAuditSnapshotsDir(params.namespace);
|
|
3531
|
-
await
|
|
3797
|
+
await mkdir4(dir, { recursive: true });
|
|
3532
3798
|
const fileName = `${params.auditId}.json`;
|
|
3533
|
-
const abs =
|
|
3799
|
+
const abs = path7.join(dir, fileName);
|
|
3534
3800
|
const payload = {
|
|
3535
3801
|
v: 1,
|
|
3536
3802
|
auditId: params.auditId,
|
|
@@ -3544,14 +3810,14 @@ async function tryWritePreSnapshot(params) {
|
|
|
3544
3810
|
if (Buffer.byteLength(json, "utf8") > MAX_SNAPSHOT_BYTES) {
|
|
3545
3811
|
return { skippedReason: "snapshot_too_large" };
|
|
3546
3812
|
}
|
|
3547
|
-
await
|
|
3813
|
+
await writeFile2(abs, json, "utf8");
|
|
3548
3814
|
if (process.platform !== "win32") {
|
|
3549
3815
|
try {
|
|
3550
3816
|
await chmod2(abs, 384);
|
|
3551
3817
|
} catch {
|
|
3552
3818
|
}
|
|
3553
3819
|
}
|
|
3554
|
-
return { ref:
|
|
3820
|
+
return { ref: path7.join("snapshots", fileName).replace(/\\/g, "/") };
|
|
3555
3821
|
} catch (e) {
|
|
3556
3822
|
const msg = e instanceof Error ? e.message : String(e);
|
|
3557
3823
|
return { skippedReason: `get_failed:${msg.slice(0, 200)}` };
|
|
@@ -3783,271 +4049,11 @@ var init_cli_table = __esm({
|
|
|
3783
4049
|
}
|
|
3784
4050
|
});
|
|
3785
4051
|
|
|
3786
|
-
// src/utils/snapshot/dir.ts
|
|
3787
|
-
import * as fs3 from "fs/promises";
|
|
3788
|
-
import * as path6 from "path";
|
|
3789
|
-
function resolveSnapshotDir(snapshotDir) {
|
|
3790
|
-
const trimmed = snapshotDir.trim();
|
|
3791
|
-
if (!trimmed) {
|
|
3792
|
-
throw new Error("\u5FEB\u7167\u76EE\u5F55\u4E0D\u80FD\u4E3A\u7A7A");
|
|
3793
|
-
}
|
|
3794
|
-
if (trimmed.includes("\0")) {
|
|
3795
|
-
throw new Error("\u5FEB\u7167\u76EE\u5F55\u8DEF\u5F84\u975E\u6CD5");
|
|
3796
|
-
}
|
|
3797
|
-
return path6.resolve(trimmed);
|
|
3798
|
-
}
|
|
3799
|
-
async function ensureSnapshotDir(snapshotDir) {
|
|
3800
|
-
const abs = resolveSnapshotDir(snapshotDir);
|
|
3801
|
-
await fs3.mkdir(abs, { recursive: true });
|
|
3802
|
-
return abs;
|
|
3803
|
-
}
|
|
3804
|
-
var init_dir = __esm({
|
|
3805
|
-
"src/utils/snapshot/dir.ts"() {
|
|
3806
|
-
"use strict";
|
|
3807
|
-
}
|
|
3808
|
-
});
|
|
3809
|
-
|
|
3810
4052
|
// src/utils/snapshot/cli-json.ts
|
|
3811
|
-
import * as fs4 from "fs/promises";
|
|
3812
|
-
import * as path7 from "path";
|
|
3813
|
-
function slugifyCliSnapshotSectionKey(section) {
|
|
3814
|
-
return section.trim().toLowerCase().replace(/[^a-z0-9_-]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
3815
|
-
}
|
|
3816
|
-
function slugifyIdSuffix(id) {
|
|
3817
|
-
if (id === void 0 || id === null) return "";
|
|
3818
|
-
return slugifyCliSnapshotSectionKey(String(id));
|
|
3819
|
-
}
|
|
3820
|
-
function applyIdSuffix(stem, idSuffix) {
|
|
3821
|
-
const slug = slugifyIdSuffix(idSuffix);
|
|
3822
|
-
if (!slug) return stem;
|
|
3823
|
-
if (stem === slug || stem.endsWith(`-${slug}`)) return stem;
|
|
3824
|
-
return `${stem}-${slug}`;
|
|
3825
|
-
}
|
|
3826
|
-
function normalizeCliSnapshotSection(section) {
|
|
3827
|
-
const slug = slugifyCliSnapshotSectionKey(section);
|
|
3828
|
-
if (!slug || !/^[a-z0-9_-]+$/.test(slug)) {
|
|
3829
|
-
throw new Error(`\u975E\u6CD5 section\uFF0C\u65E0\u6CD5\u751F\u6210\u6587\u4EF6\u540D\uFF1A${section}`);
|
|
3830
|
-
}
|
|
3831
|
-
return slug;
|
|
3832
|
-
}
|
|
3833
|
-
function cliManifestFileName(idSuffix) {
|
|
3834
|
-
return `${applyIdSuffix("cli-manifest", idSuffix)}.json`;
|
|
3835
|
-
}
|
|
3836
|
-
async function readCliManifestIfExists(absDir, idSuffix) {
|
|
3837
|
-
const candidates = [cliManifestFileName(idSuffix)];
|
|
3838
|
-
if (idSuffix && candidates[0] !== CLI_SNAPSHOT_MANIFEST_FILE) {
|
|
3839
|
-
candidates.push(CLI_SNAPSHOT_MANIFEST_FILE);
|
|
3840
|
-
}
|
|
3841
|
-
for (const name2 of candidates) {
|
|
3842
|
-
const manifestPath = path7.join(absDir, name2);
|
|
3843
|
-
try {
|
|
3844
|
-
const raw = await fs4.readFile(manifestPath, "utf8");
|
|
3845
|
-
const parsed = JSON.parse(raw);
|
|
3846
|
-
if (parsed?.schemaVersion !== SCHEMA_VERSION || parsed.kind !== "siluzan-cli-json-snapshot" || !Array.isArray(parsed.artifacts)) {
|
|
3847
|
-
continue;
|
|
3848
|
-
}
|
|
3849
|
-
return parsed;
|
|
3850
|
-
} catch {
|
|
3851
|
-
continue;
|
|
3852
|
-
}
|
|
3853
|
-
}
|
|
3854
|
-
return null;
|
|
3855
|
-
}
|
|
3856
|
-
function mergeCliSnapshotManifest(existing, params) {
|
|
3857
|
-
const prevArts = existing?.artifacts ?? [];
|
|
3858
|
-
const key = slugifyCliSnapshotSectionKey(params.newArtifact.section);
|
|
3859
|
-
const without = prevArts.filter((a) => slugifyCliSnapshotSectionKey(a.section) !== key);
|
|
3860
|
-
const artifacts = [...without, params.newArtifact];
|
|
3861
|
-
const fieldGuide = existing?.fieldGuide ?? DEFAULT_FIELD_GUIDE;
|
|
3862
|
-
return {
|
|
3863
|
-
schemaVersion: SCHEMA_VERSION,
|
|
3864
|
-
kind: "siluzan-cli-json-snapshot",
|
|
3865
|
-
cliPackage: CLI_PACKAGE,
|
|
3866
|
-
cliVersion: params.cliVersion,
|
|
3867
|
-
fieldGuide: { markdownRefs: [...fieldGuide.markdownRefs] },
|
|
3868
|
-
artifacts
|
|
3869
|
-
};
|
|
3870
|
-
}
|
|
3871
|
-
function rejectDeprecatedJsonFlag(json, commandLabel) {
|
|
3872
|
-
if (json) {
|
|
3873
|
-
console.error(
|
|
3874
|
-
`
|
|
3875
|
-
\u274C ${commandLabel}\uFF1A--json \u5DF2\u79FB\u9664\u3002\u8BF7\u4F7F\u7528 --json-out <\u76EE\u5F55\u6216 *.json \u6587\u4EF6> \u843D\u76D8\u7ED3\u6784\u5316\u6570\u636E\uFF08stdout \u4EC5\u4E00\u884C\u6458\u8981\uFF09\u3002
|
|
3876
|
-
`
|
|
3877
|
-
);
|
|
3878
|
-
process.exit(1);
|
|
3879
|
-
}
|
|
3880
|
-
}
|
|
3881
|
-
function tsLikeQuoteKey(key) {
|
|
3882
|
-
if (/^[A-Za-z_$][\w$]*$/.test(key)) return key;
|
|
3883
|
-
return JSON.stringify(key);
|
|
3884
|
-
}
|
|
3885
|
-
function describePayloadTsLike(value) {
|
|
3886
|
-
const visiting = /* @__PURE__ */ new WeakSet();
|
|
3887
|
-
const memoDone = /* @__PURE__ */ new WeakSet();
|
|
3888
|
-
function tsScalar(v) {
|
|
3889
|
-
if (v === null) return "null";
|
|
3890
|
-
if (v === void 0) return "undefined";
|
|
3891
|
-
if (typeof v === "boolean") return "boolean";
|
|
3892
|
-
if (typeof v === "number") return "number";
|
|
3893
|
-
if (typeof v === "string") return "string";
|
|
3894
|
-
if (typeof v === "bigint") return "bigint";
|
|
3895
|
-
if (typeof v !== "object") return "unknown";
|
|
3896
|
-
return "";
|
|
3897
|
-
}
|
|
3898
|
-
function ts(v) {
|
|
3899
|
-
const prim = tsScalar(v);
|
|
3900
|
-
if (prim) return prim;
|
|
3901
|
-
if (Array.isArray(v)) {
|
|
3902
|
-
if (v.length === 0) return "unknown[]";
|
|
3903
|
-
const parts = [];
|
|
3904
|
-
const seen = /* @__PURE__ */ new Set();
|
|
3905
|
-
const n = Math.min(v.length, TS_LIKE_ARRAY_SAMPLE_MAX);
|
|
3906
|
-
for (let i = 0; i < n; i++) {
|
|
3907
|
-
const t = ts(v[i]);
|
|
3908
|
-
if (seen.has(t)) continue;
|
|
3909
|
-
seen.add(t);
|
|
3910
|
-
parts.push(t);
|
|
3911
|
-
}
|
|
3912
|
-
const inner = parts.length === 1 ? parts[0] : `(${parts.join(" | ")})`;
|
|
3913
|
-
return `${inner}[]`;
|
|
3914
|
-
}
|
|
3915
|
-
const o = v;
|
|
3916
|
-
if (visiting.has(o)) return "any";
|
|
3917
|
-
if (memoDone.has(o)) return "any";
|
|
3918
|
-
visiting.add(o);
|
|
3919
|
-
const props = [];
|
|
3920
|
-
for (const k of Object.keys(o)) {
|
|
3921
|
-
const child = o[k];
|
|
3922
|
-
if (child === void 0) {
|
|
3923
|
-
props.push(`${tsLikeQuoteKey(k)}: undefined`);
|
|
3924
|
-
continue;
|
|
3925
|
-
}
|
|
3926
|
-
props.push(`${tsLikeQuoteKey(k)}: ${ts(child)}`);
|
|
3927
|
-
}
|
|
3928
|
-
visiting.delete(o);
|
|
3929
|
-
memoDone.add(o);
|
|
3930
|
-
return `{ ${props.join("; ")} }`;
|
|
3931
|
-
}
|
|
3932
|
-
return ts(value);
|
|
3933
|
-
}
|
|
3934
|
-
function isJsonFilePath(p) {
|
|
3935
|
-
return p.trim().toLowerCase().endsWith(".json");
|
|
3936
|
-
}
|
|
3937
|
-
function snapshotOutlineFileName(jsonBasename) {
|
|
3938
|
-
const lower = jsonBasename.trim().toLowerCase();
|
|
3939
|
-
if (!lower.endsWith(".json")) {
|
|
3940
|
-
throw new Error(`\u5FEB\u7167 JSON \u6587\u4EF6\u540D\u987B\u4EE5 .json \u7ED3\u5C3E\uFF1A${jsonBasename}`);
|
|
3941
|
-
}
|
|
3942
|
-
return `${jsonBasename.slice(0, -".json".length)}.outline.txt`;
|
|
3943
|
-
}
|
|
3944
|
-
function formatOutlineFileBody(jsonBasename, payload, extraHeaderCommentLines) {
|
|
3945
|
-
const typeText = describePayloadTsLike(payload);
|
|
3946
|
-
const lines = [
|
|
3947
|
-
`// outline of \`${jsonBasename}\` \u2014 schema-only, NOT the data.`,
|
|
3948
|
-
`// \u7528\u6CD5\uFF1A\u5904\u7406\u540C\u76EE\u5F55 \`${jsonBasename}\` \u7684\u6570\u636E\u524D\uFF0C\u5148\u8BFB\u672C\u6587\u4EF6\u4E86\u89E3\u5B57\u6BB5\u7C7B\u578B\uFF0C\u518D\u7528\u811A\u672C\uFF08fs.readFileSync / require\uFF09\u8BFB\u53D6\u8BE5 JSON \u505A\u7B5B\u9009\u4E0E\u805A\u5408\u3002`,
|
|
3949
|
-
`// \u7C7B\u578B\u7531 JSON \u63A8\u65AD\uFF1A\u6570\u7EC4\u53D6\u524D 8 \u9879\u53BB\u91CD\u5E76\u96C6\uFF1B\u73AF\u6216\u91CD\u590D\u5BF9\u8C61\u5F15\u7528\u8BB0\u4E3A any\u3002`,
|
|
3950
|
-
...extraHeaderCommentLines ?? [],
|
|
3951
|
-
typeText
|
|
3952
|
-
];
|
|
3953
|
-
return lines.join("\n");
|
|
3954
|
-
}
|
|
3955
|
-
async function writeCliJsonSnapshot(params) {
|
|
3956
|
-
const trimmed = params.snapshotDir.trim();
|
|
3957
|
-
const idSlug = slugifyIdSuffix(params.idSuffix);
|
|
3958
|
-
const manifestFile = cliManifestFileName(idSlug || void 0);
|
|
3959
|
-
let absDir;
|
|
3960
|
-
let fileName;
|
|
3961
|
-
let section;
|
|
3962
|
-
if (isJsonFilePath(trimmed)) {
|
|
3963
|
-
const absFile = path7.resolve(trimmed);
|
|
3964
|
-
absDir = path7.dirname(absFile);
|
|
3965
|
-
if (absDir.includes("\0") || absFile.includes("\0")) {
|
|
3966
|
-
throw new Error("\u5FEB\u7167\u8DEF\u5F84\u975E\u6CD5");
|
|
3967
|
-
}
|
|
3968
|
-
await fs4.mkdir(absDir, { recursive: true });
|
|
3969
|
-
fileName = path7.basename(absFile);
|
|
3970
|
-
if (!fileName.toLowerCase().endsWith(".json")) {
|
|
3971
|
-
throw new Error(`--json-out \u6587\u4EF6\u8DEF\u5F84\u987B\u4EE5 .json \u7ED3\u5C3E\uFF1A${trimmed}`);
|
|
3972
|
-
}
|
|
3973
|
-
if (fileName.toLowerCase() === manifestFile.toLowerCase()) {
|
|
3974
|
-
throw new Error(`\u8F93\u51FA\u6587\u4EF6\u4E0D\u80FD\u547D\u540D\u4E3A ${manifestFile}\uFF0C\u4E0E\u6E05\u5355\u6587\u4EF6\u51B2\u7A81`);
|
|
3975
|
-
}
|
|
3976
|
-
const base = fileName.slice(0, -".json".length);
|
|
3977
|
-
section = normalizeCliSnapshotSection(base || params.section);
|
|
3978
|
-
} else {
|
|
3979
|
-
absDir = await ensureSnapshotDir(trimmed);
|
|
3980
|
-
section = normalizeCliSnapshotSection(params.section);
|
|
3981
|
-
const stem = applyIdSuffix(section, idSlug || void 0);
|
|
3982
|
-
fileName = `${stem}.json`;
|
|
3983
|
-
}
|
|
3984
|
-
const writtenAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3985
|
-
const absFilePath = path7.join(absDir, fileName);
|
|
3986
|
-
await fs4.writeFile(absFilePath, `${JSON.stringify(params.payload, null, 2)}
|
|
3987
|
-
`, "utf8");
|
|
3988
|
-
const outlineFileName = snapshotOutlineFileName(fileName);
|
|
3989
|
-
if (outlineFileName.toLowerCase() === manifestFile.toLowerCase()) {
|
|
3990
|
-
throw new Error(`\u975E\u6CD5\u8F93\u51FA\uFF1A${outlineFileName} \u4E0E\u6E05\u5355\u6587\u4EF6\u540D\u51B2\u7A81`);
|
|
3991
|
-
}
|
|
3992
|
-
const outlineBody = formatOutlineFileBody(fileName, params.payload);
|
|
3993
|
-
await fs4.writeFile(path7.join(absDir, outlineFileName), `${outlineBody}
|
|
3994
|
-
`, "utf8");
|
|
3995
|
-
const existing = await readCliManifestIfExists(absDir, idSlug || void 0);
|
|
3996
|
-
const newArtifact = {
|
|
3997
|
-
section,
|
|
3998
|
-
file: fileName,
|
|
3999
|
-
writtenAt,
|
|
4000
|
-
...params.commandHint ? { commandHint: params.commandHint } : {}
|
|
4001
|
-
};
|
|
4002
|
-
const merged = mergeCliSnapshotManifest(existing, {
|
|
4003
|
-
cliVersion: params.cliVersion ?? getCurrentVersion2(),
|
|
4004
|
-
newArtifact
|
|
4005
|
-
});
|
|
4006
|
-
await fs4.writeFile(
|
|
4007
|
-
path7.join(absDir, manifestFile),
|
|
4008
|
-
`${JSON.stringify(merged, null, 2)}
|
|
4009
|
-
`,
|
|
4010
|
-
"utf8"
|
|
4011
|
-
);
|
|
4012
|
-
return {
|
|
4013
|
-
kind: "siluzan-tso-cli-json-snapshot",
|
|
4014
|
-
schemaVersion: SCHEMA_VERSION,
|
|
4015
|
-
absoluteSnapshotDir: absDir,
|
|
4016
|
-
manifestFile,
|
|
4017
|
-
writtenFiles: [fileName, outlineFileName],
|
|
4018
|
-
section,
|
|
4019
|
-
outlineFile: outlineFileName,
|
|
4020
|
-
agentHint: OUTLINE_AGENT_HINT
|
|
4021
|
-
};
|
|
4022
|
-
}
|
|
4023
|
-
async function emitCliJsonOrSnapshot(opts, params) {
|
|
4024
|
-
if (opts.jsonOut) {
|
|
4025
|
-
const summary = await writeCliJsonSnapshot({
|
|
4026
|
-
snapshotDir: opts.jsonOut,
|
|
4027
|
-
section: params.section,
|
|
4028
|
-
payload: params.payload,
|
|
4029
|
-
commandHint: params.commandHint,
|
|
4030
|
-
idSuffix: params.idSuffix
|
|
4031
|
-
});
|
|
4032
|
-
console.log(JSON.stringify(summary));
|
|
4033
|
-
return true;
|
|
4034
|
-
}
|
|
4035
|
-
return false;
|
|
4036
|
-
}
|
|
4037
|
-
var CLI_SNAPSHOT_MANIFEST_FILE, CLI_PACKAGE, SCHEMA_VERSION, OUTLINE_AGENT_HINT, DEFAULT_FIELD_GUIDE, TS_LIKE_ARRAY_SAMPLE_MAX;
|
|
4038
4053
|
var init_cli_json = __esm({
|
|
4039
4054
|
"src/utils/snapshot/cli-json.ts"() {
|
|
4040
4055
|
"use strict";
|
|
4041
|
-
|
|
4042
|
-
init_version();
|
|
4043
|
-
CLI_SNAPSHOT_MANIFEST_FILE = "cli-manifest.json";
|
|
4044
|
-
CLI_PACKAGE = "siluzan-tso-cli";
|
|
4045
|
-
SCHEMA_VERSION = 1;
|
|
4046
|
-
OUTLINE_AGENT_HINT = "\u7981\u6B62\u7528 Read/cat \u6253\u5F00 writtenFiles \u4E2D\u7684\u5B8C\u6574\u4E1A\u52A1 *.json\u3002\u987A\u5E8F\uFF1A1) \u89E3\u6790\u672C\u884C stdout \u6458\u8981\uFF1B2) \u4EC5\u53EF\u8BFB outlineFile\uFF08*.outline.txt\uFF0Cschema\uFF09\uFF1B3) \u5FC5\u987B\u7528 node/python \u811A\u672C require/readFileSync \u8BFB JSON \u5E76\u6253\u5370\u6240\u9700\u5B57\u6BB5\u3002\u52FF\u628A JSON \u8D34\u8FDB\u5BF9\u8BDD\u3002";
|
|
4047
|
-
DEFAULT_FIELD_GUIDE = {
|
|
4048
|
-
markdownRefs: ["references/accounts/currency.md", "references/core/tips.md", "references/accounts/accounts.md"]
|
|
4049
|
-
};
|
|
4050
|
-
TS_LIKE_ARRAY_SAMPLE_MAX = 8;
|
|
4056
|
+
init_dist();
|
|
4051
4057
|
}
|
|
4052
4058
|
});
|
|
4053
4059
|
|
|
@@ -4272,6 +4278,65 @@ var init_balance = __esm({
|
|
|
4272
4278
|
}
|
|
4273
4279
|
});
|
|
4274
4280
|
|
|
4281
|
+
// src/utils/media-account-batch.ts
|
|
4282
|
+
function mediaAccountDataBatchSize(media) {
|
|
4283
|
+
switch (media) {
|
|
4284
|
+
case "BingV2":
|
|
4285
|
+
case "TikTok":
|
|
4286
|
+
case "Yandex":
|
|
4287
|
+
return 10;
|
|
4288
|
+
case "Kwai":
|
|
4289
|
+
return 20;
|
|
4290
|
+
default:
|
|
4291
|
+
return 100;
|
|
4292
|
+
}
|
|
4293
|
+
}
|
|
4294
|
+
async function fetchBalanceAndOverviewSequential(opts) {
|
|
4295
|
+
const { media, accountIds, config, startDate, endDate, verbose } = opts;
|
|
4296
|
+
const balanceMap = /* @__PURE__ */ new Map();
|
|
4297
|
+
const overviewMap = /* @__PURE__ */ new Map();
|
|
4298
|
+
if (accountIds.length === 0) {
|
|
4299
|
+
return { balanceMap, overviewMap, chunkSize: mediaAccountDataBatchSize(media), chunkCount: 0 };
|
|
4300
|
+
}
|
|
4301
|
+
const chunkSize = mediaAccountDataBatchSize(media);
|
|
4302
|
+
const chunks = [];
|
|
4303
|
+
for (let i = 0; i < accountIds.length; i += chunkSize) {
|
|
4304
|
+
chunks.push(accountIds.slice(i, i + chunkSize));
|
|
4305
|
+
}
|
|
4306
|
+
for (let chunkIdx = 0; chunkIdx < chunks.length; chunkIdx++) {
|
|
4307
|
+
const ids = chunks[chunkIdx];
|
|
4308
|
+
const oneBased = chunkIdx + 1;
|
|
4309
|
+
opts.onChunkStart?.(oneBased, chunks.length, ids);
|
|
4310
|
+
const overviewChunk = await fetchOverviewMap(
|
|
4311
|
+
media,
|
|
4312
|
+
ids,
|
|
4313
|
+
config,
|
|
4314
|
+
startDate,
|
|
4315
|
+
endDate,
|
|
4316
|
+
verbose
|
|
4317
|
+
);
|
|
4318
|
+
opts.onOverviewDone?.(oneBased, chunks.length, ids, overviewChunk.size);
|
|
4319
|
+
for (const [k, v] of overviewChunk) overviewMap.set(k, v);
|
|
4320
|
+
const balanceChunk = await fetchBalanceMap(
|
|
4321
|
+
media,
|
|
4322
|
+
ids,
|
|
4323
|
+
config,
|
|
4324
|
+
startDate,
|
|
4325
|
+
endDate,
|
|
4326
|
+
verbose
|
|
4327
|
+
);
|
|
4328
|
+
opts.onBalanceDone?.(oneBased, chunks.length, ids, balanceChunk.size);
|
|
4329
|
+
for (const [k, v] of balanceChunk) balanceMap.set(k, v);
|
|
4330
|
+
}
|
|
4331
|
+
return { balanceMap, overviewMap, chunkSize, chunkCount: chunks.length };
|
|
4332
|
+
}
|
|
4333
|
+
var init_media_account_batch = __esm({
|
|
4334
|
+
"src/utils/media-account-batch.ts"() {
|
|
4335
|
+
"use strict";
|
|
4336
|
+
init_balance();
|
|
4337
|
+
}
|
|
4338
|
+
});
|
|
4339
|
+
|
|
4275
4340
|
// src/commands/accounts-digest/list-fetch.ts
|
|
4276
4341
|
async function fetchAccountsByMedia(media, config, opts) {
|
|
4277
4342
|
const cfg = DIGEST_PLATFORM_CONFIG[media];
|
|
@@ -4420,30 +4485,47 @@ async function runAccountsDigest(opts) {
|
|
|
4420
4485
|
}
|
|
4421
4486
|
}
|
|
4422
4487
|
const validIds = accountsList.filter((it) => it.ma?.mediaCustomerId && !it.ma?.invalidOAuthToken).map((it) => String(it.ma.mediaCustomerId));
|
|
4423
|
-
|
|
4424
|
-
|
|
4425
|
-
|
|
4426
|
-
|
|
4427
|
-
const overviewMap = /* @__PURE__ */ new Map();
|
|
4428
|
-
if (chunks.length > 0) {
|
|
4488
|
+
let balanceMap = /* @__PURE__ */ new Map();
|
|
4489
|
+
let overviewMap = /* @__PURE__ */ new Map();
|
|
4490
|
+
let batchChunkSize = null;
|
|
4491
|
+
if (validIds.length > 0) {
|
|
4429
4492
|
process.stderr.write(
|
|
4430
|
-
`\u23F3 \
|
|
4493
|
+
`\u23F3 [accounts-digest] \u6709\u6548\u8D26\u6237 ${validIds.length} \u4E2A\uFF1B\u4E32\u884C\u62C9\u53D6\u6D88\u8017 \u2192 \u4F59\u989D\uFF08\u4E0E Web \u8D26\u6237\u5217\u8868\u5206\u6279\u53E3\u5F84\u4E00\u81F4\uFF09\u3002
|
|
4431
4494
|
`
|
|
4432
4495
|
);
|
|
4433
|
-
const
|
|
4434
|
-
|
|
4435
|
-
|
|
4436
|
-
|
|
4437
|
-
|
|
4438
|
-
|
|
4439
|
-
|
|
4440
|
-
|
|
4441
|
-
|
|
4442
|
-
|
|
4443
|
-
|
|
4444
|
-
|
|
4445
|
-
|
|
4446
|
-
|
|
4496
|
+
const fetched = await fetchBalanceAndOverviewSequential({
|
|
4497
|
+
media,
|
|
4498
|
+
accountIds: validIds,
|
|
4499
|
+
config,
|
|
4500
|
+
startDate: opts.startDate,
|
|
4501
|
+
endDate: opts.endDate,
|
|
4502
|
+
verbose: opts.verbose,
|
|
4503
|
+
onChunkStart: (chunkIdx, totalChunks, ids) => {
|
|
4504
|
+
process.stderr.write(
|
|
4505
|
+
` \u2192 [\u6D88\u8017] \u7B2C ${chunkIdx}/${totalChunks} \u6279\uFF08${ids.length} \u6237\uFF09\u2026
|
|
4506
|
+
`
|
|
4507
|
+
);
|
|
4508
|
+
},
|
|
4509
|
+
onOverviewDone: (chunkIdx, totalChunks, ids, size) => {
|
|
4510
|
+
process.stderr.write(
|
|
4511
|
+
` \u2713 [\u6D88\u8017] \u7B2C ${chunkIdx}/${totalChunks} \u6279\u5B8C\u6210\uFF08${ids.length} \u6237 \u2192 ${size} \u6761\uFF09
|
|
4512
|
+
`
|
|
4513
|
+
);
|
|
4514
|
+
process.stderr.write(
|
|
4515
|
+
` \u2192 [\u4F59\u989D] \u7B2C ${chunkIdx}/${totalChunks} \u6279\uFF08${ids.length} \u6237\uFF09\u2026
|
|
4516
|
+
`
|
|
4517
|
+
);
|
|
4518
|
+
},
|
|
4519
|
+
onBalanceDone: (chunkIdx, totalChunks, ids, size) => {
|
|
4520
|
+
process.stderr.write(
|
|
4521
|
+
` \u2713 [\u4F59\u989D] \u7B2C ${chunkIdx}/${totalChunks} \u6279\u5B8C\u6210\uFF08${ids.length} \u6237 \u2192 ${size} \u6761\uFF09
|
|
4522
|
+
`
|
|
4523
|
+
);
|
|
4524
|
+
}
|
|
4525
|
+
});
|
|
4526
|
+
balanceMap = fetched.balanceMap;
|
|
4527
|
+
overviewMap = fetched.overviewMap;
|
|
4528
|
+
batchChunkSize = fetched.chunkSize;
|
|
4447
4529
|
}
|
|
4448
4530
|
const rows = [];
|
|
4449
4531
|
for (const item of accountsList) {
|
|
@@ -4499,6 +4581,7 @@ async function runAccountsDigest(opts) {
|
|
|
4499
4581
|
/** 取数策略:list = 全量翻清单后过滤,subset = 跳过翻页直接对 -a 指定的 ID 拉数据 */
|
|
4500
4582
|
source: scannedFromList ? "list" : "subset",
|
|
4501
4583
|
returned: rows.length,
|
|
4584
|
+
batchChunkSize: validIds.length > 0 ? batchChunkSize : null,
|
|
4502
4585
|
totals: {
|
|
4503
4586
|
spend: +totals.spend.toFixed(2),
|
|
4504
4587
|
clicks: totals.clicks,
|
|
@@ -4575,6 +4658,7 @@ var init_run = __esm({
|
|
|
4575
4658
|
init_balance();
|
|
4576
4659
|
init_cli_table();
|
|
4577
4660
|
init_currency_display();
|
|
4661
|
+
init_media_account_batch();
|
|
4578
4662
|
init_list_fetch();
|
|
4579
4663
|
}
|
|
4580
4664
|
});
|
|
@@ -4752,6 +4836,14 @@ var init_wrap_snapshot_envelope = __esm({
|
|
|
4752
4836
|
}
|
|
4753
4837
|
});
|
|
4754
4838
|
|
|
4839
|
+
// src/utils/snapshot/dir.ts
|
|
4840
|
+
var init_dir = __esm({
|
|
4841
|
+
"src/utils/snapshot/dir.ts"() {
|
|
4842
|
+
"use strict";
|
|
4843
|
+
init_dist();
|
|
4844
|
+
}
|
|
4845
|
+
});
|
|
4846
|
+
|
|
4755
4847
|
// src/utils/snapshot/google-analysis.ts
|
|
4756
4848
|
import * as fs7 from "fs/promises";
|
|
4757
4849
|
import * as path11 from "path";
|
|
@@ -4815,10 +4907,10 @@ function mergeGoogleAnalysisManifest(existing, params) {
|
|
|
4815
4907
|
const prevArts = existing?.artifacts ?? [];
|
|
4816
4908
|
const without = prevArts.filter((a) => a.file !== params.newArtifact.file);
|
|
4817
4909
|
const artifacts = [...without, params.newArtifact];
|
|
4818
|
-
const fieldGuide = existing?.fieldGuide ??
|
|
4910
|
+
const fieldGuide = existing?.fieldGuide ?? DEFAULT_FIELD_GUIDE;
|
|
4819
4911
|
return {
|
|
4820
4912
|
schemaVersion: SCHEMA_VERSION2,
|
|
4821
|
-
cliPackage:
|
|
4913
|
+
cliPackage: CLI_PACKAGE,
|
|
4822
4914
|
cliVersion: params.cliVersion,
|
|
4823
4915
|
googleAnalysis: {
|
|
4824
4916
|
accountId: params.accountId,
|
|
@@ -4884,7 +4976,7 @@ async function writeGoogleAnalysisSnapshot(params) {
|
|
|
4884
4976
|
agentHint: OUTLINE_AGENT_HINT
|
|
4885
4977
|
};
|
|
4886
4978
|
}
|
|
4887
|
-
var LEGACY_MANIFEST_FILE,
|
|
4979
|
+
var LEGACY_MANIFEST_FILE, CLI_PACKAGE, SCHEMA_VERSION2, DEFAULT_FIELD_GUIDE, GOOGLE_ANALYSIS_CAMPAIGNS_OUTLINE_BUDGET_HINTS, GOOGLE_ANALYSIS_ADS_OUTLINE_BUDGET_HINTS, GOOGLE_ANALYSIS_CAMPAIGNS_COMPETITIVE_METRICS_HINTS, RATE_BEARING_SECTIONS, GOOGLE_ANALYSIS_RATE_NORMALIZED_OUTLINE_HINTS, GOOGLE_ANALYSIS_ENVELOPE_OUTLINE_HINTS, GOOGLE_ANALYSIS_ZH_FIELD_HINTS_BY_SECTION;
|
|
4888
4980
|
var init_google_analysis = __esm({
|
|
4889
4981
|
"src/utils/snapshot/google-analysis.ts"() {
|
|
4890
4982
|
"use strict";
|
|
@@ -4892,9 +4984,9 @@ var init_google_analysis = __esm({
|
|
|
4892
4984
|
init_wrap_snapshot_envelope();
|
|
4893
4985
|
init_dir();
|
|
4894
4986
|
LEGACY_MANIFEST_FILE = "manifest.json";
|
|
4895
|
-
|
|
4987
|
+
CLI_PACKAGE = "siluzan-tso-cli";
|
|
4896
4988
|
SCHEMA_VERSION2 = 3;
|
|
4897
|
-
|
|
4989
|
+
DEFAULT_FIELD_GUIDE = {
|
|
4898
4990
|
markdownRefs: ["references/accounts/currency.md", "references/analytics/account-analytics.md"],
|
|
4899
4991
|
tsTypesModule: "tso-cli/src/types/google-analysis-api.ts"
|
|
4900
4992
|
};
|
|
@@ -104329,7 +104421,15 @@ function stripListAccountsEnrichmentFields(item) {
|
|
|
104329
104421
|
delete item.ma.platformStatus;
|
|
104330
104422
|
delete item.ma.aritScore;
|
|
104331
104423
|
const maExt = item.ma;
|
|
104332
|
-
for (const key of [
|
|
104424
|
+
for (const key of [
|
|
104425
|
+
"balance",
|
|
104426
|
+
"status",
|
|
104427
|
+
"spend",
|
|
104428
|
+
"impressions",
|
|
104429
|
+
"clicks",
|
|
104430
|
+
"conversions",
|
|
104431
|
+
"costPerClick"
|
|
104432
|
+
]) {
|
|
104333
104433
|
delete maExt[key];
|
|
104334
104434
|
}
|
|
104335
104435
|
}
|
|
@@ -104381,7 +104481,6 @@ var COL_SPEND_METRICS = [
|
|
|
104381
104481
|
{ key: "conversions", header: "\u8F6C\u5316\u6B21\u6570" },
|
|
104382
104482
|
{ key: "clicks", header: "\u70B9\u51FB\u6B21\u6570" },
|
|
104383
104483
|
{ key: "cpc", header: "\u70B9\u51FB\u5747\u4EF7" },
|
|
104384
|
-
{ key: "balance", header: "\u4F59\u989D" },
|
|
104385
104484
|
{ key: "authStatus", header: "\u6388\u6743\u72B6\u6001" },
|
|
104386
104485
|
{ key: "createdAt", header: "\u6388\u6743/\u5F00\u6237\u65F6\u95F4" },
|
|
104387
104486
|
{ key: "shared", header: "\u5206\u4EAB\u8D26\u6237" }
|
|
@@ -104396,7 +104495,6 @@ var COL_YANDEX = [
|
|
|
104396
104495
|
{ key: "conversions", header: "\u8F6C\u5316\u6B21\u6570" },
|
|
104397
104496
|
{ key: "clicks", header: "\u70B9\u51FB\u6B21\u6570" },
|
|
104398
104497
|
{ key: "cpc", header: "\u70B9\u51FB\u5747\u4EF7" },
|
|
104399
|
-
{ key: "balance", header: "\u4F59\u989D" },
|
|
104400
104498
|
{ key: "authStatus", header: "\u6388\u6743\u72B6\u6001" },
|
|
104401
104499
|
{ key: "createdAt", header: "\u6388\u6743/\u5F00\u6237\u65F6\u95F4" },
|
|
104402
104500
|
{ key: "shared", header: "\u5206\u4EAB\u8D26\u6237" }
|
|
@@ -104411,7 +104509,6 @@ var COL_TIKTOK = [
|
|
|
104411
104509
|
{ key: "conversions", header: "\u8F6C\u5316\u6B21\u6570" },
|
|
104412
104510
|
{ key: "clicks", header: "\u70B9\u51FB\u6B21\u6570" },
|
|
104413
104511
|
{ key: "cpc", header: "\u70B9\u51FB\u5747\u4EF7" },
|
|
104414
|
-
{ key: "balance", header: "\u4F59\u989D" },
|
|
104415
104512
|
{ key: "bc", header: "BC" },
|
|
104416
104513
|
{ key: "authStatus", header: "\u6388\u6743\u72B6\u6001" },
|
|
104417
104514
|
{ key: "auditStatus", header: "\u5BA1\u6838\u72B6\u6001" },
|
|
@@ -104428,7 +104525,6 @@ var COL_GOOGLE = [
|
|
|
104428
104525
|
{ key: "conversions", header: "\u8F6C\u5316" },
|
|
104429
104526
|
{ key: "clicks", header: "\u70B9\u51FB" },
|
|
104430
104527
|
{ key: "cpc", header: "\u70B9\u51FB\u5747\u4EF7" },
|
|
104431
|
-
{ key: "balance", header: "\u4F59\u989D" },
|
|
104432
104528
|
{ key: "arit", header: "Arit" },
|
|
104433
104529
|
{ key: "platformStatus", header: "\u8D26\u6237\u72B6\u6001" },
|
|
104434
104530
|
{ key: "authStatus", header: "\u6388\u6743\u72B6\u6001" },
|
|
@@ -104529,7 +104625,6 @@ function printSpendMetricsAccountTable(items, tableOpts) {
|
|
|
104529
104625
|
const rows = items.map((item) => {
|
|
104530
104626
|
const ma = item.ma;
|
|
104531
104627
|
const authStatus = ma.invalidOAuthToken ? "\u26A0\uFE0F \u5931\u6548" : "\u2705 \u6B63\u5E38";
|
|
104532
|
-
const balance = ma.remainingAccountBudget != null ? formatMoneyDisplay(ma.remainingAccountBudget, ma.currencyCode) : "-";
|
|
104533
104628
|
const sharedCount = Array.isArray(ma.accountIds) ? String(Math.max(0, ma.accountIds.length - 1)) : "-";
|
|
104534
104629
|
return {
|
|
104535
104630
|
company: item.mag?.advertiserName ?? "-",
|
|
@@ -104541,7 +104636,6 @@ function printSpendMetricsAccountTable(items, tableOpts) {
|
|
|
104541
104636
|
conversions: fmtNum(ma["conversions"], 0),
|
|
104542
104637
|
clicks: fmtNum(ma["clicks"], 0),
|
|
104543
104638
|
cpc: fmtNum(ma["costPerClick"]),
|
|
104544
|
-
balance,
|
|
104545
104639
|
authStatus,
|
|
104546
104640
|
createdAt: fmtDate(ma.createdDateTime),
|
|
104547
104641
|
shared: sharedCount
|
|
@@ -104553,7 +104647,6 @@ function printYandexTable(items, tableOpts) {
|
|
|
104553
104647
|
const rows = items.map((item) => {
|
|
104554
104648
|
const ma = item.ma;
|
|
104555
104649
|
const authStatus = ma.invalidOAuthToken ? "\u26A0\uFE0F \u5931\u6548" : "\u2705 \u6B63\u5E38";
|
|
104556
|
-
const balance = ma.remainingAccountBudget != null ? formatMoneyDisplay(ma.remainingAccountBudget, ma.currencyCode) : "-";
|
|
104557
104650
|
const sharedCount = Array.isArray(ma.accountIds) ? String(Math.max(0, ma.accountIds.length - 1)) : "-";
|
|
104558
104651
|
const pwd = ma.mediaPassword != null && String(ma.mediaPassword).trim() !== "" ? String(ma.mediaPassword) : "-";
|
|
104559
104652
|
return {
|
|
@@ -104566,7 +104659,6 @@ function printYandexTable(items, tableOpts) {
|
|
|
104566
104659
|
conversions: fmtNum(ma["conversions"], 0),
|
|
104567
104660
|
clicks: fmtNum(ma["clicks"], 0),
|
|
104568
104661
|
cpc: fmtNum(ma["costPerClick"]),
|
|
104569
|
-
balance,
|
|
104570
104662
|
authStatus,
|
|
104571
104663
|
createdAt: fmtDate(ma.createdDateTime),
|
|
104572
104664
|
shared: sharedCount
|
|
@@ -104578,7 +104670,6 @@ function printTikTokTable(items, tableOpts) {
|
|
|
104578
104670
|
const rows = items.map((item) => {
|
|
104579
104671
|
const ma = item.ma;
|
|
104580
104672
|
const authStatus = ma.invalidOAuthToken ? "\u26A0\uFE0F \u5931\u6548" : "\u2705 \u6B63\u5E38";
|
|
104581
|
-
const balance = ma.remainingAccountBudget != null ? formatMoneyDisplay(ma.remainingAccountBudget, ma.currencyCode) : "-";
|
|
104582
104673
|
const sharedCount = Array.isArray(ma.accountIds) ? String(Math.max(0, ma.accountIds.length - 1)) : "-";
|
|
104583
104674
|
return {
|
|
104584
104675
|
company: item.mag?.advertiserName ?? "-",
|
|
@@ -104590,7 +104681,6 @@ function printTikTokTable(items, tableOpts) {
|
|
|
104590
104681
|
conversions: fmtNum(ma["conversions"], 0),
|
|
104591
104682
|
clicks: fmtNum(ma["clicks"], 0),
|
|
104592
104683
|
cpc: fmtNum(ma["costPerClick"]),
|
|
104593
|
-
balance,
|
|
104594
104684
|
bc: formatTikTokBcColumn(ma),
|
|
104595
104685
|
authStatus,
|
|
104596
104686
|
auditStatus: formatTikTokAuditStatus(ma),
|
|
@@ -104605,7 +104695,6 @@ function printGoogleTable(items, tableOpts) {
|
|
|
104605
104695
|
const ma = item.ma;
|
|
104606
104696
|
const authStatus = ma.invalidOAuthToken ? "\u26A0\uFE0F \u5931\u6548" : "\u2705 \u6B63\u5E38";
|
|
104607
104697
|
const platformStatus = ma.platformStatus ?? "-";
|
|
104608
|
-
const balance = ma.remainingAccountBudget != null ? formatMoneyDisplay(ma.remainingAccountBudget, ma.currencyCode) : "-";
|
|
104609
104698
|
const sharedCount = Array.isArray(ma.accountIds) ? String(Math.max(0, ma.accountIds.length - 1)) : "-";
|
|
104610
104699
|
return {
|
|
104611
104700
|
company: item.mag?.advertiserName ?? "-",
|
|
@@ -104617,7 +104706,6 @@ function printGoogleTable(items, tableOpts) {
|
|
|
104617
104706
|
conversions: fmtNum(ma["conversions"], 0),
|
|
104618
104707
|
clicks: fmtNum(ma["clicks"], 0),
|
|
104619
104708
|
cpc: fmtNum(ma["costPerClick"]),
|
|
104620
|
-
balance,
|
|
104621
104709
|
arit: String(ma.aritScore ?? "-"),
|
|
104622
104710
|
platformStatus,
|
|
104623
104711
|
authStatus,
|
|
@@ -104633,7 +104721,6 @@ var COL_DEFAULT_WITH_MEDIA = [
|
|
|
104633
104721
|
{ key: "advertiserName", header: "\u5E7F\u544A\u4E3B" },
|
|
104634
104722
|
{ key: "mediaCustomerName", header: "\u8D26\u6237\u540D\u79F0" },
|
|
104635
104723
|
COL_CURRENCY,
|
|
104636
|
-
{ key: "balance", header: "\u4F59\u989D" },
|
|
104637
104724
|
{ key: "status", header: "\u8D26\u6237\u72B6\u6001" }
|
|
104638
104725
|
];
|
|
104639
104726
|
var COL_DEFAULT_SINGLE = COL_DEFAULT_WITH_MEDIA.filter(
|
|
@@ -104652,14 +104739,12 @@ function printDefaultTable(items, media, tableOpts) {
|
|
|
104652
104739
|
} else {
|
|
104653
104740
|
statusLabel = "\u2705 \u6B63\u5E38";
|
|
104654
104741
|
}
|
|
104655
|
-
const balance = ma.remainingAccountBudget != null ? formatMoneyDisplay(ma.remainingAccountBudget, ma.currencyCode) : "-";
|
|
104656
104742
|
return {
|
|
104657
104743
|
mediaType: ma.mediaAccountType ?? "",
|
|
104658
104744
|
mediaCustomerId: String(ma.mediaCustomerId ?? "(\u672A\u5F00\u901A)"),
|
|
104659
104745
|
advertiserName: item.mag?.advertiserName ?? "",
|
|
104660
104746
|
mediaCustomerName: ma.mediaCustomerName ?? "",
|
|
104661
104747
|
currency: formatCurrencyCodeLabel(ma.currencyCode),
|
|
104662
|
-
balance,
|
|
104663
104748
|
status: statusLabel
|
|
104664
104749
|
};
|
|
104665
104750
|
});
|
|
@@ -104684,7 +104769,6 @@ function wrapListJson(params) {
|
|
|
104684
104769
|
}
|
|
104685
104770
|
|
|
104686
104771
|
// src/commands/list-accounts/main.ts
|
|
104687
|
-
init_balance();
|
|
104688
104772
|
async function runListAccounts(opts) {
|
|
104689
104773
|
let config = loadConfig(opts.token);
|
|
104690
104774
|
if (opts.refreshDp) {
|
|
@@ -104772,81 +104856,7 @@ async function runListAccounts(opts) {
|
|
|
104772
104856
|
process.exit(1);
|
|
104773
104857
|
}
|
|
104774
104858
|
}
|
|
104775
|
-
if (items.length > 0
|
|
104776
|
-
const groups = /* @__PURE__ */ new Map();
|
|
104777
|
-
for (const item of items) {
|
|
104778
|
-
const id = item.ma.mediaCustomerId;
|
|
104779
|
-
const mediaType = item.ma.mediaAccountType ?? opts.media ?? "";
|
|
104780
|
-
if (id && mediaType) {
|
|
104781
|
-
if (!groups.has(mediaType)) groups.set(mediaType, []);
|
|
104782
|
-
groups.get(mediaType).push(id);
|
|
104783
|
-
}
|
|
104784
|
-
}
|
|
104785
|
-
const diagnoseIdToItem = /* @__PURE__ */ new Map();
|
|
104786
|
-
const aritDiagnoseIds = [];
|
|
104787
|
-
for (const item of items) {
|
|
104788
|
-
for (const report of item.ma.diagnoseReports ?? []) {
|
|
104789
|
-
if (report.reportSource === "ARIT") {
|
|
104790
|
-
diagnoseIdToItem.set(report.websiteDiagnoseId, item);
|
|
104791
|
-
aritDiagnoseIds.push(report.websiteDiagnoseId);
|
|
104792
|
-
}
|
|
104793
|
-
}
|
|
104794
|
-
}
|
|
104795
|
-
const [balanceMaps, overviewMaps, aritReports] = await Promise.all([
|
|
104796
|
-
// 各媒体类型余额
|
|
104797
|
-
Promise.all(
|
|
104798
|
-
Array.from(groups.entries()).map(
|
|
104799
|
-
([mediaType, ids]) => fetchBalanceMap(mediaType, ids, config, void 0, void 0, opts.verbose)
|
|
104800
|
-
)
|
|
104801
|
-
),
|
|
104802
|
-
// 各媒体类型消耗数据(MetaAd 不支持,fetchOverviewMap 内部会跳过)
|
|
104803
|
-
Promise.all(
|
|
104804
|
-
Array.from(groups.entries()).map(
|
|
104805
|
-
([mediaType, ids]) => fetchOverviewMap(mediaType, ids, config, void 0, void 0, opts.verbose)
|
|
104806
|
-
)
|
|
104807
|
-
),
|
|
104808
|
-
// ARIT 得分
|
|
104809
|
-
aritDiagnoseIds.length > 0 ? apiFetch2(
|
|
104810
|
-
`${config.apiBaseUrl}/query/WebsiteDiagnoseReport/search?ids=${aritDiagnoseIds.join(",")}`,
|
|
104811
|
-
config,
|
|
104812
|
-
{},
|
|
104813
|
-
opts.verbose
|
|
104814
|
-
).catch(() => []) : Promise.resolve([])
|
|
104815
|
-
]);
|
|
104816
|
-
const globalBalance = /* @__PURE__ */ new Map();
|
|
104817
|
-
for (const m of balanceMaps) {
|
|
104818
|
-
for (const [id, info] of m) globalBalance.set(id, info);
|
|
104819
|
-
}
|
|
104820
|
-
const globalOverview = /* @__PURE__ */ new Map();
|
|
104821
|
-
for (const m of overviewMaps) {
|
|
104822
|
-
for (const [id, info] of m) globalOverview.set(id, info);
|
|
104823
|
-
}
|
|
104824
|
-
for (const item of items) {
|
|
104825
|
-
const id = item.ma.mediaCustomerId;
|
|
104826
|
-
if (!id) continue;
|
|
104827
|
-
const balanceInfo = globalBalance.get(id);
|
|
104828
|
-
if (balanceInfo) {
|
|
104829
|
-
item.ma.remainingAccountBudget = balanceInfo.remainingAccountBudget;
|
|
104830
|
-
item.ma.platformStatus = balanceInfo.status ?? balanceInfo.accountStatus;
|
|
104831
|
-
}
|
|
104832
|
-
const overviewInfo = globalOverview.get(id);
|
|
104833
|
-
if (overviewInfo) {
|
|
104834
|
-
const maExt = item.ma;
|
|
104835
|
-
maExt["spend"] = overviewInfo.spend;
|
|
104836
|
-
maExt["impressions"] = overviewInfo.impressions;
|
|
104837
|
-
maExt["clicks"] = overviewInfo.clicks;
|
|
104838
|
-
maExt["conversions"] = overviewInfo.conversions;
|
|
104839
|
-
const clicks = Number(overviewInfo.clicks ?? 0);
|
|
104840
|
-
maExt["costPerClick"] = clicks > 0 ? Number(overviewInfo.spend ?? 0) / clicks : null;
|
|
104841
|
-
}
|
|
104842
|
-
}
|
|
104843
|
-
for (const report of Array.isArray(aritReports) ? aritReports : []) {
|
|
104844
|
-
const targetItem = diagnoseIdToItem.get(report.websiteDiagnoseId);
|
|
104845
|
-
if (targetItem) {
|
|
104846
|
-
targetItem.ma.aritScore = report.summaryScore ?? "-";
|
|
104847
|
-
}
|
|
104848
|
-
}
|
|
104849
|
-
} else if (items.length > 0) {
|
|
104859
|
+
if (items.length > 0) {
|
|
104850
104860
|
for (const item of items) {
|
|
104851
104861
|
stripListAccountsEnrichmentFields(item);
|
|
104852
104862
|
}
|
|
@@ -104875,13 +104885,9 @@ async function runListAccounts(opts) {
|
|
|
104875
104885
|
return;
|
|
104876
104886
|
}
|
|
104877
104887
|
const totalInfo = total !== void 0 ? `\uFF0C\u5171 ${total} \u6761` : "";
|
|
104878
|
-
|
|
104879
|
-
\u5E7F\u544A\u8D26\u6237\u5217\u8868\uFF08\u7B2C ${page} \u9875\uFF0C\u672C\u9875 ${items.length} \u6761${totalInfo}\uFF09
|
|
104880
|
-
|
|
104881
|
-
listHeader += "\n \uFF08\u9ED8\u8BA4\u5217\u8868\uFF1A\u672A\u5408\u5E76\u4F59\u989D\u4E0E\u6D88\u8017\uFF0CJSON \u4E0D\u542B\u76F8\u5173\u5B57\u6BB5\uFF1B\u52A0 --detail \u53EF\u62C9\u53D6\u771F\u5B9E\u6570\u503C\uFF0C\u54CD\u5E94\u8F83\u6162\u3002TikTok/Meta \u4ECD\u5408\u5E76\u5217\u8868\u63A5\u53E3\u540C\u5305\u5185\u7684 adList\u3002\uFF09";
|
|
104882
|
-
}
|
|
104883
|
-
listHeader += "\n";
|
|
104884
|
-
console.log(listHeader);
|
|
104888
|
+
console.log(`
|
|
104889
|
+
\u5E7F\u544A\u8D26\u6237\u5217\u8868\uFF08\u7B2C ${page} \u9875\uFF0C\u672C\u9875 ${items.length} \u6761${totalInfo}\uFF09
|
|
104890
|
+
`);
|
|
104885
104891
|
if (items.length === 0) {
|
|
104886
104892
|
console.log("\u6682\u65E0\u5E7F\u544A\u8D26\u6237\u6570\u636E\u3002\n");
|
|
104887
104893
|
return;
|
|
@@ -104940,10 +104946,6 @@ function register6(program2) {
|
|
|
104940
104946
|
"--json-out <path>",
|
|
104941
104947
|
"\u843D\u76D8\uFF08\u76EE\u5F55\u6216 *.json \u6587\u4EF6\u8DEF\u5F84\uFF09\u5E76\u66F4\u65B0 cli-manifest[-<\u67E5\u8BE2id>].json\uFF1B\u76EE\u5F55\u6A21\u5F0F\u6587\u4EF6\u540D\u4E3A `<section>[-<\u67E5\u8BE2id>].json`\uFF1Bstdout \u4E00\u884C\u6458\u8981 JSON\uFF0C\u542B outlineFile\uFF08TS \u5F0F\u7C7B\u578B\u5728 `*.outline.txt`\uFF09",
|
|
104942
104948
|
void 0
|
|
104943
|
-
).option(
|
|
104944
|
-
"--detail",
|
|
104945
|
-
"\u5408\u5E76\u4F59\u989D\u4E0E\u8FD1\u671F\u6D88\u8017\uFF08\u53CA\u5C55\u793A/\u70B9\u51FB/\u8F6C\u5316/CPC\u3001Arit \u5F97\u5206\uFF09\uFF1B\u672A\u4F20\u65F6 JSON \u4E0D\u542B\u8FD9\u4E9B\u5B57\u6BB5\uFF0C\u5217\u8868\u66F4\u5FEB",
|
|
104946
|
-
false
|
|
104947
104949
|
).option("--unicode", "\u4F7F\u7528 Unicode \u7EBF\u6846\u8868\u683C\uFF08cli-table3\uFF09\uFF1B\u9ED8\u8BA4 ASCII +-|", false).option("--plain", "\u5DF2\u9ED8\u8BA4 ASCII \u7EBF\u6846\uFF0C\u65E0\u9700\u518D\u4F20\uFF1B\u4FDD\u7559\u517C\u5BB9\u65E7\u811A\u672C", false).option(
|
|
104948
104950
|
"--refresh-dp",
|
|
104949
104951
|
"\u5F3A\u5236\u91CD\u62C9 Datapermission \u540E\u518D\u8BF7\u6C42\u5217\u8868\uFF08\u7528\u4E8E\u300C\u7B2C\u4E8C\u6B21\u62C9\u53D6\u5168\u90E8\u5931\u6548\u300D\u7C7B\u4F1A\u8BDD\u5F02\u5E38\u7684\u4E00\u952E\u6392\u67E5\uFF09",
|
|
@@ -104958,7 +104960,6 @@ function register6(program2) {
|
|
|
104958
104960
|
page: opts.page,
|
|
104959
104961
|
pageSize: opts.pageSize,
|
|
104960
104962
|
jsonOut: opts.jsonOut,
|
|
104961
|
-
detail: opts.detail,
|
|
104962
104963
|
unicode: opts.unicode,
|
|
104963
104964
|
refreshDp: opts.refreshDp,
|
|
104964
104965
|
verbose: opts.verbose
|
|
@@ -105237,6 +105238,7 @@ init_cli_json_snapshot();
|
|
|
105237
105238
|
init_balance();
|
|
105238
105239
|
init_cli_table();
|
|
105239
105240
|
init_currency_display();
|
|
105241
|
+
init_media_account_batch();
|
|
105240
105242
|
|
|
105241
105243
|
// src/commands/balance-scan/list-fetch.ts
|
|
105242
105244
|
init_auth();
|
|
@@ -105437,60 +105439,46 @@ async function runBalanceScan(opts) {
|
|
|
105437
105439
|
}
|
|
105438
105440
|
let balanceMap = /* @__PURE__ */ new Map();
|
|
105439
105441
|
let overviewMap = /* @__PURE__ */ new Map();
|
|
105442
|
+
let batchChunkSize = null;
|
|
105440
105443
|
if (validIds.length > 0) {
|
|
105441
|
-
const
|
|
105442
|
-
const chunks = [];
|
|
105443
|
-
for (let i = 0; i < validIds.length; i += CHUNK) chunks.push(validIds.slice(i, i + CHUNK));
|
|
105444
|
+
const chunkSize = mediaAccountDataBatchSize(media);
|
|
105444
105445
|
process.stderr.write(
|
|
105445
|
-
`\u23F3 [balance-scan] \u6709\u6548\u8D26\u6237 ${validIds.length} \u4E2A\
|
|
105446
|
+
`\u23F3 [balance-scan] \u6709\u6548\u8D26\u6237 ${validIds.length} \u4E2A\uFF1B\u6309 Web \u8D26\u6237\u5217\u8868\u53E3\u5F84\u4E32\u884C\u62C9\u53D6\u6D88\u8017 \u2192 \u4F59\u989D\uFF08\u6BCF\u6279 \u2264${chunkSize} \u6237\uFF0C\u5355\u8BF7\u6C42\u6700\u957F\u7EA6 10 \u5206\u949F\uFF09\u3002
|
|
105446
105447
|
`
|
|
105447
105448
|
);
|
|
105448
|
-
const
|
|
105449
|
-
|
|
105450
|
-
|
|
105449
|
+
const fetched = await fetchBalanceAndOverviewSequential({
|
|
105450
|
+
media,
|
|
105451
|
+
accountIds: validIds,
|
|
105452
|
+
config,
|
|
105453
|
+
verbose: opts.verbose,
|
|
105454
|
+
onChunkStart: (chunkIdx, totalChunks, ids) => {
|
|
105455
|
+
process.stderr.write(
|
|
105456
|
+
` \u2192 [\u8FD17\u65E5\u6D88\u8017] \u7B2C ${chunkIdx}/${totalChunks} \u6279\u8BF7\u6C42\u4E2D\uFF08${ids.length} \u6237\uFF09\u2026
|
|
105451
105457
|
`
|
|
105452
|
-
|
|
105453
|
-
|
|
105454
|
-
|
|
105455
|
-
|
|
105456
|
-
|
|
105458
|
+
);
|
|
105459
|
+
},
|
|
105460
|
+
onOverviewDone: (chunkIdx, totalChunks, ids, size) => {
|
|
105461
|
+
process.stderr.write(
|
|
105462
|
+
` \u2713 [\u8FD17\u65E5\u6D88\u8017] \u7B2C ${chunkIdx}/${totalChunks} \u6279\u5B8C\u6210\uFF08${ids.length} \u6237 \u2192 ${size} \u6761\uFF09
|
|
105457
105463
|
`
|
|
105458
|
-
|
|
105459
|
-
|
|
105460
|
-
|
|
105461
|
-
Promise.all(
|
|
105462
|
-
chunks.map((ids, chunkIdx) => {
|
|
105463
|
-
process.stderr.write(
|
|
105464
|
-
` \u2192 [\u4F59\u989D] \u7B2C ${chunkIdx + 1}/${chunks.length} \u6279\u8BF7\u6C42\u4E2D\uFF08${ids.length} \u6237\uFF09\u2026
|
|
105464
|
+
);
|
|
105465
|
+
process.stderr.write(
|
|
105466
|
+
` \u2192 [\u4F59\u989D] \u7B2C ${chunkIdx}/${totalChunks} \u6279\u8BF7\u6C42\u4E2D\uFF08${ids.length} \u6237\uFF09\u2026
|
|
105465
105467
|
`
|
|
105466
|
-
|
|
105467
|
-
|
|
105468
|
-
|
|
105469
|
-
|
|
105470
|
-
|
|
105471
|
-
}
|
|
105472
|
-
);
|
|
105473
|
-
})
|
|
105474
|
-
),
|
|
105475
|
-
Promise.all(
|
|
105476
|
-
chunks.map((ids, chunkIdx) => {
|
|
105477
|
-
process.stderr.write(
|
|
105478
|
-
` \u2192 [\u8FD17\u65E5\u6D88\u8017] \u7B2C ${chunkIdx + 1}/${chunks.length} \u6279\u8BF7\u6C42\u4E2D\uFF08${ids.length} \u6237\uFF09\u2026
|
|
105468
|
+
);
|
|
105469
|
+
},
|
|
105470
|
+
onBalanceDone: (chunkIdx, totalChunks, ids, size) => {
|
|
105471
|
+
process.stderr.write(
|
|
105472
|
+
` \u2713 [\u4F59\u989D] \u7B2C ${chunkIdx}/${totalChunks} \u6279\u5B8C\u6210\uFF08${ids.length} \u6237 \u2192 ${size} \u6761\uFF09
|
|
105479
105473
|
`
|
|
105480
|
-
|
|
105481
|
-
|
|
105482
|
-
|
|
105483
|
-
|
|
105484
|
-
|
|
105485
|
-
|
|
105486
|
-
);
|
|
105487
|
-
})
|
|
105488
|
-
)
|
|
105489
|
-
]);
|
|
105474
|
+
);
|
|
105475
|
+
}
|
|
105476
|
+
});
|
|
105477
|
+
balanceMap = fetched.balanceMap;
|
|
105478
|
+
overviewMap = fetched.overviewMap;
|
|
105479
|
+
batchChunkSize = fetched.chunkSize;
|
|
105490
105480
|
process.stderr.write(`\u23F3 [balance-scan] \u4F59\u989D\u4E0E\u6D88\u8017\u5DF2\u9F50\uFF0C\u6B63\u5728\u6309\u9608\u503C\u7B5B\u9009\u2026
|
|
105491
105481
|
`);
|
|
105492
|
-
for (const m of bMaps) for (const [k, v] of m) balanceMap.set(k, v);
|
|
105493
|
-
for (const m of oMaps) for (const [k, v] of m) overviewMap.set(k, v);
|
|
105494
105482
|
}
|
|
105495
105483
|
const evaluated = [];
|
|
105496
105484
|
for (const item of allItems) {
|
|
@@ -105547,6 +105535,7 @@ async function runBalanceScan(opts) {
|
|
|
105547
105535
|
},
|
|
105548
105536
|
pageSize,
|
|
105549
105537
|
maxPages,
|
|
105538
|
+
batchChunkSize: validIds.length > 0 ? batchChunkSize : null,
|
|
105550
105539
|
totalReported: total ?? null,
|
|
105551
105540
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
105552
105541
|
};
|
|
@@ -105622,6 +105611,9 @@ function register9(program2) {
|
|
|
105622
105611
|
).requiredOption(
|
|
105623
105612
|
"-m, --media <type>",
|
|
105624
105613
|
"\u5A92\u4F53\u7C7B\u578B\uFF1AGoogle | TikTok | Yandex | BingV2 | Kwai\uFF08MetaAd \u63A5\u53E3\u672A\u5F00\u653E\u4F59\u989D\u67E5\u8BE2\uFF09"
|
|
105614
|
+
).option(
|
|
105615
|
+
"-a, --accounts <ids>",
|
|
105616
|
+
"\u6307\u5B9A mediaCustomerId\uFF08\u9017\u53F7\u5206\u9694\uFF09\uFF1B\u8DF3\u8FC7\u6E05\u5355\u7FFB\u9875\uFF0C\u62C9\u53D6\u540E\u5168\u90E8\u8F93\u51FA\uFF08\u4E0D\u6309\u9608\u503C\u8FC7\u6EE4\uFF09"
|
|
105625
105617
|
).option(
|
|
105626
105618
|
"--threshold-days <n>",
|
|
105627
105619
|
"\u5269\u4F59\u7EED\u822A\u5929\u6570\u9608\u503C\uFF08\u9ED8\u8BA4 7\uFF0C\u6309\u8FD1 7 \u65E5\u65E5\u5747\u6D88\u8017\u4F30\u7B97\uFF09",
|
|
@@ -105647,6 +105639,7 @@ function register9(program2) {
|
|
|
105647
105639
|
await runBalanceScan({
|
|
105648
105640
|
token: opts.token,
|
|
105649
105641
|
media: opts.media,
|
|
105642
|
+
accounts: opts.accounts,
|
|
105650
105643
|
thresholdDays: opts.thresholdDays,
|
|
105651
105644
|
minBalance: opts.minBalance,
|
|
105652
105645
|
minDailySpend: opts.minDailySpend,
|
|
@@ -106000,12 +105993,12 @@ init_cli_json();
|
|
|
106000
105993
|
import * as fs8 from "fs/promises";
|
|
106001
105994
|
import * as path12 from "path";
|
|
106002
105995
|
var REPORT_MANIFEST_FILE = "report-manifest.json";
|
|
106003
|
-
var
|
|
105996
|
+
var CLI_PACKAGE2 = "siluzan-tso-cli";
|
|
106004
105997
|
var SCHEMA_VERSION3 = 1;
|
|
106005
105998
|
function reportManifestFile(accountId) {
|
|
106006
105999
|
return `${applyIdSuffix("report-manifest", accountId)}.json`;
|
|
106007
106000
|
}
|
|
106008
|
-
var
|
|
106001
|
+
var DEFAULT_FIELD_GUIDE2 = {
|
|
106009
106002
|
markdownRefs: ["references/analytics/account-analytics.md"]
|
|
106010
106003
|
};
|
|
106011
106004
|
function sectionToFilename(section, accountId) {
|
|
@@ -106038,11 +106031,11 @@ function mergeReportAnalysisManifest(existing, params) {
|
|
|
106038
106031
|
const prevArts = existing?.artifacts ?? [];
|
|
106039
106032
|
const without = prevArts.filter((a) => a.section !== params.newArtifact.section);
|
|
106040
106033
|
const artifacts = [...without, params.newArtifact];
|
|
106041
|
-
const fieldGuide = existing?.fieldGuide ??
|
|
106034
|
+
const fieldGuide = existing?.fieldGuide ?? DEFAULT_FIELD_GUIDE2;
|
|
106042
106035
|
return {
|
|
106043
106036
|
schemaVersion: SCHEMA_VERSION3,
|
|
106044
106037
|
kind: "report-analysis",
|
|
106045
|
-
cliPackage:
|
|
106038
|
+
cliPackage: CLI_PACKAGE2,
|
|
106046
106039
|
cliVersion: params.cliVersion,
|
|
106047
106040
|
accountId: params.accountId ?? existing?.accountId,
|
|
106048
106041
|
dateRange: params.dateRange ?? existing?.dateRange ?? null,
|
|
@@ -106260,7 +106253,12 @@ function buildMetaOverviewUrl(config, accountId, mediaAccountType, startDate, en
|
|
|
106260
106253
|
const { apiId, manifestId } = normalizeFacebookAccountId(accountId);
|
|
106261
106254
|
const params = new URLSearchParams({ startDate, endDate }).toString();
|
|
106262
106255
|
if (mediaAccountType === "FacebookAds") {
|
|
106263
|
-
return facebookReportingUrl(
|
|
106256
|
+
return facebookReportingUrl(
|
|
106257
|
+
config,
|
|
106258
|
+
apiId,
|
|
106259
|
+
"OverviewSectionData",
|
|
106260
|
+
params
|
|
106261
|
+
);
|
|
106264
106262
|
}
|
|
106265
106263
|
return `${config.apiBaseUrl}/reporting/media-account/MetaAd/${manifestId}/OverviewSectionData?${params}`;
|
|
106266
106264
|
}
|
|
@@ -106384,9 +106382,7 @@ async function lookupAccountsByCustomerIds(apiBaseUrl, config, mediaType, custom
|
|
|
106384
106382
|
{},
|
|
106385
106383
|
verbose
|
|
106386
106384
|
);
|
|
106387
|
-
const idSet = new Set(
|
|
106388
|
-
customerIds.map((id) => metaOverviewManifestId(id.trim())).filter(Boolean)
|
|
106389
|
-
);
|
|
106385
|
+
const idSet = new Set(customerIds.map((id) => metaOverviewManifestId(id.trim())).filter(Boolean));
|
|
106390
106386
|
const result = /* @__PURE__ */ new Map();
|
|
106391
106387
|
const items = Array.isArray(data) ? data : [];
|
|
106392
106388
|
for (const item of items) {
|
|
@@ -110038,15 +110034,24 @@ function validateStructuredSnippetExtension(prefix, rec, errors) {
|
|
|
110038
110034
|
return;
|
|
110039
110035
|
}
|
|
110040
110036
|
if (typeof ssv !== "object" || Array.isArray(ssv)) {
|
|
110041
|
-
pushErr(
|
|
110037
|
+
pushErr(
|
|
110038
|
+
errors,
|
|
110039
|
+
`${prefix}.StructuredSnippetHeaderValue \u5FC5\u987B\u662F\u5BF9\u8C61 { Key: string; Value: string[] }`
|
|
110040
|
+
);
|
|
110042
110041
|
return;
|
|
110043
110042
|
}
|
|
110044
110043
|
const sv = ssv;
|
|
110045
110044
|
if (typeof sv["Key"] !== "string" || !sv["Key"].trim()) {
|
|
110046
|
-
pushErr(
|
|
110045
|
+
pushErr(
|
|
110046
|
+
errors,
|
|
110047
|
+
`${prefix}.StructuredSnippetHeaderValue.Key \u4E0D\u80FD\u4E3A\u7A7A\uFF08\u7247\u6BB5\u6807\u9898\uFF0C\u5982 "Services"\uFF09`
|
|
110048
|
+
);
|
|
110047
110049
|
}
|
|
110048
110050
|
if (!Array.isArray(sv["Value"]) || sv["Value"].length === 0) {
|
|
110049
|
-
pushErr(
|
|
110051
|
+
pushErr(
|
|
110052
|
+
errors,
|
|
110053
|
+
`${prefix}.StructuredSnippetHeaderValue.Value \u4E0D\u80FD\u4E3A\u7A7A\u6570\u7EC4\uFF08\u81F3\u5C11\u586B\u4E00\u4E2A\u7247\u6BB5\u503C\uFF09`
|
|
110054
|
+
);
|
|
110050
110055
|
}
|
|
110051
110056
|
}
|
|
110052
110057
|
function validateCampaignExtensionsForBatchJob(campaign, errors, warnings, lengthViolations = []) {
|
|
@@ -110072,7 +110077,13 @@ function validateCampaignExtensionsForBatchJob(campaign, errors, warnings, lengt
|
|
|
110072
110077
|
continue;
|
|
110073
110078
|
}
|
|
110074
110079
|
if (props && typeof props === "object") {
|
|
110075
|
-
validateSitelinkProperties(
|
|
110080
|
+
validateSitelinkProperties(
|
|
110081
|
+
prefix,
|
|
110082
|
+
props,
|
|
110083
|
+
errors,
|
|
110084
|
+
warnings,
|
|
110085
|
+
lengthViolations
|
|
110086
|
+
);
|
|
110076
110087
|
} else {
|
|
110077
110088
|
pushErr(errors, `${prefix}\uFF08SITELINK\uFF09\u7F3A\u5C11 Properties`);
|
|
110078
110089
|
}
|
|
@@ -110709,7 +110720,10 @@ function normalizeKeywordTextList(texts, matchTypeRaw, path27, fieldLabel, error
|
|
|
110709
110720
|
}
|
|
110710
110721
|
const trimmedRaw = collapseDuplicateSpacesInKeywordText(raw.trim());
|
|
110711
110722
|
const beforeUi = inferMatchTypeFromDisplayText(trimmedRaw);
|
|
110712
|
-
const { formatted, matchType, inferredMatchType } = normalizeKeywordSurface(
|
|
110723
|
+
const { formatted, matchType, inferredMatchType } = normalizeKeywordSurface(
|
|
110724
|
+
trimmedRaw,
|
|
110725
|
+
matchTypeRaw
|
|
110726
|
+
);
|
|
110713
110727
|
pushKeywordAutoFixWarning(
|
|
110714
110728
|
warnings,
|
|
110715
110729
|
path27,
|
|
@@ -110722,7 +110736,8 @@ function normalizeKeywordTextList(texts, matchTypeRaw, path27, fieldLabel, error
|
|
|
110722
110736
|
inferredMatchType
|
|
110723
110737
|
);
|
|
110724
110738
|
const core = unwrapKeywordDisplayTextForEdit(formatted);
|
|
110725
|
-
if (!validateKeywordCore(core, `${path27}.${fieldLabel}[${k}]`, errors, lengthViolations))
|
|
110739
|
+
if (!validateKeywordCore(core, `${path27}.${fieldLabel}[${k}]`, errors, lengthViolations))
|
|
110740
|
+
continue;
|
|
110726
110741
|
const dedupeKey = `${matchTypeUiToV2(matchType)}:${core.toLowerCase()}`;
|
|
110727
110742
|
if (seen.has(dedupeKey)) {
|
|
110728
110743
|
warnings.push(`${path27}.${fieldLabel}[${k}] \u4E0E\u540C\u7EC4\u91CD\u590D\uFF0C\u5DF2\u8DF3\u8FC7\uFF1A${formatted}`);
|
|
@@ -110781,7 +110796,10 @@ function normalizeKeywordsForBatchJobBlock(block, path27, errors, warnings, leng
|
|
|
110781
110796
|
}
|
|
110782
110797
|
const trimmedRaw = collapseDuplicateSpacesInKeywordText(raw.trim());
|
|
110783
110798
|
const beforeUi = inferMatchTypeFromDisplayText(trimmedRaw);
|
|
110784
|
-
const { formatted, matchType, inferredMatchType } = normalizeKeywordSurface(
|
|
110799
|
+
const { formatted, matchType, inferredMatchType } = normalizeKeywordSurface(
|
|
110800
|
+
trimmedRaw,
|
|
110801
|
+
matchTypeRaw
|
|
110802
|
+
);
|
|
110785
110803
|
pushKeywordAutoFixWarning(
|
|
110786
110804
|
warnings,
|
|
110787
110805
|
path27,
|
|
@@ -110794,7 +110812,8 @@ function normalizeKeywordsForBatchJobBlock(block, path27, errors, warnings, leng
|
|
|
110794
110812
|
inferredMatchType
|
|
110795
110813
|
);
|
|
110796
110814
|
const core = unwrapKeywordDisplayTextForEdit(formatted);
|
|
110797
|
-
if (!validateKeywordCore(core, `${path27}.Items[${k}].Text`, errors, lengthViolations))
|
|
110815
|
+
if (!validateKeywordCore(core, `${path27}.Items[${k}].Text`, errors, lengthViolations))
|
|
110816
|
+
continue;
|
|
110798
110817
|
const dedupeKey = `${matchTypeUiToV2(matchType)}:${core.toLowerCase()}`;
|
|
110799
110818
|
if (seen.has(dedupeKey)) {
|
|
110800
110819
|
warnings.push(`${path27}.Items[${k}].Text \u4E0E\u540C\u7EC4\u91CD\u590D\uFF0C\u5DF2\u8DF3\u8FC7\uFF1A${formatted}`);
|
|
@@ -110859,6 +110878,104 @@ function normalizeCampaignKeywordTrees(campaign, errors, warnings, lengthViolati
|
|
|
110859
110878
|
);
|
|
110860
110879
|
}
|
|
110861
110880
|
}
|
|
110881
|
+
warnIfNegativeKeywordsInPositiveKeywords(campaign, warnings);
|
|
110882
|
+
}
|
|
110883
|
+
var COMMON_NEGATIVE_KEYWORD_STEMS = /* @__PURE__ */ new Set([
|
|
110884
|
+
"free",
|
|
110885
|
+
"torrent",
|
|
110886
|
+
"crack",
|
|
110887
|
+
"hack",
|
|
110888
|
+
"pirated",
|
|
110889
|
+
"jobs",
|
|
110890
|
+
"job",
|
|
110891
|
+
"salary",
|
|
110892
|
+
"career",
|
|
110893
|
+
"hiring",
|
|
110894
|
+
"interview",
|
|
110895
|
+
"diy",
|
|
110896
|
+
"tutorial",
|
|
110897
|
+
"scam",
|
|
110898
|
+
"fraud",
|
|
110899
|
+
"complaint",
|
|
110900
|
+
"lawsuit",
|
|
110901
|
+
"reddit",
|
|
110902
|
+
"quora",
|
|
110903
|
+
"wiki",
|
|
110904
|
+
"youtube",
|
|
110905
|
+
"sample",
|
|
110906
|
+
"template",
|
|
110907
|
+
"example",
|
|
110908
|
+
"\u514D\u8D39",
|
|
110909
|
+
"\u6559\u7A0B",
|
|
110910
|
+
"\u62DB\u8058",
|
|
110911
|
+
"\u9A97\u5C40",
|
|
110912
|
+
"\u7834\u89E3",
|
|
110913
|
+
"\u76D7\u7248",
|
|
110914
|
+
"\u6837\u672C",
|
|
110915
|
+
"\u6A21\u677F"
|
|
110916
|
+
]);
|
|
110917
|
+
var NEGATIVE_PHRASE_PREFIXES = ["how to make", "how to ", "diy ", "download free"];
|
|
110918
|
+
function normalizeKeywordCoreForNegativeCheck(raw) {
|
|
110919
|
+
return unwrapKeywordDisplayTextForEdit(raw).trim().toLowerCase();
|
|
110920
|
+
}
|
|
110921
|
+
function collectNegativeCoresFromBlocks(blocks) {
|
|
110922
|
+
const cores = /* @__PURE__ */ new Set();
|
|
110923
|
+
if (!Array.isArray(blocks)) return cores;
|
|
110924
|
+
for (const block of blocks) {
|
|
110925
|
+
if (!block || typeof block !== "object") continue;
|
|
110926
|
+
const texts = block["KeywordText"];
|
|
110927
|
+
if (!Array.isArray(texts)) continue;
|
|
110928
|
+
for (const t of texts) {
|
|
110929
|
+
if (typeof t === "string" && t.trim()) {
|
|
110930
|
+
cores.add(normalizeKeywordCoreForNegativeCheck(t));
|
|
110931
|
+
}
|
|
110932
|
+
}
|
|
110933
|
+
}
|
|
110934
|
+
return cores;
|
|
110935
|
+
}
|
|
110936
|
+
function looksLikeStandaloneNegativeKeyword(core) {
|
|
110937
|
+
const lower = core.toLowerCase();
|
|
110938
|
+
if (COMMON_NEGATIVE_KEYWORD_STEMS.has(lower)) return true;
|
|
110939
|
+
for (const p of NEGATIVE_PHRASE_PREFIXES) {
|
|
110940
|
+
if (lower.startsWith(p)) return true;
|
|
110941
|
+
}
|
|
110942
|
+
const tokens = lower.split(/\s+/).filter(Boolean);
|
|
110943
|
+
if (tokens.length === 1 && COMMON_NEGATIVE_KEYWORD_STEMS.has(tokens[0])) return true;
|
|
110944
|
+
if (tokens.length >= 2 && tokens.length <= 4) {
|
|
110945
|
+
if (tokens[0] && COMMON_NEGATIVE_KEYWORD_STEMS.has(tokens[0])) return true;
|
|
110946
|
+
if (tokens.every((t) => COMMON_NEGATIVE_KEYWORD_STEMS.has(t))) return true;
|
|
110947
|
+
}
|
|
110948
|
+
return false;
|
|
110949
|
+
}
|
|
110950
|
+
function warnIfNegativeKeywordsInPositiveKeywords(campaign, warnings) {
|
|
110951
|
+
const plannedNegCores = collectNegativeCoresFromBlocks(campaign["NegativeKeywordsForBatchJob"]);
|
|
110952
|
+
const groups = campaign["AdGroupsForBatchJob"];
|
|
110953
|
+
if (!Array.isArray(groups)) return;
|
|
110954
|
+
for (let gi = 0; gi < groups.length; gi++) {
|
|
110955
|
+
const kws = groups[gi]?.["KeywordsForBatchJob"];
|
|
110956
|
+
if (!Array.isArray(kws)) continue;
|
|
110957
|
+
for (let bi = 0; bi < kws.length; bi++) {
|
|
110958
|
+
const texts = kws[bi]?.["KeywordText"];
|
|
110959
|
+
if (!Array.isArray(texts)) continue;
|
|
110960
|
+
for (let ki = 0; ki < texts.length; ki++) {
|
|
110961
|
+
const raw = texts[ki];
|
|
110962
|
+
if (typeof raw !== "string" || !raw.trim()) continue;
|
|
110963
|
+
const core = normalizeKeywordCoreForNegativeCheck(raw);
|
|
110964
|
+
const path27 = `campaign.AdGroupsForBatchJob[${gi}].KeywordsForBatchJob[${bi}].KeywordText[${ki}]`;
|
|
110965
|
+
if (plannedNegCores.has(core)) {
|
|
110966
|
+
warnings.push(
|
|
110967
|
+
`${path27} \u8BCD\u9762\u300C${raw}\u300D\u5DF2\u51FA\u73B0\u5728 campaign.NegativeKeywordsForBatchJob\uFF0C\u4E0D\u5E94\u91CD\u590D\u5199\u5165\u6B63\u5411 KeywordsForBatchJob\uFF1B\u5426\u8BCD\u53EA\u653E NegativeKeywordsForBatchJob`
|
|
110968
|
+
);
|
|
110969
|
+
continue;
|
|
110970
|
+
}
|
|
110971
|
+
if (looksLikeStandaloneNegativeKeyword(core)) {
|
|
110972
|
+
warnings.push(
|
|
110973
|
+
`${path27} \u8BCD\u9762\u300C${raw}\u300D\u7591\u4F3C\u5426\u5B9A\u5173\u952E\u8BCD\uFF08\u5E38\u89C1\u5426\u8BCD\u8BCD\u6839\uFF09\uFF0C\u5E94\u5199\u5165 campaign.NegativeKeywordsForBatchJob\uFF08MatchTypeV2: BROAD\uFF09\uFF0C\u52FF\u653E\u5165 KeywordsForBatchJob`
|
|
110974
|
+
);
|
|
110975
|
+
}
|
|
110976
|
+
}
|
|
110977
|
+
}
|
|
110978
|
+
}
|
|
110862
110979
|
}
|
|
110863
110980
|
|
|
110864
110981
|
// src/commands/ad/campaign-rsa.ts
|
|
@@ -111010,10 +111127,7 @@ function validateRsaDisplayPath(prefix, ad, errors, lengthViolations) {
|
|
|
111010
111127
|
continue;
|
|
111011
111128
|
}
|
|
111012
111129
|
if (typeof raw !== "string") {
|
|
111013
|
-
pushErr2(
|
|
111014
|
-
errors,
|
|
111015
|
-
`${prefix}.${field} \u5FC5\u987B\u662F\u5B57\u7B26\u4E32\uFF0C\u5F53\u524D\u7C7B\u578B\u4E3A ${typeof raw}\u3002${jsonHint}`
|
|
111016
|
-
);
|
|
111130
|
+
pushErr2(errors, `${prefix}.${field} \u5FC5\u987B\u662F\u5B57\u7B26\u4E32\uFF0C\u5F53\u524D\u7C7B\u578B\u4E3A ${typeof raw}\u3002${jsonHint}`);
|
|
111017
111131
|
continue;
|
|
111018
111132
|
}
|
|
111019
111133
|
const len = calcGoogleCharLength(raw);
|
|
@@ -111027,10 +111141,7 @@ function validateRsaDisplayPath(prefix, ad, errors, lengthViolations) {
|
|
|
111027
111141
|
countMode: "google",
|
|
111028
111142
|
text: raw
|
|
111029
111143
|
});
|
|
111030
|
-
pushErr2(
|
|
111031
|
-
errors,
|
|
111032
|
-
`${prefix}.${field} \u8D85\u8FC7 15 \u5B57\u7B26\uFF08\u5F53\u524D ${len}\uFF0CCJK \u6309 2 \u8BA1\uFF09\uFF1A"${raw}"`
|
|
111033
|
-
);
|
|
111144
|
+
pushErr2(errors, `${prefix}.${field} \u8D85\u8FC7 15 \u5B57\u7B26\uFF08\u5F53\u524D ${len}\uFF0CCJK \u6309 2 \u8BA1\uFF09\uFF1A"${raw}"`);
|
|
111034
111145
|
}
|
|
111035
111146
|
if (raw.length > 0 && !PATH_SEGMENT_REGEX.test(raw)) {
|
|
111036
111147
|
pushErr2(
|
|
@@ -111187,10 +111298,7 @@ function validateCampaignCreateConfigCore(cfg) {
|
|
|
111187
111298
|
);
|
|
111188
111299
|
}
|
|
111189
111300
|
if (campaign["TargetContentNetwork"] === true) {
|
|
111190
|
-
pushErr2(
|
|
111191
|
-
errors,
|
|
111192
|
-
"campaign.TargetContentNetwork \u5FC5\u987B\u4E3A false\uFF08\u641C\u7D22\u4E13\u5C5E\u65B9\u6848\uFF1A\u7981\u6B62\u5C55\u793A\u5E7F\u544A\u7F51\u7EDC\uFF09"
|
|
111193
|
-
);
|
|
111301
|
+
pushErr2(errors, "campaign.TargetContentNetwork \u5FC5\u987B\u4E3A false\uFF08\u641C\u7D22\u4E13\u5C5E\u65B9\u6848\uFF1A\u7981\u6B62\u5C55\u793A\u5E7F\u544A\u7F51\u7EDC\uFF09");
|
|
111194
111302
|
}
|
|
111195
111303
|
const name2 = campaign["Name"];
|
|
111196
111304
|
if (typeof name2 !== "string" || !name2.trim()) {
|
|
@@ -111254,10 +111362,7 @@ function validateCampaignCreateConfigCore(cfg) {
|
|
|
111254
111362
|
if (typeof roas !== "number" || !Number.isFinite(roas) || roas <= 0) {
|
|
111255
111363
|
pushErr2(errors, "TARGET_ROAS \u51FA\u4EF7\u7B56\u7565\u4E0B campaign.TargetRoas \u5FC5\u987B\u4E3A\u6B63\u6570");
|
|
111256
111364
|
} else if (roas > 1e3) {
|
|
111257
|
-
pushWarn2(
|
|
111258
|
-
warnings,
|
|
111259
|
-
`campaign.TargetRoas=${roas} \u5F02\u5E38\u504F\u5927\uFF08250% ROAS \u5E94\u586B 2.5\uFF0C\u800C\u975E 250\uFF09`
|
|
111260
|
-
);
|
|
111365
|
+
pushWarn2(warnings, `campaign.TargetRoas=${roas} \u5F02\u5E38\u504F\u5927\uFF08250% ROAS \u5E94\u586B 2.5\uFF0C\u800C\u975E 250\uFF09`);
|
|
111261
111366
|
}
|
|
111262
111367
|
}
|
|
111263
111368
|
}
|
|
@@ -111302,7 +111407,15 @@ function validateCampaignCreateConfigCore(cfg) {
|
|
|
111302
111407
|
pushErr2(errors, `campaign.EndTime\uFF08${end}\uFF09\u5FC5\u987B\u665A\u4E8E StartTime\uFF08${start}\uFF09`);
|
|
111303
111408
|
}
|
|
111304
111409
|
}
|
|
111305
|
-
const VALID_DAY_OF_WEEK_V2 = [
|
|
111410
|
+
const VALID_DAY_OF_WEEK_V2 = [
|
|
111411
|
+
"Monday",
|
|
111412
|
+
"Tuesday",
|
|
111413
|
+
"Wednesday",
|
|
111414
|
+
"Thursday",
|
|
111415
|
+
"Friday",
|
|
111416
|
+
"Saturday",
|
|
111417
|
+
"Sunday"
|
|
111418
|
+
];
|
|
111306
111419
|
const VALID_MINUTE_OF_HOUR_V2 = ["Zero", "Fifteen", "Thirty", "FortyFive"];
|
|
111307
111420
|
const adSchedules = campaign["AdSchedules"];
|
|
111308
111421
|
if (Array.isArray(adSchedules) && adSchedules.length > 0) {
|
|
@@ -111385,7 +111498,13 @@ function validateCampaignCreateConfigCore(cfg) {
|
|
|
111385
111498
|
for (let j = 0; j < ads.length; j++) {
|
|
111386
111499
|
const ad = ads[j];
|
|
111387
111500
|
if (ad && typeof ad === "object") {
|
|
111388
|
-
validateRsaAd(
|
|
111501
|
+
validateRsaAd(
|
|
111502
|
+
`${gPrefix}.AdsForBatchJob[${j}]`,
|
|
111503
|
+
ad,
|
|
111504
|
+
errors,
|
|
111505
|
+
warnings,
|
|
111506
|
+
lengthViolations
|
|
111507
|
+
);
|
|
111389
111508
|
}
|
|
111390
111509
|
}
|
|
111391
111510
|
}
|
|
@@ -111406,7 +111525,10 @@ function validateCampaignCreateConfigCore(cfg) {
|
|
|
111406
111525
|
for (let k = 0; k < texts.length; k++) {
|
|
111407
111526
|
const kw = texts[k];
|
|
111408
111527
|
if (typeof kw !== "string" || !kw.trim()) {
|
|
111409
|
-
pushErr2(
|
|
111528
|
+
pushErr2(
|
|
111529
|
+
errors,
|
|
111530
|
+
`${gPrefix}.KeywordsForBatchJob[${j}].KeywordText[${k}] \u4E0D\u80FD\u4E3A\u7A7A\u5B57\u7B26\u4E32`
|
|
111531
|
+
);
|
|
111410
111532
|
}
|
|
111411
111533
|
}
|
|
111412
111534
|
}
|
|
@@ -111845,7 +111967,9 @@ async function runAdCampaignCreate(opts) {
|
|
|
111845
111967
|
);
|
|
111846
111968
|
if (taskId) {
|
|
111847
111969
|
console.log(` \u4EFB\u52A1 ID\uFF1A${String(taskId)}`);
|
|
111848
|
-
console.log(
|
|
111970
|
+
console.log(
|
|
111971
|
+
` 1. \u8F6E\u8BE2\uFF1Asiluzan-tso ad batch get --id ${String(taskId)} --config-file ${opts.configFile}`
|
|
111972
|
+
);
|
|
111849
111973
|
console.log(
|
|
111850
111974
|
` 2. \u6210\u529F\u6216\u90E8\u5206\u6210\u529F\u540E\u6BD4\u5BF9\uFF1Asiluzan-tso ad batch diff --batch-id ${String(taskId)} --config-file ${opts.configFile}`
|
|
111851
111975
|
);
|
|
@@ -111907,11 +112031,9 @@ function validateCampaignEditBidding(opts) {
|
|
|
111907
112031
|
process.exit(1);
|
|
111908
112032
|
}
|
|
111909
112033
|
if (s === "TARGET_ROAS" && opts.targetRoas != null && opts.targetRoas > 100) {
|
|
111910
|
-
console.error(
|
|
111911
|
-
`
|
|
112034
|
+
console.error(`
|
|
111912
112035
|
\u274C --target-roas=${opts.targetRoas} \u7591\u4F3C\u8BEF\u586B\u767E\u5206\u6570\uFF1B250% ROAS \u5E94\u586B 2.5
|
|
111913
|
-
`
|
|
111914
|
-
);
|
|
112036
|
+
`);
|
|
111915
112037
|
process.exit(1);
|
|
111916
112038
|
}
|
|
111917
112039
|
}
|
|
@@ -111969,7 +112091,8 @@ async function runAdCampaignEdit(opts) {
|
|
|
111969
112091
|
const parts = [];
|
|
111970
112092
|
if (opts.name !== void 0) parts.push(`\u540D\u79F0\u2192${opts.name}`);
|
|
111971
112093
|
if (opts.budget !== void 0) parts.push(`\u9884\u7B97\u2192${opts.budget}`);
|
|
111972
|
-
if (opts.biddingStrategy !== void 0)
|
|
112094
|
+
if (opts.biddingStrategy !== void 0)
|
|
112095
|
+
parts.push(`\u51FA\u4EF7\u7B56\u7565\u2192${opts.biddingStrategy.toUpperCase()}`);
|
|
111973
112096
|
if (opts.targetSpendBidCeiling !== void 0) parts.push(`CPC\u4E0A\u9650\u2192${opts.targetSpendBidCeiling}`);
|
|
111974
112097
|
if (opts.targetCpa !== void 0) parts.push(`\u76EE\u6807CPA\u2192${opts.targetCpa}`);
|
|
111975
112098
|
if (opts.targetRoas !== void 0) parts.push(`\u76EE\u6807ROAS\u2192${opts.targetRoas}`);
|
|
@@ -112919,9 +113042,11 @@ async function runAdExtensionPmaxTypes(opts) {
|
|
|
112919
113042
|
try {
|
|
112920
113043
|
data = await apiFetch2(url, config, {}, opts.verbose);
|
|
112921
113044
|
} catch (err) {
|
|
112922
|
-
console.error(
|
|
113045
|
+
console.error(
|
|
113046
|
+
`
|
|
112923
113047
|
\u274C \u67E5\u8BE2 PMax \u9644\u52A0\u8D44\u4EA7\u7C7B\u578B\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
|
|
112924
|
-
`
|
|
113048
|
+
`
|
|
113049
|
+
);
|
|
112925
113050
|
process.exit(1);
|
|
112926
113051
|
}
|
|
112927
113052
|
if (await emitCliJsonOrSnapshot(opts, {
|
|
@@ -112945,7 +113070,9 @@ async function runAdExtensionPmaxTypes(opts) {
|
|
|
112945
113070
|
console.log(` Lead Form \u5C42\u7EA7\uFF1A${lfLevels}`);
|
|
112946
113071
|
console.log(` WhatsApp\uFF08BUSINESS_MESSAGE\uFF09\u5C42\u7EA7\uFF1A${bmLevels}`);
|
|
112947
113072
|
console.log(" \u4E0D\u652F\u6301\uFF1AAd Group\uFF08PMax \u4F1A 400\uFF09");
|
|
112948
|
-
console.log(
|
|
113073
|
+
console.log(
|
|
113074
|
+
" WhatsApp \u9700 Google API \u767D\u540D\u5355\uFF08\u672A\u5F00\u901A\u4F1A CUSTOMER_NOT_ON_ALLOWLIST_FOR_MESSAGE_ASSETS\uFF09\n"
|
|
113075
|
+
);
|
|
112949
113076
|
}
|
|
112950
113077
|
async function runAdExtensionSnippetHeaders(opts) {
|
|
112951
113078
|
const config = loadConfig(opts.token);
|
|
@@ -113013,9 +113140,11 @@ async function runAdExtensionLeadFormCreate(opts) {
|
|
|
113013
113140
|
opts.verbose
|
|
113014
113141
|
);
|
|
113015
113142
|
} catch (err) {
|
|
113016
|
-
console.error(
|
|
113143
|
+
console.error(
|
|
113144
|
+
`
|
|
113017
113145
|
\u274C \u521B\u5EFA Lead Form \u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
|
|
113018
|
-
`
|
|
113146
|
+
`
|
|
113147
|
+
);
|
|
113019
113148
|
process.exit(1);
|
|
113020
113149
|
}
|
|
113021
113150
|
if (await emitCliJsonOrSnapshot(opts, {
|
|
@@ -113067,9 +113196,11 @@ async function runAdExtensionWhatsappCreate(opts) {
|
|
|
113067
113196
|
opts.verbose
|
|
113068
113197
|
);
|
|
113069
113198
|
} catch (err) {
|
|
113070
|
-
console.error(
|
|
113199
|
+
console.error(
|
|
113200
|
+
`
|
|
113071
113201
|
\u274C \u521B\u5EFA WhatsApp \u6269\u5C55\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
|
|
113072
|
-
`
|
|
113202
|
+
`
|
|
113203
|
+
);
|
|
113073
113204
|
process.exit(1);
|
|
113074
113205
|
}
|
|
113075
113206
|
if (await emitCliJsonOrSnapshot(opts, {
|
|
@@ -113320,9 +113451,11 @@ async function runAdDeviceBidList(opts) {
|
|
|
113320
113451
|
try {
|
|
113321
113452
|
data = await apiFetch2(url, config, {}, opts.verbose);
|
|
113322
113453
|
} catch (err) {
|
|
113323
|
-
console.error(
|
|
113454
|
+
console.error(
|
|
113455
|
+
`
|
|
113324
113456
|
\u274C \u67E5\u8BE2\u5E7F\u544A\u7EC4\u8BBE\u5907\u51FA\u4EF7\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
|
|
113325
|
-
`
|
|
113457
|
+
`
|
|
113458
|
+
);
|
|
113326
113459
|
process.exit(1);
|
|
113327
113460
|
}
|
|
113328
113461
|
items = (Array.isArray(data) ? data : []).map((row) => ({
|
|
@@ -113342,9 +113475,11 @@ async function runAdDeviceBidList(opts) {
|
|
|
113342
113475
|
try {
|
|
113343
113476
|
data = await apiFetch2(url, config, {}, opts.verbose);
|
|
113344
113477
|
} catch (err) {
|
|
113345
|
-
console.error(
|
|
113478
|
+
console.error(
|
|
113479
|
+
`
|
|
113346
113480
|
\u274C \u67E5\u8BE2\u7CFB\u5217\u8BBE\u5907\u51FA\u4EF7\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
|
|
113347
|
-
`
|
|
113481
|
+
`
|
|
113482
|
+
);
|
|
113348
113483
|
process.exit(1);
|
|
113349
113484
|
}
|
|
113350
113485
|
let rows = Array.isArray(data) ? data : [];
|
|
@@ -113414,9 +113549,11 @@ async function runAdDeviceBidSet(opts) {
|
|
|
113414
113549
|
try {
|
|
113415
113550
|
list2 = await apiFetch2(listUrl2, config, {}, opts.verbose);
|
|
113416
113551
|
} catch (err) {
|
|
113417
|
-
console.error(
|
|
113552
|
+
console.error(
|
|
113553
|
+
`
|
|
113418
113554
|
\u274C \u62C9\u53D6\u5E7F\u544A\u7EC4\u8BBE\u5907\u51FA\u4EF7\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
|
|
113419
|
-
`
|
|
113555
|
+
`
|
|
113556
|
+
);
|
|
113420
113557
|
process.exit(1);
|
|
113421
113558
|
}
|
|
113422
113559
|
const criterionId2 = resolveCriterionId(Array.isArray(list2) ? list2 : [], {
|
|
@@ -113444,9 +113581,11 @@ async function runAdDeviceBidSet(opts) {
|
|
|
113444
113581
|
try {
|
|
113445
113582
|
await apiFetch2(putUrl2, config, { method: "PUT", body: JSON.stringify(body) }, opts.verbose);
|
|
113446
113583
|
} catch (err) {
|
|
113447
|
-
console.error(
|
|
113584
|
+
console.error(
|
|
113585
|
+
`
|
|
113448
113586
|
\u274C \u4FEE\u6539\u5E7F\u544A\u7EC4\u8BBE\u5907\u51FA\u4EF7\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
|
|
113449
|
-
`
|
|
113587
|
+
`
|
|
113588
|
+
);
|
|
113450
113589
|
process.exit(1);
|
|
113451
113590
|
}
|
|
113452
113591
|
console.log(
|
|
@@ -113461,9 +113600,11 @@ async function runAdDeviceBidSet(opts) {
|
|
|
113461
113600
|
try {
|
|
113462
113601
|
list = await apiFetch2(listUrl, config, {}, opts.verbose);
|
|
113463
113602
|
} catch (err) {
|
|
113464
|
-
console.error(
|
|
113603
|
+
console.error(
|
|
113604
|
+
`
|
|
113465
113605
|
\u274C \u62C9\u53D6\u7CFB\u5217\u8BBE\u5907\u51FA\u4EF7\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
|
|
113466
|
-
`
|
|
113606
|
+
`
|
|
113607
|
+
);
|
|
113467
113608
|
process.exit(1);
|
|
113468
113609
|
}
|
|
113469
113610
|
const criterionId = resolveCriterionId(Array.isArray(list) ? list : [], {
|
|
@@ -113475,9 +113616,11 @@ async function runAdDeviceBidSet(opts) {
|
|
|
113475
113616
|
try {
|
|
113476
113617
|
await apiFetch2(putUrl, config, { method: "PUT" }, opts.verbose);
|
|
113477
113618
|
} catch (err) {
|
|
113478
|
-
console.error(
|
|
113619
|
+
console.error(
|
|
113620
|
+
`
|
|
113479
113621
|
\u274C \u4FEE\u6539\u7CFB\u5217\u8BBE\u5907\u51FA\u4EF7\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
|
|
113480
|
-
`
|
|
113622
|
+
`
|
|
113623
|
+
);
|
|
113481
113624
|
process.exit(1);
|
|
113482
113625
|
}
|
|
113483
113626
|
const deviceLabel = opts.deviceType ? normalizeDeviceType(opts.deviceType) : "\u8BBE\u5907";
|
|
@@ -113664,9 +113807,11 @@ async function runAdGeoSetBid(opts) {
|
|
|
113664
113807
|
opts.verbose
|
|
113665
113808
|
);
|
|
113666
113809
|
} catch (err) {
|
|
113667
|
-
console.error(
|
|
113810
|
+
console.error(
|
|
113811
|
+
`
|
|
113668
113812
|
\u274C \u62C9\u53D6\u5DF2\u5B9A\u5411\u5730\u7406\u4F4D\u7F6E\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
|
|
113669
|
-
`
|
|
113813
|
+
`
|
|
113814
|
+
);
|
|
113670
113815
|
process.exit(1);
|
|
113671
113816
|
}
|
|
113672
113817
|
let criterionId;
|
|
@@ -113686,9 +113831,11 @@ async function runAdGeoSetBid(opts) {
|
|
|
113686
113831
|
try {
|
|
113687
113832
|
await apiFetch2(putUrl, config, { method: "PUT" }, opts.verbose);
|
|
113688
113833
|
} catch (err) {
|
|
113689
|
-
console.error(
|
|
113834
|
+
console.error(
|
|
113835
|
+
`
|
|
113690
113836
|
\u274C \u4FEE\u6539\u5730\u7406\u4F4D\u7F6E\u51FA\u4EF7\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
|
|
113691
|
-
`
|
|
113837
|
+
`
|
|
113838
|
+
);
|
|
113692
113839
|
process.exit(1);
|
|
113693
113840
|
}
|
|
113694
113841
|
const row = rows.find((r) => String(r["id"] ?? "") === criterionId);
|
|
@@ -113767,7 +113914,9 @@ function formatExtensionPlanned(ext) {
|
|
|
113767
113914
|
const props = asRecord2(ext["Properties"]);
|
|
113768
113915
|
const phone = props ? pickString(props["PhoneNumber"], props["phoneNumber"]) : "";
|
|
113769
113916
|
const code = props ? pickString(props["ContryCode"], props["CountryCode"]) : "";
|
|
113770
|
-
const parts = [type ? `\u7C7B\u578B: ${type}` : "", code || phone ? `\u7535\u8BDD: ${code}${phone}` : ""].filter(
|
|
113917
|
+
const parts = [type ? `\u7C7B\u578B: ${type}` : "", code || phone ? `\u7535\u8BDD: ${code}${phone}` : ""].filter(
|
|
113918
|
+
Boolean
|
|
113919
|
+
);
|
|
113771
113920
|
return parts.join(" | ") || JSON.stringify(ext).slice(0, 120);
|
|
113772
113921
|
}
|
|
113773
113922
|
function listPlannedKeywords(campaign) {
|
|
@@ -113972,7 +114121,9 @@ function compareCampaignCreateToLive(cfg, campaignId, live, meta) {
|
|
|
113972
114121
|
}
|
|
113973
114122
|
}
|
|
113974
114123
|
const plannedKeywords = listPlannedKeywords(campaign);
|
|
113975
|
-
const liveKwCount = live.keywords.filter(
|
|
114124
|
+
const liveKwCount = live.keywords.filter(
|
|
114125
|
+
(k) => belongsToCampaign(k, campaignId, campaignName)
|
|
114126
|
+
).length;
|
|
113976
114127
|
const liveAdCount = live.ads.filter((a) => belongsToCampaign(a, campaignId, campaignName)).length;
|
|
113977
114128
|
return {
|
|
113978
114129
|
accountId,
|
|
@@ -114057,7 +114208,9 @@ async function fetchLiveSnapshotForCampaign(config, accountId, campaignId, campa
|
|
|
114057
114208
|
campaignName,
|
|
114058
114209
|
adGroups: adGroups.filter((g) => belongsToCampaign(g, campaignId, campaignName)),
|
|
114059
114210
|
keywords: keywords.filter((k) => belongsToCampaign(k, campaignId, campaignName)),
|
|
114060
|
-
negativeKeywords: negativeKeywords.filter(
|
|
114211
|
+
negativeKeywords: negativeKeywords.filter(
|
|
114212
|
+
(k) => belongsToCampaign(k, campaignId, campaignName)
|
|
114213
|
+
),
|
|
114061
114214
|
ads: ads.filter((a) => belongsToCampaign(a, campaignId, campaignName)),
|
|
114062
114215
|
extensions: extensions.filter((e) => belongsToCampaign(e, campaignId, campaignName))
|
|
114063
114216
|
};
|
|
@@ -114672,13 +114825,13 @@ var VALID_PMAX_BIDDING_STRATEGIES = [
|
|
|
114672
114825
|
"TARGET_CPA",
|
|
114673
114826
|
"TARGET_ROAS"
|
|
114674
114827
|
];
|
|
114675
|
-
var PMAX_HEADLINE_MIN =
|
|
114828
|
+
var PMAX_HEADLINE_MIN = 15;
|
|
114676
114829
|
var PMAX_HEADLINE_MAX = 15;
|
|
114677
114830
|
var PMAX_HEADLINE_CHAR_MAX = 30;
|
|
114678
|
-
var PMAX_LONG_HEADLINE_MIN =
|
|
114831
|
+
var PMAX_LONG_HEADLINE_MIN = 5;
|
|
114679
114832
|
var PMAX_LONG_HEADLINE_MAX = 5;
|
|
114680
114833
|
var PMAX_LONG_HEADLINE_CHAR_MAX = 90;
|
|
114681
|
-
var PMAX_DESCRIPTION_MIN =
|
|
114834
|
+
var PMAX_DESCRIPTION_MIN = 5;
|
|
114682
114835
|
var PMAX_DESCRIPTION_MAX = 5;
|
|
114683
114836
|
var PMAX_DESCRIPTION_CHAR_MAX = 90;
|
|
114684
114837
|
var PMAX_BUSINESS_NAME_CHAR_MAX = 25;
|
|
@@ -114712,6 +114865,29 @@ function hasImageSource(cfg, kind) {
|
|
|
114712
114865
|
const p = paths?.[pathKey];
|
|
114713
114866
|
return typeof p === "string" && p.trim().length > 0;
|
|
114714
114867
|
}
|
|
114868
|
+
var PMAX_IMAGE_SLOT_LABELS = {
|
|
114869
|
+
marketing: "\u6A2A\u56FE MARKETING_IMAGE\uFF08imagePaths.marketing / marketingImageAssetId\uFF09",
|
|
114870
|
+
square: "\u65B9\u56FE SQUARE_MARKETING_IMAGE\uFF08imagePaths.square / squareMarketingImageAssetId\uFF09",
|
|
114871
|
+
logo: "Logo LOGO_IMAGE\uFF08imagePaths.logo / logoImageAssetId\uFF09"
|
|
114872
|
+
};
|
|
114873
|
+
function collectMissingPmaxMediaWarnings(cfg) {
|
|
114874
|
+
const warnings = [];
|
|
114875
|
+
const missingImages = [];
|
|
114876
|
+
for (const kind of ["marketing", "square", "logo"]) {
|
|
114877
|
+
if (!hasImageSource(cfg, kind)) {
|
|
114878
|
+
missingImages.push(PMAX_IMAGE_SLOT_LABELS[kind]);
|
|
114879
|
+
}
|
|
114880
|
+
}
|
|
114881
|
+
if (missingImages.length > 0) {
|
|
114882
|
+
warnings.push(`\u672A\u914D\u7F6E\u56FE\u7247\u7D20\u6750\uFF1A${missingImages.join("\uFF1B")}\u3002\u4E09\u5F20\u56FE\u5747\u4E3A Google \u5FC5\u586B`);
|
|
114883
|
+
}
|
|
114884
|
+
if (!cfg.videoPath?.trim() && !cfg.youtubeUrlOrId?.trim()) {
|
|
114885
|
+
warnings.push(
|
|
114886
|
+
"\u672A\u914D\u7F6E videoPath \u6216 youtubeUrlOrId\uFF1AGoogle \u5EFA\u8BAE PMax \u8D44\u4EA7\u7EC4\u81F3\u5C11 1 \u6761\u89C6\u9891\uFF1B\u672A\u63D0\u4F9B\u65F6\u53EF\u80FD\u7528\u56FE\u7247\u81EA\u52A8\u751F\u6210\u4F4E\u8D28\u91CF\u89C6\u9891\u3002\u672C\u5730\u6587\u4EF6\u586B videoPath\uFF0C\u5DF2\u6709 YouTube \u586B youtubeUrlOrId"
|
|
114887
|
+
);
|
|
114888
|
+
}
|
|
114889
|
+
return warnings;
|
|
114890
|
+
}
|
|
114715
114891
|
function normalizeBidding(raw) {
|
|
114716
114892
|
if (raw == null || raw === "") return "MAXIMIZE_CONVERSIONS";
|
|
114717
114893
|
const s = String(raw).trim().toUpperCase();
|
|
@@ -114849,7 +115025,10 @@ function runPmaxCreateValidation(cfg) {
|
|
|
114849
115025
|
} else if (bidding === "TARGET_ROAS") {
|
|
114850
115026
|
const roas = Number(cfg.targetRoas);
|
|
114851
115027
|
if (!Number.isFinite(roas) || roas <= 0) {
|
|
114852
|
-
pushErr3(
|
|
115028
|
+
pushErr3(
|
|
115029
|
+
errors,
|
|
115030
|
+
"biddingStrategyTypeV2 \u4E3A TARGET_ROAS \u65F6 targetRoas \u987B > 0\uFF08\u5982 2.5 \u8868\u793A 250%\uFF09"
|
|
115031
|
+
);
|
|
114853
115032
|
}
|
|
114854
115033
|
}
|
|
114855
115034
|
if (cfg.targetedLocations?.length) {
|
|
@@ -114935,6 +115114,18 @@ function runPmaxCreateValidation(cfg) {
|
|
|
114935
115114
|
);
|
|
114936
115115
|
}
|
|
114937
115116
|
}
|
|
115117
|
+
if (cfg.brandGuidelinesEnabled === false) {
|
|
115118
|
+
pushWarn3(
|
|
115119
|
+
warnings,
|
|
115120
|
+
"brandGuidelinesEnabled=false\uFF1A\u54C1\u724C\u5C06\u6302\u5728 AssetGroup \u7EA7\uFF08\u65E7\u8DEF\u5F84\uFF09\uFF1Bv21+ \u9ED8\u8BA4 true\uFF08Campaign \u7EA7 BG\uFF09"
|
|
115121
|
+
);
|
|
115122
|
+
} else if (cfg.brandGuidelinesEnabled !== true) {
|
|
115123
|
+
pushWarn3(
|
|
115124
|
+
warnings,
|
|
115125
|
+
"\u672A\u4F20 brandGuidelinesEnabled\uFF1A\u7F51\u5173\u9ED8\u8BA4 true\uFF1B\u521B\u5EFA\u540E\u9996\u4E2A AG \u7684 assets[] \u4E0D\u542B Logo/\u5546\u5BB6\u540D\uFF08\u89C1 brandAssets[]\uFF09"
|
|
115126
|
+
);
|
|
115127
|
+
}
|
|
115128
|
+
warnings.push(...collectMissingPmaxMediaWarnings(cfg));
|
|
114938
115129
|
return { errors, warnings, lengthViolations };
|
|
114939
115130
|
}
|
|
114940
115131
|
|
|
@@ -115051,7 +115242,16 @@ function resolveImagePath(configFile, relOrAbs) {
|
|
|
115051
115242
|
return isAbsolute2(t) ? t : resolve6(dirname7(configFile), t);
|
|
115052
115243
|
}
|
|
115053
115244
|
function checkSpec(spec, width, height, fileSizeKB, errors, warnings) {
|
|
115054
|
-
const {
|
|
115245
|
+
const {
|
|
115246
|
+
label,
|
|
115247
|
+
ratio,
|
|
115248
|
+
ratioPct,
|
|
115249
|
+
minWidth,
|
|
115250
|
+
minHeight,
|
|
115251
|
+
recommendedWidth,
|
|
115252
|
+
recommendedHeight,
|
|
115253
|
+
maxFileSizeKB
|
|
115254
|
+
} = spec;
|
|
115055
115255
|
const actualRatio = width / height;
|
|
115056
115256
|
const ratioOk = Math.abs(actualRatio - ratio) <= ratio * ratioPct;
|
|
115057
115257
|
if (!ratioOk) {
|
|
@@ -115060,9 +115260,7 @@ function checkSpec(spec, width, height, fileSizeKB, errors, warnings) {
|
|
|
115060
115260
|
);
|
|
115061
115261
|
}
|
|
115062
115262
|
if (width < minWidth || height < minHeight) {
|
|
115063
|
-
errors.push(
|
|
115064
|
-
`${label}\uFF1A\u5C3A\u5BF8 ${width}\xD7${height} \u4F4E\u4E8E Google \u6700\u5C0F\u8981\u6C42 ${minWidth}\xD7${minHeight}`
|
|
115065
|
-
);
|
|
115263
|
+
errors.push(`${label}\uFF1A\u5C3A\u5BF8 ${width}\xD7${height} \u4F4E\u4E8E Google \u6700\u5C0F\u8981\u6C42 ${minWidth}\xD7${minHeight}`);
|
|
115066
115264
|
} else if (width < recommendedWidth || height < recommendedHeight) {
|
|
115067
115265
|
warnings.push(
|
|
115068
115266
|
`${label}\uFF1A\u5C3A\u5BF8 ${width}\xD7${height} \u4F4E\u4E8E\u63A8\u8350\u5C3A\u5BF8 ${recommendedWidth}\xD7${recommendedHeight}\uFF0C\u53EF\u80FD\u5F71\u54CD\u5C55\u793A\u6548\u679C`
|
|
@@ -115126,6 +115324,12 @@ function pmaxCampaignUrl(googleApiUrl, accountId, campaignId) {
|
|
|
115126
115324
|
const path27 = `${base}/accounts/${accountId}/campaign/pmax`;
|
|
115127
115325
|
return campaignId ? `${path27}/${campaignId}` : path27;
|
|
115128
115326
|
}
|
|
115327
|
+
function pmaxBrandAssetsUrl(googleApiUrl, accountId, campaignId) {
|
|
115328
|
+
return `${pmaxCampaignUrl(googleApiUrl, accountId, campaignId)}/brand-assets`;
|
|
115329
|
+
}
|
|
115330
|
+
function pmaxBrandGuidelinesEnableUrl(googleApiUrl, accountId, campaignId) {
|
|
115331
|
+
return `${pmaxCampaignUrl(googleApiUrl, accountId, campaignId)}/brand-guidelines/enable`;
|
|
115332
|
+
}
|
|
115129
115333
|
function pmaxAssetGroupUrl(googleApiUrl, accountId, assetGroupId, suffix) {
|
|
115130
115334
|
const base = googleApiUrl.replace(/\/$/, "");
|
|
115131
115335
|
const path27 = `${base}/accounts/${accountId}/campaign/pmax/asset-group/${assetGroupId}`;
|
|
@@ -115321,7 +115525,7 @@ async function resolveOneSlot(configFile, cfg, kind, accountId, config, googleAp
|
|
|
115321
115525
|
);
|
|
115322
115526
|
}
|
|
115323
115527
|
async function resolvePmaxImageSlots(configFile, cfg, accountId, config, googleApiUrl, opts) {
|
|
115324
|
-
const kinds = ["marketing", "square", "logo"];
|
|
115528
|
+
const kinds = opts?.requiredKinds ?? ["marketing", "square", "logo"];
|
|
115325
115529
|
const willUpload = kinds.some((k) => {
|
|
115326
115530
|
const meta = SLOT_META[k];
|
|
115327
115531
|
if (String(cfg[meta.configAssetIdKey] ?? "").trim()) return false;
|
|
@@ -115345,8 +115549,8 @@ async function resolvePmaxImageSlots(configFile, cfg, accountId, config, googleA
|
|
|
115345
115549
|
}
|
|
115346
115550
|
return merged;
|
|
115347
115551
|
}
|
|
115348
|
-
function assertPmaxImageSlotsResolved(slots) {
|
|
115349
|
-
for (const kind of
|
|
115552
|
+
function assertPmaxImageSlotsResolved(slots, requiredKinds = ["marketing", "square", "logo"]) {
|
|
115553
|
+
for (const kind of requiredKinds) {
|
|
115350
115554
|
const meta = SLOT_META[kind];
|
|
115351
115555
|
const id = slots[meta.assetIdKey];
|
|
115352
115556
|
const b64 = slots[meta.base64Key];
|
|
@@ -115358,6 +115562,431 @@ function assertPmaxImageSlotsResolved(slots) {
|
|
|
115358
115562
|
|
|
115359
115563
|
// src/commands/ad/pmax-load.ts
|
|
115360
115564
|
import { readFileSync as readFileSync9 } from "fs";
|
|
115565
|
+
|
|
115566
|
+
// src/commands/ad/pmax-campaign-extensions.ts
|
|
115567
|
+
init_auth();
|
|
115568
|
+
var PMAX_POLICY_CALLOUT_MIN = 20;
|
|
115569
|
+
var PMAX_POLICY_STRUCTURED_SNIPPET_MIN = 20;
|
|
115570
|
+
var PMAX_POLICY_SITELINK_MIN = 6;
|
|
115571
|
+
var PMAX_EXTENSIONS_POLICY_HINT = `\u987B\u542B\uFF1A\u5BA3\u4F20\u4FE1\u606F \u2265${PMAX_POLICY_CALLOUT_MIN}\u3001\u7ED3\u6784\u5316\u6458\u8981 \u2265${PMAX_POLICY_STRUCTURED_SNIPPET_MIN}\u3001\u7AD9\u5185\u94FE\u63A5 \u2265${PMAX_POLICY_SITELINK_MIN}\u3001\u6F5C\u5728\u5BA2\u6237\u8868\u5355 leadForm\u3001WhatsApp businessMessage`;
|
|
115572
|
+
function countNonEmptyCallouts(callouts) {
|
|
115573
|
+
return (callouts ?? []).filter((t) => t?.trim()).length;
|
|
115574
|
+
}
|
|
115575
|
+
function countValidSnippets(snippets) {
|
|
115576
|
+
return (snippets ?? []).filter(
|
|
115577
|
+
(s) => s?.header?.trim() && (s.values ?? []).filter((v) => v?.trim()).length >= 3
|
|
115578
|
+
).length;
|
|
115579
|
+
}
|
|
115580
|
+
function countValidSitelinks(sitelinks) {
|
|
115581
|
+
return (sitelinks ?? []).filter((s) => s?.text?.trim() && s?.destinationUrl?.trim()).length;
|
|
115582
|
+
}
|
|
115583
|
+
function validatePmaxSitelinkSpec(link, prefix, errors, warnings) {
|
|
115584
|
+
if (!link) {
|
|
115585
|
+
errors.push(`${prefix} \u4E0D\u80FD\u4E3A\u7A7A`);
|
|
115586
|
+
return;
|
|
115587
|
+
}
|
|
115588
|
+
const text = link.text?.trim();
|
|
115589
|
+
const url = link.destinationUrl?.trim();
|
|
115590
|
+
if (!text) errors.push(`${prefix}.text \u4E0D\u80FD\u4E3A\u7A7A`);
|
|
115591
|
+
if (!url) {
|
|
115592
|
+
errors.push(`${prefix}.destinationUrl \u4E0D\u80FD\u4E3A\u7A7A`);
|
|
115593
|
+
} else if (!/^https?:\/\/.+/i.test(url)) {
|
|
115594
|
+
errors.push(`${prefix}.destinationUrl \u683C\u5F0F\u4E0D\u6B63\u786E\uFF1A${url}`);
|
|
115595
|
+
}
|
|
115596
|
+
if (text && url) {
|
|
115597
|
+
validateSitelinkProperties(
|
|
115598
|
+
prefix,
|
|
115599
|
+
{
|
|
115600
|
+
Text: text,
|
|
115601
|
+
DestinationUrl: url,
|
|
115602
|
+
Line2: link.line2?.trim() ?? "",
|
|
115603
|
+
Line3: link.line3?.trim() ?? ""
|
|
115604
|
+
},
|
|
115605
|
+
errors,
|
|
115606
|
+
warnings,
|
|
115607
|
+
[]
|
|
115608
|
+
);
|
|
115609
|
+
}
|
|
115610
|
+
}
|
|
115611
|
+
function validatePmaxExtensionsPolicyCounts(ext, errors) {
|
|
115612
|
+
const calloutCount = countNonEmptyCallouts(ext.callouts);
|
|
115613
|
+
if (calloutCount < PMAX_POLICY_CALLOUT_MIN) {
|
|
115614
|
+
errors.push(
|
|
115615
|
+
`campaignExtensions.callouts \u81F3\u5C11 ${PMAX_POLICY_CALLOUT_MIN} \u6761\u975E\u7A7A\u5BA3\u4F20\u4FE1\u606F\uFF08\u5F53\u524D ${calloutCount}\uFF09`
|
|
115616
|
+
);
|
|
115617
|
+
}
|
|
115618
|
+
const snippetCount = countValidSnippets(ext.structuredSnippets);
|
|
115619
|
+
if (snippetCount < PMAX_POLICY_STRUCTURED_SNIPPET_MIN) {
|
|
115620
|
+
errors.push(
|
|
115621
|
+
`campaignExtensions.structuredSnippets \u81F3\u5C11 ${PMAX_POLICY_STRUCTURED_SNIPPET_MIN} \u6761\u6709\u6548\u7ED3\u6784\u5316\u6458\u8981\uFF08\u6BCF\u6761 header + \u22653 values\uFF0C\u5F53\u524D ${snippetCount}\uFF09`
|
|
115622
|
+
);
|
|
115623
|
+
}
|
|
115624
|
+
const sitelinkCount = countValidSitelinks(ext.sitelinks);
|
|
115625
|
+
if (sitelinkCount < PMAX_POLICY_SITELINK_MIN) {
|
|
115626
|
+
errors.push(
|
|
115627
|
+
`campaignExtensions.sitelinks \u81F3\u5C11 ${PMAX_POLICY_SITELINK_MIN} \u6761\u7AD9\u5185\u94FE\u63A5\uFF08text + destinationUrl\uFF0C\u5F53\u524D ${sitelinkCount}\uFF09`
|
|
115628
|
+
);
|
|
115629
|
+
}
|
|
115630
|
+
if (!ext.leadForm) {
|
|
115631
|
+
errors.push("campaignExtensions.leadForm \u5FC5\u586B\uFF08\u6F5C\u5728\u5BA2\u6237\u8868\u5355\uFF09");
|
|
115632
|
+
}
|
|
115633
|
+
if (!ext.businessMessage) {
|
|
115634
|
+
errors.push("campaignExtensions.businessMessage \u5FC5\u586B\uFF08WhatsApp \u79C1\u4FE1\uFF09");
|
|
115635
|
+
}
|
|
115636
|
+
}
|
|
115637
|
+
function convertExtensionsForBatchJobToCampaignExtensions(items) {
|
|
115638
|
+
const config = {
|
|
115639
|
+
callouts: [],
|
|
115640
|
+
structuredSnippets: [],
|
|
115641
|
+
sitelinks: []
|
|
115642
|
+
};
|
|
115643
|
+
const warnings = [];
|
|
115644
|
+
for (let i = 0; i < items.length; i++) {
|
|
115645
|
+
const item = items[i];
|
|
115646
|
+
if (!item || typeof item !== "object") continue;
|
|
115647
|
+
const ext = item;
|
|
115648
|
+
const type = String(ext.typeV2 ?? ext.TypeV2 ?? ext.AssetFieldType ?? "").toUpperCase();
|
|
115649
|
+
const props = ext.Properties ?? {};
|
|
115650
|
+
if (type === "CALLOUT") {
|
|
115651
|
+
const text = String(props.Text ?? props.CalloutText ?? "").trim();
|
|
115652
|
+
if (text) config.callouts.push(text);
|
|
115653
|
+
continue;
|
|
115654
|
+
}
|
|
115655
|
+
if (type === "SITELINK") {
|
|
115656
|
+
const text = String(props.Text ?? props.LinkText ?? "").trim();
|
|
115657
|
+
const destinationUrl = String(props.DestinationUrl ?? "").trim();
|
|
115658
|
+
if (text && destinationUrl) {
|
|
115659
|
+
config.sitelinks.push({
|
|
115660
|
+
text,
|
|
115661
|
+
destinationUrl,
|
|
115662
|
+
line2: String(props.Line2 ?? props.Description1 ?? "").trim() || void 0,
|
|
115663
|
+
line3: String(props.Line3 ?? props.Description2 ?? "").trim() || void 0
|
|
115664
|
+
});
|
|
115665
|
+
}
|
|
115666
|
+
continue;
|
|
115667
|
+
}
|
|
115668
|
+
if (type === "STRUCTURED_SNIPPET") {
|
|
115669
|
+
const headerValue = ext.structuredSnippetHeaderValue ?? ext.StructuredSnippetHeaderValue;
|
|
115670
|
+
if (headerValue && typeof headerValue === "object") {
|
|
115671
|
+
const hv = headerValue;
|
|
115672
|
+
const header = String(hv.key ?? hv.Key ?? "").trim();
|
|
115673
|
+
const rawValues = hv.value ?? hv.Value;
|
|
115674
|
+
const values = Array.isArray(rawValues) ? rawValues.map((v) => String(v).trim()).filter(Boolean) : [];
|
|
115675
|
+
if (header && values.length >= 3) {
|
|
115676
|
+
config.structuredSnippets.push({ header, values });
|
|
115677
|
+
}
|
|
115678
|
+
}
|
|
115679
|
+
continue;
|
|
115680
|
+
}
|
|
115681
|
+
if (type === "LEAD_FORM" && ext.leadForm && typeof ext.leadForm === "object") {
|
|
115682
|
+
config.leadForm = ext.leadForm;
|
|
115683
|
+
continue;
|
|
115684
|
+
}
|
|
115685
|
+
if (type === "CALL" || type === "BUSINESS_MESSAGE") {
|
|
115686
|
+
warnings.push(
|
|
115687
|
+
`ExtensionsForBatchJob[${i}]\uFF08${type}\uFF09\u65E0\u6CD5\u81EA\u52A8\u8F6C\u4E3A campaignExtensions\uFF1BPMax \u8BF7\u7528 campaignExtensions \u6216\u521B\u5EFA\u540E ad extension * \u8865\u6302`
|
|
115688
|
+
);
|
|
115689
|
+
}
|
|
115690
|
+
}
|
|
115691
|
+
if ((config.callouts?.length ?? 0) === 0) delete config.callouts;
|
|
115692
|
+
if ((config.structuredSnippets?.length ?? 0) === 0) delete config.structuredSnippets;
|
|
115693
|
+
if ((config.sitelinks?.length ?? 0) === 0) delete config.sitelinks;
|
|
115694
|
+
return { config, warnings };
|
|
115695
|
+
}
|
|
115696
|
+
function validatePmaxCampaignExtensions(ext, opts) {
|
|
115697
|
+
const errors = [];
|
|
115698
|
+
const warnings = [];
|
|
115699
|
+
const skip = opts?.skipCampaignExtensions === true;
|
|
115700
|
+
if (!ext && !skip) {
|
|
115701
|
+
const batchExt = opts?.rawConfig?.["ExtensionsForBatchJob"];
|
|
115702
|
+
if (Array.isArray(batchExt) && batchExt.length > 0) {
|
|
115703
|
+
errors.push(
|
|
115704
|
+
`\u68C0\u6D4B\u5230 ExtensionsForBatchJob\uFF08Search \u6A21\u677F\u5B57\u6BB5\uFF09\uFF1APMax \u987B\u6539\u7528 campaignExtensions\u3002${PMAX_EXTENSIONS_POLICY_HINT}`
|
|
115705
|
+
);
|
|
115706
|
+
const { config: converted, warnings: convertWarnings } = convertExtensionsForBatchJobToCampaignExtensions(batchExt);
|
|
115707
|
+
warnings.push(...convertWarnings);
|
|
115708
|
+
if (hasExtensionsToAttach(converted)) {
|
|
115709
|
+
errors.push(
|
|
115710
|
+
"\u53EF\u4ECE ExtensionsForBatchJob \u81EA\u52A8\u6620\u5C04\u90E8\u5206\u5B57\u6BB5\uFF1B\u8BF7\u5728 JSON \u6839\u7EA7\u6DFB\u52A0 campaignExtensions \u540E\u5220\u9664 ExtensionsForBatchJob"
|
|
115711
|
+
);
|
|
115712
|
+
}
|
|
115713
|
+
} else {
|
|
115714
|
+
errors.push(
|
|
115715
|
+
`\u672A\u914D\u7F6E campaignExtensions\uFF1A${PMAX_EXTENSIONS_POLICY_HINT}\uFF1B\u7528\u6237\u660E\u786E\u4E0D\u8981\u9644\u52A0\u8D44\u4EA7\u65F6\u8BBE skipCampaignExtensions: true`
|
|
115716
|
+
);
|
|
115717
|
+
}
|
|
115718
|
+
return { errors, warnings };
|
|
115719
|
+
}
|
|
115720
|
+
if (skip && !ext) return { errors, warnings };
|
|
115721
|
+
if (!ext) return { errors, warnings };
|
|
115722
|
+
if (opts?.rawConfig?.["ExtensionsForBatchJob"]) {
|
|
115723
|
+
warnings.push(
|
|
115724
|
+
"JSON \u4ECD\u542B ExtensionsForBatchJob\uFF08Search \u6A21\u677F\u5B57\u6BB5\uFF09\uFF1B\u8BF7\u5220\u8BE5\u5B57\u6BB5\u5E76\u4EC5\u4FDD\u7559 campaignExtensions"
|
|
115725
|
+
);
|
|
115726
|
+
}
|
|
115727
|
+
const callouts = ext.callouts ?? [];
|
|
115728
|
+
for (let i = 0; i < callouts.length; i++) {
|
|
115729
|
+
const text = callouts[i]?.trim();
|
|
115730
|
+
if (!text) {
|
|
115731
|
+
errors.push(`campaignExtensions.callouts[${i}] \u4E0D\u80FD\u4E3A\u7A7A`);
|
|
115732
|
+
continue;
|
|
115733
|
+
}
|
|
115734
|
+
if (text.length > CALLOUT_TEXT_MAX_LEN) {
|
|
115735
|
+
errors.push(
|
|
115736
|
+
`campaignExtensions.callouts[${i}] \u8D85\u8FC7 ${CALLOUT_TEXT_MAX_LEN} \u5B57\u7B26\uFF08\u5F53\u524D ${text.length}\uFF09\uFF1A"${text}"`
|
|
115737
|
+
);
|
|
115738
|
+
}
|
|
115739
|
+
}
|
|
115740
|
+
const snippets = ext.structuredSnippets ?? [];
|
|
115741
|
+
for (let i = 0; i < snippets.length; i++) {
|
|
115742
|
+
const s = snippets[i];
|
|
115743
|
+
const prefix = `campaignExtensions.structuredSnippets[${i}]`;
|
|
115744
|
+
if (!s?.header?.trim()) {
|
|
115745
|
+
errors.push(`${prefix}.header \u4E0D\u80FD\u4E3A\u7A7A`);
|
|
115746
|
+
}
|
|
115747
|
+
const values = (s?.values ?? []).map((v) => v?.trim()).filter(Boolean);
|
|
115748
|
+
if (values.length < 3) {
|
|
115749
|
+
errors.push(`${prefix}.values \u81F3\u5C11 3 \u4E2A\u975E\u7A7A\u503C\uFF08\u5F53\u524D ${values.length}\uFF09`);
|
|
115750
|
+
}
|
|
115751
|
+
}
|
|
115752
|
+
const sitelinks = ext.sitelinks ?? [];
|
|
115753
|
+
for (let i = 0; i < sitelinks.length; i++) {
|
|
115754
|
+
validatePmaxSitelinkSpec(sitelinks[i], `campaignExtensions.sitelinks[${i}]`, errors, warnings);
|
|
115755
|
+
}
|
|
115756
|
+
if (ext.leadForm) {
|
|
115757
|
+
errors.push(...validateLeadFormPayload(ext.leadForm, "campaignExtensions.leadForm"));
|
|
115758
|
+
}
|
|
115759
|
+
if (ext.businessMessage) {
|
|
115760
|
+
errors.push(
|
|
115761
|
+
...validateBusinessMessagePayload(ext.businessMessage, "campaignExtensions.businessMessage")
|
|
115762
|
+
);
|
|
115763
|
+
warnings.push(
|
|
115764
|
+
"WhatsApp\uFF08BUSINESS_MESSAGE\uFF09\u9700 Google API \u767D\u540D\u5355\uFF1B\u672A\u5F00\u901A\u4F1A\u8FD4\u56DE CUSTOMER_NOT_ON_ALLOWLIST_FOR_MESSAGE_ASSETS"
|
|
115765
|
+
);
|
|
115766
|
+
}
|
|
115767
|
+
if (!skip) {
|
|
115768
|
+
validatePmaxExtensionsPolicyCounts(ext, errors);
|
|
115769
|
+
}
|
|
115770
|
+
return { errors, warnings };
|
|
115771
|
+
}
|
|
115772
|
+
function hasExtensionsToAttach(ext) {
|
|
115773
|
+
if (!ext) return false;
|
|
115774
|
+
return countNonEmptyCallouts(ext.callouts) > 0 || countValidSnippets(ext.structuredSnippets) > 0 || countValidSitelinks(ext.sitelinks) > 0 || Boolean(ext.leadForm) || Boolean(ext.businessMessage);
|
|
115775
|
+
}
|
|
115776
|
+
async function postExtension(config, googleApiUrl, accountId, body, verbose) {
|
|
115777
|
+
const url = `${googleApiUrl}/extensionmanagement/extension/${accountId}`;
|
|
115778
|
+
try {
|
|
115779
|
+
const data = await apiFetch2(
|
|
115780
|
+
url,
|
|
115781
|
+
config,
|
|
115782
|
+
{ method: "POST", body: JSON.stringify(body) },
|
|
115783
|
+
verbose
|
|
115784
|
+
);
|
|
115785
|
+
return { ok: true, data };
|
|
115786
|
+
} catch (err) {
|
|
115787
|
+
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
115788
|
+
}
|
|
115789
|
+
}
|
|
115790
|
+
function buildCalloutBody(accountId, campaignId, text) {
|
|
115791
|
+
return {
|
|
115792
|
+
activeuseridg: accountId,
|
|
115793
|
+
campaignId,
|
|
115794
|
+
level: "Campaign",
|
|
115795
|
+
typeV2: "CALLOUT",
|
|
115796
|
+
assetFieldType: "CALLOUT",
|
|
115797
|
+
properties: { Text: text.trim() }
|
|
115798
|
+
};
|
|
115799
|
+
}
|
|
115800
|
+
function buildSnippetBody(accountId, campaignId, header, values) {
|
|
115801
|
+
return {
|
|
115802
|
+
activeuseridg: accountId,
|
|
115803
|
+
campaignId,
|
|
115804
|
+
level: "Campaign",
|
|
115805
|
+
typeV2: "STRUCTURED_SNIPPET",
|
|
115806
|
+
assetFieldType: "STRUCTURED_SNIPPET",
|
|
115807
|
+
structuredSnippetHeaderValue: {
|
|
115808
|
+
key: header.trim(),
|
|
115809
|
+
value: values.map((v) => v.trim()).filter(Boolean)
|
|
115810
|
+
}
|
|
115811
|
+
};
|
|
115812
|
+
}
|
|
115813
|
+
function buildSitelinkBody(accountId, campaignId, link) {
|
|
115814
|
+
const text = link.text.trim();
|
|
115815
|
+
const line2 = link.line2?.trim() || text;
|
|
115816
|
+
const line3 = link.line3?.trim() || line2;
|
|
115817
|
+
return {
|
|
115818
|
+
activeuseridg: accountId,
|
|
115819
|
+
campaignId,
|
|
115820
|
+
level: "Campaign",
|
|
115821
|
+
typeV2: "SITELINK",
|
|
115822
|
+
assetFieldType: "SITELINK",
|
|
115823
|
+
properties: {
|
|
115824
|
+
Text: text,
|
|
115825
|
+
Line2: line2,
|
|
115826
|
+
Line3: line3,
|
|
115827
|
+
DestinationUrl: link.destinationUrl.trim()
|
|
115828
|
+
}
|
|
115829
|
+
};
|
|
115830
|
+
}
|
|
115831
|
+
function buildLeadFormBodyForCampaign(accountId, campaignId, leadForm) {
|
|
115832
|
+
return buildLeadFormExtensionBody({ account: accountId, campaignId, leadForm });
|
|
115833
|
+
}
|
|
115834
|
+
async function attachPmaxCampaignExtensions(opts) {
|
|
115835
|
+
const ext = opts.extensions;
|
|
115836
|
+
if (!hasExtensionsToAttach(ext)) {
|
|
115837
|
+
return {
|
|
115838
|
+
callouts: [],
|
|
115839
|
+
structuredSnippets: [],
|
|
115840
|
+
sitelinks: [],
|
|
115841
|
+
allOk: true,
|
|
115842
|
+
skippedReason: "\u672A\u914D\u7F6E campaignExtensions \u6216\u5185\u5BB9\u4E3A\u7A7A"
|
|
115843
|
+
};
|
|
115844
|
+
}
|
|
115845
|
+
const result = {
|
|
115846
|
+
callouts: [],
|
|
115847
|
+
structuredSnippets: [],
|
|
115848
|
+
sitelinks: [],
|
|
115849
|
+
allOk: true
|
|
115850
|
+
};
|
|
115851
|
+
for (const raw of ext.callouts ?? []) {
|
|
115852
|
+
const text = raw?.trim();
|
|
115853
|
+
if (!text) continue;
|
|
115854
|
+
if (opts.logProgress) console.log(`
|
|
115855
|
+
\u6302\u8F7D\u5BA3\u4F20\u4FE1\u606F\uFF1A${text}`);
|
|
115856
|
+
const posted = await postExtension(
|
|
115857
|
+
opts.config,
|
|
115858
|
+
opts.googleApiUrl,
|
|
115859
|
+
opts.accountId,
|
|
115860
|
+
buildCalloutBody(opts.accountId, opts.campaignId, text),
|
|
115861
|
+
opts.verbose
|
|
115862
|
+
);
|
|
115863
|
+
if (posted.ok) {
|
|
115864
|
+
result.callouts.push({
|
|
115865
|
+
ok: true,
|
|
115866
|
+
id: String(posted.data["id"] ?? ""),
|
|
115867
|
+
label: text
|
|
115868
|
+
});
|
|
115869
|
+
} else {
|
|
115870
|
+
result.allOk = false;
|
|
115871
|
+
result.callouts.push({ ok: false, error: posted.error, label: text });
|
|
115872
|
+
}
|
|
115873
|
+
}
|
|
115874
|
+
for (const s of ext.structuredSnippets ?? []) {
|
|
115875
|
+
const header = s?.header?.trim();
|
|
115876
|
+
const values = (s?.values ?? []).map((v) => v?.trim()).filter(Boolean);
|
|
115877
|
+
if (!header || values.length < 3) continue;
|
|
115878
|
+
const label = `${header}: ${values.join(", ")}`;
|
|
115879
|
+
if (opts.logProgress) console.log(`
|
|
115880
|
+
\u6302\u8F7D\u7ED3\u6784\u5316\u6458\u8981\uFF1A${label}`);
|
|
115881
|
+
const posted = await postExtension(
|
|
115882
|
+
opts.config,
|
|
115883
|
+
opts.googleApiUrl,
|
|
115884
|
+
opts.accountId,
|
|
115885
|
+
buildSnippetBody(opts.accountId, opts.campaignId, header, values),
|
|
115886
|
+
opts.verbose
|
|
115887
|
+
);
|
|
115888
|
+
if (posted.ok) {
|
|
115889
|
+
result.structuredSnippets.push({
|
|
115890
|
+
ok: true,
|
|
115891
|
+
id: String(posted.data["id"] ?? ""),
|
|
115892
|
+
label
|
|
115893
|
+
});
|
|
115894
|
+
} else {
|
|
115895
|
+
result.allOk = false;
|
|
115896
|
+
result.structuredSnippets.push({ ok: false, error: posted.error, label });
|
|
115897
|
+
}
|
|
115898
|
+
}
|
|
115899
|
+
for (const link of ext.sitelinks ?? []) {
|
|
115900
|
+
const text = link?.text?.trim();
|
|
115901
|
+
const url = link?.destinationUrl?.trim();
|
|
115902
|
+
if (!text || !url) continue;
|
|
115903
|
+
if (opts.logProgress) console.log(`
|
|
115904
|
+
\u6302\u8F7D\u7AD9\u5185\u94FE\u63A5\uFF1A${text} \u2192 ${url}`);
|
|
115905
|
+
const posted = await postExtension(
|
|
115906
|
+
opts.config,
|
|
115907
|
+
opts.googleApiUrl,
|
|
115908
|
+
opts.accountId,
|
|
115909
|
+
buildSitelinkBody(opts.accountId, opts.campaignId, link),
|
|
115910
|
+
opts.verbose
|
|
115911
|
+
);
|
|
115912
|
+
if (posted.ok) {
|
|
115913
|
+
result.sitelinks.push({
|
|
115914
|
+
ok: true,
|
|
115915
|
+
id: String(posted.data["id"] ?? ""),
|
|
115916
|
+
label: text
|
|
115917
|
+
});
|
|
115918
|
+
} else {
|
|
115919
|
+
result.allOk = false;
|
|
115920
|
+
result.sitelinks.push({ ok: false, error: posted.error, label: text });
|
|
115921
|
+
}
|
|
115922
|
+
}
|
|
115923
|
+
if (ext.leadForm) {
|
|
115924
|
+
const headline = ext.leadForm.headline?.trim() || "Lead Form";
|
|
115925
|
+
if (opts.logProgress) console.log(`
|
|
115926
|
+
\u6302\u8F7D\u6F5C\u5728\u5BA2\u6237\u8868\u5355\uFF1A${headline}`);
|
|
115927
|
+
const posted = await postExtension(
|
|
115928
|
+
opts.config,
|
|
115929
|
+
opts.googleApiUrl,
|
|
115930
|
+
opts.accountId,
|
|
115931
|
+
buildLeadFormBodyForCampaign(opts.accountId, opts.campaignId, ext.leadForm),
|
|
115932
|
+
opts.verbose
|
|
115933
|
+
);
|
|
115934
|
+
if (posted.ok) {
|
|
115935
|
+
result.leadForm = {
|
|
115936
|
+
ok: true,
|
|
115937
|
+
id: String(posted.data["id"] ?? ""),
|
|
115938
|
+
label: headline
|
|
115939
|
+
};
|
|
115940
|
+
} else {
|
|
115941
|
+
result.allOk = false;
|
|
115942
|
+
result.leadForm = { ok: false, error: posted.error, label: headline };
|
|
115943
|
+
}
|
|
115944
|
+
}
|
|
115945
|
+
if (ext.businessMessage) {
|
|
115946
|
+
const label = ext.businessMessage.starterMessage?.trim().slice(0, 40) || "WhatsApp";
|
|
115947
|
+
if (opts.logProgress) console.log(`
|
|
115948
|
+
\u6302\u8F7D WhatsApp \u79C1\u4FE1\uFF1A${label}`);
|
|
115949
|
+
const posted = await postExtension(
|
|
115950
|
+
opts.config,
|
|
115951
|
+
opts.googleApiUrl,
|
|
115952
|
+
opts.accountId,
|
|
115953
|
+
buildBusinessMessageBodyForCampaign(opts.accountId, opts.campaignId, ext.businessMessage),
|
|
115954
|
+
opts.verbose
|
|
115955
|
+
);
|
|
115956
|
+
if (posted.ok) {
|
|
115957
|
+
result.whatsapp = {
|
|
115958
|
+
ok: true,
|
|
115959
|
+
id: String(posted.data["id"] ?? ""),
|
|
115960
|
+
label
|
|
115961
|
+
};
|
|
115962
|
+
} else {
|
|
115963
|
+
result.allOk = false;
|
|
115964
|
+
result.whatsapp = { ok: false, error: posted.error, label };
|
|
115965
|
+
}
|
|
115966
|
+
}
|
|
115967
|
+
return result;
|
|
115968
|
+
}
|
|
115969
|
+
function formatPmaxExtensionsAttachErrors(result) {
|
|
115970
|
+
const msgs = [];
|
|
115971
|
+
for (const c of result.callouts) {
|
|
115972
|
+
if (!c.ok) msgs.push(`\u5BA3\u4F20\u4FE1\u606F\u300C${c.label}\u300D\u5931\u8D25\uFF1A${c.error}`);
|
|
115973
|
+
}
|
|
115974
|
+
for (const s of result.structuredSnippets) {
|
|
115975
|
+
if (!s.ok) msgs.push(`\u7ED3\u6784\u5316\u6458\u8981\u300C${s.label}\u300D\u5931\u8D25\uFF1A${s.error}`);
|
|
115976
|
+
}
|
|
115977
|
+
for (const sl of result.sitelinks) {
|
|
115978
|
+
if (!sl.ok) msgs.push(`\u7AD9\u5185\u94FE\u63A5\u300C${sl.label}\u300D\u5931\u8D25\uFF1A${sl.error}`);
|
|
115979
|
+
}
|
|
115980
|
+
if (result.leadForm && !result.leadForm.ok) {
|
|
115981
|
+
msgs.push(`\u6F5C\u5728\u5BA2\u6237\u8868\u5355\u300C${result.leadForm.label}\u300D\u5931\u8D25\uFF1A${result.leadForm.error}`);
|
|
115982
|
+
}
|
|
115983
|
+
if (result.whatsapp && !result.whatsapp.ok) {
|
|
115984
|
+
msgs.push(`WhatsApp\u300C${result.whatsapp.label}\u300D\u5931\u8D25\uFF1A${result.whatsapp.error}`);
|
|
115985
|
+
}
|
|
115986
|
+
return msgs;
|
|
115987
|
+
}
|
|
115988
|
+
|
|
115989
|
+
// src/commands/ad/pmax-load.ts
|
|
115361
115990
|
function loadPmaxCreateConfig(configFile) {
|
|
115362
115991
|
const cfg = tryLoadPmaxCreateConfig(configFile);
|
|
115363
115992
|
if (!cfg) {
|
|
@@ -115369,9 +115998,35 @@ function loadPmaxCreateConfig(configFile) {
|
|
|
115369
115998
|
return cfg;
|
|
115370
115999
|
}
|
|
115371
116000
|
var PMAX_VIDEO_PATH_ALIASES = ["video", "videoFile", "localVideo"];
|
|
116001
|
+
function normalizeCampaignExtensions(stripped, cfg) {
|
|
116002
|
+
const migrationWarnings = [];
|
|
116003
|
+
if (!cfg.campaignExtensions) {
|
|
116004
|
+
const alias = stripped["extensions"];
|
|
116005
|
+
if (alias && typeof alias === "object" && !Array.isArray(alias)) {
|
|
116006
|
+
cfg.campaignExtensions = alias;
|
|
116007
|
+
migrationWarnings.push("\u5DF2\u5C06 extensions \u522B\u540D\u6620\u5C04\u4E3A campaignExtensions");
|
|
116008
|
+
}
|
|
116009
|
+
}
|
|
116010
|
+
const batchExt = stripped["ExtensionsForBatchJob"];
|
|
116011
|
+
if (!cfg.campaignExtensions && Array.isArray(batchExt) && batchExt.length > 0) {
|
|
116012
|
+
const { config: converted, warnings } = convertExtensionsForBatchJobToCampaignExtensions(batchExt);
|
|
116013
|
+
migrationWarnings.push(...warnings);
|
|
116014
|
+
if (hasExtensionsToAttach(converted)) {
|
|
116015
|
+
cfg.campaignExtensions = converted;
|
|
116016
|
+
migrationWarnings.push(
|
|
116017
|
+
"\u5DF2\u5C06 ExtensionsForBatchJob \u81EA\u52A8\u8F6C\u6362\u4E3A campaignExtensions\uFF08\u8BF7\u6539 JSON \u6839\u7EA7\u5B57\u6BB5\u5E76\u5220\u9664 ExtensionsForBatchJob\uFF09"
|
|
116018
|
+
);
|
|
116019
|
+
}
|
|
116020
|
+
}
|
|
116021
|
+
if (stripped["skipCampaignExtensions"] === true) {
|
|
116022
|
+
cfg.skipCampaignExtensions = true;
|
|
116023
|
+
}
|
|
116024
|
+
return migrationWarnings;
|
|
116025
|
+
}
|
|
115372
116026
|
function normalizePmaxCreateConfig(raw) {
|
|
115373
116027
|
const stripped = stripMetaKeys(raw);
|
|
115374
116028
|
const cfg = { ...stripped };
|
|
116029
|
+
normalizeCampaignExtensions(stripped, cfg);
|
|
115375
116030
|
if (!cfg.videoPath?.trim()) {
|
|
115376
116031
|
for (const key of PMAX_VIDEO_PATH_ALIASES) {
|
|
115377
116032
|
const v = stripped[key];
|
|
@@ -115476,6 +116131,9 @@ function buildPmaxCreateApiBody(cfg, imageSlots) {
|
|
|
115476
116131
|
if (roas != null && Number(roas) > 0) {
|
|
115477
116132
|
body.targetRoas = Number(roas);
|
|
115478
116133
|
}
|
|
116134
|
+
if (cfg.brandGuidelinesEnabled !== void 0) {
|
|
116135
|
+
body.brandGuidelinesEnabled = cfg.brandGuidelinesEnabled;
|
|
116136
|
+
}
|
|
115479
116137
|
return body;
|
|
115480
116138
|
}
|
|
115481
116139
|
function buildPmaxCreateUrl(googleApiUrl, accountId) {
|
|
@@ -115521,17 +116179,27 @@ function convertPmaxMoneyInObject(body) {
|
|
|
115521
116179
|
}
|
|
115522
116180
|
return out;
|
|
115523
116181
|
}
|
|
115524
|
-
function buildPmaxAssetGroupApiBody(cfg, imageSlots) {
|
|
115525
|
-
|
|
115526
|
-
|
|
116182
|
+
function buildPmaxAssetGroupApiBody(cfg, imageSlots, opts) {
|
|
116183
|
+
const requiredKinds = opts?.omitBrandAssets ? ["marketing", "square"] : ["marketing", "square", "logo"];
|
|
116184
|
+
assertPmaxImageSlotsResolved(imageSlots, [...requiredKinds]);
|
|
116185
|
+
const body = {
|
|
115527
116186
|
name: cfg.name.trim(),
|
|
115528
116187
|
finalUrls: cfg.finalUrls.map((u) => u.trim()).filter(Boolean),
|
|
115529
116188
|
headlines: cfg.headlines.map((h) => h.trim()).filter(Boolean),
|
|
115530
116189
|
longHeadlines: cfg.longHeadlines.map((h) => h.trim()).filter(Boolean),
|
|
115531
|
-
descriptions: cfg.descriptions.map((d) => d.trim()).filter(Boolean)
|
|
115532
|
-
businessName: cfg.businessName.trim(),
|
|
115533
|
-
...imageSlots
|
|
116190
|
+
descriptions: cfg.descriptions.map((d) => d.trim()).filter(Boolean)
|
|
115534
116191
|
};
|
|
116192
|
+
if (!opts?.omitBrandAssets) {
|
|
116193
|
+
body["businessName"] = cfg.businessName.trim();
|
|
116194
|
+
}
|
|
116195
|
+
for (const key of Object.keys(imageSlots)) {
|
|
116196
|
+
if (opts?.omitBrandAssets && (key === "logoImageAssetId" || key === "logoImageBase64")) {
|
|
116197
|
+
continue;
|
|
116198
|
+
}
|
|
116199
|
+
const val = imageSlots[key];
|
|
116200
|
+
if (val != null && String(val).trim()) body[key] = val;
|
|
116201
|
+
}
|
|
116202
|
+
return body;
|
|
115535
116203
|
}
|
|
115536
116204
|
async function processAssetsUpdateBodyWithUpload(configFile, body, accountId, config, googleApiUrl, verbose) {
|
|
115537
116205
|
const out = { ...body };
|
|
@@ -115574,12 +116242,7 @@ async function postPmaxYoutubeLink(opts) {
|
|
|
115574
116242
|
};
|
|
115575
116243
|
if (opts.campaignId?.trim()) body["campaignId"] = opts.campaignId.trim();
|
|
115576
116244
|
if (opts.assetName?.trim()) body["assetName"] = opts.assetName.trim();
|
|
115577
|
-
const url = pmaxAssetGroupUrl(
|
|
115578
|
-
opts.googleApiUrl,
|
|
115579
|
-
opts.accountId,
|
|
115580
|
-
opts.assetGroupId,
|
|
115581
|
-
"youtube"
|
|
115582
|
-
);
|
|
116245
|
+
const url = pmaxAssetGroupUrl(opts.googleApiUrl, opts.accountId, opts.assetGroupId, "youtube");
|
|
115583
116246
|
const raw = await pmaxApiFetch(
|
|
115584
116247
|
url,
|
|
115585
116248
|
opts.config,
|
|
@@ -115610,7 +116273,13 @@ function requireAccountId(account, label = "-a, --account") {
|
|
|
115610
116273
|
|
|
115611
116274
|
// src/commands/ad/pmax-video-upload.ts
|
|
115612
116275
|
var VIDEO_UPLOAD_SUFFIX = /\.(mp4|mov|webm|avi|mpeg|mpg)$/i;
|
|
115613
|
-
var TERMINAL_VIDEO_STATES = /* @__PURE__ */ new Set([
|
|
116276
|
+
var TERMINAL_VIDEO_STATES = /* @__PURE__ */ new Set([
|
|
116277
|
+
"PROCESSED",
|
|
116278
|
+
"FAILED",
|
|
116279
|
+
"REJECTED",
|
|
116280
|
+
"UNAVAILABLE",
|
|
116281
|
+
"NOT_FOUND"
|
|
116282
|
+
]);
|
|
115614
116283
|
var FAILED_VIDEO_STATES = /* @__PURE__ */ new Set(["FAILED", "REJECTED", "UNAVAILABLE", "NOT_FOUND"]);
|
|
115615
116284
|
var PMAX_VIDEO_POLL_INTERVAL_MS = 1e4;
|
|
115616
116285
|
var PMAX_VIDEO_POLL_MAX_ATTEMPTS = 120;
|
|
@@ -115655,7 +116324,9 @@ function runPmaxVideoValidation(configFile, cfg) {
|
|
|
115655
116324
|
const videoPath = cfg.videoPath?.trim();
|
|
115656
116325
|
const youtube = cfg.youtubeUrlOrId?.trim();
|
|
115657
116326
|
if (videoPath && youtube) {
|
|
115658
|
-
errors.push(
|
|
116327
|
+
errors.push(
|
|
116328
|
+
"videoPath \u4E0E youtubeUrlOrId \u53EA\u80FD\u4E8C\u9009\u4E00\uFF08\u672C\u5730\u6587\u4EF6\u8D70 PyAPI \u4E0A\u4F20\uFF0C\u540E\u8005\u4E3A\u5DF2\u6709 YouTube\uFF09"
|
|
116329
|
+
);
|
|
115659
116330
|
return { errors, warnings };
|
|
115660
116331
|
}
|
|
115661
116332
|
if (!videoPath) return { errors, warnings };
|
|
@@ -115785,7 +116456,9 @@ async function uploadPmaxLocalVideo(opts) {
|
|
|
115785
116456
|
resourceName,
|
|
115786
116457
|
opts.verbose,
|
|
115787
116458
|
(state, attempt) => {
|
|
115788
|
-
opts.onProgress?.(
|
|
116459
|
+
opts.onProgress?.(
|
|
116460
|
+
`\u5904\u7406\u4E2D state=${state}\uFF08\u7B2C ${attempt}/${PMAX_VIDEO_POLL_MAX_ATTEMPTS} \u6B21\u67E5\u8BE2\uFF09`
|
|
116461
|
+
);
|
|
115789
116462
|
}
|
|
115790
116463
|
);
|
|
115791
116464
|
}
|
|
@@ -115819,7 +116492,9 @@ async function linkPmaxVideoAfterCreate(opts) {
|
|
|
115819
116492
|
let youtubeTarget = opts.cfg.youtubeUrlOrId?.trim();
|
|
115820
116493
|
const videoPath = opts.cfg.videoPath?.trim();
|
|
115821
116494
|
if (!youtubeTarget && !videoPath) {
|
|
115822
|
-
return {
|
|
116495
|
+
return {
|
|
116496
|
+
skippedReason: "\u672A\u914D\u7F6E videoPath / youtubeUrlOrId\uFF0C\u672A\u94FE\u63A5\u89C6\u9891\uFF1BGoogle \u5EFA\u8BAE PMax \u81F3\u5C11 1 \u6761 YouTube \u89C6\u9891"
|
|
116497
|
+
};
|
|
115823
116498
|
}
|
|
115824
116499
|
if (videoPath) {
|
|
115825
116500
|
const absVideo = resolveVideoPath(opts.configFile, videoPath);
|
|
@@ -115866,234 +116541,6 @@ async function linkPmaxVideoAfterCreate(opts) {
|
|
|
115866
116541
|
}
|
|
115867
116542
|
}
|
|
115868
116543
|
|
|
115869
|
-
// src/commands/ad/pmax-campaign-extensions.ts
|
|
115870
|
-
init_auth();
|
|
115871
|
-
function validatePmaxCampaignExtensions(ext) {
|
|
115872
|
-
const errors = [];
|
|
115873
|
-
const warnings = [];
|
|
115874
|
-
if (!ext) return { errors, warnings };
|
|
115875
|
-
const callouts = ext.callouts ?? [];
|
|
115876
|
-
for (let i = 0; i < callouts.length; i++) {
|
|
115877
|
-
const text = callouts[i]?.trim();
|
|
115878
|
-
if (!text) {
|
|
115879
|
-
errors.push(`campaignExtensions.callouts[${i}] \u4E0D\u80FD\u4E3A\u7A7A`);
|
|
115880
|
-
continue;
|
|
115881
|
-
}
|
|
115882
|
-
if (text.length > CALLOUT_TEXT_MAX_LEN) {
|
|
115883
|
-
errors.push(
|
|
115884
|
-
`campaignExtensions.callouts[${i}] \u8D85\u8FC7 ${CALLOUT_TEXT_MAX_LEN} \u5B57\u7B26\uFF08\u5F53\u524D ${text.length}\uFF09\uFF1A"${text}"`
|
|
115885
|
-
);
|
|
115886
|
-
}
|
|
115887
|
-
}
|
|
115888
|
-
const snippets = ext.structuredSnippets ?? [];
|
|
115889
|
-
for (let i = 0; i < snippets.length; i++) {
|
|
115890
|
-
const s = snippets[i];
|
|
115891
|
-
const prefix = `campaignExtensions.structuredSnippets[${i}]`;
|
|
115892
|
-
if (!s?.header?.trim()) {
|
|
115893
|
-
errors.push(`${prefix}.header \u4E0D\u80FD\u4E3A\u7A7A`);
|
|
115894
|
-
}
|
|
115895
|
-
const values = (s?.values ?? []).map((v) => v?.trim()).filter(Boolean);
|
|
115896
|
-
if (values.length < 3) {
|
|
115897
|
-
errors.push(`${prefix}.values \u81F3\u5C11 3 \u4E2A\u975E\u7A7A\u503C\uFF08\u5F53\u524D ${values.length}\uFF09`);
|
|
115898
|
-
}
|
|
115899
|
-
}
|
|
115900
|
-
if (ext.leadForm) {
|
|
115901
|
-
const lfErrors = validateLeadFormPayload(ext.leadForm, "campaignExtensions.leadForm");
|
|
115902
|
-
errors.push(...lfErrors);
|
|
115903
|
-
}
|
|
115904
|
-
if (ext.businessMessage) {
|
|
115905
|
-
errors.push(
|
|
115906
|
-
...validateBusinessMessagePayload(ext.businessMessage, "campaignExtensions.businessMessage")
|
|
115907
|
-
);
|
|
115908
|
-
warnings.push(
|
|
115909
|
-
"WhatsApp\uFF08BUSINESS_MESSAGE\uFF09\u9700 Google API \u767D\u540D\u5355\uFF1B\u672A\u5F00\u901A\u4F1A\u8FD4\u56DE CUSTOMER_NOT_ON_ALLOWLIST_FOR_MESSAGE_ASSETS"
|
|
115910
|
-
);
|
|
115911
|
-
}
|
|
115912
|
-
if (callouts.length === 0 && snippets.length === 0 && !ext.leadForm && !ext.businessMessage) {
|
|
115913
|
-
warnings.push(
|
|
115914
|
-
"campaignExtensions \u5DF2\u58F0\u660E\u4F46 callouts / structuredSnippets / leadForm / businessMessage \u5747\u4E3A\u7A7A\uFF0C\u521B\u5EFA\u65F6\u5C06\u8DF3\u8FC7\u9644\u52A0\u8D44\u4EA7"
|
|
115915
|
-
);
|
|
115916
|
-
}
|
|
115917
|
-
return { errors, warnings };
|
|
115918
|
-
}
|
|
115919
|
-
function hasExtensionsToAttach(ext) {
|
|
115920
|
-
if (!ext) return false;
|
|
115921
|
-
const hasCallouts = (ext.callouts ?? []).some((t) => t?.trim());
|
|
115922
|
-
const hasSnippets = (ext.structuredSnippets ?? []).some(
|
|
115923
|
-
(s) => s?.header?.trim() && (s.values ?? []).some((v) => v?.trim())
|
|
115924
|
-
);
|
|
115925
|
-
return hasCallouts || hasSnippets || Boolean(ext.leadForm) || Boolean(ext.businessMessage);
|
|
115926
|
-
}
|
|
115927
|
-
async function postExtension(config, googleApiUrl, accountId, body, verbose) {
|
|
115928
|
-
const url = `${googleApiUrl}/extensionmanagement/extension/${accountId}`;
|
|
115929
|
-
try {
|
|
115930
|
-
const data = await apiFetch2(
|
|
115931
|
-
url,
|
|
115932
|
-
config,
|
|
115933
|
-
{ method: "POST", body: JSON.stringify(body) },
|
|
115934
|
-
verbose
|
|
115935
|
-
);
|
|
115936
|
-
return { ok: true, data };
|
|
115937
|
-
} catch (err) {
|
|
115938
|
-
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
115939
|
-
}
|
|
115940
|
-
}
|
|
115941
|
-
function buildCalloutBody(accountId, campaignId, text) {
|
|
115942
|
-
return {
|
|
115943
|
-
activeuseridg: accountId,
|
|
115944
|
-
campaignId,
|
|
115945
|
-
level: "Campaign",
|
|
115946
|
-
typeV2: "CALLOUT",
|
|
115947
|
-
assetFieldType: "CALLOUT",
|
|
115948
|
-
properties: { Text: text.trim() }
|
|
115949
|
-
};
|
|
115950
|
-
}
|
|
115951
|
-
function buildSnippetBody(accountId, campaignId, header, values) {
|
|
115952
|
-
return {
|
|
115953
|
-
activeuseridg: accountId,
|
|
115954
|
-
campaignId,
|
|
115955
|
-
level: "Campaign",
|
|
115956
|
-
typeV2: "STRUCTURED_SNIPPET",
|
|
115957
|
-
assetFieldType: "STRUCTURED_SNIPPET",
|
|
115958
|
-
structuredSnippetHeaderValue: {
|
|
115959
|
-
key: header.trim(),
|
|
115960
|
-
value: values.map((v) => v.trim()).filter(Boolean)
|
|
115961
|
-
}
|
|
115962
|
-
};
|
|
115963
|
-
}
|
|
115964
|
-
function buildLeadFormBodyForCampaign(accountId, campaignId, leadForm) {
|
|
115965
|
-
return buildLeadFormExtensionBody({ account: accountId, campaignId, leadForm });
|
|
115966
|
-
}
|
|
115967
|
-
async function attachPmaxCampaignExtensions(opts) {
|
|
115968
|
-
const ext = opts.extensions;
|
|
115969
|
-
if (!hasExtensionsToAttach(ext)) {
|
|
115970
|
-
return {
|
|
115971
|
-
callouts: [],
|
|
115972
|
-
structuredSnippets: [],
|
|
115973
|
-
allOk: true,
|
|
115974
|
-
skippedReason: "\u672A\u914D\u7F6E campaignExtensions \u6216\u5185\u5BB9\u4E3A\u7A7A"
|
|
115975
|
-
};
|
|
115976
|
-
}
|
|
115977
|
-
const result = {
|
|
115978
|
-
callouts: [],
|
|
115979
|
-
structuredSnippets: [],
|
|
115980
|
-
allOk: true
|
|
115981
|
-
};
|
|
115982
|
-
for (const raw of ext.callouts ?? []) {
|
|
115983
|
-
const text = raw?.trim();
|
|
115984
|
-
if (!text) continue;
|
|
115985
|
-
if (opts.logProgress) console.log(`
|
|
115986
|
-
\u6302\u8F7D\u5BA3\u4F20\u4FE1\u606F\uFF1A${text}`);
|
|
115987
|
-
const posted = await postExtension(
|
|
115988
|
-
opts.config,
|
|
115989
|
-
opts.googleApiUrl,
|
|
115990
|
-
opts.accountId,
|
|
115991
|
-
buildCalloutBody(opts.accountId, opts.campaignId, text),
|
|
115992
|
-
opts.verbose
|
|
115993
|
-
);
|
|
115994
|
-
if (posted.ok) {
|
|
115995
|
-
result.callouts.push({
|
|
115996
|
-
ok: true,
|
|
115997
|
-
id: String(posted.data["id"] ?? ""),
|
|
115998
|
-
label: text
|
|
115999
|
-
});
|
|
116000
|
-
} else {
|
|
116001
|
-
result.allOk = false;
|
|
116002
|
-
result.callouts.push({ ok: false, error: posted.error, label: text });
|
|
116003
|
-
}
|
|
116004
|
-
}
|
|
116005
|
-
for (const s of ext.structuredSnippets ?? []) {
|
|
116006
|
-
const header = s?.header?.trim();
|
|
116007
|
-
const values = (s?.values ?? []).map((v) => v?.trim()).filter(Boolean);
|
|
116008
|
-
if (!header || values.length < 3) continue;
|
|
116009
|
-
const label = `${header}: ${values.join(", ")}`;
|
|
116010
|
-
if (opts.logProgress) console.log(`
|
|
116011
|
-
\u6302\u8F7D\u7ED3\u6784\u5316\u6458\u8981\uFF1A${label}`);
|
|
116012
|
-
const posted = await postExtension(
|
|
116013
|
-
opts.config,
|
|
116014
|
-
opts.googleApiUrl,
|
|
116015
|
-
opts.accountId,
|
|
116016
|
-
buildSnippetBody(opts.accountId, opts.campaignId, header, values),
|
|
116017
|
-
opts.verbose
|
|
116018
|
-
);
|
|
116019
|
-
if (posted.ok) {
|
|
116020
|
-
result.structuredSnippets.push({
|
|
116021
|
-
ok: true,
|
|
116022
|
-
id: String(posted.data["id"] ?? ""),
|
|
116023
|
-
label
|
|
116024
|
-
});
|
|
116025
|
-
} else {
|
|
116026
|
-
result.allOk = false;
|
|
116027
|
-
result.structuredSnippets.push({ ok: false, error: posted.error, label });
|
|
116028
|
-
}
|
|
116029
|
-
}
|
|
116030
|
-
if (ext.leadForm) {
|
|
116031
|
-
const headline = ext.leadForm.headline?.trim() || "Lead Form";
|
|
116032
|
-
if (opts.logProgress) console.log(`
|
|
116033
|
-
\u6302\u8F7D\u6F5C\u5728\u5BA2\u6237\u8868\u5355\uFF1A${headline}`);
|
|
116034
|
-
const posted = await postExtension(
|
|
116035
|
-
opts.config,
|
|
116036
|
-
opts.googleApiUrl,
|
|
116037
|
-
opts.accountId,
|
|
116038
|
-
buildLeadFormBodyForCampaign(opts.accountId, opts.campaignId, ext.leadForm),
|
|
116039
|
-
opts.verbose
|
|
116040
|
-
);
|
|
116041
|
-
if (posted.ok) {
|
|
116042
|
-
result.leadForm = {
|
|
116043
|
-
ok: true,
|
|
116044
|
-
id: String(posted.data["id"] ?? ""),
|
|
116045
|
-
label: headline
|
|
116046
|
-
};
|
|
116047
|
-
} else {
|
|
116048
|
-
result.allOk = false;
|
|
116049
|
-
result.leadForm = { ok: false, error: posted.error, label: headline };
|
|
116050
|
-
}
|
|
116051
|
-
}
|
|
116052
|
-
if (ext.businessMessage) {
|
|
116053
|
-
const label = ext.businessMessage.starterMessage?.trim().slice(0, 40) || "WhatsApp";
|
|
116054
|
-
if (opts.logProgress) console.log(`
|
|
116055
|
-
\u6302\u8F7D WhatsApp \u79C1\u4FE1\uFF1A${label}`);
|
|
116056
|
-
const posted = await postExtension(
|
|
116057
|
-
opts.config,
|
|
116058
|
-
opts.googleApiUrl,
|
|
116059
|
-
opts.accountId,
|
|
116060
|
-
buildBusinessMessageBodyForCampaign(
|
|
116061
|
-
opts.accountId,
|
|
116062
|
-
opts.campaignId,
|
|
116063
|
-
ext.businessMessage
|
|
116064
|
-
),
|
|
116065
|
-
opts.verbose
|
|
116066
|
-
);
|
|
116067
|
-
if (posted.ok) {
|
|
116068
|
-
result.whatsapp = {
|
|
116069
|
-
ok: true,
|
|
116070
|
-
id: String(posted.data["id"] ?? ""),
|
|
116071
|
-
label
|
|
116072
|
-
};
|
|
116073
|
-
} else {
|
|
116074
|
-
result.allOk = false;
|
|
116075
|
-
result.whatsapp = { ok: false, error: posted.error, label };
|
|
116076
|
-
}
|
|
116077
|
-
}
|
|
116078
|
-
return result;
|
|
116079
|
-
}
|
|
116080
|
-
function formatPmaxExtensionsAttachErrors(result) {
|
|
116081
|
-
const msgs = [];
|
|
116082
|
-
for (const c of result.callouts) {
|
|
116083
|
-
if (!c.ok) msgs.push(`\u5BA3\u4F20\u4FE1\u606F\u300C${c.label}\u300D\u5931\u8D25\uFF1A${c.error}`);
|
|
116084
|
-
}
|
|
116085
|
-
for (const s of result.structuredSnippets) {
|
|
116086
|
-
if (!s.ok) msgs.push(`\u7ED3\u6784\u5316\u6458\u8981\u300C${s.label}\u300D\u5931\u8D25\uFF1A${s.error}`);
|
|
116087
|
-
}
|
|
116088
|
-
if (result.leadForm && !result.leadForm.ok) {
|
|
116089
|
-
msgs.push(`\u6F5C\u5728\u5BA2\u6237\u8868\u5355\u300C${result.leadForm.label}\u300D\u5931\u8D25\uFF1A${result.leadForm.error}`);
|
|
116090
|
-
}
|
|
116091
|
-
if (result.whatsapp && !result.whatsapp.ok) {
|
|
116092
|
-
msgs.push(`WhatsApp\u300C${result.whatsapp.label}\u300D\u5931\u8D25\uFF1A${result.whatsapp.error}`);
|
|
116093
|
-
}
|
|
116094
|
-
return msgs;
|
|
116095
|
-
}
|
|
116096
|
-
|
|
116097
116544
|
// src/commands/ad/pmax-create.ts
|
|
116098
116545
|
function printVideoLinkRecoveryHint(accountId, assetGroupId, campaignId, youtubeTarget) {
|
|
116099
116546
|
const cid = campaignId != null ? ` --campaign-id ${campaignId}` : "";
|
|
@@ -116121,7 +116568,11 @@ async function runAdPmaxCreate(opts) {
|
|
|
116121
116568
|
cfg
|
|
116122
116569
|
);
|
|
116123
116570
|
const { errors: extErrors, warnings: extWarnings } = validatePmaxCampaignExtensions(
|
|
116124
|
-
cfg.campaignExtensions
|
|
116571
|
+
cfg.campaignExtensions,
|
|
116572
|
+
{
|
|
116573
|
+
skipCampaignExtensions: cfg.skipCampaignExtensions,
|
|
116574
|
+
rawConfig: rawCfg
|
|
116575
|
+
}
|
|
116125
116576
|
);
|
|
116126
116577
|
const errors = [...deprecatedVideoKeys, ...cfgErrors, ...imgErrors, ...videoErrors, ...extErrors];
|
|
116127
116578
|
const warnings = [...cfgWarnings, ...imgWarnings, ...videoWarnings, ...extWarnings];
|
|
@@ -116177,9 +116628,11 @@ async function runAdPmaxCreate(opts) {
|
|
|
116177
116628
|
opts.verbose
|
|
116178
116629
|
);
|
|
116179
116630
|
} catch (err) {
|
|
116180
|
-
console.error(
|
|
116631
|
+
console.error(
|
|
116632
|
+
`
|
|
116181
116633
|
\u274C \u521B\u5EFA PMax \u5E7F\u544A\u7CFB\u5217\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
|
|
116182
|
-
`
|
|
116634
|
+
`
|
|
116635
|
+
);
|
|
116183
116636
|
process.exit(1);
|
|
116184
116637
|
}
|
|
116185
116638
|
const campaignId = data["campaignId"] ?? data["campaign_id"];
|
|
@@ -116213,6 +116666,8 @@ async function runAdPmaxCreate(opts) {
|
|
|
116213
116666
|
}
|
|
116214
116667
|
const payload = {
|
|
116215
116668
|
...data,
|
|
116669
|
+
_brandGuidelinesEnabled: cfg.brandGuidelinesEnabled !== false,
|
|
116670
|
+
_agentBrandHint: cfg.brandGuidelinesEnabled === false ? "\u54C1\u724C\u5728 AssetGroup \u7EA7\uFF1B\u6539\u54C1\u724C\u7528 pmax-assets-update" : "\u54C1\u724C\u5728 Campaign \u7EA7 brandAssets[]\uFF1B\u6539\u54C1\u724C\u7528 pmax-brand-assets-edit\uFF1B\u8FFD\u52A0 AG \u53EF\u7701\u7565 Logo/\u5546\u5BB6\u540D",
|
|
116216
116671
|
videoLink: {
|
|
116217
116672
|
ok: Boolean(videoResult.youtubeLink && !videoResult.uploadError && !videoResult.linkError),
|
|
116218
116673
|
youtubeTarget: videoResult.youtubeTarget,
|
|
@@ -116221,13 +116676,21 @@ async function runAdPmaxCreate(opts) {
|
|
|
116221
116676
|
linkError: videoResult.linkError,
|
|
116222
116677
|
skippedReason: videoResult.skippedReason
|
|
116223
116678
|
},
|
|
116224
|
-
campaignExtensions: extensionsResult ?? {
|
|
116679
|
+
campaignExtensions: extensionsResult ?? {
|
|
116680
|
+
allOk: true,
|
|
116681
|
+
skippedReason: "\u672A\u914D\u7F6E campaignExtensions"
|
|
116682
|
+
}
|
|
116225
116683
|
};
|
|
116226
116684
|
if (videoResult.uploadError) {
|
|
116227
116685
|
console.error(`
|
|
116228
116686
|
\u26A0\uFE0F \u6D3B\u52A8\u5DF2\u521B\u5EFA\uFF0C\u4F46\u672C\u5730\u89C6\u9891\u4E0A\u4F20\u5931\u8D25\uFF1A${videoResult.uploadError}`);
|
|
116229
116687
|
if (assetGroupId != null) {
|
|
116230
|
-
printVideoLinkRecoveryHint(
|
|
116688
|
+
printVideoLinkRecoveryHint(
|
|
116689
|
+
accountId,
|
|
116690
|
+
assetGroupId,
|
|
116691
|
+
campaignId,
|
|
116692
|
+
void 0
|
|
116693
|
+
);
|
|
116231
116694
|
}
|
|
116232
116695
|
} else if (videoResult.linkError) {
|
|
116233
116696
|
console.error(`
|
|
@@ -116240,7 +116703,7 @@ async function runAdPmaxCreate(opts) {
|
|
|
116240
116703
|
videoResult.youtubeTarget
|
|
116241
116704
|
);
|
|
116242
116705
|
}
|
|
116243
|
-
} else if (videoResult.skippedReason
|
|
116706
|
+
} else if (videoResult.skippedReason) {
|
|
116244
116707
|
console.error(`
|
|
116245
116708
|
\u26A0\uFE0F \u6D3B\u52A8\u5DF2\u521B\u5EFA\uFF0C\u4F46\u89C6\u9891\u672A\u94FE\u63A5\uFF1A${videoResult.skippedReason}
|
|
116246
116709
|
`);
|
|
@@ -116254,7 +116717,7 @@ async function runAdPmaxCreate(opts) {
|
|
|
116254
116717
|
` \u53EF\u624B\u52A8\u8865\u6302\uFF1Asiluzan-tso ad extension callout|snippet|lead-form -a ${accountId} --campaign-id ${campaignId} \u2026
|
|
116255
116718
|
`
|
|
116256
116719
|
);
|
|
116257
|
-
} else if (extensionsResult?.allOk && extensionsResult.callouts.length + extensionsResult.structuredSnippets.length + (extensionsResult.leadForm ? 1 : 0) + (extensionsResult.whatsapp ? 1 : 0) > 0) {
|
|
116720
|
+
} else if (extensionsResult?.allOk && extensionsResult.callouts.length + extensionsResult.structuredSnippets.length + extensionsResult.sitelinks.length + (extensionsResult.leadForm ? 1 : 0) + (extensionsResult.whatsapp ? 1 : 0) > 0) {
|
|
116258
116721
|
if (!opts.jsonOut) {
|
|
116259
116722
|
console.log("\n\u2705 Campaign \u9644\u52A0\u8D44\u4EA7\u5DF2\u6302\u8F7D");
|
|
116260
116723
|
}
|
|
@@ -116312,7 +116775,11 @@ async function runAdPmaxValidate(opts) {
|
|
|
116312
116775
|
cfg
|
|
116313
116776
|
);
|
|
116314
116777
|
const { errors: extErrors, warnings: extWarnings } = validatePmaxCampaignExtensions(
|
|
116315
|
-
cfg.campaignExtensions
|
|
116778
|
+
cfg.campaignExtensions,
|
|
116779
|
+
{
|
|
116780
|
+
skipCampaignExtensions: cfg.skipCampaignExtensions,
|
|
116781
|
+
rawConfig: rawCfg
|
|
116782
|
+
}
|
|
116316
116783
|
);
|
|
116317
116784
|
const errors = [...deprecatedVideoKeys, ...cfgErrors, ...imgErrors, ...videoErrors, ...extErrors];
|
|
116318
116785
|
const warnings = [...cfgWarnings, ...imgWarnings, ...videoWarnings, ...extWarnings];
|
|
@@ -116365,6 +116832,175 @@ async function runAdPmaxValidate(opts) {
|
|
|
116365
116832
|
|
|
116366
116833
|
// src/commands/ad/pmax-mgmt.ts
|
|
116367
116834
|
import { basename as basename6, isAbsolute as isAbsolute6 } from "path";
|
|
116835
|
+
|
|
116836
|
+
// src/commands/ad/pmax-brand-guidelines.ts
|
|
116837
|
+
var PMAX_CAMPAIGN_BRAND_FIELD_TYPES = /* @__PURE__ */ new Set(["BUSINESS_NAME", "LOGO", "LANDSCAPE_LOGO"]);
|
|
116838
|
+
function isPmaxBrandGuidelinesActive(detail) {
|
|
116839
|
+
if (detail["brandGuidelinesEnabled"] === true) return true;
|
|
116840
|
+
const assets = detail["brandAssets"];
|
|
116841
|
+
if (!Array.isArray(assets)) return false;
|
|
116842
|
+
return assets.some((row) => {
|
|
116843
|
+
if (!row || typeof row !== "object") return false;
|
|
116844
|
+
const ft = String(row["fieldType"] ?? "").toUpperCase();
|
|
116845
|
+
return PMAX_CAMPAIGN_BRAND_FIELD_TYPES.has(ft);
|
|
116846
|
+
});
|
|
116847
|
+
}
|
|
116848
|
+
function brandEditAgentHint(bgActive) {
|
|
116849
|
+
return bgActive ? "\u6539\u5546\u5BB6\u540D/Logo/\u6837\u5F0F\uFF1Aad pmax-brand-assets-edit\uFF08\u52FF\u5BF9 AssetGroup \u7528 pmax-assets-update \u6539 BUSINESS_NAME/LOGO\uFF09" : "\u6539\u54C1\u724C\uFF1Aad pmax-assets-update\uFF08AssetGroup \u7EA7\uFF09\uFF1B\u6216 ad pmax-brand-guidelines-enable \u542F\u7528 BG \u540E\u8D70 ad pmax-brand-assets-edit";
|
|
116850
|
+
}
|
|
116851
|
+
function enrichPmaxDetailForAgent(detail) {
|
|
116852
|
+
const bgActive = isPmaxBrandGuidelinesActive(detail);
|
|
116853
|
+
return {
|
|
116854
|
+
...detail,
|
|
116855
|
+
_brandGuidelinesActive: bgActive,
|
|
116856
|
+
_agentBrandHint: brandEditAgentHint(bgActive)
|
|
116857
|
+
};
|
|
116858
|
+
}
|
|
116859
|
+
async function fetchPmaxCampaignDetail(config, googleApiUrl, accountId, campaignId, verbose) {
|
|
116860
|
+
const url = pmaxCampaignUrl(googleApiUrl, accountId, campaignId);
|
|
116861
|
+
const raw = await pmaxApiFetch(url, config, {}, verbose);
|
|
116862
|
+
if (raw && typeof raw === "object" && !Array.isArray(raw)) {
|
|
116863
|
+
return raw;
|
|
116864
|
+
}
|
|
116865
|
+
return { data: raw };
|
|
116866
|
+
}
|
|
116867
|
+
function hasBrandImageSource(cfg, slot) {
|
|
116868
|
+
if (slot === "logo") {
|
|
116869
|
+
return Boolean(
|
|
116870
|
+
cfg.logoImageAssetId?.trim() || cfg.logoImageBase64?.trim() || cfg.imagePaths?.logo?.trim()
|
|
116871
|
+
);
|
|
116872
|
+
}
|
|
116873
|
+
return Boolean(
|
|
116874
|
+
cfg.landscapeLogoImageAssetId?.trim() || cfg.landscapeLogoImageBase64?.trim() || cfg.imagePaths?.landscapeLogo?.trim()
|
|
116875
|
+
);
|
|
116876
|
+
}
|
|
116877
|
+
function validateBrandAssetsPatchBody(cfg, opts) {
|
|
116878
|
+
const errors = [];
|
|
116879
|
+
const style = cfg.brandGuidelines;
|
|
116880
|
+
const hasMain = Boolean(style?.mainColor?.trim());
|
|
116881
|
+
const hasAccent = Boolean(style?.accentColor?.trim());
|
|
116882
|
+
if (hasMain !== hasAccent) {
|
|
116883
|
+
errors.push("brandGuidelines.mainColor \u4E0E accentColor \u987B\u6210\u5BF9\u4F20\u5165");
|
|
116884
|
+
}
|
|
116885
|
+
const hasAny = Boolean(cfg.businessName?.trim()) || hasBrandImageSource(cfg, "logo") || hasBrandImageSource(cfg, "landscapeLogo") || Boolean(style?.predefinedFontFamily?.trim()) || hasMain;
|
|
116886
|
+
if (opts?.requireAtLeastOne !== false && !hasAny) {
|
|
116887
|
+
errors.push(
|
|
116888
|
+
"\u81F3\u5C11\u4F20\u4E00\u9879\uFF1AbusinessName\u3001logoImage*\u3001landscapeLogoImage* \u6216 brandGuidelines \u6837\u5F0F\u5B57\u6BB5"
|
|
116889
|
+
);
|
|
116890
|
+
}
|
|
116891
|
+
return errors;
|
|
116892
|
+
}
|
|
116893
|
+
function validateEnableBrandGuidelinesBody(cfg) {
|
|
116894
|
+
const errors = validateBrandAssetsPatchBody(cfg, {
|
|
116895
|
+
requireAtLeastOne: cfg.autoPopulateBrandAssets !== true
|
|
116896
|
+
});
|
|
116897
|
+
if (cfg.autoPopulateBrandAssets !== true) {
|
|
116898
|
+
if (!cfg.businessName?.trim()) {
|
|
116899
|
+
errors.push("autoPopulateBrandAssets=false \u65F6 businessName \u5FC5\u586B");
|
|
116900
|
+
}
|
|
116901
|
+
if (!hasBrandImageSource(cfg, "logo")) {
|
|
116902
|
+
errors.push("autoPopulateBrandAssets=false \u65F6 Logo\uFF08logoImage* / imagePaths.logo\uFF09\u5FC5\u586B");
|
|
116903
|
+
}
|
|
116904
|
+
}
|
|
116905
|
+
return errors;
|
|
116906
|
+
}
|
|
116907
|
+
function detectAssetGroupBrandLinkBlocked(body, bgActive) {
|
|
116908
|
+
if (!bgActive) return [];
|
|
116909
|
+
const errors = [];
|
|
116910
|
+
const links = body["assetsToLink"];
|
|
116911
|
+
if (Array.isArray(links)) {
|
|
116912
|
+
for (const item of links) {
|
|
116913
|
+
if (!item || typeof item !== "object") continue;
|
|
116914
|
+
const ft = String(item["fieldType"] ?? "").toUpperCase();
|
|
116915
|
+
if (PMAX_CAMPAIGN_BRAND_FIELD_TYPES.has(ft)) {
|
|
116916
|
+
errors.push(
|
|
116917
|
+
`Brand Guidelines \u5DF2\u751F\u6548\uFF0C\u7981\u6B62\u5728 AssetGroup PUT ${ft}\uFF1B\u8BF7\u7528 ad pmax-brand-assets-edit`
|
|
116918
|
+
);
|
|
116919
|
+
}
|
|
116920
|
+
}
|
|
116921
|
+
}
|
|
116922
|
+
const replace = body["replaceFieldTypes"];
|
|
116923
|
+
if (Array.isArray(replace)) {
|
|
116924
|
+
for (const ft of replace) {
|
|
116925
|
+
const upper = String(ft ?? "").toUpperCase();
|
|
116926
|
+
if (PMAX_CAMPAIGN_BRAND_FIELD_TYPES.has(upper)) {
|
|
116927
|
+
errors.push(
|
|
116928
|
+
`Brand Guidelines \u5DF2\u751F\u6548\uFF0CreplaceFieldTypes \u542B ${upper}\uFF1B\u8BF7\u7528 ad pmax-brand-assets-edit`
|
|
116929
|
+
);
|
|
116930
|
+
}
|
|
116931
|
+
}
|
|
116932
|
+
}
|
|
116933
|
+
return errors;
|
|
116934
|
+
}
|
|
116935
|
+
async function buildBrandAssetsApiBody(configFile, cfg, accountId, config, googleApiUrl, verbose) {
|
|
116936
|
+
const body = {};
|
|
116937
|
+
if (cfg.businessName?.trim()) body["businessName"] = cfg.businessName.trim();
|
|
116938
|
+
const logoCfg = {
|
|
116939
|
+
logoImageAssetId: cfg.logoImageAssetId,
|
|
116940
|
+
logoImageBase64: cfg.logoImageBase64,
|
|
116941
|
+
imagePaths: cfg.imagePaths?.logo ? { logo: cfg.imagePaths.logo } : void 0
|
|
116942
|
+
};
|
|
116943
|
+
const needsLogo = cfg.logoImageAssetId?.trim() || cfg.logoImageBase64?.trim() || cfg.imagePaths?.logo?.trim();
|
|
116944
|
+
if (needsLogo) {
|
|
116945
|
+
const slots = await resolvePmaxImageSlots(
|
|
116946
|
+
configFile,
|
|
116947
|
+
logoCfg,
|
|
116948
|
+
accountId,
|
|
116949
|
+
config,
|
|
116950
|
+
googleApiUrl,
|
|
116951
|
+
{ verbose, logUploads: false, requiredKinds: ["logo"] }
|
|
116952
|
+
);
|
|
116953
|
+
if (slots.logoImageAssetId) body["logoImageAssetId"] = slots.logoImageAssetId;
|
|
116954
|
+
else if (slots.logoImageBase64) body["logoImageBase64"] = slots.logoImageBase64;
|
|
116955
|
+
}
|
|
116956
|
+
const needsLandscape = cfg.landscapeLogoImageAssetId?.trim() || cfg.landscapeLogoImageBase64?.trim() || cfg.imagePaths?.landscapeLogo?.trim();
|
|
116957
|
+
if (needsLandscape) {
|
|
116958
|
+
const landscapeCfg = {
|
|
116959
|
+
logoImageAssetId: cfg.landscapeLogoImageAssetId,
|
|
116960
|
+
logoImageBase64: cfg.landscapeLogoImageBase64,
|
|
116961
|
+
imagePaths: cfg.imagePaths?.landscapeLogo ? { logo: cfg.imagePaths.landscapeLogo } : void 0
|
|
116962
|
+
};
|
|
116963
|
+
const slots = await resolvePmaxImageSlots(
|
|
116964
|
+
configFile,
|
|
116965
|
+
landscapeCfg,
|
|
116966
|
+
accountId,
|
|
116967
|
+
config,
|
|
116968
|
+
googleApiUrl,
|
|
116969
|
+
{ verbose, logUploads: false, requiredKinds: ["logo"] }
|
|
116970
|
+
);
|
|
116971
|
+
if (slots.logoImageAssetId) body["landscapeLogoImageAssetId"] = slots.logoImageAssetId;
|
|
116972
|
+
else if (slots.logoImageBase64) body["landscapeLogoImageBase64"] = slots.logoImageBase64;
|
|
116973
|
+
}
|
|
116974
|
+
if (cfg.brandGuidelines && typeof cfg.brandGuidelines === "object") {
|
|
116975
|
+
const style = {};
|
|
116976
|
+
const src = cfg.brandGuidelines;
|
|
116977
|
+
if (src.mainColor?.trim()) style["mainColor"] = src.mainColor.trim();
|
|
116978
|
+
if (src.accentColor?.trim()) style["accentColor"] = src.accentColor.trim();
|
|
116979
|
+
if (src.predefinedFontFamily?.trim()) {
|
|
116980
|
+
style["predefinedFontFamily"] = src.predefinedFontFamily.trim();
|
|
116981
|
+
}
|
|
116982
|
+
if (Object.keys(style).length > 0) body["brandGuidelines"] = style;
|
|
116983
|
+
}
|
|
116984
|
+
return body;
|
|
116985
|
+
}
|
|
116986
|
+
async function buildEnableBrandGuidelinesApiBody(configFile, cfg, accountId, config, googleApiUrl, verbose) {
|
|
116987
|
+
const body = await buildBrandAssetsApiBody(
|
|
116988
|
+
configFile,
|
|
116989
|
+
cfg,
|
|
116990
|
+
accountId,
|
|
116991
|
+
config,
|
|
116992
|
+
googleApiUrl,
|
|
116993
|
+
verbose
|
|
116994
|
+
);
|
|
116995
|
+
if (cfg.autoPopulateBrandAssets === true) {
|
|
116996
|
+
body["autoPopulateBrandAssets"] = true;
|
|
116997
|
+
} else {
|
|
116998
|
+
body["autoPopulateBrandAssets"] = false;
|
|
116999
|
+
}
|
|
117000
|
+
return body;
|
|
117001
|
+
}
|
|
117002
|
+
|
|
117003
|
+
// src/commands/ad/pmax-mgmt.ts
|
|
116368
117004
|
function buildReportQuery(opts) {
|
|
116369
117005
|
const params = new URLSearchParams();
|
|
116370
117006
|
params.set("startDate", toGoogleDate(opts.startDate, -30).replace(/\//g, "-"));
|
|
@@ -116425,18 +117061,32 @@ async function runAdPmaxChannelTypes(opts) {
|
|
|
116425
117061
|
async function runAdPmaxGet(opts) {
|
|
116426
117062
|
const accountId = requireAccountId(opts.account);
|
|
116427
117063
|
await withGoogleApi(opts, async (config, googleApiUrl) => {
|
|
116428
|
-
const url = pmaxCampaignUrl(googleApiUrl, accountId, opts.campaignId.trim());
|
|
116429
117064
|
let data;
|
|
116430
117065
|
try {
|
|
116431
|
-
|
|
116432
|
-
|
|
117066
|
+
data = await fetchPmaxCampaignDetail(
|
|
117067
|
+
config,
|
|
117068
|
+
googleApiUrl,
|
|
117069
|
+
accountId,
|
|
117070
|
+
opts.campaignId.trim(),
|
|
117071
|
+
opts.verbose
|
|
117072
|
+
);
|
|
116433
117073
|
} catch (err) {
|
|
116434
|
-
console.error(
|
|
117074
|
+
console.error(
|
|
117075
|
+
`
|
|
116435
117076
|
\u274C \u83B7\u53D6 PMax \u8BE6\u60C5\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
|
|
116436
|
-
`
|
|
117077
|
+
`
|
|
117078
|
+
);
|
|
116437
117079
|
process.exit(1);
|
|
116438
117080
|
}
|
|
116439
|
-
|
|
117081
|
+
const enriched = enrichPmaxDetailForAgent(data);
|
|
117082
|
+
const bgActive = enriched["_brandGuidelinesActive"] === true;
|
|
117083
|
+
if (await emitPmaxResult(
|
|
117084
|
+
opts,
|
|
117085
|
+
`ad-pmax-get-${accountId}`,
|
|
117086
|
+
"ad pmax-get",
|
|
117087
|
+
enriched,
|
|
117088
|
+
opts.campaignId
|
|
117089
|
+
)) {
|
|
116440
117090
|
return;
|
|
116441
117091
|
}
|
|
116442
117092
|
console.log(`
|
|
@@ -116444,9 +117094,13 @@ async function runAdPmaxGet(opts) {
|
|
|
116444
117094
|
console.log(` \u540D\u79F0\uFF1A${data["name"] ?? "\u2014"}`);
|
|
116445
117095
|
console.log(` \u72B6\u6001\uFF1A${data["statusV2"] ?? "\u2014"}`);
|
|
116446
117096
|
console.log(` \u9884\u7B97\uFF1A${data["budget"] ?? "\u2014"}\uFF08API \u5206\uFF0C\u5C55\u793A \xF7100\uFF09`);
|
|
117097
|
+
console.log(
|
|
117098
|
+
` Brand Guidelines\uFF1A${bgActive ? "\u751F\u6548\uFF08\u54C1\u724C\u5728 Campaign \u7EA7 brandAssets[]\uFF09" : "\u672A\u751F\u6548\uFF08\u54C1\u724C\u5728 AssetGroup assets[]\uFF09"}`
|
|
117099
|
+
);
|
|
116447
117100
|
const groups = data["assetGroups"];
|
|
116448
117101
|
const n = Array.isArray(groups) ? groups.length : 0;
|
|
116449
117102
|
console.log(` \u8D44\u4EA7\u7EC4\uFF1A${n} \u4E2A`);
|
|
117103
|
+
console.log(` ${brandEditAgentHint(bgActive)}`);
|
|
116450
117104
|
console.log(`
|
|
116451
117105
|
\u5B8C\u6574\u7ED3\u6784\u8BF7\u4F7F\u7528 --json-out
|
|
116452
117106
|
`);
|
|
@@ -116494,9 +117148,11 @@ async function runAdPmaxEdit(opts) {
|
|
|
116494
117148
|
opts.verbose
|
|
116495
117149
|
);
|
|
116496
117150
|
} catch (err) {
|
|
116497
|
-
console.error(
|
|
117151
|
+
console.error(
|
|
117152
|
+
`
|
|
116498
117153
|
\u274C \u66F4\u65B0 PMax \u6D3B\u52A8\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
|
|
116499
|
-
`
|
|
117154
|
+
`
|
|
117155
|
+
);
|
|
116500
117156
|
process.exit(1);
|
|
116501
117157
|
}
|
|
116502
117158
|
if (await emitPmaxResult(
|
|
@@ -116521,6 +117177,7 @@ async function runAdPmaxAssetGroupCreate(opts) {
|
|
|
116521
117177
|
console.error("\n\u274C JSON \u987B\u5305\u542B campaignId\n");
|
|
116522
117178
|
process.exit(1);
|
|
116523
117179
|
}
|
|
117180
|
+
const forceLegacyBrand = cfg["includeBrandAssets"] === true || cfg["forceBrandAssets"] === true;
|
|
116524
117181
|
const assetFields = {
|
|
116525
117182
|
name: String(cfg["name"] ?? ""),
|
|
116526
117183
|
finalUrls: cfg["finalUrls"] ?? [],
|
|
@@ -116537,6 +117194,45 @@ async function runAdPmaxAssetGroupCreate(opts) {
|
|
|
116537
117194
|
logoImageBase64: cfg["logoImageBase64"]
|
|
116538
117195
|
};
|
|
116539
117196
|
await withGoogleApi(opts, async (config, googleApiUrl) => {
|
|
117197
|
+
let omitBrandAssets = false;
|
|
117198
|
+
const warnings = [];
|
|
117199
|
+
if (!forceLegacyBrand) {
|
|
117200
|
+
try {
|
|
117201
|
+
const detail = await fetchPmaxCampaignDetail(
|
|
117202
|
+
config,
|
|
117203
|
+
googleApiUrl,
|
|
117204
|
+
accountId,
|
|
117205
|
+
campaignId,
|
|
117206
|
+
opts.verbose
|
|
117207
|
+
);
|
|
117208
|
+
omitBrandAssets = isPmaxBrandGuidelinesActive(detail);
|
|
117209
|
+
if (omitBrandAssets) {
|
|
117210
|
+
const hasBrandInConfig = assetFields.businessName.trim() || assetFields.logoImageAssetId?.trim() || assetFields.logoImageBase64?.trim() || assetFields.imagePaths?.logo?.trim();
|
|
117211
|
+
if (hasBrandInConfig) {
|
|
117212
|
+
warnings.push(
|
|
117213
|
+
"\u6D3B\u52A8 Brand Guidelines \u5DF2\u751F\u6548\uFF1AbusinessName/Logo \u5C06\u88AB\u5FFD\u7565\uFF08\u54C1\u724C\u5728 Campaign \u7EA7\uFF09\uFF1B\u53EF\u4ECE JSON \u5220\u9664\u4EE5\u51CF\u566A"
|
|
117214
|
+
);
|
|
117215
|
+
}
|
|
117216
|
+
}
|
|
117217
|
+
} catch (err) {
|
|
117218
|
+
console.error(
|
|
117219
|
+
`
|
|
117220
|
+
\u274C \u8BFB\u53D6\u6D3B\u52A8\u8BE6\u60C5\u5931\u8D25\uFF08\u65E0\u6CD5\u5224\u65AD Brand Guidelines\uFF09\uFF1A${err instanceof Error ? err.message : String(err)}
|
|
117221
|
+
`
|
|
117222
|
+
);
|
|
117223
|
+
process.exit(1);
|
|
117224
|
+
}
|
|
117225
|
+
}
|
|
117226
|
+
if (!omitBrandAssets && !assetFields.businessName.trim()) {
|
|
117227
|
+
console.error(
|
|
117228
|
+
"\n\u274C Brand Guidelines \u672A\u751F\u6548\u65F6 businessName \u5FC5\u586B\uFF1B\u82E5\u6D3B\u52A8\u5DF2\u5F00 BG \u8BF7\u52FF\u4F20 includeBrandAssets\n"
|
|
117229
|
+
);
|
|
117230
|
+
process.exit(1);
|
|
117231
|
+
}
|
|
117232
|
+
if (warnings.length > 0 && !opts.jsonOut) {
|
|
117233
|
+
console.warn("\n\u26A0\uFE0F PMax \u8D44\u4EA7\u7EC4\u521B\u5EFA\u8B66\u544A\uFF1A");
|
|
117234
|
+
for (const w of warnings) console.warn(` \u2022 ${w}`);
|
|
117235
|
+
}
|
|
116540
117236
|
let imageSlots;
|
|
116541
117237
|
try {
|
|
116542
117238
|
imageSlots = await resolvePmaxImageSlots(
|
|
@@ -116545,7 +117241,11 @@ async function runAdPmaxAssetGroupCreate(opts) {
|
|
|
116545
117241
|
accountId,
|
|
116546
117242
|
config,
|
|
116547
117243
|
googleApiUrl,
|
|
116548
|
-
{
|
|
117244
|
+
{
|
|
117245
|
+
verbose: opts.verbose,
|
|
117246
|
+
logUploads: !opts.jsonOut,
|
|
117247
|
+
requiredKinds: omitBrandAssets ? ["marketing", "square"] : void 0
|
|
117248
|
+
}
|
|
116549
117249
|
);
|
|
116550
117250
|
} catch (err) {
|
|
116551
117251
|
console.error(
|
|
@@ -116557,7 +117257,7 @@ async function runAdPmaxAssetGroupCreate(opts) {
|
|
|
116557
117257
|
}
|
|
116558
117258
|
let body;
|
|
116559
117259
|
try {
|
|
116560
|
-
body = buildPmaxAssetGroupApiBody(assetFields, imageSlots);
|
|
117260
|
+
body = buildPmaxAssetGroupApiBody(assetFields, imageSlots, { omitBrandAssets });
|
|
116561
117261
|
} catch (err) {
|
|
116562
117262
|
console.error(`
|
|
116563
117263
|
\u274C \u6784\u5EFA\u8BF7\u6C42\u4F53\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
|
|
@@ -116586,7 +117286,11 @@ async function runAdPmaxAssetGroupCreate(opts) {
|
|
|
116586
117286
|
{ ...opts, account: accountId },
|
|
116587
117287
|
`ad-pmax-asset-group-create-${accountId}`,
|
|
116588
117288
|
"ad pmax-asset-group-create",
|
|
116589
|
-
|
|
117289
|
+
{
|
|
117290
|
+
...data,
|
|
117291
|
+
_brandGuidelinesActive: omitBrandAssets,
|
|
117292
|
+
_warnings: warnings.length > 0 ? warnings : void 0
|
|
117293
|
+
},
|
|
116590
117294
|
String(data["id"] ?? campaignId)
|
|
116591
117295
|
)) {
|
|
116592
117296
|
return;
|
|
@@ -116612,9 +117316,7 @@ async function runAdPmaxAssetGroupEdit(opts) {
|
|
|
116612
117316
|
}
|
|
116613
117317
|
if (opts.status !== void 0) body["status"] = opts.status.trim().toUpperCase();
|
|
116614
117318
|
if (Object.keys(body).length === 0) {
|
|
116615
|
-
console.error(
|
|
116616
|
-
"\n\u274C \u8BF7\u6307\u5B9A --patch-file \u6216 --name / --final-urls / --status \u81F3\u5C11\u4E00\u9879\n"
|
|
116617
|
-
);
|
|
117319
|
+
console.error("\n\u274C \u8BF7\u6307\u5B9A --patch-file \u6216 --name / --final-urls / --status \u81F3\u5C11\u4E00\u9879\n");
|
|
116618
117320
|
process.exit(1);
|
|
116619
117321
|
}
|
|
116620
117322
|
}
|
|
@@ -116662,6 +117364,25 @@ async function runAdPmaxAssetsUpdate(opts) {
|
|
|
116662
117364
|
process.exit(1);
|
|
116663
117365
|
}
|
|
116664
117366
|
await withGoogleApi(opts, async (config, googleApiUrl) => {
|
|
117367
|
+
const campaignId = String(cfg["campaignId"] ?? "").trim();
|
|
117368
|
+
let bgActive = false;
|
|
117369
|
+
try {
|
|
117370
|
+
const detail = await fetchPmaxCampaignDetail(
|
|
117371
|
+
config,
|
|
117372
|
+
googleApiUrl,
|
|
117373
|
+
accountId,
|
|
117374
|
+
campaignId,
|
|
117375
|
+
opts.verbose
|
|
117376
|
+
);
|
|
117377
|
+
bgActive = isPmaxBrandGuidelinesActive(detail);
|
|
117378
|
+
} catch (err) {
|
|
117379
|
+
console.error(
|
|
117380
|
+
`
|
|
117381
|
+
\u274C \u8BFB\u53D6\u6D3B\u52A8\u8BE6\u60C5\u5931\u8D25\uFF08Brand Guidelines \u9884\u68C0\uFF09\uFF1A${err instanceof Error ? err.message : String(err)}
|
|
117382
|
+
`
|
|
117383
|
+
);
|
|
117384
|
+
process.exit(1);
|
|
117385
|
+
}
|
|
116665
117386
|
let body;
|
|
116666
117387
|
try {
|
|
116667
117388
|
const { account: _a, ...rest } = cfg;
|
|
@@ -116674,19 +117395,23 @@ async function runAdPmaxAssetsUpdate(opts) {
|
|
|
116674
117395
|
opts.verbose
|
|
116675
117396
|
);
|
|
116676
117397
|
} catch (err) {
|
|
116677
|
-
console.error(
|
|
117398
|
+
console.error(
|
|
117399
|
+
`
|
|
116678
117400
|
\u274C \u5904\u7406 assets JSON \u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
|
|
116679
|
-
`
|
|
117401
|
+
`
|
|
117402
|
+
);
|
|
117403
|
+
process.exit(1);
|
|
117404
|
+
}
|
|
117405
|
+
const brandBlocked = detectAssetGroupBrandLinkBlocked(body, bgActive);
|
|
117406
|
+
if (brandBlocked.length > 0) {
|
|
117407
|
+
console.error("\n\u274C PMax \u8D44\u4EA7\u66F4\u65B0\u88AB Brand Guidelines \u89C4\u5219\u963B\u65AD\uFF1A");
|
|
117408
|
+
for (const e of brandBlocked) console.error(` \u2022 ${e}`);
|
|
117409
|
+
console.error();
|
|
116680
117410
|
process.exit(1);
|
|
116681
117411
|
}
|
|
116682
117412
|
const url = pmaxAssetGroupUrl(googleApiUrl, accountId, assetGroupId, "assets");
|
|
116683
117413
|
try {
|
|
116684
|
-
await pmaxApiFetch(
|
|
116685
|
-
url,
|
|
116686
|
-
config,
|
|
116687
|
-
{ method: "PUT", body: JSON.stringify(body) },
|
|
116688
|
-
opts.verbose
|
|
116689
|
-
);
|
|
117414
|
+
await pmaxApiFetch(url, config, { method: "PUT", body: JSON.stringify(body) }, opts.verbose);
|
|
116690
117415
|
} catch (err) {
|
|
116691
117416
|
console.error(
|
|
116692
117417
|
`
|
|
@@ -116835,7 +117560,9 @@ async function runAdPmaxSignalsGet(opts) {
|
|
|
116835
117560
|
for (const s of signals) {
|
|
116836
117561
|
if (s && typeof s === "object") {
|
|
116837
117562
|
const row = s;
|
|
116838
|
-
console.log(
|
|
117563
|
+
console.log(
|
|
117564
|
+
` ${row["type"] ?? "?"} ${row["audienceResourceName"] ?? row["searchThemeText"] ?? ""}`
|
|
117565
|
+
);
|
|
116839
117566
|
}
|
|
116840
117567
|
}
|
|
116841
117568
|
}
|
|
@@ -116857,12 +117584,7 @@ async function runAdPmaxSignalsSet(opts) {
|
|
|
116857
117584
|
await withGoogleApi(opts, async (config, googleApiUrl) => {
|
|
116858
117585
|
const url = pmaxAssetGroupUrl(googleApiUrl, accountId, assetGroupId, "signals");
|
|
116859
117586
|
try {
|
|
116860
|
-
await pmaxApiFetch(
|
|
116861
|
-
url,
|
|
116862
|
-
config,
|
|
116863
|
-
{ method: "PUT", body: JSON.stringify(body) },
|
|
116864
|
-
opts.verbose
|
|
116865
|
-
);
|
|
117587
|
+
await pmaxApiFetch(url, config, { method: "PUT", body: JSON.stringify(body) }, opts.verbose);
|
|
116866
117588
|
} catch (err) {
|
|
116867
117589
|
console.error(
|
|
116868
117590
|
`
|
|
@@ -117013,11 +117735,9 @@ async function runAdPmaxImageUpload(opts) {
|
|
|
117013
117735
|
process.exit(1);
|
|
117014
117736
|
}
|
|
117015
117737
|
} catch (err) {
|
|
117016
|
-
console.error(
|
|
117017
|
-
`
|
|
117738
|
+
console.error(`
|
|
117018
117739
|
\u274C \u4E0A\u4F20\u56FE\u7247\u8D44\u4EA7\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
|
|
117019
|
-
`
|
|
117020
|
-
);
|
|
117740
|
+
`);
|
|
117021
117741
|
process.exit(1);
|
|
117022
117742
|
}
|
|
117023
117743
|
if (await emitPmaxResult(
|
|
@@ -117064,9 +117784,11 @@ async function runAdPmaxReportAssetGroups(opts) {
|
|
|
117064
117784
|
}
|
|
117065
117785
|
const campaigns = data["campaigns"];
|
|
117066
117786
|
const n = Array.isArray(campaigns) ? campaigns.length : 0;
|
|
117067
|
-
console.log(
|
|
117787
|
+
console.log(
|
|
117788
|
+
`
|
|
117068
117789
|
PMax \u8D44\u4EA7\u7EC4\u6548\u679C\u62A5\u8868\uFF08${query.get("startDate")} ~ ${query.get("endDate")}\uFF0C${n} \u884C\uFF09
|
|
117069
|
-
`
|
|
117790
|
+
`
|
|
117791
|
+
);
|
|
117070
117792
|
console.log(" \u5B8C\u6574\u6570\u636E\u8BF7\u4F7F\u7528 --json-out\n");
|
|
117071
117793
|
});
|
|
117072
117794
|
}
|
|
@@ -117168,9 +117890,7 @@ async function loadSharp() {
|
|
|
117168
117890
|
const mod = await import("sharp");
|
|
117169
117891
|
return mod.default ?? mod;
|
|
117170
117892
|
} catch {
|
|
117171
|
-
console.error(
|
|
117172
|
-
"\n\u274C \u627E\u4E0D\u5230 sharp \u6A21\u5757\uFF0C\u8BF7\u5148\u6267\u884C\uFF1Apnpm install\uFF08\u6216 npm install\uFF09\n"
|
|
117173
|
-
);
|
|
117893
|
+
console.error("\n\u274C \u627E\u4E0D\u5230 sharp \u6A21\u5757\uFF0C\u8BF7\u5148\u6267\u884C\uFF1Apnpm install\uFF08\u6216 npm install\uFF09\n");
|
|
117174
117894
|
process.exit(1);
|
|
117175
117895
|
}
|
|
117176
117896
|
}
|
|
@@ -117229,10 +117949,10 @@ async function runAdPmaxImageConvert(opts) {
|
|
|
117229
117949
|
const absOutput = resolve10(outputDir, outName);
|
|
117230
117950
|
try {
|
|
117231
117951
|
await convertOne(sharpFn, absInput, absOutput, spec, quality);
|
|
117232
|
-
const sizeKB = Math.ceil(
|
|
117233
|
-
|
|
117952
|
+
const sizeKB = Math.ceil((await import("fs")).statSync(absOutput).size / 1024);
|
|
117953
|
+
console.log(
|
|
117954
|
+
`\u2705 ${spec.kind.padEnd(9)} ${spec.width}\xD7${spec.height} ${sizeKB} KB \u2192 ${absOutput}`
|
|
117234
117955
|
);
|
|
117235
|
-
console.log(`\u2705 ${spec.kind.padEnd(9)} ${spec.width}\xD7${spec.height} ${sizeKB} KB \u2192 ${absOutput}`);
|
|
117236
117956
|
outputPaths[spec.kind] = absOutput;
|
|
117237
117957
|
} catch (err) {
|
|
117238
117958
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -117267,6 +117987,141 @@ async function runAdPmaxImageConvert(opts) {
|
|
|
117267
117987
|
);
|
|
117268
117988
|
}
|
|
117269
117989
|
|
|
117990
|
+
// src/commands/ad/pmax-brand-mgmt.ts
|
|
117991
|
+
function loadBrandConfig(path27, accountId, campaignId) {
|
|
117992
|
+
const cfg = loadPmaxJsonFile(path27);
|
|
117993
|
+
return {
|
|
117994
|
+
...cfg,
|
|
117995
|
+
account: String(cfg["account"] ?? accountId).trim(),
|
|
117996
|
+
campaignId: String(cfg["campaignId"] ?? campaignId).trim()
|
|
117997
|
+
};
|
|
117998
|
+
}
|
|
117999
|
+
async function runAdPmaxBrandAssetsEdit(opts) {
|
|
118000
|
+
const accountId = requireAccountId(opts.account);
|
|
118001
|
+
const campaignId = opts.campaignId.trim();
|
|
118002
|
+
if (!opts.patchFile?.trim()) {
|
|
118003
|
+
console.error("\n\u274C \u8BF7\u6307\u5B9A --patch-file\uFF08\u6A21\u677F pmax-brand-assets-template.json\uFF09\n");
|
|
118004
|
+
process.exit(1);
|
|
118005
|
+
}
|
|
118006
|
+
const cfg = loadBrandConfig(opts.patchFile, accountId, campaignId);
|
|
118007
|
+
const errors = validateBrandAssetsPatchBody(cfg);
|
|
118008
|
+
if (errors.length > 0) {
|
|
118009
|
+
console.error("\n\u274C Brand Assets \u914D\u7F6E\u6821\u9A8C\u5931\u8D25\uFF1A");
|
|
118010
|
+
for (const e of errors) console.error(` \u2022 ${e}`);
|
|
118011
|
+
console.error();
|
|
118012
|
+
process.exit(1);
|
|
118013
|
+
}
|
|
118014
|
+
await withGoogleApi(opts, async (config, googleApiUrl) => {
|
|
118015
|
+
let body;
|
|
118016
|
+
try {
|
|
118017
|
+
body = await buildBrandAssetsApiBody(
|
|
118018
|
+
opts.patchFile,
|
|
118019
|
+
cfg,
|
|
118020
|
+
accountId,
|
|
118021
|
+
config,
|
|
118022
|
+
googleApiUrl,
|
|
118023
|
+
opts.verbose
|
|
118024
|
+
);
|
|
118025
|
+
} catch (err) {
|
|
118026
|
+
console.error(
|
|
118027
|
+
`
|
|
118028
|
+
\u274C \u6784\u5EFA brand-assets \u8BF7\u6C42\u4F53\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
|
|
118029
|
+
`
|
|
118030
|
+
);
|
|
118031
|
+
process.exit(1);
|
|
118032
|
+
}
|
|
118033
|
+
const url = pmaxBrandAssetsUrl(googleApiUrl, accountId, campaignId);
|
|
118034
|
+
try {
|
|
118035
|
+
await pmaxApiFetch(
|
|
118036
|
+
url,
|
|
118037
|
+
config,
|
|
118038
|
+
{ method: "PATCH", body: JSON.stringify(body) },
|
|
118039
|
+
opts.verbose
|
|
118040
|
+
);
|
|
118041
|
+
} catch (err) {
|
|
118042
|
+
console.error(
|
|
118043
|
+
`
|
|
118044
|
+
\u274C \u66F4\u65B0 PMax Campaign \u54C1\u724C\u8D44\u4EA7\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
|
|
118045
|
+
`
|
|
118046
|
+
);
|
|
118047
|
+
process.exit(1);
|
|
118048
|
+
}
|
|
118049
|
+
if (await emitPmaxResult(
|
|
118050
|
+
{ ...opts, account: accountId },
|
|
118051
|
+
`ad-pmax-brand-assets-edit-${accountId}`,
|
|
118052
|
+
"ad pmax-brand-assets-edit",
|
|
118053
|
+
{ ok: true, campaignId, patch: body },
|
|
118054
|
+
campaignId
|
|
118055
|
+
)) {
|
|
118056
|
+
return;
|
|
118057
|
+
}
|
|
118058
|
+
console.log(`
|
|
118059
|
+
\u2705 PMax \u6D3B\u52A8 ${campaignId} Campaign \u7EA7\u54C1\u724C\u8D44\u4EA7\u5DF2\u66F4\u65B0
|
|
118060
|
+
`);
|
|
118061
|
+
});
|
|
118062
|
+
}
|
|
118063
|
+
async function runAdPmaxBrandGuidelinesEnable(opts) {
|
|
118064
|
+
const cfgRaw = loadPmaxJsonFile(opts.configFile);
|
|
118065
|
+
const accountId = requireAccountId(String(cfgRaw["account"] ?? opts.account ?? ""));
|
|
118066
|
+
const campaignId = String(cfgRaw["campaignId"] ?? opts.campaignId ?? "").trim();
|
|
118067
|
+
if (!campaignId) {
|
|
118068
|
+
console.error("\n\u274C JSON \u987B\u5305\u542B campaignId\n");
|
|
118069
|
+
process.exit(1);
|
|
118070
|
+
}
|
|
118071
|
+
const cfg = loadBrandConfig(opts.configFile, accountId, campaignId);
|
|
118072
|
+
const errors = validateEnableBrandGuidelinesBody(cfg);
|
|
118073
|
+
if (errors.length > 0) {
|
|
118074
|
+
console.error("\n\u274C Enable Brand Guidelines \u914D\u7F6E\u6821\u9A8C\u5931\u8D25\uFF1A");
|
|
118075
|
+
for (const e of errors) console.error(` \u2022 ${e}`);
|
|
118076
|
+
console.error();
|
|
118077
|
+
process.exit(1);
|
|
118078
|
+
}
|
|
118079
|
+
await withGoogleApi(opts, async (config, googleApiUrl) => {
|
|
118080
|
+
let body;
|
|
118081
|
+
try {
|
|
118082
|
+
body = await buildEnableBrandGuidelinesApiBody(
|
|
118083
|
+
opts.configFile,
|
|
118084
|
+
cfg,
|
|
118085
|
+
accountId,
|
|
118086
|
+
config,
|
|
118087
|
+
googleApiUrl,
|
|
118088
|
+
opts.verbose
|
|
118089
|
+
);
|
|
118090
|
+
} catch (err) {
|
|
118091
|
+
console.error(
|
|
118092
|
+
`
|
|
118093
|
+
\u274C \u6784\u5EFA brand-guidelines/enable \u8BF7\u6C42\u4F53\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
|
|
118094
|
+
`
|
|
118095
|
+
);
|
|
118096
|
+
process.exit(1);
|
|
118097
|
+
}
|
|
118098
|
+
const url = pmaxBrandGuidelinesEnableUrl(googleApiUrl, accountId, campaignId);
|
|
118099
|
+
try {
|
|
118100
|
+
await pmaxApiFetch(url, config, { method: "POST", body: JSON.stringify(body) }, opts.verbose);
|
|
118101
|
+
} catch (err) {
|
|
118102
|
+
console.error(
|
|
118103
|
+
`
|
|
118104
|
+
\u274C \u542F\u7528 Brand Guidelines \u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
|
|
118105
|
+
`
|
|
118106
|
+
);
|
|
118107
|
+
process.exit(1);
|
|
118108
|
+
}
|
|
118109
|
+
if (await emitPmaxResult(
|
|
118110
|
+
{ ...opts, account: accountId },
|
|
118111
|
+
`ad-pmax-brand-guidelines-enable-${accountId}`,
|
|
118112
|
+
"ad pmax-brand-guidelines-enable",
|
|
118113
|
+
{ ok: true, campaignId, body },
|
|
118114
|
+
campaignId
|
|
118115
|
+
)) {
|
|
118116
|
+
return;
|
|
118117
|
+
}
|
|
118118
|
+
console.log(`
|
|
118119
|
+
\u2705 PMax \u6D3B\u52A8 ${campaignId} \u5DF2\u542F\u7528 Brand Guidelines\uFF08\u4E0D\u53EF\u5173\u95ED\uFF09
|
|
118120
|
+
`);
|
|
118121
|
+
console.log(" \u540E\u7EED\u6539\u54C1\u724C\u8BF7\u7528\uFF1Aad pmax-brand-assets-edit\n");
|
|
118122
|
+
});
|
|
118123
|
+
}
|
|
118124
|
+
|
|
117270
118125
|
// src/commands/ad/_register.ts
|
|
117271
118126
|
function register20(program2) {
|
|
117272
118127
|
const adCmd = program2.command("ad").description(
|
|
@@ -117316,7 +118171,10 @@ function register20(program2) {
|
|
|
117316
118171
|
);
|
|
117317
118172
|
adBatchCmd.command("diff").description(
|
|
117318
118173
|
"\u6210\u529F\u6216\u90E8\u5206\u6210\u529F\uFF1A\u6309 campaign-create JSON \u4E0E\u8D26\u6237\u5B9E\u51B5\u9010\u5C42\u6BD4\u5BF9\n Failed \u4E0D\u9002\u7528\uFF08\u7CFB\u5217\u672A\u521B\u5EFA\uFF09\uFF0C\u8BF7\u7528 ad batch get \u67E5\u770B reason/errors \u540E\u6539 JSON \u91CD\u63D0\n\n \u793A\u4F8B\uFF1A\n siluzan-tso ad batch diff --campaign-id <id> --config-file ./campaign.json\n siluzan-tso ad batch diff --batch-id <taskId> --campaign-id <id> --config-file ./campaign.json"
|
|
117319
|
-
).requiredOption("--config-file <path>", "\u63D0\u4EA4\u65F6\u4F7F\u7528\u7684 campaign-create JSON").option(
|
|
118174
|
+
).requiredOption("--config-file <path>", "\u63D0\u4EA4\u65F6\u4F7F\u7528\u7684 campaign-create JSON").option(
|
|
118175
|
+
"--campaign-id <id>",
|
|
118176
|
+
"Google \u5E7F\u544A\u7CFB\u5217 ID\uFF08\u53EF\u4ECE ad campaigns --json-out \u83B7\u53D6\uFF1B\u7701\u7565\u65F6\u6309\u7CFB\u5217\u540D\u5339\u914D\uFF09"
|
|
118177
|
+
).option("--batch-id <id>", "\u6279\u91CF\u4EFB\u52A1 ID\uFF08\u6821\u9A8C\u975E Failed\uFF1BHasFailed \u65F6\u53EF\u7701\u7565 campaign-id\uFF09").option("-a, --account <id>", "\u5A92\u4F53\u8D26\u6237 ID\uFF08\u9ED8\u8BA4\u8BFB JSON \u7684 account\uFF09").option("--json-out <path>", "\u6BD4\u5BF9\u7ED3\u679C\u843D\u76D8\u8DEF\u5F84").option("-t, --token <token>", "Auth Token").option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(
|
|
117320
118178
|
async (opts) => {
|
|
117321
118179
|
await runCampaignBatchDiff({
|
|
117322
118180
|
token: opts.token,
|
|
@@ -117605,13 +118463,7 @@ function register20(program2) {
|
|
|
117605
118463
|
);
|
|
117606
118464
|
adCmd.command("campaign-validate").description(
|
|
117607
118465
|
"\u6821\u9A8C campaign-create JSON\uFF08\u8BCD\u9762\u89C4\u8303\u5316 + \u540E\u7AEF\u786C\u7EA6\u675F\uFF1B\u4E0D\u8C03\u7528 API\uFF09\n\n \u7528\u6CD5\uFF1A\n siluzan-tso ad campaign-validate --config-file ./campaign.json --json-out ./snap-campaign\n siluzan-tso ad campaign-validate --config-file ./campaign.json --write-normalized ./campaign.normalized.json"
|
|
117608
|
-
).requiredOption("--config-file <path>", "campaign-create JSON \u8DEF\u5F84").option(
|
|
117609
|
-
"--write-normalized <path>",
|
|
117610
|
-
"\u5C06\u89C4\u8303\u5316\u540E\u7684 JSON \u5199\u5165\u8BE5\u8DEF\u5F84\uFF08\u5173\u952E\u8BCD\u8BCD\u9762\u5DF2\u4FEE\u6B63\uFF09"
|
|
117611
|
-
).option(
|
|
117612
|
-
"--json-out <path>",
|
|
117613
|
-
"\u843D\u76D8\u6821\u9A8C\u7ED3\u679C\uFF08\u542B lengthViolations\uFF09\u5E76\u66F4\u65B0 cli-manifest"
|
|
117614
|
-
).option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(
|
|
118466
|
+
).requiredOption("--config-file <path>", "campaign-create JSON \u8DEF\u5F84").option("--write-normalized <path>", "\u5C06\u89C4\u8303\u5316\u540E\u7684 JSON \u5199\u5165\u8BE5\u8DEF\u5F84\uFF08\u5173\u952E\u8BCD\u8BCD\u9762\u5DF2\u4FEE\u6B63\uFF09").option("--json-out <path>", "\u843D\u76D8\u6821\u9A8C\u7ED3\u679C\uFF08\u542B lengthViolations\uFF09\u5E76\u66F4\u65B0 cli-manifest").option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(
|
|
117615
118467
|
async (opts) => {
|
|
117616
118468
|
await runAdCampaignValidate({
|
|
117617
118469
|
configFile: opts.configFile,
|
|
@@ -117642,10 +118494,7 @@ function register20(program2) {
|
|
|
117642
118494
|
);
|
|
117643
118495
|
adCmd.command("pmax-validate").description(
|
|
117644
118496
|
"\u6821\u9A8C pmax-create JSON\uFF08PMax \u4E13\u7528\uFF1B\u4E0D\u8C03\u7528 API\uFF09\n\n \u7528\u6CD5\uFF1A\n siluzan-tso ad pmax-validate --config-file ./pmax.json --json-out ./snap-pmax\n siluzan-tso ad pmax-validate --config-file ./pmax.json --write-normalized ./pmax.normalized.json"
|
|
117645
|
-
).requiredOption("--config-file <path>", "pmax-create JSON \u8DEF\u5F84").option("--write-normalized <path>", "\u5C06 JSON \u5199\u5165\u8BE5\u8DEF\u5F84\uFF08\u4FDD\u7559 _ \u6CE8\u89E3\u952E\uFF09").option(
|
|
117646
|
-
"--json-out <path>",
|
|
117647
|
-
"\u843D\u76D8\u6821\u9A8C\u7ED3\u679C\u5E76\u66F4\u65B0 cli-manifest"
|
|
117648
|
-
).option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(
|
|
118497
|
+
).requiredOption("--config-file <path>", "pmax-create JSON \u8DEF\u5F84").option("--write-normalized <path>", "\u5C06 JSON \u5199\u5165\u8BE5\u8DEF\u5F84\uFF08\u4FDD\u7559 _ \u6CE8\u89E3\u952E\uFF09").option("--json-out <path>", "\u843D\u76D8\u6821\u9A8C\u7ED3\u679C\u5E76\u66F4\u65B0 cli-manifest").option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(
|
|
117649
118498
|
async (opts) => {
|
|
117650
118499
|
await runAdPmaxValidate({
|
|
117651
118500
|
configFile: opts.configFile,
|
|
@@ -117660,11 +118509,7 @@ function register20(program2) {
|
|
|
117660
118509
|
).requiredOption(
|
|
117661
118510
|
"--config-file <path>",
|
|
117662
118511
|
"JSON \u914D\u7F6E\u6587\u4EF6\uFF08\u6A21\u677F\u89C1 assets/siluzan-ads/assets/pmax-create-*.json|md\uFF09"
|
|
117663
|
-
).option("-t, --token <token>", "Auth Token").option(
|
|
117664
|
-
"--json-out <path>",
|
|
117665
|
-
"\u843D\u76D8\u54CD\u5E94\u5E76\u66F4\u65B0 cli-manifest",
|
|
117666
|
-
void 0
|
|
117667
|
-
).option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(
|
|
118512
|
+
).option("-t, --token <token>", "Auth Token").option("--json-out <path>", "\u843D\u76D8\u54CD\u5E94\u5E76\u66F4\u65B0 cli-manifest", void 0).option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(
|
|
117668
118513
|
async (opts) => {
|
|
117669
118514
|
await runAdPmaxCreate({
|
|
117670
118515
|
configFile: opts.configFile,
|
|
@@ -117695,9 +118540,11 @@ function register20(program2) {
|
|
|
117695
118540
|
);
|
|
117696
118541
|
addPmaxJsonOptions(
|
|
117697
118542
|
adCmd.command("pmax-channel-types").description("\u5217\u51FA PMax \u4E0E\u5176\u5B83\u6E20\u9053\u7C7B\u578B\u679A\u4E3E\uFF08GET /campaign/pmax/channel-types\uFF09").option("-t, --token <token>", "Auth Token")
|
|
117698
|
-
).action(
|
|
117699
|
-
|
|
117700
|
-
|
|
118543
|
+
).action(
|
|
118544
|
+
async (opts) => {
|
|
118545
|
+
await runAdPmaxChannelTypes(opts);
|
|
118546
|
+
}
|
|
118547
|
+
);
|
|
117701
118548
|
addPmaxJsonOptions(
|
|
117702
118549
|
adCmd.command("pmax-get").description("\u83B7\u53D6 PMax \u6D3B\u52A8\u8BE6\u60C5\uFF08\u542B\u8D44\u4EA7\u7EC4\u4E0E ENABLED \u8D44\u4EA7\uFF09").requiredOption("-a, --account <id>", "Google \u5A92\u4F53\u5BA2\u6237 ID").requiredOption("--campaign-id <id>", "\u6D3B\u52A8 ID").option("-t, --token <token>", "Auth Token")
|
|
117703
118550
|
).action(
|
|
@@ -117714,9 +118561,27 @@ function register20(program2) {
|
|
|
117714
118561
|
await runAdPmaxEdit(opts);
|
|
117715
118562
|
}
|
|
117716
118563
|
);
|
|
118564
|
+
addPmaxJsonOptions(
|
|
118565
|
+
adCmd.command("pmax-brand-assets-edit").description(
|
|
118566
|
+
"\u66F4\u65B0 PMax Campaign \u7EA7\u54C1\u724C\u8D44\u4EA7/\u6837\u5F0F\uFF08PATCH .../brand-assets\uFF1BBG \u5DF2\u5F00\u542F\u65F6\u7528\u6B64\u547D\u4EE4\uFF0C\u52FF pmax-assets-update \u6539 BUSINESS_NAME/LOGO\uFF09\n \u6A21\u677F pmax-brand-assets-template.json"
|
|
118567
|
+
).requiredOption("-a, --account <id>", "Google \u5A92\u4F53\u5BA2\u6237 ID").requiredOption("--campaign-id <id>", "\u6D3B\u52A8 ID").requiredOption("--patch-file <path>", "PATCH JSON\uFF08\u81F3\u5C11\u4F20\u4E00\u9879\u53D8\u66F4\u5B57\u6BB5\uFF09").option("-t, --token <token>", "Auth Token")
|
|
118568
|
+
).action(
|
|
118569
|
+
async (opts) => {
|
|
118570
|
+
await runAdPmaxBrandAssetsEdit(opts);
|
|
118571
|
+
}
|
|
118572
|
+
);
|
|
118573
|
+
addPmaxJsonOptions(
|
|
118574
|
+
adCmd.command("pmax-brand-guidelines-enable").description(
|
|
118575
|
+
"\u5B58\u91CF PMax \u6D3B\u52A8\u542F\u7528 Brand Guidelines\uFF08POST .../brand-guidelines/enable\uFF1B\u542F\u7528\u540E\u4E0D\u53EF\u5173\u95ED\uFF09\n \u6A21\u677F pmax-brand-guidelines-enable-template.json"
|
|
118576
|
+
).requiredOption("--config-file <path>", "JSON \u914D\u7F6E\u8DEF\u5F84").option("-a, --account <id>", "Google \u5A92\u4F53\u5BA2\u6237 ID\uFF08\u53EF\u5199\u5728 JSON account\uFF09").option("--campaign-id <id>", "\u6D3B\u52A8 ID\uFF08\u53EF\u5199\u5728 JSON campaignId\uFF09").option("-t, --token <token>", "Auth Token")
|
|
118577
|
+
).action(
|
|
118578
|
+
async (opts) => {
|
|
118579
|
+
await runAdPmaxBrandGuidelinesEnable(opts);
|
|
118580
|
+
}
|
|
118581
|
+
);
|
|
117717
118582
|
addPmaxJsonOptions(
|
|
117718
118583
|
adCmd.command("pmax-asset-group-create").description(
|
|
117719
|
-
"\u5728\u540C\u4E00 PMax \u6D3B\u52A8\u4E0B\u65B0\u5EFA\u8D44\u4EA7\u7EC4\uFF08\u6A21\u677F pmax-asset-group-template.json\uFF09"
|
|
118584
|
+
"\u5728\u540C\u4E00 PMax \u6D3B\u52A8\u4E0B\u65B0\u5EFA\u8D44\u4EA7\u7EC4\uFF08\u6A21\u677F pmax-asset-group-template.json\uFF1BBG \u751F\u6548\u65F6\u81EA\u52A8\u7701\u7565\u54C1\u724C\u5B57\u6BB5\uFF09"
|
|
117720
118585
|
).requiredOption("--config-file <path>", "JSON \u914D\u7F6E\u8DEF\u5F84").option("-t, --token <token>", "Auth Token")
|
|
117721
118586
|
).action(
|
|
117722
118587
|
async (opts) => {
|
|
@@ -117732,7 +118597,7 @@ function register20(program2) {
|
|
|
117732
118597
|
);
|
|
117733
118598
|
addPmaxJsonOptions(
|
|
117734
118599
|
adCmd.command("pmax-assets-update").description(
|
|
117735
|
-
"\u6279\u91CF\u94FE\u63A5/\u53D6\u6D88\u94FE\u63A5/\u66FF\u6362\u8D44\u4EA7\uFF08PUT .../assets\
|
|
118600
|
+
"\u6279\u91CF\u94FE\u63A5/\u53D6\u6D88\u94FE\u63A5/\u66FF\u6362\u8D44\u4EA7\uFF08PUT .../assets\uFF1BBG \u751F\u6548\u65F6\u52FF\u6539 BUSINESS_NAME/LOGO\uFF0C\u7528 pmax-brand-assets-edit\uFF09"
|
|
117736
118601
|
).requiredOption("--config-file <path>", "JSON \u914D\u7F6E\u8DEF\u5F84").option("-t, --token <token>", "Auth Token")
|
|
117737
118602
|
).action(
|
|
117738
118603
|
async (opts) => {
|
|
@@ -117742,7 +118607,10 @@ function register20(program2) {
|
|
|
117742
118607
|
addPmaxJsonOptions(
|
|
117743
118608
|
adCmd.command("pmax-youtube-link").description(
|
|
117744
118609
|
"\u4E3A\u8D44\u4EA7\u7EC4\u8FFD\u52A0\u5355\u6761 YouTube \u89C6\u9891\uFF08POST .../youtube\uFF1B\u9ED8\u8BA4\u4E0D\u66FF\u6362\u5DF2\u6709\u89C6\u9891\uFF0C\u6BCF\u8D44\u4EA7\u7EC4 \u226415 \u6761\uFF09\n --youtube\uFF1A\u5DF2\u6709 URL/ID\uFF1B--video-path\uFF1A\u672C\u5730\u6587\u4EF6\u7ECF PyAPI \u4E0A\u4F20\u540E\u94FE\u63A5\uFF1B\u6279\u91CF\u8FFD\u52A0\u7528 pmax-assets-update"
|
|
117745
|
-
).requiredOption("-a, --account <id>", "Google \u5A92\u4F53\u5BA2\u6237 ID").requiredOption("--asset-group-id <id>", "\u8D44\u4EA7\u7EC4 ID").option("--campaign-id <id>", "\u6240\u5C5E\u6D3B\u52A8 ID\uFF08\u5EFA\u8BAE\u4F20\uFF09").option("--youtube <urlOrId>", "YouTube URL \u6216 11 \u4F4D\u89C6\u9891 ID\uFF08\u4E0E --video-path \u4E8C\u9009\u4E00\uFF09").option("--video-path <path>", "\u672C\u5730\u89C6\u9891\u8DEF\u5F84\uFF08PyAPI \u4E0A\u4F20\uFF1B\u4E0E --youtube \u4E8C\u9009\u4E00\uFF09").option("--video-title <title>", "PyAPI \u4E0A\u4F20\u6807\u9898\uFF08\u9ED8\u8BA4\u6587\u4EF6\u540D\uFF09").option("--video-description <text>", "PyAPI \u4E0A\u4F20\u63CF\u8FF0").option("--asset-name <name>", "\u8D44\u4EA7\u663E\u793A\u540D").option(
|
|
118610
|
+
).requiredOption("-a, --account <id>", "Google \u5A92\u4F53\u5BA2\u6237 ID").requiredOption("--asset-group-id <id>", "\u8D44\u4EA7\u7EC4 ID").option("--campaign-id <id>", "\u6240\u5C5E\u6D3B\u52A8 ID\uFF08\u5EFA\u8BAE\u4F20\uFF09").option("--youtube <urlOrId>", "YouTube URL \u6216 11 \u4F4D\u89C6\u9891 ID\uFF08\u4E0E --video-path \u4E8C\u9009\u4E00\uFF09").option("--video-path <path>", "\u672C\u5730\u89C6\u9891\u8DEF\u5F84\uFF08PyAPI \u4E0A\u4F20\uFF1B\u4E0E --youtube \u4E8C\u9009\u4E00\uFF09").option("--video-title <title>", "PyAPI \u4E0A\u4F20\u6807\u9898\uFF08\u9ED8\u8BA4\u6587\u4EF6\u540D\uFF09").option("--video-description <text>", "PyAPI \u4E0A\u4F20\u63CF\u8FF0").option("--asset-name <name>", "\u8D44\u4EA7\u663E\u793A\u540D").option(
|
|
118611
|
+
"--body-file <path>",
|
|
118612
|
+
"\u5B8C\u6574 JSON body\uFF08\u542B youtubeUrlOrId\uFF1B\u8986\u76D6 --youtube/--video-path\uFF09"
|
|
118613
|
+
).option("-t, --token <token>", "Auth Token")
|
|
117746
118614
|
).action(
|
|
117747
118615
|
async (opts) => {
|
|
117748
118616
|
await runAdPmaxYoutubeLink({
|
|
@@ -117769,9 +118637,7 @@ function register20(program2) {
|
|
|
117769
118637
|
}
|
|
117770
118638
|
);
|
|
117771
118639
|
addPmaxJsonOptions(
|
|
117772
|
-
adCmd.command("pmax-signals-set").description(
|
|
117773
|
-
"\u5168\u91CF\u540C\u6B65\u8D44\u4EA7\u7EC4\u4FE1\u53F7\uFF08PUT .../signals\uFF1B\u6A21\u677F pmax-signals-template.json\uFF09"
|
|
117774
|
-
).requiredOption("--config-file <path>", "JSON \u914D\u7F6E\u8DEF\u5F84").option("-t, --token <token>", "Auth Token")
|
|
118640
|
+
adCmd.command("pmax-signals-set").description("\u5168\u91CF\u540C\u6B65\u8D44\u4EA7\u7EC4\u4FE1\u53F7\uFF08PUT .../signals\uFF1B\u6A21\u677F pmax-signals-template.json\uFF09").requiredOption("--config-file <path>", "JSON \u914D\u7F6E\u8DEF\u5F84").option("-t, --token <token>", "Auth Token")
|
|
117775
118641
|
).action(
|
|
117776
118642
|
async (opts) => {
|
|
117777
118643
|
await runAdPmaxSignalsSet(opts);
|
|
@@ -117780,11 +118646,7 @@ function register20(program2) {
|
|
|
117780
118646
|
addPmaxJsonOptions(
|
|
117781
118647
|
adCmd.command("pmax-audiences").description(
|
|
117782
118648
|
"\u62C9\u53D6 PMax \u4FE1\u53F7\u7528\u53D7\u4F17\u5217\u8868\uFF08userlists \u4E0E/\u6216 audiences\uFF1BresourceName \u7528\u4E8E signals PUT\uFF09"
|
|
117783
|
-
).requiredOption("-a, --account <id>", "Google \u5A92\u4F53\u5BA2\u6237 ID").option(
|
|
117784
|
-
"--source <source>",
|
|
117785
|
-
"userlists | audiences | both\uFF08\u9ED8\u8BA4 both\uFF09",
|
|
117786
|
-
"both"
|
|
117787
|
-
).option("--limit <n>", "audiences \u63A5\u53E3\u53EF\u9009 limit\uFF081\u20132000\uFF09", (v) => parseInt(v, 10)).option("-t, --token <token>", "Auth Token")
|
|
118649
|
+
).requiredOption("-a, --account <id>", "Google \u5A92\u4F53\u5BA2\u6237 ID").option("--source <source>", "userlists | audiences | both\uFF08\u9ED8\u8BA4 both\uFF09", "both").option("--limit <n>", "audiences \u63A5\u53E3\u53EF\u9009 limit\uFF081\u20132000\uFF09", (v) => parseInt(v, 10)).option("-t, --token <token>", "Auth Token")
|
|
117788
118650
|
).action(
|
|
117789
118651
|
async (opts) => {
|
|
117790
118652
|
const src = (opts.source ?? "both").toLowerCase();
|
|
@@ -117878,11 +118740,11 @@ function register20(program2) {
|
|
|
117878
118740
|
"--target-cpa <amount>",
|
|
117879
118741
|
"TARGET_CPA / MAXIMIZE_CONVERSIONS\uFF08\u5E26\u76EE\u6807 CPA\uFF09\u76EE\u6807 CPA\uFF0C\u4E3B\u5E01\u79CD\u91D1\u989D\uFF08CLI \u5185\u90E8 \xD7100 \u5199\u5165\uFF09",
|
|
117880
118742
|
parseFloat
|
|
118743
|
+
).option("--target-roas <n>", "TARGET_ROAS \u76EE\u6807 ROAS\uFF08\u5982 2.5 = 250%\uFF0C\u52FF\u586B 250\uFF09", parseFloat).option(
|
|
118744
|
+
"--manual-ecpc <bool>",
|
|
118745
|
+
"MANUAL_CPC \u662F\u5426\u542F\u7528 eCPC\uFF1Atrue | false",
|
|
118746
|
+
(v) => v === "true"
|
|
117881
118747
|
).option(
|
|
117882
|
-
"--target-roas <n>",
|
|
117883
|
-
"TARGET_ROAS \u76EE\u6807 ROAS\uFF08\u5982 2.5 = 250%\uFF0C\u52FF\u586B 250\uFF09",
|
|
117884
|
-
parseFloat
|
|
117885
|
-
).option("--manual-ecpc <bool>", "MANUAL_CPC \u662F\u5426\u542F\u7528 eCPC\uFF1Atrue | false", (v) => v === "true").option(
|
|
117886
118748
|
"--search-network <bool>",
|
|
117887
118749
|
"\u6295\u653E\u5230 Google \u641C\u7D22\uFF1Atrue | false",
|
|
117888
118750
|
(v) => v === "true"
|
|
@@ -118043,7 +118905,10 @@ function register20(program2) {
|
|
|
118043
118905
|
});
|
|
118044
118906
|
}
|
|
118045
118907
|
);
|
|
118046
|
-
adCmd.command("keyword-negative-edit").description("\u7F16\u8F91\u5426\u5B9A\u5173\u952E\u8BCD\uFF08\u6587\u672C\u6216\u5339\u914D\u7C7B\u578B\uFF0C\u81F3\u5C11\u4F20\u4E00\u4E2A\u4FEE\u6539\u9879\uFF09").requiredOption("-a, --account <id>", "Google \u8D26\u6237 mediaCustomerId").requiredOption(
|
|
118908
|
+
adCmd.command("keyword-negative-edit").description("\u7F16\u8F91\u5426\u5B9A\u5173\u952E\u8BCD\uFF08\u6587\u672C\u6216\u5339\u914D\u7C7B\u578B\uFF0C\u81F3\u5C11\u4F20\u4E00\u4E2A\u4FEE\u6539\u9879\uFF09").requiredOption("-a, --account <id>", "Google \u8D26\u6237 mediaCustomerId").requiredOption(
|
|
118909
|
+
"--id <keywordId>",
|
|
118910
|
+
"\u5426\u5B9A\u5173\u952E\u8BCD ID\uFF08\u6765\u81EA ad keywords --negative --json-out \u2192 id\uFF09"
|
|
118911
|
+
).option("--text <text>", "\u65B0\u5173\u952E\u8BCD\u6587\u672C").option(
|
|
118047
118912
|
"--match-type <type>",
|
|
118048
118913
|
'\u65B0\u5339\u914D\u7C7B\u578B\uFF1ABroad | Phrase | Exact\uFF08\u5199 matchTypeV2\uFF0C\u5E76\u9ED8\u8BA4\u540C\u6B65\u6539\u5199 keywordText \u4E3A\u8BCD\u5E72/"\u8BCD"/[\u8BCD]\uFF09'
|
|
118049
118914
|
).option("--start <date>", "\u5217\u8868\u67E5\u8BE2\u8D77\u59CB\u65E5\u671F YYYY-MM-DD").option("--end <date>", "\u5217\u8868\u67E5\u8BE2\u7ED3\u675F\u65E5\u671F YYYY-MM-DD").option("-t, --token <token>", "Auth Token").option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(
|
|
@@ -118071,28 +118936,24 @@ function register20(program2) {
|
|
|
118071
118936
|
"--json-out <path>",
|
|
118072
118937
|
"\u843D\u76D8\uFF08\u76EE\u5F55\u6216 *.json \u6587\u4EF6\u8DEF\u5F84\uFF09\u5E76\u66F4\u65B0 cli-manifest[-<\u67E5\u8BE2id>].json",
|
|
118073
118938
|
void 0
|
|
118074
|
-
).option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(
|
|
118075
|
-
|
|
118076
|
-
|
|
118077
|
-
|
|
118078
|
-
|
|
118079
|
-
|
|
118080
|
-
|
|
118081
|
-
}
|
|
118082
|
-
);
|
|
118939
|
+
).option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(async (opts) => {
|
|
118940
|
+
await runAdExtensionPmaxTypes({
|
|
118941
|
+
token: opts.token,
|
|
118942
|
+
jsonOut: opts.jsonOut,
|
|
118943
|
+
verbose: opts.verbose
|
|
118944
|
+
});
|
|
118945
|
+
});
|
|
118083
118946
|
extensionCmd.command("snippet-headers").description("\u67E5\u8BE2\u7ED3\u6784\u5316\u6458\u8981\u53EF\u9009\u6807\u5934\uFF08\u6309\u8BED\u8A00\u5206\u7EC4\uFF09").option("-t, --token <token>", "Auth Token").option(
|
|
118084
118947
|
"--json-out <path>",
|
|
118085
118948
|
"\u843D\u76D8\uFF08\u76EE\u5F55\u6216 *.json \u6587\u4EF6\u8DEF\u5F84\uFF09\u5E76\u66F4\u65B0 cli-manifest[-<\u67E5\u8BE2id>].json",
|
|
118086
118949
|
void 0
|
|
118087
|
-
).option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(
|
|
118088
|
-
|
|
118089
|
-
|
|
118090
|
-
|
|
118091
|
-
|
|
118092
|
-
|
|
118093
|
-
|
|
118094
|
-
}
|
|
118095
|
-
);
|
|
118950
|
+
).option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(async (opts) => {
|
|
118951
|
+
await runAdExtensionSnippetHeaders({
|
|
118952
|
+
token: opts.token,
|
|
118953
|
+
jsonOut: opts.jsonOut,
|
|
118954
|
+
verbose: opts.verbose
|
|
118955
|
+
});
|
|
118956
|
+
});
|
|
118096
118957
|
extensionCmd.command("list").description("\u67E5\u8BE2\u9644\u52A0\u4FE1\u606F\u5217\u8868").requiredOption("-a, --account <id>", "Google \u8D26\u6237 mediaCustomerId").option(
|
|
118097
118958
|
"--type <type>",
|
|
118098
118959
|
"\u6309\u7C7B\u578B\u7B5B\u9009\uFF1ASITELINK | CALL | CALLOUT | STRUCTURED_SNIPPET | LEAD_FORM | BUSINESS_MESSAGE"
|
|
@@ -118114,7 +118975,10 @@ function register20(program2) {
|
|
|
118114
118975
|
);
|
|
118115
118976
|
extensionCmd.command("lead-form").description(
|
|
118116
118977
|
"\u4E3A PMax \u6D3B\u52A8\u6DFB\u52A0\u6F5C\u5728\u5BA2\u6237\u8868\u5355\uFF08LEAD_FORM\uFF1B\u4EC5 Campaign \u7EA7\uFF1B\u987B --config-file\uFF09\n \u6A21\u677F\uFF1Aassets/pmax-lead-form-template.json"
|
|
118117
|
-
).requiredOption("-a, --account <id>", "Google \u8D26\u6237 mediaCustomerId").requiredOption(
|
|
118978
|
+
).requiredOption("-a, --account <id>", "Google \u8D26\u6237 mediaCustomerId").requiredOption(
|
|
118979
|
+
"--config-file <path>",
|
|
118980
|
+
"Lead Form JSON \u914D\u7F6E\uFF08\u89C1 pmax-lead-form-template.json\uFF09"
|
|
118981
|
+
).option("--campaign-id <id>", "\u8986\u76D6\u914D\u7F6E\u6587\u4EF6\u4E2D\u7684 campaignId").option("-t, --token <token>", "Auth Token").option(
|
|
118118
118982
|
"--json-out <path>",
|
|
118119
118983
|
"\u843D\u76D8\uFF08\u76EE\u5F55\u6216 *.json \u6587\u4EF6\u8DEF\u5F84\uFF09\u5E76\u66F4\u65B0 cli-manifest[-<\u67E5\u8BE2id>].json",
|
|
118120
118984
|
void 0
|
|
@@ -118338,7 +119202,13 @@ function register20(program2) {
|
|
|
118338
119202
|
"--bid-modifier <n>",
|
|
118339
119203
|
"Google \u51FA\u4EF7\u500D\u7387\uFF1A1.0=\u4E0D\u8C03\u6574\uFF0C1.2=+20%\uFF0C0.8=-20%",
|
|
118340
119204
|
parseFloat
|
|
118341
|
-
).option(
|
|
119205
|
+
).option(
|
|
119206
|
+
"--criterion-id <id>",
|
|
119207
|
+
"campaign_criterion id\uFF08geo list --mode targeted --json-out \u2192 id\uFF09"
|
|
119208
|
+
).option(
|
|
119209
|
+
"--location-id <id>",
|
|
119210
|
+
"\u5730\u7406\u4F4D\u7F6E ID\uFF08\u4E0E list/search \u7684 id \u76F8\u540C\uFF1B\u4E0E --criterion-id \u4E8C\u9009\u4E00\uFF09"
|
|
119211
|
+
).option("-t, --token <token>", "Auth Token").option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(
|
|
118342
119212
|
async (opts) => {
|
|
118343
119213
|
if (!opts.criterionId && !opts.locationId) {
|
|
118344
119214
|
console.error("\n\u274C \u987B\u6307\u5B9A --criterion-id \u6216 --location-id\n");
|
|
@@ -118387,7 +119257,10 @@ function register20(program2) {
|
|
|
118387
119257
|
"--bid-modifier <n>",
|
|
118388
119258
|
"\u51FA\u4EF7\u500D\u7387\uFF081.0=\u57FA\u51C6\uFF1B0.8=-20%\uFF1B1.2=+20%\uFF1B0=\u6392\u9664\u8BE5\u8BBE\u5907\uFF09",
|
|
118389
119259
|
parseFloat
|
|
118390
|
-
).option("--level <level>", "campaign | adgroup\uFF08\u9ED8\u8BA4 campaign\uFF09", "campaign").option("--ad-group-id <id>", "\u5E7F\u544A\u7EC4 ID\uFF08--level adgroup \u65F6\u5FC5\u586B\uFF09").option("--criterion-id <id>", "\u8BBE\u5907\u51FA\u4EF7 criterion id\uFF08\u6765\u81EA list --json-out \u2192 id\uFF09").option(
|
|
119260
|
+
).option("--level <level>", "campaign | adgroup\uFF08\u9ED8\u8BA4 campaign\uFF09", "campaign").option("--ad-group-id <id>", "\u5E7F\u544A\u7EC4 ID\uFF08--level adgroup \u65F6\u5FC5\u586B\uFF09").option("--criterion-id <id>", "\u8BBE\u5907\u51FA\u4EF7 criterion id\uFF08\u6765\u81EA list --json-out \u2192 id\uFF09").option(
|
|
119261
|
+
"--device-type <type>",
|
|
119262
|
+
"\u8BBE\u5907\u7C7B\u578B\uFF1AMobile | Desktop | Tablet\uFF08\u4E0E --criterion-id \u4E8C\u9009\u4E00\uFF09"
|
|
119263
|
+
).option("-t, --token <token>", "Auth Token").option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(
|
|
118391
119264
|
async (opts) => {
|
|
118392
119265
|
if (!["campaign", "adgroup"].includes(opts.level)) {
|
|
118393
119266
|
console.error("\n\u274C --level \u53EA\u63A5\u53D7 campaign | adgroup\n");
|
|
@@ -118445,12 +119318,7 @@ async function fetchAccountPositiveKeywordNormSet(config, googleApiUrl, accountI
|
|
|
118445
119318
|
params.set("startDate", toGoogleDate(void 0, -lookback));
|
|
118446
119319
|
params.set("endDate", toGoogleDate(void 0, 0));
|
|
118447
119320
|
const url = `${googleApiUrl}/keywordmanagement/v2/list/${accountId}?${params}`;
|
|
118448
|
-
const data = await apiFetch2(
|
|
118449
|
-
url,
|
|
118450
|
-
config,
|
|
118451
|
-
{},
|
|
118452
|
-
opts?.verbose
|
|
118453
|
-
);
|
|
119321
|
+
const data = await apiFetch2(url, config, {}, opts?.verbose);
|
|
118454
119322
|
const rawItems = data.data ?? [];
|
|
118455
119323
|
const seenIds = /* @__PURE__ */ new Set();
|
|
118456
119324
|
const normSet = /* @__PURE__ */ new Set();
|
|
@@ -118603,7 +119471,12 @@ async function runKeywordGeoList(opts) {
|
|
|
118603
119471
|
const apiUrl = geoTargetConstantsListPath(googleApiUrl);
|
|
118604
119472
|
let items = [];
|
|
118605
119473
|
try {
|
|
118606
|
-
const data = await apiFetch2(
|
|
119474
|
+
const data = await apiFetch2(
|
|
119475
|
+
apiUrl,
|
|
119476
|
+
config,
|
|
119477
|
+
{ method: "GET" },
|
|
119478
|
+
opts.verbose
|
|
119479
|
+
);
|
|
118607
119480
|
items = Array.isArray(data) ? data : [];
|
|
118608
119481
|
} catch (err) {
|
|
118609
119482
|
console.error(
|
|
@@ -119353,11 +120226,7 @@ function register22(program2) {
|
|
|
119353
120226
|
const ragCmd = program2.command("rag").description(
|
|
119354
120227
|
"RAG \u77E5\u8BC6\u5E93\u68C0\u7D22\uFF08\u534F\u52A9\u5E7F\u544A\u6295\u653E\u3001\u62D3\u8BCD\u3001\u8D26\u6237\u8BCA\u65AD\u4E0E\u62A5\u544A\uFF1B\u9ED8\u8BA4 belongToId=account/me \u7684 companyId\uFF09"
|
|
119355
120228
|
);
|
|
119356
|
-
ragCmd.command("list").description("\u5217\u51FA\u5F53\u524D\u4F01\u4E1A\u4E0B\u6240\u6709\u77E5\u8BC6\u5E93\u6587\u4EF6\u5939\uFF0C\u4F9B\u9009\u62E9\u5BA2\u6237/\u54C1\u724C\u68C0\u7D22\u8303\u56F4").option("--belong-to-id <id>", "\u4F01\u4E1A ID\uFF1B\u4E0D\u4F20\u5219\u4ECE GET /query/account/me \u53D6 companyId").option("--rag-only", "\u4EC5\u663E\u793A\u5DF2\u5EFA RAG \u5411\u91CF\u7D22\u5F15\uFF08ragStatus=true\uFF09\u7684\u77E5\u8BC6\u5E93", false).option("-t, --token <token>", "Token\uFF08\u53EF\u9009\uFF1B\u4F18\u5148\u4E8E ~/.siluzan/config.json\uFF09").option(
|
|
119357
|
-
"--json-out <path>",
|
|
119358
|
-
"\u843D\u76D8 JSON + outline + cli-manifest\uFF1Bstdout \u4E00\u884C\u6458\u8981",
|
|
119359
|
-
void 0
|
|
119360
|
-
).option("--verbose", "\u663E\u793A\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(
|
|
120229
|
+
ragCmd.command("list").description("\u5217\u51FA\u5F53\u524D\u4F01\u4E1A\u4E0B\u6240\u6709\u77E5\u8BC6\u5E93\u6587\u4EF6\u5939\uFF0C\u4F9B\u9009\u62E9\u5BA2\u6237/\u54C1\u724C\u68C0\u7D22\u8303\u56F4").option("--belong-to-id <id>", "\u4F01\u4E1A ID\uFF1B\u4E0D\u4F20\u5219\u4ECE GET /query/account/me \u53D6 companyId").option("--rag-only", "\u4EC5\u663E\u793A\u5DF2\u5EFA RAG \u5411\u91CF\u7D22\u5F15\uFF08ragStatus=true\uFF09\u7684\u77E5\u8BC6\u5E93", false).option("-t, --token <token>", "Token\uFF08\u53EF\u9009\uFF1B\u4F18\u5148\u4E8E ~/.siluzan/config.json\uFF09").option("--json-out <path>", "\u843D\u76D8 JSON + outline + cli-manifest\uFF1Bstdout \u4E00\u884C\u6458\u8981", void 0).option("--verbose", "\u663E\u793A\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(
|
|
119361
120230
|
async (opts) => {
|
|
119362
120231
|
await runRagList({
|
|
119363
120232
|
belongToId: opts.belongToId,
|
|
@@ -119372,11 +120241,7 @@ function register22(program2) {
|
|
|
119372
120241
|
"--partition <wiki|default>",
|
|
119373
120242
|
"\u68C0\u7D22\u5206\u533A\uFF08queryknowledges \u7684 partition\uFF0C\u9ED8\u8BA4 default\uFF09",
|
|
119374
120243
|
"default"
|
|
119375
|
-
).option("-t, --token <token>", "Token\uFF08\u53EF\u9009\uFF1B\u4F18\u5148\u4E8E ~/.siluzan/config.json\uFF09").option(
|
|
119376
|
-
"--json-out <path>",
|
|
119377
|
-
"\u843D\u76D8 JSON + outline + cli-manifest\uFF1Bstdout \u4E00\u884C\u6458\u8981",
|
|
119378
|
-
void 0
|
|
119379
|
-
).option("--verbose", "\u663E\u793A\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(
|
|
120244
|
+
).option("-t, --token <token>", "Token\uFF08\u53EF\u9009\uFF1B\u4F18\u5148\u4E8E ~/.siluzan/config.json\uFF09").option("--json-out <path>", "\u843D\u76D8 JSON + outline + cli-manifest\uFF1Bstdout \u4E00\u884C\u6458\u8981", void 0).option("--verbose", "\u663E\u793A\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(
|
|
119380
120245
|
async (opts) => {
|
|
119381
120246
|
await runRagQuery({
|
|
119382
120247
|
query: opts.query,
|
|
@@ -120885,11 +121750,7 @@ function accountOpeningHistoryWebMediaType(mediaType) {
|
|
|
120885
121750
|
}
|
|
120886
121751
|
function logAccountOpeningHistoryWebUrl(apiBaseUrl, mediaType) {
|
|
120887
121752
|
const extraQuery = mediaType?.trim() ? { mediaType: accountOpeningHistoryWebMediaType(mediaType) } : void 0;
|
|
120888
|
-
const url = buildTsoPageWebUrl(
|
|
120889
|
-
deriveWebUrl(apiBaseUrl),
|
|
120890
|
-
"accountOpeningHistory",
|
|
120891
|
-
extraQuery
|
|
120892
|
-
);
|
|
121753
|
+
const url = buildTsoPageWebUrl(deriveWebUrl(apiBaseUrl), "accountOpeningHistory", extraQuery);
|
|
120893
121754
|
console.log(` \u5982\u9700\u5728\u7F51\u9875\u67E5\u770B\u6216\u786E\u8BA4\uFF0C\u8BF7\u6253\u5F00\u300C\u5F00\u6237\u8BB0\u5F55\u300D\uFF1A
|
|
120894
121755
|
${url}
|
|
120895
121756
|
`);
|
|
@@ -121212,9 +122073,7 @@ function extractTradeListRecords(raw) {
|
|
|
121212
122073
|
}
|
|
121213
122074
|
function buildBingIndustryOptions(records) {
|
|
121214
122075
|
const byId = new Map(records.map((r) => [String(r.id), r]));
|
|
121215
|
-
const level2 = records.filter(
|
|
121216
|
-
(r) => String(r.tradeLevel) === "2" || r.tradeLevel === 2
|
|
121217
|
-
);
|
|
122076
|
+
const level2 = records.filter((r) => String(r.tradeLevel) === "2" || r.tradeLevel === 2);
|
|
121218
122077
|
const byParent = /* @__PURE__ */ new Map();
|
|
121219
122078
|
for (const row of level2) {
|
|
121220
122079
|
const pid = row.parentId == null ? ORPHAN_PARENT_KEY : String(row.parentId);
|
|
@@ -121602,9 +122461,11 @@ async function runOpenAccountKwai(opts) {
|
|
|
121602
122461
|
try {
|
|
121603
122462
|
blobstoreKey = await uploadToKwai(opts.licenseFile, config.apiBaseUrl, config, opts.verbose);
|
|
121604
122463
|
} catch (err) {
|
|
121605
|
-
console.error(
|
|
122464
|
+
console.error(
|
|
122465
|
+
`
|
|
121606
122466
|
\u274C Kwai \u8425\u4E1A\u6267\u7167\u4E0A\u4F20\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
|
|
121607
|
-
`
|
|
122467
|
+
`
|
|
122468
|
+
);
|
|
121608
122469
|
process.exit(1);
|
|
121609
122470
|
}
|
|
121610
122471
|
console.log(` Kwai blobstoreKey: ${blobstoreKey}`);
|
|
@@ -122305,14 +123166,7 @@ function parseMetaOpenAccountLinkResponse(raw) {
|
|
|
122305
123166
|
}
|
|
122306
123167
|
if (raw && typeof raw === "object") {
|
|
122307
123168
|
const o = raw;
|
|
122308
|
-
for (const key of [
|
|
122309
|
-
"url",
|
|
122310
|
-
"link",
|
|
122311
|
-
"openAccountLink",
|
|
122312
|
-
"openAccountUrl",
|
|
122313
|
-
"redirectUrl",
|
|
122314
|
-
"href"
|
|
122315
|
-
]) {
|
|
123169
|
+
for (const key of ["url", "link", "openAccountLink", "openAccountUrl", "redirectUrl", "href"]) {
|
|
122316
123170
|
const v = o[key];
|
|
122317
123171
|
if (typeof v === "string") {
|
|
122318
123172
|
const t = v.trim();
|
|
@@ -122377,11 +123231,7 @@ function register24(program2) {
|
|
|
122377
123231
|
const openAccountCmd = program2.command("open-account").description("\u5F00\u6237\u7533\u8BF7\uFF08Google / TikTok / Yandex / Bing / Kwai / Meta \u5F00\u6237\u94FE\u63A5\uFF09");
|
|
122378
123232
|
openAccountCmd.command("meta").description(
|
|
122379
123233
|
"\u83B7\u53D6 Meta/Facebook \u5B98\u65B9\u5F00\u6237\u94FE\u63A5\uFF08GET MetaAccount/Management/GetOpenAccountLink\uFF0C\u4E0E\u7F51\u9875\u300C\u7533\u8BF7\u5F00\u6237\u300D\u4E00\u81F4\uFF09"
|
|
122380
|
-
).option("-t, --token <token>", "Auth Token").option("--open-browser", "\u5C1D\u8BD5\u7528\u7CFB\u7EDF\u9ED8\u8BA4\u6D4F\u89C8\u5668\u6253\u5F00\u94FE\u63A5").option(
|
|
122381
|
-
"--json-out <path>",
|
|
122382
|
-
"\u843D\u76D8 JSON\uFF08\u542B openAccountUrl\uFF09\uFF1Bstdout \u4E00\u884C\u6458\u8981",
|
|
122383
|
-
void 0
|
|
122384
|
-
).option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(
|
|
123234
|
+
).option("-t, --token <token>", "Auth Token").option("--open-browser", "\u5C1D\u8BD5\u7528\u7CFB\u7EDF\u9ED8\u8BA4\u6D4F\u89C8\u5668\u6253\u5F00\u94FE\u63A5").option("--json-out <path>", "\u843D\u76D8 JSON\uFF08\u542B openAccountUrl\uFF09\uFF1Bstdout \u4E00\u884C\u6458\u8981", void 0).option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(
|
|
122385
123235
|
async (opts) => {
|
|
122386
123236
|
await runOpenAccountMeta({
|
|
122387
123237
|
token: opts.token,
|
|
@@ -122416,7 +123266,10 @@ function register24(program2) {
|
|
|
122416
123266
|
});
|
|
122417
123267
|
}
|
|
122418
123268
|
);
|
|
122419
|
-
openAccountCmd.command("bing").description("\u63D0\u4EA4 Bing/BingV2 \u5F00\u6237\u7533\u8BF7\uFF08\u9700\u4E0A\u4F20\u8425\u4E1A\u6267\u7167\u56FE\u7247\uFF0C\u6309\u516C\u53F8\u540D\u81EA\u52A8\u521B\u5EFA/\u5173\u8054\u5E7F\u544A\u4E3B\u7EC4\uFF09").requiredOption("--pattern <Direct|Agency>", "\u76F4\u63A5/\u4EE3\u7406\uFF1ADirect\uFF08\u76F4\u63A5\uFF09\u6216 Agency\uFF08\u4EE3\u7406\uFF09").requiredOption("--advertiser-name <name>", "\u516C\u53F8\u540D\u79F0\uFF08\u5E7F\u544A\u4E3B\u5168\u79F0\uFF0C\u7528\u4E8E\u5339\u914D\u6216\u521B\u5EFA\u5E7F\u544A\u4E3B\u7EC4\uFF09").requiredOption("--name-short <short>", "\u516C\u53F8\u7B80\u79F0").requiredOption("--name-remark-list <name>", "\u5F00\u6237\u540D\u79F0\uFF08\u8D26\u6237\u663E\u793A\u540D\uFF09").requiredOption("--province <province>", "\u516C\u53F8\u6240\u5728\u7701\u4EFD").requiredOption("--city <city>", "\u516C\u53F8\u6240\u5728\u57CE\u5E02").requiredOption("--address <address>", "\u516C\u53F8\u8BE6\u7EC6\u5730\u5740").requiredOption("--postcode <code>", "\u516C\u53F8\u6240\u5728\u5730\u90AE\u7F16").requiredOption(
|
|
123269
|
+
openAccountCmd.command("bing").description("\u63D0\u4EA4 Bing/BingV2 \u5F00\u6237\u7533\u8BF7\uFF08\u9700\u4E0A\u4F20\u8425\u4E1A\u6267\u7167\u56FE\u7247\uFF0C\u6309\u516C\u53F8\u540D\u81EA\u52A8\u521B\u5EFA/\u5173\u8054\u5E7F\u544A\u4E3B\u7EC4\uFF09").requiredOption("--pattern <Direct|Agency>", "\u76F4\u63A5/\u4EE3\u7406\uFF1ADirect\uFF08\u76F4\u63A5\uFF09\u6216 Agency\uFF08\u4EE3\u7406\uFF09").requiredOption("--advertiser-name <name>", "\u516C\u53F8\u540D\u79F0\uFF08\u5E7F\u544A\u4E3B\u5168\u79F0\uFF0C\u7528\u4E8E\u5339\u914D\u6216\u521B\u5EFA\u5E7F\u544A\u4E3B\u7EC4\uFF09").requiredOption("--name-short <short>", "\u516C\u53F8\u7B80\u79F0").requiredOption("--name-remark-list <name>", "\u5F00\u6237\u540D\u79F0\uFF08\u8D26\u6237\u663E\u793A\u540D\uFF09").requiredOption("--province <province>", "\u516C\u53F8\u6240\u5728\u7701\u4EFD").requiredOption("--city <city>", "\u516C\u53F8\u6240\u5728\u57CE\u5E02").requiredOption("--address <address>", "\u516C\u53F8\u8BE6\u7EC6\u5730\u5740").requiredOption("--postcode <code>", "\u516C\u53F8\u6240\u5728\u5730\u90AE\u7F16").requiredOption(
|
|
123270
|
+
"--trade-id <id>",
|
|
123271
|
+
"\u884C\u4E1A tradeId\uFF08\u6765\u81EA open-account bing-industries \u7684 id\uFF0C\u4E0E\u7F51\u9875\u4E0B\u62C9 value \u4E00\u81F4\uFF09"
|
|
123272
|
+
).requiredOption("--promotion-link <url>", "\u5F00\u6237\u7F51\u5740\uFF08\u63A8\u5E7F\u94FE\u63A5\uFF09").requiredOption("--license-file <path>", "\u8425\u4E1A\u6267\u7167\u56FE\u7247\u672C\u5730\u8DEF\u5F84\uFF08JPG/PNG/PDF\uFF09").option("--account-count <n>", "\u5F00\u6237\u6570\u91CF\uFF081~6\uFF0C\u9ED8\u8BA4 1\uFF09", parseInt).option("--advertiser-cid <cid>", "\u7ECF\u7406\u8D26\u53F7 ID\uFF08\u53EF\u9009\uFF09").option("--advertiser-name2 <name>", "\u7ECF\u7406\u8D26\u53F7\u540D\u79F0\uFF08\u53EF\u9009\uFF0C\u4E0E --advertiser-cid \u914D\u5BF9\uFF09").option("--advertiser-id <magKey>", "\u53EF\u9009\uFF1A\u624B\u52A8\u6307\u5B9A\u5E7F\u544A\u4E3B\u7EC4 magKey\uFF08\u4E00\u822C\u65E0\u9700\u586B\u5199\uFF09").option("-t, --token <token>", "Auth Token").option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(
|
|
122420
123273
|
async (opts) => {
|
|
122421
123274
|
await runOpenAccountBing({
|
|
122422
123275
|
token: opts.token,
|
|
@@ -122607,9 +123460,13 @@ function register25(program2) {
|
|
|
122607
123460
|
console.log(" \u3010\u7F51\u7AD9\u8BCA\u65AD\u3011\uFF08\u5BF9\u9F50 Aiagent tso_agent getWebsiteDiagnosisData\uFF09");
|
|
122608
123461
|
console.log(" CLI \u62C9\u6570\uFF1A");
|
|
122609
123462
|
console.log(" siluzan-tso website-diagnosis collect --url <url> --json-out ./snap-web");
|
|
122610
|
-
console.log(
|
|
123463
|
+
console.log(
|
|
123464
|
+
" siluzan-tso website-diagnosis performance --url <url> --json-out ./snap-web"
|
|
123465
|
+
);
|
|
122611
123466
|
console.log(" \u9ED8\u8BA4\u4EA4\u4ED8 HTML\uFF1A");
|
|
122612
|
-
console.log(
|
|
123467
|
+
console.log(
|
|
123468
|
+
" siluzan-tso website-diagnosis render --data ./diagnosis.json --collect ./snap-web/<collect>.json"
|
|
123469
|
+
);
|
|
122613
123470
|
console.log(" Skill\uFF1Areferences/analytics/website-diagnosis-guide.md\n");
|
|
122614
123471
|
console.log(" \u3010\u8D26\u6237\u5217\u8868 ARIT \u5206\u3011");
|
|
122615
123472
|
console.log(" list-accounts \u4F1A\u8865\u5145 ma.diagnoseReports \u4E2D reportSource=ARIT \u7684\u5F97\u5206\uFF1B");
|
|
@@ -122911,20 +123768,19 @@ async function runWebsiteDiagnosisRender(opts) {
|
|
|
122911
123768
|
await fsPromises.writeFile(outPath, injectReportData(html, data), "utf8");
|
|
122912
123769
|
const runtimeSrc = websiteDiagnosisRuntimePath();
|
|
122913
123770
|
const runtimeOut = path17.join(outDir, TEMPLATE_BASENAMES.runtime);
|
|
123771
|
+
let runtimeCopied = false;
|
|
122914
123772
|
try {
|
|
122915
123773
|
await fsPromises.copyFile(runtimeSrc, runtimeOut);
|
|
123774
|
+
runtimeCopied = true;
|
|
122916
123775
|
} catch {
|
|
122917
|
-
console.error(`
|
|
122918
|
-
\u274C \u672A\u627E\u5230\u62A5\u544A\u8FD0\u884C\u65F6\uFF1A${runtimeSrc}
|
|
122919
|
-
\u8BF7\u5148\u6267\u884C npm run build
|
|
122920
|
-
`);
|
|
122921
|
-
process.exit(1);
|
|
122922
123776
|
}
|
|
122923
123777
|
console.log(`
|
|
122924
123778
|
\u2705 \u7F51\u7AD9\u8BCA\u65AD HTML \u62A5\u544A\u5DF2\u751F\u6210\uFF1A${outPath}
|
|
122925
123779
|
`);
|
|
122926
|
-
|
|
123780
|
+
if (runtimeCopied) {
|
|
123781
|
+
console.log(` \u8FD0\u884C\u65F6\u811A\u672C\uFF1A${runtimeOut}
|
|
122927
123782
|
`);
|
|
123783
|
+
}
|
|
122928
123784
|
console.log(
|
|
122929
123785
|
"\u5728\u6D4F\u89C8\u5668\u4E2D\u6253\u5F00 HTML \u6587\u4EF6\u5373\u53EF\u67E5\u770B\u56FE\u8868\u4E0E\u5B8C\u6574\u7AE0\u8282\uFF08ECharts\uFF1Astaticpn.siluzan.com/homeCDN/echarts.js\uFF09\u3002\n"
|
|
122930
123786
|
);
|
|
@@ -122965,9 +123821,7 @@ function registerWebsiteDiagnosisCommands(program2) {
|
|
|
122965
123821
|
});
|
|
122966
123822
|
}
|
|
122967
123823
|
);
|
|
122968
|
-
root.command("render").description(
|
|
122969
|
-
"\u6839\u636E\u8BCA\u65AD JSON \u751F\u6210 HTML \u7EC8\u7A3F\uFF08\u9ED8\u8BA4\u4EA4\u4ED8\u683C\u5F0F\uFF0Cwebsite-diagnosis-report.html\uFF09"
|
|
122970
|
-
).requiredOption("--data <file>", "\u8BCA\u65AD\u7ED3\u679C JSON\uFF08getWebsiteDiagnosisData \u540C\u7ED3\u6784\uFF09").option("--collect <file>", "\u53EF\u9009\uFF1Acollect \u843D\u76D8 JSON\uFF0C\u7528\u4E8E\u5408\u5E76 lighthouse").option("--out <file>", "\u8F93\u51FA HTML \u8DEF\u5F84\uFF08\u9ED8\u8BA4\u540C --data \u76EE\u5F55\uFF09").action(async (opts) => {
|
|
123824
|
+
root.command("render").description("\u6839\u636E\u8BCA\u65AD JSON \u751F\u6210 HTML \u7EC8\u7A3F\uFF08\u9ED8\u8BA4\u4EA4\u4ED8\u683C\u5F0F\uFF0Cwebsite-diagnosis-report.html\uFF09").requiredOption("--data <file>", "\u8BCA\u65AD\u7ED3\u679C JSON\uFF08getWebsiteDiagnosisData \u540C\u7ED3\u6784\uFF09").option("--collect <file>", "\u53EF\u9009\uFF1Acollect \u843D\u76D8 JSON\uFF0C\u7528\u4E8E\u5408\u5E76 lighthouse").option("--out <file>", "\u8F93\u51FA HTML \u8DEF\u5F84\uFF08\u9ED8\u8BA4\u540C --data \u76EE\u5F55\uFF09").action(async (opts) => {
|
|
122971
123825
|
await runWebsiteDiagnosisRender({
|
|
122972
123826
|
dataFile: opts.data,
|
|
122973
123827
|
collectFile: opts.collect,
|
|
@@ -123001,7 +123855,7 @@ function buildMarketAnalysisAgentHint() {
|
|
|
123001
123855
|
return [
|
|
123002
123856
|
"1) Read assets/market-analysis-rules.md\uFF08\u539F\u59CB\u4E1A\u52A1\u7EF4\u5EA6\u6E05\u5355\uFF09\u4E0E report-templates/market-analysis-report.md\uFF1B",
|
|
123003
123857
|
"2) \u7528\u5BBF\u4E3B WebSearch/WebFetch \u6309\u7F3A\u5931\u7EF4\u5EA6\u8865\u5168\u516C\u5F00\u6570\u636E\uFF1B",
|
|
123004
|
-
"3) \u5C06\u5B8C\u6574 HTML \u5199\u5165 market-report.json\uFF1Brender \u4F1A\u6821\u9A8C\u5FC5\u542B\u7EF4\u5EA6\uFF0C\u7F3A\u9879\u5C06\u5931\u8D25\uFF1B",
|
|
123858
|
+
"3) \u5C06\u5B8C\u6574 HTML \u5199\u5165 market-report.json\uFF08\u56FE\u8868\u7EDF\u4E00 ECharts\uFF1AhomeCDN/echarts.js + echarts.init\uFF09\uFF1Brender \u4F1A\u6821\u9A8C\u5FC5\u542B\u7EF4\u5EA6\uFF0C\u7F3A\u9879\u5C06\u5931\u8D25\uFF1B",
|
|
123005
123859
|
"4) siluzan-tso market-analysis render --data ./market-report.json --out ./market-analysis-report.html\u3002"
|
|
123006
123860
|
].join(" ");
|
|
123007
123861
|
}
|
|
@@ -123009,10 +123863,11 @@ function injectChartSizeLimitStyles(htmlContent) {
|
|
|
123009
123863
|
if (!htmlContent) return htmlContent;
|
|
123010
123864
|
const chartSizeLimitStyle = `
|
|
123011
123865
|
<style>
|
|
123866
|
+
.chart-box,
|
|
123867
|
+
div[id$="Chart"],
|
|
123012
123868
|
canvas {
|
|
123013
123869
|
max-height: 400px !important;
|
|
123014
123870
|
width: 100% !important;
|
|
123015
|
-
height: auto !important;
|
|
123016
123871
|
}
|
|
123017
123872
|
</style>
|
|
123018
123873
|
`;
|
|
@@ -123109,6 +123964,7 @@ import fs13 from "fs/promises";
|
|
|
123109
123964
|
import path18 from "path";
|
|
123110
123965
|
|
|
123111
123966
|
// src/commands/market-analysis/report-content.ts
|
|
123967
|
+
var SILUZAN_ECHARTS_CDN_URL = "https://staticpn.siluzan.com/assets/slz/homeCDN/echarts.js";
|
|
123112
123968
|
var MARKET_REPORT_DIMENSIONS = [
|
|
123113
123969
|
// ── 封面与元信息 ──
|
|
123114
123970
|
{
|
|
@@ -123312,10 +124168,10 @@ var MARKET_REPORT_DIMENSIONS = [
|
|
|
123312
124168
|
},
|
|
123313
124169
|
// ── 呈现规范(原始 prompt HTML 要求)──
|
|
123314
124170
|
{
|
|
123315
|
-
id: "viz-
|
|
124171
|
+
id: "viz-echarts",
|
|
123316
124172
|
chapter: "\u5448\u73B0",
|
|
123317
|
-
dimension: "
|
|
123318
|
-
hint:
|
|
124173
|
+
dimension: "ECharts \u6570\u636E\u53EF\u89C6\u5316",
|
|
124174
|
+
hint: `\u987B\u5F15\u7528 ${SILUZAN_ECHARTS_CDN_URL} \u4E14\u81F3\u5C11\u4E00\u5904 echarts.init \u56FE\u8868`
|
|
123319
124175
|
},
|
|
123320
124176
|
{
|
|
123321
124177
|
id: "viz-source",
|
|
@@ -123324,6 +124180,14 @@ var MARKET_REPORT_DIMENSIONS = [
|
|
|
123324
124180
|
hint: "\u987B\u542B Source: \u6216\u300C\u6765\u6E90\u300D\u811A\u6CE8"
|
|
123325
124181
|
}
|
|
123326
124182
|
];
|
|
124183
|
+
function countEchartsInstances(html) {
|
|
124184
|
+
const inits = (html.match(/echarts\.init\(/g) ?? []).length;
|
|
124185
|
+
if (inits > 0) return inits;
|
|
124186
|
+
return (html.match(/\.setOption\(/g) ?? []).length > 0 ? 1 : 0;
|
|
124187
|
+
}
|
|
124188
|
+
function hasEchartsCdn(html) {
|
|
124189
|
+
return /homeCDN\/echarts\.js|echarts\.min\.js|npm\/echarts@/i.test(html);
|
|
124190
|
+
}
|
|
123327
124191
|
function countPainPoints(html) {
|
|
123328
124192
|
const labeled = (html.match(/痛点\s*\d/g) ?? []).length;
|
|
123329
124193
|
if (labeled >= 4) return labeled;
|
|
@@ -123374,7 +124238,10 @@ var CONTENT_RULES = [
|
|
|
123374
124238
|
{ id: "ch2", test: (h) => /行业深度洞察|目标行业/.test(h) },
|
|
123375
124239
|
{ id: "ch2-pain", test: (h, ctx) => ctx.painPointCount >= 4 },
|
|
123376
124240
|
{ id: "ch2-compliance", test: (h) => /法规|合规|FCC|隐私/.test(h) && /<table/.test(h) },
|
|
123377
|
-
{
|
|
124241
|
+
{
|
|
124242
|
+
id: "ch2-bpmn",
|
|
124243
|
+
test: (h) => /原流程|BPMN|介入后|新流程/.test(h) && /效率|成本|提升|下降/.test(h)
|
|
124244
|
+
},
|
|
123378
124245
|
{ id: "ch3", test: (h) => /目标受众|受众与场景/.test(h) },
|
|
123379
124246
|
{ id: "ch3-audience", test: (h) => /用户|受众|画像|群体/.test(h) && /<table/.test(h) },
|
|
123380
124247
|
{ id: "ch3-scenario", test: (h) => /应用场景|场景|差异化|用户价值|企业价值/.test(h) },
|
|
@@ -123384,9 +124251,12 @@ var CONTENT_RULES = [
|
|
|
123384
124251
|
{ id: "ch4-compare-table", test: (h) => competitorCompareTableRows(h) >= 11 },
|
|
123385
124252
|
{
|
|
123386
124253
|
id: "ch4-gaps",
|
|
123387
|
-
test: (h) => [
|
|
123388
|
-
|
|
123389
|
-
|
|
124254
|
+
test: (h) => [
|
|
124255
|
+
/功能空白|功能.*空白/.test(h),
|
|
124256
|
+
/场景/.test(h),
|
|
124257
|
+
/用户群|用户/.test(h),
|
|
124258
|
+
/地域|地区|区域/.test(h)
|
|
124259
|
+
].filter(Boolean).length >= 3
|
|
123390
124260
|
},
|
|
123391
124261
|
{ id: "ch4-positioning", test: (h) => /定位陈述|定位:|核心定位|差异化/.test(h) },
|
|
123392
124262
|
{ id: "ch5", test: (h) => /增长与品牌|GTM/.test(h) },
|
|
@@ -123400,14 +124270,17 @@ var CONTENT_RULES = [
|
|
|
123400
124270
|
{ id: "appendix", test: (h) => /附录|数据来源/.test(h) },
|
|
123401
124271
|
{ id: "appendix-sources", test: (h) => /数据来源|Source:|报告名称|发布日期|http/.test(h) },
|
|
123402
124272
|
{ id: "appendix-methods", test: (h) => /调研方法|桌面研究|访谈|问卷/.test(h) },
|
|
123403
|
-
{
|
|
123404
|
-
|
|
124273
|
+
{
|
|
124274
|
+
id: "appendix-glossary",
|
|
124275
|
+
test: (h) => [/ROI/.test(h), /CAGR/.test(h), /GTM/.test(h), /BPMN/.test(h)].filter(Boolean).length >= 3
|
|
124276
|
+
},
|
|
124277
|
+
{ id: "viz-echarts", test: (h, ctx) => hasEchartsCdn(h) && ctx.chartCount >= 1 },
|
|
123405
124278
|
{ id: "viz-source", test: (h) => /Source:|来源:|来源:/.test(h) }
|
|
123406
124279
|
];
|
|
123407
124280
|
function validateMarketReportContent(html) {
|
|
123408
124281
|
const ctx = {
|
|
123409
124282
|
tableCount: (html.match(/<table/g) ?? []).length,
|
|
123410
|
-
chartCount: (html
|
|
124283
|
+
chartCount: countEchartsInstances(html),
|
|
123411
124284
|
painPointCount: countPainPoints(html)
|
|
123412
124285
|
};
|
|
123413
124286
|
const dimById = new Map(MARKET_REPORT_DIMENSIONS.map((d) => [d.id, d]));
|
|
@@ -123513,7 +124386,9 @@ async function runMarketAnalysisRender(opts) {
|
|
|
123513
124386
|
` \u5FC5\u542B\u7EF4\u5EA6\u5DF2\u5168\u90E8\u8986\u76D6\uFF5C${stats.tables} \u8868\uFF5C${stats.charts} \u56FE\uFF5C${stats.painPoints} \u75DB\u70B9\uFF5C\u7ADE\u54C1\u8868 ${stats.competitorTableRows} \u884C
|
|
123514
124387
|
`
|
|
123515
124388
|
);
|
|
123516
|
-
console.log(
|
|
124389
|
+
console.log(
|
|
124390
|
+
"\u5728\u6D4F\u89C8\u5668\u4E2D\u6253\u5F00 HTML \u6587\u4EF6\u5373\u53EF\u67E5\u770B\u5B8C\u6574\u62A5\u544A\uFF08Bootstrap + ECharts\uFF1Astaticpn.siluzan.com/homeCDN/echarts.js\uFF09\u3002\n"
|
|
124391
|
+
);
|
|
123517
124392
|
}
|
|
123518
124393
|
|
|
123519
124394
|
// src/commands/market-analysis/register.ts
|
|
@@ -123538,9 +124413,7 @@ function registerMarketAnalysisCommands(program2) {
|
|
|
123538
124413
|
});
|
|
123539
124414
|
}
|
|
123540
124415
|
);
|
|
123541
|
-
root.command("render").description(
|
|
123542
|
-
"\u6839\u636E Agent \u751F\u6210\u7684 market-report.json\uFF08\u542B htmlContent\uFF09\u5199\u51FA HTML \u7EC8\u7A3F"
|
|
123543
|
-
).requiredOption("--data <file>", "Agent \u4EA7\u51FA\u7684 market-report.json").option("--out <file>", "\u8F93\u51FA HTML \u8DEF\u5F84\uFF08\u9ED8\u8BA4\u540C --data \u76EE\u5F55\uFF09").action(async (opts) => {
|
|
124416
|
+
root.command("render").description("\u6839\u636E Agent \u751F\u6210\u7684 market-report.json\uFF08\u542B htmlContent\uFF09\u5199\u51FA HTML \u7EC8\u7A3F").requiredOption("--data <file>", "Agent \u4EA7\u51FA\u7684 market-report.json").option("--out <file>", "\u8F93\u51FA HTML \u8DEF\u5F84\uFF08\u9ED8\u8BA4\u540C --data \u76EE\u5F55\uFF09").action(async (opts) => {
|
|
123544
124417
|
await runMarketAnalysisRender({
|
|
123545
124418
|
dataFile: opts.data,
|
|
123546
124419
|
out: opts.out
|
|
@@ -123931,7 +124804,9 @@ function statusFromCpl(cpl, avg) {
|
|
|
123931
124804
|
}
|
|
123932
124805
|
async function readManifest(snapshotDir) {
|
|
123933
124806
|
const entries = await fs16.readdir(snapshotDir);
|
|
123934
|
-
const manifestNames = entries.filter(
|
|
124807
|
+
const manifestNames = entries.filter(
|
|
124808
|
+
(n) => n.startsWith("report-manifest") && n.endsWith(".json")
|
|
124809
|
+
);
|
|
123935
124810
|
const ordered = [
|
|
123936
124811
|
...manifestNames.filter((n) => n !== REPORT_MANIFEST_FILE),
|
|
123937
124812
|
...manifestNames.includes(REPORT_MANIFEST_FILE) ? [REPORT_MANIFEST_FILE] : []
|
|
@@ -123987,7 +124862,9 @@ async function mergeFacebookSnapshotIntoReport(payload, snapshotDir) {
|
|
|
123987
124862
|
const absDir = path23.resolve(snapshotDir);
|
|
123988
124863
|
const manifest = await readManifest(snapshotDir);
|
|
123989
124864
|
if (!manifest) {
|
|
123990
|
-
throw new Error(
|
|
124865
|
+
throw new Error(
|
|
124866
|
+
`\u672A\u5728 ${absDir} \u627E\u5230 report-manifest*.json\uFF0C\u8BF7\u5148\u6267\u884C facebook-analysis --json-out`
|
|
124867
|
+
);
|
|
123991
124868
|
}
|
|
123992
124869
|
const sectionMap = /* @__PURE__ */ new Map();
|
|
123993
124870
|
for (const art of manifest.artifacts) {
|
|
@@ -124175,7 +125052,14 @@ function validateMetaPeriodReportContent(data) {
|
|
|
124175
125052
|
"meta.periodLabel \u6216 meta.startDate + meta.endDate"
|
|
124176
125053
|
);
|
|
124177
125054
|
}
|
|
124178
|
-
for (const key of [
|
|
125055
|
+
for (const key of [
|
|
125056
|
+
"spend",
|
|
125057
|
+
"results",
|
|
125058
|
+
"costPerResult",
|
|
125059
|
+
"reach",
|
|
125060
|
+
"impressions",
|
|
125061
|
+
"frequency"
|
|
125062
|
+
]) {
|
|
124179
125063
|
if (!isFiniteNumber(kpis[key])) {
|
|
124180
125064
|
pushMissing(
|
|
124181
125065
|
missing,
|
|
@@ -124187,17 +125071,41 @@ function validateMetaPeriodReportContent(data) {
|
|
|
124187
125071
|
}
|
|
124188
125072
|
}
|
|
124189
125073
|
if (chartLabels(charts?.platform) < 1) {
|
|
124190
|
-
pushMissing(
|
|
125074
|
+
pushMissing(
|
|
125075
|
+
missing,
|
|
125076
|
+
"chart-platform",
|
|
125077
|
+
"\u6570\u636E\u56FE\u8868",
|
|
125078
|
+
"\u5E73\u53F0 CPL \u56FE",
|
|
125079
|
+
"charts.platform.labels \u81F3\u5C11 1 \u9879"
|
|
125080
|
+
);
|
|
124191
125081
|
}
|
|
124192
125082
|
if (chartLabels(charts?.country) < 1) {
|
|
124193
|
-
pushMissing(
|
|
125083
|
+
pushMissing(
|
|
125084
|
+
missing,
|
|
125085
|
+
"chart-country",
|
|
125086
|
+
"\u6570\u636E\u56FE\u8868",
|
|
125087
|
+
"\u56FD\u5BB6 CPL \u56FE",
|
|
125088
|
+
"charts.country.labels \u81F3\u5C11 1 \u9879"
|
|
125089
|
+
);
|
|
124194
125090
|
}
|
|
124195
125091
|
if (chartLabels(charts?.audience) < 1) {
|
|
124196
|
-
pushMissing(
|
|
125092
|
+
pushMissing(
|
|
125093
|
+
missing,
|
|
125094
|
+
"chart-audience",
|
|
125095
|
+
"\u6570\u636E\u56FE\u8868",
|
|
125096
|
+
"\u53D7\u4F17 CPL \u56FE",
|
|
125097
|
+
"charts.audience.labels \u81F3\u5C11 1 \u9879"
|
|
125098
|
+
);
|
|
124197
125099
|
}
|
|
124198
125100
|
const funnel = asRecord6(charts?.funnel);
|
|
124199
125101
|
if (!isFiniteNumber(funnel?.reach)) {
|
|
124200
|
-
pushMissing(
|
|
125102
|
+
pushMissing(
|
|
125103
|
+
missing,
|
|
125104
|
+
"chart-funnel",
|
|
125105
|
+
"\u6570\u636E\u56FE\u8868",
|
|
125106
|
+
"\u6F0F\u6597\u56FE reach",
|
|
125107
|
+
"charts.funnel.reach \u987B\u4E3A\u6709\u6548\u6570\u503C"
|
|
125108
|
+
);
|
|
124201
125109
|
}
|
|
124202
125110
|
if (adSetsWithSpend2 < 1) {
|
|
124203
125111
|
pushMissing(
|
|
@@ -124209,7 +125117,13 @@ function validateMetaPeriodReportContent(data) {
|
|
|
124209
125117
|
);
|
|
124210
125118
|
}
|
|
124211
125119
|
if (asArray2(tables?.audienceTop).length < 1) {
|
|
124212
|
-
pushMissing(
|
|
125120
|
+
pushMissing(
|
|
125121
|
+
missing,
|
|
125122
|
+
"table-audience-top",
|
|
125123
|
+
"\u6570\u636E\u8868\u683C",
|
|
125124
|
+
"\u53D7\u4F17 Top",
|
|
125125
|
+
"tables.audienceTop \u81F3\u5C11 1 \u884C"
|
|
125126
|
+
);
|
|
124213
125127
|
}
|
|
124214
125128
|
if (asArray2(tables?.audienceBottom).length < 1) {
|
|
124215
125129
|
pushMissing(
|
|
@@ -124510,13 +125424,7 @@ function validateMetaPeriodReportContent(data) {
|
|
|
124510
125424
|
);
|
|
124511
125425
|
}
|
|
124512
125426
|
if (abTests.length < 3) {
|
|
124513
|
-
pushMissing(
|
|
124514
|
-
missing,
|
|
124515
|
-
"ab-tests",
|
|
124516
|
-
"A/B \u5B9E\u9A8C",
|
|
124517
|
-
"abTests",
|
|
124518
|
-
"\u22653 \u4E2A\u5B9E\u9A8C\uFF08\u53D8\u91CF\u3001\u5047\u8BBE\u3001\u6210\u529F\u6807\u51C6\uFF09"
|
|
124519
|
-
);
|
|
125427
|
+
pushMissing(missing, "ab-tests", "A/B \u5B9E\u9A8C", "abTests", "\u22653 \u4E2A\u5B9E\u9A8C\uFF08\u53D8\u91CF\u3001\u5047\u8BBE\u3001\u6210\u529F\u6807\u51C6\uFF09");
|
|
124520
125428
|
}
|
|
124521
125429
|
const today = asArray2(actionChecklist?.today).filter((s) => textLen2(s) > 0);
|
|
124522
125430
|
const thisWeek = asArray2(actionChecklist?.thisWeek).filter((s) => textLen2(s) > 0);
|
|
@@ -124658,7 +125566,9 @@ async function runFacebookAnalysisRender(opts) {
|
|
|
124658
125566
|
console.log(` \u8FD0\u884C\u65F6\u811A\u672C\uFF1A${runtimeOut}
|
|
124659
125567
|
`);
|
|
124660
125568
|
}
|
|
124661
|
-
console.log(
|
|
125569
|
+
console.log(
|
|
125570
|
+
"\u5728\u6D4F\u89C8\u5668\u4E2D\u6253\u5F00 HTML \u6587\u4EF6\u5373\u53EF\u67E5\u770B\u56FE\u8868\u4E0E\u5B8C\u6574\u7AE0\u8282\uFF08ECharts\uFF1Astaticpn.siluzan.com/homeCDN/echarts.js\uFF09\u3002\n"
|
|
125571
|
+
);
|
|
124662
125572
|
}
|
|
124663
125573
|
|
|
124664
125574
|
// src/commands/facebook-analysis/run-batch.ts
|
|
@@ -124691,12 +125601,8 @@ function resolveSectionList2(sections, exclude) {
|
|
|
124691
125601
|
);
|
|
124692
125602
|
process.exit(1);
|
|
124693
125603
|
}
|
|
124694
|
-
const includeCanonical = new Set(
|
|
124695
|
-
|
|
124696
|
-
);
|
|
124697
|
-
const dropCanonical = new Set(
|
|
124698
|
-
dropRaw.map((n) => normalizeSectionToken(n)).filter(Boolean)
|
|
124699
|
-
);
|
|
125604
|
+
const includeCanonical = new Set(include.map((n) => normalizeSectionToken(n)).filter(Boolean));
|
|
125605
|
+
const dropCanonical = new Set(dropRaw.map((n) => normalizeSectionToken(n)).filter(Boolean));
|
|
124700
125606
|
const base = include.length > 0 ? FACEBOOK_SECTIONS.filter((s) => includeCanonical.has(s.name)) : [...FACEBOOK_SECTIONS];
|
|
124701
125607
|
return base.filter((s) => !dropCanonical.has(s.name));
|
|
124702
125608
|
}
|
|
@@ -124734,36 +125640,38 @@ async function runAllFacebookSections(opts) {
|
|
|
124734
125640
|
);
|
|
124735
125641
|
const cliVersion = getCurrentVersion2();
|
|
124736
125642
|
const overallT0 = performance5.now();
|
|
124737
|
-
const tasks = targets.map(
|
|
124738
|
-
|
|
124739
|
-
|
|
124740
|
-
|
|
124741
|
-
|
|
124742
|
-
|
|
124743
|
-
|
|
124744
|
-
|
|
124745
|
-
|
|
124746
|
-
|
|
124747
|
-
|
|
124748
|
-
|
|
124749
|
-
|
|
124750
|
-
|
|
124751
|
-
|
|
124752
|
-
|
|
124753
|
-
|
|
124754
|
-
|
|
124755
|
-
|
|
124756
|
-
|
|
124757
|
-
|
|
124758
|
-
|
|
124759
|
-
|
|
124760
|
-
|
|
124761
|
-
|
|
124762
|
-
|
|
124763
|
-
|
|
124764
|
-
|
|
125643
|
+
const tasks = targets.map(
|
|
125644
|
+
(def) => async () => {
|
|
125645
|
+
const t0 = performance5.now();
|
|
125646
|
+
const hint = endpointHintForFacebookSection(def, apiId);
|
|
125647
|
+
try {
|
|
125648
|
+
const payload = await fetchFacebookSectionPayload(def, opts, config, apiId);
|
|
125649
|
+
const summary2 = await writeReportAnalysisSnapshot({
|
|
125650
|
+
snapshotDir: opts.jsonOut,
|
|
125651
|
+
section: def.name,
|
|
125652
|
+
accountId: manifestId,
|
|
125653
|
+
dateRange: { start: startDate, end: endDate },
|
|
125654
|
+
payload,
|
|
125655
|
+
cliVersion
|
|
125656
|
+
});
|
|
125657
|
+
return {
|
|
125658
|
+
section: def.name,
|
|
125659
|
+
ok: true,
|
|
125660
|
+
elapsedMs: performance5.now() - t0,
|
|
125661
|
+
file: summary2.writtenFiles[0],
|
|
125662
|
+
endpointHint: hint
|
|
125663
|
+
};
|
|
125664
|
+
} catch (err) {
|
|
125665
|
+
return {
|
|
125666
|
+
section: def.name,
|
|
125667
|
+
ok: false,
|
|
125668
|
+
elapsedMs: performance5.now() - t0,
|
|
125669
|
+
error: err instanceof Error ? err.message : String(err),
|
|
125670
|
+
endpointHint: hint
|
|
125671
|
+
};
|
|
125672
|
+
}
|
|
124765
125673
|
}
|
|
124766
|
-
|
|
125674
|
+
);
|
|
124767
125675
|
const results = await runWithConcurrency2(tasks, concurrency);
|
|
124768
125676
|
const succeeded = results.filter((r) => r.ok).length;
|
|
124769
125677
|
const failed = results.length - succeeded;
|
|
@@ -124788,16 +125696,10 @@ async function runAllFacebookSections(opts) {
|
|
|
124788
125696
|
function registerBatchCommand(sectionHelp) {
|
|
124789
125697
|
return new Command("run").description(
|
|
124790
125698
|
"\u6279\u91CF\u62C9\u53D6 TSO reporting/media-account/FacebookAds Section \u6570\u636E\uFF08\u7701\u7565\u5B50\u547D\u4EE4\u540D\u65F6\u9ED8\u8BA4\u6267\u884C\u672C\u547D\u4EE4\uFF09"
|
|
124791
|
-
).requiredOption(
|
|
124792
|
-
"-a, --account <id>",
|
|
124793
|
-
"Facebook \u5E7F\u544A\u8D26\u6237 mediaCustomerId\uFF08\u6570\u5B57\u6216 act_<\u6570\u5B57>\uFF09"
|
|
124794
|
-
).requiredOption(
|
|
125699
|
+
).requiredOption("-a, --account <id>", "Facebook \u5E7F\u544A\u8D26\u6237 mediaCustomerId\uFF08\u6570\u5B57\u6216 act_<\u6570\u5B57>\uFF09").requiredOption(
|
|
124795
125700
|
"--json-out <dir>",
|
|
124796
125701
|
"\u7ED3\u679C\u843D\u76D8\u76EE\u5F55\uFF1B\u6BCF\u7EF4\u5EA6 `<section>-<accountId>.json` + `report-manifest-<accountId>.json`"
|
|
124797
|
-
).option("-t, --token <token>", "Auth Token").option(
|
|
124798
|
-
"--start <date>",
|
|
124799
|
-
"\u5F00\u59CB\u65E5\u671F YYYY-MM-DD\uFF08\u4E0E --end \u540C\u4F20\u6216\u540C\u7701\u7565\uFF1B\u7701\u7565=\u8FD1 7 \u5929\u622A\u81F3\u6628\u5929\uFF09"
|
|
124800
|
-
).option("--end <date>", "\u7ED3\u675F\u65E5\u671F YYYY-MM-DD").option(
|
|
125702
|
+
).option("-t, --token <token>", "Auth Token").option("--start <date>", "\u5F00\u59CB\u65E5\u671F YYYY-MM-DD\uFF08\u4E0E --end \u540C\u4F20\u6216\u540C\u7701\u7565\uFF1B\u7701\u7565=\u8FD1 7 \u5929\u622A\u81F3\u6628\u5929\uFF09").option("--end <date>", "\u7ED3\u675F\u65E5\u671F YYYY-MM-DD").option(
|
|
124801
125703
|
"--sections <list>",
|
|
124802
125704
|
`\u4EC5\u6267\u884C\u6307\u5B9A\u7EF4\u5EA6\uFF08\u9017\u53F7\u5206\u9694\uFF09\uFF0C\u5982 overview,ad-sets,platform\uFF1B\u7701\u7565=\u5168\u90E8 7 \u4E2A\u3002\u53EF\u9009\uFF1A${sectionHelp}\uFF1BGoogle \u522B\u540D\uFF1Acampaigns,geographic,devices,ads,materials`
|
|
124803
125705
|
).option("--exclude <list>", "\u6392\u9664\u6307\u5B9A\u7EF4\u5EA6\uFF08\u9017\u53F7\u5206\u9694\uFF09\uFF0C\u4E0E --sections \u53EF\u53E0\u52A0").option(
|
|
@@ -124842,6 +125744,15 @@ function getVersion() {
|
|
|
124842
125744
|
return "0.0.0";
|
|
124843
125745
|
}
|
|
124844
125746
|
}
|
|
125747
|
+
configureCliJsonSnapshot({
|
|
125748
|
+
cliPackage: "siluzan-tso-cli",
|
|
125749
|
+
resolveVersion: getVersion,
|
|
125750
|
+
fieldGuideRefs: [
|
|
125751
|
+
"references/accounts/currency.md",
|
|
125752
|
+
"references/core/tips.md",
|
|
125753
|
+
"references/accounts/accounts.md"
|
|
125754
|
+
]
|
|
125755
|
+
});
|
|
124845
125756
|
var program = new Command2();
|
|
124846
125757
|
program.name("siluzan-tso").description(
|
|
124847
125758
|
"Siluzan \u5E7F\u544A\u8D26\u6237\u7BA1\u7406\uFF1A\u8D26\u6237\u67E5\u8BE2\u3001\u4F59\u989D\u3001\u6295\u653E\u6570\u636E\u3001\u5F00\u6237\u7533\u8BF7\uFF08Google/TikTok/Yandex/Bing/Kwai\uFF09\u3001\n\u8D26\u53F7\u5206\u4EAB/\u89E3\u7ED1\u3001\u4F18\u5316\u62A5\u544A\u3001Google \u8D26\u6237\u5206\u6790\u7F51\u5173\uFF08google-analysis\uFF09\u3001\u7F51\u7AD9\u8BCA\u65AD\uFF08website-diagnosis\uFF09\u3001\u6218\u7565\u5E02\u573A\u5206\u6790\uFF08market-analysis\uFF0CAgent \u751F\u6210\u62A5\u544A\uFF09\u3001\u5145\u503C\u8F6C\u8D26\u3001\u5F00\u7968\u3001\u667A\u80FD\u9884\u8B66\u3001Google \u5E7F\u544A\u7BA1\u7406\uFF08\u542B\u5F02\u6B65\u6279\u91CF\uFF09\u3002"
|