siluzan-tso-cli 1.1.47 → 1.1.48-beta.2
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 +2 -1
- package/dist/index.js +1999 -42
- package/dist/skill/AGENTS.md +1 -0
- package/dist/skill/SKILL.md +12 -2
- package/dist/skill/_meta.json +2 -2
- package/dist/skill/assets/meta-lead-create-template.json +105 -0
- package/dist/skill/assets/meta-lead-create-template.md +46 -0
- package/dist/skill/references/README.md +12 -1
- package/dist/skill/references/accounts/accounts-list.md +1 -1
- package/dist/skill/references/accounts/accounts-permissions.md +2 -2
- package/dist/skill/references/accounts/finance.md +1 -1
- package/dist/skill/references/accounts/open-account-by-media.md +1 -1
- package/dist/skill/references/accounts/open-account-google-ui.md +1 -1
- package/dist/skill/references/analytics/rag.md +1 -1
- package/dist/skill/references/analytics/reporting.md +5 -5
- package/dist/skill/references/core/intent-routing.md +4 -0
- package/dist/skill/references/core/setup.md +5 -5
- package/dist/skill/references/core/workflows.md +22 -2
- package/dist/skill/references/meta-ads/meta-ads-read.md +33 -0
- package/dist/skill/references/meta-ads/meta-ads-write.md +70 -0
- package/dist/skill/references/meta-ads/meta-ads.md +37 -0
- package/dist/skill/references/meta-ads/meta-lead-launch-plan-template.md +168 -0
- package/dist/skill/references/misc/tso-home.md +2 -2
- package/dist/skill/references/operations/clue.md +2 -1
- package/dist/skill/scripts/install.ps1 +3 -3
- package/dist/skill/scripts/install.sh +3 -3
- package/eval/cases/accounts-entityid-vs-mediaccustomerid.scenario.json +14 -2
- package/eval/cases/accounts-mcc-bind-inquiry.scenario.json +3 -1
- package/eval/cases/accounts-single-balance-not-bulk.scenario.json +14 -3
- package/eval/cases/budget-display-not-raw-micros.scenario.json +8 -1
- package/eval/cases/clue-meta-leads-json.scenario.json +14 -2
- package/eval/cases/clue-tiktok-leads-json.scenario.json +11 -2
- package/eval/cases/destructive-account-delink-needs-confirm.scenario.json +9 -3
- package/eval/cases/destructive-forewarning-delete-needs-confirm.scenario.json +9 -3
- package/eval/cases/destructive-invoice-apply-needs-confirm.scenario.json +9 -3
- package/eval/cases/facebook-analysis-google-section-aliases.scenario.json +11 -2
- package/eval/cases/facebook-analysis-not-google-keywords.scenario.json +10 -3
- package/eval/cases/facebook-analysis-period-default.scenario.json +14 -2
- package/eval/cases/finance-invoice-info-list.scenario.json +11 -3
- package/eval/cases/forewarning-list-google.scenario.json +14 -3
- package/eval/cases/google-ads-no-structural-without-confirm.scenario.json +6 -2
- package/eval/cases/google-analysis-keywords-route.scenario.json +14 -2
- package/eval/cases/guard-budget-circuit-all-google.scenario.json +12 -2
- package/eval/cases/guard-zero-conv-empty-spend.scenario.json +12 -2
- package/eval/cases/human-p1-multiturn.scenario.json +5 -1
- package/eval/cases/ir-p1-vs-p8-account-vs-url.scenario.json +17 -5
- package/eval/cases/ir-p3-cpa-monitor-not-p7.scenario.json +19 -2
- package/eval/cases/ir-p3-zero-inquiry-not-p7.scenario.json +16 -3
- package/eval/cases/ir-p4-period-not-diagnosis.scenario.json +16 -3
- package/eval/cases/ir-p8-meets-google-ads-requirements-with-url.scenario.json +28 -3
- package/eval/cases/ir-p8-network-diagnosis-with-url.scenario.json +25 -3
- package/eval/cases/ir-p8-website-monitor-report-with-url.scenario.json +28 -3
- package/eval/cases/ir-p8-website-test-report-no-url.scenario.json +14 -3
- package/eval/cases/ir-p9-industry-not-p8.scenario.json +19 -3
- package/eval/cases/ir-vague-report-clarify.scenario.json +14 -3
- package/eval/cases/ir-w3-vs-p8-from-website.scenario.json +8 -2
- package/eval/cases/ir-w5-keyword-planner-url-core-terms.scenario.json +22 -2
- package/eval/cases/meta-single-balance-not-bulk.scenario.json +17 -3
- package/eval/cases/no-legacy-json-flag.scenario.json +6 -2
- package/eval/cases/open-account-google-noninteractive.scenario.json +4 -2
- package/eval/cases/open-account-tiktok-license-file.scenario.json +3 -1
- package/eval/cases/optimize-list-by-account.scenario.json +11 -3
- package/eval/cases/p1-single-account-profile.scenario.json +11 -1
- package/eval/cases/p2-balance-scan-bulk.scenario.json +9 -2
- package/eval/cases/p3-accounts-digest.scenario.json +5 -1
- package/eval/cases/p3-cpa-scan-max-cpa.scenario.json +14 -2
- package/eval/cases/p4-fb-meta-period-report.scenario.json +12 -2
- package/eval/cases/p4-period-report-window.scenario.json +8 -1
- package/eval/cases/pmax-asset-group-create-with-bg.scenario.json +13 -2
- package/eval/cases/pmax-brand-edit-routing.scenario.json +16 -3
- package/eval/cases/pmax-edit-not-campaign-edit.scenario.json +12 -2
- package/eval/cases/pmax-enable-brand-guidelines.scenario.json +12 -2
- package/eval/cases/pmax-no-assets-update-brand-fields.scenario.json +14 -3
- package/eval/cases/rag-before-keyword-expand.scenario.json +11 -1
- package/eval/cases/rag-list-then-query.scenario.json +14 -2
- package/eval/cases/report-list-google.scenario.json +11 -2
- package/eval/cases/report-push-list-google.scenario.json +11 -2
- package/eval/cases/setup-login-or-env.scenario.json +3 -1
- package/eval/cases/setup-siluzan-data-permission-env.scenario.json +3 -1
- package/eval/cases/tiktok-bc-bind-inquiry.scenario.json +6 -2
- package/eval/cases/time-range-user-delegates-default.scenario.json +8 -1
- package/eval/cases/tips-json-out-filtering.scenario.json +3 -1
- package/eval/cases/tips-large-json-pagination.scenario.json +3 -1
- package/eval/cases/uj-ad-campaign-validate-before-create-stub.scenario.json +11 -2
- package/eval/cases/uj-ad-outdoor-campgear-search-plan.scenario.json +3 -1
- package/eval/cases/uj-ad-website-search-plan-table-format.scenario.json +3 -1
- package/eval/cases/uj-analytics-30d-pdf-campaign-device-geo.scenario.json +18 -6
- package/eval/cases/uj-analytics-compare-google-tiktok-last-month-roi.scenario.json +8 -1
- package/eval/cases/uj-analytics-ecommerce-industry-report.scenario.json +14 -2
- package/eval/cases/uj-analytics-google-weekly-trends-campaigns-keywords.scenario.json +11 -2
- package/eval/cases/uj-analytics-report-push-weekly-email.scenario.json +3 -1
- package/eval/cases/uj-finance-invoice-records-this-month.scenario.json +11 -2
- package/eval/cases/uj-life-newbie-siluzan-google-end-to-end.scenario.json +4 -1
- package/eval/cases/uj-meta-ad-validate-before-create-stub.scenario.json +30 -0
- package/eval/cases/uj-ops-google-accounts-list-normal.scenario.json +17 -2
- package/eval/cases/uj-ops-google-yesterday-spend-conversions.scenario.json +14 -2
- package/eval/cases/uj-ops-open-google-b2c-usd-shenzhen.scenario.json +1 -1
- package/eval/cases/uj-ops-pause-worst-adgroup-confirm.scenario.json +6 -2
- package/eval/cases/uj-ops-tiktok-leads-last-week.scenario.json +17 -3
- package/eval/cases/uj-patrol-cpc-spike-adgroups-over-15.scenario.json +5 -1
- package/eval/cases/uj-patrol-forewarning-create-daily-cap-3000.scenario.json +3 -1
- package/eval/cases/uj-patrol-forewarning-trigger-records.scenario.json +17 -3
- package/eval/cases/uj-patrol-google-balances-low.scenario.json +11 -2
- package/eval/cases/uj-roi-optimize-records-then-execute-cautiously.scenario.json +14 -3
- package/eval/cases/uj-roi-search-terms-add-negative-keywords.scenario.json +14 -2
- package/eval/cases/yandex-stats-use-porg-mediaccustomerid.scenario.json +21 -3
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -4026,7 +4026,7 @@ var DEFAULT_API_BASE;
|
|
|
4026
4026
|
var init_defaults = __esm({
|
|
4027
4027
|
"src/config/defaults.ts"() {
|
|
4028
4028
|
"use strict";
|
|
4029
|
-
DEFAULT_API_BASE = "https://tso-api.siluzan.com";
|
|
4029
|
+
DEFAULT_API_BASE = "https://tso-api-ci.siluzan.com";
|
|
4030
4030
|
}
|
|
4031
4031
|
});
|
|
4032
4032
|
|
|
@@ -5572,8 +5572,8 @@ var init_accounts_digest2 = __esm({
|
|
|
5572
5572
|
async function withAsyncLock(key, fn) {
|
|
5573
5573
|
const prev = chains.get(key) ?? Promise.resolve();
|
|
5574
5574
|
let release;
|
|
5575
|
-
const gate = new Promise((
|
|
5576
|
-
release =
|
|
5575
|
+
const gate = new Promise((resolve24) => {
|
|
5576
|
+
release = resolve24;
|
|
5577
5577
|
});
|
|
5578
5578
|
chains.set(
|
|
5579
5579
|
key,
|
|
@@ -6179,14 +6179,14 @@ function computeBackoffMs(attempt, policy) {
|
|
|
6179
6179
|
return capped + jitter;
|
|
6180
6180
|
}
|
|
6181
6181
|
function sleep3(ms, signal) {
|
|
6182
|
-
return new Promise((
|
|
6182
|
+
return new Promise((resolve24, reject) => {
|
|
6183
6183
|
if (signal?.aborted) {
|
|
6184
6184
|
reject(new Error("retry sleep aborted"));
|
|
6185
6185
|
return;
|
|
6186
6186
|
}
|
|
6187
6187
|
const timer = setTimeout(() => {
|
|
6188
6188
|
signal?.removeEventListener("abort", onAbort);
|
|
6189
|
-
|
|
6189
|
+
resolve24();
|
|
6190
6190
|
}, ms);
|
|
6191
6191
|
const onAbort = () => {
|
|
6192
6192
|
clearTimeout(timer);
|
|
@@ -102747,12 +102747,12 @@ import fs22 from "fs";
|
|
|
102747
102747
|
import fsPromises2 from "fs/promises";
|
|
102748
102748
|
import path27 from "path";
|
|
102749
102749
|
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
102750
|
-
function resolveSkillTemplatePath3(
|
|
102750
|
+
function resolveSkillTemplatePath3(basename13) {
|
|
102751
102751
|
const dir = path27.dirname(fileURLToPath6(import.meta.url));
|
|
102752
102752
|
const candidates = [
|
|
102753
|
-
path27.join(dir, "skill", "report-templates",
|
|
102754
|
-
path27.join(dir, "skill", "siluzan-ads", "report-templates",
|
|
102755
|
-
path27.join(dir, "..", "..", "assets", "siluzan-ads", "report-templates",
|
|
102753
|
+
path27.join(dir, "skill", "report-templates", basename13),
|
|
102754
|
+
path27.join(dir, "skill", "siluzan-ads", "report-templates", basename13),
|
|
102755
|
+
path27.join(dir, "..", "..", "assets", "siluzan-ads", "report-templates", basename13)
|
|
102756
102756
|
];
|
|
102757
102757
|
for (const p of candidates) {
|
|
102758
102758
|
if (fs22.existsSync(p)) return p;
|
|
@@ -122320,6 +122320,1962 @@ function register21(program2) {
|
|
|
122320
122320
|
);
|
|
122321
122321
|
}
|
|
122322
122322
|
|
|
122323
|
+
// src/commands/meta-ad/account.ts
|
|
122324
|
+
init_cli_table();
|
|
122325
|
+
|
|
122326
|
+
// src/commands/meta-ad/client.ts
|
|
122327
|
+
init_auth();
|
|
122328
|
+
|
|
122329
|
+
// src/commands/meta-ad/_shared.ts
|
|
122330
|
+
init_cli_json_snapshot();
|
|
122331
|
+
var META_PREDEFINED_QUESTION_TYPES = [
|
|
122332
|
+
"EMAIL",
|
|
122333
|
+
"FULL_NAME",
|
|
122334
|
+
"FIRST_NAME",
|
|
122335
|
+
"LAST_NAME",
|
|
122336
|
+
"PHONE",
|
|
122337
|
+
"CITY",
|
|
122338
|
+
"STATE",
|
|
122339
|
+
"COUNTRY",
|
|
122340
|
+
"POST_CODE",
|
|
122341
|
+
"ZIP",
|
|
122342
|
+
"COMPANY_NAME",
|
|
122343
|
+
"JOB_TITLE",
|
|
122344
|
+
"WORK_EMAIL",
|
|
122345
|
+
"WORK_PHONE_NUMBER",
|
|
122346
|
+
"DATE_OF_BIRTH",
|
|
122347
|
+
"GENDER",
|
|
122348
|
+
"MARITAL_STATUS"
|
|
122349
|
+
];
|
|
122350
|
+
var META_OPTIMIZATION_GOALS = ["LEAD_GENERATION", "QUALITY_LEAD"];
|
|
122351
|
+
var ZERO_DECIMAL_CURRENCIES = /* @__PURE__ */ new Set([
|
|
122352
|
+
"BIF",
|
|
122353
|
+
"CLP",
|
|
122354
|
+
"DJF",
|
|
122355
|
+
"GNF",
|
|
122356
|
+
"ISK",
|
|
122357
|
+
"JPY",
|
|
122358
|
+
"KMF",
|
|
122359
|
+
"KRW",
|
|
122360
|
+
"PYG",
|
|
122361
|
+
"RWF",
|
|
122362
|
+
"UGX",
|
|
122363
|
+
"VND",
|
|
122364
|
+
"VUV",
|
|
122365
|
+
"XAF",
|
|
122366
|
+
"XOF",
|
|
122367
|
+
"XPF"
|
|
122368
|
+
]);
|
|
122369
|
+
var THREE_DECIMAL_CURRENCIES = /* @__PURE__ */ new Set(["BHD", "IQD", "JOD", "KWD", "LYD", "OMR", "TND"]);
|
|
122370
|
+
function currencyMinorExponent(currency) {
|
|
122371
|
+
const c = (currency ?? "").trim().toUpperCase();
|
|
122372
|
+
if (ZERO_DECIMAL_CURRENCIES.has(c)) return 0;
|
|
122373
|
+
if (THREE_DECIMAL_CURRENCIES.has(c)) return 3;
|
|
122374
|
+
return 2;
|
|
122375
|
+
}
|
|
122376
|
+
function toMinorUnitString(major, currency) {
|
|
122377
|
+
if (!Number.isFinite(major) || major < 0) {
|
|
122378
|
+
throw new Error(`\u91D1\u989D\u5FC5\u987B\u662F\u975E\u8D1F\u6709\u9650\u6570\u5B57\uFF0C\u6536\u5230 ${major}`);
|
|
122379
|
+
}
|
|
122380
|
+
const exp = currencyMinorExponent(currency);
|
|
122381
|
+
const n = Math.round(major * 10 ** exp);
|
|
122382
|
+
if (major > 0 && n < 1) {
|
|
122383
|
+
throw new Error(`\u91D1\u989D\u8FC7\u5C0F\uFF0C\u6309 ${currency || "USD"} \u65E0\u6CD5\u8868\u793A\u6700\u5C0F\u8D27\u5E01\u5355\u4F4D`);
|
|
122384
|
+
}
|
|
122385
|
+
return String(n);
|
|
122386
|
+
}
|
|
122387
|
+
function fromMinorUnitDisplay(raw, currency) {
|
|
122388
|
+
if (raw == null || raw === "") return null;
|
|
122389
|
+
const n = Number(raw);
|
|
122390
|
+
if (!Number.isFinite(n)) return null;
|
|
122391
|
+
return n / 10 ** currencyMinorExponent(currency);
|
|
122392
|
+
}
|
|
122393
|
+
var META_MONEY_KEYS = [
|
|
122394
|
+
"daily_budget",
|
|
122395
|
+
"lifetime_budget",
|
|
122396
|
+
"bid_amount",
|
|
122397
|
+
"spend_cap",
|
|
122398
|
+
"amount_spent",
|
|
122399
|
+
"balance"
|
|
122400
|
+
];
|
|
122401
|
+
function withMoneyDisplay(obj, currency) {
|
|
122402
|
+
const out = { ...obj };
|
|
122403
|
+
for (const key of META_MONEY_KEYS) {
|
|
122404
|
+
if (key in obj) {
|
|
122405
|
+
out[`${key}Display`] = fromMinorUnitDisplay(obj[key], currency);
|
|
122406
|
+
}
|
|
122407
|
+
}
|
|
122408
|
+
return out;
|
|
122409
|
+
}
|
|
122410
|
+
function formatYuanDisplay(value) {
|
|
122411
|
+
return value == null ? "\u2014" : String(value);
|
|
122412
|
+
}
|
|
122413
|
+
var META_PENDING_ACCOUNT = "[PENDING_ACCOUNT]";
|
|
122414
|
+
var META_PENDING_PAGE = "[PENDING_PAGE]";
|
|
122415
|
+
function isPendingMetaAccount(raw) {
|
|
122416
|
+
const t = (raw ?? "").trim();
|
|
122417
|
+
return t === "" || t === META_PENDING_ACCOUNT || t === "PENDING_ACCOUNT";
|
|
122418
|
+
}
|
|
122419
|
+
function isPendingMetaPage(raw) {
|
|
122420
|
+
const t = (raw ?? "").trim();
|
|
122421
|
+
return t === "" || t === META_PENDING_PAGE || t === "PENDING_PAGE";
|
|
122422
|
+
}
|
|
122423
|
+
function parseMetaAccountId(raw) {
|
|
122424
|
+
const t = (raw ?? "").trim();
|
|
122425
|
+
if (isPendingMetaAccount(t)) {
|
|
122426
|
+
return { ok: false, error: "account \u4ECD\u662F\u5360\u4F4D\uFF0Ccreate \u524D\u987B\u6362\u6210\u771F\u5B9E mediaCustomerId" };
|
|
122427
|
+
}
|
|
122428
|
+
if (!t) return { ok: false, error: "account \u4E0D\u80FD\u4E3A\u7A7A" };
|
|
122429
|
+
const digits = t.startsWith("act_") ? t.slice(4) : t;
|
|
122430
|
+
if (!/^\d+$/.test(digits)) {
|
|
122431
|
+
return { ok: false, error: "account \u987B\u4E3A\u6570\u5B57 mediaCustomerId \u6216 act_<\u6570\u5B57>" };
|
|
122432
|
+
}
|
|
122433
|
+
return { ok: true, digits, apiId: `act_${digits}` };
|
|
122434
|
+
}
|
|
122435
|
+
function resolveMetaAccountId(raw) {
|
|
122436
|
+
const parsed = parseMetaAccountId(raw);
|
|
122437
|
+
if (!parsed.ok) {
|
|
122438
|
+
console.error(`
|
|
122439
|
+
\u274C ${parsed.error}
|
|
122440
|
+
`);
|
|
122441
|
+
process.exit(1);
|
|
122442
|
+
}
|
|
122443
|
+
return { digits: parsed.digits, apiId: parsed.apiId };
|
|
122444
|
+
}
|
|
122445
|
+
function requireFacebookApi(config) {
|
|
122446
|
+
const raw = (config.facebookApiUrl ?? "").replace(/\/$/, "");
|
|
122447
|
+
if (!raw) {
|
|
122448
|
+
console.error(
|
|
122449
|
+
"\n\u274C \u672A\u80FD\u63A8\u5BFC Facebook \u7F51\u5173\u5730\u5740\uFF08facebookApiUrl\uFF09\u3002\n \u4ECE TSO API \u5730\u5740\u81EA\u52A8\u63A8\u5BFC\uFF08tso-api[-ci].siluzan.com \u2192 facebookapi[-ci].mysiluzan.com\uFF09\u3002\n"
|
|
122450
|
+
);
|
|
122451
|
+
process.exit(1);
|
|
122452
|
+
}
|
|
122453
|
+
return raw;
|
|
122454
|
+
}
|
|
122455
|
+
function metaAdsBaseUrl(facebookApiUrl, account) {
|
|
122456
|
+
const { digits } = resolveMetaAccountId(account);
|
|
122457
|
+
return `${facebookApiUrl.replace(/\/$/, "")}/api/fb/ads/${digits}`;
|
|
122458
|
+
}
|
|
122459
|
+
function formatMetaAdError(err) {
|
|
122460
|
+
const raw = err instanceof Error ? err.message : String(err);
|
|
122461
|
+
const start = raw.indexOf("{");
|
|
122462
|
+
const end = raw.lastIndexOf("}");
|
|
122463
|
+
if (start >= 0 && end > start) {
|
|
122464
|
+
try {
|
|
122465
|
+
const parsed = JSON.parse(raw.slice(start, end + 1));
|
|
122466
|
+
const code = parsed.code != null ? String(parsed.code) : "";
|
|
122467
|
+
const message = typeof parsed.message === "string" && parsed.message || typeof parsed.Message === "string" && parsed.Message || "";
|
|
122468
|
+
if (code || message) {
|
|
122469
|
+
const prefix = code ? `[${code}] ` : "";
|
|
122470
|
+
return `${prefix}${message || raw}`;
|
|
122471
|
+
}
|
|
122472
|
+
} catch {
|
|
122473
|
+
}
|
|
122474
|
+
}
|
|
122475
|
+
return raw;
|
|
122476
|
+
}
|
|
122477
|
+
function isBidAmountRequired(err) {
|
|
122478
|
+
const text = formatMetaAdError(err);
|
|
122479
|
+
return /BID_AMOUNT_REQUIRED/i.test(text) || /缺少竞价金额/.test(text) || /必须提供 bid_amount/.test(text) || /必须在 bid_amount/.test(text);
|
|
122480
|
+
}
|
|
122481
|
+
function failMetaAd(action, err) {
|
|
122482
|
+
console.error(`
|
|
122483
|
+
\u274C ${action}\uFF1A${formatMetaAdError(err)}
|
|
122484
|
+
`);
|
|
122485
|
+
process.exit(1);
|
|
122486
|
+
}
|
|
122487
|
+
async function emitMetaJson(opts, section, commandLabel, payload, idSuffix) {
|
|
122488
|
+
return emitCliJsonOrSnapshot(opts, {
|
|
122489
|
+
section,
|
|
122490
|
+
commandLabel,
|
|
122491
|
+
commandHint: idSuffix,
|
|
122492
|
+
payload,
|
|
122493
|
+
idSuffix
|
|
122494
|
+
});
|
|
122495
|
+
}
|
|
122496
|
+
function isHttpUrl(value) {
|
|
122497
|
+
try {
|
|
122498
|
+
const u = new URL(value);
|
|
122499
|
+
return u.protocol === "http:" || u.protocol === "https:";
|
|
122500
|
+
} catch {
|
|
122501
|
+
return false;
|
|
122502
|
+
}
|
|
122503
|
+
}
|
|
122504
|
+
function isIsoCountry(code) {
|
|
122505
|
+
return /^[A-Za-z]{2}$/.test(code.trim());
|
|
122506
|
+
}
|
|
122507
|
+
function normalizeQuestionType(type) {
|
|
122508
|
+
return type.trim().toUpperCase();
|
|
122509
|
+
}
|
|
122510
|
+
function isPredefinedQuestionType(type) {
|
|
122511
|
+
return META_PREDEFINED_QUESTION_TYPES.includes(
|
|
122512
|
+
normalizeQuestionType(type)
|
|
122513
|
+
);
|
|
122514
|
+
}
|
|
122515
|
+
function extractCreatedId(data) {
|
|
122516
|
+
if (!data || typeof data !== "object") return void 0;
|
|
122517
|
+
const rec = data;
|
|
122518
|
+
const id = rec.id ?? rec.Id;
|
|
122519
|
+
if (id == null) return void 0;
|
|
122520
|
+
const s = String(id).trim();
|
|
122521
|
+
return s || void 0;
|
|
122522
|
+
}
|
|
122523
|
+
function extractImageHash(data) {
|
|
122524
|
+
if (!data || typeof data !== "object") return void 0;
|
|
122525
|
+
const rec = data;
|
|
122526
|
+
const hash = rec.hash ?? rec.image_hash;
|
|
122527
|
+
if (hash == null) return void 0;
|
|
122528
|
+
const s = String(hash).trim();
|
|
122529
|
+
return s || void 0;
|
|
122530
|
+
}
|
|
122531
|
+
function parseCommaList(raw) {
|
|
122532
|
+
if (!raw?.trim()) return [];
|
|
122533
|
+
return raw.split(",").map((s) => s.trim()).filter(Boolean);
|
|
122534
|
+
}
|
|
122535
|
+
function parseOptionalNumber(raw) {
|
|
122536
|
+
if (raw == null || raw === "") return void 0;
|
|
122537
|
+
const n = typeof raw === "number" ? raw : Number(raw);
|
|
122538
|
+
if (!Number.isFinite(n)) return void 0;
|
|
122539
|
+
return n;
|
|
122540
|
+
}
|
|
122541
|
+
|
|
122542
|
+
// src/commands/meta-ad/client.ts
|
|
122543
|
+
function metaAdContext(token) {
|
|
122544
|
+
const config = loadConfig(token);
|
|
122545
|
+
return { config, facebookApiUrl: requireFacebookApi(config) };
|
|
122546
|
+
}
|
|
122547
|
+
function adsUrl(facebookApiUrl, account, path47) {
|
|
122548
|
+
const base = metaAdsBaseUrl(facebookApiUrl, account);
|
|
122549
|
+
const p = path47.startsWith("/") ? path47 : `/${path47}`;
|
|
122550
|
+
return `${base}${p}`;
|
|
122551
|
+
}
|
|
122552
|
+
async function metaAdGet(url, config, verbose, action) {
|
|
122553
|
+
try {
|
|
122554
|
+
return await apiFetch2(url, config, {}, verbose);
|
|
122555
|
+
} catch (err) {
|
|
122556
|
+
failMetaAd(action, err);
|
|
122557
|
+
}
|
|
122558
|
+
}
|
|
122559
|
+
async function metaAdPost(url, config, body, verbose, action) {
|
|
122560
|
+
try {
|
|
122561
|
+
return await apiFetch2(url, config, { method: "POST", body: JSON.stringify(body) }, verbose);
|
|
122562
|
+
} catch (err) {
|
|
122563
|
+
failMetaAd(action, err);
|
|
122564
|
+
}
|
|
122565
|
+
}
|
|
122566
|
+
function readAccountCurrency(account) {
|
|
122567
|
+
const c = account?.currency;
|
|
122568
|
+
return typeof c === "string" && c.trim() ? c.trim() : "USD";
|
|
122569
|
+
}
|
|
122570
|
+
async function fetchAccountCurrency(facebookApiUrl, account, config, verbose) {
|
|
122571
|
+
const acc = await metaAdGet(
|
|
122572
|
+
adsUrl(facebookApiUrl, account, "/account"),
|
|
122573
|
+
config,
|
|
122574
|
+
verbose,
|
|
122575
|
+
"\u8BFB\u53D6\u8D26\u6237\u5E01\u79CD\u5931\u8D25"
|
|
122576
|
+
);
|
|
122577
|
+
return readAccountCurrency(acc);
|
|
122578
|
+
}
|
|
122579
|
+
|
|
122580
|
+
// src/commands/meta-ad/account.ts
|
|
122581
|
+
async function runMetaAdAccount(opts) {
|
|
122582
|
+
const { digits } = resolveMetaAccountId(opts.account);
|
|
122583
|
+
const { config, facebookApiUrl } = metaAdContext(opts.token);
|
|
122584
|
+
const raw = await metaAdGet(
|
|
122585
|
+
adsUrl(facebookApiUrl, digits, "/account"),
|
|
122586
|
+
config,
|
|
122587
|
+
opts.verbose,
|
|
122588
|
+
"\u8BFB\u53D6\u5E7F\u544A\u8D26\u6237\u5931\u8D25"
|
|
122589
|
+
);
|
|
122590
|
+
const currency = typeof raw.currency === "string" ? raw.currency : "USD";
|
|
122591
|
+
const payload = withMoneyDisplay(raw, currency);
|
|
122592
|
+
if (await emitMetaJson(opts, "meta-ad-account", "meta-ad account", payload, digits)) return;
|
|
122593
|
+
console.log(`
|
|
122594
|
+
Meta \u5E7F\u544A\u8D26\u6237 ${digits}\uFF08${currency}\uFF09
|
|
122595
|
+
`);
|
|
122596
|
+
printCliTable(
|
|
122597
|
+
[
|
|
122598
|
+
{
|
|
122599
|
+
id: String(payload.account_id ?? payload.id ?? digits),
|
|
122600
|
+
name: String(payload.name ?? "\u2014"),
|
|
122601
|
+
status: String(payload.account_status ?? "\u2014"),
|
|
122602
|
+
spent: formatYuanDisplay(payload.amount_spentDisplay),
|
|
122603
|
+
balance: formatYuanDisplay(payload.balanceDisplay),
|
|
122604
|
+
cap: formatYuanDisplay(payload.spend_capDisplay)
|
|
122605
|
+
}
|
|
122606
|
+
],
|
|
122607
|
+
[
|
|
122608
|
+
{ key: "id", header: "\u8D26\u6237ID" },
|
|
122609
|
+
{ key: "name", header: "\u540D\u79F0" },
|
|
122610
|
+
{ key: "status", header: "\u72B6\u6001" },
|
|
122611
|
+
{ key: "spent", header: "\u5DF2\u82B1\u8D39(\u5143)" },
|
|
122612
|
+
{ key: "balance", header: "\u4F59\u989D(\u5143)" },
|
|
122613
|
+
{ key: "cap", header: "\u82B1\u8D39\u4E0A\u9650(\u5143)" }
|
|
122614
|
+
]
|
|
122615
|
+
);
|
|
122616
|
+
console.log();
|
|
122617
|
+
}
|
|
122618
|
+
|
|
122619
|
+
// src/commands/meta-ad/create.ts
|
|
122620
|
+
init_auth();
|
|
122621
|
+
init_cli_json_snapshot();
|
|
122622
|
+
import { isAbsolute as isAbsolute7, resolve as resolve11, dirname as dirname12, basename as basename9 } from "path";
|
|
122623
|
+
|
|
122624
|
+
// src/commands/meta-ad/bodies.ts
|
|
122625
|
+
function buildCreateFormBody(form) {
|
|
122626
|
+
const questions = (form.questions ?? []).map((q) => {
|
|
122627
|
+
const type = normalizeQuestionType(q.type);
|
|
122628
|
+
const item = { type };
|
|
122629
|
+
if (type === "CUSTOM") {
|
|
122630
|
+
if (q.label) item.label = q.label;
|
|
122631
|
+
if (q.key) item.key = q.key;
|
|
122632
|
+
}
|
|
122633
|
+
return item;
|
|
122634
|
+
});
|
|
122635
|
+
const body = {
|
|
122636
|
+
name: form.name,
|
|
122637
|
+
locale: form.locale || "en_US",
|
|
122638
|
+
privacy_policy: { url: form.privacyPolicyUrl },
|
|
122639
|
+
questions
|
|
122640
|
+
};
|
|
122641
|
+
if (form.followUpActionUrl) body.follow_up_action_url = form.followUpActionUrl;
|
|
122642
|
+
if (form.thankYouPage) {
|
|
122643
|
+
const typ = form.thankYouPage;
|
|
122644
|
+
const thankYou = {};
|
|
122645
|
+
if (typ.title) thankYou.title = typ.title;
|
|
122646
|
+
if (typ.body) thankYou.body = typ.body;
|
|
122647
|
+
if (typ.buttonText) thankYou.button_text = typ.buttonText;
|
|
122648
|
+
if (typ.buttonType) thankYou.button_type = typ.buttonType;
|
|
122649
|
+
if (typ.websiteUrl) thankYou.website_url = typ.websiteUrl;
|
|
122650
|
+
if (Object.keys(thankYou).length > 0) body.thank_you_page = thankYou;
|
|
122651
|
+
}
|
|
122652
|
+
return body;
|
|
122653
|
+
}
|
|
122654
|
+
function buildUploadImageBody(opts) {
|
|
122655
|
+
const body = { name: opts.name };
|
|
122656
|
+
if (opts.bytesBase64) body.bytes = opts.bytesBase64;
|
|
122657
|
+
else if (opts.imageUrl) body.image_url = opts.imageUrl;
|
|
122658
|
+
return body;
|
|
122659
|
+
}
|
|
122660
|
+
function buildCreateCampaignBody(opts) {
|
|
122661
|
+
const c = opts.campaign;
|
|
122662
|
+
const body = {
|
|
122663
|
+
name: c.name,
|
|
122664
|
+
special_ad_categories: c.specialAdCategories ?? [],
|
|
122665
|
+
buying_type: c.buyingType || "AUCTION"
|
|
122666
|
+
};
|
|
122667
|
+
if (opts.dailyBudgetMinor) body.daily_budget = opts.dailyBudgetMinor;
|
|
122668
|
+
if (opts.lifetimeBudgetMinor) body.lifetime_budget = opts.lifetimeBudgetMinor;
|
|
122669
|
+
if (opts.spendCapMinor) body.spend_cap = opts.spendCapMinor;
|
|
122670
|
+
if (c.bidStrategy) body.bid_strategy = c.bidStrategy;
|
|
122671
|
+
if (c.startTime) body.start_time = c.startTime;
|
|
122672
|
+
if (c.stopTime) body.stop_time = c.stopTime;
|
|
122673
|
+
if (c.status && (c.status === "ACTIVE" || c.status === "PAUSED")) {
|
|
122674
|
+
body.status = c.status;
|
|
122675
|
+
}
|
|
122676
|
+
return body;
|
|
122677
|
+
}
|
|
122678
|
+
function buildCreateAdSetBody(opts) {
|
|
122679
|
+
const a = opts.adset;
|
|
122680
|
+
const targeting = {
|
|
122681
|
+
geo_locations: { countries: (a.countries ?? []).map((c) => c.trim().toUpperCase()) }
|
|
122682
|
+
};
|
|
122683
|
+
if (a.ageMin != null) targeting.age_min = a.ageMin;
|
|
122684
|
+
if (a.ageMax != null) targeting.age_max = a.ageMax;
|
|
122685
|
+
if (a.genders && a.genders.length > 0) targeting.genders = a.genders;
|
|
122686
|
+
if (a.flexibleSpec) targeting.flexible_spec = a.flexibleSpec;
|
|
122687
|
+
const body = {
|
|
122688
|
+
name: a.name,
|
|
122689
|
+
campaign_id: opts.campaignId,
|
|
122690
|
+
promoted_object: { page_id: opts.pageId },
|
|
122691
|
+
targeting,
|
|
122692
|
+
optimization_goal: a.optimizationGoal || "LEAD_GENERATION"
|
|
122693
|
+
};
|
|
122694
|
+
if (opts.dailyBudgetMinor) body.daily_budget = opts.dailyBudgetMinor;
|
|
122695
|
+
if (opts.lifetimeBudgetMinor) body.lifetime_budget = opts.lifetimeBudgetMinor;
|
|
122696
|
+
if (a.bidStrategy) body.bid_strategy = a.bidStrategy;
|
|
122697
|
+
if (opts.bidAmountMinor) body.bid_amount = opts.bidAmountMinor;
|
|
122698
|
+
if (a.startTime) body.start_time = a.startTime;
|
|
122699
|
+
if (a.endTime) body.end_time = a.endTime;
|
|
122700
|
+
if (a.status && (a.status === "ACTIVE" || a.status === "PAUSED")) {
|
|
122701
|
+
body.status = a.status;
|
|
122702
|
+
}
|
|
122703
|
+
return body;
|
|
122704
|
+
}
|
|
122705
|
+
function buildCreateCreativeBody(opts) {
|
|
122706
|
+
const c = opts.creative;
|
|
122707
|
+
return {
|
|
122708
|
+
name: c.name,
|
|
122709
|
+
object_story_spec: {
|
|
122710
|
+
page_id: opts.pageId,
|
|
122711
|
+
link_data: {
|
|
122712
|
+
message: c.message,
|
|
122713
|
+
link: c.link,
|
|
122714
|
+
image_hash: opts.imageHash,
|
|
122715
|
+
name: c.headline || c.name,
|
|
122716
|
+
call_to_action: {
|
|
122717
|
+
type: c.ctaType || "SIGN_UP",
|
|
122718
|
+
value: { lead_gen_form_id: opts.formId }
|
|
122719
|
+
}
|
|
122720
|
+
}
|
|
122721
|
+
}
|
|
122722
|
+
};
|
|
122723
|
+
}
|
|
122724
|
+
function buildCreateAdBody(opts) {
|
|
122725
|
+
const body = {
|
|
122726
|
+
name: opts.name,
|
|
122727
|
+
adset_id: opts.adsetId,
|
|
122728
|
+
creative: { creative_id: opts.creativeId }
|
|
122729
|
+
};
|
|
122730
|
+
if (opts.status && (opts.status === "ACTIVE" || opts.status === "PAUSED")) {
|
|
122731
|
+
body.status = opts.status;
|
|
122732
|
+
}
|
|
122733
|
+
return body;
|
|
122734
|
+
}
|
|
122735
|
+
function buildUpdateCampaignBody(opts) {
|
|
122736
|
+
const body = {};
|
|
122737
|
+
if (opts.name != null) body.name = opts.name;
|
|
122738
|
+
if (opts.dailyBudgetMinor != null) body.daily_budget = opts.dailyBudgetMinor;
|
|
122739
|
+
if (opts.lifetimeBudgetMinor != null) body.lifetime_budget = opts.lifetimeBudgetMinor;
|
|
122740
|
+
if (opts.bidStrategy != null) body.bid_strategy = opts.bidStrategy;
|
|
122741
|
+
if (opts.spendCapMinor != null) body.spend_cap = opts.spendCapMinor;
|
|
122742
|
+
if (opts.startTime != null) body.start_time = opts.startTime;
|
|
122743
|
+
if (opts.stopTime != null) body.stop_time = opts.stopTime;
|
|
122744
|
+
if (opts.specialAdCategories != null) body.special_ad_categories = opts.specialAdCategories;
|
|
122745
|
+
if (opts.status != null) body.status = opts.status;
|
|
122746
|
+
return body;
|
|
122747
|
+
}
|
|
122748
|
+
function buildUpdateAdSetBody(opts) {
|
|
122749
|
+
const body = {};
|
|
122750
|
+
if (opts.name != null) body.name = opts.name;
|
|
122751
|
+
if (opts.dailyBudgetMinor != null) body.daily_budget = opts.dailyBudgetMinor;
|
|
122752
|
+
if (opts.lifetimeBudgetMinor != null) body.lifetime_budget = opts.lifetimeBudgetMinor;
|
|
122753
|
+
if (opts.optimizationGoal != null) body.optimization_goal = opts.optimizationGoal;
|
|
122754
|
+
if (opts.bidStrategy != null) body.bid_strategy = opts.bidStrategy;
|
|
122755
|
+
if (opts.bidAmountMinor != null) body.bid_amount = opts.bidAmountMinor;
|
|
122756
|
+
if (opts.startTime != null) body.start_time = opts.startTime;
|
|
122757
|
+
if (opts.endTime != null) body.end_time = opts.endTime;
|
|
122758
|
+
if (opts.status != null) body.status = opts.status;
|
|
122759
|
+
const hasTargeting = opts.countries != null || opts.ageMin != null || opts.ageMax != null || opts.genders != null || opts.flexibleSpec != null;
|
|
122760
|
+
if (hasTargeting) {
|
|
122761
|
+
const targeting = {};
|
|
122762
|
+
if (opts.countries) {
|
|
122763
|
+
targeting.geo_locations = { countries: opts.countries.map((c) => c.trim().toUpperCase()) };
|
|
122764
|
+
}
|
|
122765
|
+
if (opts.ageMin != null) targeting.age_min = opts.ageMin;
|
|
122766
|
+
if (opts.ageMax != null) targeting.age_max = opts.ageMax;
|
|
122767
|
+
if (opts.genders) targeting.genders = opts.genders;
|
|
122768
|
+
if (opts.flexibleSpec) targeting.flexible_spec = opts.flexibleSpec;
|
|
122769
|
+
body.targeting = targeting;
|
|
122770
|
+
}
|
|
122771
|
+
return body;
|
|
122772
|
+
}
|
|
122773
|
+
function buildUpdateAdBody(opts) {
|
|
122774
|
+
const body = {};
|
|
122775
|
+
if (opts.name != null) body.name = opts.name;
|
|
122776
|
+
if (opts.creativeId != null) body.creative = { creative_id: opts.creativeId };
|
|
122777
|
+
if (opts.status != null) body.status = opts.status;
|
|
122778
|
+
return body;
|
|
122779
|
+
}
|
|
122780
|
+
function buildStatusBody(status) {
|
|
122781
|
+
return { status: status.trim() };
|
|
122782
|
+
}
|
|
122783
|
+
function formHasPredefinedQuestion(form) {
|
|
122784
|
+
return (form.questions ?? []).some((q) => isPredefinedQuestionType(q.type));
|
|
122785
|
+
}
|
|
122786
|
+
|
|
122787
|
+
// src/commands/meta-ad/load.ts
|
|
122788
|
+
import { readFileSync as readFileSync15 } from "fs";
|
|
122789
|
+
function stripMetaKeys2(value) {
|
|
122790
|
+
if (Array.isArray(value)) {
|
|
122791
|
+
return value.map((item) => stripMetaKeys2(item));
|
|
122792
|
+
}
|
|
122793
|
+
if (value && typeof value === "object") {
|
|
122794
|
+
const out = {};
|
|
122795
|
+
for (const [key, val] of Object.entries(value)) {
|
|
122796
|
+
if (key.startsWith("_")) continue;
|
|
122797
|
+
out[key] = stripMetaKeys2(val);
|
|
122798
|
+
}
|
|
122799
|
+
return out;
|
|
122800
|
+
}
|
|
122801
|
+
return value;
|
|
122802
|
+
}
|
|
122803
|
+
function tryLoadJsonFile(configFile) {
|
|
122804
|
+
try {
|
|
122805
|
+
return JSON.parse(readFileSync15(configFile, "utf8"));
|
|
122806
|
+
} catch {
|
|
122807
|
+
return null;
|
|
122808
|
+
}
|
|
122809
|
+
}
|
|
122810
|
+
function loadMetaLeadCreateConfig(configFile) {
|
|
122811
|
+
const raw = tryLoadJsonFile(configFile);
|
|
122812
|
+
if (!raw || typeof raw !== "object") {
|
|
122813
|
+
console.error(`
|
|
122814
|
+
\u274C \u8BFB\u53D6\u914D\u7F6E\u6587\u4EF6\u5931\u8D25\uFF08${configFile}\uFF09
|
|
122815
|
+
`);
|
|
122816
|
+
process.exit(1);
|
|
122817
|
+
}
|
|
122818
|
+
return stripMetaKeys2(raw);
|
|
122819
|
+
}
|
|
122820
|
+
function loadMetaLeadFormConfig(configFile) {
|
|
122821
|
+
const raw = tryLoadJsonFile(configFile);
|
|
122822
|
+
if (!raw || typeof raw !== "object") {
|
|
122823
|
+
console.error(`
|
|
122824
|
+
\u274C \u8BFB\u53D6\u8868\u5355\u914D\u7F6E\u5931\u8D25\uFF08${configFile}\uFF09
|
|
122825
|
+
`);
|
|
122826
|
+
process.exit(1);
|
|
122827
|
+
}
|
|
122828
|
+
const stripped = stripMetaKeys2(raw);
|
|
122829
|
+
if (stripped.form && typeof stripped.form === "object") {
|
|
122830
|
+
return stripped.form;
|
|
122831
|
+
}
|
|
122832
|
+
return stripped;
|
|
122833
|
+
}
|
|
122834
|
+
|
|
122835
|
+
// src/commands/meta-ad/images.ts
|
|
122836
|
+
import { readFileSync as readFileSync16 } from "fs";
|
|
122837
|
+
import { basename as basename8 } from "path";
|
|
122838
|
+
function readImageBytesBase64(imagePath) {
|
|
122839
|
+
const buf = readFileSync16(imagePath);
|
|
122840
|
+
if (buf.length < 1 || buf.length > 8 * 1024 * 1024) {
|
|
122841
|
+
throw new Error(`\u56FE\u7247\u5927\u5C0F\u987B\u4E3A 1 \u5B57\u8282\uFF5E8MB\uFF0C\u5F53\u524D ${buf.length} \u5B57\u8282`);
|
|
122842
|
+
}
|
|
122843
|
+
return buf.toString("base64");
|
|
122844
|
+
}
|
|
122845
|
+
async function runMetaAdImageUpload(opts) {
|
|
122846
|
+
const { digits } = resolveMetaAccountId(opts.account);
|
|
122847
|
+
const hasUrl = Boolean(opts.imageUrl?.trim());
|
|
122848
|
+
const hasPath = Boolean(opts.imagePath?.trim());
|
|
122849
|
+
if (hasUrl === hasPath) {
|
|
122850
|
+
console.error("\n\u274C --image-url \u4E0E --image-path \u987B\u4E8C\u9009\u4E00\n");
|
|
122851
|
+
process.exit(1);
|
|
122852
|
+
}
|
|
122853
|
+
if (hasUrl && !isHttpUrl(opts.imageUrl.trim())) {
|
|
122854
|
+
console.error("\n\u274C --image-url \u987B\u4E3A\u516C\u7F51 http/https\n");
|
|
122855
|
+
process.exit(1);
|
|
122856
|
+
}
|
|
122857
|
+
let bytes;
|
|
122858
|
+
let name2 = opts.name?.trim();
|
|
122859
|
+
if (hasPath) {
|
|
122860
|
+
try {
|
|
122861
|
+
bytes = readImageBytesBase64(opts.imagePath);
|
|
122862
|
+
} catch (err) {
|
|
122863
|
+
console.error(`
|
|
122864
|
+
\u274C \u8BFB\u53D6\u672C\u5730\u56FE\u7247\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
|
|
122865
|
+
`);
|
|
122866
|
+
process.exit(1);
|
|
122867
|
+
}
|
|
122868
|
+
name2 = name2 || basename8(opts.imagePath);
|
|
122869
|
+
}
|
|
122870
|
+
if (!name2) {
|
|
122871
|
+
console.error("\n\u274C --name \u5FC5\u586B\uFF08\u6216\u4ECE --image-path \u63A8\u65AD\uFF09\n");
|
|
122872
|
+
process.exit(1);
|
|
122873
|
+
}
|
|
122874
|
+
const { config, facebookApiUrl } = metaAdContext(opts.token);
|
|
122875
|
+
const body = buildUploadImageBody({
|
|
122876
|
+
name: name2,
|
|
122877
|
+
imageUrl: hasUrl ? opts.imageUrl.trim() : void 0,
|
|
122878
|
+
bytesBase64: bytes
|
|
122879
|
+
});
|
|
122880
|
+
const data = await metaAdPost(
|
|
122881
|
+
adsUrl(facebookApiUrl, digits, "/images"),
|
|
122882
|
+
config,
|
|
122883
|
+
body,
|
|
122884
|
+
opts.verbose,
|
|
122885
|
+
"\u4E0A\u4F20\u5E7F\u544A\u56FE\u5931\u8D25"
|
|
122886
|
+
);
|
|
122887
|
+
const hash = extractImageHash(data);
|
|
122888
|
+
const payload = { ...data, hash, name: data.name ?? name2 };
|
|
122889
|
+
if (await emitMetaJson(opts, "meta-ad-image-upload", "meta-ad image-upload", payload, digits)) {
|
|
122890
|
+
return;
|
|
122891
|
+
}
|
|
122892
|
+
console.log(`
|
|
122893
|
+
\u2705 \u56FE\u7247\u5DF2\u4E0A\u4F20 hash=${hash ?? "\u2014"}
|
|
122894
|
+
`);
|
|
122895
|
+
}
|
|
122896
|
+
|
|
122897
|
+
// src/commands/meta-ad/validate.ts
|
|
122898
|
+
init_cli_json_snapshot();
|
|
122899
|
+
function hasBudget(daily, lifetime) {
|
|
122900
|
+
return daily != null || lifetime != null;
|
|
122901
|
+
}
|
|
122902
|
+
function pushBudgetPairErrors(errors, label, daily, lifetime) {
|
|
122903
|
+
if (daily != null && lifetime != null) {
|
|
122904
|
+
errors.push(`${label} \u4E0D\u80FD\u540C\u65F6\u586B dailyBudget \u4E0E lifetimeBudget`);
|
|
122905
|
+
}
|
|
122906
|
+
if (daily != null && !(Number.isFinite(daily) && daily > 0)) {
|
|
122907
|
+
errors.push(`${label}.dailyBudget \u987B\u4E3A\u5927\u4E8E 0 \u7684\u6570\u5B57\uFF08\u4E3B\u5E01\u79CD\u5143\uFF09`);
|
|
122908
|
+
}
|
|
122909
|
+
if (lifetime != null && !(Number.isFinite(lifetime) && lifetime > 0)) {
|
|
122910
|
+
errors.push(`${label}.lifetimeBudget \u987B\u4E3A\u5927\u4E8E 0 \u7684\u6570\u5B57\uFF08\u4E3B\u5E01\u79CD\u5143\uFF09`);
|
|
122911
|
+
}
|
|
122912
|
+
}
|
|
122913
|
+
function runMetaLeadValidation(cfg) {
|
|
122914
|
+
const errors = [];
|
|
122915
|
+
const warnings = [];
|
|
122916
|
+
const pendingAccount = isPendingMetaAccount(cfg.account);
|
|
122917
|
+
if (pendingAccount) {
|
|
122918
|
+
warnings.push(
|
|
122919
|
+
"account \u4E3A\u5360\u4F4D\uFF08\u4EC5\u51FA\u65B9\u6848\uFF09\uFF1Bcreate \u524D\u987B\u6362\u6210\u771F\u5B9E mediaCustomerId\uFF0C\u5E76\u5148 meta-ad pages"
|
|
122920
|
+
);
|
|
122921
|
+
} else {
|
|
122922
|
+
const account = parseMetaAccountId(cfg.account);
|
|
122923
|
+
if (!account.ok) errors.push(account.error);
|
|
122924
|
+
}
|
|
122925
|
+
if (isPendingMetaPage(cfg.pageId)) {
|
|
122926
|
+
if (pendingAccount) {
|
|
122927
|
+
warnings.push("pageId \u4E3A\u5360\u4F4D/\u7A7A\uFF08\u4EC5\u51FA\u65B9\u6848\uFF09\uFF1Bcreate \u524D\u5148 meta-ad pages \u53D6\u53EF\u6295\u653E\u4E3B\u9875");
|
|
122928
|
+
} else {
|
|
122929
|
+
errors.push("pageId \u5FC5\u586B\uFF08\u5148 meta-ad pages \u53D6\u53EF\u6295\u653E\u4E3B\u9875\uFF09");
|
|
122930
|
+
}
|
|
122931
|
+
}
|
|
122932
|
+
const mode = String(cfg.budgetMode ?? "").trim().toUpperCase();
|
|
122933
|
+
if (mode !== "CBO" && mode !== "ABO") {
|
|
122934
|
+
errors.push("budgetMode \u987B\u4E3A CBO \u6216 ABO");
|
|
122935
|
+
}
|
|
122936
|
+
const campaign = cfg.campaign ?? {};
|
|
122937
|
+
const adset = cfg.adset ?? {};
|
|
122938
|
+
const form = cfg.form ?? {};
|
|
122939
|
+
const creative = cfg.creative ?? {};
|
|
122940
|
+
const ad = cfg.ad ?? {};
|
|
122941
|
+
if (!campaign.name?.trim()) errors.push("campaign.name \u5FC5\u586B");
|
|
122942
|
+
if (!adset.name?.trim()) errors.push("adset.name \u5FC5\u586B");
|
|
122943
|
+
if (!creative.name?.trim()) errors.push("creative.name \u5FC5\u586B");
|
|
122944
|
+
if (!ad.name?.trim()) errors.push("ad.name \u5FC5\u586B");
|
|
122945
|
+
pushBudgetPairErrors(errors, "campaign", campaign.dailyBudget, campaign.lifetimeBudget);
|
|
122946
|
+
pushBudgetPairErrors(errors, "adset", adset.dailyBudget, adset.lifetimeBudget);
|
|
122947
|
+
const campaignHasBudget = hasBudget(campaign.dailyBudget, campaign.lifetimeBudget);
|
|
122948
|
+
const adsetHasBudget = hasBudget(adset.dailyBudget, adset.lifetimeBudget);
|
|
122949
|
+
if (mode === "CBO") {
|
|
122950
|
+
if (!campaignHasBudget) errors.push("CBO \u987B\u5728 campaign \u4E0A\u4F20 dailyBudget \u6216 lifetimeBudget");
|
|
122951
|
+
if (adsetHasBudget)
|
|
122952
|
+
errors.push("CBO \u7EC4\u4E0A\u4E0D\u80FD\u4F20 dailyBudget / lifetimeBudget\uFF08CBO_ABO_CONFLICT\uFF09");
|
|
122953
|
+
}
|
|
122954
|
+
if (mode === "ABO") {
|
|
122955
|
+
if (!adsetHasBudget) errors.push("ABO \u987B\u5728 adset \u4E0A\u4F20 dailyBudget \u6216 lifetimeBudget");
|
|
122956
|
+
if (campaignHasBudget)
|
|
122957
|
+
errors.push("ABO \u7CFB\u5217\u4E0A\u4E0D\u80FD\u4F20 dailyBudget / lifetimeBudget\uFF08CBO_ABO_CONFLICT\uFF09");
|
|
122958
|
+
}
|
|
122959
|
+
const countries = adset.countries ?? [];
|
|
122960
|
+
if (countries.length === 0) {
|
|
122961
|
+
errors.push("adset.countries \u81F3\u5C11\u4E00\u4E2A\u56FD\u5BB6\uFF08ISO \u4E24\u4F4D\uFF0C\u5982 US\uFF09");
|
|
122962
|
+
} else {
|
|
122963
|
+
for (const c of countries) {
|
|
122964
|
+
if (!isIsoCountry(String(c))) errors.push(`adset.countries \u975E\u6CD5\uFF1A${c}\uFF08\u987B ISO \u4E24\u4F4D\uFF09`);
|
|
122965
|
+
}
|
|
122966
|
+
}
|
|
122967
|
+
if (adset.ageMin != null && (!Number.isFinite(adset.ageMin) || adset.ageMin < 13)) {
|
|
122968
|
+
errors.push("adset.ageMin \u987B \u2265 13");
|
|
122969
|
+
}
|
|
122970
|
+
if (adset.ageMax != null && (!Number.isFinite(adset.ageMax) || adset.ageMax > 65)) {
|
|
122971
|
+
errors.push("adset.ageMax \u987B \u2264 65");
|
|
122972
|
+
}
|
|
122973
|
+
if (adset.ageMin != null && adset.ageMax != null && Number.isFinite(adset.ageMin) && Number.isFinite(adset.ageMax) && adset.ageMin > adset.ageMax) {
|
|
122974
|
+
errors.push("adset.ageMin \u4E0D\u80FD\u5927\u4E8E ageMax");
|
|
122975
|
+
}
|
|
122976
|
+
if (adset.optimizationGoal) {
|
|
122977
|
+
const g = adset.optimizationGoal.trim().toUpperCase();
|
|
122978
|
+
if (!META_OPTIMIZATION_GOALS.includes(g)) {
|
|
122979
|
+
errors.push("adset.optimizationGoal \u53EA\u5141\u8BB8 LEAD_GENERATION / QUALITY_LEAD");
|
|
122980
|
+
}
|
|
122981
|
+
}
|
|
122982
|
+
if (adset.bidAmount != null && !(Number.isFinite(adset.bidAmount) && adset.bidAmount > 0)) {
|
|
122983
|
+
errors.push("adset.bidAmount \u987B\u4E3A\u5927\u4E8E 0 \u7684\u6570\u5B57\uFF08\u4E3B\u5E01\u79CD\u5143\uFF09");
|
|
122984
|
+
}
|
|
122985
|
+
const reuseId = form.reuseId?.trim();
|
|
122986
|
+
if (reuseId) {
|
|
122987
|
+
if (form.name || form.questions?.length || form.privacyPolicyUrl) {
|
|
122988
|
+
warnings.push("form.reuseId \u5DF2\u8BBE\uFF0C\u5C06\u5FFD\u7565\u65B0\u5EFA\u8868\u5355\u5B57\u6BB5");
|
|
122989
|
+
}
|
|
122990
|
+
} else {
|
|
122991
|
+
if (!form.name?.trim()) errors.push("form.name \u5FC5\u586B\uFF08\u6216\u6539\u586B form.reuseId\uFF09");
|
|
122992
|
+
if (!form.privacyPolicyUrl?.trim()) {
|
|
122993
|
+
errors.push("form.privacyPolicyUrl \u5FC5\u586B");
|
|
122994
|
+
} else if (!isHttpUrl(form.privacyPolicyUrl.trim())) {
|
|
122995
|
+
errors.push("form.privacyPolicyUrl \u987B\u4E3A http/https \u7EDD\u5BF9\u5730\u5740");
|
|
122996
|
+
}
|
|
122997
|
+
if (!form.questions?.length) {
|
|
122998
|
+
errors.push("form.questions \u81F3\u5C11\u4E00\u9879");
|
|
122999
|
+
} else {
|
|
123000
|
+
if (!formHasPredefinedQuestion(form)) {
|
|
123001
|
+
errors.push("form.questions \u81F3\u5C11\u5305\u542B\u4E00\u4E2A\u9884\u5B9A\u4E49\u5B57\u6BB5\uFF08\u5982 EMAIL / FULL_NAME / PHONE\uFF09");
|
|
123002
|
+
}
|
|
123003
|
+
for (const [i, q] of form.questions.entries()) {
|
|
123004
|
+
const t = normalizeQuestionType(q.type ?? "");
|
|
123005
|
+
if (!t) {
|
|
123006
|
+
errors.push(`form.questions[${i}].type \u5FC5\u586B`);
|
|
123007
|
+
continue;
|
|
123008
|
+
}
|
|
123009
|
+
if (t === "CUSTOM") {
|
|
123010
|
+
if (!q.label?.trim()) errors.push(`form.questions[${i}] CUSTOM \u5FC5\u987B\u5E26 label`);
|
|
123011
|
+
} else if (!isPredefinedQuestionType(t)) {
|
|
123012
|
+
errors.push(`form.questions[${i}].type \u4E0D\u652F\u6301\uFF1A${q.type}`);
|
|
123013
|
+
}
|
|
123014
|
+
}
|
|
123015
|
+
}
|
|
123016
|
+
}
|
|
123017
|
+
if (!creative.message?.trim()) errors.push("creative.message \u5FC5\u586B");
|
|
123018
|
+
if (!creative.link?.trim()) {
|
|
123019
|
+
errors.push("creative.link \u5FC5\u586B");
|
|
123020
|
+
} else if (!isHttpUrl(creative.link.trim())) {
|
|
123021
|
+
errors.push("creative.link \u987B\u4E3A http/https \u7EDD\u5BF9\u5730\u5740");
|
|
123022
|
+
}
|
|
123023
|
+
const imageSources = [creative.imageHash, creative.imageUrl, creative.imagePath].filter(
|
|
123024
|
+
(x) => Boolean(x?.trim())
|
|
123025
|
+
);
|
|
123026
|
+
if (imageSources.length === 0) {
|
|
123027
|
+
errors.push("creative \u987B\u63D0\u4F9B imageHash / imageUrl / imagePath \u4E4B\u4E00");
|
|
123028
|
+
}
|
|
123029
|
+
if (imageSources.length > 1) {
|
|
123030
|
+
warnings.push(
|
|
123031
|
+
"creative \u540C\u65F6\u7ED9\u4E86\u591A\u79CD\u56FE\u7247\u6765\u6E90\uFF0C\u4F18\u5148 imageHash\uFF0C\u5176\u6B21\u672C\u5730 imagePath\uFF0C\u518D\u6B21 imageUrl"
|
|
123032
|
+
);
|
|
123033
|
+
}
|
|
123034
|
+
if (creative.imageUrl && !isHttpUrl(creative.imageUrl.trim())) {
|
|
123035
|
+
errors.push("creative.imageUrl \u987B\u4E3A\u516C\u7F51 http/https");
|
|
123036
|
+
}
|
|
123037
|
+
if (cfg.activate === true) {
|
|
123038
|
+
warnings.push(
|
|
123039
|
+
"activate \u4E0D\u4F1A\u81EA\u52A8\u628A\u5BF9\u8C61\u6539\u6210 ACTIVE\uFF1B\u6295\u653E\u8BF7\u7528 meta-ad campaign-status / adset-status / ad-status"
|
|
123040
|
+
);
|
|
123041
|
+
}
|
|
123042
|
+
const createStatuses = ["ACTIVE", "PAUSED", void 0, ""];
|
|
123043
|
+
for (const [label, status] of [
|
|
123044
|
+
["campaign.status", campaign.status],
|
|
123045
|
+
["adset.status", adset.status],
|
|
123046
|
+
["ad.status", ad.status]
|
|
123047
|
+
]) {
|
|
123048
|
+
if (status && !createStatuses.includes(status.trim().toUpperCase())) {
|
|
123049
|
+
errors.push(`${label} \u521B\u5EFA\u65F6\u53EA\u5141\u8BB8 ACTIVE / PAUSED\uFF08\u7F3A\u7701 PAUSED\uFF09`);
|
|
123050
|
+
}
|
|
123051
|
+
if (status && status.trim().toUpperCase() === "ACTIVE") {
|
|
123052
|
+
warnings.push(`${label}=ACTIVE \u4F1A\u5728\u521B\u5EFA\u65F6\u5C1D\u8BD5\u6295\u653E\uFF1B\u5EFA\u8BAE\u7701\u7565\uFF0C\u9ED8\u8BA4 PAUSED\uFF0C\u786E\u8BA4\u540E\u518D\u6539\u72B6\u6001`);
|
|
123053
|
+
}
|
|
123054
|
+
}
|
|
123055
|
+
const plan = cfg.plan;
|
|
123056
|
+
if (plan) {
|
|
123057
|
+
const tracks = plan.tracks ?? [];
|
|
123058
|
+
if (tracks.length < 3) {
|
|
123059
|
+
warnings.push("plan.tracks \u5EFA\u8BAE\u81F3\u5C11 3 \u4E2A\u89C6\u89C9\u5DEE\u5F02\u5957\u7CFB\uFF08\u5BA1\u67E5\u7A3F A/B/C\uFF09");
|
|
123060
|
+
}
|
|
123061
|
+
const visual = plan.matrix?.visual ?? [];
|
|
123062
|
+
const bodyTone = plan.matrix?.bodyTone ?? [];
|
|
123063
|
+
const headline = plan.matrix?.headline ?? [];
|
|
123064
|
+
if (visual.length < 3 || bodyTone.length < 2 || headline.length < 2) {
|
|
123065
|
+
warnings.push("plan.matrix \u5EFA\u8BAE\u81F3\u5C11 3 \u89C6\u89C9 \xD7 2 \u6B63\u6587\u6C14\u8D28 \xD7 2 \u6807\u9898\uFF083\xD72\xD72\uFF09");
|
|
123066
|
+
}
|
|
123067
|
+
if ((plan.adSets?.length ?? 0) > 1) {
|
|
123068
|
+
warnings.push(
|
|
123069
|
+
"plan.adSets \u591A\u7EC4\u53EA\u5199\u5165\u5BA1\u67E5\u7A3F\uFF1B\u4E00\u6B21 meta-ad create \u53EA\u843D\u5730 JSON.adset\uFF0C\u5176\u4F59\u7528 adset-create \u7EED\u5EFA"
|
|
123070
|
+
);
|
|
123071
|
+
}
|
|
123072
|
+
} else {
|
|
123073
|
+
warnings.push(
|
|
123074
|
+
"\u7F3A\u5C11 plan\uFF1A\u521B\u5EFA\u524D\u987B\u6309 meta-lead-launch-plan-template.md \u4ECE JSON \u6295\u5F71\u5B8C\u6574\u5BA1\u67E5\u7A3F\uFF1B\u5EFA\u8BAE\u8865 plan \u4EE5\u514D\u5BA1\u67E5\u7A3F\u7F3A\u54C1\u724C/\u5957\u7CFB/\u77E9\u9635"
|
|
123075
|
+
);
|
|
123076
|
+
}
|
|
123077
|
+
return { errors, warnings };
|
|
123078
|
+
}
|
|
123079
|
+
async function runMetaAdValidate(opts) {
|
|
123080
|
+
const cfg = loadMetaLeadCreateConfig(opts.configFile);
|
|
123081
|
+
const { errors, warnings } = runMetaLeadValidation(cfg);
|
|
123082
|
+
const payload = {
|
|
123083
|
+
ok: errors.length === 0,
|
|
123084
|
+
configFile: opts.configFile,
|
|
123085
|
+
account: cfg.account?.toString().trim() || void 0,
|
|
123086
|
+
pageId: cfg.pageId?.toString().trim() || void 0,
|
|
123087
|
+
budgetMode: cfg.budgetMode,
|
|
123088
|
+
errors,
|
|
123089
|
+
warnings
|
|
123090
|
+
};
|
|
123091
|
+
if (await emitCliJsonOrSnapshot(opts, {
|
|
123092
|
+
section: "meta-ad-validate",
|
|
123093
|
+
commandLabel: "meta-ad validate",
|
|
123094
|
+
commandHint: opts.configFile,
|
|
123095
|
+
payload,
|
|
123096
|
+
idSuffix: payload.account
|
|
123097
|
+
})) {
|
|
123098
|
+
if (!payload.ok) process.exit(1);
|
|
123099
|
+
return;
|
|
123100
|
+
}
|
|
123101
|
+
if (warnings.length > 0) {
|
|
123102
|
+
console.warn("\n\u26A0\uFE0F Meta Lead \u914D\u7F6E\u8B66\u544A\uFF1A");
|
|
123103
|
+
for (const w of warnings) console.warn(` \u2022 ${w}`);
|
|
123104
|
+
}
|
|
123105
|
+
if (errors.length > 0) {
|
|
123106
|
+
console.error("\n\u274C Meta Lead \u914D\u7F6E\u6821\u9A8C\u5931\u8D25\uFF1A");
|
|
123107
|
+
for (const e of errors) console.error(` \u2022 ${e}`);
|
|
123108
|
+
console.error();
|
|
123109
|
+
process.exit(1);
|
|
123110
|
+
}
|
|
123111
|
+
console.log("\n\u2705 Meta Lead \u914D\u7F6E\u6821\u9A8C\u901A\u8FC7\uFF08\u672C\u5730\uFF0C\u672A\u8C03\u7F51\u5173\uFF09\n");
|
|
123112
|
+
}
|
|
123113
|
+
|
|
123114
|
+
// src/commands/meta-ad/create.ts
|
|
123115
|
+
function resolveBesideConfig(configFile, p) {
|
|
123116
|
+
if (isAbsolute7(p)) return p;
|
|
123117
|
+
return resolve11(dirname12(resolve11(configFile)), p);
|
|
123118
|
+
}
|
|
123119
|
+
async function postJson(url, config, body, verbose) {
|
|
123120
|
+
return apiFetch2(url, config, { method: "POST", body: JSON.stringify(body) }, verbose);
|
|
123121
|
+
}
|
|
123122
|
+
function resolveCreateImageSource(cfg, configFile) {
|
|
123123
|
+
const c = cfg.creative;
|
|
123124
|
+
const name2 = c.name?.trim() || "lead.jpg";
|
|
123125
|
+
if (c.imageHash?.trim()) return { imageHash: c.imageHash.trim(), name: name2 };
|
|
123126
|
+
if (c.imagePath?.trim()) {
|
|
123127
|
+
return {
|
|
123128
|
+
imagePath: resolveBesideConfig(configFile, c.imagePath.trim()),
|
|
123129
|
+
name: basename9(c.imagePath)
|
|
123130
|
+
};
|
|
123131
|
+
}
|
|
123132
|
+
return { imageUrl: c.imageUrl?.trim(), name: name2 };
|
|
123133
|
+
}
|
|
123134
|
+
async function runMetaAdCreate(opts) {
|
|
123135
|
+
const cfg = loadMetaLeadCreateConfig(opts.configFile);
|
|
123136
|
+
const { errors, warnings } = runMetaLeadValidation(cfg);
|
|
123137
|
+
if (warnings.length > 0) {
|
|
123138
|
+
console.warn("\n\u26A0\uFE0F Meta Lead \u914D\u7F6E\u8B66\u544A\uFF1A");
|
|
123139
|
+
for (const w of warnings) console.warn(` \u2022 ${w}`);
|
|
123140
|
+
}
|
|
123141
|
+
if (errors.length > 0) {
|
|
123142
|
+
console.error("\n\u274C Meta Lead \u914D\u7F6E\u6821\u9A8C\u5931\u8D25\uFF1A");
|
|
123143
|
+
for (const e of errors) console.error(` \u2022 ${e}`);
|
|
123144
|
+
console.error();
|
|
123145
|
+
process.exit(1);
|
|
123146
|
+
}
|
|
123147
|
+
const { digits } = resolveMetaAccountId(cfg.account);
|
|
123148
|
+
const config = loadConfig(opts.token);
|
|
123149
|
+
const facebookApiUrl = requireFacebookApi(config);
|
|
123150
|
+
const pageId = cfg.pageId.trim();
|
|
123151
|
+
const progress = { ok: false, account: digits, pageId };
|
|
123152
|
+
const emitProgress = async () => emitCliJsonOrSnapshot(opts, {
|
|
123153
|
+
section: "meta-ad-create",
|
|
123154
|
+
commandLabel: "meta-ad create",
|
|
123155
|
+
commandHint: opts.configFile,
|
|
123156
|
+
payload: progress,
|
|
123157
|
+
idSuffix: digits
|
|
123158
|
+
});
|
|
123159
|
+
try {
|
|
123160
|
+
const account = await apiFetch2(
|
|
123161
|
+
adsUrl(facebookApiUrl, digits, "/account"),
|
|
123162
|
+
config,
|
|
123163
|
+
{},
|
|
123164
|
+
opts.verbose
|
|
123165
|
+
);
|
|
123166
|
+
progress.currency = readAccountCurrency(account);
|
|
123167
|
+
} catch (err) {
|
|
123168
|
+
progress.failedStep = "account";
|
|
123169
|
+
progress.error = formatMetaAdError(err);
|
|
123170
|
+
await emitProgress();
|
|
123171
|
+
console.error(`
|
|
123172
|
+
\u274C \u8BFB\u53D6\u8D26\u6237\u5931\u8D25\uFF1A${progress.error}
|
|
123173
|
+
`);
|
|
123174
|
+
process.exit(1);
|
|
123175
|
+
}
|
|
123176
|
+
const currency = progress.currency ?? "USD";
|
|
123177
|
+
try {
|
|
123178
|
+
if (cfg.form.reuseId?.trim()) {
|
|
123179
|
+
progress.formId = cfg.form.reuseId.trim();
|
|
123180
|
+
} else {
|
|
123181
|
+
const data = await postJson(
|
|
123182
|
+
adsUrl(facebookApiUrl, digits, `/pages/${encodeURIComponent(pageId)}/forms`),
|
|
123183
|
+
config,
|
|
123184
|
+
buildCreateFormBody(cfg.form),
|
|
123185
|
+
opts.verbose
|
|
123186
|
+
);
|
|
123187
|
+
progress.formId = extractCreatedId(data);
|
|
123188
|
+
if (!progress.formId) throw new Error("\u5EFA\u8868\u5355\u6210\u529F\u4F46\u672A\u8FD4\u56DE id");
|
|
123189
|
+
}
|
|
123190
|
+
} catch (err) {
|
|
123191
|
+
progress.failedStep = "form";
|
|
123192
|
+
progress.error = formatMetaAdError(err);
|
|
123193
|
+
await emitProgress();
|
|
123194
|
+
console.error(
|
|
123195
|
+
`
|
|
123196
|
+
\u274C \u8868\u5355\u6B65\u9AA4\u5931\u8D25\uFF1A${progress.error}
|
|
123197
|
+
\u5DF2\u5EFA\u6210\uFF1A${summarizeProgress(progress)}
|
|
123198
|
+
`
|
|
123199
|
+
);
|
|
123200
|
+
process.exit(1);
|
|
123201
|
+
}
|
|
123202
|
+
try {
|
|
123203
|
+
const src = resolveCreateImageSource(cfg, opts.configFile);
|
|
123204
|
+
if (src.imageHash) {
|
|
123205
|
+
progress.imageHash = src.imageHash;
|
|
123206
|
+
} else {
|
|
123207
|
+
let bytes;
|
|
123208
|
+
if (src.imagePath) bytes = readImageBytesBase64(src.imagePath);
|
|
123209
|
+
const data = await postJson(
|
|
123210
|
+
adsUrl(facebookApiUrl, digits, "/images"),
|
|
123211
|
+
config,
|
|
123212
|
+
buildUploadImageBody({ name: src.name, imageUrl: src.imageUrl, bytesBase64: bytes }),
|
|
123213
|
+
opts.verbose
|
|
123214
|
+
);
|
|
123215
|
+
progress.imageHash = extractImageHash(data);
|
|
123216
|
+
if (!progress.imageHash) throw new Error("\u4F20\u56FE\u6210\u529F\u4F46\u672A\u8FD4\u56DE hash");
|
|
123217
|
+
}
|
|
123218
|
+
} catch (err) {
|
|
123219
|
+
progress.failedStep = "image";
|
|
123220
|
+
progress.error = formatMetaAdError(err);
|
|
123221
|
+
await emitProgress();
|
|
123222
|
+
console.error(`
|
|
123223
|
+
\u274C \u4F20\u56FE\u5931\u8D25\uFF1A${progress.error}
|
|
123224
|
+
\u5DF2\u5EFA\u6210\uFF1A${summarizeProgress(progress)}
|
|
123225
|
+
`);
|
|
123226
|
+
process.exit(1);
|
|
123227
|
+
}
|
|
123228
|
+
try {
|
|
123229
|
+
const data = await postJson(
|
|
123230
|
+
adsUrl(facebookApiUrl, digits, "/campaigns"),
|
|
123231
|
+
config,
|
|
123232
|
+
buildCreateCampaignBody({
|
|
123233
|
+
campaign: { ...cfg.campaign, status: omitActive(cfg.campaign.status) },
|
|
123234
|
+
dailyBudgetMinor: cfg.campaign.dailyBudget != null ? toMinorUnitString(cfg.campaign.dailyBudget, currency) : void 0,
|
|
123235
|
+
lifetimeBudgetMinor: cfg.campaign.lifetimeBudget != null ? toMinorUnitString(cfg.campaign.lifetimeBudget, currency) : void 0
|
|
123236
|
+
}),
|
|
123237
|
+
opts.verbose
|
|
123238
|
+
);
|
|
123239
|
+
progress.campaignId = extractCreatedId(data);
|
|
123240
|
+
if (!progress.campaignId) throw new Error("\u5EFA\u7CFB\u5217\u6210\u529F\u4F46\u672A\u8FD4\u56DE id");
|
|
123241
|
+
} catch (err) {
|
|
123242
|
+
progress.failedStep = "campaign";
|
|
123243
|
+
progress.error = formatMetaAdError(err);
|
|
123244
|
+
await emitProgress();
|
|
123245
|
+
console.error(
|
|
123246
|
+
`
|
|
123247
|
+
\u274C \u5EFA\u7CFB\u5217\u5931\u8D25\uFF1A${progress.error}
|
|
123248
|
+
\u5DF2\u5EFA\u6210\uFF1A${summarizeProgress(progress)}
|
|
123249
|
+
`
|
|
123250
|
+
);
|
|
123251
|
+
process.exit(1);
|
|
123252
|
+
}
|
|
123253
|
+
try {
|
|
123254
|
+
const adsetUrl = adsUrl(facebookApiUrl, digits, "/adsets");
|
|
123255
|
+
const baseAdset = {
|
|
123256
|
+
...cfg.adset,
|
|
123257
|
+
optimizationGoal: cfg.adset.optimizationGoal?.toUpperCase(),
|
|
123258
|
+
status: omitActive(cfg.adset.status)
|
|
123259
|
+
};
|
|
123260
|
+
const money3 = {
|
|
123261
|
+
dailyBudgetMinor: cfg.adset.dailyBudget != null ? toMinorUnitString(cfg.adset.dailyBudget, currency) : void 0,
|
|
123262
|
+
lifetimeBudgetMinor: cfg.adset.lifetimeBudget != null ? toMinorUnitString(cfg.adset.lifetimeBudget, currency) : void 0
|
|
123263
|
+
};
|
|
123264
|
+
let data;
|
|
123265
|
+
try {
|
|
123266
|
+
data = await postJson(
|
|
123267
|
+
adsetUrl,
|
|
123268
|
+
config,
|
|
123269
|
+
buildCreateAdSetBody({
|
|
123270
|
+
adset: baseAdset,
|
|
123271
|
+
campaignId: progress.campaignId,
|
|
123272
|
+
pageId,
|
|
123273
|
+
...money3,
|
|
123274
|
+
bidAmountMinor: cfg.adset.bidAmount != null ? toMinorUnitString(cfg.adset.bidAmount, currency) : void 0
|
|
123275
|
+
}),
|
|
123276
|
+
opts.verbose
|
|
123277
|
+
);
|
|
123278
|
+
} catch (firstErr) {
|
|
123279
|
+
if (!isBidAmountRequired(firstErr) || cfg.adset.bidAmount != null) throw firstErr;
|
|
123280
|
+
const retryMajor = cfg.campaign.dailyBudget ?? cfg.campaign.lifetimeBudget ?? cfg.adset.dailyBudget ?? cfg.adset.lifetimeBudget ?? 1;
|
|
123281
|
+
if (!opts.jsonOut) {
|
|
123282
|
+
console.warn(
|
|
123283
|
+
`
|
|
123284
|
+
\u26A0\uFE0F \u8BE5\u6237\u8981\u6C42 bid cap\uFF0C\u6309\u65E5/\u603B\u9884\u7B97 ${retryMajor} ${currency} \u5E26 LOWEST_COST_WITH_BID_CAP \u91CD\u8BD5\u5EFA\u7EC4
|
|
123285
|
+
`
|
|
123286
|
+
);
|
|
123287
|
+
}
|
|
123288
|
+
data = await postJson(
|
|
123289
|
+
adsetUrl,
|
|
123290
|
+
config,
|
|
123291
|
+
buildCreateAdSetBody({
|
|
123292
|
+
adset: { ...baseAdset, bidStrategy: "LOWEST_COST_WITH_BID_CAP" },
|
|
123293
|
+
campaignId: progress.campaignId,
|
|
123294
|
+
pageId,
|
|
123295
|
+
...money3,
|
|
123296
|
+
bidAmountMinor: toMinorUnitString(retryMajor, currency)
|
|
123297
|
+
}),
|
|
123298
|
+
opts.verbose
|
|
123299
|
+
);
|
|
123300
|
+
}
|
|
123301
|
+
progress.adSetId = extractCreatedId(data);
|
|
123302
|
+
if (!progress.adSetId) throw new Error("\u5EFA\u7EC4\u6210\u529F\u4F46\u672A\u8FD4\u56DE id");
|
|
123303
|
+
} catch (err) {
|
|
123304
|
+
progress.failedStep = "adset";
|
|
123305
|
+
progress.error = formatMetaAdError(err);
|
|
123306
|
+
await emitProgress();
|
|
123307
|
+
console.error(
|
|
123308
|
+
`
|
|
123309
|
+
\u274C \u5EFA\u5E7F\u544A\u7EC4\u5931\u8D25\uFF1A${progress.error}
|
|
123310
|
+
\u5DF2\u5EFA\u6210\uFF1A${summarizeProgress(progress)}
|
|
123311
|
+
`
|
|
123312
|
+
);
|
|
123313
|
+
process.exit(1);
|
|
123314
|
+
}
|
|
123315
|
+
try {
|
|
123316
|
+
const data = await postJson(
|
|
123317
|
+
adsUrl(facebookApiUrl, digits, "/adcreatives"),
|
|
123318
|
+
config,
|
|
123319
|
+
buildCreateCreativeBody({
|
|
123320
|
+
creative: cfg.creative,
|
|
123321
|
+
pageId,
|
|
123322
|
+
formId: progress.formId,
|
|
123323
|
+
imageHash: progress.imageHash
|
|
123324
|
+
}),
|
|
123325
|
+
opts.verbose
|
|
123326
|
+
);
|
|
123327
|
+
progress.creativeId = extractCreatedId(data);
|
|
123328
|
+
if (!progress.creativeId) throw new Error("\u5EFA\u521B\u610F\u6210\u529F\u4F46\u672A\u8FD4\u56DE id");
|
|
123329
|
+
} catch (err) {
|
|
123330
|
+
progress.failedStep = "creative";
|
|
123331
|
+
progress.error = formatMetaAdError(err);
|
|
123332
|
+
await emitProgress();
|
|
123333
|
+
console.error(
|
|
123334
|
+
`
|
|
123335
|
+
\u274C \u5EFA\u521B\u610F\u5931\u8D25\uFF1A${progress.error}
|
|
123336
|
+
\u5DF2\u5EFA\u6210\uFF1A${summarizeProgress(progress)}
|
|
123337
|
+
`
|
|
123338
|
+
);
|
|
123339
|
+
process.exit(1);
|
|
123340
|
+
}
|
|
123341
|
+
try {
|
|
123342
|
+
const data = await postJson(
|
|
123343
|
+
adsUrl(facebookApiUrl, digits, "/ads"),
|
|
123344
|
+
config,
|
|
123345
|
+
buildCreateAdBody({
|
|
123346
|
+
name: cfg.ad.name.trim(),
|
|
123347
|
+
adsetId: progress.adSetId,
|
|
123348
|
+
creativeId: progress.creativeId,
|
|
123349
|
+
status: omitActive(cfg.ad.status)
|
|
123350
|
+
}),
|
|
123351
|
+
opts.verbose
|
|
123352
|
+
);
|
|
123353
|
+
progress.adId = extractCreatedId(data);
|
|
123354
|
+
if (!progress.adId) throw new Error("\u5EFA\u5E7F\u544A\u6210\u529F\u4F46\u672A\u8FD4\u56DE id");
|
|
123355
|
+
} catch (err) {
|
|
123356
|
+
progress.failedStep = "ad";
|
|
123357
|
+
progress.error = formatMetaAdError(err);
|
|
123358
|
+
await emitProgress();
|
|
123359
|
+
console.error(
|
|
123360
|
+
`
|
|
123361
|
+
\u274C \u5EFA\u5E7F\u544A\u5931\u8D25\uFF1A${progress.error}
|
|
123362
|
+
\u5DF2\u5EFA\u6210\uFF1A${summarizeProgress(progress)}
|
|
123363
|
+
`
|
|
123364
|
+
);
|
|
123365
|
+
process.exit(1);
|
|
123366
|
+
}
|
|
123367
|
+
progress.ok = true;
|
|
123368
|
+
if (await emitProgress()) return;
|
|
123369
|
+
console.log("\n\u2705 Meta Instant Form \u7EBF\u7D22\u5E7F\u544A\u5DF2\u521B\u5EFA\uFF08\u9ED8\u8BA4 PAUSED\uFF09\n");
|
|
123370
|
+
console.log(` formId ${progress.formId}`);
|
|
123371
|
+
console.log(` imageHash ${progress.imageHash}`);
|
|
123372
|
+
console.log(` campaignId ${progress.campaignId}`);
|
|
123373
|
+
console.log(` adSetId ${progress.adSetId}`);
|
|
123374
|
+
console.log(` creativeId ${progress.creativeId}`);
|
|
123375
|
+
console.log(` adId ${progress.adId}`);
|
|
123376
|
+
console.log(
|
|
123377
|
+
"\n \u6295\u653E\uFF1A\u4E09\u4E2A\u5BF9\u8C61\u5206\u522B meta-ad campaign-status / adset-status / ad-status --status ACTIVE\n"
|
|
123378
|
+
);
|
|
123379
|
+
}
|
|
123380
|
+
function omitActive(status) {
|
|
123381
|
+
if (!status) return void 0;
|
|
123382
|
+
const t = status.trim().toUpperCase();
|
|
123383
|
+
if (t === "ACTIVE") return void 0;
|
|
123384
|
+
return t === "PAUSED" ? "PAUSED" : status;
|
|
123385
|
+
}
|
|
123386
|
+
function summarizeProgress(p) {
|
|
123387
|
+
const parts = [
|
|
123388
|
+
p.formId && `formId=${p.formId}`,
|
|
123389
|
+
p.imageHash && `imageHash=${p.imageHash}`,
|
|
123390
|
+
p.campaignId && `campaignId=${p.campaignId}`,
|
|
123391
|
+
p.adSetId && `adSetId=${p.adSetId}`,
|
|
123392
|
+
p.creativeId && `creativeId=${p.creativeId}`,
|
|
123393
|
+
p.adId && `adId=${p.adId}`
|
|
123394
|
+
].filter(Boolean);
|
|
123395
|
+
return parts.length > 0 ? parts.join(" ") : "\u65E0";
|
|
123396
|
+
}
|
|
123397
|
+
|
|
123398
|
+
// src/commands/meta-ad/pages.ts
|
|
123399
|
+
init_cli_table();
|
|
123400
|
+
async function runMetaAdPages(opts) {
|
|
123401
|
+
const { digits } = resolveMetaAccountId(opts.account);
|
|
123402
|
+
const { config, facebookApiUrl } = metaAdContext(opts.token);
|
|
123403
|
+
const raw = await metaAdGet(
|
|
123404
|
+
adsUrl(facebookApiUrl, digits, "/pages"),
|
|
123405
|
+
config,
|
|
123406
|
+
opts.verbose,
|
|
123407
|
+
"\u5217\u4E3B\u9875\u5931\u8D25"
|
|
123408
|
+
);
|
|
123409
|
+
const items2 = Array.isArray(raw) ? raw : [];
|
|
123410
|
+
const payload = wrapListJson({
|
|
123411
|
+
page: 1,
|
|
123412
|
+
pageSize: items2.length || 25,
|
|
123413
|
+
total: items2.length,
|
|
123414
|
+
items: items2
|
|
123415
|
+
});
|
|
123416
|
+
if (await emitMetaJson(opts, "meta-ad-pages", "meta-ad pages", payload, digits)) return;
|
|
123417
|
+
console.log(`
|
|
123418
|
+
\u53EF\u6295\u653E\u4E3B\u9875\uFF08\u8D26\u6237 ${digits}\uFF0C${items2.length} \u4E2A\uFF09
|
|
123419
|
+
`);
|
|
123420
|
+
if (items2.length === 0) {
|
|
123421
|
+
console.log(
|
|
123422
|
+
" \u7A7A\u5217\u8868\u3002\u5F53\u524D FacebookAds Token \u5217\u4E0D\u5230\u4E3B\u9875\uFF0C\u5148\u52A0\u4E3B\u9875\u5E7F\u544A\u6295\u653E/\u7BA1\u7406\u5458\u6743\u9650\u518D\u91CD\u65B0\u6388\u6743\u3002\n"
|
|
123423
|
+
);
|
|
123424
|
+
return;
|
|
123425
|
+
}
|
|
123426
|
+
printCliTable(
|
|
123427
|
+
items2.map((p) => {
|
|
123428
|
+
const rec = p;
|
|
123429
|
+
return {
|
|
123430
|
+
id: String(rec.id ?? ""),
|
|
123431
|
+
name: String(rec.name ?? "\u2014"),
|
|
123432
|
+
category: String(rec.category ?? "\u2014"),
|
|
123433
|
+
tasks: Array.isArray(rec.tasks) ? rec.tasks.join(",") : "\u2014"
|
|
123434
|
+
};
|
|
123435
|
+
}),
|
|
123436
|
+
[
|
|
123437
|
+
{ key: "id", header: "pageId" },
|
|
123438
|
+
{ key: "name", header: "\u540D\u79F0" },
|
|
123439
|
+
{ key: "category", header: "\u7C7B\u76EE" },
|
|
123440
|
+
{ key: "tasks", header: "\u4EFB\u52A1" }
|
|
123441
|
+
]
|
|
123442
|
+
);
|
|
123443
|
+
console.log();
|
|
123444
|
+
}
|
|
123445
|
+
|
|
123446
|
+
// src/commands/meta-ad/forms.ts
|
|
123447
|
+
init_cli_table();
|
|
123448
|
+
async function runMetaAdForms(opts) {
|
|
123449
|
+
const { digits } = resolveMetaAccountId(opts.account);
|
|
123450
|
+
const { config, facebookApiUrl } = metaAdContext(opts.token);
|
|
123451
|
+
const params = new URLSearchParams();
|
|
123452
|
+
if (opts.limit) params.set("limit", opts.limit);
|
|
123453
|
+
if (opts.after) params.set("after", opts.after);
|
|
123454
|
+
if (opts.before) params.set("before", opts.before);
|
|
123455
|
+
const qs = params.toString();
|
|
123456
|
+
const path47 = `/pages/${encodeURIComponent(opts.pageId)}/forms${qs ? `?${qs}` : ""}`;
|
|
123457
|
+
const raw = await metaAdGet(
|
|
123458
|
+
adsUrl(facebookApiUrl, digits, path47),
|
|
123459
|
+
config,
|
|
123460
|
+
opts.verbose,
|
|
123461
|
+
"\u5217\u8868\u5355\u5931\u8D25"
|
|
123462
|
+
);
|
|
123463
|
+
if (await emitMetaJson(opts, "meta-ad-forms", "meta-ad forms", raw, digits)) return;
|
|
123464
|
+
const data = Array.isArray(raw.data) ? raw.data : [];
|
|
123465
|
+
console.log(`
|
|
123466
|
+
Instant Form\uFF08page ${opts.pageId}\uFF0C\u672C\u9875 ${data.length} \u6761\uFF09
|
|
123467
|
+
`);
|
|
123468
|
+
if (data.length === 0) {
|
|
123469
|
+
console.log(" \u6682\u65E0\u8868\u5355\u3002\u53EF\u7528 meta-ad form-create \u65B0\u5EFA\u3002\n");
|
|
123470
|
+
return;
|
|
123471
|
+
}
|
|
123472
|
+
printCliTable(
|
|
123473
|
+
data.map((row) => {
|
|
123474
|
+
const rec = row;
|
|
123475
|
+
return {
|
|
123476
|
+
id: String(rec.id ?? ""),
|
|
123477
|
+
name: String(rec.name ?? "\u2014"),
|
|
123478
|
+
status: String(rec.status ?? "\u2014"),
|
|
123479
|
+
locale: String(rec.locale ?? "\u2014")
|
|
123480
|
+
};
|
|
123481
|
+
}),
|
|
123482
|
+
[
|
|
123483
|
+
{ key: "id", header: "formId" },
|
|
123484
|
+
{ key: "name", header: "\u540D\u79F0" },
|
|
123485
|
+
{ key: "status", header: "\u72B6\u6001" },
|
|
123486
|
+
{ key: "locale", header: "\u8BED\u8A00" }
|
|
123487
|
+
]
|
|
123488
|
+
);
|
|
123489
|
+
console.log();
|
|
123490
|
+
}
|
|
123491
|
+
function assertFormCreateable(form) {
|
|
123492
|
+
const fake = {
|
|
123493
|
+
account: "1",
|
|
123494
|
+
pageId: "1",
|
|
123495
|
+
budgetMode: "CBO",
|
|
123496
|
+
form,
|
|
123497
|
+
campaign: { name: "x", dailyBudget: 1 },
|
|
123498
|
+
adset: { name: "x", countries: ["US"] },
|
|
123499
|
+
creative: {
|
|
123500
|
+
name: "x",
|
|
123501
|
+
message: "x",
|
|
123502
|
+
link: "https://example.com",
|
|
123503
|
+
imageHash: "x"
|
|
123504
|
+
},
|
|
123505
|
+
ad: { name: "x" }
|
|
123506
|
+
};
|
|
123507
|
+
const { errors } = runMetaLeadValidation(fake);
|
|
123508
|
+
const formErrors = errors.filter((e) => e.startsWith("form."));
|
|
123509
|
+
if (form.reuseId?.trim()) {
|
|
123510
|
+
console.error("\n\u274C form-create \u662F\u65B0\u5EFA\u8868\u5355\uFF0C\u4E0D\u8981\u4F20 reuseId\n");
|
|
123511
|
+
process.exit(1);
|
|
123512
|
+
}
|
|
123513
|
+
if (!form.name?.trim()) {
|
|
123514
|
+
console.error("\n\u274C form.name \u5FC5\u586B\n");
|
|
123515
|
+
process.exit(1);
|
|
123516
|
+
}
|
|
123517
|
+
if (!form.privacyPolicyUrl?.trim() || !isHttpUrl(form.privacyPolicyUrl)) {
|
|
123518
|
+
console.error("\n\u274C form.privacyPolicyUrl \u987B\u4E3A http/https \u7EDD\u5BF9\u5730\u5740\n");
|
|
123519
|
+
process.exit(1);
|
|
123520
|
+
}
|
|
123521
|
+
if (formErrors.length > 0) {
|
|
123522
|
+
console.error("\n\u274C \u8868\u5355\u914D\u7F6E\u65E0\u6548\uFF1A");
|
|
123523
|
+
for (const e of formErrors) console.error(` \u2022 ${e}`);
|
|
123524
|
+
console.error();
|
|
123525
|
+
process.exit(1);
|
|
123526
|
+
}
|
|
123527
|
+
}
|
|
123528
|
+
async function runMetaAdFormCreate(opts) {
|
|
123529
|
+
const { digits } = resolveMetaAccountId(opts.account);
|
|
123530
|
+
const form = loadMetaLeadFormConfig(opts.configFile);
|
|
123531
|
+
assertFormCreateable(form);
|
|
123532
|
+
const { config, facebookApiUrl } = metaAdContext(opts.token);
|
|
123533
|
+
const body = buildCreateFormBody(form);
|
|
123534
|
+
const data = await metaAdPost(
|
|
123535
|
+
adsUrl(facebookApiUrl, digits, `/pages/${encodeURIComponent(opts.pageId)}/forms`),
|
|
123536
|
+
config,
|
|
123537
|
+
body,
|
|
123538
|
+
opts.verbose,
|
|
123539
|
+
"\u65B0\u5EFA\u8868\u5355\u5931\u8D25"
|
|
123540
|
+
);
|
|
123541
|
+
const id = extractCreatedId(data);
|
|
123542
|
+
const payload = { ...data, id };
|
|
123543
|
+
if (await emitMetaJson(opts, "meta-ad-form-create", "meta-ad form-create", payload, digits)) {
|
|
123544
|
+
return;
|
|
123545
|
+
}
|
|
123546
|
+
console.log(`
|
|
123547
|
+
\u2705 Instant Form \u5DF2\u521B\u5EFA\uFF1A${id ?? JSON.stringify(data)}
|
|
123548
|
+
`);
|
|
123549
|
+
}
|
|
123550
|
+
|
|
123551
|
+
// src/commands/meta-ad/campaign.ts
|
|
123552
|
+
init_cli_table();
|
|
123553
|
+
async function runMetaAdCampaignGet(opts) {
|
|
123554
|
+
const { digits } = resolveMetaAccountId(opts.account);
|
|
123555
|
+
const { config, facebookApiUrl } = metaAdContext(opts.token);
|
|
123556
|
+
const [raw, currency] = await Promise.all([
|
|
123557
|
+
metaAdGet(
|
|
123558
|
+
adsUrl(facebookApiUrl, digits, `/campaigns/${encodeURIComponent(opts.id)}`),
|
|
123559
|
+
config,
|
|
123560
|
+
opts.verbose,
|
|
123561
|
+
"\u8BFB\u53D6\u7CFB\u5217\u5931\u8D25"
|
|
123562
|
+
),
|
|
123563
|
+
fetchAccountCurrency(facebookApiUrl, digits, config, opts.verbose)
|
|
123564
|
+
]);
|
|
123565
|
+
const payload = withMoneyDisplay(raw, currency);
|
|
123566
|
+
if (await emitMetaJson(opts, "meta-ad-campaign", "meta-ad campaign", payload, digits)) return;
|
|
123567
|
+
console.log(`
|
|
123568
|
+
\u7CFB\u5217 ${opts.id}\uFF08${currency}\uFF09
|
|
123569
|
+
`);
|
|
123570
|
+
printCliTable(
|
|
123571
|
+
[
|
|
123572
|
+
{
|
|
123573
|
+
id: String(payload.id ?? opts.id),
|
|
123574
|
+
name: String(payload.name ?? "\u2014"),
|
|
123575
|
+
status: String(payload.status ?? "\u2014"),
|
|
123576
|
+
objective: String(payload.objective ?? "\u2014"),
|
|
123577
|
+
budget: formatYuanDisplay(payload.daily_budgetDisplay)
|
|
123578
|
+
}
|
|
123579
|
+
],
|
|
123580
|
+
[
|
|
123581
|
+
{ key: "id", header: "ID" },
|
|
123582
|
+
{ key: "name", header: "\u540D\u79F0" },
|
|
123583
|
+
{ key: "status", header: "\u72B6\u6001" },
|
|
123584
|
+
{ key: "objective", header: "\u76EE\u6807" },
|
|
123585
|
+
{ key: "budget", header: "\u65E5\u9884\u7B97(\u5143)" }
|
|
123586
|
+
]
|
|
123587
|
+
);
|
|
123588
|
+
console.log();
|
|
123589
|
+
}
|
|
123590
|
+
async function runMetaAdCampaignCreate(opts) {
|
|
123591
|
+
const { digits } = resolveMetaAccountId(opts.account);
|
|
123592
|
+
if (!opts.name?.trim()) {
|
|
123593
|
+
console.error("\n\u274C --name \u5FC5\u586B\n");
|
|
123594
|
+
process.exit(1);
|
|
123595
|
+
}
|
|
123596
|
+
const daily = parseOptionalNumber(opts.dailyBudget);
|
|
123597
|
+
const lifetime = parseOptionalNumber(opts.lifetimeBudget);
|
|
123598
|
+
if (daily != null && lifetime != null) {
|
|
123599
|
+
console.error("\n\u274C --daily-budget \u4E0E --lifetime-budget \u4E0D\u80FD\u540C\u65F6\u4F20\n");
|
|
123600
|
+
process.exit(1);
|
|
123601
|
+
}
|
|
123602
|
+
const { config, facebookApiUrl } = metaAdContext(opts.token);
|
|
123603
|
+
const currency = await fetchAccountCurrency(facebookApiUrl, digits, config, opts.verbose);
|
|
123604
|
+
const campaign = {
|
|
123605
|
+
name: opts.name.trim(),
|
|
123606
|
+
dailyBudget: daily,
|
|
123607
|
+
lifetimeBudget: lifetime,
|
|
123608
|
+
specialAdCategories: parseCommaList(opts.specialAdCategories),
|
|
123609
|
+
status: opts.status
|
|
123610
|
+
};
|
|
123611
|
+
let body;
|
|
123612
|
+
try {
|
|
123613
|
+
body = buildCreateCampaignBody({
|
|
123614
|
+
campaign,
|
|
123615
|
+
dailyBudgetMinor: daily != null ? toMinorUnitString(daily, currency) : void 0,
|
|
123616
|
+
lifetimeBudgetMinor: lifetime != null ? toMinorUnitString(lifetime, currency) : void 0
|
|
123617
|
+
});
|
|
123618
|
+
} catch (err) {
|
|
123619
|
+
console.error(`
|
|
123620
|
+
\u274C ${err instanceof Error ? err.message : String(err)}
|
|
123621
|
+
`);
|
|
123622
|
+
process.exit(1);
|
|
123623
|
+
}
|
|
123624
|
+
const data = await metaAdPost(
|
|
123625
|
+
adsUrl(facebookApiUrl, digits, "/campaigns"),
|
|
123626
|
+
config,
|
|
123627
|
+
body,
|
|
123628
|
+
opts.verbose,
|
|
123629
|
+
"\u5EFA\u7CFB\u5217\u5931\u8D25"
|
|
123630
|
+
);
|
|
123631
|
+
const id = extractCreatedId(data);
|
|
123632
|
+
const payload = { ...data, id, currency };
|
|
123633
|
+
if (await emitMetaJson(opts, "meta-ad-campaign-create", "meta-ad campaign-create", payload, digits)) {
|
|
123634
|
+
return;
|
|
123635
|
+
}
|
|
123636
|
+
console.log(
|
|
123637
|
+
`
|
|
123638
|
+
\u2705 \u7CFB\u5217\u5DF2\u521B\u5EFA\uFF1A${id ?? JSON.stringify(data)}\uFF08\u9ED8\u8BA4 PAUSED\uFF0Cobjective=OUTCOME_LEADS\uFF09
|
|
123639
|
+
`
|
|
123640
|
+
);
|
|
123641
|
+
}
|
|
123642
|
+
async function runMetaAdCampaignEdit(opts) {
|
|
123643
|
+
const { digits } = resolveMetaAccountId(opts.account);
|
|
123644
|
+
const daily = parseOptionalNumber(opts.dailyBudget);
|
|
123645
|
+
const lifetime = parseOptionalNumber(opts.lifetimeBudget);
|
|
123646
|
+
if (daily != null && lifetime != null) {
|
|
123647
|
+
console.error("\n\u274C --daily-budget \u4E0E --lifetime-budget \u4E0D\u80FD\u540C\u65F6\u4F20\n");
|
|
123648
|
+
process.exit(1);
|
|
123649
|
+
}
|
|
123650
|
+
const { config, facebookApiUrl } = metaAdContext(opts.token);
|
|
123651
|
+
const currency = await fetchAccountCurrency(facebookApiUrl, digits, config, opts.verbose);
|
|
123652
|
+
let body;
|
|
123653
|
+
try {
|
|
123654
|
+
body = buildUpdateCampaignBody({
|
|
123655
|
+
name: opts.name?.trim() || void 0,
|
|
123656
|
+
dailyBudgetMinor: daily != null ? toMinorUnitString(daily, currency) : void 0,
|
|
123657
|
+
lifetimeBudgetMinor: lifetime != null ? toMinorUnitString(lifetime, currency) : void 0,
|
|
123658
|
+
bidStrategy: opts.bidStrategy,
|
|
123659
|
+
spendCapMinor: opts.spendCap != null ? toMinorUnitString(Number(opts.spendCap), currency) : void 0,
|
|
123660
|
+
startTime: opts.startTime,
|
|
123661
|
+
stopTime: opts.stopTime,
|
|
123662
|
+
specialAdCategories: opts.specialAdCategories != null ? parseCommaList(opts.specialAdCategories) : void 0,
|
|
123663
|
+
status: opts.status
|
|
123664
|
+
});
|
|
123665
|
+
} catch (err) {
|
|
123666
|
+
console.error(`
|
|
123667
|
+
\u274C ${err instanceof Error ? err.message : String(err)}
|
|
123668
|
+
`);
|
|
123669
|
+
process.exit(1);
|
|
123670
|
+
}
|
|
123671
|
+
if (Object.keys(body).length === 0) {
|
|
123672
|
+
console.error("\n\u274C \u81F3\u5C11\u4F20\u4E00\u4E2A\u53EF\u6539\u5B57\u6BB5\n");
|
|
123673
|
+
process.exit(1);
|
|
123674
|
+
}
|
|
123675
|
+
const data = await metaAdPost(
|
|
123676
|
+
adsUrl(facebookApiUrl, digits, `/campaigns/${encodeURIComponent(opts.id)}`),
|
|
123677
|
+
config,
|
|
123678
|
+
body,
|
|
123679
|
+
opts.verbose,
|
|
123680
|
+
"\u6539\u7CFB\u5217\u5931\u8D25"
|
|
123681
|
+
);
|
|
123682
|
+
const payload = { id: extractCreatedId(data) ?? opts.id, ...data };
|
|
123683
|
+
if (await emitMetaJson(opts, "meta-ad-campaign-edit", "meta-ad campaign-edit", payload, digits)) {
|
|
123684
|
+
return;
|
|
123685
|
+
}
|
|
123686
|
+
console.log(`
|
|
123687
|
+
\u2705 \u7CFB\u5217 ${opts.id} \u5DF2\u66F4\u65B0
|
|
123688
|
+
`);
|
|
123689
|
+
}
|
|
123690
|
+
async function runMetaAdCampaignStatus(opts) {
|
|
123691
|
+
const { digits } = resolveMetaAccountId(opts.account);
|
|
123692
|
+
if (!opts.status?.trim()) {
|
|
123693
|
+
console.error("\n\u274C --status \u5FC5\u586B\uFF08ACTIVE / PAUSED / DELETED / ARCHIVED\uFF09\n");
|
|
123694
|
+
process.exit(1);
|
|
123695
|
+
}
|
|
123696
|
+
const { config, facebookApiUrl } = metaAdContext(opts.token);
|
|
123697
|
+
const data = await metaAdPost(
|
|
123698
|
+
adsUrl(facebookApiUrl, digits, `/campaigns/${encodeURIComponent(opts.id)}/status`),
|
|
123699
|
+
config,
|
|
123700
|
+
buildStatusBody(opts.status),
|
|
123701
|
+
opts.verbose,
|
|
123702
|
+
"\u6539\u7CFB\u5217\u72B6\u6001\u5931\u8D25"
|
|
123703
|
+
);
|
|
123704
|
+
const payload = { id: extractCreatedId(data) ?? opts.id, status: opts.status.trim(), ...data };
|
|
123705
|
+
if (await emitMetaJson(opts, "meta-ad-campaign-status", "meta-ad campaign-status", payload, digits)) {
|
|
123706
|
+
return;
|
|
123707
|
+
}
|
|
123708
|
+
console.log(`
|
|
123709
|
+
\u2705 \u7CFB\u5217 ${opts.id} \u72B6\u6001\u5DF2\u8BBE\u4E3A ${opts.status.trim()}
|
|
123710
|
+
`);
|
|
123711
|
+
}
|
|
123712
|
+
|
|
123713
|
+
// src/commands/meta-ad/adset.ts
|
|
123714
|
+
init_cli_table();
|
|
123715
|
+
function parseGenders(raw) {
|
|
123716
|
+
if (!raw?.trim()) return void 0;
|
|
123717
|
+
const nums = raw.split(",").map((s) => Number(s.trim()));
|
|
123718
|
+
if (nums.some((n) => n !== 1 && n !== 2)) {
|
|
123719
|
+
throw new Error("--genders \u53EA\u5141\u8BB8 1\uFF08\u7537\uFF09/ 2\uFF08\u5973\uFF09\uFF0C\u9017\u53F7\u5206\u9694");
|
|
123720
|
+
}
|
|
123721
|
+
return nums;
|
|
123722
|
+
}
|
|
123723
|
+
function assertCountries(raw) {
|
|
123724
|
+
const countries = parseCommaList(raw);
|
|
123725
|
+
if (countries.length === 0) {
|
|
123726
|
+
throw new Error("--countries \u81F3\u5C11\u4E00\u4E2A ISO \u4E24\u4F4D\u56FD\u5BB6\uFF08\u5982 US,CN\uFF09");
|
|
123727
|
+
}
|
|
123728
|
+
for (const c of countries) {
|
|
123729
|
+
if (!isIsoCountry(c)) throw new Error(`\u975E\u6CD5\u56FD\u5BB6\u7801\uFF1A${c}`);
|
|
123730
|
+
}
|
|
123731
|
+
return countries;
|
|
123732
|
+
}
|
|
123733
|
+
async function runMetaAdAdsetGet(opts) {
|
|
123734
|
+
const { digits } = resolveMetaAccountId(opts.account);
|
|
123735
|
+
const { config, facebookApiUrl } = metaAdContext(opts.token);
|
|
123736
|
+
const [raw, currency] = await Promise.all([
|
|
123737
|
+
metaAdGet(
|
|
123738
|
+
adsUrl(facebookApiUrl, digits, `/adsets/${encodeURIComponent(opts.id)}`),
|
|
123739
|
+
config,
|
|
123740
|
+
opts.verbose,
|
|
123741
|
+
"\u8BFB\u53D6\u5E7F\u544A\u7EC4\u5931\u8D25"
|
|
123742
|
+
),
|
|
123743
|
+
fetchAccountCurrency(facebookApiUrl, digits, config, opts.verbose)
|
|
123744
|
+
]);
|
|
123745
|
+
const payload = withMoneyDisplay(raw, currency);
|
|
123746
|
+
if (await emitMetaJson(opts, "meta-ad-adset", "meta-ad adset", payload, digits)) return;
|
|
123747
|
+
console.log(`
|
|
123748
|
+
\u5E7F\u544A\u7EC4 ${opts.id}\uFF08${currency}\uFF09
|
|
123749
|
+
`);
|
|
123750
|
+
printCliTable(
|
|
123751
|
+
[
|
|
123752
|
+
{
|
|
123753
|
+
id: String(payload.id ?? opts.id),
|
|
123754
|
+
name: String(payload.name ?? "\u2014"),
|
|
123755
|
+
status: String(payload.status ?? "\u2014"),
|
|
123756
|
+
goal: String(payload.optimization_goal ?? "\u2014"),
|
|
123757
|
+
budget: formatYuanDisplay(payload.daily_budgetDisplay)
|
|
123758
|
+
}
|
|
123759
|
+
],
|
|
123760
|
+
[
|
|
123761
|
+
{ key: "id", header: "ID" },
|
|
123762
|
+
{ key: "name", header: "\u540D\u79F0" },
|
|
123763
|
+
{ key: "status", header: "\u72B6\u6001" },
|
|
123764
|
+
{ key: "goal", header: "\u4F18\u5316\u76EE\u6807" },
|
|
123765
|
+
{ key: "budget", header: "\u65E5\u9884\u7B97(\u5143)" }
|
|
123766
|
+
]
|
|
123767
|
+
);
|
|
123768
|
+
console.log();
|
|
123769
|
+
}
|
|
123770
|
+
async function runMetaAdAdsetCreate(opts) {
|
|
123771
|
+
const { digits } = resolveMetaAccountId(opts.account);
|
|
123772
|
+
if (!opts.name?.trim() || !opts.campaignId?.trim() || !opts.pageId?.trim()) {
|
|
123773
|
+
console.error("\n\u274C --name / --campaign-id / --page-id \u5FC5\u586B\n");
|
|
123774
|
+
process.exit(1);
|
|
123775
|
+
}
|
|
123776
|
+
let countries;
|
|
123777
|
+
let genders;
|
|
123778
|
+
try {
|
|
123779
|
+
countries = assertCountries(opts.countries);
|
|
123780
|
+
genders = parseGenders(opts.genders);
|
|
123781
|
+
} catch (err) {
|
|
123782
|
+
console.error(`
|
|
123783
|
+
\u274C ${err instanceof Error ? err.message : String(err)}
|
|
123784
|
+
`);
|
|
123785
|
+
process.exit(1);
|
|
123786
|
+
}
|
|
123787
|
+
const daily = parseOptionalNumber(opts.dailyBudget);
|
|
123788
|
+
const lifetime = parseOptionalNumber(opts.lifetimeBudget);
|
|
123789
|
+
if (daily != null && lifetime != null) {
|
|
123790
|
+
console.error("\n\u274C --daily-budget \u4E0E --lifetime-budget \u4E0D\u80FD\u540C\u65F6\u4F20\n");
|
|
123791
|
+
process.exit(1);
|
|
123792
|
+
}
|
|
123793
|
+
if (opts.optimizationGoal) {
|
|
123794
|
+
const g = opts.optimizationGoal.trim().toUpperCase();
|
|
123795
|
+
if (!META_OPTIMIZATION_GOALS.includes(g)) {
|
|
123796
|
+
console.error("\n\u274C --optimization-goal \u53EA\u5141\u8BB8 LEAD_GENERATION / QUALITY_LEAD\n");
|
|
123797
|
+
process.exit(1);
|
|
123798
|
+
}
|
|
123799
|
+
}
|
|
123800
|
+
const { config, facebookApiUrl } = metaAdContext(opts.token);
|
|
123801
|
+
const currency = await fetchAccountCurrency(facebookApiUrl, digits, config, opts.verbose);
|
|
123802
|
+
const adset = {
|
|
123803
|
+
name: opts.name.trim(),
|
|
123804
|
+
countries,
|
|
123805
|
+
ageMin: parseOptionalNumber(opts.ageMin),
|
|
123806
|
+
ageMax: parseOptionalNumber(opts.ageMax),
|
|
123807
|
+
genders,
|
|
123808
|
+
optimizationGoal: opts.optimizationGoal?.trim().toUpperCase(),
|
|
123809
|
+
bidStrategy: opts.bidStrategy,
|
|
123810
|
+
bidAmount: parseOptionalNumber(opts.bidAmount),
|
|
123811
|
+
startTime: opts.startTime,
|
|
123812
|
+
endTime: opts.endTime,
|
|
123813
|
+
status: opts.status
|
|
123814
|
+
};
|
|
123815
|
+
let body;
|
|
123816
|
+
try {
|
|
123817
|
+
body = buildCreateAdSetBody({
|
|
123818
|
+
adset,
|
|
123819
|
+
campaignId: opts.campaignId.trim(),
|
|
123820
|
+
pageId: opts.pageId.trim(),
|
|
123821
|
+
dailyBudgetMinor: daily != null ? toMinorUnitString(daily, currency) : void 0,
|
|
123822
|
+
lifetimeBudgetMinor: lifetime != null ? toMinorUnitString(lifetime, currency) : void 0,
|
|
123823
|
+
bidAmountMinor: adset.bidAmount != null ? toMinorUnitString(adset.bidAmount, currency) : void 0
|
|
123824
|
+
});
|
|
123825
|
+
} catch (err) {
|
|
123826
|
+
console.error(`
|
|
123827
|
+
\u274C ${err instanceof Error ? err.message : String(err)}
|
|
123828
|
+
`);
|
|
123829
|
+
process.exit(1);
|
|
123830
|
+
}
|
|
123831
|
+
const data = await metaAdPost(
|
|
123832
|
+
adsUrl(facebookApiUrl, digits, "/adsets"),
|
|
123833
|
+
config,
|
|
123834
|
+
body,
|
|
123835
|
+
opts.verbose,
|
|
123836
|
+
"\u5EFA\u5E7F\u544A\u7EC4\u5931\u8D25"
|
|
123837
|
+
);
|
|
123838
|
+
const id = extractCreatedId(data);
|
|
123839
|
+
const payload = { ...data, id, currency };
|
|
123840
|
+
if (await emitMetaJson(opts, "meta-ad-adset-create", "meta-ad adset-create", payload, digits)) {
|
|
123841
|
+
return;
|
|
123842
|
+
}
|
|
123843
|
+
console.log(`
|
|
123844
|
+
\u2705 \u5E7F\u544A\u7EC4\u5DF2\u521B\u5EFA\uFF1A${id ?? JSON.stringify(data)}
|
|
123845
|
+
`);
|
|
123846
|
+
}
|
|
123847
|
+
async function runMetaAdAdsetEdit(opts) {
|
|
123848
|
+
const { digits } = resolveMetaAccountId(opts.account);
|
|
123849
|
+
const daily = parseOptionalNumber(opts.dailyBudget);
|
|
123850
|
+
const lifetime = parseOptionalNumber(opts.lifetimeBudget);
|
|
123851
|
+
if (daily != null && lifetime != null) {
|
|
123852
|
+
console.error("\n\u274C --daily-budget \u4E0E --lifetime-budget \u4E0D\u80FD\u540C\u65F6\u4F20\n");
|
|
123853
|
+
process.exit(1);
|
|
123854
|
+
}
|
|
123855
|
+
let countries;
|
|
123856
|
+
let genders;
|
|
123857
|
+
try {
|
|
123858
|
+
if (opts.countries != null) countries = assertCountries(opts.countries);
|
|
123859
|
+
genders = parseGenders(opts.genders);
|
|
123860
|
+
} catch (err) {
|
|
123861
|
+
console.error(`
|
|
123862
|
+
\u274C ${err instanceof Error ? err.message : String(err)}
|
|
123863
|
+
`);
|
|
123864
|
+
process.exit(1);
|
|
123865
|
+
}
|
|
123866
|
+
if (opts.optimizationGoal) {
|
|
123867
|
+
const g = opts.optimizationGoal.trim().toUpperCase();
|
|
123868
|
+
if (!META_OPTIMIZATION_GOALS.includes(g)) {
|
|
123869
|
+
console.error("\n\u274C --optimization-goal \u53EA\u5141\u8BB8 LEAD_GENERATION / QUALITY_LEAD\n");
|
|
123870
|
+
process.exit(1);
|
|
123871
|
+
}
|
|
123872
|
+
}
|
|
123873
|
+
const { config, facebookApiUrl } = metaAdContext(opts.token);
|
|
123874
|
+
const currency = await fetchAccountCurrency(facebookApiUrl, digits, config, opts.verbose);
|
|
123875
|
+
const bidAmount = parseOptionalNumber(opts.bidAmount);
|
|
123876
|
+
let body;
|
|
123877
|
+
try {
|
|
123878
|
+
body = buildUpdateAdSetBody({
|
|
123879
|
+
name: opts.name?.trim() || void 0,
|
|
123880
|
+
dailyBudgetMinor: daily != null ? toMinorUnitString(daily, currency) : void 0,
|
|
123881
|
+
lifetimeBudgetMinor: lifetime != null ? toMinorUnitString(lifetime, currency) : void 0,
|
|
123882
|
+
countries,
|
|
123883
|
+
ageMin: parseOptionalNumber(opts.ageMin),
|
|
123884
|
+
ageMax: parseOptionalNumber(opts.ageMax),
|
|
123885
|
+
genders,
|
|
123886
|
+
optimizationGoal: opts.optimizationGoal?.trim().toUpperCase(),
|
|
123887
|
+
bidStrategy: opts.bidStrategy,
|
|
123888
|
+
bidAmountMinor: bidAmount != null ? toMinorUnitString(bidAmount, currency) : void 0,
|
|
123889
|
+
startTime: opts.startTime,
|
|
123890
|
+
endTime: opts.endTime,
|
|
123891
|
+
status: opts.status
|
|
123892
|
+
});
|
|
123893
|
+
} catch (err) {
|
|
123894
|
+
console.error(`
|
|
123895
|
+
\u274C ${err instanceof Error ? err.message : String(err)}
|
|
123896
|
+
`);
|
|
123897
|
+
process.exit(1);
|
|
123898
|
+
}
|
|
123899
|
+
if (Object.keys(body).length === 0) {
|
|
123900
|
+
console.error("\n\u274C \u81F3\u5C11\u4F20\u4E00\u4E2A\u53EF\u6539\u5B57\u6BB5\n");
|
|
123901
|
+
process.exit(1);
|
|
123902
|
+
}
|
|
123903
|
+
const data = await metaAdPost(
|
|
123904
|
+
adsUrl(facebookApiUrl, digits, `/adsets/${encodeURIComponent(opts.id)}`),
|
|
123905
|
+
config,
|
|
123906
|
+
body,
|
|
123907
|
+
opts.verbose,
|
|
123908
|
+
"\u6539\u5E7F\u544A\u7EC4\u5931\u8D25"
|
|
123909
|
+
);
|
|
123910
|
+
const payload = { id: extractCreatedId(data) ?? opts.id, ...data };
|
|
123911
|
+
if (await emitMetaJson(opts, "meta-ad-adset-edit", "meta-ad adset-edit", payload, digits)) return;
|
|
123912
|
+
console.log(`
|
|
123913
|
+
\u2705 \u5E7F\u544A\u7EC4 ${opts.id} \u5DF2\u66F4\u65B0
|
|
123914
|
+
`);
|
|
123915
|
+
}
|
|
123916
|
+
async function runMetaAdAdsetStatus(opts) {
|
|
123917
|
+
const { digits } = resolveMetaAccountId(opts.account);
|
|
123918
|
+
if (!opts.status?.trim()) {
|
|
123919
|
+
console.error("\n\u274C --status \u5FC5\u586B\uFF08ACTIVE / PAUSED / DELETED / ARCHIVED\uFF09\n");
|
|
123920
|
+
process.exit(1);
|
|
123921
|
+
}
|
|
123922
|
+
const { config, facebookApiUrl } = metaAdContext(opts.token);
|
|
123923
|
+
const data = await metaAdPost(
|
|
123924
|
+
adsUrl(facebookApiUrl, digits, `/adsets/${encodeURIComponent(opts.id)}/status`),
|
|
123925
|
+
config,
|
|
123926
|
+
buildStatusBody(opts.status),
|
|
123927
|
+
opts.verbose,
|
|
123928
|
+
"\u6539\u5E7F\u544A\u7EC4\u72B6\u6001\u5931\u8D25"
|
|
123929
|
+
);
|
|
123930
|
+
const payload = { id: extractCreatedId(data) ?? opts.id, status: opts.status.trim(), ...data };
|
|
123931
|
+
if (await emitMetaJson(opts, "meta-ad-adset-status", "meta-ad adset-status", payload, digits)) {
|
|
123932
|
+
return;
|
|
123933
|
+
}
|
|
123934
|
+
console.log(`
|
|
123935
|
+
\u2705 \u5E7F\u544A\u7EC4 ${opts.id} \u72B6\u6001\u5DF2\u8BBE\u4E3A ${opts.status.trim()}
|
|
123936
|
+
`);
|
|
123937
|
+
}
|
|
123938
|
+
|
|
123939
|
+
// src/commands/meta-ad/creative.ts
|
|
123940
|
+
async function runMetaAdCreativeCreate(opts) {
|
|
123941
|
+
const { digits } = resolveMetaAccountId(opts.account);
|
|
123942
|
+
if (!opts.pageId?.trim() || !opts.name?.trim() || !opts.message?.trim() || !opts.link?.trim() || !opts.imageHash?.trim() || !opts.formId?.trim()) {
|
|
123943
|
+
console.error("\n\u274C --page-id / --name / --message / --link / --image-hash / --form-id \u5FC5\u586B\n");
|
|
123944
|
+
process.exit(1);
|
|
123945
|
+
}
|
|
123946
|
+
if (!isHttpUrl(opts.link.trim())) {
|
|
123947
|
+
console.error("\n\u274C --link \u987B\u4E3A http/https \u7EDD\u5BF9\u5730\u5740\n");
|
|
123948
|
+
process.exit(1);
|
|
123949
|
+
}
|
|
123950
|
+
const { config, facebookApiUrl } = metaAdContext(opts.token);
|
|
123951
|
+
const body = buildCreateCreativeBody({
|
|
123952
|
+
creative: {
|
|
123953
|
+
name: opts.name.trim(),
|
|
123954
|
+
message: opts.message,
|
|
123955
|
+
link: opts.link.trim(),
|
|
123956
|
+
headline: opts.headline,
|
|
123957
|
+
ctaType: opts.ctaType
|
|
123958
|
+
},
|
|
123959
|
+
pageId: opts.pageId.trim(),
|
|
123960
|
+
formId: opts.formId.trim(),
|
|
123961
|
+
imageHash: opts.imageHash.trim()
|
|
123962
|
+
});
|
|
123963
|
+
const data = await metaAdPost(
|
|
123964
|
+
adsUrl(facebookApiUrl, digits, "/adcreatives"),
|
|
123965
|
+
config,
|
|
123966
|
+
body,
|
|
123967
|
+
opts.verbose,
|
|
123968
|
+
"\u5EFA\u521B\u610F\u5931\u8D25"
|
|
123969
|
+
);
|
|
123970
|
+
const id = extractCreatedId(data);
|
|
123971
|
+
const payload = { ...data, id };
|
|
123972
|
+
if (await emitMetaJson(opts, "meta-ad-creative-create", "meta-ad creative-create", payload, digits)) {
|
|
123973
|
+
return;
|
|
123974
|
+
}
|
|
123975
|
+
console.log(
|
|
123976
|
+
`
|
|
123977
|
+
\u2705 \u521B\u610F\u5DF2\u521B\u5EFA\uFF1A${id ?? JSON.stringify(data)}\uFF08\u65E0\u5C31\u5730\u6539\u53E3\uFF0C\u6362\u56FE/\u6587\u6848/\u8868\u5355\u987B\u65B0\u5EFA\uFF09
|
|
123978
|
+
`
|
|
123979
|
+
);
|
|
123980
|
+
}
|
|
123981
|
+
|
|
123982
|
+
// src/commands/meta-ad/ad.ts
|
|
123983
|
+
init_cli_table();
|
|
123984
|
+
async function runMetaAdGet(opts) {
|
|
123985
|
+
const { digits } = resolveMetaAccountId(opts.account);
|
|
123986
|
+
const { config, facebookApiUrl } = metaAdContext(opts.token);
|
|
123987
|
+
const raw = await metaAdGet(
|
|
123988
|
+
adsUrl(facebookApiUrl, digits, `/ads/${encodeURIComponent(opts.id)}`),
|
|
123989
|
+
config,
|
|
123990
|
+
opts.verbose,
|
|
123991
|
+
"\u8BFB\u53D6\u5E7F\u544A\u5931\u8D25"
|
|
123992
|
+
);
|
|
123993
|
+
if (await emitMetaJson(opts, "meta-ad-ad", "meta-ad ad", raw, digits)) return;
|
|
123994
|
+
const creative = raw.creative ?? {};
|
|
123995
|
+
console.log(`
|
|
123996
|
+
\u5E7F\u544A ${opts.id}
|
|
123997
|
+
`);
|
|
123998
|
+
printCliTable(
|
|
123999
|
+
[
|
|
124000
|
+
{
|
|
124001
|
+
id: String(raw.id ?? opts.id),
|
|
124002
|
+
name: String(raw.name ?? "\u2014"),
|
|
124003
|
+
status: String(raw.status ?? "\u2014"),
|
|
124004
|
+
adset: String(raw.adset_id ?? "\u2014"),
|
|
124005
|
+
creative: String(creative.id ?? "\u2014")
|
|
124006
|
+
}
|
|
124007
|
+
],
|
|
124008
|
+
[
|
|
124009
|
+
{ key: "id", header: "ID" },
|
|
124010
|
+
{ key: "name", header: "\u540D\u79F0" },
|
|
124011
|
+
{ key: "status", header: "\u72B6\u6001" },
|
|
124012
|
+
{ key: "adset", header: "\u5E7F\u544A\u7EC4" },
|
|
124013
|
+
{ key: "creative", header: "\u521B\u610F" }
|
|
124014
|
+
]
|
|
124015
|
+
);
|
|
124016
|
+
console.log();
|
|
124017
|
+
}
|
|
124018
|
+
async function runMetaAdCreate2(opts) {
|
|
124019
|
+
const { digits } = resolveMetaAccountId(opts.account);
|
|
124020
|
+
if (!opts.name?.trim() || !opts.adsetId?.trim() || !opts.creativeId?.trim()) {
|
|
124021
|
+
console.error("\n\u274C --name / --adset-id / --creative-id \u5FC5\u586B\n");
|
|
124022
|
+
process.exit(1);
|
|
124023
|
+
}
|
|
124024
|
+
const { config, facebookApiUrl } = metaAdContext(opts.token);
|
|
124025
|
+
const body = buildCreateAdBody({
|
|
124026
|
+
name: opts.name.trim(),
|
|
124027
|
+
adsetId: opts.adsetId.trim(),
|
|
124028
|
+
creativeId: opts.creativeId.trim(),
|
|
124029
|
+
status: opts.status
|
|
124030
|
+
});
|
|
124031
|
+
const data = await metaAdPost(
|
|
124032
|
+
adsUrl(facebookApiUrl, digits, "/ads"),
|
|
124033
|
+
config,
|
|
124034
|
+
body,
|
|
124035
|
+
opts.verbose,
|
|
124036
|
+
"\u5EFA\u5E7F\u544A\u5931\u8D25"
|
|
124037
|
+
);
|
|
124038
|
+
const id = extractCreatedId(data);
|
|
124039
|
+
const payload = { ...data, id };
|
|
124040
|
+
if (await emitMetaJson(opts, "meta-ad-ad-create", "meta-ad ad-create", payload, digits)) return;
|
|
124041
|
+
console.log(`
|
|
124042
|
+
\u2705 \u5E7F\u544A\u5DF2\u521B\u5EFA\uFF1A${id ?? JSON.stringify(data)}
|
|
124043
|
+
`);
|
|
124044
|
+
}
|
|
124045
|
+
async function runMetaAdEdit(opts) {
|
|
124046
|
+
const { digits } = resolveMetaAccountId(opts.account);
|
|
124047
|
+
const body = buildUpdateAdBody({
|
|
124048
|
+
name: opts.name?.trim() || void 0,
|
|
124049
|
+
creativeId: opts.creativeId?.trim() || void 0,
|
|
124050
|
+
status: opts.status
|
|
124051
|
+
});
|
|
124052
|
+
if (Object.keys(body).length === 0) {
|
|
124053
|
+
console.error("\n\u274C \u81F3\u5C11\u4F20 --name / --creative-id / --status \u4E4B\u4E00\n");
|
|
124054
|
+
process.exit(1);
|
|
124055
|
+
}
|
|
124056
|
+
const { config, facebookApiUrl } = metaAdContext(opts.token);
|
|
124057
|
+
const data = await metaAdPost(
|
|
124058
|
+
adsUrl(facebookApiUrl, digits, `/ads/${encodeURIComponent(opts.id)}`),
|
|
124059
|
+
config,
|
|
124060
|
+
body,
|
|
124061
|
+
opts.verbose,
|
|
124062
|
+
"\u6539\u5E7F\u544A\u5931\u8D25"
|
|
124063
|
+
);
|
|
124064
|
+
const payload = { id: extractCreatedId(data) ?? opts.id, ...data };
|
|
124065
|
+
if (await emitMetaJson(opts, "meta-ad-ad-edit", "meta-ad ad-edit", payload, digits)) return;
|
|
124066
|
+
console.log(`
|
|
124067
|
+
\u2705 \u5E7F\u544A ${opts.id} \u5DF2\u66F4\u65B0
|
|
124068
|
+
`);
|
|
124069
|
+
}
|
|
124070
|
+
async function runMetaAdStatus(opts) {
|
|
124071
|
+
const { digits } = resolveMetaAccountId(opts.account);
|
|
124072
|
+
if (!opts.status?.trim()) {
|
|
124073
|
+
console.error("\n\u274C --status \u5FC5\u586B\uFF08ACTIVE / PAUSED / DELETED / ARCHIVED\uFF09\n");
|
|
124074
|
+
process.exit(1);
|
|
124075
|
+
}
|
|
124076
|
+
const { config, facebookApiUrl } = metaAdContext(opts.token);
|
|
124077
|
+
const data = await metaAdPost(
|
|
124078
|
+
adsUrl(facebookApiUrl, digits, `/ads/${encodeURIComponent(opts.id)}/status`),
|
|
124079
|
+
config,
|
|
124080
|
+
buildStatusBody(opts.status),
|
|
124081
|
+
opts.verbose,
|
|
124082
|
+
"\u6539\u5E7F\u544A\u72B6\u6001\u5931\u8D25"
|
|
124083
|
+
);
|
|
124084
|
+
const payload = { id: extractCreatedId(data) ?? opts.id, status: opts.status.trim(), ...data };
|
|
124085
|
+
if (await emitMetaJson(opts, "meta-ad-ad-status", "meta-ad ad-status", payload, digits)) return;
|
|
124086
|
+
console.log(`
|
|
124087
|
+
\u2705 \u5E7F\u544A ${opts.id} \u72B6\u6001\u5DF2\u8BBE\u4E3A ${opts.status.trim()}
|
|
124088
|
+
`);
|
|
124089
|
+
}
|
|
124090
|
+
|
|
124091
|
+
// src/commands/meta-ad/_register.ts
|
|
124092
|
+
var JSON_OUT_HELP = "\u843D\u76D8\uFF08\u76EE\u5F55\u6216 *.json \u6587\u4EF6\u8DEF\u5F84\uFF09\u5E76\u66F4\u65B0 cli-manifest[-<\u67E5\u8BE2id>].json\uFF1Bstdout \u4E00\u884C\u6458\u8981";
|
|
124093
|
+
function addCommon(cmd) {
|
|
124094
|
+
return cmd.option("-t, --token <token>", "Auth Token").option("--json-out <path>", JSON_OUT_HELP, void 0).option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false);
|
|
124095
|
+
}
|
|
124096
|
+
function addAccount(cmd) {
|
|
124097
|
+
return cmd.requiredOption(
|
|
124098
|
+
"-a, --account <id>",
|
|
124099
|
+
"Meta \u5E7F\u544A\u8D26\u6237 mediaCustomerId\uFF08\u6570\u5B57\u6216 act_<\u6570\u5B57>\uFF09"
|
|
124100
|
+
);
|
|
124101
|
+
}
|
|
124102
|
+
function register22(program2) {
|
|
124103
|
+
const meta = program2.command("meta-ad").description(
|
|
124104
|
+
"Meta / Facebook Instant Form \u7EBF\u7D22\u5E7F\u544A\uFF08FacebookAPI\uFF0C\u5730\u5740\u4ECE TSO API \u63A8\u5BFC\uFF09\n \u4EC5 OUTCOME_LEADS\uFF1B\u4E3B\u8DEF\u5F84\uFF1Avalidate \u2192 create\uFF1B\u5B58\u91CF\u7528\u539F\u8BED\u7EED\u8DD1 / \u6539\u72B6\u6001"
|
|
124105
|
+
);
|
|
124106
|
+
addCommon(
|
|
124107
|
+
meta.command("validate").description("\u672C\u5730\u6821\u9A8C Lead Ad JSON\uFF08\u4E0D\u8C03\u7F51\u5173\uFF09").requiredOption(
|
|
124108
|
+
"--config-file <path>",
|
|
124109
|
+
"JSON \u914D\u7F6E\uFF08\u89C1 assets/meta-lead-create-template.json\uFF09"
|
|
124110
|
+
)
|
|
124111
|
+
).action(async (opts) => {
|
|
124112
|
+
await runMetaAdValidate(opts);
|
|
124113
|
+
});
|
|
124114
|
+
addCommon(
|
|
124115
|
+
meta.command("create").description("\u6309 JSON \u7F16\u6392\uFF1A\u8868\u5355\u2192\u56FE\u2192\u7CFB\u5217\u2192\u7EC4\u2192\u521B\u610F\u2192\u5E7F\u544A\uFF08\u9ED8\u8BA4 PAUSED\uFF1B\u5931\u8D25\u4E0D\u56DE\u6EDA\uFF09").requiredOption("--config-file <path>", "JSON \u914D\u7F6E\uFF08account \u5199\u5728\u6587\u4EF6\u91CC\uFF09")
|
|
124116
|
+
).action(
|
|
124117
|
+
async (opts) => {
|
|
124118
|
+
await runMetaAdCreate(opts);
|
|
124119
|
+
}
|
|
124120
|
+
);
|
|
124121
|
+
addCommon(addAccount(meta.command("account").description("\u8BFB\u5E7F\u544A\u8D26\u6237\uFF08\u4F59\u989D/\u82B1\u8D39/\u5E01\u79CD\uFF09"))).action(
|
|
124122
|
+
async (opts) => {
|
|
124123
|
+
await runMetaAdAccount(opts);
|
|
124124
|
+
}
|
|
124125
|
+
);
|
|
124126
|
+
addCommon(
|
|
124127
|
+
addAccount(meta.command("pages").description("\u5217 Ads Token \u4E0B\u53EF\u6295\u653E\u4E3B\u9875\uFF08\u4E0D\u542B Page Token\uFF09"))
|
|
124128
|
+
).action(
|
|
124129
|
+
async (opts) => {
|
|
124130
|
+
await runMetaAdPages(opts);
|
|
124131
|
+
}
|
|
124132
|
+
);
|
|
124133
|
+
addCommon(
|
|
124134
|
+
addAccount(
|
|
124135
|
+
meta.command("forms").description("\u5217\u67D0\u4E3B\u9875 Instant Form").requiredOption("--page-id <id>", "Facebook \u4E3B\u9875 ID").option("--limit <n>", "\u5206\u9875\u5927\u5C0F").option("--after <cursor>", "\u5411\u540E\u7FFB\u9875").option("--before <cursor>", "\u5411\u524D\u7FFB\u9875")
|
|
124136
|
+
)
|
|
124137
|
+
).action(
|
|
124138
|
+
async (opts) => {
|
|
124139
|
+
await runMetaAdForms(opts);
|
|
124140
|
+
}
|
|
124141
|
+
);
|
|
124142
|
+
addCommon(
|
|
124143
|
+
addAccount(
|
|
124144
|
+
meta.command("form-create").description("\u65B0\u5EFA Instant Form\uFF08\u5EFA\u6210\u540E\u51E0\u4E4E\u4E0D\u53EF\u6539\uFF09").requiredOption("--page-id <id>", "Facebook \u4E3B\u9875 ID").requiredOption("--config-file <path>", "\u8868\u5355 JSON\uFF0C\u6216\u542B form \u6BB5\u7684 create JSON")
|
|
124145
|
+
)
|
|
124146
|
+
).action(
|
|
124147
|
+
async (opts) => {
|
|
124148
|
+
await runMetaAdFormCreate(opts);
|
|
124149
|
+
}
|
|
124150
|
+
);
|
|
124151
|
+
addCommon(
|
|
124152
|
+
addAccount(
|
|
124153
|
+
meta.command("image-upload").description("\u4E0A\u4F20\u5E7F\u544A\u56FE\uFF0C\u8FD4\u56DE hash\uFF08\u521B\u610F\u5FC5\u987B\u7528 hash\uFF09").option("--name <name>", "\u6587\u4EF6\u540D").option("--image-url <url>", "\u516C\u7F51\u56FE\u7247 URL").option("--image-path <path>", "\u672C\u5730\u56FE\u7247\u8DEF\u5F84")
|
|
124154
|
+
)
|
|
124155
|
+
).action(
|
|
124156
|
+
async (opts) => {
|
|
124157
|
+
await runMetaAdImageUpload(opts);
|
|
124158
|
+
}
|
|
124159
|
+
);
|
|
124160
|
+
addCommon(
|
|
124161
|
+
addAccount(
|
|
124162
|
+
meta.command("campaign").description("\u6309 ID \u8BFB\u7CFB\u5217\uFF08\u65E0\u5217\u8868\u53E3\uFF09").requiredOption("--id <campaignId>", "\u7CFB\u5217 ID")
|
|
124163
|
+
)
|
|
124164
|
+
).action(
|
|
124165
|
+
async (opts) => {
|
|
124166
|
+
await runMetaAdCampaignGet(opts);
|
|
124167
|
+
}
|
|
124168
|
+
);
|
|
124169
|
+
addCommon(
|
|
124170
|
+
addAccount(
|
|
124171
|
+
meta.command("campaign-create").description("\u5EFA\u7EBF\u7D22\u7CFB\u5217\uFF08objective \u56FA\u5B9A OUTCOME_LEADS\uFF1B\u9ED8\u8BA4 PAUSED\uFF09").requiredOption("--name <name>", "\u7CFB\u5217\u540D\u79F0").option("--daily-budget <amount>", "\u65E5\u9884\u7B97\uFF0C\u4E3B\u5E01\u79CD\u5143\uFF08CBO\uFF09", parseFloat).option("--lifetime-budget <amount>", "\u603B\u9884\u7B97\uFF0C\u4E3B\u5E01\u79CD\u5143", parseFloat).option("--special-ad-categories <list>", "\u7279\u6B8A\u7C7B\u76EE\uFF0C\u9017\u53F7\u5206\u9694").option("--status <status>", "ACTIVE | PAUSED")
|
|
124172
|
+
)
|
|
124173
|
+
).action(
|
|
124174
|
+
async (opts) => {
|
|
124175
|
+
await runMetaAdCampaignCreate(opts);
|
|
124176
|
+
}
|
|
124177
|
+
);
|
|
124178
|
+
addCommon(
|
|
124179
|
+
addAccount(
|
|
124180
|
+
meta.command("campaign-edit").description("\u6539\u7CFB\u5217\uFF08\u53EA\u4F20\u8981\u6539\u7684\u5B57\u6BB5\uFF1B\u4E0D\u80FD\u5207 CBO/ABO\uFF09").requiredOption("--id <campaignId>", "\u7CFB\u5217 ID").option("--name <name>", "\u65B0\u540D\u79F0").option("--daily-budget <amount>", "\u65E5\u9884\u7B97\uFF0C\u4E3B\u5E01\u79CD\u5143", parseFloat).option("--lifetime-budget <amount>", "\u603B\u9884\u7B97\uFF0C\u4E3B\u5E01\u79CD\u5143", parseFloat).option("--bid-strategy <s>", "\u51FA\u4EF7\u7B56\u7565").option("--spend-cap <amount>", "\u82B1\u8D39\u4E0A\u9650\uFF0C\u4E3B\u5E01\u79CD\u5143", parseFloat).option("--start-time <iso>", "\u5F00\u59CB\u65F6\u95F4").option("--stop-time <iso>", "\u7ED3\u675F\u65F6\u95F4").option("--special-ad-categories <list>", "\u7279\u6B8A\u7C7B\u76EE\uFF0C\u9017\u53F7\u5206\u9694").option("--status <status>", "\u72B6\u6001")
|
|
124181
|
+
)
|
|
124182
|
+
).action(
|
|
124183
|
+
async (opts) => {
|
|
124184
|
+
await runMetaAdCampaignEdit(opts);
|
|
124185
|
+
}
|
|
124186
|
+
);
|
|
124187
|
+
addCommon(
|
|
124188
|
+
addAccount(
|
|
124189
|
+
meta.command("campaign-status").description("\u6539\u7CFB\u5217\u72B6\u6001\uFF08ACTIVE / PAUSED / DELETED / ARCHIVED\uFF09").requiredOption("--id <campaignId>", "\u7CFB\u5217 ID").requiredOption("--status <status>", "\u76EE\u6807\u72B6\u6001")
|
|
124190
|
+
)
|
|
124191
|
+
).action(
|
|
124192
|
+
async (opts) => {
|
|
124193
|
+
await runMetaAdCampaignStatus(opts);
|
|
124194
|
+
}
|
|
124195
|
+
);
|
|
124196
|
+
addCommon(
|
|
124197
|
+
addAccount(
|
|
124198
|
+
meta.command("adset").description("\u6309 ID \u8BFB\u5E7F\u544A\u7EC4").requiredOption("--id <adSetId>", "\u5E7F\u544A\u7EC4 ID")
|
|
124199
|
+
)
|
|
124200
|
+
).action(
|
|
124201
|
+
async (opts) => {
|
|
124202
|
+
await runMetaAdAdsetGet(opts);
|
|
124203
|
+
}
|
|
124204
|
+
);
|
|
124205
|
+
addCommon(
|
|
124206
|
+
addAccount(
|
|
124207
|
+
meta.command("adset-create").description("\u5EFA\u7EBF\u7D22\u5E7F\u544A\u7EC4\uFF08destination=ON_AD\uFF1B\u9ED8\u8BA4 LEAD_GENERATION\uFF09").requiredOption("--campaign-id <id>", "\u7CFB\u5217 ID").requiredOption("--page-id <id>", "\u6295\u653E\u4E3B\u9875 ID").requiredOption("--name <name>", "\u7EC4\u540D\u79F0").requiredOption("--countries <list>", "ISO \u56FD\u5BB6\uFF0C\u9017\u53F7\u5206\u9694\uFF08\u5982 US,GB\uFF09").option("--daily-budget <amount>", "\u65E5\u9884\u7B97\uFF0C\u4E3B\u5E01\u79CD\u5143\uFF08ABO\uFF09", parseFloat).option("--lifetime-budget <amount>", "\u603B\u9884\u7B97\uFF0C\u4E3B\u5E01\u79CD\u5143", parseFloat).option("--age-min <n>", "\u6700\u5C0F\u5E74\u9F84", parseInt).option("--age-max <n>", "\u6700\u5927\u5E74\u9F84", parseInt).option("--genders <list>", "1=\u7537,2=\u5973\uFF0C\u9017\u53F7\u5206\u9694").option("--optimization-goal <g>", "LEAD_GENERATION | QUALITY_LEAD").option("--bid-strategy <s>", "\u51FA\u4EF7\u7B56\u7565").option("--bid-amount <amount>", "\u51FA\u4EF7\u4E0A\u9650\uFF0C\u4E3B\u5E01\u79CD\u5143", parseFloat).option("--start-time <iso>", "\u5F00\u59CB\u65F6\u95F4").option("--end-time <iso>", "\u7ED3\u675F\u65F6\u95F4").option("--status <status>", "ACTIVE | PAUSED")
|
|
124208
|
+
)
|
|
124209
|
+
).action(
|
|
124210
|
+
async (opts) => {
|
|
124211
|
+
await runMetaAdAdsetCreate(opts);
|
|
124212
|
+
}
|
|
124213
|
+
);
|
|
124214
|
+
addCommon(
|
|
124215
|
+
addAccount(
|
|
124216
|
+
meta.command("adset-edit").description("\u6539\u5E7F\u544A\u7EC4\uFF08targeting \u6574\u4EFD\u66FF\u6362\uFF0C\u53EA\u8BA4\u56FD\u5BB6/\u5E74\u9F84/\u6027\u522B\uFF09").requiredOption("--id <adSetId>", "\u5E7F\u544A\u7EC4 ID").option("--name <name>", "\u65B0\u540D\u79F0").option("--daily-budget <amount>", "\u65E5\u9884\u7B97\uFF0C\u4E3B\u5E01\u79CD\u5143", parseFloat).option("--lifetime-budget <amount>", "\u603B\u9884\u7B97\uFF0C\u4E3B\u5E01\u79CD\u5143", parseFloat).option("--countries <list>", "ISO \u56FD\u5BB6\uFF0C\u9017\u53F7\u5206\u9694").option("--age-min <n>", "\u6700\u5C0F\u5E74\u9F84", parseInt).option("--age-max <n>", "\u6700\u5927\u5E74\u9F84", parseInt).option("--genders <list>", "1=\u7537,2=\u5973").option("--optimization-goal <g>", "LEAD_GENERATION | QUALITY_LEAD").option("--bid-strategy <s>", "\u51FA\u4EF7\u7B56\u7565").option("--bid-amount <amount>", "\u51FA\u4EF7\u4E0A\u9650\uFF0C\u4E3B\u5E01\u79CD\u5143", parseFloat).option("--start-time <iso>", "\u5F00\u59CB\u65F6\u95F4").option("--end-time <iso>", "\u7ED3\u675F\u65F6\u95F4").option("--status <status>", "\u72B6\u6001")
|
|
124217
|
+
)
|
|
124218
|
+
).action(
|
|
124219
|
+
async (opts) => {
|
|
124220
|
+
await runMetaAdAdsetEdit(opts);
|
|
124221
|
+
}
|
|
124222
|
+
);
|
|
124223
|
+
addCommon(
|
|
124224
|
+
addAccount(
|
|
124225
|
+
meta.command("adset-status").description("\u6539\u5E7F\u544A\u7EC4\u72B6\u6001").requiredOption("--id <adSetId>", "\u5E7F\u544A\u7EC4 ID").requiredOption("--status <status>", "\u76EE\u6807\u72B6\u6001")
|
|
124226
|
+
)
|
|
124227
|
+
).action(
|
|
124228
|
+
async (opts) => {
|
|
124229
|
+
await runMetaAdAdsetStatus(opts);
|
|
124230
|
+
}
|
|
124231
|
+
);
|
|
124232
|
+
addCommon(
|
|
124233
|
+
addAccount(
|
|
124234
|
+
meta.command("creative-create").description("\u5EFA\u7EBF\u7D22\u521B\u610F\uFF08\u6302 Instant Form + image hash\uFF09").requiredOption("--page-id <id>", "\u4E3B\u9875 ID").requiredOption("--name <name>", "\u521B\u610F\u540D\u79F0").requiredOption("--message <text>", "\u6B63\u6587").requiredOption("--link <url>", "\u94FE\u63A5\uFF08\u5E38\u7528\u9690\u79C1\u653F\u7B56 URL\uFF09").requiredOption("--image-hash <hash>", "image-upload \u8FD4\u56DE\u7684 hash").requiredOption("--form-id <id>", "Instant Form ID").option("--headline <text>", "\u6807\u9898").option("--cta-type <type>", "CTA\uFF0C\u7F3A\u7701 SIGN_UP")
|
|
124235
|
+
)
|
|
124236
|
+
).action(
|
|
124237
|
+
async (opts) => {
|
|
124238
|
+
await runMetaAdCreativeCreate(opts);
|
|
124239
|
+
}
|
|
124240
|
+
);
|
|
124241
|
+
addCommon(
|
|
124242
|
+
addAccount(
|
|
124243
|
+
meta.command("ad").description("\u6309 ID \u8BFB\u5E7F\u544A").requiredOption("--id <adId>", "\u5E7F\u544A ID")
|
|
124244
|
+
)
|
|
124245
|
+
).action(
|
|
124246
|
+
async (opts) => {
|
|
124247
|
+
await runMetaAdGet(opts);
|
|
124248
|
+
}
|
|
124249
|
+
);
|
|
124250
|
+
addCommon(
|
|
124251
|
+
addAccount(
|
|
124252
|
+
meta.command("ad-create").description("\u5EFA\u5E7F\u544A\uFF08\u6302\u5DF2\u6709\u521B\u610F\uFF09").requiredOption("--adset-id <id>", "\u5E7F\u544A\u7EC4 ID").requiredOption("--creative-id <id>", "\u521B\u610F ID").requiredOption("--name <name>", "\u5E7F\u544A\u540D\u79F0").option("--status <status>", "ACTIVE | PAUSED")
|
|
124253
|
+
)
|
|
124254
|
+
).action(
|
|
124255
|
+
async (opts) => {
|
|
124256
|
+
await runMetaAdCreate2(opts);
|
|
124257
|
+
}
|
|
124258
|
+
);
|
|
124259
|
+
addCommon(
|
|
124260
|
+
addAccount(
|
|
124261
|
+
meta.command("ad-edit").description("\u6539\u5E7F\u544A\uFF08\u540D\u79F0 / \u6362\u521B\u610F / \u72B6\u6001\uFF09").requiredOption("--id <adId>", "\u5E7F\u544A ID").option("--name <name>", "\u65B0\u540D\u79F0").option("--creative-id <id>", "\u65B0\u521B\u610F ID").option("--status <status>", "\u72B6\u6001")
|
|
124262
|
+
)
|
|
124263
|
+
).action(
|
|
124264
|
+
async (opts) => {
|
|
124265
|
+
await runMetaAdEdit(opts);
|
|
124266
|
+
}
|
|
124267
|
+
);
|
|
124268
|
+
addCommon(
|
|
124269
|
+
addAccount(
|
|
124270
|
+
meta.command("ad-status").description("\u6539\u5E7F\u544A\u72B6\u6001").requiredOption("--id <adId>", "\u5E7F\u544A ID").requiredOption("--status <status>", "\u76EE\u6807\u72B6\u6001")
|
|
124271
|
+
)
|
|
124272
|
+
).action(
|
|
124273
|
+
async (opts) => {
|
|
124274
|
+
await runMetaAdStatus(opts);
|
|
124275
|
+
}
|
|
124276
|
+
);
|
|
124277
|
+
}
|
|
124278
|
+
|
|
122323
124279
|
// src/utils/cli-json-out-option.ts
|
|
122324
124280
|
var JSON_OUT_DESC = "\u843D\u76D8 JSON + outline + cli-manifest[-<\u67E5\u8BE2id>].json\uFF1Bstdout \u4E00\u884C\u6458\u8981 JSON\uFF08\u542B outlineFile\uFF09";
|
|
122325
124281
|
function attachJsonOutOption(cmd) {
|
|
@@ -122788,7 +124744,7 @@ function attachBaseCliOptions(cmd) {
|
|
|
122788
124744
|
function splitKeywordSeeds(raw) {
|
|
122789
124745
|
return raw.split(",").map((s) => s.trim()).filter(Boolean);
|
|
122790
124746
|
}
|
|
122791
|
-
function
|
|
124747
|
+
function register23(program2) {
|
|
122792
124748
|
const keywordCmd = program2.command("keyword").description(
|
|
122793
124749
|
"Google \u5173\u952E\u5B57\u63A8\u8350\uFF08\u53EF\u9009 -a \u8D26\u6237 ID \u6309\u8D26\u6237\u5E01\u79CD\u51FA\u4EF7\uFF1B\u5426\u5219\u5171\u4EAB MCC/USD\uFF1B\u53EF --geo \u9650\u5B9A\u5E02\u573A\uFF09"
|
|
122794
124750
|
);
|
|
@@ -123257,7 +125213,7 @@ async function runRagList(options) {
|
|
|
123257
125213
|
];
|
|
123258
125214
|
console.log(lines.join("\n"));
|
|
123259
125215
|
}
|
|
123260
|
-
function
|
|
125216
|
+
function register24(program2) {
|
|
123261
125217
|
const ragCmd = program2.command("rag").description(
|
|
123262
125218
|
"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"
|
|
123263
125219
|
);
|
|
@@ -124705,7 +126661,7 @@ async function runAccountBmBind(opts) {
|
|
|
124705
126661
|
}
|
|
124706
126662
|
|
|
124707
126663
|
// src/commands/account-manage-register.ts
|
|
124708
|
-
function
|
|
126664
|
+
function register25(program2) {
|
|
124709
126665
|
const accountCmd = program2.command("account").description("\u5E7F\u544A\u8D26\u6237\u7BA1\u7406\uFF1AOAuth \u6DFB\u52A0\u6388\u6743\u3001\u89E3\u9664\u5173\u8054\uFF08\u89E3\u7ED1\uFF09\u3001Google MCC \u7ED1\u5B9A/\u89E3\u7ED1\u3001\u8D26\u53F7\u5206\u4EAB");
|
|
124710
126666
|
accountCmd.command("list", { hidden: true }).description("\uFF08\u517C\u5BB9\u522B\u540D\uFF09\u540C list-accounts").option(
|
|
124711
126667
|
"-m, --media <type>",
|
|
@@ -126662,7 +128618,7 @@ async function runOpenAccountMeta(opts) {
|
|
|
126662
128618
|
}
|
|
126663
128619
|
|
|
126664
128620
|
// src/commands/open-account-register.ts
|
|
126665
|
-
function
|
|
128621
|
+
function register26(program2) {
|
|
126666
128622
|
const openAccountCmd = program2.command("open-account").description("\u5F00\u6237\u7533\u8BF7\uFF08Google / TikTok / Yandex / Bing / Kwai / Meta \u5F00\u6237\u94FE\u63A5\uFF09");
|
|
126667
128623
|
openAccountCmd.command("meta").description(
|
|
126668
128624
|
"\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"
|
|
@@ -126910,7 +128866,7 @@ function register25(program2) {
|
|
|
126910
128866
|
}
|
|
126911
128867
|
|
|
126912
128868
|
// src/commands/diagnostic.ts
|
|
126913
|
-
function
|
|
128869
|
+
function register27(program2) {
|
|
126914
128870
|
program2.command("diagnostic").description("\u8BCA\u65AD\u5165\u53E3\u8BF4\u660E\uFF1AGoogle \u5E7F\u544A\u8D26\u6237\u8BCA\u65AD\u9875 vs \u7F51\u7AD9\u8BCA\u65AD CLI").action(() => {
|
|
126915
128871
|
console.log("\n\u8BCA\u65AD\u80FD\u529B\u8BF4\u660E\n");
|
|
126916
128872
|
console.log(" \u3010Google \u5E7F\u544A\u8D26\u6237\u8BCA\u65AD\u3011");
|
|
@@ -127647,12 +129603,12 @@ var TEMPLATE_BASENAMES = {
|
|
|
127647
129603
|
html: "website-diagnosis-report.html",
|
|
127648
129604
|
runtime: "website-diagnosis-report.runtime.js"
|
|
127649
129605
|
};
|
|
127650
|
-
function resolveSkillTemplatePath(
|
|
129606
|
+
function resolveSkillTemplatePath(basename13) {
|
|
127651
129607
|
const dir = path19.dirname(fileURLToPath4(import.meta.url));
|
|
127652
129608
|
const candidates = [
|
|
127653
|
-
path19.join(dir, "skill", "report-templates",
|
|
127654
|
-
path19.join(dir, "skill", "siluzan-ads", "report-templates",
|
|
127655
|
-
path19.join(dir, "..", "..", "assets", "siluzan-ads", "report-templates",
|
|
129609
|
+
path19.join(dir, "skill", "report-templates", basename13),
|
|
129610
|
+
path19.join(dir, "skill", "siluzan-ads", "report-templates", basename13),
|
|
129611
|
+
path19.join(dir, "..", "..", "assets", "siluzan-ads", "report-templates", basename13)
|
|
127656
129612
|
];
|
|
127657
129613
|
for (const p of candidates) {
|
|
127658
129614
|
if (fs14.existsSync(p)) return p;
|
|
@@ -128526,12 +130482,12 @@ function buildGoogleAdsDiagnosisNarrativeChecks() {
|
|
|
128526
130482
|
return checks;
|
|
128527
130483
|
}
|
|
128528
130484
|
var GOOGLE_ADS_DIAGNOSIS_NARRATIVE_CHECKS = buildGoogleAdsDiagnosisNarrativeChecks();
|
|
128529
|
-
function resolveSkillTemplatePath2(
|
|
130485
|
+
function resolveSkillTemplatePath2(basename13) {
|
|
128530
130486
|
const dir = path21.dirname(fileURLToPath5(import.meta.url));
|
|
128531
130487
|
const candidates = [
|
|
128532
|
-
path21.join(dir, "skill", "report-templates",
|
|
128533
|
-
path21.join(dir, "skill", "siluzan-ads", "report-templates",
|
|
128534
|
-
path21.join(dir, "..", "..", "assets", "siluzan-ads", "report-templates",
|
|
130488
|
+
path21.join(dir, "skill", "report-templates", basename13),
|
|
130489
|
+
path21.join(dir, "skill", "siluzan-ads", "report-templates", basename13),
|
|
130490
|
+
path21.join(dir, "..", "..", "assets", "siluzan-ads", "report-templates", basename13)
|
|
128535
130491
|
];
|
|
128536
130492
|
for (const p of candidates) {
|
|
128537
130493
|
if (fs16.existsSync(p)) return p;
|
|
@@ -131262,12 +133218,12 @@ var TEMPLATE_BASENAMES2 = {
|
|
|
131262
133218
|
runtime: "meta-period-report.runtime.js"
|
|
131263
133219
|
};
|
|
131264
133220
|
var INJECT_MARKER2 = "window.__META_PERIOD_REPORT__ = window.__META_PERIOD_REPORT__ || null;";
|
|
131265
|
-
function resolveSkillTemplatePath4(
|
|
133221
|
+
function resolveSkillTemplatePath4(basename13) {
|
|
131266
133222
|
const dir = path33.dirname(fileURLToPath7(import.meta.url));
|
|
131267
133223
|
const candidates = [
|
|
131268
|
-
path33.join(dir, "skill", "report-templates",
|
|
131269
|
-
path33.join(dir, "skill", "siluzan-ads", "report-templates",
|
|
131270
|
-
path33.join(dir, "..", "..", "assets", "siluzan-ads", "report-templates",
|
|
133224
|
+
path33.join(dir, "skill", "report-templates", basename13),
|
|
133225
|
+
path33.join(dir, "skill", "siluzan-ads", "report-templates", basename13),
|
|
133226
|
+
path33.join(dir, "..", "..", "assets", "siluzan-ads", "report-templates", basename13)
|
|
131271
133227
|
];
|
|
131272
133228
|
for (const p of candidates) {
|
|
131273
133229
|
if (fs26.existsSync(p)) return p;
|
|
@@ -131714,12 +133670,12 @@ function formatMetaDiagnosisReportErrors(result) {
|
|
|
131714
133670
|
// src/commands/facebook-analysis/diagnosis-render-report.ts
|
|
131715
133671
|
var TEMPLATE_BASENAME2 = "meta-account-diagnosis-report.html";
|
|
131716
133672
|
var INJECT_MARKER3 = "window.__META_DIAGNOSIS_REPORT__ = window.__META_DIAGNOSIS_REPORT__ || null;";
|
|
131717
|
-
function resolveSkillTemplatePath5(
|
|
133673
|
+
function resolveSkillTemplatePath5(basename13) {
|
|
131718
133674
|
const dir = path35.dirname(fileURLToPath8(import.meta.url));
|
|
131719
133675
|
const candidates = [
|
|
131720
|
-
path35.join(dir, "skill", "report-templates",
|
|
131721
|
-
path35.join(dir, "skill", "siluzan-ads", "report-templates",
|
|
131722
|
-
path35.join(dir, "..", "..", "assets", "siluzan-ads", "report-templates",
|
|
133676
|
+
path35.join(dir, "skill", "report-templates", basename13),
|
|
133677
|
+
path35.join(dir, "skill", "siluzan-ads", "report-templates", basename13),
|
|
133678
|
+
path35.join(dir, "..", "..", "assets", "siluzan-ads", "report-templates", basename13)
|
|
131723
133679
|
];
|
|
131724
133680
|
for (const p of candidates) {
|
|
131725
133681
|
if (fs28.existsSync(p)) return p;
|
|
@@ -133242,12 +135198,12 @@ function formatBingPeriodReportErrors(result) {
|
|
|
133242
135198
|
// src/commands/bing-analysis/render-report.ts
|
|
133243
135199
|
var TEMPLATE_BASENAME3 = "bing-period-report.html";
|
|
133244
135200
|
var INJECT_MARKER4 = "window.__BING_PERIOD_REPORT__ = window.__BING_PERIOD_REPORT__ || null;";
|
|
133245
|
-
function resolveSkillTemplatePath6(
|
|
135201
|
+
function resolveSkillTemplatePath6(basename13) {
|
|
133246
135202
|
const dir = path39.dirname(fileURLToPath9(import.meta.url));
|
|
133247
135203
|
const candidates = [
|
|
133248
|
-
path39.join(dir, "skill", "report-templates",
|
|
133249
|
-
path39.join(dir, "skill", "siluzan-ads", "report-templates",
|
|
133250
|
-
path39.join(dir, "..", "..", "assets", "siluzan-ads", "report-templates",
|
|
135204
|
+
path39.join(dir, "skill", "report-templates", basename13),
|
|
135205
|
+
path39.join(dir, "skill", "siluzan-ads", "report-templates", basename13),
|
|
135206
|
+
path39.join(dir, "..", "..", "assets", "siluzan-ads", "report-templates", basename13)
|
|
133251
135207
|
];
|
|
133252
135208
|
for (const p of candidates) {
|
|
133253
135209
|
if (fs30.existsSync(p)) return p;
|
|
@@ -134139,12 +136095,12 @@ function formatTikTokPeriodReportErrors(result) {
|
|
|
134139
136095
|
// src/commands/tiktok-analysis/render-report.ts
|
|
134140
136096
|
var TEMPLATE_BASENAME4 = "tiktok-period-report.html";
|
|
134141
136097
|
var INJECT_MARKER5 = "window.__TIKTOK_PERIOD_REPORT__ = window.__TIKTOK_PERIOD_REPORT__ || null;";
|
|
134142
|
-
function resolveSkillTemplatePath7(
|
|
136098
|
+
function resolveSkillTemplatePath7(basename13) {
|
|
134143
136099
|
const dir = path42.dirname(fileURLToPath10(import.meta.url));
|
|
134144
136100
|
const candidates = [
|
|
134145
|
-
path42.join(dir, "skill", "report-templates",
|
|
134146
|
-
path42.join(dir, "skill", "siluzan-ads", "report-templates",
|
|
134147
|
-
path42.join(dir, "..", "..", "assets", "siluzan-ads", "report-templates",
|
|
136101
|
+
path42.join(dir, "skill", "report-templates", basename13),
|
|
136102
|
+
path42.join(dir, "skill", "siluzan-ads", "report-templates", basename13),
|
|
136103
|
+
path42.join(dir, "..", "..", "assets", "siluzan-ads", "report-templates", basename13)
|
|
134148
136104
|
];
|
|
134149
136105
|
for (const p of candidates) {
|
|
134150
136106
|
if (fs32.existsSync(p)) return p;
|
|
@@ -135913,12 +137869,12 @@ function formatYandexPeriodReportErrors(result) {
|
|
|
135913
137869
|
// src/commands/yandex-analysis/render-report.ts
|
|
135914
137870
|
var TEMPLATE_BASENAME5 = "yandex-period-report.html";
|
|
135915
137871
|
var INJECT_MARKER6 = "window.__YANDEX_PERIOD_REPORT__ = window.__YANDEX_PERIOD_REPORT__ || null;";
|
|
135916
|
-
function resolveSkillTemplatePath8(
|
|
137872
|
+
function resolveSkillTemplatePath8(basename13) {
|
|
135917
137873
|
const dir = path45.dirname(fileURLToPath11(import.meta.url));
|
|
135918
137874
|
const candidates = [
|
|
135919
|
-
path45.join(dir, "skill", "report-templates",
|
|
135920
|
-
path45.join(dir, "skill", "siluzan-ads", "report-templates",
|
|
135921
|
-
path45.join(dir, "..", "..", "assets", "siluzan-ads", "report-templates",
|
|
137875
|
+
path45.join(dir, "skill", "report-templates", basename13),
|
|
137876
|
+
path45.join(dir, "skill", "siluzan-ads", "report-templates", basename13),
|
|
137877
|
+
path45.join(dir, "..", "..", "assets", "siluzan-ads", "report-templates", basename13)
|
|
135922
137878
|
];
|
|
135923
137879
|
for (const p of candidates) {
|
|
135924
137880
|
if (fs34.existsSync(p)) return p;
|
|
@@ -136196,6 +138152,7 @@ var REGISTRARS = [
|
|
|
136196
138152
|
register24,
|
|
136197
138153
|
register25,
|
|
136198
138154
|
register26,
|
|
138155
|
+
register27,
|
|
136199
138156
|
registerWebsiteDiagnosisCommands,
|
|
136200
138157
|
registerMarketAnalysisCommands,
|
|
136201
138158
|
registerGoogleAdsDiagnosisCommands
|