siluzan-tso-cli 1.1.20-beta.21 → 1.1.20-beta.23
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 -1
- package/dist/index.js +443 -315
- package/dist/skill/SKILL.md +1 -1
- package/dist/skill/_meta.json +2 -2
- package/dist/skill/assets/campaign-create-template.md +3 -3
- package/dist/skill/references/account-analytics.md +1 -1
- package/dist/skill/references/currency.md +1 -1
- package/dist/skill/references/google-ads-campaign-plan.md +14 -2
- package/dist/skill/references/google-ads-rules/google-ads-campaign-optimization.md +1 -0
- package/dist/skill/references/google-ads-rules/google-ads-keyword-taxonomy.md +1 -1
- package/dist/skill/references/google-ads.md +23 -3
- package/dist/skill/references/keyword-planner-workflows.md +1 -1
- package/dist/skill/scripts/install.ps1 +1 -1
- package/dist/skill/scripts/install.sh +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2418,12 +2418,12 @@ function createVersionNotifier(opts) {
|
|
|
2418
2418
|
const HOURS_24 = 24 * 60 * 60 * 1e3;
|
|
2419
2419
|
const TTL_MAIN_TAG_MS = 60 * 60 * 1e3;
|
|
2420
2420
|
const TTL_MIN_REQUIRED_MS = HOURS_24;
|
|
2421
|
-
async function fetchVersionByTag(tag,
|
|
2421
|
+
async function fetchVersionByTag(tag, cacheKey, fetchAtKey, cfg, maxAgeMs) {
|
|
2422
2422
|
const lastAt = cfg[fetchAtKey];
|
|
2423
|
-
if (typeof lastAt === "string" &&
|
|
2423
|
+
if (typeof lastAt === "string" && cacheKey in cfg) {
|
|
2424
2424
|
const lastMs = new Date(lastAt).getTime();
|
|
2425
2425
|
if (Date.now() - lastMs < maxAgeMs) {
|
|
2426
|
-
const v = cfg[
|
|
2426
|
+
const v = cfg[cacheKey];
|
|
2427
2427
|
const sv = typeof v === "string" && v ? v : null;
|
|
2428
2428
|
return { version: sv, hitNetwork: false };
|
|
2429
2429
|
}
|
|
@@ -109480,6 +109480,31 @@ init_cli_json_snapshot();
|
|
|
109480
109480
|
init_strip_legacy_google_fields();
|
|
109481
109481
|
init_cli_table();
|
|
109482
109482
|
|
|
109483
|
+
// src/commands/ad/campaign-length-violations.ts
|
|
109484
|
+
function pushLengthViolation(violations, item) {
|
|
109485
|
+
violations.push({ ...item, excess: item.actual - item.limit });
|
|
109486
|
+
}
|
|
109487
|
+
function formatLengthViolationsReport(violations) {
|
|
109488
|
+
if (violations.length === 0) return "";
|
|
109489
|
+
const lines = [
|
|
109490
|
+
"",
|
|
109491
|
+
`\u{1F4CF} \u8D85\u957F\u5185\u5BB9\u6E05\u5355\uFF08\u5171 ${violations.length} \u9879\uFF09`,
|
|
109492
|
+
" \u8BF7\u52FF\u5728 JSON \u4E2D\u81EA\u52A8\u622A\u65AD\uFF1B\u8BF7\u5C06\u5168\u90E8\u6761\u76EE\u4E0E\u4FEE\u6539\u65B9\u6848\u5217\u7ED9\u7528\u6237\uFF0C\u786E\u8BA4\u540E\u518D\u6539 JSON \u5E76\u91CD\u65B0 campaign-validate\u3002",
|
|
109493
|
+
""
|
|
109494
|
+
];
|
|
109495
|
+
for (let i = 0; i < violations.length; i++) {
|
|
109496
|
+
const v = violations[i];
|
|
109497
|
+
const mode = v.countMode === "google" ? "Google \u5B57\u7B26\u5BBD\uFF08CJK\xD72\uFF09" : "\u5B57\u7B26\u6570";
|
|
109498
|
+
lines.push(
|
|
109499
|
+
` ${i + 1}. ${v.path}`,
|
|
109500
|
+
` \u5B57\u6BB5 ${v.field} \xB7 \u4E0A\u9650 ${v.limit} \xB7 \u5F53\u524D ${v.actual}\uFF08${mode}\uFF0C\u8D85\u51FA ${v.excess}\uFF09`,
|
|
109501
|
+
` \u539F\u6587\uFF1A${JSON.stringify(v.text)}`
|
|
109502
|
+
);
|
|
109503
|
+
}
|
|
109504
|
+
lines.push("");
|
|
109505
|
+
return lines.join("\n");
|
|
109506
|
+
}
|
|
109507
|
+
|
|
109483
109508
|
// src/commands/ad/campaign-extensions.ts
|
|
109484
109509
|
var SITELINK_DESCRIPTION_MAX_LEN = 25;
|
|
109485
109510
|
function pushErr(errors, msg) {
|
|
@@ -109506,12 +109531,11 @@ function coercePropertiesFromRaw(raw) {
|
|
|
109506
109531
|
return props;
|
|
109507
109532
|
}
|
|
109508
109533
|
function normalizeSitelinkProperties(props) {
|
|
109509
|
-
const trimSitelinkDesc = (s) => s.length <= SITELINK_DESCRIPTION_MAX_LEN ? s : `${s.slice(0, SITELINK_DESCRIPTION_MAX_LEN - 1)}\u2026`;
|
|
109510
109534
|
if (props.LinkText && !props.Text) props.Text = props.LinkText;
|
|
109511
109535
|
const d1 = props.Description1 ?? props.Line2 ?? props.Text ?? props.LinkText;
|
|
109512
109536
|
const d2 = props.Description2 ?? props.Line3;
|
|
109513
|
-
props.Line2 =
|
|
109514
|
-
props.Line3 =
|
|
109537
|
+
props.Line2 = (d1 ?? "").trim() || props.Text || " ";
|
|
109538
|
+
props.Line3 = (d2 ?? "").trim() || props.Line2;
|
|
109515
109539
|
delete props.LinkText;
|
|
109516
109540
|
delete props.Description1;
|
|
109517
109541
|
delete props.Description2;
|
|
@@ -109530,7 +109554,7 @@ function normalizeExtensionsForBatchJob(extensions) {
|
|
|
109530
109554
|
return out;
|
|
109531
109555
|
});
|
|
109532
109556
|
}
|
|
109533
|
-
function validateSitelinkProperties(prefix, raw, errors, warnings) {
|
|
109557
|
+
function validateSitelinkProperties(prefix, raw, errors, warnings, lengthViolations) {
|
|
109534
109558
|
if (Array.isArray(raw["FinalUrls"])) {
|
|
109535
109559
|
pushErr(
|
|
109536
109560
|
errors,
|
|
@@ -109559,15 +109583,33 @@ function validateSitelinkProperties(prefix, raw, errors, warnings) {
|
|
|
109559
109583
|
const line2Raw = props.Description2 ?? props.Line3;
|
|
109560
109584
|
const line2 = typeof line2Raw === "string" ? line2Raw.trim() : "";
|
|
109561
109585
|
if (line1.length > SITELINK_DESCRIPTION_MAX_LEN) {
|
|
109586
|
+
pushLengthViolation(lengthViolations, {
|
|
109587
|
+
path: `${prefix}.Properties`,
|
|
109588
|
+
field: "Description1/Line2",
|
|
109589
|
+
kind: "sitelink_description",
|
|
109590
|
+
limit: SITELINK_DESCRIPTION_MAX_LEN,
|
|
109591
|
+
actual: line1.length,
|
|
109592
|
+
countMode: "ascii",
|
|
109593
|
+
text: line1
|
|
109594
|
+
});
|
|
109562
109595
|
pushErr(
|
|
109563
109596
|
errors,
|
|
109564
|
-
`${prefix}.Properties \u63CF\u8FF0\u884C 1 \u8D85\u8FC7 ${SITELINK_DESCRIPTION_MAX_LEN} \u5B57\u7B26\uFF08\u5F53\u524D ${line1.length}\uFF09\uFF1A"${line1
|
|
109597
|
+
`${prefix}.Properties \u63CF\u8FF0\u884C 1 \u8D85\u8FC7 ${SITELINK_DESCRIPTION_MAX_LEN} \u5B57\u7B26\uFF08\u5F53\u524D ${line1.length}\uFF09\uFF1A"${line1}"`
|
|
109565
109598
|
);
|
|
109566
109599
|
}
|
|
109567
109600
|
if (line2.length > SITELINK_DESCRIPTION_MAX_LEN) {
|
|
109601
|
+
pushLengthViolation(lengthViolations, {
|
|
109602
|
+
path: `${prefix}.Properties`,
|
|
109603
|
+
field: "Description2/Line3",
|
|
109604
|
+
kind: "sitelink_description",
|
|
109605
|
+
limit: SITELINK_DESCRIPTION_MAX_LEN,
|
|
109606
|
+
actual: line2.length,
|
|
109607
|
+
countMode: "ascii",
|
|
109608
|
+
text: line2
|
|
109609
|
+
});
|
|
109568
109610
|
pushErr(
|
|
109569
109611
|
errors,
|
|
109570
|
-
`${prefix}.Properties \u63CF\u8FF0\u884C 2 \u8D85\u8FC7 ${SITELINK_DESCRIPTION_MAX_LEN} \u5B57\u7B26\uFF08\u5F53\u524D ${line2.length}\uFF09\uFF1A"${line2
|
|
109612
|
+
`${prefix}.Properties \u63CF\u8FF0\u884C 2 \u8D85\u8FC7 ${SITELINK_DESCRIPTION_MAX_LEN} \u5B57\u7B26\uFF08\u5F53\u524D ${line2.length}\uFF09\uFF1A"${line2}"`
|
|
109571
109613
|
);
|
|
109572
109614
|
}
|
|
109573
109615
|
const rawDesc2 = raw["Description2"];
|
|
@@ -109581,7 +109623,7 @@ function validateSitelinkProperties(prefix, raw, errors, warnings) {
|
|
|
109581
109623
|
);
|
|
109582
109624
|
}
|
|
109583
109625
|
}
|
|
109584
|
-
function validateCampaignExtensionsForBatchJob(campaign, errors, warnings) {
|
|
109626
|
+
function validateCampaignExtensionsForBatchJob(campaign, errors, warnings, lengthViolations = []) {
|
|
109585
109627
|
const extensions = campaign["ExtensionsForBatchJob"];
|
|
109586
109628
|
if (extensions === void 0) return;
|
|
109587
109629
|
if (!Array.isArray(extensions)) {
|
|
@@ -109603,7 +109645,13 @@ function validateCampaignExtensionsForBatchJob(campaign, errors, warnings) {
|
|
|
109603
109645
|
continue;
|
|
109604
109646
|
}
|
|
109605
109647
|
if (type === "SITELINK" && props && typeof props === "object") {
|
|
109606
|
-
validateSitelinkProperties(
|
|
109648
|
+
validateSitelinkProperties(
|
|
109649
|
+
prefix,
|
|
109650
|
+
props,
|
|
109651
|
+
errors,
|
|
109652
|
+
warnings,
|
|
109653
|
+
lengthViolations
|
|
109654
|
+
);
|
|
109607
109655
|
} else if (type === "SITELINK") {
|
|
109608
109656
|
pushErr(errors, `${prefix}\uFF08SITELINK\uFF09\u7F3A\u5C11 Properties`);
|
|
109609
109657
|
}
|
|
@@ -109651,6 +109699,11 @@ function firstKeywordTextFromRecord(k) {
|
|
|
109651
109699
|
}
|
|
109652
109700
|
return "";
|
|
109653
109701
|
}
|
|
109702
|
+
function keywordRowToYuan(item) {
|
|
109703
|
+
const { maxCPC: rawMaxCpc, ...rest } = item;
|
|
109704
|
+
if (rawMaxCpc == null) return item;
|
|
109705
|
+
return { ...rest, maxCPCYuan: toDisplayMoney(rawMaxCpc) };
|
|
109706
|
+
}
|
|
109654
109707
|
async function runAdKeywords(opts) {
|
|
109655
109708
|
const config = loadConfig(opts.token);
|
|
109656
109709
|
const googleApiUrl = requireGoogleApi(config);
|
|
@@ -109678,8 +109731,9 @@ async function runAdKeywords(opts) {
|
|
|
109678
109731
|
});
|
|
109679
109732
|
const label = opts.negative ? "\u5426\u5B9A\u5173\u952E\u8BCD" : "\u5173\u952E\u8BCD";
|
|
109680
109733
|
const n = items.length;
|
|
109734
|
+
const displayItems = opts.negative ? items : items.map((row) => keywordRowToYuan(row));
|
|
109681
109735
|
const kwPayload = stripLegacyGoogleFieldsIfV2Present(
|
|
109682
|
-
wrapListJson({ page: 1, pageSize: Math.max(n, 1), total: n, items })
|
|
109736
|
+
wrapListJson({ page: 1, pageSize: Math.max(n, 1), total: n, items: displayItems })
|
|
109683
109737
|
);
|
|
109684
109738
|
if (await emitCliJsonOrSnapshot(opts, {
|
|
109685
109739
|
section: `ad-keywords-${opts.account}-${opts.negative ? "negative" : "positive"}`,
|
|
@@ -109870,7 +109924,7 @@ async function runAdKeywordEdit(opts) {
|
|
|
109870
109924
|
} else if (opts.text !== void 0) {
|
|
109871
109925
|
body["keywordText"] = [opts.text];
|
|
109872
109926
|
}
|
|
109873
|
-
if (opts.maxCpc !== void 0) body["maxCPC"] = opts.maxCpc;
|
|
109927
|
+
if (opts.maxCpc !== void 0) body["maxCPC"] = toCentAmount(opts.maxCpc);
|
|
109874
109928
|
if (opts.finalUrl !== void 0) body["finalURL"] = opts.finalUrl;
|
|
109875
109929
|
if (opts.status !== void 0) body["userStatusV2"] = opts.status;
|
|
109876
109930
|
const url = `${googleApiUrl}/keywordmanagement/Keyword/${opts.account}/batch`;
|
|
@@ -110040,15 +110094,26 @@ function inferMatchTypeFromDisplayText(text) {
|
|
|
110040
110094
|
if (t.length >= 2 && t.startsWith("[") && t.endsWith("]")) return "Exact";
|
|
110041
110095
|
return "Broad";
|
|
110042
110096
|
}
|
|
110043
|
-
function validateKeywordCore(core, path22, errors) {
|
|
110097
|
+
function validateKeywordCore(core, path22, errors, lengthViolations) {
|
|
110044
110098
|
const trimmed = core.trim();
|
|
110045
110099
|
if (!trimmed) {
|
|
110046
110100
|
errors.push(`${path22} \u5173\u952E\u8BCD\u8BCD\u5E72\u4E0D\u80FD\u4E3A\u7A7A`);
|
|
110047
110101
|
return false;
|
|
110048
110102
|
}
|
|
110049
110103
|
if (trimmed.length > GOOGLE_KEYWORD_MAX_CORE_LENGTH) {
|
|
110104
|
+
if (lengthViolations) {
|
|
110105
|
+
pushLengthViolation(lengthViolations, {
|
|
110106
|
+
path: path22,
|
|
110107
|
+
field: "KeywordText",
|
|
110108
|
+
kind: "keyword_core",
|
|
110109
|
+
limit: GOOGLE_KEYWORD_MAX_CORE_LENGTH,
|
|
110110
|
+
actual: trimmed.length,
|
|
110111
|
+
countMode: "ascii",
|
|
110112
|
+
text: trimmed
|
|
110113
|
+
});
|
|
110114
|
+
}
|
|
110050
110115
|
errors.push(
|
|
110051
|
-
`${path22} \u5173\u952E\u8BCD\u8BCD\u5E72\u8D85\u8FC7 ${GOOGLE_KEYWORD_MAX_CORE_LENGTH} \u5B57\u7B26\uFF08\u5F53\u524D ${trimmed.length}\uFF09\uFF1A"${trimmed
|
|
110116
|
+
`${path22} \u5173\u952E\u8BCD\u8BCD\u5E72\u8D85\u8FC7 ${GOOGLE_KEYWORD_MAX_CORE_LENGTH} \u5B57\u7B26\uFF08\u5F53\u524D ${trimmed.length}\uFF09\uFF1A"${trimmed}"`
|
|
110052
110117
|
);
|
|
110053
110118
|
return false;
|
|
110054
110119
|
}
|
|
@@ -110066,7 +110131,7 @@ function normalizeKeywordSurface(raw, matchTypeV2) {
|
|
|
110066
110131
|
const formatted = formatKeywordTextForMatchType(raw, matchType);
|
|
110067
110132
|
return { formatted, matchType, inferredMatchType: fromField === null };
|
|
110068
110133
|
}
|
|
110069
|
-
function normalizeKeywordsForBatchJobBlock(block, path22, errors, warnings) {
|
|
110134
|
+
function normalizeKeywordsForBatchJobBlock(block, path22, errors, warnings, lengthViolations) {
|
|
110070
110135
|
const texts = block["KeywordText"];
|
|
110071
110136
|
if (!Array.isArray(texts)) return;
|
|
110072
110137
|
const declaredUi = matchTypeV2ToUi(block["MatchTypeV2"]);
|
|
@@ -110100,7 +110165,8 @@ function normalizeKeywordsForBatchJobBlock(block, path22, errors, warnings) {
|
|
|
110100
110165
|
warnings.push(`${path22}.KeywordText[${k}] \u8BCD\u9762\u5DF2\u89C4\u8303\u5316\uFF1A${trimmedRaw} \u2192 ${formatted}`);
|
|
110101
110166
|
}
|
|
110102
110167
|
const core = unwrapKeywordDisplayTextForEdit(formatted);
|
|
110103
|
-
if (!validateKeywordCore(core, `${path22}.KeywordText[${k}]`, errors))
|
|
110168
|
+
if (!validateKeywordCore(core, `${path22}.KeywordText[${k}]`, errors, lengthViolations))
|
|
110169
|
+
continue;
|
|
110104
110170
|
const dedupeKey = `${matchTypeUiToV2(matchType)}:${core.toLowerCase()}`;
|
|
110105
110171
|
if (seen.has(dedupeKey)) {
|
|
110106
110172
|
warnings.push(`${path22}.KeywordText[${k}] \u4E0E\u540C\u7EC4\u91CD\u590D\uFF0C\u5DF2\u8DF3\u8FC7\uFF1A${formatted}`);
|
|
@@ -110118,7 +110184,7 @@ function normalizeKeywordsForBatchJobBlock(block, path22, errors, warnings) {
|
|
|
110118
110184
|
block["MatchTypeV2"] = matchTypeUiToV2(resolvedUi);
|
|
110119
110185
|
}
|
|
110120
110186
|
}
|
|
110121
|
-
function normalizeCampaignKeywordTrees(campaign, errors, warnings) {
|
|
110187
|
+
function normalizeCampaignKeywordTrees(campaign, errors, warnings, lengthViolations) {
|
|
110122
110188
|
const neg = campaign["NegativeKeywordsForBatchJob"];
|
|
110123
110189
|
if (Array.isArray(neg)) {
|
|
110124
110190
|
for (let i = 0; i < neg.length; i++) {
|
|
@@ -110129,7 +110195,8 @@ function normalizeCampaignKeywordTrees(campaign, errors, warnings) {
|
|
|
110129
110195
|
block,
|
|
110130
110196
|
`campaign.NegativeKeywordsForBatchJob[${i}]`,
|
|
110131
110197
|
errors,
|
|
110132
|
-
warnings
|
|
110198
|
+
warnings,
|
|
110199
|
+
lengthViolations
|
|
110133
110200
|
);
|
|
110134
110201
|
}
|
|
110135
110202
|
}
|
|
@@ -110147,7 +110214,8 @@ function normalizeCampaignKeywordTrees(campaign, errors, warnings) {
|
|
|
110147
110214
|
block,
|
|
110148
110215
|
`campaign.AdGroupsForBatchJob[${i}].KeywordsForBatchJob[${j}]`,
|
|
110149
110216
|
errors,
|
|
110150
|
-
warnings
|
|
110217
|
+
warnings,
|
|
110218
|
+
lengthViolations
|
|
110151
110219
|
);
|
|
110152
110220
|
}
|
|
110153
110221
|
}
|
|
@@ -110265,7 +110333,7 @@ function calcGoogleCharLength(text) {
|
|
|
110265
110333
|
}
|
|
110266
110334
|
return len;
|
|
110267
110335
|
}
|
|
110268
|
-
function validateRsaDisplayPath(prefix, ad, errors) {
|
|
110336
|
+
function validateRsaDisplayPath(prefix, ad, errors, lengthViolations) {
|
|
110269
110337
|
const jsonHint = `\u5728 ${prefix} \u589E\u52A0 "Path1"\u3001"Path2"\uFF08PascalCase\uFF0C\u52FF\u7528 path1/path2\uFF1B\u6A21\u677F\u89C1 assets/campaign-create-template.json\uFF09`;
|
|
110270
110338
|
if (ad["path1"] !== void 0 && ad["Path1"] === void 0) {
|
|
110271
110339
|
pushErr2(
|
|
@@ -110307,6 +110375,15 @@ function validateRsaDisplayPath(prefix, ad, errors) {
|
|
|
110307
110375
|
}
|
|
110308
110376
|
const len = calcGoogleCharLength(raw);
|
|
110309
110377
|
if (len > 15) {
|
|
110378
|
+
pushLengthViolation(lengthViolations, {
|
|
110379
|
+
path: `${prefix}.${field}`,
|
|
110380
|
+
field,
|
|
110381
|
+
kind: "rsa_path",
|
|
110382
|
+
limit: 15,
|
|
110383
|
+
actual: len,
|
|
110384
|
+
countMode: "google",
|
|
110385
|
+
text: raw
|
|
110386
|
+
});
|
|
110310
110387
|
pushErr2(
|
|
110311
110388
|
errors,
|
|
110312
110389
|
`${prefix}.${field} \u8D85\u8FC7 15 \u5B57\u7B26\uFF08\u5F53\u524D ${len}\uFF0CCJK \u6309 2 \u8BA1\uFF09\uFF1A"${raw}"`
|
|
@@ -110320,7 +110397,7 @@ function validateRsaDisplayPath(prefix, ad, errors) {
|
|
|
110320
110397
|
}
|
|
110321
110398
|
}
|
|
110322
110399
|
}
|
|
110323
|
-
function validateRsaAd(prefix, ad, errors, warnings) {
|
|
110400
|
+
function validateRsaAd(prefix, ad, errors, warnings, lengthViolations) {
|
|
110324
110401
|
const h1 = ad["headlinePart1"];
|
|
110325
110402
|
const h2 = ad["headlinePart2"];
|
|
110326
110403
|
const h3 = ad["headlinePart3"];
|
|
@@ -110341,12 +110418,28 @@ function validateRsaAd(prefix, ad, errors, warnings) {
|
|
|
110341
110418
|
} else if (headlines.length < 12) {
|
|
110342
110419
|
pushWarn2(warnings, `${prefix} \u6807\u9898\u63A8\u8350 12\u201315 \u6761\uFF08\u5F53\u524D ${headlines.length} \u6761\uFF09\uFF0C\u66F4\u591A\u7EC4\u5408\u53EF\u63D0\u5347\u6295\u653E\u8868\u73B0`);
|
|
110343
110420
|
}
|
|
110421
|
+
const headlineFields = [
|
|
110422
|
+
"headlinePart1",
|
|
110423
|
+
"headlinePart2",
|
|
110424
|
+
"headlinePart3",
|
|
110425
|
+
...moreH.map((_, idx) => `AddtionalHeadlines[${idx}]`)
|
|
110426
|
+
];
|
|
110344
110427
|
for (let i = 0; i < headlines.length; i++) {
|
|
110345
110428
|
const len = calcGoogleCharLength(headlines[i]);
|
|
110346
110429
|
if (len > 30) {
|
|
110430
|
+
const field = headlineFields[i] ?? `headline[${i}]`;
|
|
110431
|
+
pushLengthViolation(lengthViolations, {
|
|
110432
|
+
path: `${prefix}.${field}`,
|
|
110433
|
+
field,
|
|
110434
|
+
kind: "rsa_headline",
|
|
110435
|
+
limit: 30,
|
|
110436
|
+
actual: len,
|
|
110437
|
+
countMode: "google",
|
|
110438
|
+
text: headlines[i]
|
|
110439
|
+
});
|
|
110347
110440
|
pushErr2(
|
|
110348
110441
|
errors,
|
|
110349
|
-
`${prefix} \u6807\u9898[${i}] \u8D85\u8FC7 30 \u5B57\u7B26\uFF08\u5F53\u524D ${len}\uFF0CCJK \u8BA1 2\uFF09\uFF1A"${headlines[i]
|
|
110442
|
+
`${prefix} \u6807\u9898[${i}] \u8D85\u8FC7 30 \u5B57\u7B26\uFF08\u5F53\u524D ${len}\uFF0CCJK \u8BA1 2\uFF09\uFF1A"${headlines[i]}"`
|
|
110350
110443
|
);
|
|
110351
110444
|
}
|
|
110352
110445
|
}
|
|
@@ -110360,13 +110453,28 @@ function validateRsaAd(prefix, ad, errors, warnings) {
|
|
|
110360
110453
|
} else if (descriptions.length < 4) {
|
|
110361
110454
|
pushWarn2(warnings, `${prefix} \u63CF\u8FF0\u63A8\u8350 4 \u6761\uFF08\u5F53\u524D ${descriptions.length} \u6761\uFF09`);
|
|
110362
110455
|
}
|
|
110456
|
+
const descriptionFields = [
|
|
110457
|
+
"adDescription",
|
|
110458
|
+
"adDescription2",
|
|
110459
|
+
...moreD.map((_, idx) => `AddtionalAdDescriptions[${idx}]`)
|
|
110460
|
+
];
|
|
110363
110461
|
for (let i = 0; i < descriptions.length; i++) {
|
|
110364
110462
|
const len = calcGoogleCharLength(descriptions[i]);
|
|
110365
110463
|
if (len > 90) {
|
|
110366
|
-
|
|
110464
|
+
const field = descriptionFields[i] ?? `description[${i}]`;
|
|
110465
|
+
pushLengthViolation(lengthViolations, {
|
|
110466
|
+
path: `${prefix}.${field}`,
|
|
110467
|
+
field,
|
|
110468
|
+
kind: "rsa_description",
|
|
110469
|
+
limit: 90,
|
|
110470
|
+
actual: len,
|
|
110471
|
+
countMode: "google",
|
|
110472
|
+
text: descriptions[i]
|
|
110473
|
+
});
|
|
110474
|
+
pushErr2(errors, `${prefix} \u63CF\u8FF0[${i}] \u8D85\u8FC7 90 \u5B57\u7B26\uFF08\u5F53\u524D ${len}\uFF09\uFF1A"${descriptions[i]}"`);
|
|
110367
110475
|
}
|
|
110368
110476
|
}
|
|
110369
|
-
validateRsaDisplayPath(prefix, ad, errors);
|
|
110477
|
+
validateRsaDisplayPath(prefix, ad, errors, lengthViolations);
|
|
110370
110478
|
const finalUrl = ad["Finalurl"] ?? ad["DestinationUrl"];
|
|
110371
110479
|
if (typeof finalUrl === "string" && finalUrl.length > 0 && !URL_REGEX.test(finalUrl)) {
|
|
110372
110480
|
pushErr2(errors, `${prefix} Finalurl \u683C\u5F0F\u4E0D\u6B63\u786E\uFF08\u5E94\u4EE5 http(s):// \u5F00\u5934\uFF09\uFF1A${finalUrl}`);
|
|
@@ -110376,8 +110484,14 @@ function validateRsaAd(prefix, ad, errors, warnings) {
|
|
|
110376
110484
|
function validateCampaignCreateConfigCore(cfg) {
|
|
110377
110485
|
const errors = [];
|
|
110378
110486
|
const warnings = [];
|
|
110487
|
+
const lengthViolations = [];
|
|
110379
110488
|
if (cfg.campaign && typeof cfg.campaign === "object" && !Array.isArray(cfg.campaign)) {
|
|
110380
|
-
normalizeCampaignKeywordTrees(
|
|
110489
|
+
normalizeCampaignKeywordTrees(
|
|
110490
|
+
cfg.campaign,
|
|
110491
|
+
errors,
|
|
110492
|
+
warnings,
|
|
110493
|
+
lengthViolations
|
|
110494
|
+
);
|
|
110381
110495
|
}
|
|
110382
110496
|
if (!cfg.account?.toString().trim()) {
|
|
110383
110497
|
pushErr2(errors, "account\uFF08\u9876\u5C42 customerId\uFF09\u4E0D\u80FD\u4E3A\u7A7A");
|
|
@@ -110391,7 +110505,7 @@ function validateCampaignCreateConfigCore(cfg) {
|
|
|
110391
110505
|
}
|
|
110392
110506
|
if (!cfg.campaign || typeof cfg.campaign !== "object" || Array.isArray(cfg.campaign)) {
|
|
110393
110507
|
pushErr2(errors, "campaign \u5BF9\u8C61\u4E0D\u80FD\u4E3A\u7A7A\uFF08\u540E\u7AEF\uFF1Acampaign is null\uFF09");
|
|
110394
|
-
return { errors, warnings };
|
|
110508
|
+
return { errors, warnings, lengthViolations };
|
|
110395
110509
|
}
|
|
110396
110510
|
const campaign = cfg.campaign;
|
|
110397
110511
|
if (campaign["TargetPartnerSearchNetwork"] === true) {
|
|
@@ -110554,7 +110668,7 @@ function validateCampaignCreateConfigCore(cfg) {
|
|
|
110554
110668
|
for (let j = 0; j < ads.length; j++) {
|
|
110555
110669
|
const ad = ads[j];
|
|
110556
110670
|
if (ad && typeof ad === "object") {
|
|
110557
|
-
validateRsaAd(`${gPrefix}.AdsForBatchJob[${j}]`, ad, errors, warnings);
|
|
110671
|
+
validateRsaAd(`${gPrefix}.AdsForBatchJob[${j}]`, ad, errors, warnings, lengthViolations);
|
|
110558
110672
|
}
|
|
110559
110673
|
}
|
|
110560
110674
|
}
|
|
@@ -110571,8 +110685,8 @@ function validateCampaignCreateConfigCore(cfg) {
|
|
|
110571
110685
|
}
|
|
110572
110686
|
validateCampaignRsaCrossGroupHeadlines(campaign, errors);
|
|
110573
110687
|
}
|
|
110574
|
-
validateCampaignExtensionsForBatchJob(campaign, errors, warnings);
|
|
110575
|
-
return { errors, warnings };
|
|
110688
|
+
validateCampaignExtensionsForBatchJob(campaign, errors, warnings, lengthViolations);
|
|
110689
|
+
return { errors, warnings, lengthViolations };
|
|
110576
110690
|
}
|
|
110577
110691
|
function runCampaignCreateValidation(cfg) {
|
|
110578
110692
|
return validateCampaignCreateConfigCore(cfg);
|
|
@@ -111842,141 +111956,6 @@ async function runAdSearchTerms(opts) {
|
|
|
111842
111956
|
init_auth();
|
|
111843
111957
|
init_cli_json_snapshot();
|
|
111844
111958
|
init_strip_legacy_google_fields();
|
|
111845
|
-
async function runAdGeoSearch(opts) {
|
|
111846
|
-
const config = loadConfig(opts.token);
|
|
111847
|
-
const googleApiUrl = requireGoogleApi(config);
|
|
111848
|
-
const url = `${googleApiUrl}/campaignmanagement/geolocations/${opts.account}/${encodeURIComponent(opts.query)}`;
|
|
111849
|
-
let data;
|
|
111850
|
-
try {
|
|
111851
|
-
data = await apiFetch2(url, config, {}, opts.verbose);
|
|
111852
|
-
} catch (err) {
|
|
111853
|
-
console.error(`
|
|
111854
|
-
\u274C \u641C\u7D22\u5730\u7406\u4F4D\u7F6E\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
|
|
111855
|
-
`);
|
|
111856
|
-
process.exit(1);
|
|
111857
|
-
}
|
|
111858
|
-
const items = Array.isArray(data) ? data : data.data ?? [];
|
|
111859
|
-
console.log(`
|
|
111860
|
-
\u5730\u7406\u4F4D\u7F6E\u641C\u7D22\u7ED3\u679C\u300C${opts.query}\u300D\uFF08\u5171 ${items.length} \u6761\uFF09
|
|
111861
|
-
`);
|
|
111862
|
-
if (items.length === 0) {
|
|
111863
|
-
console.log(" \u672A\u627E\u5230\u5339\u914D\u4F4D\u7F6E\u3002\n");
|
|
111864
|
-
return;
|
|
111865
|
-
}
|
|
111866
|
-
for (const item of items) {
|
|
111867
|
-
const id = String(item["id"] ?? "");
|
|
111868
|
-
const name = String(item["locationName"] ?? item["canonicalName"] ?? item["name"] ?? "");
|
|
111869
|
-
const type = String(item["targetType"] ?? item["typeV2"] ?? "");
|
|
111870
|
-
console.log(` id:${id} ${name} [${type}]`);
|
|
111871
|
-
}
|
|
111872
|
-
console.log();
|
|
111873
|
-
}
|
|
111874
|
-
async function runAdGeoList(opts) {
|
|
111875
|
-
const config = loadConfig(opts.token);
|
|
111876
|
-
const googleApiUrl = requireGoogleApi(config);
|
|
111877
|
-
let url;
|
|
111878
|
-
const params = new URLSearchParams();
|
|
111879
|
-
if (opts.mode === "report") {
|
|
111880
|
-
url = `${googleApiUrl}/geotargetmanagement/v2/list/${opts.account}?${params}`;
|
|
111881
|
-
} else {
|
|
111882
|
-
params.set("startDate", toGoogleDate(opts.startDate, -30));
|
|
111883
|
-
params.set("endDate", toGoogleDate(opts.endDate, 0));
|
|
111884
|
-
url = `${googleApiUrl}/campaignmanagement/v2/${opts.mode}locations/${opts.account}?${params}`;
|
|
111885
|
-
}
|
|
111886
|
-
let data;
|
|
111887
|
-
try {
|
|
111888
|
-
data = await apiFetch2(url, config, {}, opts.verbose);
|
|
111889
|
-
} catch (err) {
|
|
111890
|
-
console.error(`
|
|
111891
|
-
\u274C \u67E5\u8BE2\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
|
|
111892
|
-
`);
|
|
111893
|
-
process.exit(1);
|
|
111894
|
-
}
|
|
111895
|
-
const rawData = data.data;
|
|
111896
|
-
let items = Array.isArray(rawData) ? rawData : rawData?.countries ?? [];
|
|
111897
|
-
if (opts.campaignId) {
|
|
111898
|
-
const targetId = opts.campaignId;
|
|
111899
|
-
items = items.filter((item) => String(item["campaignId"] ?? "") === targetId);
|
|
111900
|
-
}
|
|
111901
|
-
const modeLabel = { targeted: "\u5DF2\u5B9A\u4F4D", excluded: "\u5DF2\u6392\u9664", report: "\u6D88\u8017\u62A5\u544A" }[opts.mode];
|
|
111902
|
-
const n = items.length;
|
|
111903
|
-
const geoPayload = stripLegacyGoogleFieldsIfV2Present(
|
|
111904
|
-
wrapListJson({ page: 1, pageSize: Math.max(n, 1), total: n, items })
|
|
111905
|
-
);
|
|
111906
|
-
if (await emitCliJsonOrSnapshot(opts, {
|
|
111907
|
-
section: `ad-geo-${opts.mode}-${opts.account}`,
|
|
111908
|
-
commandLabel: "ad geo",
|
|
111909
|
-
commandHint: `${opts.mode}:${opts.account}`,
|
|
111910
|
-
payload: geoPayload,
|
|
111911
|
-
idSuffix: opts.account
|
|
111912
|
-
})) {
|
|
111913
|
-
return;
|
|
111914
|
-
}
|
|
111915
|
-
console.log(
|
|
111916
|
-
`
|
|
111917
|
-
\u5730\u7406\u4F4D\u7F6E\uFF08${modeLabel}\uFF0C\u8D26\u6237\uFF1A${opts.account}\uFF0C\u7B2C 1 \u9875\uFF0C\u672C\u9875 ${items.length} \u6761\uFF0C\u5171 ${items.length} \u6761\uFF09
|
|
111918
|
-
`
|
|
111919
|
-
);
|
|
111920
|
-
if (items.length === 0) {
|
|
111921
|
-
console.log(" \u6682\u65E0\u6570\u636E\u3002\n");
|
|
111922
|
-
return;
|
|
111923
|
-
}
|
|
111924
|
-
for (const item of items) {
|
|
111925
|
-
const id = String(item["id"] ?? item["countryCriteriaId"] ?? "");
|
|
111926
|
-
const name = String(
|
|
111927
|
-
item["name"] ?? item["countryOrRegion"] ?? item["country"] ?? item["location"] ?? item["canonicalName"] ?? ""
|
|
111928
|
-
);
|
|
111929
|
-
const campaign = item["campaignName"] ? ` campaign:${item["campaignName"]}` : "";
|
|
111930
|
-
const bid = item["bidModifier"] != null ? ` \u51FA\u4EF7\u500D\u7387:${item["bidModifier"]}` : "";
|
|
111931
|
-
const spend = item["spend"] != null ? ` \u6D88\u8017:${item["spend"]}` : "";
|
|
111932
|
-
const clicks = item["clicks"] != null ? ` \u70B9\u51FB:${item["clicks"]}` : "";
|
|
111933
|
-
console.log(` id:${id} ${name}${campaign}${bid}${spend}${clicks}`);
|
|
111934
|
-
}
|
|
111935
|
-
console.log();
|
|
111936
|
-
}
|
|
111937
|
-
async function runAdGeoAdd(opts) {
|
|
111938
|
-
const config = loadConfig(opts.token);
|
|
111939
|
-
const googleApiUrl = requireGoogleApi(config);
|
|
111940
|
-
const body = {
|
|
111941
|
-
CampaignId: opts.campaignId,
|
|
111942
|
-
Id: opts.locationId,
|
|
111943
|
-
TypeV2: "Location",
|
|
111944
|
-
BidModifier: opts.bidModifier ?? 0,
|
|
111945
|
-
BidModifierSpecified: opts.bidModifier !== void 0
|
|
111946
|
-
};
|
|
111947
|
-
let url = `${googleApiUrl}/campaignmanagement/criterion/${opts.account}`;
|
|
111948
|
-
const method = opts.exclude ? "POST" : "PUT";
|
|
111949
|
-
if (opts.exclude) url += "?isNegative=true";
|
|
111950
|
-
try {
|
|
111951
|
-
await apiFetch2(url, config, { method, body: JSON.stringify(body) }, opts.verbose);
|
|
111952
|
-
} catch (err) {
|
|
111953
|
-
console.error(`
|
|
111954
|
-
\u274C \u6DFB\u52A0\u5730\u7406\u4F4D\u7F6E\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
|
|
111955
|
-
`);
|
|
111956
|
-
process.exit(1);
|
|
111957
|
-
}
|
|
111958
|
-
const label = opts.exclude ? "\u5DF2\u6392\u9664" : "\u5DF2\u6DFB\u52A0\u5B9A\u4F4D";
|
|
111959
|
-
console.log(`
|
|
111960
|
-
\u2705 \u5730\u7406\u4F4D\u7F6E ${opts.locationId} ${label}\uFF08\u5E7F\u544A\u7CFB\u5217\uFF1A${opts.campaignId}\uFF09
|
|
111961
|
-
`);
|
|
111962
|
-
}
|
|
111963
|
-
async function runAdGeoRemove(opts) {
|
|
111964
|
-
const config = loadConfig(opts.token);
|
|
111965
|
-
const googleApiUrl = requireGoogleApi(config);
|
|
111966
|
-
const body = { campaignId: opts.campaignId, id: opts.locationId };
|
|
111967
|
-
const url = `${googleApiUrl}/campaignmanagement/criterion/${opts.account}?campaignId=${opts.campaignId}`;
|
|
111968
|
-
try {
|
|
111969
|
-
await apiDeleteRaw(url, config, { body: JSON.stringify(body), verbose: opts.verbose });
|
|
111970
|
-
} catch (err) {
|
|
111971
|
-
console.error(`
|
|
111972
|
-
\u274C \u5220\u9664\u5730\u7406\u4F4D\u7F6E\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
|
|
111973
|
-
`);
|
|
111974
|
-
process.exit(1);
|
|
111975
|
-
}
|
|
111976
|
-
console.log(`
|
|
111977
|
-
\u2705 \u5730\u7406\u4F4D\u7F6E ${opts.locationId} \u5DF2\u4ECE\u5E7F\u544A\u7CFB\u5217 ${opts.campaignId} \u4E2D\u79FB\u9664
|
|
111978
|
-
`);
|
|
111979
|
-
}
|
|
111980
111959
|
|
|
111981
111960
|
// src/commands/ad/device-bid.ts
|
|
111982
111961
|
init_auth();
|
|
@@ -112209,6 +112188,220 @@ async function runAdDeviceBidSet(opts) {
|
|
|
112209
112188
|
);
|
|
112210
112189
|
}
|
|
112211
112190
|
|
|
112191
|
+
// src/commands/ad/geo.ts
|
|
112192
|
+
async function runAdGeoSearch(opts) {
|
|
112193
|
+
const config = loadConfig(opts.token);
|
|
112194
|
+
const googleApiUrl = requireGoogleApi(config);
|
|
112195
|
+
const url = `${googleApiUrl}/campaignmanagement/geolocations/${opts.account}/${encodeURIComponent(opts.query)}`;
|
|
112196
|
+
let data;
|
|
112197
|
+
try {
|
|
112198
|
+
data = await apiFetch2(url, config, {}, opts.verbose);
|
|
112199
|
+
} catch (err) {
|
|
112200
|
+
console.error(`
|
|
112201
|
+
\u274C \u641C\u7D22\u5730\u7406\u4F4D\u7F6E\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
|
|
112202
|
+
`);
|
|
112203
|
+
process.exit(1);
|
|
112204
|
+
}
|
|
112205
|
+
const items = Array.isArray(data) ? data : data.data ?? [];
|
|
112206
|
+
console.log(`
|
|
112207
|
+
\u5730\u7406\u4F4D\u7F6E\u641C\u7D22\u7ED3\u679C\u300C${opts.query}\u300D\uFF08\u5171 ${items.length} \u6761\uFF09
|
|
112208
|
+
`);
|
|
112209
|
+
if (items.length === 0) {
|
|
112210
|
+
console.log(" \u672A\u627E\u5230\u5339\u914D\u4F4D\u7F6E\u3002\n");
|
|
112211
|
+
return;
|
|
112212
|
+
}
|
|
112213
|
+
for (const item of items) {
|
|
112214
|
+
const id = String(item["id"] ?? "");
|
|
112215
|
+
const name = String(item["locationName"] ?? item["canonicalName"] ?? item["name"] ?? "");
|
|
112216
|
+
const type = String(item["targetType"] ?? item["typeV2"] ?? "");
|
|
112217
|
+
console.log(` id:${id} ${name} [${type}]`);
|
|
112218
|
+
}
|
|
112219
|
+
console.log();
|
|
112220
|
+
}
|
|
112221
|
+
async function runAdGeoList(opts) {
|
|
112222
|
+
const config = loadConfig(opts.token);
|
|
112223
|
+
const googleApiUrl = requireGoogleApi(config);
|
|
112224
|
+
let url;
|
|
112225
|
+
const params = new URLSearchParams();
|
|
112226
|
+
if (opts.mode === "report") {
|
|
112227
|
+
url = `${googleApiUrl}/geotargetmanagement/v2/list/${opts.account}?${params}`;
|
|
112228
|
+
} else {
|
|
112229
|
+
params.set("startDate", toGoogleDate(opts.startDate, -30));
|
|
112230
|
+
params.set("endDate", toGoogleDate(opts.endDate, 0));
|
|
112231
|
+
url = `${googleApiUrl}/campaignmanagement/v2/${opts.mode}locations/${opts.account}?${params}`;
|
|
112232
|
+
}
|
|
112233
|
+
let data;
|
|
112234
|
+
try {
|
|
112235
|
+
data = await apiFetch2(url, config, {}, opts.verbose);
|
|
112236
|
+
} catch (err) {
|
|
112237
|
+
console.error(`
|
|
112238
|
+
\u274C \u67E5\u8BE2\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
|
|
112239
|
+
`);
|
|
112240
|
+
process.exit(1);
|
|
112241
|
+
}
|
|
112242
|
+
const rawData = data.data;
|
|
112243
|
+
let items = Array.isArray(rawData) ? rawData : rawData?.countries ?? [];
|
|
112244
|
+
if (opts.campaignId) {
|
|
112245
|
+
const targetId = opts.campaignId;
|
|
112246
|
+
items = items.filter((item) => String(item["campaignId"] ?? "") === targetId);
|
|
112247
|
+
}
|
|
112248
|
+
const modeLabel = { targeted: "\u5DF2\u5B9A\u4F4D", excluded: "\u5DF2\u6392\u9664", report: "\u6D88\u8017\u62A5\u544A" }[opts.mode];
|
|
112249
|
+
const n = items.length;
|
|
112250
|
+
const geoPayload = stripLegacyGoogleFieldsIfV2Present(
|
|
112251
|
+
wrapListJson({ page: 1, pageSize: Math.max(n, 1), total: n, items })
|
|
112252
|
+
);
|
|
112253
|
+
if (await emitCliJsonOrSnapshot(opts, {
|
|
112254
|
+
section: `ad-geo-${opts.mode}-${opts.account}`,
|
|
112255
|
+
commandLabel: "ad geo",
|
|
112256
|
+
commandHint: `${opts.mode}:${opts.account}`,
|
|
112257
|
+
payload: geoPayload,
|
|
112258
|
+
idSuffix: opts.account
|
|
112259
|
+
})) {
|
|
112260
|
+
return;
|
|
112261
|
+
}
|
|
112262
|
+
console.log(
|
|
112263
|
+
`
|
|
112264
|
+
\u5730\u7406\u4F4D\u7F6E\uFF08${modeLabel}\uFF0C\u8D26\u6237\uFF1A${opts.account}\uFF0C\u7B2C 1 \u9875\uFF0C\u672C\u9875 ${items.length} \u6761\uFF0C\u5171 ${items.length} \u6761\uFF09
|
|
112265
|
+
`
|
|
112266
|
+
);
|
|
112267
|
+
if (items.length === 0) {
|
|
112268
|
+
console.log(" \u6682\u65E0\u6570\u636E\u3002\n");
|
|
112269
|
+
return;
|
|
112270
|
+
}
|
|
112271
|
+
for (const item of items) {
|
|
112272
|
+
const id = String(item["id"] ?? item["countryCriteriaId"] ?? "");
|
|
112273
|
+
const name = String(
|
|
112274
|
+
item["name"] ?? item["countryOrRegion"] ?? item["country"] ?? item["location"] ?? item["canonicalName"] ?? ""
|
|
112275
|
+
);
|
|
112276
|
+
const campaign = item["campaignName"] ? ` campaign:${item["campaignName"]}` : "";
|
|
112277
|
+
const bid = item["bidModifier"] != null && typeof item["bidModifier"] === "number" ? ` \u51FA\u4EF7\u8C03\u6574:${formatBidModifierPercent(item["bidModifier"])}` : item["bidModifier"] != null ? ` \u51FA\u4EF7\u8C03\u6574:${item["bidModifier"]}` : "";
|
|
112278
|
+
const spend = item["spend"] != null ? ` \u6D88\u8017:${item["spend"]}` : "";
|
|
112279
|
+
const clicks = item["clicks"] != null ? ` \u70B9\u51FB:${item["clicks"]}` : "";
|
|
112280
|
+
console.log(` id:${id} ${name}${campaign}${bid}${spend}${clicks}`);
|
|
112281
|
+
}
|
|
112282
|
+
console.log();
|
|
112283
|
+
}
|
|
112284
|
+
async function runAdGeoAdd(opts) {
|
|
112285
|
+
const config = loadConfig(opts.token);
|
|
112286
|
+
const googleApiUrl = requireGoogleApi(config);
|
|
112287
|
+
const body = {
|
|
112288
|
+
CampaignId: opts.campaignId,
|
|
112289
|
+
Id: opts.locationId,
|
|
112290
|
+
TypeV2: "Location",
|
|
112291
|
+
BidModifier: opts.bidModifier !== void 0 ? multiplierToAdGroupBidPercent(opts.bidModifier) : 0,
|
|
112292
|
+
BidModifierSpecified: opts.bidModifier !== void 0
|
|
112293
|
+
};
|
|
112294
|
+
let url = `${googleApiUrl}/campaignmanagement/criterion/${opts.account}`;
|
|
112295
|
+
const method = opts.exclude ? "POST" : "PUT";
|
|
112296
|
+
if (opts.exclude) url += "?isNegative=true";
|
|
112297
|
+
try {
|
|
112298
|
+
await apiFetch2(url, config, { method, body: JSON.stringify(body) }, opts.verbose);
|
|
112299
|
+
} catch (err) {
|
|
112300
|
+
console.error(`
|
|
112301
|
+
\u274C \u6DFB\u52A0\u5730\u7406\u4F4D\u7F6E\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
|
|
112302
|
+
`);
|
|
112303
|
+
process.exit(1);
|
|
112304
|
+
}
|
|
112305
|
+
const label = opts.exclude ? "\u5DF2\u6392\u9664" : "\u5DF2\u6DFB\u52A0\u5B9A\u4F4D";
|
|
112306
|
+
console.log(`
|
|
112307
|
+
\u2705 \u5730\u7406\u4F4D\u7F6E ${opts.locationId} ${label}\uFF08\u5E7F\u544A\u7CFB\u5217\uFF1A${opts.campaignId}\uFF09
|
|
112308
|
+
`);
|
|
112309
|
+
}
|
|
112310
|
+
async function runAdGeoRemove(opts) {
|
|
112311
|
+
const config = loadConfig(opts.token);
|
|
112312
|
+
const googleApiUrl = requireGoogleApi(config);
|
|
112313
|
+
const body = { campaignId: opts.campaignId, id: opts.locationId };
|
|
112314
|
+
const url = `${googleApiUrl}/campaignmanagement/criterion/${opts.account}?campaignId=${opts.campaignId}`;
|
|
112315
|
+
try {
|
|
112316
|
+
await apiDeleteRaw(url, config, { body: JSON.stringify(body), verbose: opts.verbose });
|
|
112317
|
+
} catch (err) {
|
|
112318
|
+
console.error(`
|
|
112319
|
+
\u274C \u5220\u9664\u5730\u7406\u4F4D\u7F6E\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
|
|
112320
|
+
`);
|
|
112321
|
+
process.exit(1);
|
|
112322
|
+
}
|
|
112323
|
+
console.log(`
|
|
112324
|
+
\u2705 \u5730\u7406\u4F4D\u7F6E ${opts.locationId} \u5DF2\u4ECE\u5E7F\u544A\u7CFB\u5217 ${opts.campaignId} \u4E2D\u79FB\u9664
|
|
112325
|
+
`);
|
|
112326
|
+
}
|
|
112327
|
+
async function fetchTargetedGeoRows(config, googleApiUrl, account, campaignId, verbose) {
|
|
112328
|
+
const params = new URLSearchParams({
|
|
112329
|
+
startDate: toGoogleDate(void 0, -30),
|
|
112330
|
+
endDate: toGoogleDate(void 0, 0)
|
|
112331
|
+
});
|
|
112332
|
+
const url = `${googleApiUrl}/campaignmanagement/v2/targetedlocations/${account}?${params}`;
|
|
112333
|
+
const data = await apiFetch2(url, config, {}, verbose);
|
|
112334
|
+
const items = Array.isArray(data.data) ? data.data : [];
|
|
112335
|
+
return items.filter((item) => String(item["campaignId"] ?? "") === campaignId);
|
|
112336
|
+
}
|
|
112337
|
+
function resolveGeoCriterionId(rows, opts) {
|
|
112338
|
+
if (opts.criterionId) return opts.criterionId;
|
|
112339
|
+
if (!opts.locationId) {
|
|
112340
|
+
throw new Error("\u8BF7\u6307\u5B9A --criterion-id\uFF0C\u6216\u914D\u5408 --location-id \u4ECE\u5DF2\u5B9A\u5411\u5217\u8868\u81EA\u52A8\u5339\u914D");
|
|
112341
|
+
}
|
|
112342
|
+
const match = rows.find((r) => String(r["id"] ?? "") === opts.locationId);
|
|
112343
|
+
if (!match?.["id"]) {
|
|
112344
|
+
throw new Error(
|
|
112345
|
+
`\u672A\u627E\u5230\u5DF2\u5B9A\u5411\u5730\u7406\u4F4D\u7F6E id=${opts.locationId}\uFF08\u7CFB\u5217 ${opts.campaignId}\uFF09\uFF0C\u8BF7\u5148\u6267\u884C ad geo list --mode targeted`
|
|
112346
|
+
);
|
|
112347
|
+
}
|
|
112348
|
+
return String(match["id"]);
|
|
112349
|
+
}
|
|
112350
|
+
async function runAdGeoSetBid(opts) {
|
|
112351
|
+
const config = loadConfig(opts.token);
|
|
112352
|
+
const googleApiUrl = requireGoogleApi(config);
|
|
112353
|
+
if (opts.bidModifier < 0) {
|
|
112354
|
+
console.error("\n\u274C --bid-modifier \u4E0D\u80FD\u4E3A\u8D1F\u6570\n");
|
|
112355
|
+
process.exit(1);
|
|
112356
|
+
}
|
|
112357
|
+
let rows;
|
|
112358
|
+
try {
|
|
112359
|
+
rows = await fetchTargetedGeoRows(
|
|
112360
|
+
config,
|
|
112361
|
+
googleApiUrl,
|
|
112362
|
+
opts.account,
|
|
112363
|
+
opts.campaignId,
|
|
112364
|
+
opts.verbose
|
|
112365
|
+
);
|
|
112366
|
+
} catch (err) {
|
|
112367
|
+
console.error(`
|
|
112368
|
+
\u274C \u62C9\u53D6\u5DF2\u5B9A\u5411\u5730\u7406\u4F4D\u7F6E\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
|
|
112369
|
+
`);
|
|
112370
|
+
process.exit(1);
|
|
112371
|
+
}
|
|
112372
|
+
let criterionId;
|
|
112373
|
+
try {
|
|
112374
|
+
criterionId = resolveGeoCriterionId(rows, {
|
|
112375
|
+
criterionId: opts.criterionId,
|
|
112376
|
+
locationId: opts.locationId,
|
|
112377
|
+
campaignId: opts.campaignId
|
|
112378
|
+
});
|
|
112379
|
+
} catch (err) {
|
|
112380
|
+
console.error(`
|
|
112381
|
+
\u274C ${err instanceof Error ? err.message : String(err)}
|
|
112382
|
+
`);
|
|
112383
|
+
process.exit(1);
|
|
112384
|
+
}
|
|
112385
|
+
const putUrl = `${googleApiUrl}/campaignmanagement/${opts.account}/campaigns/${opts.campaignId}/Criteria/${criterionId}/BidModifier/${opts.bidModifier}`;
|
|
112386
|
+
try {
|
|
112387
|
+
await apiFetch2(putUrl, config, { method: "PUT" }, opts.verbose);
|
|
112388
|
+
} catch (err) {
|
|
112389
|
+
console.error(`
|
|
112390
|
+
\u274C \u4FEE\u6539\u5730\u7406\u4F4D\u7F6E\u51FA\u4EF7\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
|
|
112391
|
+
`);
|
|
112392
|
+
process.exit(1);
|
|
112393
|
+
}
|
|
112394
|
+
const row = rows.find((r) => String(r["id"] ?? "") === criterionId);
|
|
112395
|
+
const name = String(
|
|
112396
|
+
row?.["locationName"] ?? row?.["name"] ?? row?.["countryOrRegion"] ?? criterionId
|
|
112397
|
+
);
|
|
112398
|
+
console.log(
|
|
112399
|
+
`
|
|
112400
|
+
\u2705 \u5DF2\u66F4\u65B0\u5730\u7406\u4F4D\u7F6E\u51FA\u4EF7\uFF08\u7CFB\u5217 ${opts.campaignId} / ${name} / id ${criterionId} \u2192 \u500D\u7387 ${opts.bidModifier}\uFF0C${formatBidModifierPercent(opts.bidModifier)}\uFF09
|
|
112401
|
+
`
|
|
112402
|
+
);
|
|
112403
|
+
}
|
|
112404
|
+
|
|
112212
112405
|
// src/commands/ad/ai-creation.ts
|
|
112213
112406
|
init_auth();
|
|
112214
112407
|
init_cli_json_snapshot();
|
|
@@ -113146,46 +113339,53 @@ async function runAiCreationUpdate(opts) {
|
|
|
113146
113339
|
|
|
113147
113340
|
// src/commands/ad/campaign-validate.ts
|
|
113148
113341
|
import { writeFileSync as writeFileSync3 } from "fs";
|
|
113342
|
+
init_cli_json_snapshot();
|
|
113343
|
+
var LENGTH_VIOLATION_AGENT_HINT = "\u52FF\u81EA\u52A8\u622A\u65AD JSON\u3002\u8BFB\u53D6\u843D\u76D8 JSON \u7684 lengthViolations\uFF0C\u5C06\u5168\u90E8\u6761\u76EE\u4E0E\u4FEE\u6539\u65B9\u6848\u5217\u7ED9\u7528\u6237\uFF0C\u786E\u8BA4\u540E\u5199\u5165 campaign.json \u5E76\u91CD\u65B0 campaign-validate\u3002";
|
|
113149
113344
|
async function runAdCampaignValidate(opts) {
|
|
113150
113345
|
const cfg = loadCampaignCreateConfig(opts.configFile);
|
|
113151
|
-
const { errors, warnings } = runCampaignCreateValidation(cfg);
|
|
113346
|
+
const { errors, warnings, lengthViolations } = runCampaignCreateValidation(cfg);
|
|
113152
113347
|
if (opts.writeNormalized) {
|
|
113153
113348
|
const toWrite = stripMetaKeysForExport(cfg);
|
|
113154
113349
|
writeFileSync3(opts.writeNormalized, `${JSON.stringify(toWrite, null, 2)}
|
|
113155
113350
|
`, "utf8");
|
|
113156
113351
|
}
|
|
113157
|
-
|
|
113158
|
-
|
|
113159
|
-
|
|
113160
|
-
|
|
113161
|
-
|
|
113162
|
-
|
|
113163
|
-
|
|
113164
|
-
|
|
113165
|
-
|
|
113166
|
-
|
|
113167
|
-
|
|
113168
|
-
|
|
113169
|
-
|
|
113170
|
-
|
|
113171
|
-
|
|
113172
|
-
|
|
113173
|
-
|
|
113174
|
-
if (
|
|
113175
|
-
|
|
113176
|
-
|
|
113177
|
-
|
|
113178
|
-
|
|
113179
|
-
}
|
|
113180
|
-
console.log("\n\u2705 \u6295\u653E\u914D\u7F6E\u6821\u9A8C\u901A\u8FC7");
|
|
113181
|
-
if (opts.writeNormalized) {
|
|
113182
|
-
console.log(` \u5DF2\u5199\u5165\u89C4\u8303\u5316 JSON\uFF1A${opts.writeNormalized}`);
|
|
113183
|
-
}
|
|
113184
|
-
console.log();
|
|
113352
|
+
const payload = {
|
|
113353
|
+
ok: errors.length === 0,
|
|
113354
|
+
configFile: opts.configFile,
|
|
113355
|
+
account: cfg.account?.toString().trim() || void 0,
|
|
113356
|
+
errors,
|
|
113357
|
+
warnings,
|
|
113358
|
+
lengthViolations,
|
|
113359
|
+
agentHint: lengthViolations.length > 0 ? LENGTH_VIOLATION_AGENT_HINT : void 0
|
|
113360
|
+
};
|
|
113361
|
+
const accountSuffix = payload.account || void 0;
|
|
113362
|
+
if (await emitCliJsonOrSnapshot(opts, {
|
|
113363
|
+
section: "ad-campaign-validate",
|
|
113364
|
+
commandLabel: "ad campaign-validate",
|
|
113365
|
+
commandHint: opts.configFile,
|
|
113366
|
+
payload,
|
|
113367
|
+
idSuffix: accountSuffix
|
|
113368
|
+
})) {
|
|
113369
|
+
if (!payload.ok) process.exit(1);
|
|
113370
|
+
return;
|
|
113371
|
+
}
|
|
113372
|
+
if (warnings.length > 0) {
|
|
113373
|
+
console.warn("\n\u26A0\uFE0F \u6295\u653E\u914D\u7F6E\u8B66\u544A\uFF1A");
|
|
113374
|
+
for (const w of warnings) console.warn(` \u2022 ${w}`);
|
|
113185
113375
|
}
|
|
113186
113376
|
if (errors.length > 0) {
|
|
113377
|
+
console.error("\n\u274C \u6295\u653E\u914D\u7F6E\u6821\u9A8C\u5931\u8D25\uFF1A");
|
|
113378
|
+
for (const e of errors) console.error(` \u2022 ${e}`);
|
|
113379
|
+
const lengthReport = formatLengthViolationsReport(lengthViolations);
|
|
113380
|
+
if (lengthReport) console.error(lengthReport);
|
|
113381
|
+
console.error();
|
|
113187
113382
|
process.exit(1);
|
|
113188
113383
|
}
|
|
113384
|
+
console.log("\n\u2705 \u6295\u653E\u914D\u7F6E\u6821\u9A8C\u901A\u8FC7");
|
|
113385
|
+
if (opts.writeNormalized) {
|
|
113386
|
+
console.log(` \u5DF2\u5199\u5165\u89C4\u8303\u5316 JSON\uFF1A${opts.writeNormalized}`);
|
|
113387
|
+
}
|
|
113388
|
+
console.log();
|
|
113189
113389
|
}
|
|
113190
113390
|
|
|
113191
113391
|
// src/commands/ad/_register.ts
|
|
@@ -113535,16 +113735,20 @@ function register20(program2) {
|
|
|
113535
113735
|
}
|
|
113536
113736
|
);
|
|
113537
113737
|
adCmd.command("campaign-validate").description(
|
|
113538
|
-
"\u6821\u9A8C campaign-create JSON\uFF08\u8BCD\u9762\u89C4\u8303\u5316 + \u540E\u7AEF\u786C\u7EA6\u675F\uFF1B\u4E0D\u8C03\u7528 API\uFF09\n\n \u7528\u6CD5\uFF1A\n siluzan-tso ad campaign-validate --config-file ./campaign.json\n siluzan-tso ad campaign-validate --config-file ./campaign.json --write-normalized ./campaign.normalized.json"
|
|
113738
|
+
"\u6821\u9A8C campaign-create JSON\uFF08\u8BCD\u9762\u89C4\u8303\u5316 + \u540E\u7AEF\u786C\u7EA6\u675F\uFF1B\u4E0D\u8C03\u7528 API\uFF09\n\n \u7528\u6CD5\uFF1A\n siluzan-tso ad campaign-validate --config-file ./campaign.json --json-out ./snap-campaign\n siluzan-tso ad campaign-validate --config-file ./campaign.json --write-normalized ./campaign.normalized.json"
|
|
113539
113739
|
).requiredOption("--config-file <path>", "campaign-create JSON \u8DEF\u5F84").option(
|
|
113540
113740
|
"--write-normalized <path>",
|
|
113541
113741
|
"\u5C06\u89C4\u8303\u5316\u540E\u7684 JSON \u5199\u5165\u8BE5\u8DEF\u5F84\uFF08\u5173\u952E\u8BCD\u8BCD\u9762\u5DF2\u4FEE\u6B63\uFF09"
|
|
113542
|
-
).option("--json", "\u8F93\u51FA { ok, errors, warnings }
|
|
113742
|
+
).option("--json", "\u8F93\u51FA { ok, errors, warnings, lengthViolations }\uFF08\u4E0E --json-out \u4E92\u65A5\uFF09", false).option(
|
|
113743
|
+
"--json-out <path>",
|
|
113744
|
+
"\u843D\u76D8\u6821\u9A8C\u7ED3\u679C\uFF08\u542B lengthViolations\uFF09\u5E76\u66F4\u65B0 cli-manifest\uFF1B\u4E0E --json \u4E92\u65A5"
|
|
113745
|
+
).option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(
|
|
113543
113746
|
async (opts) => {
|
|
113544
113747
|
await runAdCampaignValidate({
|
|
113545
113748
|
configFile: opts.configFile,
|
|
113546
113749
|
writeNormalized: opts.writeNormalized,
|
|
113547
113750
|
json: opts.json,
|
|
113751
|
+
jsonOut: opts.jsonOut,
|
|
113548
113752
|
verbose: opts.verbose
|
|
113549
113753
|
});
|
|
113550
113754
|
}
|
|
@@ -113705,7 +113909,7 @@ function register20(program2) {
|
|
|
113705
113909
|
'\u65B0\u5339\u914D\u7C7B\u578B\uFF1ABroad | Phrase | Exact\uFF08\u5199 matchTypeV2\uFF0C\u5E76\u9ED8\u8BA4\u540C\u6B65\u6539\u5199 keywordText \u4E3A\u8BCD\u5E72/"\u8BCD"/[\u8BCD] \u4EE5\u7B26\u5408\u7F51\u5173\u63A8\u65AD\uFF09'
|
|
113706
113910
|
).option(
|
|
113707
113911
|
"--max-cpc <n>",
|
|
113708
|
-
"\u6700\u9AD8\u6BCF\u6B21\u70B9\u51FB\u8D39\u7528 maxCPC\uFF0C\u4E3B\u5E01\u79CD\u91D1\u989D\uFF08\u5982
|
|
113912
|
+
"\u6700\u9AD8\u6BCF\u6B21\u70B9\u51FB\u8D39\u7528 maxCPC\uFF0C\u4E3B\u5E01\u79CD\u91D1\u989D\uFF08\u5982 10 \u8868\u793A \xA510\uFF1BCLI \u5185\u90E8 \xD7100 \u5199\u5165\u300C\u5206\u300D\u5B57\u6BB5\uFF0C\u4E0E adgroup-edit --max-cpc \u540C\u53E3\u5F84\uFF1B0 \u8868\u793A\u6309\u5E73\u53F0/\u8BA1\u5212\u9ED8\u8BA4\uFF09"
|
|
113709
113913
|
).option("--final-url <url>", "\u5173\u952E\u8BCD\u7EA7\u6700\u7EC8\u5230\u8FBE\u7F51\u5740 finalURL").option("--status <Enabled|Paused>", "\u7528\u6237\u72B6\u6001\uFF08\u5199\u5165 userStatusV2\uFF0C\u4E0E Web \u5173\u952E\u8BCD\u5F00\u5173\u4E00\u81F4\uFF09").option("--start <date>", "\u5217\u8868\u67E5\u8BE2\u8D77\u59CB\u65E5\u671F YYYY-MM-DD").option("--end <date>", "\u5217\u8868\u67E5\u8BE2\u7ED3\u675F\u65E5\u671F YYYY-MM-DD").option("-t, --token <token>", "Auth Token").option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(
|
|
113710
113914
|
async (opts) => {
|
|
113711
113915
|
if (opts.matchType && !["Broad", "Phrase", "Exact"].includes(opts.matchType)) {
|
|
@@ -113908,7 +114112,7 @@ function register20(program2) {
|
|
|
113908
114112
|
});
|
|
113909
114113
|
}
|
|
113910
114114
|
);
|
|
113911
|
-
const geoCmd = adCmd.command("geo").description("\u5730\u7406\u4F4D\u7F6E\u5B9A\u5411\u7BA1\u7406\uFF08\u641C\u7D22/\u5217\u8868/\u6DFB\u52A0/\u5220\u9664\uFF09");
|
|
114115
|
+
const geoCmd = adCmd.command("geo").description("\u5730\u7406\u4F4D\u7F6E\u5B9A\u5411\u7BA1\u7406\uFF08\u641C\u7D22/\u5217\u8868/\u6DFB\u52A0/\u5220\u9664/\u51FA\u4EF7\u8C03\u6574\uFF09");
|
|
113912
114116
|
geoCmd.command("search").description("\u641C\u7D22\u53EF\u7528\u7684\u5730\u7406\u4F4D\u7F6E\uFF08\u83B7\u53D6 locationId \u7528\u4E8E\u6DFB\u52A0\uFF09").requiredOption("-a, --account <id>", "Google \u8D26\u6237 mediaCustomerId").requiredOption("-q, --query <text>", "\u5730\u7406\u4F4D\u7F6E\u540D\u79F0\uFF0C\u5982 Beijing | United States").option("-t, --token <token>", "Auth Token").option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(async (opts) => {
|
|
113913
114117
|
await runAdGeoSearch({
|
|
113914
114118
|
token: opts.token,
|
|
@@ -113966,6 +114170,33 @@ function register20(program2) {
|
|
|
113966
114170
|
});
|
|
113967
114171
|
}
|
|
113968
114172
|
);
|
|
114173
|
+
geoCmd.command("set-bid").description(
|
|
114174
|
+
"\u4FEE\u6539\u5DF2\u5B9A\u5411\u5730\u7406\u4F4D\u7F6E\u7684\u51FA\u4EF7\u8C03\u6574\uFF08PUT Criteria/\u2026/BidModifier\uFF1B\u500D\u7387\u53E3\u5F84\u540C ad device-bid set\uFF09"
|
|
114175
|
+
).requiredOption("-a, --account <id>", "Google \u8D26\u6237 mediaCustomerId").requiredOption("--campaign-id <id>", "\u5E7F\u544A\u7CFB\u5217 ID").requiredOption(
|
|
114176
|
+
"--bid-modifier <n>",
|
|
114177
|
+
"Google \u51FA\u4EF7\u500D\u7387\uFF1A1.0=\u4E0D\u8C03\u6574\uFF0C1.2=+20%\uFF0C0.8=-20%",
|
|
114178
|
+
parseFloat
|
|
114179
|
+
).option("--criterion-id <id>", "campaign_criterion id\uFF08geo list --mode targeted --json \u2192 id\uFF09").option("--location-id <id>", "\u5730\u7406\u4F4D\u7F6E ID\uFF08\u4E0E list/search \u7684 id \u76F8\u540C\uFF1B\u4E0E --criterion-id \u4E8C\u9009\u4E00\uFF09").option("-t, --token <token>", "Auth Token").option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(
|
|
114180
|
+
async (opts) => {
|
|
114181
|
+
if (!opts.criterionId && !opts.locationId) {
|
|
114182
|
+
console.error("\n\u274C \u987B\u6307\u5B9A --criterion-id \u6216 --location-id\n");
|
|
114183
|
+
process.exit(1);
|
|
114184
|
+
}
|
|
114185
|
+
if (Number.isNaN(opts.bidModifier)) {
|
|
114186
|
+
console.error("\n\u274C --bid-modifier \u987B\u4E3A\u6709\u6548\u6570\u5B57\n");
|
|
114187
|
+
process.exit(1);
|
|
114188
|
+
}
|
|
114189
|
+
await runAdGeoSetBid({
|
|
114190
|
+
token: opts.token,
|
|
114191
|
+
account: opts.account,
|
|
114192
|
+
campaignId: opts.campaignId,
|
|
114193
|
+
bidModifier: opts.bidModifier,
|
|
114194
|
+
criterionId: opts.criterionId,
|
|
114195
|
+
locationId: opts.locationId,
|
|
114196
|
+
verbose: opts.verbose
|
|
114197
|
+
});
|
|
114198
|
+
}
|
|
114199
|
+
);
|
|
113969
114200
|
const deviceBidCmd = adCmd.command("device-bid").description("\u8BBE\u5907\u51FA\u4EF7\u8C03\u6574\uFF08\u7CFB\u5217\u7EA7 / \u5E7F\u544A\u7EC4\u7EA7\uFF1B\u4E0E\u524D\u7AEF updateDeviceAndAddress \u540C\u6E90\uFF09");
|
|
113970
114201
|
deviceBidCmd.command("list").description("\u67E5\u8BE2\u8BBE\u5907\u51FA\u4EF7\u8C03\u6574\u5217\u8868\uFF08\u7CFB\u5217\u7EA7\u9ED8\u8BA4\uFF1B\u5E7F\u544A\u7EC4\u7EA7\u9700 --level adgroup\uFF09").requiredOption("-a, --account <id>", "Google \u8D26\u6237 mediaCustomerId").option("--level <level>", "campaign | adgroup\uFF08\u9ED8\u8BA4 campaign\uFF09", "campaign").option("--campaign-id <id>", "\u53EF\u9009\uFF1A\u6309\u5E7F\u544A\u7CFB\u5217\u8FC7\u6EE4\uFF08\u7CFB\u5217\u7EA7\uFF09\u6216\u5FC5\u586B\u4E0A\u4E0B\u6587\uFF08\u5E7F\u544A\u7EC4\u7EA7\uFF09").option("--ad-group-id <id>", "\u5E7F\u544A\u7EC4 ID\uFF08--level adgroup \u65F6\u5FC5\u586B\uFF09").option("-t, --token <token>", "Auth Token").option("--json", "JSON \u683C\u5F0F\u8F93\u51FA", false).option(
|
|
113971
114202
|
"--json-out <path>",
|
|
@@ -114022,59 +114253,6 @@ function register20(program2) {
|
|
|
114022
114253
|
|
|
114023
114254
|
// src/commands/keyword.ts
|
|
114024
114255
|
init_auth();
|
|
114025
|
-
|
|
114026
|
-
// src/utils/usd-cny-rate.ts
|
|
114027
|
-
init_auth();
|
|
114028
|
-
var RATE_CACHE_TTL_MS = 10 * 60 * 1e3;
|
|
114029
|
-
var rateCache = null;
|
|
114030
|
-
function cacheKey(config) {
|
|
114031
|
-
return `${config.apiBaseUrl.replace(/\/$/, "")}|USD|CNY`;
|
|
114032
|
-
}
|
|
114033
|
-
function parseExrate(data) {
|
|
114034
|
-
if (data == null || typeof data !== "object") return null;
|
|
114035
|
-
const raw = data.exrate;
|
|
114036
|
-
const n = typeof raw === "string" ? Number(raw) : Number(raw);
|
|
114037
|
-
if (!Number.isFinite(n) || n <= 0) return null;
|
|
114038
|
-
return n;
|
|
114039
|
-
}
|
|
114040
|
-
async function getUsdToCnyExchangeRate(config, verbose) {
|
|
114041
|
-
const key = cacheKey(config);
|
|
114042
|
-
const now = Date.now();
|
|
114043
|
-
if (rateCache && rateCache.key === key && now - rateCache.fetchedAt < RATE_CACHE_TTL_MS) {
|
|
114044
|
-
if (verbose) {
|
|
114045
|
-
console.error(` [\u6C47\u7387] \u4F7F\u7528\u7F13\u5B58 USD\u2192CNY=${rateCache.rate}\uFF08${Math.round((now - rateCache.fetchedAt) / 1e3)}s \u524D\uFF09`);
|
|
114046
|
-
}
|
|
114047
|
-
return rateCache.rate;
|
|
114048
|
-
}
|
|
114049
|
-
const base = config.apiBaseUrl.replace(/\/$/, "");
|
|
114050
|
-
const url = `${base}/allinpay/GetRate?srcccy=USD&dstccy=CNY`;
|
|
114051
|
-
try {
|
|
114052
|
-
const data = await apiFetch2(url, config, { method: "GET" }, verbose);
|
|
114053
|
-
const rate = parseExrate(data);
|
|
114054
|
-
if (rate == null) {
|
|
114055
|
-
console.error("\n\u26A0 \u83B7\u53D6\u6C47\u7387\u5931\u8D25\uFF1AGetRate \u672A\u8FD4\u56DE\u6709\u6548 exrate\uFF0C\u51FA\u4EF7 CNY \u5B57\u6BB5\u5C06\u7701\u7565\u3002\n");
|
|
114056
|
-
return null;
|
|
114057
|
-
}
|
|
114058
|
-
rateCache = { key, rate, fetchedAt: now };
|
|
114059
|
-
if (verbose) {
|
|
114060
|
-
console.error(` [\u6C47\u7387] USD\u2192CNY=${rate}\uFF08\u5DF2\u7F13\u5B58 ${RATE_CACHE_TTL_MS / 6e4} \u5206\u949F\uFF09`);
|
|
114061
|
-
}
|
|
114062
|
-
return rate;
|
|
114063
|
-
} catch (err) {
|
|
114064
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
114065
|
-
console.error(`
|
|
114066
|
-
\u26A0 \u83B7\u53D6 USD\u2192CNY \u6C47\u7387\u5931\u8D25\uFF08\u51FA\u4EF7 CNY \u5B57\u6BB5\u5C06\u7701\u7565\uFF09\uFF1A${msg}
|
|
114067
|
-
`);
|
|
114068
|
-
return null;
|
|
114069
|
-
}
|
|
114070
|
-
}
|
|
114071
|
-
function convertUsdToCny(amountUsd, rate) {
|
|
114072
|
-
if (amountUsd == null || !Number.isFinite(amountUsd)) return null;
|
|
114073
|
-
if (!Number.isFinite(rate) || rate <= 0) return null;
|
|
114074
|
-
return Math.round(amountUsd * rate * 100) / 100;
|
|
114075
|
-
}
|
|
114076
|
-
|
|
114077
|
-
// src/commands/keyword.ts
|
|
114078
114256
|
init_cli_json_snapshot();
|
|
114079
114257
|
init_cli_table();
|
|
114080
114258
|
|
|
@@ -114145,40 +114323,16 @@ function requireGoogleApi2(config) {
|
|
|
114145
114323
|
return config.googleApiUrl;
|
|
114146
114324
|
}
|
|
114147
114325
|
var KEYWORD_SUGGEST_BID_AMOUNT_CURRENCY = "USD";
|
|
114148
|
-
function applyUsdBidAmountsInCny(item, usdToCnyRate) {
|
|
114149
|
-
return {
|
|
114150
|
-
...item,
|
|
114151
|
-
averageCpcCNY: convertUsdToCny(item.averageCpc, usdToCnyRate),
|
|
114152
|
-
lowTopOfPageBidCNY: convertUsdToCny(item.lowTopOfPageBid, usdToCnyRate),
|
|
114153
|
-
highTopOfPageBidCNY: convertUsdToCny(item.highTopOfPageBid, usdToCnyRate)
|
|
114154
|
-
};
|
|
114155
|
-
}
|
|
114156
|
-
function withUsdFieldAliases(item) {
|
|
114157
|
-
return {
|
|
114158
|
-
...item,
|
|
114159
|
-
averageCpcUSD: item.averageCpc,
|
|
114160
|
-
lowTopOfPageBidUSD: item.lowTopOfPageBid,
|
|
114161
|
-
highTopOfPageBidUSD: item.highTopOfPageBid
|
|
114162
|
-
};
|
|
114163
|
-
}
|
|
114164
114326
|
function buildKeywordSuggestJsonPayload(items, opts) {
|
|
114165
114327
|
const currency = opts.bidAmountCurrency.toUpperCase();
|
|
114166
|
-
|
|
114328
|
+
const normalized = items.map((row) => ({
|
|
114167
114329
|
...microsItemToBidAmounts(row),
|
|
114168
114330
|
bidAmountCurrency: currency
|
|
114169
114331
|
}));
|
|
114170
|
-
const rate = opts.usdToCnyRate;
|
|
114171
|
-
const isUsdPlanner = currency === KEYWORD_SUGGEST_BID_AMOUNT_CURRENCY;
|
|
114172
|
-
if (isUsdPlanner && rate != null && Number.isFinite(rate) && rate > 0) {
|
|
114173
|
-
normalized = normalized.map((row) => withUsdFieldAliases(applyUsdBidAmountsInCny(row, rate)));
|
|
114174
|
-
} else if (isUsdPlanner) {
|
|
114175
|
-
normalized = normalized.map(withUsdFieldAliases);
|
|
114176
|
-
}
|
|
114177
114332
|
const n = normalized.length;
|
|
114178
114333
|
return {
|
|
114179
114334
|
...wrapListJson({ page: 1, pageSize: Math.max(n, 1), total: n, items: normalized }),
|
|
114180
|
-
bidAmountCurrency: currency
|
|
114181
|
-
...isUsdPlanner && rate != null && Number.isFinite(rate) && rate > 0 ? { usdToCnyExchangeRate: rate } : {}
|
|
114335
|
+
bidAmountCurrency: currency
|
|
114182
114336
|
};
|
|
114183
114337
|
}
|
|
114184
114338
|
async function fetchUrlKeywords(url, keywords, verbose) {
|
|
@@ -114367,12 +114521,7 @@ async function runKeywordSuggest(opts) {
|
|
|
114367
114521
|
});
|
|
114368
114522
|
}
|
|
114369
114523
|
const n = items.length;
|
|
114370
|
-
const
|
|
114371
|
-
const usdToCnyRate = isUsdPlanner ? await getUsdToCnyExchangeRate(config, opts.verbose) : null;
|
|
114372
|
-
const kwPayload = buildKeywordSuggestJsonPayload(items, {
|
|
114373
|
-
bidAmountCurrency,
|
|
114374
|
-
usdToCnyRate
|
|
114375
|
-
});
|
|
114524
|
+
const kwPayload = buildKeywordSuggestJsonPayload(items, { bidAmountCurrency });
|
|
114376
114525
|
const displayItems = kwPayload.items;
|
|
114377
114526
|
if (await emitCliJsonOrSnapshot(opts, {
|
|
114378
114527
|
section: "keyword-suggest",
|
|
@@ -114390,16 +114539,7 @@ async function runKeywordSuggest(opts) {
|
|
|
114390
114539
|
}
|
|
114391
114540
|
const fmtAmt = (v) => v != null && Number.isFinite(v) ? v.toFixed(2) : "\u2014";
|
|
114392
114541
|
const cur = bidAmountCurrency;
|
|
114393
|
-
const
|
|
114394
|
-
const cols = hasCnyRef ? [
|
|
114395
|
-
{ key: "keyword", header: "\u5173\u952E\u8BCD" },
|
|
114396
|
-
{ key: "montlySearch", header: "\u6708\u5747\u641C\u7D22" },
|
|
114397
|
-
{ key: "cpcMain", header: `\u5E73\u5747CPC(${cur})` },
|
|
114398
|
-
{ key: "cpcCny", header: "\u5E73\u5747CPC(CNY\u53C2\u8003)" },
|
|
114399
|
-
{ key: "bidRangeMain", header: `\u9875\u9996\u51FA\u4EF7(${cur})` },
|
|
114400
|
-
{ key: "bidRangeCny", header: "\u9875\u9996\u51FA\u4EF7(CNY\u53C2\u8003)" },
|
|
114401
|
-
{ key: "competition", header: "\u7ADE\u4E89\u5EA6" }
|
|
114402
|
-
] : [
|
|
114542
|
+
const cols = [
|
|
114403
114543
|
{ key: "keyword", header: "\u5173\u952E\u8BCD" },
|
|
114404
114544
|
{ key: "montlySearch", header: "\u6708\u5747\u641C\u7D22" },
|
|
114405
114545
|
{ key: "cpcMain", header: `\u5E73\u5747CPC(${cur})` },
|
|
@@ -114412,33 +114552,21 @@ async function runKeywordSuggest(opts) {
|
|
|
114412
114552
|
const highMain = fmtAmt(item.highTopOfPageBid);
|
|
114413
114553
|
const bidRangeMain = lowMain === "\u2014" && highMain === "\u2014" ? "\u2014" : `${lowMain} ~ ${highMain}`;
|
|
114414
114554
|
const competitionDisplay = item.competitionV2 ?? (item.competition != null ? item.competition.toFixed(2) : "\u2014");
|
|
114415
|
-
|
|
114555
|
+
return {
|
|
114416
114556
|
keyword: item.keyword ?? "",
|
|
114417
114557
|
montlySearch: String(item.montlySearch ?? "\u2014"),
|
|
114418
114558
|
cpcMain,
|
|
114419
114559
|
bidRangeMain,
|
|
114420
114560
|
competition: competitionDisplay
|
|
114421
114561
|
};
|
|
114422
|
-
if (hasCnyRef) {
|
|
114423
|
-
const cpcCny = fmtAmt(item.averageCpcCNY);
|
|
114424
|
-
const lowCny = fmtAmt(item.lowTopOfPageBidCNY);
|
|
114425
|
-
const highCny = fmtAmt(item.highTopOfPageBidCNY);
|
|
114426
|
-
row.cpcCny = cpcCny;
|
|
114427
|
-
row.bidRangeCny = lowCny === "\u2014" && highCny === "\u2014" ? "\u2014" : `${lowCny} ~ ${highCny}`;
|
|
114428
|
-
}
|
|
114429
|
-
return row;
|
|
114430
114562
|
});
|
|
114431
114563
|
printCliTable(rows, cols);
|
|
114432
|
-
if (
|
|
114433
|
-
console.log(
|
|
114434
|
-
` \uFF08\u51FA\u4EF7\u5E01\u79CD ${cur}\uFF1BCNY \u4E3A\u6C47\u7387 ${usdToCnyRate} \u6362\u7B97\u53C2\u8003\uFF0C\u975E Google \u8D26\u6237\u5E01\u79CD\u53E3\u5F84\uFF09
|
|
114435
|
-
`
|
|
114436
|
-
);
|
|
114437
|
-
} else if (accountId) {
|
|
114564
|
+
if (accountId) {
|
|
114438
114565
|
console.log(` \uFF08\u51FA\u4EF7\u5E01\u79CD\uFF1A${cur}\uFF0C\u4E0E\u8D26\u6237 currencyCode \u4E00\u81F4\uFF09
|
|
114439
114566
|
`);
|
|
114440
114567
|
} else {
|
|
114441
|
-
console.log(
|
|
114568
|
+
console.log(` \uFF08\u51FA\u4EF7\u5E01\u79CD\uFF1A${cur}\uFF09
|
|
114569
|
+
`);
|
|
114442
114570
|
}
|
|
114443
114571
|
}
|
|
114444
114572
|
function microsToAmount(v) {
|
|
@@ -115686,8 +115814,8 @@ async function runAccountWithdrawSubmit(opts) {
|
|
|
115686
115814
|
});
|
|
115687
115815
|
const feeRateCache = /* @__PURE__ */ new Map();
|
|
115688
115816
|
for (const { currency, availableAmount } of accountDetails) {
|
|
115689
|
-
const
|
|
115690
|
-
if (feeRateCache.has(
|
|
115817
|
+
const cacheKey = `${currency}-${availableAmount.toFixed(2)}`;
|
|
115818
|
+
if (feeRateCache.has(cacheKey)) continue;
|
|
115691
115819
|
try {
|
|
115692
115820
|
const feeParams = new URLSearchParams({
|
|
115693
115821
|
mediaType: "Google",
|
|
@@ -115700,16 +115828,16 @@ async function runAccountWithdrawSubmit(opts) {
|
|
|
115700
115828
|
{},
|
|
115701
115829
|
opts.verbose
|
|
115702
115830
|
);
|
|
115703
|
-
feeRateCache.set(
|
|
115831
|
+
feeRateCache.set(cacheKey, feeData?.feeRate ?? 0);
|
|
115704
115832
|
} catch {
|
|
115705
115833
|
console.warn(` \u26A0\uFE0F ${currency} \u7BA1\u7406\u8D39\u67E5\u8BE2\u5931\u8D25\uFF0C\u5C06\u4EE5 0 \u8D39\u7387\u63D0\u4EA4\u3002`);
|
|
115706
|
-
feeRateCache.set(
|
|
115834
|
+
feeRateCache.set(cacheKey, 0);
|
|
115707
115835
|
}
|
|
115708
115836
|
}
|
|
115709
115837
|
const body = accountDetails.map(
|
|
115710
115838
|
({ ma, mai, currency, balance, adjustments, availableAmount }) => {
|
|
115711
|
-
const
|
|
115712
|
-
const feeRate = feeRateCache.get(
|
|
115839
|
+
const cacheKey = `${currency}-${availableAmount.toFixed(2)}`;
|
|
115840
|
+
const feeRate = feeRateCache.get(cacheKey) ?? 0;
|
|
115713
115841
|
const taxRate = currency === "CNY" ? 0.06 : 0;
|
|
115714
115842
|
const totalAmounts = availableAmount * (1 + feeRate) * (1 + taxRate);
|
|
115715
115843
|
return {
|