siluzan-tso-cli 1.1.22 → 1.1.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/dist/index.js +364 -152
- package/dist/skill/_meta.json +2 -2
- package/dist/skill/assets/campaign-create-template.md +1 -1
- package/dist/skill/assets/pmax-create-template.json +1 -0
- package/dist/skill/assets/pmax-create-template.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
|
@@ -110776,7 +110776,10 @@ function validateCampaignCreateConfigCore(cfg) {
|
|
|
110776
110776
|
pushErr2(errors, "account\uFF08\u9876\u5C42 customerId\uFF09\u4E0D\u80FD\u4E3A\u7A7A");
|
|
110777
110777
|
}
|
|
110778
110778
|
if (!cfg.customerName?.trim()) {
|
|
110779
|
-
|
|
110779
|
+
pushWarn2(
|
|
110780
|
+
warnings,
|
|
110781
|
+
"customerName \u4E3A\u7A7A\uFF1A\u63D0\u4EA4 campaign-create \u65F6\u5C06\u6309 account \u4ECE list-accounts \u81EA\u52A8\u586B\u5145 mediaCustomerName"
|
|
110782
|
+
);
|
|
110780
110783
|
}
|
|
110781
110784
|
const accountNum = Number(cfg.account);
|
|
110782
110785
|
if (!Number.isFinite(accountNum) || accountNum <= 0) {
|
|
@@ -111084,6 +111087,77 @@ function tryLoadCampaignCreateConfig(configFile) {
|
|
|
111084
111087
|
return stripMetaKeys(raw);
|
|
111085
111088
|
}
|
|
111086
111089
|
|
|
111090
|
+
// src/utils/media-account-lookup.ts
|
|
111091
|
+
init_auth();
|
|
111092
|
+
function pickMediaCustomerName(items, mediaCustomerId) {
|
|
111093
|
+
const list = Array.isArray(items) ? items : [];
|
|
111094
|
+
const target = mediaCustomerId.trim();
|
|
111095
|
+
for (const item of list) {
|
|
111096
|
+
const cid = String(item.ma?.mediaCustomerId ?? "");
|
|
111097
|
+
if (cid === target) {
|
|
111098
|
+
const name2 = String(item.ma?.mediaCustomerName ?? "").trim();
|
|
111099
|
+
return name2 || null;
|
|
111100
|
+
}
|
|
111101
|
+
}
|
|
111102
|
+
return null;
|
|
111103
|
+
}
|
|
111104
|
+
async function lookupFromMediaAccountQuery(apiBaseUrl, config, mediaType, mediaCustomerId, verbose) {
|
|
111105
|
+
const params = new URLSearchParams({
|
|
111106
|
+
MediaType: mediaType,
|
|
111107
|
+
mediaAccountState: "Approved,Linked",
|
|
111108
|
+
pageNo: "1",
|
|
111109
|
+
pageSize: "20",
|
|
111110
|
+
mediaCustomerId: mediaCustomerId.trim()
|
|
111111
|
+
});
|
|
111112
|
+
const data = await apiFetch2(
|
|
111113
|
+
`${apiBaseUrl}/query/media-account/?${params}`,
|
|
111114
|
+
config,
|
|
111115
|
+
{},
|
|
111116
|
+
verbose
|
|
111117
|
+
);
|
|
111118
|
+
return pickMediaCustomerName(data, mediaCustomerId);
|
|
111119
|
+
}
|
|
111120
|
+
async function lookupMediaCustomerName(apiBaseUrl, config, mediaType, mediaCustomerId, verbose = false) {
|
|
111121
|
+
const id = mediaCustomerId.trim();
|
|
111122
|
+
if (!id) return null;
|
|
111123
|
+
if (mediaType === "Google" || mediaType === "Yandex") {
|
|
111124
|
+
return lookupFromMediaAccountQuery(apiBaseUrl, config, mediaType, id, verbose);
|
|
111125
|
+
}
|
|
111126
|
+
return null;
|
|
111127
|
+
}
|
|
111128
|
+
|
|
111129
|
+
// src/commands/ad/campaign-customer-name.ts
|
|
111130
|
+
var CampaignCustomerNameResolveError = class extends Error {
|
|
111131
|
+
constructor(accountId) {
|
|
111132
|
+
super(
|
|
111133
|
+
`\u65E0\u6CD5\u6839\u636E account=${accountId} \u4ECE list-accounts \u89E3\u6790 customerName\uFF1B\u8BF7\u786E\u8BA4\u8D26\u6237 ID\u3001Token \u6709\u6548\uFF0C\u6216\u5728 JSON \u4E2D\u586B\u5199\u4E0E list-accounts mediaCustomerName \u5B8C\u5168\u4E00\u81F4\u7684 customerName`
|
|
111134
|
+
);
|
|
111135
|
+
this.name = "CampaignCustomerNameResolveError";
|
|
111136
|
+
}
|
|
111137
|
+
};
|
|
111138
|
+
async function resolveCampaignCustomerName(cfg, config, verbose) {
|
|
111139
|
+
const accountId = cfg.account.toString().trim();
|
|
111140
|
+
const previous = cfg.customerName?.trim() || void 0;
|
|
111141
|
+
const resolved = await lookupMediaCustomerName(
|
|
111142
|
+
config.apiBaseUrl,
|
|
111143
|
+
config,
|
|
111144
|
+
"Google",
|
|
111145
|
+
accountId,
|
|
111146
|
+
verbose
|
|
111147
|
+
);
|
|
111148
|
+
if (resolved) {
|
|
111149
|
+
return {
|
|
111150
|
+
customerName: resolved,
|
|
111151
|
+
corrected: Boolean(previous && previous !== resolved),
|
|
111152
|
+
previous: previous && previous !== resolved ? previous : void 0
|
|
111153
|
+
};
|
|
111154
|
+
}
|
|
111155
|
+
if (previous) {
|
|
111156
|
+
return { customerName: previous, corrected: false };
|
|
111157
|
+
}
|
|
111158
|
+
throw new CampaignCustomerNameResolveError(accountId);
|
|
111159
|
+
}
|
|
111160
|
+
|
|
111087
111161
|
// src/commands/ad/campaign.ts
|
|
111088
111162
|
function formatGoogleCampaignListStatus(row) {
|
|
111089
111163
|
let result = "-";
|
|
@@ -111304,10 +111378,34 @@ async function runAdCampaignCreate(opts) {
|
|
|
111304
111378
|
`);
|
|
111305
111379
|
process.exit(1);
|
|
111306
111380
|
}
|
|
111381
|
+
let customerName;
|
|
111382
|
+
try {
|
|
111383
|
+
const resolved = await resolveCampaignCustomerName(cfg, config, opts.verbose);
|
|
111384
|
+
customerName = resolved.customerName;
|
|
111385
|
+
if (resolved.corrected && resolved.previous) {
|
|
111386
|
+
console.warn(
|
|
111387
|
+
`
|
|
111388
|
+
\u26A0\uFE0F customerName \u5DF2\u6309 list-accounts \u81EA\u52A8\u66F4\u6B63\uFF1AJSON\u300C${resolved.previous}\u300D\u2192\u300C${resolved.customerName}\u300D
|
|
111389
|
+
`
|
|
111390
|
+
);
|
|
111391
|
+
} else if (!cfg.customerName?.trim()) {
|
|
111392
|
+
console.warn(`
|
|
111393
|
+
\u2139\uFE0F customerName \u5DF2\u4ECE list-accounts \u81EA\u52A8\u586B\u5145\uFF1A${resolved.customerName}
|
|
111394
|
+
`);
|
|
111395
|
+
}
|
|
111396
|
+
} catch (err) {
|
|
111397
|
+
if (err instanceof CampaignCustomerNameResolveError) {
|
|
111398
|
+
console.error(`
|
|
111399
|
+
\u274C ${err.message}
|
|
111400
|
+
`);
|
|
111401
|
+
process.exit(1);
|
|
111402
|
+
}
|
|
111403
|
+
throw err;
|
|
111404
|
+
}
|
|
111307
111405
|
const body = {
|
|
111308
111406
|
// 与 Web AICreation 一致:customerId 为 number;智投 ID 可空字符串
|
|
111309
111407
|
customerId: customerIdNum,
|
|
111310
|
-
customerName
|
|
111408
|
+
customerName,
|
|
111311
111409
|
campaignName: cfg.name ?? cfg.campaign["Name"],
|
|
111312
111410
|
url: cfg.url ?? "",
|
|
111313
111411
|
locations: cfg.locations ?? [],
|
|
@@ -113621,6 +113719,30 @@ async function runAiCreationPublish(opts) {
|
|
|
113621
113719
|
`);
|
|
113622
113720
|
process.exit(1);
|
|
113623
113721
|
}
|
|
113722
|
+
const customerId = String(draft["customerId"] ?? "").trim();
|
|
113723
|
+
if (customerId) {
|
|
113724
|
+
try {
|
|
113725
|
+
const resolved = await lookupMediaCustomerName(
|
|
113726
|
+
config.apiBaseUrl,
|
|
113727
|
+
config,
|
|
113728
|
+
"Google",
|
|
113729
|
+
customerId,
|
|
113730
|
+
opts.verbose
|
|
113731
|
+
);
|
|
113732
|
+
if (resolved) {
|
|
113733
|
+
const previous = String(draft["customerName"] ?? "").trim();
|
|
113734
|
+
if (previous && previous !== resolved) {
|
|
113735
|
+
console.warn(
|
|
113736
|
+
`
|
|
113737
|
+
\u26A0\uFE0F customerName \u5DF2\u6309 list-accounts \u81EA\u52A8\u66F4\u6B63\uFF1A\u8349\u7A3F\u300C${previous}\u300D\u2192\u300C${resolved}\u300D
|
|
113738
|
+
`
|
|
113739
|
+
);
|
|
113740
|
+
}
|
|
113741
|
+
draft["customerName"] = resolved;
|
|
113742
|
+
}
|
|
113743
|
+
} catch {
|
|
113744
|
+
}
|
|
113745
|
+
}
|
|
113624
113746
|
const body = preparePublishBody(draft);
|
|
113625
113747
|
try {
|
|
113626
113748
|
await apiFetch2(
|
|
@@ -114323,16 +114445,136 @@ function assertPmaxImageSlotsResolved(slots) {
|
|
|
114323
114445
|
}
|
|
114324
114446
|
}
|
|
114325
114447
|
|
|
114448
|
+
// src/commands/ad/pmax-load.ts
|
|
114449
|
+
import { readFileSync as readFileSync6 } from "fs";
|
|
114450
|
+
function loadPmaxCreateConfig(configFile) {
|
|
114451
|
+
const cfg = tryLoadPmaxCreateConfig(configFile);
|
|
114452
|
+
if (!cfg) {
|
|
114453
|
+
console.error(`
|
|
114454
|
+
\u274C \u8BFB\u53D6\u914D\u7F6E\u6587\u4EF6\u5931\u8D25\uFF08${configFile}\uFF09
|
|
114455
|
+
`);
|
|
114456
|
+
process.exit(1);
|
|
114457
|
+
}
|
|
114458
|
+
return cfg;
|
|
114459
|
+
}
|
|
114460
|
+
var PMAX_VIDEO_PATH_ALIASES = ["video", "videoFile", "localVideo"];
|
|
114461
|
+
function normalizePmaxCreateConfig(raw) {
|
|
114462
|
+
const stripped = stripMetaKeys(raw);
|
|
114463
|
+
const cfg = { ...stripped };
|
|
114464
|
+
if (!cfg.videoPath?.trim()) {
|
|
114465
|
+
for (const key of PMAX_VIDEO_PATH_ALIASES) {
|
|
114466
|
+
const v = stripped[key];
|
|
114467
|
+
if (typeof v === "string" && v.trim()) {
|
|
114468
|
+
cfg.videoPath = v.trim();
|
|
114469
|
+
break;
|
|
114470
|
+
}
|
|
114471
|
+
}
|
|
114472
|
+
if (!cfg.videoPath?.trim()) {
|
|
114473
|
+
const paths = stripped["videoPaths"];
|
|
114474
|
+
if (Array.isArray(paths)) {
|
|
114475
|
+
const first = paths.find((p) => typeof p === "string" && p.trim());
|
|
114476
|
+
if (typeof first === "string") cfg.videoPath = first.trim();
|
|
114477
|
+
}
|
|
114478
|
+
}
|
|
114479
|
+
}
|
|
114480
|
+
if (!cfg.videoTitle?.trim() && typeof stripped["videoName"] === "string" && stripped["videoName"].trim()) {
|
|
114481
|
+
cfg.videoTitle = stripped["videoName"].trim();
|
|
114482
|
+
}
|
|
114483
|
+
return cfg;
|
|
114484
|
+
}
|
|
114485
|
+
var DEPRECATED_PMAX_VIDEO_KEYS = ["videoPaths", "localVideo", "videoFile", "video"];
|
|
114486
|
+
function detectDeprecatedPmaxVideoKeys(raw) {
|
|
114487
|
+
const normalized = normalizePmaxCreateConfig(raw);
|
|
114488
|
+
const msgs = [];
|
|
114489
|
+
for (const key of DEPRECATED_PMAX_VIDEO_KEYS) {
|
|
114490
|
+
const v = raw[key];
|
|
114491
|
+
if (v == null) continue;
|
|
114492
|
+
if (typeof v === "string" && !v.trim()) continue;
|
|
114493
|
+
if (typeof v === "object" && !Array.isArray(v) && Object.keys(v).length === 0) {
|
|
114494
|
+
continue;
|
|
114495
|
+
}
|
|
114496
|
+
if ((key === "video" || key === "videoFile" || key === "localVideo") && normalized.videoPath?.trim()) {
|
|
114497
|
+
continue;
|
|
114498
|
+
}
|
|
114499
|
+
if (key === "videoPaths" && normalized.videoPath?.trim()) {
|
|
114500
|
+
continue;
|
|
114501
|
+
}
|
|
114502
|
+
msgs.push(`\u914D\u7F6E\u542B\u5DF2\u5E9F\u5F03\u5B57\u6BB5\u300C${key}\u300D\uFF0C\u8BF7\u6539\u7528 videoPath\uFF08\u672C\u5730\u6587\u4EF6\uFF09\u6216 youtubeUrlOrId`);
|
|
114503
|
+
}
|
|
114504
|
+
return msgs;
|
|
114505
|
+
}
|
|
114506
|
+
function tryLoadPmaxCreateConfig(configFile) {
|
|
114507
|
+
let raw;
|
|
114508
|
+
try {
|
|
114509
|
+
raw = JSON.parse(readFileSync6(configFile, "utf8"));
|
|
114510
|
+
} catch {
|
|
114511
|
+
return null;
|
|
114512
|
+
}
|
|
114513
|
+
return normalizePmaxCreateConfig(raw);
|
|
114514
|
+
}
|
|
114515
|
+
function loadPmaxCreateConfigRaw(configFile) {
|
|
114516
|
+
try {
|
|
114517
|
+
const raw = JSON.parse(readFileSync6(configFile, "utf8"));
|
|
114518
|
+
return stripMetaKeys(raw);
|
|
114519
|
+
} catch {
|
|
114520
|
+
return null;
|
|
114521
|
+
}
|
|
114522
|
+
}
|
|
114523
|
+
function parseLocationLanguageIds(items) {
|
|
114524
|
+
if (!items?.length) return void 0;
|
|
114525
|
+
return items.map((item) => ({
|
|
114526
|
+
id: Number(item.id)
|
|
114527
|
+
}));
|
|
114528
|
+
}
|
|
114529
|
+
function buildPmaxCreateApiBody(cfg, imageSlots) {
|
|
114530
|
+
assertPmaxImageSlotsResolved(imageSlots);
|
|
114531
|
+
const body = {
|
|
114532
|
+
name: cfg.name.trim(),
|
|
114533
|
+
budget: toCentAmount(Number(cfg.budget)),
|
|
114534
|
+
finalUrls: cfg.finalUrls.map((u) => u.trim()).filter(Boolean),
|
|
114535
|
+
headlines: cfg.headlines.map((h) => h.trim()).filter(Boolean),
|
|
114536
|
+
longHeadlines: cfg.longHeadlines.map((h) => h.trim()).filter(Boolean),
|
|
114537
|
+
descriptions: cfg.descriptions.map((d) => d.trim()).filter(Boolean),
|
|
114538
|
+
businessName: cfg.businessName.trim(),
|
|
114539
|
+
...imageSlots
|
|
114540
|
+
};
|
|
114541
|
+
if (cfg.budgetName?.trim()) body.budgetName = cfg.budgetName.trim();
|
|
114542
|
+
if (cfg.assetGroupName?.trim()) body.assetGroupName = cfg.assetGroupName.trim();
|
|
114543
|
+
const locations = parseLocationLanguageIds(cfg.targetedLocations);
|
|
114544
|
+
if (locations?.length) body.targetedLocations = locations;
|
|
114545
|
+
const languages = parseLocationLanguageIds(cfg.targetedLanguages);
|
|
114546
|
+
if (languages?.length) body.targetedLanguages = languages;
|
|
114547
|
+
const bidding = cfg.biddingStrategyTypeV2?.toString().trim();
|
|
114548
|
+
if (bidding) body.biddingStrategyTypeV2 = bidding.toUpperCase();
|
|
114549
|
+
const tcpa = cfg.targetCpa_BidingAmount;
|
|
114550
|
+
if (tcpa != null && Number(tcpa) > 0) {
|
|
114551
|
+
body.targetCpa_BidingAmount = toCentAmount(Number(tcpa));
|
|
114552
|
+
}
|
|
114553
|
+
const roas = cfg.targetRoas;
|
|
114554
|
+
if (roas != null && Number(roas) > 0) {
|
|
114555
|
+
body.targetRoas = Number(roas);
|
|
114556
|
+
}
|
|
114557
|
+
return body;
|
|
114558
|
+
}
|
|
114559
|
+
function buildPmaxCreateUrl(googleApiUrl, accountId) {
|
|
114560
|
+
const base = googleApiUrl.replace(/\/$/, "");
|
|
114561
|
+
return `${base}/accounts/${accountId}/campaign/pmax`;
|
|
114562
|
+
}
|
|
114563
|
+
|
|
114564
|
+
// src/commands/ad/pmax-video-upload.ts
|
|
114565
|
+
import { existsSync as existsSync3, readFileSync as readFileSync8 } from "fs";
|
|
114566
|
+
import { basename as basename5, dirname as dirname10, isAbsolute as isAbsolute5, resolve as resolve9 } from "path";
|
|
114567
|
+
|
|
114326
114568
|
// src/commands/ad/pmax-shared.ts
|
|
114327
114569
|
init_auth();
|
|
114328
114570
|
init_cli_json_snapshot();
|
|
114329
|
-
import { readFileSync as
|
|
114571
|
+
import { readFileSync as readFileSync7 } from "fs";
|
|
114330
114572
|
import { dirname as dirname9, isAbsolute as isAbsolute4, resolve as resolve8 } from "path";
|
|
114331
114573
|
var PMAX_MONEY_KEYS = /* @__PURE__ */ new Set(["budget", "targetCpa_BidingAmount"]);
|
|
114332
114574
|
function loadPmaxJsonFile(configFile) {
|
|
114333
114575
|
let raw;
|
|
114334
114576
|
try {
|
|
114335
|
-
raw = JSON.parse(
|
|
114577
|
+
raw = JSON.parse(readFileSync7(configFile, "utf8"));
|
|
114336
114578
|
} catch (e) {
|
|
114337
114579
|
const msg = e instanceof Error ? e.message : String(e);
|
|
114338
114580
|
console.error(`
|
|
@@ -114444,92 +114686,7 @@ function requireAccountId(account, label = "-a, --account") {
|
|
|
114444
114686
|
return id;
|
|
114445
114687
|
}
|
|
114446
114688
|
|
|
114447
|
-
// src/commands/ad/pmax-load.ts
|
|
114448
|
-
import { readFileSync as readFileSync7 } from "fs";
|
|
114449
|
-
function loadPmaxCreateConfig(configFile) {
|
|
114450
|
-
const cfg = tryLoadPmaxCreateConfig(configFile);
|
|
114451
|
-
if (!cfg) {
|
|
114452
|
-
console.error(`
|
|
114453
|
-
\u274C \u8BFB\u53D6\u914D\u7F6E\u6587\u4EF6\u5931\u8D25\uFF08${configFile}\uFF09
|
|
114454
|
-
`);
|
|
114455
|
-
process.exit(1);
|
|
114456
|
-
}
|
|
114457
|
-
return cfg;
|
|
114458
|
-
}
|
|
114459
|
-
var DEPRECATED_PMAX_VIDEO_KEYS = ["videoPaths", "localVideo", "videoFile", "video"];
|
|
114460
|
-
function detectDeprecatedPmaxVideoKeys(raw) {
|
|
114461
|
-
const msgs = [];
|
|
114462
|
-
for (const key of DEPRECATED_PMAX_VIDEO_KEYS) {
|
|
114463
|
-
const v = raw[key];
|
|
114464
|
-
if (v == null) continue;
|
|
114465
|
-
if (typeof v === "string" && !v.trim()) continue;
|
|
114466
|
-
if (typeof v === "object" && !Array.isArray(v) && Object.keys(v).length === 0) {
|
|
114467
|
-
continue;
|
|
114468
|
-
}
|
|
114469
|
-
msgs.push(`\u914D\u7F6E\u542B\u5DF2\u5E9F\u5F03\u5B57\u6BB5\u300C${key}\u300D\uFF0C\u8BF7\u6539\u7528 videoPath\uFF08\u672C\u5730\u6587\u4EF6\uFF09\u6216 youtubeUrlOrId`);
|
|
114470
|
-
}
|
|
114471
|
-
return msgs;
|
|
114472
|
-
}
|
|
114473
|
-
function tryLoadPmaxCreateConfig(configFile) {
|
|
114474
|
-
let raw;
|
|
114475
|
-
try {
|
|
114476
|
-
raw = JSON.parse(readFileSync7(configFile, "utf8"));
|
|
114477
|
-
} catch {
|
|
114478
|
-
return null;
|
|
114479
|
-
}
|
|
114480
|
-
return stripMetaKeys(raw);
|
|
114481
|
-
}
|
|
114482
|
-
function loadPmaxCreateConfigRaw(configFile) {
|
|
114483
|
-
try {
|
|
114484
|
-
return stripMetaKeys(JSON.parse(readFileSync7(configFile, "utf8")));
|
|
114485
|
-
} catch {
|
|
114486
|
-
return null;
|
|
114487
|
-
}
|
|
114488
|
-
}
|
|
114489
|
-
function parseLocationLanguageIds(items) {
|
|
114490
|
-
if (!items?.length) return void 0;
|
|
114491
|
-
return items.map((item) => ({
|
|
114492
|
-
id: Number(item.id)
|
|
114493
|
-
}));
|
|
114494
|
-
}
|
|
114495
|
-
function buildPmaxCreateApiBody(cfg, imageSlots) {
|
|
114496
|
-
assertPmaxImageSlotsResolved(imageSlots);
|
|
114497
|
-
const body = {
|
|
114498
|
-
name: cfg.name.trim(),
|
|
114499
|
-
budget: toCentAmount(Number(cfg.budget)),
|
|
114500
|
-
finalUrls: cfg.finalUrls.map((u) => u.trim()).filter(Boolean),
|
|
114501
|
-
headlines: cfg.headlines.map((h) => h.trim()).filter(Boolean),
|
|
114502
|
-
longHeadlines: cfg.longHeadlines.map((h) => h.trim()).filter(Boolean),
|
|
114503
|
-
descriptions: cfg.descriptions.map((d) => d.trim()).filter(Boolean),
|
|
114504
|
-
businessName: cfg.businessName.trim(),
|
|
114505
|
-
...imageSlots
|
|
114506
|
-
};
|
|
114507
|
-
if (cfg.budgetName?.trim()) body.budgetName = cfg.budgetName.trim();
|
|
114508
|
-
if (cfg.assetGroupName?.trim()) body.assetGroupName = cfg.assetGroupName.trim();
|
|
114509
|
-
const locations = parseLocationLanguageIds(cfg.targetedLocations);
|
|
114510
|
-
if (locations?.length) body.targetedLocations = locations;
|
|
114511
|
-
const languages = parseLocationLanguageIds(cfg.targetedLanguages);
|
|
114512
|
-
if (languages?.length) body.targetedLanguages = languages;
|
|
114513
|
-
const bidding = cfg.biddingStrategyTypeV2?.toString().trim();
|
|
114514
|
-
if (bidding) body.biddingStrategyTypeV2 = bidding.toUpperCase();
|
|
114515
|
-
const tcpa = cfg.targetCpa_BidingAmount;
|
|
114516
|
-
if (tcpa != null && Number(tcpa) > 0) {
|
|
114517
|
-
body.targetCpa_BidingAmount = toCentAmount(Number(tcpa));
|
|
114518
|
-
}
|
|
114519
|
-
const roas = cfg.targetRoas;
|
|
114520
|
-
if (roas != null && Number(roas) > 0) {
|
|
114521
|
-
body.targetRoas = Number(roas);
|
|
114522
|
-
}
|
|
114523
|
-
return body;
|
|
114524
|
-
}
|
|
114525
|
-
function buildPmaxCreateUrl(googleApiUrl, accountId) {
|
|
114526
|
-
const base = googleApiUrl.replace(/\/$/, "");
|
|
114527
|
-
return `${base}/accounts/${accountId}/campaign/pmax`;
|
|
114528
|
-
}
|
|
114529
|
-
|
|
114530
114689
|
// src/commands/ad/pmax-video-upload.ts
|
|
114531
|
-
import { existsSync as existsSync3, readFileSync as readFileSync8 } from "fs";
|
|
114532
|
-
import { basename as basename5, dirname as dirname10, isAbsolute as isAbsolute5, resolve as resolve9 } from "path";
|
|
114533
114690
|
var VIDEO_UPLOAD_SUFFIX = /\.(mp4|mov|webm|avi|mpeg|mpg)$/i;
|
|
114534
114691
|
var TERMINAL_VIDEO_STATES = /* @__PURE__ */ new Set(["PROCESSED", "FAILED", "REJECTED", "UNAVAILABLE", "NOT_FOUND"]);
|
|
114535
114692
|
var FAILED_VIDEO_STATES = /* @__PURE__ */ new Set(["FAILED", "REJECTED", "UNAVAILABLE", "NOT_FOUND"]);
|
|
@@ -114717,8 +114874,72 @@ function validatePmaxVideoPathQuick(absPath) {
|
|
|
114717
114874
|
}
|
|
114718
114875
|
return null;
|
|
114719
114876
|
}
|
|
114877
|
+
async function linkPmaxVideoAfterCreate(opts) {
|
|
114878
|
+
const assetGroupId = opts.assetGroupId;
|
|
114879
|
+
if (assetGroupId == null || String(assetGroupId).trim() === "") {
|
|
114880
|
+
return { skippedReason: "\u521B\u5EFA\u54CD\u5E94\u672A\u8FD4\u56DE assetGroupId\uFF0C\u65E0\u6CD5\u94FE\u63A5\u89C6\u9891" };
|
|
114881
|
+
}
|
|
114882
|
+
const agId = String(assetGroupId);
|
|
114883
|
+
const campaignId = opts.campaignId != null ? String(opts.campaignId) : void 0;
|
|
114884
|
+
let youtubeTarget = opts.cfg.youtubeUrlOrId?.trim();
|
|
114885
|
+
const videoPath = opts.cfg.videoPath?.trim();
|
|
114886
|
+
if (!youtubeTarget && !videoPath) {
|
|
114887
|
+
return {};
|
|
114888
|
+
}
|
|
114889
|
+
if (videoPath) {
|
|
114890
|
+
const absVideo = resolveVideoPath(opts.configFile, videoPath);
|
|
114891
|
+
if (opts.logProgress) console.log(`
|
|
114892
|
+
\u{1F4E4} \u4E0A\u4F20\u672C\u5730\u89C6\u9891\uFF08PyAPI\uFF09\u2026`);
|
|
114893
|
+
try {
|
|
114894
|
+
youtubeTarget = await uploadPmaxLocalVideo({
|
|
114895
|
+
config: opts.config,
|
|
114896
|
+
googleApiUrl: opts.googleApiUrl,
|
|
114897
|
+
accountId: opts.accountId,
|
|
114898
|
+
absVideoPath: absVideo,
|
|
114899
|
+
title: opts.cfg.videoTitle,
|
|
114900
|
+
description: opts.cfg.videoDescription,
|
|
114901
|
+
verbose: opts.verbose,
|
|
114902
|
+
onProgress: opts.logProgress ? (msg) => console.log(` ${msg}`) : void 0
|
|
114903
|
+
});
|
|
114904
|
+
if (opts.logProgress) console.log(` video_id\uFF1A${youtubeTarget}`);
|
|
114905
|
+
} catch (err) {
|
|
114906
|
+
return {
|
|
114907
|
+
uploadError: err instanceof Error ? err.message : String(err)
|
|
114908
|
+
};
|
|
114909
|
+
}
|
|
114910
|
+
}
|
|
114911
|
+
if (!youtubeTarget) {
|
|
114912
|
+
return { uploadError: "\u672A\u83B7\u5F97 YouTube video_id" };
|
|
114913
|
+
}
|
|
114914
|
+
try {
|
|
114915
|
+
const youtubeLink = await postPmaxYoutubeLink({
|
|
114916
|
+
config: opts.config,
|
|
114917
|
+
googleApiUrl: opts.googleApiUrl,
|
|
114918
|
+
accountId: opts.accountId,
|
|
114919
|
+
assetGroupId: agId,
|
|
114920
|
+
youtubeUrlOrId: youtubeTarget,
|
|
114921
|
+
campaignId,
|
|
114922
|
+
assetName: opts.cfg.youtubeAssetName,
|
|
114923
|
+
verbose: opts.verbose
|
|
114924
|
+
});
|
|
114925
|
+
return { youtubeTarget, youtubeLink };
|
|
114926
|
+
} catch (err) {
|
|
114927
|
+
return {
|
|
114928
|
+
youtubeTarget,
|
|
114929
|
+
linkError: err instanceof Error ? err.message : String(err)
|
|
114930
|
+
};
|
|
114931
|
+
}
|
|
114932
|
+
}
|
|
114720
114933
|
|
|
114721
114934
|
// src/commands/ad/pmax-create.ts
|
|
114935
|
+
function printVideoLinkRecoveryHint(accountId, assetGroupId, campaignId, youtubeTarget) {
|
|
114936
|
+
const cid = campaignId != null ? ` --campaign-id ${campaignId}` : "";
|
|
114937
|
+
const yt = youtubeTarget ? ` --youtube "${youtubeTarget}"` : ` --video-path "<path>"`;
|
|
114938
|
+
console.error(
|
|
114939
|
+
` \u53EF\u624B\u52A8\uFF1Asiluzan-tso ad pmax-youtube-link -a ${accountId} --asset-group-id ${assetGroupId}${cid}${yt}
|
|
114940
|
+
`
|
|
114941
|
+
);
|
|
114942
|
+
}
|
|
114722
114943
|
async function runAdPmaxCreate(opts) {
|
|
114723
114944
|
const cfg = loadPmaxCreateConfig(opts.configFile);
|
|
114724
114945
|
const rawCfg = loadPmaxCreateConfigRaw(opts.configFile) ?? {};
|
|
@@ -114795,76 +115016,67 @@ async function runAdPmaxCreate(opts) {
|
|
|
114795
115016
|
`);
|
|
114796
115017
|
process.exit(1);
|
|
114797
115018
|
}
|
|
115019
|
+
const campaignId = data["campaignId"] ?? data["campaign_id"];
|
|
115020
|
+
const assetGroupId = data["assetGroupId"] ?? data["asset_group_id"];
|
|
115021
|
+
const budgetId = data["budgetId"] ?? data["budget_id"];
|
|
115022
|
+
const videoResult = await linkPmaxVideoAfterCreate({
|
|
115023
|
+
config,
|
|
115024
|
+
googleApiUrl,
|
|
115025
|
+
accountId,
|
|
115026
|
+
configFile: opts.configFile,
|
|
115027
|
+
cfg,
|
|
115028
|
+
campaignId,
|
|
115029
|
+
assetGroupId,
|
|
115030
|
+
verbose: opts.verbose,
|
|
115031
|
+
logProgress: !opts.jsonOut
|
|
115032
|
+
});
|
|
115033
|
+
const payload = {
|
|
115034
|
+
...data,
|
|
115035
|
+
videoLink: {
|
|
115036
|
+
ok: Boolean(videoResult.youtubeLink && !videoResult.uploadError && !videoResult.linkError),
|
|
115037
|
+
youtubeTarget: videoResult.youtubeTarget,
|
|
115038
|
+
assetResourceName: videoResult.youtubeLink?.["assetResourceName"],
|
|
115039
|
+
uploadError: videoResult.uploadError,
|
|
115040
|
+
linkError: videoResult.linkError,
|
|
115041
|
+
skippedReason: videoResult.skippedReason
|
|
115042
|
+
}
|
|
115043
|
+
};
|
|
115044
|
+
if (videoResult.uploadError) {
|
|
115045
|
+
console.error(`
|
|
115046
|
+
\u26A0\uFE0F \u6D3B\u52A8\u5DF2\u521B\u5EFA\uFF0C\u4F46\u672C\u5730\u89C6\u9891\u4E0A\u4F20\u5931\u8D25\uFF1A${videoResult.uploadError}`);
|
|
115047
|
+
if (assetGroupId != null) {
|
|
115048
|
+
printVideoLinkRecoveryHint(accountId, assetGroupId, campaignId, void 0);
|
|
115049
|
+
}
|
|
115050
|
+
} else if (videoResult.linkError) {
|
|
115051
|
+
console.error(`
|
|
115052
|
+
\u26A0\uFE0F \u6D3B\u52A8\u5DF2\u521B\u5EFA\uFF0C\u4F46 YouTube \u94FE\u63A5\u5931\u8D25\uFF1A${videoResult.linkError}`);
|
|
115053
|
+
if (assetGroupId != null) {
|
|
115054
|
+
printVideoLinkRecoveryHint(
|
|
115055
|
+
accountId,
|
|
115056
|
+
assetGroupId,
|
|
115057
|
+
campaignId,
|
|
115058
|
+
videoResult.youtubeTarget
|
|
115059
|
+
);
|
|
115060
|
+
}
|
|
115061
|
+
} else if (videoResult.skippedReason && (cfg.videoPath?.trim() || cfg.youtubeUrlOrId?.trim())) {
|
|
115062
|
+
console.error(`
|
|
115063
|
+
\u26A0\uFE0F \u6D3B\u52A8\u5DF2\u521B\u5EFA\uFF0C\u4F46\u89C6\u9891\u672A\u94FE\u63A5\uFF1A${videoResult.skippedReason}
|
|
115064
|
+
`);
|
|
115065
|
+
}
|
|
114798
115066
|
if (await emitCliJsonOrSnapshot(opts, {
|
|
114799
115067
|
section: `ad-pmax-create-${accountId}`,
|
|
114800
115068
|
commandLabel: "ad pmax-create",
|
|
114801
115069
|
commandHint: accountId,
|
|
114802
|
-
payload
|
|
115070
|
+
payload,
|
|
114803
115071
|
idSuffix: accountId
|
|
114804
115072
|
})) {
|
|
114805
115073
|
return;
|
|
114806
115074
|
}
|
|
114807
|
-
|
|
114808
|
-
|
|
114809
|
-
|
|
114810
|
-
|
|
114811
|
-
|
|
114812
|
-
if (videoPath && assetGroupId != null) {
|
|
114813
|
-
const absVideo = resolvePmaxImagePath2(opts.configFile, videoPath);
|
|
114814
|
-
if (!opts.jsonOut) console.log(`
|
|
114815
|
-
\u{1F4E4} \u4E0A\u4F20\u672C\u5730\u89C6\u9891\uFF08PyAPI\uFF09\u2026`);
|
|
114816
|
-
try {
|
|
114817
|
-
youtubeTarget = await uploadPmaxLocalVideo({
|
|
114818
|
-
config,
|
|
114819
|
-
googleApiUrl,
|
|
114820
|
-
accountId,
|
|
114821
|
-
absVideoPath: absVideo,
|
|
114822
|
-
title: cfg.videoTitle,
|
|
114823
|
-
description: cfg.videoDescription,
|
|
114824
|
-
verbose: opts.verbose,
|
|
114825
|
-
onProgress: opts.jsonOut ? void 0 : (msg) => console.log(` ${msg}`)
|
|
114826
|
-
});
|
|
114827
|
-
if (!opts.jsonOut) console.log(` video_id\uFF1A${youtubeTarget}`);
|
|
114828
|
-
} catch (err) {
|
|
114829
|
-
console.error(
|
|
114830
|
-
`
|
|
114831
|
-
\u26A0\uFE0F \u6D3B\u52A8\u5DF2\u521B\u5EFA\uFF0C\u4F46\u672C\u5730\u89C6\u9891\u4E0A\u4F20\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}`
|
|
114832
|
-
);
|
|
114833
|
-
console.error(
|
|
114834
|
-
` \u53EF\u624B\u52A8\u4E0A\u4F20\u540E\u94FE\u63A5\uFF1Asiluzan-tso ad pmax-youtube-link -a ${accountId} --asset-group-id ${assetGroupId}` + (campaignId != null ? ` --campaign-id ${campaignId}` : "") + ` --youtube "<video_id>"
|
|
114835
|
-
`
|
|
114836
|
-
);
|
|
114837
|
-
youtubeTarget = void 0;
|
|
114838
|
-
}
|
|
114839
|
-
}
|
|
114840
|
-
if (youtubeTarget && assetGroupId != null) {
|
|
114841
|
-
try {
|
|
114842
|
-
const yt = await postPmaxYoutubeLink({
|
|
114843
|
-
config,
|
|
114844
|
-
googleApiUrl,
|
|
114845
|
-
accountId,
|
|
114846
|
-
assetGroupId: String(assetGroupId),
|
|
114847
|
-
youtubeUrlOrId: youtubeTarget,
|
|
114848
|
-
campaignId: campaignId != null ? String(campaignId) : void 0,
|
|
114849
|
-
assetName: cfg.youtubeAssetName,
|
|
114850
|
-
verbose: opts.verbose
|
|
114851
|
-
});
|
|
114852
|
-
if (!opts.jsonOut) {
|
|
114853
|
-
console.log(`
|
|
114854
|
-
\u2705 \u5DF2\u94FE\u63A5 YouTube\uFF1A${youtubeTarget}`);
|
|
114855
|
-
const arn = yt["assetResourceName"];
|
|
114856
|
-
if (arn != null) console.log(` assetResourceName\uFF1A${arn}`);
|
|
114857
|
-
}
|
|
114858
|
-
} catch (err) {
|
|
114859
|
-
console.error(
|
|
114860
|
-
`
|
|
114861
|
-
\u26A0\uFE0F \u6D3B\u52A8\u5DF2\u521B\u5EFA\uFF0C\u4F46 YouTube \u94FE\u63A5\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}`
|
|
114862
|
-
);
|
|
114863
|
-
console.error(
|
|
114864
|
-
` \u8BF7\u624B\u52A8\u6267\u884C\uFF1Asiluzan-tso ad pmax-youtube-link -a ${accountId} --asset-group-id ${assetGroupId}` + (campaignId != null ? ` --campaign-id ${campaignId}` : "") + ` --youtube "${youtubeTarget}"
|
|
114865
|
-
`
|
|
114866
|
-
);
|
|
114867
|
-
}
|
|
115075
|
+
if (videoResult.youtubeTarget && videoResult.youtubeLink) {
|
|
115076
|
+
console.log(`
|
|
115077
|
+
\u2705 \u5DF2\u94FE\u63A5 YouTube\uFF1A${videoResult.youtubeTarget}`);
|
|
115078
|
+
const arn = videoResult.youtubeLink["assetResourceName"];
|
|
115079
|
+
if (arn != null) console.log(` assetResourceName\uFF1A${arn}`);
|
|
114868
115080
|
}
|
|
114869
115081
|
console.log("\n\u2705 PMax \u5E7F\u544A\u7CFB\u5217\u5DF2\u521B\u5EFA\uFF08\u540C\u6B65\uFF09");
|
|
114870
115082
|
console.log(` \u6D3B\u52A8\u540D\u79F0\uFF1A${cfg.name}`);
|
|
@@ -114875,7 +115087,7 @@ async function runAdPmaxCreate(opts) {
|
|
|
114875
115087
|
`
|
|
114876
115088
|
\u590D\u6838\uFF1Asiluzan-tso ad campaigns -a ${accountId} --json-out # channelTypeV2 \u5E94\u4E3A PERFORMANCE_MAX`
|
|
114877
115089
|
);
|
|
114878
|
-
if (!
|
|
115090
|
+
if (!cfg.videoPath?.trim() && !cfg.youtubeUrlOrId?.trim()) {
|
|
114879
115091
|
console.log(
|
|
114880
115092
|
" \u89C6\u9891\uFF1A\u914D\u7F6E videoPath\uFF08\u672C\u5730\uFF09\u6216 youtubeUrlOrId \u53EF\u5728\u521B\u5EFA\u65F6\u81EA\u52A8\u94FE\u63A5\uFF1B\u6216\u521B\u5EFA\u540E ad pmax-youtube-link"
|
|
114881
115093
|
);
|
package/dist/skill/_meta.json
CHANGED
|
@@ -94,7 +94,7 @@ siluzan-tso ad batch diff --batch-id <taskId> --config-file ./campaign.json --js
|
|
|
94
94
|
| 字段 | 类型 | 必填 | 说明 |
|
|
95
95
|
| ---------------------- | ------------------- | :--: | ------------------------------------------------------------------------------------------------------ |
|
|
96
96
|
| `account` | string | ✅ | 媒体账户 ID;提交时转为数字 `customerId`(勿依赖引号字符串) |
|
|
97
|
-
| `customerName` | string |
|
|
97
|
+
| `customerName` | string | | 展示/智投用客户名;**可省略**——`campaign-create` / `batch publish` 会按 `account` 调 `list-accounts` 自动填入 `mediaCustomerName`。若填写则须与之一致,否则提交时 CLI 自动更正 |
|
|
98
98
|
| `name` | string | | 智投 `campaignName`;缺省取 `campaign.Name`;账户内不得与已有在投/暂停系列重名,否则 BatchJob 系列创建失败 |
|
|
99
99
|
| `url` | string | | 智投展示用 URL;后端只读,用于回显 |
|
|
100
100
|
| `locations` | string[] | | 展示用地区名(后端只读,可空数组) |
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
"勿与 Search campaign-create-template.json 混用(PMax 走 POST .../campaign/pmax,非 batch-asyncs)",
|
|
8
8
|
"budget / targetCpa_BidingAmount 填主币种「元」,CLI 提交前 ×100 转 API 整型",
|
|
9
9
|
"三张图:只填 imagePaths(pmax-create 会自动上传为 assetId);勿把 Base64 写入 JSON",
|
|
10
|
+
"本地视频:填 videoPath(或别名 video);创建后 CLI 自动 PyAPI 上传并链接,与 --json-out 无关",
|
|
10
11
|
"提交前:ad pmax-validate → 用户确认 → ad pmax-create"
|
|
11
12
|
]
|
|
12
13
|
},
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
| 文案超长 | `pmax-validate --json-out` 读 `lengthViolations`(含完整 `text`);**勿自动截断**,列改写方案给用户确认后再改 JSON 并重跑 validate(同 Search `campaign-validate`) |
|
|
17
17
|
| 金额 | JSON 填**主币种「元」**;CLI 提交前 `budget`、`targetCpa_BidingAmount` ×100 |
|
|
18
18
|
| 图片 | **只填 `imagePaths`** 指向本地 PNG/JPEG;`pmax-create` 会自动 multipart 上传并用 assetId 创建(勿把 Base64 提交进 Git) |
|
|
19
|
-
| 视频 |
|
|
19
|
+
| 视频 | JSON 填 **`videoPath`**(别名 `video` 亦可);`pmax-create` 成功后 **必定**经 PyAPI 上传并链接(含 `--json-out`)。已有 YouTube 用 `youtubeUrlOrId` |
|
|
20
20
|
| 改已上线 PMax | `ad pmax-get` / `pmax-edit` / `pmax-assets-update` 等(见 `references/google-ads/pmax-api.md`) |
|
|
21
21
|
| 列表复核 | `ad campaigns -a <id> --json-out ./snap`,`channelTypeV2` 应为 `PERFORMANCE_MAX` |
|
|
22
22
|
|
|
@@ -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.
|
|
12
|
+
$PKG_VERSION = '1.1.23'
|
|
13
13
|
$CLI_BIN = 'siluzan-tso'
|
|
14
14
|
$SKILL_LABEL = 'Siluzan TSO'
|
|
15
15
|
$INSTALL_CMD = 'npm install -g siluzan-tso-cli'
|
|
@@ -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.
|
|
12
|
+
readonly PKG_VERSION="1.1.23"
|
|
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"
|