siluzan-tso-cli 1.1.56-beta.3 → 1.1.56
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -2
- package/dist/index.js +927 -58
- package/dist/skill/SKILL.md +1 -1
- package/dist/skill/_meta.json +2 -2
- package/dist/skill/assets/meta-lead-create-template.json +2 -1
- package/dist/skill/assets/meta-lead-create-template.md +3 -2
- 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/agent-conventions.md +2 -1
- package/dist/skill/references/core/setup.md +5 -5
- package/dist/skill/references/core/workflows.md +2 -1
- package/dist/skill/references/google-ads/google-ads-write.md +5 -1
- package/dist/skill/references/meta-ads/meta-ads-write.md +7 -6
- package/dist/skill/references/meta-ads/meta-ads.md +2 -2
- package/dist/skill/references/misc/tso-home.md +2 -2
- package/dist/skill/scripts/install.ps1 +3 -3
- package/dist/skill/scripts/install.sh +3 -3
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -4049,7 +4049,7 @@ var DEFAULT_API_BASE;
|
|
|
4049
4049
|
var init_defaults = __esm({
|
|
4050
4050
|
"src/config/defaults.ts"() {
|
|
4051
4051
|
"use strict";
|
|
4052
|
-
DEFAULT_API_BASE = "https://tso-api
|
|
4052
|
+
DEFAULT_API_BASE = "https://tso-api.siluzan.com";
|
|
4053
4053
|
}
|
|
4054
4054
|
});
|
|
4055
4055
|
|
|
@@ -113620,6 +113620,192 @@ import { readFileSync as readFileSync4 } from "fs";
|
|
|
113620
113620
|
init_cli_json_snapshot();
|
|
113621
113621
|
init_strip_legacy_google_fields();
|
|
113622
113622
|
init_cli_table();
|
|
113623
|
+
|
|
113624
|
+
// src/commands/ad/write-verification.ts
|
|
113625
|
+
var CAMPAIGN_WRITE_SCOPE = "gateway-full-put";
|
|
113626
|
+
function sameWriteValue(expected, actual) {
|
|
113627
|
+
if (expected === actual) return true;
|
|
113628
|
+
if (typeof expected === "string" && typeof actual === "string") {
|
|
113629
|
+
return expected.trim() === actual.trim();
|
|
113630
|
+
}
|
|
113631
|
+
if (typeof expected === "number" && typeof actual === "number") return expected === actual;
|
|
113632
|
+
if (typeof expected === "boolean" && typeof actual === "boolean") return expected === actual;
|
|
113633
|
+
if (Array.isArray(expected) && Array.isArray(actual)) {
|
|
113634
|
+
if (expected.length !== actual.length) return false;
|
|
113635
|
+
return expected.every((item, i) => sameWriteValue(item, actual[i]));
|
|
113636
|
+
}
|
|
113637
|
+
return false;
|
|
113638
|
+
}
|
|
113639
|
+
function fieldCheck(field, expected, actual, present) {
|
|
113640
|
+
if (!present) return { field, expected, actual: void 0, state: "unread" };
|
|
113641
|
+
return {
|
|
113642
|
+
field,
|
|
113643
|
+
expected,
|
|
113644
|
+
actual,
|
|
113645
|
+
state: sameWriteValue(expected, actual) ? "match" : "mismatch"
|
|
113646
|
+
};
|
|
113647
|
+
}
|
|
113648
|
+
function classifyFieldChecks(checks, readFailed) {
|
|
113649
|
+
if (readFailed) return "unknown";
|
|
113650
|
+
if (checks.length === 0) return "submitted";
|
|
113651
|
+
if (checks.some((c) => c.state === "unread")) return "unknown";
|
|
113652
|
+
if (checks.every((c) => c.state === "match")) return "verified";
|
|
113653
|
+
return "partial";
|
|
113654
|
+
}
|
|
113655
|
+
function describeUnmatchedChecks(checks) {
|
|
113656
|
+
return checks.filter((c) => c.state !== "match").map((c) => {
|
|
113657
|
+
const actual = c.state === "unread" ? "\uFF08\u672A\u8BFB\u5230\uFF09" : JSON.stringify(c.actual);
|
|
113658
|
+
return `${c.field}\uFF1A\u671F\u671B ${JSON.stringify(c.expected)}\uFF0C\u8BFB\u5230 ${actual}`;
|
|
113659
|
+
}).join("\uFF1B");
|
|
113660
|
+
}
|
|
113661
|
+
function writeVerificationHeadline(verification, detail) {
|
|
113662
|
+
if (verification === "verified") return `\u2705 ${detail}`;
|
|
113663
|
+
if (verification === "partial") return `\u26A0\uFE0F \u90E8\u5206\u751F\u6548\uFF1A${detail}`;
|
|
113664
|
+
return `\u26A0\uFE0F \u5DF2\u63D0\u4EA4\uFF0C\u5C1A\u672A\u6838\u9A8C\uFF1A${detail}`;
|
|
113665
|
+
}
|
|
113666
|
+
function readBudgetSharedFlag(campaign) {
|
|
113667
|
+
const raw = campaign["budgetShared"] ?? campaign["BudgetShared"];
|
|
113668
|
+
if (typeof raw === "boolean") return raw;
|
|
113669
|
+
return void 0;
|
|
113670
|
+
}
|
|
113671
|
+
function readCampaignBudgetCents(campaign) {
|
|
113672
|
+
const raw = campaign["budget"] ?? campaign["Budget"];
|
|
113673
|
+
if (typeof raw === "number" && Number.isFinite(raw)) return raw;
|
|
113674
|
+
return void 0;
|
|
113675
|
+
}
|
|
113676
|
+
function readKey(obj, keys) {
|
|
113677
|
+
for (const key of keys) {
|
|
113678
|
+
if (Object.prototype.hasOwnProperty.call(obj, key)) return { present: true, value: obj[key] };
|
|
113679
|
+
}
|
|
113680
|
+
return { present: false, value: void 0 };
|
|
113681
|
+
}
|
|
113682
|
+
function evaluateCampaignReadback(after, expected, readFailed) {
|
|
113683
|
+
if (readFailed || !after) return { verification: "unknown", checks: [] };
|
|
113684
|
+
const checks = [];
|
|
113685
|
+
const push = (field, expectedValue, keys) => {
|
|
113686
|
+
if (expectedValue === void 0) return;
|
|
113687
|
+
const found = readKey(after, keys);
|
|
113688
|
+
checks.push(fieldCheck(field, expectedValue, found.value, found.present));
|
|
113689
|
+
};
|
|
113690
|
+
push("name", expected.name, ["name", "Name"]);
|
|
113691
|
+
push("statusV2", expected.statusV2, ["statusV2", "StatusV2"]);
|
|
113692
|
+
push("biddingStrategyTypeV2", expected.biddingStrategyTypeV2, [
|
|
113693
|
+
"biddingStrategyTypeV2",
|
|
113694
|
+
"BiddingStrategyTypeV2"
|
|
113695
|
+
]);
|
|
113696
|
+
push("targetSpend_BidCeilingAmount", expected.targetSpendBidCeilingCents, [
|
|
113697
|
+
"targetSpend_BidCeilingAmount",
|
|
113698
|
+
"TargetSpend_BidCeilingAmount"
|
|
113699
|
+
]);
|
|
113700
|
+
push("targetCpa_BidingAmount", expected.targetCpaCents, [
|
|
113701
|
+
"targetCpa_BidingAmount",
|
|
113702
|
+
"TargetCpa_BidingAmount"
|
|
113703
|
+
]);
|
|
113704
|
+
push("targetRoas", expected.targetRoas, ["targetRoas", "TargetRoas"]);
|
|
113705
|
+
push("manualCpc_EnhancedCpcEnabled", expected.manualCpcEnhanced, [
|
|
113706
|
+
"manualCpc_EnhancedCpcEnabled",
|
|
113707
|
+
"ManualCpc_EnhancedCpcEnabled"
|
|
113708
|
+
]);
|
|
113709
|
+
push("targetSearchNetwork", expected.targetSearchNetwork, [
|
|
113710
|
+
"targetSearchNetwork",
|
|
113711
|
+
"TargetSearchNetwork"
|
|
113712
|
+
]);
|
|
113713
|
+
push("targetContentNetwork", expected.targetContentNetwork, [
|
|
113714
|
+
"targetContentNetwork",
|
|
113715
|
+
"TargetContentNetwork"
|
|
113716
|
+
]);
|
|
113717
|
+
if (expected.budgetWasRequested) {
|
|
113718
|
+
push("budget", expected.budgetCents, ["budget", "Budget"]);
|
|
113719
|
+
} else if (expected.budgetCentsBefore !== void 0) {
|
|
113720
|
+
const found = readKey(after, ["budget", "Budget"]);
|
|
113721
|
+
checks.push(fieldCheck("budget", expected.budgetCentsBefore, found.value, found.present));
|
|
113722
|
+
}
|
|
113723
|
+
return { verification: classifyFieldChecks(checks, false), checks };
|
|
113724
|
+
}
|
|
113725
|
+
function judgeBudgetAfterFailedPut(input) {
|
|
113726
|
+
if (input.readFailed || input.budgetCentsBefore === void 0 || input.budgetCentsAfter === void 0) {
|
|
113727
|
+
const present = !input.readFailed && input.budgetCentsAfter !== void 0;
|
|
113728
|
+
const checks = input.budgetCentsBefore === void 0 ? [] : [fieldCheck("budget", input.budgetCentsBefore, input.budgetCentsAfter, present)];
|
|
113729
|
+
return { kind: "unknown", verification: "unknown", checks };
|
|
113730
|
+
}
|
|
113731
|
+
if (input.budgetCentsBefore !== input.budgetCentsAfter) {
|
|
113732
|
+
return {
|
|
113733
|
+
kind: "partial",
|
|
113734
|
+
verification: "partial",
|
|
113735
|
+
checks: [fieldCheck("budget", input.budgetCentsBefore, input.budgetCentsAfter, true)]
|
|
113736
|
+
};
|
|
113737
|
+
}
|
|
113738
|
+
return { kind: "unchanged" };
|
|
113739
|
+
}
|
|
113740
|
+
function coreKeyword(text2) {
|
|
113741
|
+
const t = text2.trim();
|
|
113742
|
+
if (t.length >= 2 && (t.startsWith('"') && t.endsWith('"') || t.startsWith("[") && t.endsWith("]"))) {
|
|
113743
|
+
return t.slice(1, -1).trim();
|
|
113744
|
+
}
|
|
113745
|
+
return t;
|
|
113746
|
+
}
|
|
113747
|
+
function collectKeywordCores(value) {
|
|
113748
|
+
if (typeof value === "string") {
|
|
113749
|
+
const core = coreKeyword(value);
|
|
113750
|
+
return core ? [core] : [];
|
|
113751
|
+
}
|
|
113752
|
+
if (!Array.isArray(value)) return [];
|
|
113753
|
+
const cores = [];
|
|
113754
|
+
for (const item of value) {
|
|
113755
|
+
if (typeof item !== "string") continue;
|
|
113756
|
+
const core = coreKeyword(item);
|
|
113757
|
+
if (core) cores.push(core);
|
|
113758
|
+
}
|
|
113759
|
+
return cores;
|
|
113760
|
+
}
|
|
113761
|
+
function isRecord(value) {
|
|
113762
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
113763
|
+
}
|
|
113764
|
+
function recordsFromResponse(response) {
|
|
113765
|
+
if (Array.isArray(response)) return response.filter(isRecord);
|
|
113766
|
+
if (!isRecord(response)) return [];
|
|
113767
|
+
for (const key of ["data", "items", "keywords"]) {
|
|
113768
|
+
const nested = response[key];
|
|
113769
|
+
if (Array.isArray(nested)) return nested.filter(isRecord);
|
|
113770
|
+
}
|
|
113771
|
+
const cores = [
|
|
113772
|
+
...collectKeywordCores(response["keywordText"]),
|
|
113773
|
+
...collectKeywordCores(response["KeywordText"]),
|
|
113774
|
+
...collectKeywordCores(response["text"])
|
|
113775
|
+
];
|
|
113776
|
+
if (cores.length > 0 || typeof response["id"] === "string") return [response];
|
|
113777
|
+
return [];
|
|
113778
|
+
}
|
|
113779
|
+
function summarizeNegativeKeywordCreate(requested, response, listItems) {
|
|
113780
|
+
const responseRecords = recordsFromResponse(response);
|
|
113781
|
+
const pools = listItems == null ? responseRecords : [...responseRecords, ...listItems];
|
|
113782
|
+
const matched = [];
|
|
113783
|
+
const pending = [];
|
|
113784
|
+
for (const text2 of requested) {
|
|
113785
|
+
const core = coreKeyword(text2);
|
|
113786
|
+
const hit = pools.find((item) => {
|
|
113787
|
+
const cores = [
|
|
113788
|
+
...collectKeywordCores(item["keywordText"]),
|
|
113789
|
+
...collectKeywordCores(item["KeywordText"]),
|
|
113790
|
+
...collectKeywordCores(item["text"])
|
|
113791
|
+
];
|
|
113792
|
+
return cores.includes(core);
|
|
113793
|
+
});
|
|
113794
|
+
if (!hit) {
|
|
113795
|
+
pending.push(text2);
|
|
113796
|
+
continue;
|
|
113797
|
+
}
|
|
113798
|
+
const idRaw = hit["id"] ?? hit["Id"];
|
|
113799
|
+
matched.push({ text: text2, id: typeof idRaw === "string" ? idRaw : void 0 });
|
|
113800
|
+
}
|
|
113801
|
+
let verification;
|
|
113802
|
+
if (requested.length > 0 && matched.length === requested.length) verification = "verified";
|
|
113803
|
+
else if (matched.length > 0) verification = "partial";
|
|
113804
|
+
else verification = "unknown";
|
|
113805
|
+
return { verification, matchedCount: matched.length, matched, pending };
|
|
113806
|
+
}
|
|
113807
|
+
|
|
113808
|
+
// src/commands/ad/keyword.ts
|
|
113623
113809
|
function unwrapKeywordDisplayTextForEdit(raw) {
|
|
113624
113810
|
const t = raw.trim();
|
|
113625
113811
|
if (t.length >= 2 && t.startsWith('"') && t.endsWith('"')) {
|
|
@@ -113952,17 +114138,77 @@ async function runAdNegativeKeywordCreate(opts) {
|
|
|
113952
114138
|
}
|
|
113953
114139
|
const body = buildNegativeKeywordCreateBody(level, keywords, opts);
|
|
113954
114140
|
const url = `${googleApiUrl}/negativekeywordmanagement/negativekeyword/${opts.account}`;
|
|
114141
|
+
let response;
|
|
113955
114142
|
try {
|
|
113956
|
-
await apiFetch2(
|
|
114143
|
+
response = await apiFetch2(
|
|
114144
|
+
url,
|
|
114145
|
+
config,
|
|
114146
|
+
{ method: "POST", body: JSON.stringify(body) },
|
|
114147
|
+
opts.verbose
|
|
114148
|
+
);
|
|
113957
114149
|
} catch (err) {
|
|
113958
114150
|
console.error(`
|
|
113959
114151
|
\u274C \u6DFB\u52A0\u5426\u5B9A\u5173\u952E\u8BCD\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
|
|
113960
114152
|
`);
|
|
113961
114153
|
process.exit(1);
|
|
113962
114154
|
}
|
|
114155
|
+
const dateParams = new URLSearchParams();
|
|
114156
|
+
dateParams.set("startDate", toGoogleDate(void 0, -30));
|
|
114157
|
+
dateParams.set("endDate", toGoogleDate(void 0, 0));
|
|
114158
|
+
const listUrl = `${googleApiUrl}/negativekeywordmanagement/v2/list/${opts.account}?${dateParams}`;
|
|
114159
|
+
const readList = async () => {
|
|
114160
|
+
try {
|
|
114161
|
+
const listed = await apiFetch2(
|
|
114162
|
+
listUrl,
|
|
114163
|
+
config,
|
|
114164
|
+
{},
|
|
114165
|
+
opts.verbose
|
|
114166
|
+
);
|
|
114167
|
+
return listed.data ?? [];
|
|
114168
|
+
} catch {
|
|
114169
|
+
return null;
|
|
114170
|
+
}
|
|
114171
|
+
};
|
|
114172
|
+
let listItems = await readList();
|
|
114173
|
+
let summary = summarizeNegativeKeywordCreate(keywords, response, listItems);
|
|
114174
|
+
if (summary.verification !== "verified") {
|
|
114175
|
+
await new Promise((resolve25) => setTimeout(resolve25, 1e3));
|
|
114176
|
+
listItems = await readList();
|
|
114177
|
+
summary = summarizeNegativeKeywordCreate(keywords, response, listItems);
|
|
114178
|
+
}
|
|
114179
|
+
const nextQuery = `siluzan-tso ad keywords -a ${opts.account} --negative --json-out ./snap`;
|
|
114180
|
+
const detail = summary.verification === "verified" ? `\u5DF2\u6838\u9A8C\u6DFB\u52A0 ${summary.matchedCount} \u4E2A\u5426\u5B9A\u5173\u952E\u8BCD\uFF08${negativeKeywordLevelLabel[level]}\uFF09` : `\u8BF7\u6C42 ${keywords.length} \u4E2A\uFF0C\u5BF9\u4E0A ${summary.matchedCount} \u4E2A${summary.pending.length ? `\uFF0C\u5C1A\u672A\u6838\u9A8C\uFF1A${summary.pending.join("\u3001")}` : ""}`;
|
|
114181
|
+
const headline = writeVerificationHeadline(summary.verification, detail);
|
|
114182
|
+
if (summary.verification !== "verified") {
|
|
114183
|
+
console.error(`
|
|
114184
|
+
${headline}
|
|
114185
|
+
\u8BF7\u518D\u67E5\uFF1A${nextQuery}
|
|
114186
|
+
`);
|
|
114187
|
+
}
|
|
114188
|
+
const payload = {
|
|
114189
|
+
verification: summary.verification,
|
|
114190
|
+
requestedCount: keywords.length,
|
|
114191
|
+
matchedCount: summary.matchedCount,
|
|
114192
|
+
matched: summary.matched,
|
|
114193
|
+
pending: summary.pending,
|
|
114194
|
+
nextQuery,
|
|
114195
|
+
level,
|
|
114196
|
+
response
|
|
114197
|
+
};
|
|
114198
|
+
if (await emitCliJsonOrSnapshot(opts, {
|
|
114199
|
+
section: `ad-keyword-negative-create-${opts.account}`,
|
|
114200
|
+
commandLabel: "ad keyword-negative-create",
|
|
114201
|
+
commandHint: opts.account,
|
|
114202
|
+
payload,
|
|
114203
|
+
idSuffix: opts.account,
|
|
114204
|
+
summaryExtras: { verification: summary.verification, matchedCount: summary.matchedCount }
|
|
114205
|
+
})) {
|
|
114206
|
+
return;
|
|
114207
|
+
}
|
|
113963
114208
|
console.log(
|
|
113964
114209
|
`
|
|
113965
|
-
|
|
114210
|
+
${headline}${summary.verification === "verified" ? "" : `
|
|
114211
|
+
\u8BF7\u518D\u67E5\uFF1A${nextQuery}`}
|
|
113966
114212
|
`
|
|
113967
114213
|
);
|
|
113968
114214
|
}
|
|
@@ -114058,7 +114304,7 @@ async function runAdKeywordEdit(opts) {
|
|
|
114058
114304
|
currentId = returnedId;
|
|
114059
114305
|
console.log(
|
|
114060
114306
|
`
|
|
114061
|
-
\u2139\uFE0F \u7F51\u5173\u8FD4\u56DE\u7684\u5173\u952E\u8BCD id \u5DF2\u53D8\u66F4\u4E3A ${returnedId}\uFF08\u8BF7\u6C42\u65F6 id \u4E3A ${opts.id}\uFF09\uFF0C\u540E\u7EED\u67E5\u8BE2\u8BF7\u4F7F\u7528\u65B0 id\u3002
|
|
114307
|
+
\u2139\uFE0F \u7F51\u5173\u8FD4\u56DE\u7684\u5173\u952E\u8BCD id \u5DF2\u53D8\u66F4\u4E3A ${returnedId}\uFF08\u8BF7\u6C42\u65F6 id \u4E3A ${opts.id}\uFF0CchangeKind=replace\uFF09\uFF0C\u540E\u7EED\u67E5\u8BE2\u8BF7\u4F7F\u7528\u65B0 id\u3002
|
|
114062
114308
|
`
|
|
114063
114309
|
);
|
|
114064
114310
|
}
|
|
@@ -114107,9 +114353,28 @@ ${mismatches.map((m) => ` - ${m}`).join("\n")}
|
|
|
114107
114353
|
process.exit(1);
|
|
114108
114354
|
}
|
|
114109
114355
|
}
|
|
114110
|
-
|
|
114111
|
-
|
|
114356
|
+
const verifiedFields = [];
|
|
114357
|
+
if (opts.finalUrl !== void 0) verifiedFields.push("finalURL");
|
|
114358
|
+
if (opts.maxCpc !== void 0) verifiedFields.push("maxCPC");
|
|
114359
|
+
if (opts.status !== void 0) verifiedFields.push("userStatusV2");
|
|
114360
|
+
const changeKind = currentId !== opts.id ? "replace" : "update";
|
|
114361
|
+
if (verifiedFields.length > 0) {
|
|
114362
|
+
console.log(
|
|
114363
|
+
`
|
|
114364
|
+
\u2705 \u5173\u952E\u8BCD\u5DF2\u6838\u9A8C\uFF08${verifiedFields.join("\u3001")}\uFF1BchangeKind=${changeKind}${changeKind === "replace" ? `\uFF0C\u65B0 id ${currentId}` : ""}\uFF09
|
|
114365
|
+
`
|
|
114366
|
+
);
|
|
114367
|
+
} else if (changeKind === "replace") {
|
|
114368
|
+
console.log(
|
|
114369
|
+
`
|
|
114370
|
+
\u2705 \u5173\u952E\u8BCD\u5DF2\u63D0\u4EA4\uFF08changeKind=replace\uFF0C\u65B0 id ${currentId}\uFF09\u3002\u8BCD\u9762\u6216\u5339\u914D\u7C7B\u578B\u4F1A\u65B0\u5EFA\u5E76\u79FB\u9664\u65E7\u8BCD\uFF0C\u8BF7\u7528 ad keywords \u6838\u5BF9\u65B0 id\u3002
|
|
114371
|
+
`
|
|
114372
|
+
);
|
|
114373
|
+
} else {
|
|
114374
|
+
console.log(`
|
|
114375
|
+
\u2705 \u5173\u952E\u8BCD\u5DF2\u63D0\u4EA4\uFF08changeKind=update\uFF09\u3002\u8BF7\u7528 ad keywords \u6838\u5BF9\u8BCD\u9762\u3002
|
|
114112
114376
|
`);
|
|
114377
|
+
}
|
|
114113
114378
|
}
|
|
114114
114379
|
async function runAdKeywordStatus(opts) {
|
|
114115
114380
|
opts = withResolvedAdAccount(opts);
|
|
@@ -115626,6 +115891,7 @@ async function resolveCampaignCustomerName(cfg, config, verbose) {
|
|
|
115626
115891
|
}
|
|
115627
115892
|
|
|
115628
115893
|
// src/commands/ad/campaign.ts
|
|
115894
|
+
var CAMPAIGN_READBACK_WAIT_MS = 1e3;
|
|
115629
115895
|
function formatGoogleCampaignListStatus(row) {
|
|
115630
115896
|
const raw = row.statusV2;
|
|
115631
115897
|
if (raw == null || String(raw).trim() === "") return "-";
|
|
@@ -115735,6 +116001,108 @@ async function runAdCampaigns(opts) {
|
|
|
115735
116001
|
printCliTable(rows, cols);
|
|
115736
116002
|
console.log();
|
|
115737
116003
|
}
|
|
116004
|
+
function unwrapCampaign(res) {
|
|
116005
|
+
if (res && typeof res === "object" && "data" in res && res.data && typeof res.data === "object") {
|
|
116006
|
+
return res.data;
|
|
116007
|
+
}
|
|
116008
|
+
return res;
|
|
116009
|
+
}
|
|
116010
|
+
async function fetchCampaignRecord(detailUrl, config, verbose) {
|
|
116011
|
+
const res = await apiFetch2(
|
|
116012
|
+
detailUrl,
|
|
116013
|
+
config,
|
|
116014
|
+
{},
|
|
116015
|
+
verbose
|
|
116016
|
+
);
|
|
116017
|
+
return unwrapCampaign(res);
|
|
116018
|
+
}
|
|
116019
|
+
async function verifyCampaignAfterPut(detailUrl, config, expected, verbose) {
|
|
116020
|
+
const readOnce = async () => {
|
|
116021
|
+
try {
|
|
116022
|
+
const after = await fetchCampaignRecord(detailUrl, config, verbose);
|
|
116023
|
+
return evaluateCampaignReadback(after, expected, false);
|
|
116024
|
+
} catch {
|
|
116025
|
+
return evaluateCampaignReadback(void 0, expected, true);
|
|
116026
|
+
}
|
|
116027
|
+
};
|
|
116028
|
+
let result = await readOnce();
|
|
116029
|
+
if (result.verification !== "verified") {
|
|
116030
|
+
await new Promise((resolve25) => setTimeout(resolve25, CAMPAIGN_READBACK_WAIT_MS));
|
|
116031
|
+
result = await readOnce();
|
|
116032
|
+
}
|
|
116033
|
+
return result;
|
|
116034
|
+
}
|
|
116035
|
+
async function reportFailedCampaignPut(opts) {
|
|
116036
|
+
const message = opts.err instanceof Error ? opts.err.message : String(opts.err);
|
|
116037
|
+
let budgetCentsAfter;
|
|
116038
|
+
let readFailed = false;
|
|
116039
|
+
try {
|
|
116040
|
+
const after = await fetchCampaignRecord(opts.detailUrl, opts.config, opts.verbose);
|
|
116041
|
+
budgetCentsAfter = readCampaignBudgetCents(after);
|
|
116042
|
+
} catch {
|
|
116043
|
+
readFailed = true;
|
|
116044
|
+
}
|
|
116045
|
+
const judged = judgeBudgetAfterFailedPut({
|
|
116046
|
+
readFailed,
|
|
116047
|
+
budgetCentsBefore: opts.budgetCentsBefore,
|
|
116048
|
+
budgetCentsAfter
|
|
116049
|
+
});
|
|
116050
|
+
if (judged.kind === "unchanged") {
|
|
116051
|
+
console.error(`
|
|
116052
|
+
\u274C ${opts.failureLabel}\uFF1A${message}
|
|
116053
|
+
`);
|
|
116054
|
+
process.exit(1);
|
|
116055
|
+
}
|
|
116056
|
+
const hint = judged.kind === "partial" ? "\u56DE\u8BFB\u9884\u7B97\u5DF2\u76F8\u5BF9\u63D0\u4EA4\u524D\u53D8\u5316\u3002\u9884\u7B97\u53EF\u80FD\u5DF2\u5199\u5165\uFF0C\u7CFB\u5217\u5176\u4F59\u66F4\u65B0\u672A\u5B8C\u6210\u3002\u4E0D\u8981\u6574\u5305\u91CD\u63D0\u3002" : "\u56DE\u8BFB\u5931\u8D25\u6216\u65E0\u6CD5\u5BF9\u6BD4\u9884\u7B97\uFF0C\u5199\u5165\u7ED3\u679C\u672A\u77E5\u3002\u4E0D\u8981\u6574\u5305\u91CD\u63D0\u3002";
|
|
116057
|
+
await reportCampaignWrite({
|
|
116058
|
+
jsonOut: opts.jsonOut,
|
|
116059
|
+
account: opts.account,
|
|
116060
|
+
id: opts.id,
|
|
116061
|
+
section: opts.section,
|
|
116062
|
+
commandLabel: opts.commandLabel,
|
|
116063
|
+
verification: judged.verification,
|
|
116064
|
+
checks: judged.checks,
|
|
116065
|
+
detail: `${opts.failureLabel}\uFF1A${message}\u3002${hint}`
|
|
116066
|
+
});
|
|
116067
|
+
}
|
|
116068
|
+
async function reportCampaignWrite(opts) {
|
|
116069
|
+
const nextQuery = `siluzan-tso ad campaigns -a ${opts.account} --json-out ./snap`;
|
|
116070
|
+
const unmatched = describeUnmatchedChecks(opts.checks);
|
|
116071
|
+
const text2 = opts.verification === "verified" ? opts.detail : unmatched || opts.detail;
|
|
116072
|
+
const headline = writeVerificationHeadline(opts.verification, text2);
|
|
116073
|
+
if (opts.verification !== "verified") {
|
|
116074
|
+
console.error(`
|
|
116075
|
+
${headline}
|
|
116076
|
+
\u8BF7\u518D\u67E5\uFF1A${nextQuery}
|
|
116077
|
+
`);
|
|
116078
|
+
}
|
|
116079
|
+
const payload = {
|
|
116080
|
+
id: opts.id,
|
|
116081
|
+
writeScope: CAMPAIGN_WRITE_SCOPE,
|
|
116082
|
+
verification: opts.verification,
|
|
116083
|
+
nextQuery,
|
|
116084
|
+
checks: opts.checks
|
|
116085
|
+
};
|
|
116086
|
+
if (await emitCliJsonOrSnapshot(
|
|
116087
|
+
{ jsonOut: opts.jsonOut },
|
|
116088
|
+
{
|
|
116089
|
+
section: opts.section,
|
|
116090
|
+
commandLabel: opts.commandLabel,
|
|
116091
|
+
commandHint: opts.account,
|
|
116092
|
+
payload,
|
|
116093
|
+
idSuffix: `${opts.account}-${opts.id}`,
|
|
116094
|
+
summaryExtras: { verification: opts.verification, writeScope: CAMPAIGN_WRITE_SCOPE }
|
|
116095
|
+
}
|
|
116096
|
+
)) {
|
|
116097
|
+
return;
|
|
116098
|
+
}
|
|
116099
|
+
console.log(
|
|
116100
|
+
`
|
|
116101
|
+
${headline}${opts.verification === "verified" ? "" : `
|
|
116102
|
+
\u8BF7\u518D\u67E5\uFF1A${nextQuery}`}
|
|
116103
|
+
`
|
|
116104
|
+
);
|
|
116105
|
+
}
|
|
115738
116106
|
async function runAdCampaignStatus(opts) {
|
|
115739
116107
|
opts = withResolvedAdAccount(opts);
|
|
115740
116108
|
const config = loadConfig(opts.token);
|
|
@@ -115761,14 +116129,41 @@ async function runAdCampaignStatus(opts) {
|
|
|
115761
116129
|
try {
|
|
115762
116130
|
await apiFetch2(detailUrl, config, { method: "PUT", body: JSON.stringify(body) }, opts.verbose);
|
|
115763
116131
|
} catch (err) {
|
|
115764
|
-
|
|
115765
|
-
|
|
115766
|
-
|
|
115767
|
-
|
|
116132
|
+
await reportFailedCampaignPut({
|
|
116133
|
+
err,
|
|
116134
|
+
detailUrl,
|
|
116135
|
+
config,
|
|
116136
|
+
verbose: opts.verbose,
|
|
116137
|
+
budgetCentsBefore: readCampaignBudgetCents(campaign),
|
|
116138
|
+
jsonOut: opts.jsonOut,
|
|
116139
|
+
account: opts.account,
|
|
116140
|
+
id: opts.id,
|
|
116141
|
+
section: "ad-campaign-status",
|
|
116142
|
+
commandLabel: "ad campaign-status",
|
|
116143
|
+
failureLabel: "\u72B6\u6001\u66F4\u65B0\u5931\u8D25"
|
|
116144
|
+
});
|
|
116145
|
+
return;
|
|
115768
116146
|
}
|
|
115769
|
-
|
|
115770
|
-
|
|
115771
|
-
|
|
116147
|
+
const judged = await verifyCampaignAfterPut(
|
|
116148
|
+
detailUrl,
|
|
116149
|
+
config,
|
|
116150
|
+
{
|
|
116151
|
+
statusV2: opts.status,
|
|
116152
|
+
budgetCentsBefore: readCampaignBudgetCents(campaign),
|
|
116153
|
+
budgetWasRequested: false
|
|
116154
|
+
},
|
|
116155
|
+
opts.verbose
|
|
116156
|
+
);
|
|
116157
|
+
await reportCampaignWrite({
|
|
116158
|
+
jsonOut: opts.jsonOut,
|
|
116159
|
+
account: opts.account,
|
|
116160
|
+
id: opts.id,
|
|
116161
|
+
section: "ad-campaign-status",
|
|
116162
|
+
commandLabel: "ad campaign-status",
|
|
116163
|
+
verification: judged.verification,
|
|
116164
|
+
checks: judged.checks,
|
|
116165
|
+
detail: `\u5E7F\u544A\u7CFB\u5217 ${opts.id} \u72B6\u6001\u5DF2\u6838\u9A8C\u4E3A ${opts.status}`
|
|
116166
|
+
});
|
|
115772
116167
|
}
|
|
115773
116168
|
async function runAdCampaignDelete(opts) {
|
|
115774
116169
|
opts = withResolvedAdAccount(opts);
|
|
@@ -116069,13 +116464,29 @@ async function runAdCampaignEdit(opts) {
|
|
|
116069
116464
|
console.error("\n\u274C \u8BF7\u81F3\u5C11\u6307\u5B9A\u4E00\u4E2A\u4FEE\u6539\u9879\uFF08--name / --budget / --bidding / \u2026\uFF09\n");
|
|
116070
116465
|
process.exit(1);
|
|
116071
116466
|
}
|
|
116467
|
+
if (opts.budget !== void 0 && readBudgetSharedFlag(campaign) === true) {
|
|
116468
|
+
console.error(
|
|
116469
|
+
"\n\u274C \u8BE5\u7CFB\u5217\u6807\u8BB0\u4E3A\u5171\u4EAB\u9884\u7B97\uFF08budgetShared=true\uFF09\u3002campaign-edit \u4E0D\u4F1A\u53EA\u6539\u672C\u7CFB\u5217\u7684\u5171\u4EAB\u9884\u7B97\u91D1\u989D\uFF0C\u672C\u6B21\u672A\u63D0\u4EA4\u3002\n"
|
|
116470
|
+
);
|
|
116471
|
+
process.exit(1);
|
|
116472
|
+
}
|
|
116072
116473
|
try {
|
|
116073
116474
|
await apiFetch2(detailUrl, config, { method: "PUT", body: JSON.stringify(body) }, opts.verbose);
|
|
116074
116475
|
} catch (err) {
|
|
116075
|
-
|
|
116076
|
-
|
|
116077
|
-
|
|
116078
|
-
|
|
116476
|
+
await reportFailedCampaignPut({
|
|
116477
|
+
err,
|
|
116478
|
+
detailUrl,
|
|
116479
|
+
config,
|
|
116480
|
+
verbose: opts.verbose,
|
|
116481
|
+
budgetCentsBefore: readCampaignBudgetCents(campaign),
|
|
116482
|
+
jsonOut: opts.jsonOut,
|
|
116483
|
+
account: opts.account,
|
|
116484
|
+
id: opts.id,
|
|
116485
|
+
section: "ad-campaign-edit",
|
|
116486
|
+
commandLabel: "ad campaign-edit",
|
|
116487
|
+
failureLabel: "\u7F16\u8F91\u5E7F\u544A\u7CFB\u5217\u5931\u8D25"
|
|
116488
|
+
});
|
|
116489
|
+
return;
|
|
116079
116490
|
}
|
|
116080
116491
|
const parts = [];
|
|
116081
116492
|
if (opts.name !== void 0) parts.push(`\u540D\u79F0\u2192${opts.name}`);
|
|
@@ -116085,9 +116496,34 @@ async function runAdCampaignEdit(opts) {
|
|
|
116085
116496
|
if (opts.targetSpendBidCeiling !== void 0) parts.push(`CPC\u4E0A\u9650\u2192${opts.targetSpendBidCeiling}`);
|
|
116086
116497
|
if (opts.targetCpa !== void 0) parts.push(`\u76EE\u6807CPA\u2192${opts.targetCpa}`);
|
|
116087
116498
|
if (opts.targetRoas !== void 0) parts.push(`\u76EE\u6807ROAS\u2192${opts.targetRoas}`);
|
|
116088
|
-
|
|
116089
|
-
|
|
116090
|
-
|
|
116499
|
+
const judged = await verifyCampaignAfterPut(
|
|
116500
|
+
detailUrl,
|
|
116501
|
+
config,
|
|
116502
|
+
{
|
|
116503
|
+
name: opts.name,
|
|
116504
|
+
budgetCents: opts.budget !== void 0 ? toCentAmount(opts.budget) : void 0,
|
|
116505
|
+
budgetWasRequested: opts.budget !== void 0,
|
|
116506
|
+
budgetCentsBefore: readCampaignBudgetCents(campaign),
|
|
116507
|
+
biddingStrategyTypeV2: opts.biddingStrategy?.toUpperCase(),
|
|
116508
|
+
targetSpendBidCeilingCents: opts.targetSpendBidCeiling !== void 0 ? toCentAmount(opts.targetSpendBidCeiling) : void 0,
|
|
116509
|
+
targetCpaCents: opts.targetCpa !== void 0 ? toCentAmount(opts.targetCpa) : void 0,
|
|
116510
|
+
targetRoas: opts.targetRoas,
|
|
116511
|
+
manualCpcEnhanced: opts.manualCpcEnhanced,
|
|
116512
|
+
targetSearchNetwork: opts.targetSearchNetwork,
|
|
116513
|
+
targetContentNetwork: opts.targetContentNetwork
|
|
116514
|
+
},
|
|
116515
|
+
opts.verbose
|
|
116516
|
+
);
|
|
116517
|
+
await reportCampaignWrite({
|
|
116518
|
+
jsonOut: opts.jsonOut,
|
|
116519
|
+
account: opts.account,
|
|
116520
|
+
id: opts.id,
|
|
116521
|
+
section: "ad-campaign-edit",
|
|
116522
|
+
commandLabel: "ad campaign-edit",
|
|
116523
|
+
verification: judged.verification,
|
|
116524
|
+
checks: judged.checks,
|
|
116525
|
+
detail: `\u5E7F\u544A\u7CFB\u5217 ${opts.id} \u5DF2\u6838\u9A8C${parts.length ? `\uFF08${parts.join("\uFF0C")}\uFF09` : ""}`
|
|
116526
|
+
});
|
|
116091
116527
|
}
|
|
116092
116528
|
|
|
116093
116529
|
// src/commands/ad/adgroup.ts
|
|
@@ -123928,7 +124364,7 @@ function register21(program2) {
|
|
|
123928
124364
|
});
|
|
123929
124365
|
}
|
|
123930
124366
|
);
|
|
123931
|
-
adCmd.command("campaign-status").description("\u5207\u6362\u5E7F\u544A\u7CFB\u5217\u72B6\u6001\uFF08Enabled / Paused\uFF09").requiredOption("-a, --account <id>", "Google \u8D26\u6237 mediaCustomerId").requiredOption("--id <campaignId>", "\u5E7F\u544A\u7CFB\u5217 ID").requiredOption("--status <status>", "\u76EE\u6807\u72B6\u6001\uFF1AEnabled | Paused").option("-t, --token <token>", "Auth Token").option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(
|
|
124367
|
+
adCmd.command("campaign-status").description("\u5207\u6362\u5E7F\u544A\u7CFB\u5217\u72B6\u6001\uFF08Enabled / Paused\uFF09").requiredOption("-a, --account <id>", "Google \u8D26\u6237 mediaCustomerId").requiredOption("--id <campaignId>", "\u5E7F\u544A\u7CFB\u5217 ID").requiredOption("--status <status>", "\u76EE\u6807\u72B6\u6001\uFF1AEnabled | Paused").option("--json-out <path>", "\u843D\u76D8\u6838\u9A8C\u7ED3\u679C\uFF08writeScope=gateway-full-put\uFF09").option("-t, --token <token>", "Auth Token").option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(
|
|
123932
124368
|
async (opts) => {
|
|
123933
124369
|
if (opts.status !== "Enabled" && opts.status !== "Paused") {
|
|
123934
124370
|
console.error("\n\u274C --status \u53EA\u63A5\u53D7 Enabled \u6216 Paused\n");
|
|
@@ -123939,6 +124375,7 @@ function register21(program2) {
|
|
|
123939
124375
|
account: opts.account,
|
|
123940
124376
|
id: opts.id,
|
|
123941
124377
|
status: opts.status,
|
|
124378
|
+
jsonOut: opts.jsonOut,
|
|
123942
124379
|
verbose: opts.verbose
|
|
123943
124380
|
});
|
|
123944
124381
|
}
|
|
@@ -124124,7 +124561,7 @@ function register21(program2) {
|
|
|
124124
124561
|
).option(
|
|
124125
124562
|
"--adgroup-id <id>",
|
|
124126
124563
|
"\u5E7F\u544A\u7EC4 ID\uFF08level=AdGroup \u5FC5\u4F20\uFF1B\u672A\u4F20 --level \u65F6\u6307\u5B9A\u5373\u89C6\u4E3A AdGroup \u5C42\u7EA7\uFF09"
|
|
124127
|
-
).option("--adgroup-name <name>", "\u5E7F\u544A\u7EC4\u540D\u79F0").option("-t, --token <token>", "Auth Token").option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(
|
|
124564
|
+
).option("--adgroup-name <name>", "\u5E7F\u544A\u7EC4\u540D\u79F0").option("--json-out <path>", "\u843D\u76D8\u6838\u9A8C\u7ED3\u679C\uFF08\u6210\u529F\u6761\u6570\u6765\u81EA\u56DE\u8BFB\uFF0C\u4E0D\u662F\u8BF7\u6C42\u6761\u6570\uFF09").option("-t, --token <token>", "Auth Token").option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(
|
|
124128
124565
|
async (opts) => {
|
|
124129
124566
|
if (opts.level && !["Account", "Campaign", "AdGroup"].includes(opts.level)) {
|
|
124130
124567
|
console.error("\n\u274C --level \u53EA\u63A5\u53D7 Account | Campaign | AdGroup\n");
|
|
@@ -124141,6 +124578,7 @@ function register21(program2) {
|
|
|
124141
124578
|
keywords: opts.keywords,
|
|
124142
124579
|
keywordsFile: opts.keywordsFile,
|
|
124143
124580
|
matchType: parseOptionalMatchType(opts.matchType),
|
|
124581
|
+
jsonOut: opts.jsonOut,
|
|
124144
124582
|
verbose: opts.verbose
|
|
124145
124583
|
});
|
|
124146
124584
|
}
|
|
@@ -124455,7 +124893,7 @@ function register21(program2) {
|
|
|
124455
124893
|
"--search-network <bool>",
|
|
124456
124894
|
"\u6295\u653E\u5230 Google \u641C\u7D22\uFF1Atrue | false",
|
|
124457
124895
|
(v) => v === "true"
|
|
124458
|
-
).option("--content-network <bool>", "\u6295\u653E\u5230\u5C55\u793A\u7F51\u7EDC\uFF1Atrue | false", (v) => v === "true").option("-t, --token <token>", "Auth Token").option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(
|
|
124896
|
+
).option("--content-network <bool>", "\u6295\u653E\u5230\u5C55\u793A\u7F51\u7EDC\uFF1Atrue | false", (v) => v === "true").option("--json-out <path>", "\u843D\u76D8\u6838\u9A8C\u7ED3\u679C\uFF08writeScope=gateway-full-put\uFF09").option("-t, --token <token>", "Auth Token").option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(
|
|
124459
124897
|
async (opts) => {
|
|
124460
124898
|
await runAdCampaignEdit({
|
|
124461
124899
|
token: opts.token,
|
|
@@ -124470,6 +124908,7 @@ function register21(program2) {
|
|
|
124470
124908
|
manualCpcEnhanced: opts.manualEcpc,
|
|
124471
124909
|
targetSearchNetwork: opts.searchNetwork,
|
|
124472
124910
|
targetContentNetwork: opts.contentNetwork,
|
|
124911
|
+
jsonOut: opts.jsonOut,
|
|
124473
124912
|
verbose: opts.verbose
|
|
124474
124913
|
});
|
|
124475
124914
|
}
|
|
@@ -125193,13 +125632,14 @@ function failMetaAd(action, err) {
|
|
|
125193
125632
|
`);
|
|
125194
125633
|
process.exit(1);
|
|
125195
125634
|
}
|
|
125196
|
-
async function emitMetaJson(opts, section, commandLabel, payload, idSuffix) {
|
|
125635
|
+
async function emitMetaJson(opts, section, commandLabel, payload, idSuffix, summaryExtras) {
|
|
125197
125636
|
return emitCliJsonOrSnapshot(opts, {
|
|
125198
125637
|
section,
|
|
125199
125638
|
commandLabel,
|
|
125200
125639
|
commandHint: idSuffix,
|
|
125201
125640
|
payload,
|
|
125202
|
-
idSuffix
|
|
125641
|
+
idSuffix,
|
|
125642
|
+
summaryExtras
|
|
125203
125643
|
});
|
|
125204
125644
|
}
|
|
125205
125645
|
function isHttpUrl(value) {
|
|
@@ -125480,6 +125920,10 @@ function buildUpdateAdSetBody(opts) {
|
|
|
125480
125920
|
if (opts.startTime != null) body.start_time = opts.startTime;
|
|
125481
125921
|
if (opts.endTime != null) body.end_time = opts.endTime;
|
|
125482
125922
|
if (opts.status != null) body.status = opts.status;
|
|
125923
|
+
if (opts.targeting && Object.keys(opts.targeting).length > 0) {
|
|
125924
|
+
body.targeting = opts.targeting;
|
|
125925
|
+
return body;
|
|
125926
|
+
}
|
|
125483
125927
|
const hasTargeting = opts.countries != null || opts.ageMin != null || opts.ageMax != null || opts.genders != null || opts.flexibleSpec != null || opts.advantageAudience != null;
|
|
125484
125928
|
if (hasTargeting) {
|
|
125485
125929
|
const targeting = {};
|
|
@@ -125490,17 +125934,221 @@ function buildUpdateAdSetBody(opts) {
|
|
|
125490
125934
|
if (opts.ageMax != null) targeting.age_max = opts.ageMax;
|
|
125491
125935
|
if (opts.genders) targeting.genders = opts.genders;
|
|
125492
125936
|
if (opts.flexibleSpec != null) targeting.flexible_spec = opts.flexibleSpec;
|
|
125493
|
-
|
|
125494
|
-
targeting,
|
|
125495
|
-
|
|
125496
|
-
advantageAudience: opts.advantageAudience,
|
|
125497
|
-
flexibleSpec: opts.flexibleSpec
|
|
125498
|
-
})
|
|
125499
|
-
);
|
|
125937
|
+
if (opts.advantageAudience === 0 || opts.advantageAudience === 1) {
|
|
125938
|
+
applyLeadAdTargetingAutomation(targeting, opts.advantageAudience);
|
|
125939
|
+
}
|
|
125500
125940
|
body.targeting = targeting;
|
|
125501
125941
|
}
|
|
125502
125942
|
return body;
|
|
125503
125943
|
}
|
|
125944
|
+
var ADSET_TARGETING_TOP_KEYS = /* @__PURE__ */ new Set([
|
|
125945
|
+
"geo_locations",
|
|
125946
|
+
"age_min",
|
|
125947
|
+
"age_max",
|
|
125948
|
+
"genders",
|
|
125949
|
+
"flexible_spec",
|
|
125950
|
+
"targeting_automation"
|
|
125951
|
+
]);
|
|
125952
|
+
function isPlainObject(value) {
|
|
125953
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
125954
|
+
}
|
|
125955
|
+
function readFiniteNumber(value) {
|
|
125956
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
125957
|
+
if (typeof value === "string" && value.trim() !== "") {
|
|
125958
|
+
const n = Number(value);
|
|
125959
|
+
if (Number.isFinite(n)) return n;
|
|
125960
|
+
}
|
|
125961
|
+
return void 0;
|
|
125962
|
+
}
|
|
125963
|
+
function upperUnique(list) {
|
|
125964
|
+
const seen = /* @__PURE__ */ new Set();
|
|
125965
|
+
const out = [];
|
|
125966
|
+
for (const raw of list) {
|
|
125967
|
+
const code = raw.trim().toUpperCase();
|
|
125968
|
+
if (!code || seen.has(code)) continue;
|
|
125969
|
+
seen.add(code);
|
|
125970
|
+
out.push(code);
|
|
125971
|
+
}
|
|
125972
|
+
return out;
|
|
125973
|
+
}
|
|
125974
|
+
function readCountries(targeting) {
|
|
125975
|
+
const geo = targeting?.["geo_locations"];
|
|
125976
|
+
if (!isPlainObject(geo) || !Array.isArray(geo["countries"])) return void 0;
|
|
125977
|
+
const countries = upperUnique(geo["countries"].map((c) => String(c)));
|
|
125978
|
+
return countries.length > 0 ? countries : void 0;
|
|
125979
|
+
}
|
|
125980
|
+
function readAdvantage(targeting) {
|
|
125981
|
+
const auto = targeting?.["targeting_automation"];
|
|
125982
|
+
if (!isPlainObject(auto)) return void 0;
|
|
125983
|
+
const value = readFiniteNumber(auto["advantage_audience"]);
|
|
125984
|
+
if (value === 0 || value === 1) return value;
|
|
125985
|
+
return void 0;
|
|
125986
|
+
}
|
|
125987
|
+
function unsupportedTargetingKeys(current) {
|
|
125988
|
+
if (!current) return [];
|
|
125989
|
+
const keys = [];
|
|
125990
|
+
for (const key of Object.keys(current)) {
|
|
125991
|
+
if (!ADSET_TARGETING_TOP_KEYS.has(key)) keys.push(key);
|
|
125992
|
+
}
|
|
125993
|
+
const geo = current["geo_locations"];
|
|
125994
|
+
if (isPlainObject(geo)) {
|
|
125995
|
+
for (const key of Object.keys(geo)) {
|
|
125996
|
+
if (key !== "countries") keys.push(`geo_locations.${key}`);
|
|
125997
|
+
}
|
|
125998
|
+
}
|
|
125999
|
+
const auto = current["targeting_automation"];
|
|
126000
|
+
if (isPlainObject(auto)) {
|
|
126001
|
+
for (const key of Object.keys(auto)) {
|
|
126002
|
+
if (key !== "advantage_audience") keys.push(`targeting_automation.${key}`);
|
|
126003
|
+
}
|
|
126004
|
+
}
|
|
126005
|
+
return keys;
|
|
126006
|
+
}
|
|
126007
|
+
function snapshotSupported(targeting) {
|
|
126008
|
+
const snap = {};
|
|
126009
|
+
if (!targeting) return snap;
|
|
126010
|
+
const countries = readCountries(targeting);
|
|
126011
|
+
if (countries) snap.countries = countries;
|
|
126012
|
+
const ageMin = readFiniteNumber(targeting["age_min"]);
|
|
126013
|
+
const ageMax = readFiniteNumber(targeting["age_max"]);
|
|
126014
|
+
if (ageMin !== void 0) snap.age_min = ageMin;
|
|
126015
|
+
if (ageMax !== void 0) snap.age_max = ageMax;
|
|
126016
|
+
if (Array.isArray(targeting["genders"])) snap.genders = targeting["genders"];
|
|
126017
|
+
if (targeting["flexible_spec"] !== void 0) snap.flexible_spec = targeting["flexible_spec"];
|
|
126018
|
+
const advantage = readAdvantage(targeting);
|
|
126019
|
+
if (advantage !== void 0) snap.advantage_audience = advantage;
|
|
126020
|
+
return snap;
|
|
126021
|
+
}
|
|
126022
|
+
function diffSnapshots(before, after) {
|
|
126023
|
+
const fields = /* @__PURE__ */ new Set([...Object.keys(before), ...Object.keys(after)]);
|
|
126024
|
+
const changes = [];
|
|
126025
|
+
for (const field of fields) {
|
|
126026
|
+
const from = before[field];
|
|
126027
|
+
const to = after[field];
|
|
126028
|
+
const changed = JSON.stringify(from) !== JSON.stringify(to);
|
|
126029
|
+
changes.push({ field, from: from ?? null, to: to ?? null, changed });
|
|
126030
|
+
}
|
|
126031
|
+
return changes;
|
|
126032
|
+
}
|
|
126033
|
+
function mergeAdSetTargeting(current, patch, opts) {
|
|
126034
|
+
const source = current ?? void 0;
|
|
126035
|
+
const unsupported = unsupportedTargetingKeys(source);
|
|
126036
|
+
if (unsupported.length > 0 && !opts?.allowDropUnsupported) {
|
|
126037
|
+
return { ok: false, reason: "unsupported", unsupportedKeys: unsupported };
|
|
126038
|
+
}
|
|
126039
|
+
const before = snapshotSupported(source);
|
|
126040
|
+
const next = {};
|
|
126041
|
+
let countries = readCountries(source);
|
|
126042
|
+
if (patch.countries) {
|
|
126043
|
+
const incoming = upperUnique(patch.countries);
|
|
126044
|
+
countries = (patch.countriesMode ?? "replace") === "append" && countries ? upperUnique([...countries, ...incoming]) : incoming;
|
|
126045
|
+
}
|
|
126046
|
+
if (countries && countries.length > 0) {
|
|
126047
|
+
next.geo_locations = { countries };
|
|
126048
|
+
}
|
|
126049
|
+
const ageMin = patch.ageMin != null ? patch.ageMin : readFiniteNumber(source?.["age_min"]);
|
|
126050
|
+
const ageMax = patch.ageMax != null ? patch.ageMax : readFiniteNumber(source?.["age_max"]);
|
|
126051
|
+
if (ageMin !== void 0) next.age_min = ageMin;
|
|
126052
|
+
if (ageMax !== void 0) next.age_max = ageMax;
|
|
126053
|
+
const genders = patch.genders ?? source?.["genders"];
|
|
126054
|
+
if (Array.isArray(genders)) next.genders = genders;
|
|
126055
|
+
const flexible = patch.flexibleSpec ?? source?.["flexible_spec"];
|
|
126056
|
+
if (flexible !== void 0 && Array.isArray(flexible)) next.flexible_spec = flexible;
|
|
126057
|
+
const explicitAdvantage = patch.advantageAudience === 0 || patch.advantageAudience === 1 ? patch.advantageAudience : void 0;
|
|
126058
|
+
const advantage = explicitAdvantage ?? readAdvantage(source);
|
|
126059
|
+
if (advantage === void 0) {
|
|
126060
|
+
return { ok: false, reason: "advantage_unset" };
|
|
126061
|
+
}
|
|
126062
|
+
next.targeting_automation = { advantage_audience: advantage };
|
|
126063
|
+
return {
|
|
126064
|
+
ok: true,
|
|
126065
|
+
targeting: next,
|
|
126066
|
+
changes: diffSnapshots(before, snapshotSupported(next)),
|
|
126067
|
+
droppedKeys: opts?.allowDropUnsupported ? unsupported : []
|
|
126068
|
+
};
|
|
126069
|
+
}
|
|
126070
|
+
function sortedCountries(value) {
|
|
126071
|
+
if (!Array.isArray(value)) return [];
|
|
126072
|
+
return upperUnique(value.map((c) => String(c))).sort();
|
|
126073
|
+
}
|
|
126074
|
+
function targetingReadbackChecks(expected, actual) {
|
|
126075
|
+
const checks = [];
|
|
126076
|
+
const parentRead = actual !== void 0;
|
|
126077
|
+
if (expected["geo_locations"] !== void 0) {
|
|
126078
|
+
const exp = sortedCountries(
|
|
126079
|
+
isPlainObject(expected["geo_locations"]) ? expected["geo_locations"]["countries"] : []
|
|
126080
|
+
);
|
|
126081
|
+
const act = sortedCountries(
|
|
126082
|
+
actual && isPlainObject(actual["geo_locations"]) ? actual["geo_locations"]["countries"] : []
|
|
126083
|
+
);
|
|
126084
|
+
checks.push(fieldCheck("targeting.countries", exp, parentRead ? act : void 0, parentRead));
|
|
126085
|
+
}
|
|
126086
|
+
for (const key of ["age_min", "age_max"]) {
|
|
126087
|
+
if (expected[key] === void 0) continue;
|
|
126088
|
+
const raw = actual?.[key];
|
|
126089
|
+
checks.push(
|
|
126090
|
+
fieldCheck(
|
|
126091
|
+
`targeting.${key}`,
|
|
126092
|
+
expected[key],
|
|
126093
|
+
parentRead ? readFiniteNumber(raw) : void 0,
|
|
126094
|
+
parentRead
|
|
126095
|
+
)
|
|
126096
|
+
);
|
|
126097
|
+
}
|
|
126098
|
+
if (expected["genders"] !== void 0) {
|
|
126099
|
+
const exp = Array.isArray(expected["genders"]) ? [...expected["genders"]].sort() : [];
|
|
126100
|
+
const act = actual && Array.isArray(actual["genders"]) ? [...actual["genders"]].sort() : [];
|
|
126101
|
+
checks.push(fieldCheck("targeting.genders", exp, parentRead ? act : void 0, parentRead));
|
|
126102
|
+
}
|
|
126103
|
+
if (expected["flexible_spec"] !== void 0) {
|
|
126104
|
+
const exp = expected["flexible_spec"];
|
|
126105
|
+
const act = actual?.["flexible_spec"];
|
|
126106
|
+
const match = JSON.stringify(exp) === JSON.stringify(act);
|
|
126107
|
+
checks.push({
|
|
126108
|
+
field: "targeting.flexible_spec",
|
|
126109
|
+
expected: exp,
|
|
126110
|
+
actual: parentRead ? act : void 0,
|
|
126111
|
+
state: !parentRead ? "unread" : match ? "match" : "mismatch"
|
|
126112
|
+
});
|
|
126113
|
+
}
|
|
126114
|
+
if (isPlainObject(expected["targeting_automation"])) {
|
|
126115
|
+
const exp = expected["targeting_automation"]["advantage_audience"];
|
|
126116
|
+
const act = isPlainObject(actual?.["targeting_automation"]) ? readFiniteNumber(actual["targeting_automation"]["advantage_audience"]) : void 0;
|
|
126117
|
+
checks.push(fieldCheck("targeting.advantage_audience", exp, act, parentRead));
|
|
126118
|
+
}
|
|
126119
|
+
return checks;
|
|
126120
|
+
}
|
|
126121
|
+
var ADSET_SCALAR_READBACK_KEYS = [
|
|
126122
|
+
"name",
|
|
126123
|
+
"status",
|
|
126124
|
+
"daily_budget",
|
|
126125
|
+
"lifetime_budget",
|
|
126126
|
+
"optimization_goal",
|
|
126127
|
+
"bid_strategy",
|
|
126128
|
+
"bid_amount"
|
|
126129
|
+
];
|
|
126130
|
+
function evaluateAdSetEditReadback(sentBody, actual, readFailed) {
|
|
126131
|
+
if (readFailed || !actual) return { verification: "unknown", checks: [] };
|
|
126132
|
+
const checks = [];
|
|
126133
|
+
for (const key of ADSET_SCALAR_READBACK_KEYS) {
|
|
126134
|
+
if (sentBody[key] === void 0) continue;
|
|
126135
|
+
const present = Object.prototype.hasOwnProperty.call(actual, key);
|
|
126136
|
+
const money2 = key === "daily_budget" || key === "lifetime_budget" || key === "bid_amount";
|
|
126137
|
+
const expected = money2 ? String(sentBody[key]) : sentBody[key];
|
|
126138
|
+
const actualValue = money2 && actual[key] != null && actual[key] !== "" ? String(actual[key]) : actual[key];
|
|
126139
|
+
checks.push(fieldCheck(key, expected, actualValue, present));
|
|
126140
|
+
}
|
|
126141
|
+
if (isPlainObject(sentBody["targeting"])) {
|
|
126142
|
+
const actualTargeting = isPlainObject(actual["targeting"]) ? actual["targeting"] : void 0;
|
|
126143
|
+
checks.push(
|
|
126144
|
+
...targetingReadbackChecks(
|
|
126145
|
+
sentBody["targeting"],
|
|
126146
|
+
Object.prototype.hasOwnProperty.call(actual, "targeting") ? actualTargeting : void 0
|
|
126147
|
+
)
|
|
126148
|
+
);
|
|
126149
|
+
}
|
|
126150
|
+
return { verification: classifyFieldChecks(checks, false), checks };
|
|
126151
|
+
}
|
|
125504
126152
|
function buildUpdateAdBody(opts) {
|
|
125505
126153
|
const body = {};
|
|
125506
126154
|
if (opts.name != null) body.name = opts.name;
|
|
@@ -125713,6 +126361,9 @@ function runMetaLeadValidation(cfg) {
|
|
|
125713
126361
|
if (adset.bidAmount != null && !(Number.isFinite(adset.bidAmount) && adset.bidAmount > 0)) {
|
|
125714
126362
|
errors.push("adset.bidAmount \u987B\u4E3A\u5927\u4E8E 0 \u7684\u6570\u5B57\uFF08\u4E3B\u5E01\u79CD\u5143\uFF09");
|
|
125715
126363
|
}
|
|
126364
|
+
if (adset.bidAmountOnRequired != null && !(Number.isFinite(adset.bidAmountOnRequired) && adset.bidAmountOnRequired > 0)) {
|
|
126365
|
+
errors.push("adset.bidAmountOnRequired \u987B\u4E3A\u5927\u4E8E 0 \u7684\u6570\u5B57\uFF08\u4E3B\u5E01\u79CD\u5143\uFF09");
|
|
126366
|
+
}
|
|
125716
126367
|
if (adset.advantageAudience != null && adset.advantageAudience !== 0 && adset.advantageAudience !== 1) {
|
|
125717
126368
|
errors.push("adset.advantageAudience \u53EA\u5141\u8BB8 0 \u6216 1\uFF08targeting_automation.advantage_audience\uFF09");
|
|
125718
126369
|
}
|
|
@@ -125845,6 +126496,46 @@ async function runMetaAdValidate(opts) {
|
|
|
125845
126496
|
console.log("\n\u2705 Meta Lead \u914D\u7F6E\u6821\u9A8C\u901A\u8FC7\uFF08\u672C\u5730\uFF0C\u672A\u8C03\u7F51\u5173\uFF09\n");
|
|
125846
126497
|
}
|
|
125847
126498
|
|
|
126499
|
+
// src/commands/meta-ad/bid-recovery.ts
|
|
126500
|
+
function decideBidAmountRecovery(input) {
|
|
126501
|
+
if (input.bidAmountAlreadySet) return { action: "abort" };
|
|
126502
|
+
const authorized = input.authorizedBidAmount;
|
|
126503
|
+
if (typeof authorized === "number" && Number.isFinite(authorized) && authorized > 0) {
|
|
126504
|
+
return {
|
|
126505
|
+
action: "retry",
|
|
126506
|
+
bidStrategy: "LOWEST_COST_WITH_BID_CAP",
|
|
126507
|
+
bidAmount: authorized,
|
|
126508
|
+
recovery: {
|
|
126509
|
+
applied: true,
|
|
126510
|
+
fromStrategy: input.originalBidStrategy,
|
|
126511
|
+
toStrategy: "LOWEST_COST_WITH_BID_CAP",
|
|
126512
|
+
bidAmount: authorized
|
|
126513
|
+
}
|
|
126514
|
+
};
|
|
126515
|
+
}
|
|
126516
|
+
const budgetNote = [
|
|
126517
|
+
input.dailyBudget != null ? `\u65E5\u9884\u7B97 ${input.dailyBudget}` : "",
|
|
126518
|
+
input.lifetimeBudget != null ? `\u603B\u9884\u7B97 ${input.lifetimeBudget}` : "",
|
|
126519
|
+
input.currency ? input.currency : ""
|
|
126520
|
+
].filter(Boolean).join(" ");
|
|
126521
|
+
return {
|
|
126522
|
+
action: "needs_decision",
|
|
126523
|
+
needsDecision: {
|
|
126524
|
+
code: "BID_AMOUNT_REQUIRED",
|
|
126525
|
+
status: "needs_decision",
|
|
126526
|
+
campaignId: input.campaignId,
|
|
126527
|
+
originalBidStrategy: input.originalBidStrategy,
|
|
126528
|
+
proposedBidStrategy: "LOWEST_COST_WITH_BID_CAP",
|
|
126529
|
+
message: "\u8BE5\u6237\u8981\u6C42\u51FA\u4EF7\u4E0A\u9650\uFF08LOWEST_COST_WITH_BID_CAP\uFF09\u3002\u9884\u7B97\u91D1\u989D\u4E0D\u80FD\u5F53\u4F5C bid cap\u3002\u8BF7\u5355\u72EC\u586B\u5199\u51FA\u4EF7\u540E\u518D\u7EE7\u7EED\uFF1Acreate JSON \u7528 adset.bidAmountOnRequired\uFF0C\u6216 adset-create \u7528 --bid-amount-on-required\u3002" + (budgetNote ? ` \u5F53\u524D\u9884\u7B97\u4EC5\u4F5C\u4E0A\u4E0B\u6587\uFF08${budgetNote}\uFF09\uFF0C\u4E0D\u4F1A\u5199\u5165 bid_amount\u3002` : ""),
|
|
126530
|
+
budgetContext: {
|
|
126531
|
+
dailyBudget: input.dailyBudget,
|
|
126532
|
+
lifetimeBudget: input.lifetimeBudget,
|
|
126533
|
+
currency: input.currency
|
|
126534
|
+
}
|
|
126535
|
+
}
|
|
126536
|
+
};
|
|
126537
|
+
}
|
|
126538
|
+
|
|
125848
126539
|
// src/commands/meta-ad/create.ts
|
|
125849
126540
|
function resolveBesideConfig(configFile, p) {
|
|
125850
126541
|
if (isAbsolute7(p)) return p;
|
|
@@ -126011,23 +126702,47 @@ async function runMetaAdCreate(opts) {
|
|
|
126011
126702
|
);
|
|
126012
126703
|
} catch (firstErr) {
|
|
126013
126704
|
if (!isBidAmountRequired(firstErr) || cfg.adset.bidAmount != null) throw firstErr;
|
|
126014
|
-
const
|
|
126015
|
-
|
|
126016
|
-
|
|
126705
|
+
const decision = decideBidAmountRecovery({
|
|
126706
|
+
bidAmountAlreadySet: false,
|
|
126707
|
+
authorizedBidAmount: cfg.adset.bidAmountOnRequired,
|
|
126708
|
+
originalBidStrategy: baseAdset.bidStrategy,
|
|
126709
|
+
dailyBudget: cfg.campaign.dailyBudget ?? cfg.adset.dailyBudget,
|
|
126710
|
+
lifetimeBudget: cfg.campaign.lifetimeBudget ?? cfg.adset.lifetimeBudget,
|
|
126711
|
+
currency,
|
|
126712
|
+
campaignId: progress.campaignId
|
|
126713
|
+
});
|
|
126714
|
+
if (decision.action === "needs_decision") {
|
|
126715
|
+
progress.failedStep = "adset";
|
|
126716
|
+
progress.needsDecision = decision.needsDecision;
|
|
126717
|
+
progress.error = decision.needsDecision.message;
|
|
126718
|
+
await emitProgress();
|
|
126719
|
+
console.error(
|
|
126017
126720
|
`
|
|
126018
|
-
\u26A0\uFE0F
|
|
126721
|
+
\u26A0\uFE0F ${decision.needsDecision.message}
|
|
126722
|
+
\u5DF2\u5EFA\u6210\uFF1A${summarizeProgress(progress)}
|
|
126723
|
+
\u539F\u7B56\u7565\uFF1A${decision.needsDecision.originalBidStrategy ?? "\uFF08\u672A\u6307\u5B9A\uFF09"}
|
|
126724
|
+
\u62DF\u8BAE\u7B56\u7565\uFF1A${decision.needsDecision.proposedBidStrategy}
|
|
126725
|
+
\u672C\u6B21\u672A\u6539\u51FA\u4EF7\uFF0C\u672A\u518D\u6B21\u5EFA\u7EC4\u3002
|
|
126019
126726
|
`
|
|
126020
126727
|
);
|
|
126728
|
+
process.exit(2);
|
|
126021
126729
|
}
|
|
126730
|
+
if (decision.action !== "retry") throw firstErr;
|
|
126731
|
+
console.warn(
|
|
126732
|
+
`
|
|
126733
|
+
\u26A0\uFE0F \u5DF2\u6309\u4E8B\u524D\u6388\u6743\u7684 bid cap ${decision.bidAmount} ${currency} \u4EE5 LOWEST_COST_WITH_BID_CAP \u91CD\u8BD5\u5EFA\u7EC4\uFF08\u539F\u7B56\u7565 ${decision.recovery.fromStrategy ?? "\u672A\u6307\u5B9A"}\uFF09
|
|
126734
|
+
`
|
|
126735
|
+
);
|
|
126736
|
+
progress.recovery = decision.recovery;
|
|
126022
126737
|
data = await postJson(
|
|
126023
126738
|
adsetUrl,
|
|
126024
126739
|
config,
|
|
126025
126740
|
buildCreateAdSetBody({
|
|
126026
|
-
adset: { ...baseAdset, bidStrategy:
|
|
126741
|
+
adset: { ...baseAdset, bidStrategy: decision.bidStrategy },
|
|
126027
126742
|
campaignId: progress.campaignId,
|
|
126028
126743
|
pageId,
|
|
126029
126744
|
...money2,
|
|
126030
|
-
bidAmountMinor: toMinorUnitString(
|
|
126745
|
+
bidAmountMinor: toMinorUnitString(decision.bidAmount, currency)
|
|
126031
126746
|
}),
|
|
126032
126747
|
opts.verbose
|
|
126033
126748
|
);
|
|
@@ -126856,6 +127571,28 @@ async function runMetaAdCampaignStatus(opts) {
|
|
|
126856
127571
|
// src/commands/meta-ad/adset.ts
|
|
126857
127572
|
init_cli_table();
|
|
126858
127573
|
init_auth();
|
|
127574
|
+
var READBACK_WAIT_MS = 1e3;
|
|
127575
|
+
function parseCountriesMode(raw) {
|
|
127576
|
+
if (!raw || raw === "replace") return "replace";
|
|
127577
|
+
if (raw === "append") return "append";
|
|
127578
|
+
throw new Error("--countries-mode \u53EA\u5141\u8BB8 append \u6216 replace");
|
|
127579
|
+
}
|
|
127580
|
+
function isRecord2(value) {
|
|
127581
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
127582
|
+
}
|
|
127583
|
+
function formatTargetingChange(change) {
|
|
127584
|
+
const from = change.from == null ? "\uFF08\u65E0\uFF09" : JSON.stringify(change.from);
|
|
127585
|
+
const to = change.to == null ? "\uFF08\u65E0\uFF09" : JSON.stringify(change.to);
|
|
127586
|
+
return change.changed ? `${change.field}\uFF1A${from} \u2192 ${to}` : `${change.field}\uFF1A${to}\uFF08\u4FDD\u6301\uFF09`;
|
|
127587
|
+
}
|
|
127588
|
+
async function readAdSetForVerify(url, config, verbose) {
|
|
127589
|
+
try {
|
|
127590
|
+
const raw = await apiFetch2(url, config, {}, verbose);
|
|
127591
|
+
return { adset: raw, readFailed: false };
|
|
127592
|
+
} catch {
|
|
127593
|
+
return { readFailed: true };
|
|
127594
|
+
}
|
|
127595
|
+
}
|
|
126859
127596
|
function parseGenders(raw) {
|
|
126860
127597
|
if (!raw?.trim()) return void 0;
|
|
126861
127598
|
const nums = raw.split(",").map((s) => Number(s.trim()));
|
|
@@ -127002,14 +127739,37 @@ async function runMetaAdAdsetCreate(opts) {
|
|
|
127002
127739
|
if (!isBidAmountRequired(firstErr) || adset.bidAmount != null) {
|
|
127003
127740
|
failMetaAd("\u5EFA\u5E7F\u544A\u7EC4\u5931\u8D25", firstErr);
|
|
127004
127741
|
}
|
|
127005
|
-
const
|
|
127006
|
-
|
|
127007
|
-
|
|
127742
|
+
const decision = decideBidAmountRecovery({
|
|
127743
|
+
bidAmountAlreadySet: false,
|
|
127744
|
+
authorizedBidAmount: opts.bidAmountOnRequired,
|
|
127745
|
+
originalBidStrategy: adset.bidStrategy,
|
|
127746
|
+
dailyBudget: daily,
|
|
127747
|
+
lifetimeBudget: lifetime,
|
|
127748
|
+
currency,
|
|
127749
|
+
campaignId: opts.campaignId.trim()
|
|
127750
|
+
});
|
|
127751
|
+
if (decision.action === "needs_decision") {
|
|
127752
|
+
const payload3 = { ...decision.needsDecision, currency };
|
|
127753
|
+
console.error(
|
|
127008
127754
|
`
|
|
127009
|
-
\u26A0\uFE0F
|
|
127755
|
+
\u26A0\uFE0F ${decision.needsDecision.message}
|
|
127756
|
+
\u539F\u7B56\u7565\uFF1A${decision.needsDecision.originalBidStrategy ?? "\uFF08\u672A\u6307\u5B9A\uFF09"}
|
|
127757
|
+
\u62DF\u8BAE\u7B56\u7565\uFF1A${decision.needsDecision.proposedBidStrategy}
|
|
127758
|
+
campaignId\uFF1A${opts.campaignId.trim()}
|
|
127759
|
+
\u672C\u6B21\u672A\u6539\u51FA\u4EF7\uFF0C\u672A\u518D\u6B21\u5EFA\u7EC4\u3002
|
|
127010
127760
|
`
|
|
127011
127761
|
);
|
|
127762
|
+
await emitMetaJson(opts, "meta-ad-adset-create", "meta-ad adset-create", payload3, digits, {
|
|
127763
|
+
status: "needs_decision"
|
|
127764
|
+
});
|
|
127765
|
+
process.exit(2);
|
|
127012
127766
|
}
|
|
127767
|
+
if (decision.action !== "retry") failMetaAd("\u5EFA\u5E7F\u544A\u7EC4\u5931\u8D25", firstErr);
|
|
127768
|
+
console.warn(
|
|
127769
|
+
`
|
|
127770
|
+
\u26A0\uFE0F \u5DF2\u6309\u4E8B\u524D\u6388\u6743\u7684 bid cap ${decision.bidAmount} ${currency} \u4EE5 LOWEST_COST_WITH_BID_CAP \u91CD\u8BD5\u5EFA\u7EC4\uFF08\u539F\u7B56\u7565 ${decision.recovery.fromStrategy ?? "\u672A\u6307\u5B9A"}\uFF09
|
|
127771
|
+
`
|
|
127772
|
+
);
|
|
127013
127773
|
try {
|
|
127014
127774
|
data = await apiFetch2(
|
|
127015
127775
|
adsetUrl,
|
|
@@ -127018,12 +127778,12 @@ async function runMetaAdAdsetCreate(opts) {
|
|
|
127018
127778
|
method: "POST",
|
|
127019
127779
|
body: JSON.stringify(
|
|
127020
127780
|
buildCreateAdSetBody({
|
|
127021
|
-
adset: { ...adset, bidStrategy:
|
|
127781
|
+
adset: { ...adset, bidStrategy: decision.bidStrategy },
|
|
127022
127782
|
campaignId: opts.campaignId.trim(),
|
|
127023
127783
|
pageId: opts.pageId.trim(),
|
|
127024
127784
|
dailyBudgetMinor: daily != null ? toMinorUnitString(daily, currency) : void 0,
|
|
127025
127785
|
lifetimeBudgetMinor: lifetime != null ? toMinorUnitString(lifetime, currency) : void 0,
|
|
127026
|
-
bidAmountMinor: toMinorUnitString(
|
|
127786
|
+
bidAmountMinor: toMinorUnitString(decision.bidAmount, currency)
|
|
127027
127787
|
})
|
|
127028
127788
|
)
|
|
127029
127789
|
},
|
|
@@ -127032,6 +127792,15 @@ async function runMetaAdAdsetCreate(opts) {
|
|
|
127032
127792
|
} catch (retryErr) {
|
|
127033
127793
|
failMetaAd("\u5EFA\u5E7F\u544A\u7EC4\u5931\u8D25", retryErr);
|
|
127034
127794
|
}
|
|
127795
|
+
const id2 = extractCreatedId(data);
|
|
127796
|
+
const payload2 = { ...data, id: id2, currency, recovery: decision.recovery };
|
|
127797
|
+
if (await emitMetaJson(opts, "meta-ad-adset-create", "meta-ad adset-create", payload2, digits)) {
|
|
127798
|
+
return;
|
|
127799
|
+
}
|
|
127800
|
+
console.log(`
|
|
127801
|
+
\u2705 \u5E7F\u544A\u7EC4\u5DF2\u521B\u5EFA\uFF1A${id2 ?? JSON.stringify(data)}
|
|
127802
|
+
`);
|
|
127803
|
+
return;
|
|
127035
127804
|
}
|
|
127036
127805
|
const id = extractCreatedId(data);
|
|
127037
127806
|
const payload = { ...data, id, currency };
|
|
@@ -127053,10 +127822,12 @@ async function runMetaAdAdsetEdit(opts) {
|
|
|
127053
127822
|
let countries;
|
|
127054
127823
|
let genders;
|
|
127055
127824
|
let advantageAudience;
|
|
127825
|
+
let countriesMode;
|
|
127056
127826
|
try {
|
|
127057
127827
|
if (opts.countries != null) countries = assertCountries(opts.countries);
|
|
127058
127828
|
genders = parseGenders(opts.genders);
|
|
127059
127829
|
advantageAudience = parseAdvantageAudience(opts.advantageAudience);
|
|
127830
|
+
countriesMode = parseCountriesMode(opts.countriesMode);
|
|
127060
127831
|
} catch (err) {
|
|
127061
127832
|
console.error(`
|
|
127062
127833
|
\u274C ${err instanceof Error ? err.message : String(err)}
|
|
@@ -127070,26 +127841,87 @@ async function runMetaAdAdsetEdit(opts) {
|
|
|
127070
127841
|
process.exit(1);
|
|
127071
127842
|
}
|
|
127072
127843
|
}
|
|
127844
|
+
const ageMin = parseOptionalNumber(opts.ageMin);
|
|
127845
|
+
const ageMax = parseOptionalNumber(opts.ageMax);
|
|
127846
|
+
const wantsTargeting = countries != null || ageMin != null || ageMax != null || genders != null || advantageAudience != null;
|
|
127073
127847
|
const { config, facebookApiUrl } = metaAdContext(opts.token);
|
|
127074
127848
|
const currency = await fetchAccountCurrency(facebookApiUrl, digits, config, opts.verbose);
|
|
127075
127849
|
const bidAmount = parseOptionalNumber(opts.bidAmount);
|
|
127850
|
+
const adsetUrl = adsUrl(facebookApiUrl, digits, `/adsets/${encodeURIComponent(opts.id)}`);
|
|
127851
|
+
let targeting;
|
|
127852
|
+
let targetingChanges;
|
|
127853
|
+
let droppedKeys = [];
|
|
127854
|
+
if (wantsTargeting) {
|
|
127855
|
+
let currentRaw;
|
|
127856
|
+
try {
|
|
127857
|
+
currentRaw = await apiFetch2(adsetUrl, config, {}, opts.verbose);
|
|
127858
|
+
} catch (err) {
|
|
127859
|
+
console.error(
|
|
127860
|
+
`
|
|
127861
|
+
\u274C \u8BFB\u53D6\u5F53\u524D\u5B9A\u5411\u5931\u8D25\uFF0C\u672A\u63D0\u4EA4\u4FEE\u6539\uFF1A${err instanceof Error ? err.message : String(err)}
|
|
127862
|
+
`
|
|
127863
|
+
);
|
|
127864
|
+
process.exit(1);
|
|
127865
|
+
}
|
|
127866
|
+
const currentTargeting = isRecord2(currentRaw["targeting"]) ? currentRaw["targeting"] : void 0;
|
|
127867
|
+
const merged = mergeAdSetTargeting(
|
|
127868
|
+
currentTargeting,
|
|
127869
|
+
{
|
|
127870
|
+
countries,
|
|
127871
|
+
countriesMode: countries ? countriesMode : "replace",
|
|
127872
|
+
ageMin,
|
|
127873
|
+
ageMax,
|
|
127874
|
+
genders,
|
|
127875
|
+
advantageAudience
|
|
127876
|
+
},
|
|
127877
|
+
{ allowDropUnsupported: opts.targetingReplace === true }
|
|
127878
|
+
);
|
|
127879
|
+
if (!merged.ok) {
|
|
127880
|
+
const payload2 = merged.reason === "advantage_unset" ? {
|
|
127881
|
+
status: "needs_decision",
|
|
127882
|
+
code: "ADVANTAGE_AUDIENCE_UNSET",
|
|
127883
|
+
id: opts.id,
|
|
127884
|
+
message: "\u5F53\u524D\u7EC4\u8BFB\u4E0D\u5230 advantage_audience\uFF080 \u6216 1\uFF09\u3002\u7F51\u5173\u4F1A\u5728\u8FD9\u6B21\u5B9A\u5411\u66F4\u65B0\u91CC\u628A\u5B83\u5199\u6210 1\u3002\u8BF7\u663E\u5F0F\u4F20\u5165 --advantage-audience 0 \u6216 1 \u540E\u518D\u63D0\u4EA4\u3002\u672C\u6B21\u672A\u63D0\u4EA4\u4FEE\u6539\u3002"
|
|
127885
|
+
} : {
|
|
127886
|
+
status: "needs_decision",
|
|
127887
|
+
code: "TARGETING_UNSUPPORTED",
|
|
127888
|
+
id: opts.id,
|
|
127889
|
+
unsupportedKeys: merged.unsupportedKeys,
|
|
127890
|
+
message: "\u5F53\u524D\u5B9A\u5411\u542B\u672C\u547D\u4EE4\u4E0D\u80FD\u5B89\u5168\u5199\u56DE\u7684\u5B57\u6BB5\u3002\u6574\u4EFD\u66FF\u6362\u4F1A\u4E22\u6389\u5B83\u4EEC\u3002\u786E\u8BA4\u53EA\u4FDD\u7559\u56FD\u5BB6/\u5E74\u9F84/\u6027\u522B/flexible_spec/advantage_audience \u540E\uFF0C\u52A0\u4E0A --targeting-replace \u518D\u63D0\u4EA4\u3002\u672C\u6B21\u672A\u63D0\u4EA4\u4FEE\u6539\u3002"
|
|
127891
|
+
};
|
|
127892
|
+
const extra = merged.reason === "unsupported" ? `
|
|
127893
|
+
\u4E0D\u80FD\u5199\u56DE\uFF1A${merged.unsupportedKeys.join("\u3001")}` : "";
|
|
127894
|
+
console.error(`
|
|
127895
|
+
\u26A0\uFE0F ${payload2.message}${extra}
|
|
127896
|
+
`);
|
|
127897
|
+
await emitMetaJson(opts, "meta-ad-adset-edit", "meta-ad adset-edit", payload2, digits, {
|
|
127898
|
+
status: "needs_decision"
|
|
127899
|
+
});
|
|
127900
|
+
process.exit(2);
|
|
127901
|
+
}
|
|
127902
|
+
targeting = merged.targeting;
|
|
127903
|
+
targetingChanges = merged.changes;
|
|
127904
|
+
droppedKeys = merged.droppedKeys;
|
|
127905
|
+
const lines = merged.changes.map(formatTargetingChange);
|
|
127906
|
+
if (droppedKeys.length > 0) lines.push(`\u5C06\u4E22\u5F03\uFF1A${droppedKeys.join("\u3001")}`);
|
|
127907
|
+
console.error(`
|
|
127908
|
+
\u5B9A\u5411\u5DEE\u5F02\uFF1A
|
|
127909
|
+
${lines.join("\n ")}
|
|
127910
|
+
`);
|
|
127911
|
+
}
|
|
127076
127912
|
let body;
|
|
127077
127913
|
try {
|
|
127078
127914
|
body = buildUpdateAdSetBody({
|
|
127079
127915
|
name: opts.name?.trim() || void 0,
|
|
127080
127916
|
dailyBudgetMinor: daily != null ? toMinorUnitString(daily, currency) : void 0,
|
|
127081
127917
|
lifetimeBudgetMinor: lifetime != null ? toMinorUnitString(lifetime, currency) : void 0,
|
|
127082
|
-
|
|
127083
|
-
ageMin: parseOptionalNumber(opts.ageMin),
|
|
127084
|
-
ageMax: parseOptionalNumber(opts.ageMax),
|
|
127085
|
-
genders,
|
|
127918
|
+
targeting,
|
|
127086
127919
|
optimizationGoal: opts.optimizationGoal?.trim().toUpperCase(),
|
|
127087
127920
|
bidStrategy: opts.bidStrategy,
|
|
127088
127921
|
bidAmountMinor: bidAmount != null ? toMinorUnitString(bidAmount, currency) : void 0,
|
|
127089
127922
|
startTime: opts.startTime,
|
|
127090
127923
|
endTime: opts.endTime,
|
|
127091
|
-
status: opts.status
|
|
127092
|
-
advantageAudience
|
|
127924
|
+
status: opts.status
|
|
127093
127925
|
});
|
|
127094
127926
|
} catch (err) {
|
|
127095
127927
|
console.error(`
|
|
@@ -127102,16 +127934,47 @@ async function runMetaAdAdsetEdit(opts) {
|
|
|
127102
127934
|
process.exit(1);
|
|
127103
127935
|
}
|
|
127104
127936
|
const data = await metaAdPost(
|
|
127105
|
-
|
|
127937
|
+
adsetUrl,
|
|
127106
127938
|
config,
|
|
127107
127939
|
body,
|
|
127108
127940
|
opts.verbose,
|
|
127109
127941
|
"\u6539\u5E7F\u544A\u7EC4\u5931\u8D25"
|
|
127110
127942
|
);
|
|
127111
|
-
|
|
127112
|
-
|
|
127943
|
+
let read = await readAdSetForVerify(adsetUrl, config, opts.verbose);
|
|
127944
|
+
let judged = evaluateAdSetEditReadback(body, read.adset, read.readFailed);
|
|
127945
|
+
if (judged.verification !== "verified") {
|
|
127946
|
+
await new Promise((r) => setTimeout(r, READBACK_WAIT_MS));
|
|
127947
|
+
read = await readAdSetForVerify(adsetUrl, config, opts.verbose);
|
|
127948
|
+
judged = evaluateAdSetEditReadback(body, read.adset, read.readFailed);
|
|
127949
|
+
}
|
|
127950
|
+
const verification = judged.verification;
|
|
127951
|
+
const nextQuery = `siluzan-tso meta-ad adset -a ${digits} --id ${opts.id} --json-out ./snap`;
|
|
127952
|
+
const unmatched = describeUnmatchedChecks(judged.checks);
|
|
127953
|
+
const detail = verification === "verified" ? `\u5E7F\u544A\u7EC4 ${opts.id} \u5DF2\u6838\u9A8C` : unmatched || `\u5E7F\u544A\u7EC4 ${opts.id} \u7684\u5199\u5165\u7ED3\u679C\u5C1A\u672A\u6838\u9A8C`;
|
|
127954
|
+
const headline = writeVerificationHeadline(verification, detail);
|
|
127955
|
+
if (verification !== "verified") {
|
|
127956
|
+
console.error(`
|
|
127957
|
+
${headline}
|
|
127958
|
+
\u8BF7\u518D\u67E5\uFF1A${nextQuery}
|
|
127959
|
+
`);
|
|
127960
|
+
}
|
|
127961
|
+
const payload = {
|
|
127962
|
+
id: extractCreatedId(data) ?? opts.id,
|
|
127963
|
+
verification,
|
|
127964
|
+
nextQuery,
|
|
127965
|
+
targetingDiff: targetingChanges,
|
|
127966
|
+
droppedKeys,
|
|
127967
|
+
checks: judged.checks,
|
|
127968
|
+
...data
|
|
127969
|
+
};
|
|
127970
|
+
if (await emitMetaJson(opts, "meta-ad-adset-edit", "meta-ad adset-edit", payload, digits, {
|
|
127971
|
+
verification
|
|
127972
|
+
})) {
|
|
127973
|
+
return;
|
|
127974
|
+
}
|
|
127113
127975
|
console.log(`
|
|
127114
|
-
|
|
127976
|
+
${headline}${verification === "verified" ? "" : `
|
|
127977
|
+
\u8BF7\u518D\u67E5\uFF1A${nextQuery}`}
|
|
127115
127978
|
`);
|
|
127116
127979
|
}
|
|
127117
127980
|
async function runMetaAdAdsetStatus(opts) {
|
|
@@ -127408,7 +128271,11 @@ function register22(program2) {
|
|
|
127408
128271
|
);
|
|
127409
128272
|
addCommon(
|
|
127410
128273
|
addAccount(
|
|
127411
|
-
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(
|
|
128274
|
+
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(
|
|
128275
|
+
"--bid-amount-on-required <amount>",
|
|
128276
|
+
"\u4EC5\u5F53\u5EFA\u7EC4\u8FD4\u56DE BID_AMOUNT_REQUIRED \u65F6\u4F7F\u7528\u7684\u51FA\u4EF7\u4E0A\u9650\uFF08\u4E3B\u5E01\u79CD\u5143\uFF09\u3002\u4E0D\u4F20\u5219\u505C\u4E0B\uFF0C\u4E0D\u4F1A\u7528\u9884\u7B97\u4EE3\u66FF",
|
|
128277
|
+
parseFloat
|
|
128278
|
+
).option("--start-time <iso>", "\u5F00\u59CB\u65F6\u95F4").option("--end-time <iso>", "\u7ED3\u675F\u65F6\u95F4").option("--status <status>", "ACTIVE | PAUSED").option(
|
|
127412
128279
|
"--advantage-audience <n>",
|
|
127413
128280
|
"0=\u7CBE\u786E\u5B9A\u5411 1=Advantage Audience\uFF08\u9ED8\u8BA4 1\uFF09",
|
|
127414
128281
|
(v) => Number.parseInt(v, 10)
|
|
@@ -127421,11 +128288,13 @@ function register22(program2) {
|
|
|
127421
128288
|
);
|
|
127422
128289
|
addCommon(
|
|
127423
128290
|
addAccount(
|
|
127424
|
-
meta.command("adset-edit").description(
|
|
128291
|
+
meta.command("adset-edit").description(
|
|
128292
|
+
"\u6539\u5E7F\u544A\u7EC4\u3002\u56FD\u5BB6\u9ED8\u8BA4\u6574\u8868\u66FF\u6362\uFF1B\u8FFD\u52A0\u7528 --countries-mode append\u3002\u672A\u4F20 --advantage-audience \u65F6\u4FDD\u7559\u5F53\u524D\u503C\uFF0C\u4E0D\u5957\u521B\u5EFA\u9ED8\u8BA4"
|
|
128293
|
+
).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\uFF08\u9ED8\u8BA4\u66FF\u6362\u6574\u4EFD\u56FD\u5BB6\u5217\u8868\uFF09").option("--countries-mode <mode>", "replace\uFF08\u9ED8\u8BA4\uFF09| append\uFF08\u5E76\u4E0A\u5F53\u524D\u56FD\u5BB6\uFF09").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").option(
|
|
127425
128294
|
"--advantage-audience <n>",
|
|
127426
|
-
"0
|
|
128295
|
+
"0 \u6216 1\u3002\u4E0D\u4F20\u5219\u4FDD\u7559\u5F53\u524D\u503C\uFF0C\u4E0D\u4F1A\u9ED8\u8BA4\u6210 1",
|
|
127427
128296
|
(v) => Number.parseInt(v, 10)
|
|
127428
|
-
)
|
|
128297
|
+
).option("--targeting-replace", "\u786E\u8BA4\u4E22\u6389\u5F53\u524D\u5B9A\u5411\u91CC\u672C\u547D\u4EE4\u4E0D\u80FD\u5199\u56DE\u7684\u5B57\u6BB5\u540E\u518D\u63D0\u4EA4", false)
|
|
127429
128298
|
)
|
|
127430
128299
|
).action(
|
|
127431
128300
|
async (opts) => {
|