siluzan-tso-cli 1.1.29-beta.3 → 1.1.29-beta.4
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 +278 -26
- package/dist/skill/_meta.json +2 -2
- package/dist/skill/assets/pmax-create-template.json +9 -0
- package/dist/skill/assets/pmax-create-template.md +2 -1
- package/dist/skill/assets/pmax-lead-form-template.json +1 -1
- package/dist/skill/assets/pmax-lead-form-template.md +1 -1
- package/dist/skill/assets/pmax-whatsapp-template.json +26 -0
- package/dist/skill/assets/pmax-whatsapp-template.md +45 -0
- package/dist/skill/references/core/cli-enums.md +2 -1
- package/dist/skill/references/google-ads/google-ads.md +8 -5
- package/dist/skill/references/google-ads/pmax-api.md +11 -3
- package/dist/skill/scripts/install.ps1 +1 -1
- package/dist/skill/scripts/install.sh +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -51,7 +51,7 @@ siluzan-tso init -d /path/to/skills # 写入自定义目录
|
|
|
51
51
|
siluzan-tso init --force # 强制覆盖已存在文件
|
|
52
52
|
```
|
|
53
53
|
|
|
54
|
-
> **注意**:当前为测试版(1.1.29-beta.
|
|
54
|
+
> **注意**:当前为测试版(1.1.29-beta.4),供内部测试使用。正式发布后安装命令将改为 `npm install -g siluzan-tso-cli`。
|
|
55
55
|
|
|
56
56
|
| 助手 | 建议 `--ai` |
|
|
57
57
|
| ----------------------- | ------------------------------------ |
|
package/dist/index.js
CHANGED
|
@@ -112491,6 +112491,7 @@ async function runAdEdit(opts) {
|
|
|
112491
112491
|
|
|
112492
112492
|
// src/commands/ad/extension.ts
|
|
112493
112493
|
init_auth();
|
|
112494
|
+
import { readFileSync as readFileSync7 } from "fs";
|
|
112494
112495
|
init_cli_json_snapshot();
|
|
112495
112496
|
init_strip_legacy_google_fields();
|
|
112496
112497
|
|
|
@@ -112606,6 +112607,101 @@ function buildLeadFormExtensionBody(cfg, extensionId) {
|
|
|
112606
112607
|
return body;
|
|
112607
112608
|
}
|
|
112608
112609
|
|
|
112610
|
+
// src/commands/ad/extension-whatsapp.ts
|
|
112611
|
+
import { readFileSync as readFileSync6 } from "fs";
|
|
112612
|
+
var VALID_WHATSAPP_CTA_SELECTIONS = [
|
|
112613
|
+
"CONTACT_US",
|
|
112614
|
+
"LEARN_MORE",
|
|
112615
|
+
"GET_QUOTE",
|
|
112616
|
+
"BOOK_NOW",
|
|
112617
|
+
"SHOP_NOW",
|
|
112618
|
+
"SIGN_UP"
|
|
112619
|
+
];
|
|
112620
|
+
function loadWhatsappExtensionConfig(configFile) {
|
|
112621
|
+
let raw;
|
|
112622
|
+
try {
|
|
112623
|
+
raw = JSON.parse(readFileSync6(configFile, "utf8"));
|
|
112624
|
+
} catch {
|
|
112625
|
+
console.error(`
|
|
112626
|
+
\u274C \u8BFB\u53D6\u914D\u7F6E\u6587\u4EF6\u5931\u8D25\uFF08${configFile}\uFF09
|
|
112627
|
+
`);
|
|
112628
|
+
process.exit(1);
|
|
112629
|
+
}
|
|
112630
|
+
const cfg = stripMetaKeys(raw);
|
|
112631
|
+
const result = validateWhatsappExtensionConfig(cfg);
|
|
112632
|
+
if (!result.ok) {
|
|
112633
|
+
console.error("\n\u274C WhatsApp \u914D\u7F6E\u6821\u9A8C\u5931\u8D25\uFF1A\n");
|
|
112634
|
+
for (const e of result.errors) console.error(` - ${e}`);
|
|
112635
|
+
console.error();
|
|
112636
|
+
process.exit(1);
|
|
112637
|
+
}
|
|
112638
|
+
return cfg;
|
|
112639
|
+
}
|
|
112640
|
+
function validateBusinessMessagePayload(bm, prefix = "businessMessage") {
|
|
112641
|
+
const errors = [];
|
|
112642
|
+
if (!bm || typeof bm !== "object") {
|
|
112643
|
+
errors.push(`${prefix} \u5BF9\u8C61\u4E0D\u80FD\u4E3A\u7A7A`);
|
|
112644
|
+
return errors;
|
|
112645
|
+
}
|
|
112646
|
+
if (!bm.starterMessage?.trim()) {
|
|
112647
|
+
errors.push(`${prefix}.starterMessage \u4E0D\u80FD\u4E3A\u7A7A`);
|
|
112648
|
+
}
|
|
112649
|
+
const provider = (bm.messageProvider?.trim() || "WHATSAPP").toUpperCase();
|
|
112650
|
+
if (provider !== "WHATSAPP") {
|
|
112651
|
+
errors.push(`${prefix}.messageProvider \u5F53\u524D\u4EC5\u652F\u6301 WHATSAPP`);
|
|
112652
|
+
}
|
|
112653
|
+
const cta = bm.callToActionSelection?.trim();
|
|
112654
|
+
if (cta && !VALID_WHATSAPP_CTA_SELECTIONS.includes(cta.toUpperCase())) {
|
|
112655
|
+
errors.push(
|
|
112656
|
+
`${prefix}.callToActionSelection \u65E0\u6548\uFF1A${cta}\uFF1B\u652F\u6301 ${VALID_WHATSAPP_CTA_SELECTIONS.join(", ")}`
|
|
112657
|
+
);
|
|
112658
|
+
}
|
|
112659
|
+
const wa = bm.whatsapp;
|
|
112660
|
+
if (!wa?.countryCode?.trim()) {
|
|
112661
|
+
errors.push(`${prefix}.whatsapp.countryCode \u4E0D\u80FD\u4E3A\u7A7A\uFF08\u5982 US\u3001CN\uFF09`);
|
|
112662
|
+
}
|
|
112663
|
+
if (!wa?.phoneNumber?.trim()) {
|
|
112664
|
+
errors.push(`${prefix}.whatsapp.phoneNumber \u4E0D\u80FD\u4E3A\u7A7A\uFF08WhatsApp Business \u6CE8\u518C\u53F7\u7801\uFF09`);
|
|
112665
|
+
}
|
|
112666
|
+
return errors;
|
|
112667
|
+
}
|
|
112668
|
+
function validateWhatsappExtensionConfig(cfg) {
|
|
112669
|
+
const errors = [];
|
|
112670
|
+
if (!cfg.account?.trim()) errors.push("account\uFF08\u5A92\u4F53\u5BA2\u6237 ID\uFF09\u4E0D\u80FD\u4E3A\u7A7A");
|
|
112671
|
+
if (!cfg.campaignId?.trim()) errors.push("campaignId\uFF08PMax \u6D3B\u52A8 ID\uFF09\u4E0D\u80FD\u4E3A\u7A7A");
|
|
112672
|
+
errors.push(...validateBusinessMessagePayload(cfg.businessMessage, "businessMessage"));
|
|
112673
|
+
return { ok: errors.length === 0, errors };
|
|
112674
|
+
}
|
|
112675
|
+
function buildBusinessMessageExtensionBody(cfg, extensionId) {
|
|
112676
|
+
const bm = cfg.businessMessage;
|
|
112677
|
+
const body = {
|
|
112678
|
+
activeuseridg: cfg.account,
|
|
112679
|
+
campaignId: cfg.campaignId,
|
|
112680
|
+
level: "Campaign",
|
|
112681
|
+
typeV2: "BUSINESS_MESSAGE",
|
|
112682
|
+
assetFieldType: "BUSINESS_MESSAGE",
|
|
112683
|
+
businessMessage: {
|
|
112684
|
+
messageProvider: (bm.messageProvider?.trim() || "WHATSAPP").toUpperCase(),
|
|
112685
|
+
starterMessage: bm.starterMessage.trim(),
|
|
112686
|
+
callToActionSelection: (bm.callToActionSelection?.trim() || "CONTACT_US").toUpperCase(),
|
|
112687
|
+
callToActionDescription: bm.callToActionDescription?.trim() || "Message us on WhatsApp",
|
|
112688
|
+
whatsapp: {
|
|
112689
|
+
countryCode: bm.whatsapp.countryCode.trim(),
|
|
112690
|
+
phoneNumber: bm.whatsapp.phoneNumber.trim()
|
|
112691
|
+
}
|
|
112692
|
+
}
|
|
112693
|
+
};
|
|
112694
|
+
if (extensionId) body["id"] = extensionId;
|
|
112695
|
+
return body;
|
|
112696
|
+
}
|
|
112697
|
+
function buildBusinessMessageBodyForCampaign(accountId, campaignId, bm) {
|
|
112698
|
+
return buildBusinessMessageExtensionBody({
|
|
112699
|
+
account: accountId,
|
|
112700
|
+
campaignId,
|
|
112701
|
+
businessMessage: bm
|
|
112702
|
+
});
|
|
112703
|
+
}
|
|
112704
|
+
|
|
112609
112705
|
// src/commands/ad/extension.ts
|
|
112610
112706
|
async function runAdExtensionList(opts) {
|
|
112611
112707
|
const config = loadConfig(opts.token);
|
|
@@ -112671,6 +112767,12 @@ async function runAdExtensionList(opts) {
|
|
|
112671
112767
|
} else if (type === "LEAD_FORM") {
|
|
112672
112768
|
const lf = item["leadForm"];
|
|
112673
112769
|
if (lf) detail = `\u300C${lf["headline"] ?? ""}\u300D business=${lf["businessName"] ?? ""}`;
|
|
112770
|
+
} else if (type === "BUSINESS_MESSAGE") {
|
|
112771
|
+
const bm = item["businessMessage"];
|
|
112772
|
+
if (bm) {
|
|
112773
|
+
const wa = bm["whatsapp"];
|
|
112774
|
+
detail = `\u300C${bm["starterMessage"] ?? ""}\u300D +${wa?.["countryCode"] ?? ""}${wa?.["phoneNumber"] ?? ""}`;
|
|
112775
|
+
}
|
|
112674
112776
|
}
|
|
112675
112777
|
const camp = item["campaignId"] ? ` campaign:${item["campaignId"]}` : "";
|
|
112676
112778
|
console.log(` [${type}] id:${id} level:${level}${camp} ${detail}`);
|
|
@@ -112808,11 +112910,13 @@ async function runAdExtensionPmaxTypes(opts) {
|
|
|
112808
112910
|
}
|
|
112809
112911
|
const levels = obj["supportedLevels"]?.join(", ") ?? "";
|
|
112810
112912
|
const lfLevels = obj["leadFormSupportedLevels"]?.join(", ") ?? "";
|
|
112913
|
+
const bmLevels = obj["businessMessageSupportedLevels"]?.join(", ") ?? "";
|
|
112811
112914
|
console.log(`
|
|
112812
112915
|
\u652F\u6301\u5C42\u7EA7\uFF1A${levels}`);
|
|
112813
112916
|
console.log(` Lead Form \u5C42\u7EA7\uFF1A${lfLevels}`);
|
|
112917
|
+
console.log(` WhatsApp\uFF08BUSINESS_MESSAGE\uFF09\u5C42\u7EA7\uFF1A${bmLevels}`);
|
|
112814
112918
|
console.log(" \u4E0D\u652F\u6301\uFF1AAd Group\uFF08PMax \u4F1A 400\uFF09");
|
|
112815
|
-
console.log(" WhatsApp\
|
|
112919
|
+
console.log(" WhatsApp \u9700 Google API \u767D\u540D\u5355\uFF08\u672A\u5F00\u901A\u4F1A CUSTOMER_NOT_ON_ALLOWLIST_FOR_MESSAGE_ASSETS\uFF09\n");
|
|
112816
112920
|
}
|
|
112817
112921
|
async function runAdExtensionSnippetHeaders(opts) {
|
|
112818
112922
|
const config = loadConfig(opts.token);
|
|
@@ -112905,15 +113009,98 @@ async function runAdExtensionLeadFormCreate(opts) {
|
|
|
112905
113009
|
"\u63D0\u793A\uFF1A\u521B\u5EFA\u6210\u529F\u540E\u540E\u7AEF\u4F1A\u81EA\u52A8\u5C1D\u8BD5\u5F00\u542F SUBMIT_LEAD_FORM + GOOGLE_HOSTED \u8F6C\u5316\u76EE\u6807\uFF08biddable=true\uFF09\u3002\n"
|
|
112906
113010
|
);
|
|
112907
113011
|
}
|
|
112908
|
-
async function
|
|
113012
|
+
async function runAdExtensionWhatsappCreate(opts) {
|
|
112909
113013
|
const config = loadConfig(opts.token);
|
|
112910
113014
|
const googleApiUrl = requireGoogleApi(config);
|
|
112911
|
-
|
|
112912
|
-
|
|
113015
|
+
if (!opts.configFile) {
|
|
113016
|
+
console.error(
|
|
113017
|
+
"\n\u274C WhatsApp \u987B\u4F7F\u7528 --config-file\uFF08\u6A21\u677F\u89C1 assets/pmax-whatsapp-template.json\uFF09\n"
|
|
113018
|
+
);
|
|
113019
|
+
process.exit(1);
|
|
113020
|
+
}
|
|
113021
|
+
const cfg = loadWhatsappExtensionConfig(opts.configFile);
|
|
113022
|
+
if (opts.account && opts.account !== cfg.account) {
|
|
112913
113023
|
console.error("\n\u274C --account \u4E0E\u914D\u7F6E\u6587\u4EF6 account \u4E0D\u4E00\u81F4\n");
|
|
112914
113024
|
process.exit(1);
|
|
112915
113025
|
}
|
|
112916
|
-
|
|
113026
|
+
if (opts.campaignId && opts.campaignId !== cfg.campaignId) {
|
|
113027
|
+
console.error("\n\u274C --campaign-id \u4E0E\u914D\u7F6E\u6587\u4EF6 campaignId \u4E0D\u4E00\u81F4\n");
|
|
113028
|
+
process.exit(1);
|
|
113029
|
+
}
|
|
113030
|
+
const body = buildBusinessMessageExtensionBody(cfg);
|
|
113031
|
+
const url = `${googleApiUrl}/extensionmanagement/extension/${cfg.account}`;
|
|
113032
|
+
let response;
|
|
113033
|
+
try {
|
|
113034
|
+
response = await apiFetch2(
|
|
113035
|
+
url,
|
|
113036
|
+
config,
|
|
113037
|
+
{ method: "POST", body: JSON.stringify(body) },
|
|
113038
|
+
opts.verbose
|
|
113039
|
+
);
|
|
113040
|
+
} catch (err) {
|
|
113041
|
+
console.error(`
|
|
113042
|
+
\u274C \u521B\u5EFA WhatsApp \u6269\u5C55\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
|
|
113043
|
+
`);
|
|
113044
|
+
process.exit(1);
|
|
113045
|
+
}
|
|
113046
|
+
if (await emitCliJsonOrSnapshot(opts, {
|
|
113047
|
+
section: `ad-extension-whatsapp-${cfg.account}`,
|
|
113048
|
+
commandLabel: "ad extension whatsapp",
|
|
113049
|
+
commandHint: `${cfg.account}:${cfg.campaignId}`,
|
|
113050
|
+
payload: response,
|
|
113051
|
+
idSuffix: cfg.campaignId
|
|
113052
|
+
})) {
|
|
113053
|
+
return;
|
|
113054
|
+
}
|
|
113055
|
+
const resObj = response ?? {};
|
|
113056
|
+
const extId = resObj["id"];
|
|
113057
|
+
console.log(
|
|
113058
|
+
`
|
|
113059
|
+
\u2705 PMax WhatsApp \u5DF2\u521B\u5EFA\uFF08campaignId: ${cfg.campaignId}${extId ? `\uFF0Cid: ${extId}` : ""}\uFF09
|
|
113060
|
+
`
|
|
113061
|
+
);
|
|
113062
|
+
console.log(
|
|
113063
|
+
"\u63D0\u793A\uFF1A\u8D26\u6237\u987B\u5DF2\u5F00\u901A Google Business Message API \u767D\u540D\u5355\uFF1B\u540C Campaign \u4EC5 1 \u4E2A ENABLED WhatsApp\u3002\n"
|
|
113064
|
+
);
|
|
113065
|
+
}
|
|
113066
|
+
function detectExtensionUpdateKind(raw) {
|
|
113067
|
+
if (raw["businessMessage"] != null) return "whatsapp";
|
|
113068
|
+
if (raw["leadForm"] != null) return "lead-form";
|
|
113069
|
+
console.error("\n\u274C \u914D\u7F6E\u6587\u4EF6\u987B\u542B leadForm \u6216 businessMessage \u5BF9\u8C61\n");
|
|
113070
|
+
process.exit(1);
|
|
113071
|
+
}
|
|
113072
|
+
async function runAdExtensionUpdate(opts) {
|
|
113073
|
+
const config = loadConfig(opts.token);
|
|
113074
|
+
const googleApiUrl = requireGoogleApi(config);
|
|
113075
|
+
let raw;
|
|
113076
|
+
try {
|
|
113077
|
+
raw = stripMetaKeys(JSON.parse(readFileSync7(opts.configFile, "utf8")));
|
|
113078
|
+
} catch {
|
|
113079
|
+
console.error(`
|
|
113080
|
+
\u274C \u8BFB\u53D6\u914D\u7F6E\u6587\u4EF6\u5931\u8D25\uFF08${opts.configFile}\uFF09
|
|
113081
|
+
`);
|
|
113082
|
+
process.exit(1);
|
|
113083
|
+
}
|
|
113084
|
+
const kind = detectExtensionUpdateKind(raw);
|
|
113085
|
+
let body;
|
|
113086
|
+
let commandLabel;
|
|
113087
|
+
if (kind === "whatsapp") {
|
|
113088
|
+
const cfg = loadWhatsappExtensionConfig(opts.configFile);
|
|
113089
|
+
if (opts.account !== cfg.account) {
|
|
113090
|
+
console.error("\n\u274C --account \u4E0E\u914D\u7F6E\u6587\u4EF6 account \u4E0D\u4E00\u81F4\n");
|
|
113091
|
+
process.exit(1);
|
|
113092
|
+
}
|
|
113093
|
+
body = buildBusinessMessageExtensionBody(cfg, opts.id);
|
|
113094
|
+
commandLabel = "ad extension update (whatsapp)";
|
|
113095
|
+
} else {
|
|
113096
|
+
const cfg = loadLeadFormExtensionConfig(opts.configFile);
|
|
113097
|
+
if (opts.account !== cfg.account) {
|
|
113098
|
+
console.error("\n\u274C --account \u4E0E\u914D\u7F6E\u6587\u4EF6 account \u4E0D\u4E00\u81F4\n");
|
|
113099
|
+
process.exit(1);
|
|
113100
|
+
}
|
|
113101
|
+
body = buildLeadFormExtensionBody(cfg, opts.id);
|
|
113102
|
+
commandLabel = "ad extension update (lead-form)";
|
|
113103
|
+
}
|
|
112917
113104
|
const url = `${googleApiUrl}/extensionmanagement/extension/${opts.account}/${opts.id}`;
|
|
112918
113105
|
let response;
|
|
112919
113106
|
try {
|
|
@@ -112931,16 +113118,24 @@ async function runAdExtensionUpdate(opts) {
|
|
|
112931
113118
|
}
|
|
112932
113119
|
if (await emitCliJsonOrSnapshot(opts, {
|
|
112933
113120
|
section: `ad-extension-update-${opts.account}`,
|
|
112934
|
-
commandLabel
|
|
113121
|
+
commandLabel,
|
|
112935
113122
|
commandHint: `${opts.account}:${opts.id}`,
|
|
112936
113123
|
payload: response,
|
|
112937
113124
|
idSuffix: opts.id
|
|
112938
113125
|
})) {
|
|
112939
113126
|
return;
|
|
112940
113127
|
}
|
|
112941
|
-
|
|
113128
|
+
const resObj = response ?? {};
|
|
113129
|
+
const newId = resObj["id"];
|
|
113130
|
+
if (kind === "whatsapp" && newId && newId !== opts.id) {
|
|
113131
|
+
console.log(`
|
|
113132
|
+
\u2705 WhatsApp \u5DF2\u66F4\u65B0\uFF08\u65B0 asset id: ${newId}\uFF0C\u65E7 id ${opts.id} \u5DF2\u5931\u6548\uFF09
|
|
113133
|
+
`);
|
|
113134
|
+
} else {
|
|
113135
|
+
console.log(`
|
|
112942
113136
|
\u2705 \u9644\u52A0\u4FE1\u606F ${opts.id} \u5DF2\u66F4\u65B0
|
|
112943
113137
|
`);
|
|
113138
|
+
}
|
|
112944
113139
|
}
|
|
112945
113140
|
|
|
112946
113141
|
// src/commands/ad/search-terms.ts
|
|
@@ -114890,7 +115085,7 @@ async function runPmaxImageValidation(configFile, cfg) {
|
|
|
114890
115085
|
// src/commands/ad/pmax-image-resolve.ts
|
|
114891
115086
|
init_auth();
|
|
114892
115087
|
import { basename as basename4, dirname as dirname8, isAbsolute as isAbsolute3, resolve as resolve7 } from "path";
|
|
114893
|
-
import { readFileSync as
|
|
115088
|
+
import { readFileSync as readFileSync8 } from "fs";
|
|
114894
115089
|
|
|
114895
115090
|
// src/commands/ad/pmax-urls.ts
|
|
114896
115091
|
function pmaxChannelTypesUrl(googleApiUrl) {
|
|
@@ -115047,7 +115242,7 @@ async function uploadPmaxImageFileBase64(config, googleApiUrl, accountId, absIma
|
|
|
115047
115242
|
async function uploadPmaxImageFile(config, googleApiUrl, accountId, absImagePath, name2, verbose) {
|
|
115048
115243
|
const fileName = basename4(absImagePath);
|
|
115049
115244
|
const assetName = (name2 ?? fileName).trim() || fileName;
|
|
115050
|
-
const fileBuffer =
|
|
115245
|
+
const fileBuffer = readFileSync8(absImagePath);
|
|
115051
115246
|
if (fileBuffer.length <= MAX_BASE64_UPLOAD_BYTES) {
|
|
115052
115247
|
return uploadPmaxImageFileBase64(
|
|
115053
115248
|
config,
|
|
@@ -115133,7 +115328,7 @@ function assertPmaxImageSlotsResolved(slots) {
|
|
|
115133
115328
|
}
|
|
115134
115329
|
|
|
115135
115330
|
// src/commands/ad/pmax-load.ts
|
|
115136
|
-
import { readFileSync as
|
|
115331
|
+
import { readFileSync as readFileSync9 } from "fs";
|
|
115137
115332
|
function loadPmaxCreateConfig(configFile) {
|
|
115138
115333
|
const cfg = tryLoadPmaxCreateConfig(configFile);
|
|
115139
115334
|
if (!cfg) {
|
|
@@ -115204,7 +115399,7 @@ function detectDeprecatedPmaxVideoKeys(raw) {
|
|
|
115204
115399
|
function tryLoadPmaxCreateConfig(configFile) {
|
|
115205
115400
|
let raw;
|
|
115206
115401
|
try {
|
|
115207
|
-
raw = JSON.parse(
|
|
115402
|
+
raw = JSON.parse(readFileSync9(configFile, "utf8"));
|
|
115208
115403
|
} catch {
|
|
115209
115404
|
return null;
|
|
115210
115405
|
}
|
|
@@ -115212,7 +115407,7 @@ function tryLoadPmaxCreateConfig(configFile) {
|
|
|
115212
115407
|
}
|
|
115213
115408
|
function loadPmaxCreateConfigRaw(configFile) {
|
|
115214
115409
|
try {
|
|
115215
|
-
const raw = JSON.parse(
|
|
115410
|
+
const raw = JSON.parse(readFileSync9(configFile, "utf8"));
|
|
115216
115411
|
return stripMetaKeys(raw);
|
|
115217
115412
|
} catch {
|
|
115218
115413
|
return null;
|
|
@@ -115260,19 +115455,19 @@ function buildPmaxCreateUrl(googleApiUrl, accountId) {
|
|
|
115260
115455
|
}
|
|
115261
115456
|
|
|
115262
115457
|
// src/commands/ad/pmax-video-upload.ts
|
|
115263
|
-
import { existsSync as existsSync3, readFileSync as
|
|
115458
|
+
import { existsSync as existsSync3, readFileSync as readFileSync11 } from "fs";
|
|
115264
115459
|
import { basename as basename5, dirname as dirname10, isAbsolute as isAbsolute5, resolve as resolve9 } from "path";
|
|
115265
115460
|
|
|
115266
115461
|
// src/commands/ad/pmax-shared.ts
|
|
115267
115462
|
init_auth();
|
|
115268
115463
|
init_cli_json_snapshot();
|
|
115269
|
-
import { readFileSync as
|
|
115464
|
+
import { readFileSync as readFileSync10 } from "fs";
|
|
115270
115465
|
import { dirname as dirname9, isAbsolute as isAbsolute4, resolve as resolve8 } from "path";
|
|
115271
115466
|
var PMAX_MONEY_KEYS = /* @__PURE__ */ new Set(["budget", "targetCpa_BidingAmount"]);
|
|
115272
115467
|
function loadPmaxJsonFile(configFile) {
|
|
115273
115468
|
let raw;
|
|
115274
115469
|
try {
|
|
115275
|
-
raw = JSON.parse(
|
|
115470
|
+
raw = JSON.parse(readFileSync10(configFile, "utf8"));
|
|
115276
115471
|
} catch (e) {
|
|
115277
115472
|
const msg = e instanceof Error ? e.message : String(e);
|
|
115278
115473
|
console.error(`
|
|
@@ -115459,7 +115654,7 @@ function runPmaxVideoValidation(configFile, cfg) {
|
|
|
115459
115654
|
return { errors, warnings };
|
|
115460
115655
|
}
|
|
115461
115656
|
try {
|
|
115462
|
-
const buf =
|
|
115657
|
+
const buf = readFileSync11(abs);
|
|
115463
115658
|
if (buf.length === 0) errors.push(`videoPath \u4E3A\u7A7A\u6587\u4EF6\uFF1A${abs}`);
|
|
115464
115659
|
} catch (e) {
|
|
115465
115660
|
const msg = e instanceof Error ? e.message : String(e);
|
|
@@ -115522,7 +115717,7 @@ async function uploadPmaxLocalVideo(opts) {
|
|
|
115522
115717
|
const fileName = basename5(opts.absVideoPath);
|
|
115523
115718
|
const title = (opts.title ?? fileName).trim() || fileName;
|
|
115524
115719
|
const description = (opts.description ?? "Uploaded via siluzan-tso PMax video upload").trim() || "Uploaded via siluzan-tso";
|
|
115525
|
-
const fileBuffer =
|
|
115720
|
+
const fileBuffer = readFileSync11(opts.absVideoPath);
|
|
115526
115721
|
const form = new FormData();
|
|
115527
115722
|
form.append("media_account_id", opts.accountId.replace(/-/g, ""));
|
|
115528
115723
|
form.append("title", title);
|
|
@@ -115578,7 +115773,7 @@ function validatePmaxVideoPathQuick(absPath) {
|
|
|
115578
115773
|
return `\u89C6\u9891\u6587\u4EF6\u4E0D\u5B58\u5728\uFF1A${absPath}`;
|
|
115579
115774
|
}
|
|
115580
115775
|
try {
|
|
115581
|
-
if (
|
|
115776
|
+
if (readFileSync11(absPath).length === 0) return `\u89C6\u9891\u6587\u4EF6\u4E3A\u7A7A\uFF1A${absPath}`;
|
|
115582
115777
|
} catch (e) {
|
|
115583
115778
|
const msg = e instanceof Error ? e.message : String(e);
|
|
115584
115779
|
return `\u65E0\u6CD5\u8BFB\u53D6\u89C6\u9891\u6587\u4EF6\uFF08${absPath}\uFF09\uFF1A${msg}`;
|
|
@@ -115677,9 +115872,17 @@ function validatePmaxCampaignExtensions(ext) {
|
|
|
115677
115872
|
const lfErrors = validateLeadFormPayload(ext.leadForm, "campaignExtensions.leadForm");
|
|
115678
115873
|
errors.push(...lfErrors);
|
|
115679
115874
|
}
|
|
115680
|
-
if (
|
|
115875
|
+
if (ext.businessMessage) {
|
|
115876
|
+
errors.push(
|
|
115877
|
+
...validateBusinessMessagePayload(ext.businessMessage, "campaignExtensions.businessMessage")
|
|
115878
|
+
);
|
|
115879
|
+
warnings.push(
|
|
115880
|
+
"WhatsApp\uFF08BUSINESS_MESSAGE\uFF09\u9700 Google API \u767D\u540D\u5355\uFF1B\u672A\u5F00\u901A\u4F1A\u8FD4\u56DE CUSTOMER_NOT_ON_ALLOWLIST_FOR_MESSAGE_ASSETS"
|
|
115881
|
+
);
|
|
115882
|
+
}
|
|
115883
|
+
if (callouts.length === 0 && snippets.length === 0 && !ext.leadForm && !ext.businessMessage) {
|
|
115681
115884
|
warnings.push(
|
|
115682
|
-
"campaignExtensions \u5DF2\u58F0\u660E\u4F46 callouts / structuredSnippets / leadForm \u5747\u4E3A\u7A7A\uFF0C\u521B\u5EFA\u65F6\u5C06\u8DF3\u8FC7\u9644\u52A0\u8D44\u4EA7"
|
|
115885
|
+
"campaignExtensions \u5DF2\u58F0\u660E\u4F46 callouts / structuredSnippets / leadForm / businessMessage \u5747\u4E3A\u7A7A\uFF0C\u521B\u5EFA\u65F6\u5C06\u8DF3\u8FC7\u9644\u52A0\u8D44\u4EA7"
|
|
115683
115886
|
);
|
|
115684
115887
|
}
|
|
115685
115888
|
return { errors, warnings };
|
|
@@ -115690,7 +115893,7 @@ function hasExtensionsToAttach(ext) {
|
|
|
115690
115893
|
const hasSnippets = (ext.structuredSnippets ?? []).some(
|
|
115691
115894
|
(s) => s?.header?.trim() && (s.values ?? []).some((v) => v?.trim())
|
|
115692
115895
|
);
|
|
115693
|
-
return hasCallouts || hasSnippets || Boolean(ext.leadForm);
|
|
115896
|
+
return hasCallouts || hasSnippets || Boolean(ext.leadForm) || Boolean(ext.businessMessage);
|
|
115694
115897
|
}
|
|
115695
115898
|
async function postExtension(config, googleApiUrl, accountId, body, verbose) {
|
|
115696
115899
|
const url = `${googleApiUrl}/extensionmanagement/extension/${accountId}`;
|
|
@@ -115817,6 +116020,32 @@ async function attachPmaxCampaignExtensions(opts) {
|
|
|
115817
116020
|
result.leadForm = { ok: false, error: posted.error, label: headline };
|
|
115818
116021
|
}
|
|
115819
116022
|
}
|
|
116023
|
+
if (ext.businessMessage) {
|
|
116024
|
+
const label = ext.businessMessage.starterMessage?.trim().slice(0, 40) || "WhatsApp";
|
|
116025
|
+
if (opts.logProgress) console.log(`
|
|
116026
|
+
\u6302\u8F7D WhatsApp \u79C1\u4FE1\uFF1A${label}`);
|
|
116027
|
+
const posted = await postExtension(
|
|
116028
|
+
opts.config,
|
|
116029
|
+
opts.googleApiUrl,
|
|
116030
|
+
opts.accountId,
|
|
116031
|
+
buildBusinessMessageBodyForCampaign(
|
|
116032
|
+
opts.accountId,
|
|
116033
|
+
opts.campaignId,
|
|
116034
|
+
ext.businessMessage
|
|
116035
|
+
),
|
|
116036
|
+
opts.verbose
|
|
116037
|
+
);
|
|
116038
|
+
if (posted.ok) {
|
|
116039
|
+
result.whatsapp = {
|
|
116040
|
+
ok: true,
|
|
116041
|
+
id: String(posted.data["id"] ?? ""),
|
|
116042
|
+
label
|
|
116043
|
+
};
|
|
116044
|
+
} else {
|
|
116045
|
+
result.allOk = false;
|
|
116046
|
+
result.whatsapp = { ok: false, error: posted.error, label };
|
|
116047
|
+
}
|
|
116048
|
+
}
|
|
115820
116049
|
return result;
|
|
115821
116050
|
}
|
|
115822
116051
|
function formatPmaxExtensionsAttachErrors(result) {
|
|
@@ -115830,6 +116059,9 @@ function formatPmaxExtensionsAttachErrors(result) {
|
|
|
115830
116059
|
if (result.leadForm && !result.leadForm.ok) {
|
|
115831
116060
|
msgs.push(`\u6F5C\u5728\u5BA2\u6237\u8868\u5355\u300C${result.leadForm.label}\u300D\u5931\u8D25\uFF1A${result.leadForm.error}`);
|
|
115832
116061
|
}
|
|
116062
|
+
if (result.whatsapp && !result.whatsapp.ok) {
|
|
116063
|
+
msgs.push(`WhatsApp\u300C${result.whatsapp.label}\u300D\u5931\u8D25\uFF1A${result.whatsapp.error}`);
|
|
116064
|
+
}
|
|
115833
116065
|
return msgs;
|
|
115834
116066
|
}
|
|
115835
116067
|
|
|
@@ -115993,7 +116225,7 @@ async function runAdPmaxCreate(opts) {
|
|
|
115993
116225
|
` \u53EF\u624B\u52A8\u8865\u6302\uFF1Asiluzan-tso ad extension callout|snippet|lead-form -a ${accountId} --campaign-id ${campaignId} \u2026
|
|
115994
116226
|
`
|
|
115995
116227
|
);
|
|
115996
|
-
} else if (extensionsResult?.allOk && extensionsResult.callouts.length + extensionsResult.structuredSnippets.length + (extensionsResult.leadForm ? 1 : 0) > 0) {
|
|
116228
|
+
} else if (extensionsResult?.allOk && extensionsResult.callouts.length + extensionsResult.structuredSnippets.length + (extensionsResult.leadForm ? 1 : 0) + (extensionsResult.whatsapp ? 1 : 0) > 0) {
|
|
115997
116229
|
if (!opts.jsonOut) {
|
|
115998
116230
|
console.log("\n\u2705 Campaign \u9644\u52A0\u8D44\u4EA7\u5DF2\u6302\u8F7D");
|
|
115999
116231
|
}
|
|
@@ -116895,7 +117127,7 @@ async function runAdCampaignValidate(opts) {
|
|
|
116895
117127
|
}
|
|
116896
117128
|
|
|
116897
117129
|
// src/commands/ad/pmax-image-convert.ts
|
|
116898
|
-
import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync5, readFileSync as
|
|
117130
|
+
import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync5, readFileSync as readFileSync12, existsSync as existsSync4 } from "fs";
|
|
116899
117131
|
import { resolve as resolve10, dirname as dirname11, basename as basename7, extname } from "path";
|
|
116900
117132
|
var SPECS = [
|
|
116901
117133
|
{ kind: "marketing", width: 1200, height: 628, fit: "cover", suffix: "_marketing" },
|
|
@@ -116986,7 +117218,7 @@ async function runAdPmaxImageConvert(opts) {
|
|
|
116986
117218
|
if (opts.updateConfig) {
|
|
116987
117219
|
const configPath = resolve10(opts.updateConfig);
|
|
116988
117220
|
try {
|
|
116989
|
-
const raw = JSON.parse(
|
|
117221
|
+
const raw = JSON.parse(readFileSync12(configPath, "utf8"));
|
|
116990
117222
|
const imgPaths = raw["imagePaths"] ?? {};
|
|
116991
117223
|
if (outputPaths.marketing) imgPaths["marketing"] = outputPaths.marketing;
|
|
116992
117224
|
if (outputPaths.square) imgPaths["square"] = outputPaths.square;
|
|
@@ -117834,7 +118066,7 @@ function register20(program2) {
|
|
|
117834
118066
|
);
|
|
117835
118067
|
extensionCmd.command("list").description("\u67E5\u8BE2\u9644\u52A0\u4FE1\u606F\u5217\u8868").requiredOption("-a, --account <id>", "Google \u8D26\u6237 mediaCustomerId").option(
|
|
117836
118068
|
"--type <type>",
|
|
117837
|
-
"\u6309\u7C7B\u578B\u7B5B\u9009\uFF1ASITELINK | CALL | CALLOUT | STRUCTURED_SNIPPET | LEAD_FORM"
|
|
118069
|
+
"\u6309\u7C7B\u578B\u7B5B\u9009\uFF1ASITELINK | CALL | CALLOUT | STRUCTURED_SNIPPET | LEAD_FORM | BUSINESS_MESSAGE"
|
|
117838
118070
|
).option("--campaign-id <id>", "\u6309\u5E7F\u544A\u7CFB\u5217 ID \u7B5B\u9009\uFF08PMax Campaign \u7EA7\u6269\u5C55\uFF09").option("-t, --token <token>", "Auth Token").option(
|
|
117839
118071
|
"--json-out <path>",
|
|
117840
118072
|
"\u843D\u76D8\uFF08\u76EE\u5F55\u6216 *.json \u6587\u4EF6\u8DEF\u5F84\uFF09\u5E76\u66F4\u65B0 cli-manifest[-<\u67E5\u8BE2id>].json\uFF1B\u76EE\u5F55\u6A21\u5F0F\u6587\u4EF6\u540D\u4E3A `<section>[-<\u67E5\u8BE2id>].json`\uFF1Bstdout \u4E00\u884C\u6458\u8981 JSON\uFF0C\u542B outlineFile\uFF08TS \u5F0F\u7C7B\u578B\u5728 `*.outline.txt`\uFF09",
|
|
@@ -117869,7 +118101,27 @@ function register20(program2) {
|
|
|
117869
118101
|
});
|
|
117870
118102
|
}
|
|
117871
118103
|
);
|
|
117872
|
-
extensionCmd.command("
|
|
118104
|
+
extensionCmd.command("whatsapp").description(
|
|
118105
|
+
"\u4E3A PMax \u6D3B\u52A8\u6DFB\u52A0 WhatsApp \u79C1\u4FE1\uFF08BUSINESS_MESSAGE\uFF1B\u987B --config-file\uFF1B\u9700 Google API \u767D\u540D\u5355\uFF09\n \u6A21\u677F\uFF1Aassets/pmax-whatsapp-template.json"
|
|
118106
|
+
).requiredOption("-a, --account <id>", "Google \u8D26\u6237 mediaCustomerId").requiredOption("--config-file <path>", "WhatsApp JSON \u914D\u7F6E\uFF08\u89C1 pmax-whatsapp-template.json\uFF09").option("--campaign-id <id>", "\u8986\u76D6\u914D\u7F6E\u6587\u4EF6\u4E2D\u7684 campaignId").option("-t, --token <token>", "Auth Token").option(
|
|
118107
|
+
"--json-out <path>",
|
|
118108
|
+
"\u843D\u76D8\uFF08\u76EE\u5F55\u6216 *.json \u6587\u4EF6\u8DEF\u5F84\uFF09\u5E76\u66F4\u65B0 cli-manifest[-<\u67E5\u8BE2id>].json",
|
|
118109
|
+
void 0
|
|
118110
|
+
).option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(
|
|
118111
|
+
async (opts) => {
|
|
118112
|
+
await runAdExtensionWhatsappCreate({
|
|
118113
|
+
token: opts.token,
|
|
118114
|
+
account: opts.account,
|
|
118115
|
+
campaignId: opts.campaignId,
|
|
118116
|
+
configFile: opts.configFile,
|
|
118117
|
+
jsonOut: opts.jsonOut,
|
|
118118
|
+
verbose: opts.verbose
|
|
118119
|
+
});
|
|
118120
|
+
}
|
|
118121
|
+
);
|
|
118122
|
+
extensionCmd.command("update").description(
|
|
118123
|
+
"\u66F4\u65B0\u9644\u52A0\u4FE1\u606F\uFF08LEAD_FORM \u6216 WhatsApp\uFF1B\u914D\u7F6E\u6587\u4EF6\u542B leadForm / businessMessage\uFF1BWhatsApp PUT \u540E id \u4F1A\u53D8\uFF09"
|
|
118124
|
+
).requiredOption("-a, --account <id>", "Google \u8D26\u6237 mediaCustomerId").requiredOption("--id <extensionId>", "\u9644\u52A0\u4FE1\u606F ID\uFF08\u5148 ad extension list \u67E5\u8BE2\uFF09").requiredOption("--config-file <path>", "Lead Form JSON \u914D\u7F6E").option("-t, --token <token>", "Auth Token").option(
|
|
117873
118125
|
"--json-out <path>",
|
|
117874
118126
|
"\u843D\u76D8\uFF08\u76EE\u5F55\u6216 *.json \u6587\u4EF6\u8DEF\u5F84\uFF09\u5E76\u66F4\u65B0 cli-manifest[-<\u67E5\u8BE2id>].json",
|
|
117875
118127
|
void 0
|
package/dist/skill/_meta.json
CHANGED
|
@@ -61,6 +61,15 @@
|
|
|
61
61
|
{ "inputType": "EMAIL" },
|
|
62
62
|
{ "inputType": "PHONE_NUMBER" }
|
|
63
63
|
]
|
|
64
|
+
},
|
|
65
|
+
"businessMessage": {
|
|
66
|
+
"starterMessage": "Hi! How can we help you today?",
|
|
67
|
+
"callToActionSelection": "CONTACT_US",
|
|
68
|
+
"callToActionDescription": "Message us on WhatsApp",
|
|
69
|
+
"whatsapp": {
|
|
70
|
+
"countryCode": "US",
|
|
71
|
+
"phoneNumber": "2125550100"
|
|
72
|
+
}
|
|
64
73
|
}
|
|
65
74
|
}
|
|
66
75
|
}
|
|
@@ -81,8 +81,9 @@ siluzan-tso ad campaigns -a <accountId> --json-out ./snap
|
|
|
81
81
|
| `callouts` | string[] | 宣传信息(CALLOUT),每条 ≤25 字符,各创建 1 个扩展 |
|
|
82
82
|
| `structuredSnippets` | object[] | `{ header, values }`;`values` 至少 3 项 |
|
|
83
83
|
| `leadForm` | object | 潜在客户表单;字段同 `pmax-lead-form-template.json` 的 `leadForm` |
|
|
84
|
+
| `businessMessage` | object | WhatsApp 私信;字段同 `pmax-whatsapp-template.json` 的 `businessMessage` |
|
|
84
85
|
|
|
85
|
-
标头可选值:`ad extension snippet-headers`。WhatsApp
|
|
86
|
+
标头可选值:`ad extension snippet-headers`。WhatsApp 需 Google API 白名单(见 `pmax-whatsapp-template.md`)。
|
|
86
87
|
|
|
87
88
|
若挂载失败,活动仍已创建;`--json-out` 的 `campaignExtensions` 段含各条 `ok` / `error`,可手动 `ad extension *` 补挂。
|
|
88
89
|
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"勿在 pmax-create 流程内创建;先 pmax-create 再 ad extension lead-form",
|
|
9
9
|
"每个 PMax 活动通常仅允许 1 个 Lead Form;创建前用 ad extension list --type LEAD_FORM --campaign-id 检查",
|
|
10
10
|
"账户须在 Google Ads UI 接受 Lead Form ToS",
|
|
11
|
-
"WhatsApp
|
|
11
|
+
"WhatsApp 见 pmax-whatsapp-template.json 或 campaignExtensions.businessMessage"
|
|
12
12
|
]
|
|
13
13
|
},
|
|
14
14
|
"account": "REPLACE_mediaCustomerId",
|
|
@@ -54,7 +54,7 @@ siluzan-tso ad extension update -a <accountId> --id <extensionId> --config-file
|
|
|
54
54
|
| 潜在客户表单 | `LEAD_FORM` | `ad extension lead-form` |
|
|
55
55
|
| 附加链接 | `SITELINK` | `ad extension sitelink` |
|
|
56
56
|
| 附加电话 | `CALL` | `ad extension call` |
|
|
57
|
-
| 私信 WhatsApp |
|
|
57
|
+
| 私信 WhatsApp | `BUSINESS_MESSAGE` | `ad extension whatsapp` 或 `campaignExtensions.businessMessage` |
|
|
58
58
|
|
|
59
59
|
**层级**:PMax 仅 `Account` / `Campaign`;`Ad Group` 会 **400**。
|
|
60
60
|
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_meta": {
|
|
3
|
+
"schema": "pmax-whatsapp/v1",
|
|
4
|
+
"doc": "pmax-whatsapp-template.md",
|
|
5
|
+
"note": "以 _ 开头的键仅作说明,CLI 提交前会剥离",
|
|
6
|
+
"agentPitfalls": [
|
|
7
|
+
"BUSINESS_MESSAGE 仅支持 Campaign / Account 级;PMax 用 Campaign + campaignId",
|
|
8
|
+
"每 Campaign 仅 1 个 ENABLED WhatsApp;重复 POST 会 400,须 DELETE 或 PUT 更新",
|
|
9
|
+
"PUT 成功后 asset id 会变化,后续删除/更新须用响应中的新 id",
|
|
10
|
+
"账户须已开通 Google Business Message API 白名单,否则 CUSTOMER_NOT_ON_ALLOWLIST_FOR_MESSAGE_ASSETS",
|
|
11
|
+
"勿与 pmax-create 同时挂 businessMessage 若活动上已有 WhatsApp"
|
|
12
|
+
]
|
|
13
|
+
},
|
|
14
|
+
"account": "REPLACE_mediaCustomerId",
|
|
15
|
+
"campaignId": "REPLACE_pmaxCampaignId",
|
|
16
|
+
"businessMessage": {
|
|
17
|
+
"messageProvider": "WHATSAPP",
|
|
18
|
+
"starterMessage": "Hi! How can we help you today?",
|
|
19
|
+
"callToActionSelection": "CONTACT_US",
|
|
20
|
+
"callToActionDescription": "Message us on WhatsApp",
|
|
21
|
+
"whatsapp": {
|
|
22
|
+
"countryCode": "US",
|
|
23
|
+
"phoneNumber": "2125550100"
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# `ad extension whatsapp` JSON 配置说明
|
|
2
|
+
|
|
3
|
+
为**已创建的 PMax 活动**挂载 WhatsApp 私信(`BUSINESS_MESSAGE`)。也可写入 `pmax-create` JSON 的 `campaignExtensions.businessMessage` 由 CLI 自动编排。
|
|
4
|
+
|
|
5
|
+
模板:`pmax-whatsapp-template.json`。
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 命令
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
# 单独挂载
|
|
13
|
+
siluzan-tso ad extension whatsapp -a <accountId> --config-file ./whatsapp.json --json-out ./snap
|
|
14
|
+
|
|
15
|
+
# 更新(PUT 后 id 会变,用响应新 id)
|
|
16
|
+
siluzan-tso ad extension update -a <accountId> --id <extensionId> --config-file ./whatsapp.json
|
|
17
|
+
|
|
18
|
+
# 或在 pmax-create 一并挂载
|
|
19
|
+
# campaignExtensions.businessMessage 见 pmax-create-template.md
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## 字段
|
|
25
|
+
|
|
26
|
+
| 字段 | 必填 | 说明 |
|
|
27
|
+
|------|:----:|------|
|
|
28
|
+
| `account` | ✅ | Google 媒体客户 ID |
|
|
29
|
+
| `campaignId` | ✅ | PMax 活动 ID |
|
|
30
|
+
| `businessMessage.starterMessage` | ✅ | 欢迎语 |
|
|
31
|
+
| `businessMessage.callToActionDescription` | | 默认 `Message us on WhatsApp` |
|
|
32
|
+
| `businessMessage.callToActionSelection` | | 默认 `CONTACT_US`;可选 `LEARN_MORE`、`GET_QUOTE` 等 |
|
|
33
|
+
| `businessMessage.messageProvider` | | 默认 `WHATSAPP` |
|
|
34
|
+
| `businessMessage.whatsapp.countryCode` | ✅ | 两位国家码(如 `US`) |
|
|
35
|
+
| `businessMessage.whatsapp.phoneNumber` | ✅ | WhatsApp Business 注册号码 |
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## 前置条件
|
|
40
|
+
|
|
41
|
+
1. **Google API 白名单**(阻塞项):未开通返回 `CUSTOMER_NOT_ON_ALLOWLIST_FOR_MESSAGE_ASSETS`;须 MCC 运营联系 Google AM 申请。
|
|
42
|
+
2. 号码已在 **WhatsApp Business** 注册。
|
|
43
|
+
3. 同 Campaign **仅 1 个** ENABLED WhatsApp;已有则 POST 400,须 DELETE 或 PUT。
|
|
44
|
+
|
|
45
|
+
详见 Sammamish `pmax-frontend-api.md` §3.2.4。
|
|
@@ -68,8 +68,9 @@
|
|
|
68
68
|
| 命令 | 选项 | 合法值 |
|
|
69
69
|
| ---- | ---- | ------ |
|
|
70
70
|
| ad extension sitelink, callout, snippet, … | --level | Account \| Campaign \| AdGroup |
|
|
71
|
-
| ad extension list | --type | SITELINK \| CALL \| CALLOUT \| STRUCTURED_SNIPPET \| LEAD_FORM |
|
|
71
|
+
| ad extension list | --type | SITELINK \| CALL \| CALLOUT \| STRUCTURED_SNIPPET \| LEAD_FORM \| BUSINESS_MESSAGE |
|
|
72
72
|
| ad extension lead-form / update | --config-file | JSON(见 `assets/pmax-lead-form-template.json`) |
|
|
73
|
+
| ad extension whatsapp / update | --config-file | JSON(见 `assets/pmax-whatsapp-template.json`) |
|
|
73
74
|
|
|
74
75
|
|
|
75
76
|
### 搜索系列 `BiddingStrategyTypeV2`
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
| 改资产 | `ad pmax-assets-update --config-file …` |
|
|
65
65
|
| YouTube 追加 | `ad pmax-youtube-link`(单条 `--youtube` / `--video-path`);批量见 `ad pmax-assets-update` |
|
|
66
66
|
| 信号 | `ad pmax-signals-get` / `ad pmax-signals-set`;受众下拉 `ad pmax-audiences` |
|
|
67
|
-
| 附加资产 | `ad extension pmax-types`;`callout` / `snippet` / `lead-form`(见 `pmax-api.md` § 附加资产) |
|
|
67
|
+
| 附加资产 | `ad extension pmax-types`;`callout` / `snippet` / `lead-form` / `whatsapp`(见 `pmax-api.md` § 附加资产) |
|
|
68
68
|
| 图片库 | `ad pmax-image-upload` |
|
|
69
69
|
| 报表 | `ad pmax-report-asset-groups` / `ad pmax-report-geo` |
|
|
70
70
|
| 删活动 | `ad campaign-delete -a <id> --id <campaignId>`(与 Search 共用;勿用 `ad campaign-edit`) |
|
|
@@ -513,7 +513,7 @@ siluzan-tso ad keyword-negative-edit \
|
|
|
513
513
|
|
|
514
514
|
## ad extension — 附加信息管理
|
|
515
515
|
|
|
516
|
-
Callout / Snippet / Sitelink / Call 等类型修改可**先删后建**;**Lead Form** 支持 `ad extension update`(PUT
|
|
516
|
+
Callout / Snippet / Sitelink / Call 等类型修改可**先删后建**;**Lead Form / WhatsApp** 支持 `ad extension update`(PUT;WhatsApp 成功后 **id 会变**)。所有 `extension <type>` 子命令均支持 `--json-out`,输出网关返回的扩展对象(含 `id`),批量脚本可据此回填。
|
|
517
517
|
|
|
518
518
|
```bash
|
|
519
519
|
# PMax 支持的类型与层级(含 LEAD_FORM)
|
|
@@ -524,7 +524,7 @@ siluzan-tso ad extension snippet-headers [--json-out ./snap]
|
|
|
524
524
|
|
|
525
525
|
# 查询
|
|
526
526
|
siluzan-tso ad extension list -a <accountId> \
|
|
527
|
-
[--type SITELINK|CALL|CALLOUT|STRUCTURED_SNIPPET|LEAD_FORM] \
|
|
527
|
+
[--type SITELINK|CALL|CALLOUT|STRUCTURED_SNIPPET|LEAD_FORM|BUSINESS_MESSAGE] \
|
|
528
528
|
[--campaign-id <campaignId>] [--json-out ./snap]
|
|
529
529
|
|
|
530
530
|
# 附加链接
|
|
@@ -544,14 +544,17 @@ siluzan-tso ad extension snippet -a <accountId> --header "Brands" --values "A,B,
|
|
|
544
544
|
# PMax 潜在客户表单(仅 Campaign 级;模板 assets/pmax-lead-form-template.json)
|
|
545
545
|
siluzan-tso ad extension lead-form -a <accountId> --config-file ./lead-form.json [--json-out ./snap]
|
|
546
546
|
|
|
547
|
-
#
|
|
547
|
+
# PMax WhatsApp 私信(BUSINESS_MESSAGE;需 Google API 白名单;模板 assets/pmax-whatsapp-template.json)
|
|
548
|
+
siluzan-tso ad extension whatsapp -a <accountId> --config-file ./whatsapp.json [--json-out ./snap]
|
|
549
|
+
|
|
550
|
+
# 更新 Lead Form 或 WhatsApp(配置文件含 leadForm / businessMessage;WhatsApp PUT 后 id 会变)
|
|
548
551
|
siluzan-tso ad extension update -a <accountId> --id <extensionId> --config-file ./lead-form.json
|
|
549
552
|
|
|
550
553
|
# 删除
|
|
551
554
|
siluzan-tso ad extension delete -a <accountId> --id <extensionId>
|
|
552
555
|
```
|
|
553
556
|
|
|
554
|
-
**PMax 约束**:仅 `Account` / `Campaign` 层级;`Ad Group` 会 400。`LEAD_FORM`
|
|
557
|
+
**PMax 约束**:仅 `Account` / `Campaign` 层级;`Ad Group` 会 400。`LEAD_FORM` 仅 Campaign。WhatsApp 每 Campaign 仅 1 个 ENABLED,须 Google API 白名单。
|
|
555
558
|
|
|
556
559
|
`--header` 常用值:`Brands`/`Services`/`Amenities`/`Types`/`Styles`/`Courses`/`Models` 等(完整列表:`ad extension snippet-headers`)。
|
|
557
560
|
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
| `ad pmax-report-asset-groups` / `ad pmax-report-geo` | 报表 |
|
|
31
31
|
| `ad campaigns` | 列表(筛 `PERFORMANCE_MAX`) |
|
|
32
32
|
| `ad extension pmax-types` | PMax 支持的附加资产类型与层级 |
|
|
33
|
-
| `ad extension callout` / `snippet` / `lead-form` | 宣传信息 / 结构化摘要 / 潜在客户表单 |
|
|
33
|
+
| `ad extension callout` / `snippet` / `lead-form` / `whatsapp` | 宣传信息 / 结构化摘要 / 潜在客户表单 / WhatsApp |
|
|
34
34
|
| `ad extension list` | 查询已挂载扩展(`--type` / `--campaign-id`) |
|
|
35
35
|
|
|
36
36
|
模板:`assets/pmax-create-template.json`、`pmax-asset-group-template.json`、`pmax-assets-update-template.json`、`pmax-signals-template.json`、`pmax-patch-campaign-template.json`、`pmax-lead-form-template.json`。
|
|
@@ -46,7 +46,8 @@
|
|
|
46
46
|
"campaignExtensions": {
|
|
47
47
|
"callouts": ["Free shipping"],
|
|
48
48
|
"structuredSnippets": [{ "header": "Services", "values": ["A", "B", "C"] }],
|
|
49
|
-
"leadForm": { "businessName": "...", "headline": "...", "description": "...", "privacyPolicyUrl": "...", "finalUrl": "...", "fields": [{ "inputType": "EMAIL" }] }
|
|
49
|
+
"leadForm": { "businessName": "...", "headline": "...", "description": "...", "privacyPolicyUrl": "...", "finalUrl": "...", "fields": [{ "inputType": "EMAIL" }] },
|
|
50
|
+
"businessMessage": { "starterMessage": "Hi!", "whatsapp": { "countryCode": "US", "phoneNumber": "2125550100" } }
|
|
50
51
|
}
|
|
51
52
|
}
|
|
52
53
|
```
|
|
@@ -61,7 +62,7 @@
|
|
|
61
62
|
| 结构化摘要 | `STRUCTURED_SNIPPET` | `ad extension snippet -a <id> --header Brands --values "A,B,C" --level Campaign --campaign-id <cid>` |
|
|
62
63
|
| 潜在客户表单 | `LEAD_FORM` | `ad extension lead-form -a <id> --config-file ./lead-form.json` |
|
|
63
64
|
| 附加链接 / 电话 | `SITELINK` / `CALL` | `ad extension sitelink` / `call`(同上 `--level Campaign`) |
|
|
64
|
-
| 私信 WhatsApp |
|
|
65
|
+
| 私信 WhatsApp | `BUSINESS_MESSAGE` | `ad extension whatsapp` 或 `campaignExtensions.businessMessage` |
|
|
65
66
|
|
|
66
67
|
```bash
|
|
67
68
|
# 查 PMax 支持类型
|
|
@@ -73,15 +74,22 @@ siluzan-tso ad extension snippet-headers --json-out ./snap
|
|
|
73
74
|
# Lead Form(模板见 assets/pmax-lead-form-template.md)
|
|
74
75
|
siluzan-tso ad extension lead-form -a <accountId> --config-file ./lead-form.json --json-out ./snap
|
|
75
76
|
|
|
77
|
+
# WhatsApp(模板见 assets/pmax-whatsapp-template.md;需 Google API 白名单)
|
|
78
|
+
siluzan-tso ad extension whatsapp -a <accountId> --config-file ./whatsapp.json --json-out ./snap
|
|
79
|
+
|
|
76
80
|
# 复核 / 更新 / 删除
|
|
77
81
|
siluzan-tso ad extension list -a <accountId> --type LEAD_FORM --campaign-id <cid> --json-out ./snap
|
|
82
|
+
siluzan-tso ad extension list -a <accountId> --type BUSINESS_MESSAGE --campaign-id <cid> --json-out ./snap
|
|
78
83
|
siluzan-tso ad extension update -a <accountId> --id <extId> --config-file ./lead-form.json
|
|
84
|
+
siluzan-tso ad extension update -a <accountId> --id <extId> --config-file ./whatsapp.json # id 会变
|
|
79
85
|
siluzan-tso ad extension delete -a <accountId> --id <extId>
|
|
80
86
|
```
|
|
81
87
|
|
|
82
88
|
**约束**:
|
|
83
89
|
- PMax **不支持** `level: Ad Group`(会 400)
|
|
84
90
|
- `LEAD_FORM` **仅** `Campaign` 级;每活动通常 1 个
|
|
91
|
+
- `BUSINESS_MESSAGE`(WhatsApp)每 Campaign **仅 1 个** ENABLED;须 **Google API 白名单**
|
|
92
|
+
- WhatsApp **PUT 后 asset id 会变**;后续操作须用响应中的新 `id`
|
|
85
93
|
- 创建 Lead Form 后后端自动尝试开启 `SUBMIT_LEAD_FORM` + `GOOGLE_HOSTED` 转化目标
|
|
86
94
|
|
|
87
95
|
**网关**:`GET /extensionmanagement/pmaxSupportedTypeList`;`POST|PUT|DELETE /extensionmanagement/extension/{accountId}[/{id}]`(Sammamish `ExtensionManagementController.cs`)。
|
|
@@ -9,7 +9,7 @@ $ErrorActionPreference = 'Stop'
|
|
|
9
9
|
# -- Package info (injected at build time) ------------------------------------
|
|
10
10
|
$PKG_NAME = 'siluzan-tso-cli'
|
|
11
11
|
# PKG_VERSION 锁定到与本脚本同批构建产物一致的版本,避免与 dist/skill 错位
|
|
12
|
-
$PKG_VERSION = '1.1.29-beta.
|
|
12
|
+
$PKG_VERSION = '1.1.29-beta.4'
|
|
13
13
|
$CLI_BIN = 'siluzan-tso'
|
|
14
14
|
$SKILL_LABEL = 'Siluzan TSO'
|
|
15
15
|
$INSTALL_CMD = 'npm install -g siluzan-tso-cli@beta'
|
|
@@ -9,7 +9,7 @@ set -euo pipefail
|
|
|
9
9
|
# -- Package info (injected at build time) ------------------------------------
|
|
10
10
|
readonly PKG_NAME="siluzan-tso-cli"
|
|
11
11
|
# PKG_VERSION 锁定到与本脚本同批构建产物一致的版本,避免与 dist/skill 错位
|
|
12
|
-
readonly PKG_VERSION="1.1.29-beta.
|
|
12
|
+
readonly PKG_VERSION="1.1.29-beta.4"
|
|
13
13
|
readonly CLI_BIN="siluzan-tso"
|
|
14
14
|
readonly SKILL_LABEL="Siluzan TSO"
|
|
15
15
|
readonly INSTALL_CMD="npm install -g siluzan-tso-cli@beta"
|