addashboard-cli 0.8.1 → 0.9.0
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/commands/ads.js +97 -7
- package/dist/commands/ads.js.map +1 -1
- package/dist/commands/submission.js +232 -16
- package/dist/commands/submission.js.map +1 -1
- package/dist/index.js +8 -2
- package/dist/index.js.map +1 -1
- package/dist/schemas/submit-plan.js +80 -5
- package/dist/schemas/submit-plan.js.map +1 -1
- package/package.json +1 -1
- package/skills/ad-ops/SKILL.md +179 -9
- package/skills/ad-ops/ad-planner.html +1203 -2
- package/skills/ad-ops/scripts/adops.sh +55 -6
- package/skills/ad-ops/scripts/plan_lint.py +164 -24
- package/skills/ad-ops//343/202/242/343/202/253/343/202/246/343/203/263/343/203/210/bytedance/index.json +5 -0
- package/skills/ad-ops//343/202/242/343/202/253/343/202/246/343/203/263/343/203/210/bytedance/sample_account.json +55 -0
- package/skills/ad-ops//343/202/242/343/202/253/343/202/246/343/203/263/343/203/210/google/index.json +5 -0
- package/skills/ad-ops//343/202/242/343/202/253/343/202/246/343/203/263/343/203/210/google/sample_account.json +40 -0
- package/skills/ad-ops//343/202/265/343/203/263/343/203/227/343/203/253/343/203/227/343/203/251/343/203/263_bytedance.json +30 -0
- package/skills/ad-ops//343/202/265/343/203/263/343/203/227/343/203/253/343/203/227/343/203/251/343/203/263_google.json +33 -0
package/dist/commands/ads.js
CHANGED
|
@@ -16,13 +16,19 @@ exports.ALLOWED_RESOURCES = [
|
|
|
16
16
|
'pages',
|
|
17
17
|
'pixels',
|
|
18
18
|
'instagram-accounts',
|
|
19
|
+
'identities',
|
|
20
|
+
'regions',
|
|
21
|
+
'adgroups',
|
|
19
22
|
];
|
|
20
23
|
/** yda のみ対応のリソース。
|
|
21
|
-
* - media: 登録画像一覧 (W6 新規アカウントインポート用)。他媒体は画像一覧 API 未実装。
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
*
|
|
25
|
-
|
|
24
|
+
* - media: 登録画像一覧 (W6 新規アカウントインポート用)。他媒体は画像一覧 API 未実装。 */
|
|
25
|
+
const YDA_ONLY_RESOURCES = ['media'];
|
|
26
|
+
/** `ads` (入稿済み広告の一覧) に対応している媒体。
|
|
27
|
+
* 広告レベルの文言・URL・状態を取る。campaign-import は広告グループ設定までしか返さないため、
|
|
28
|
+
* 配信中の広告の中身と **入稿後に意図した状態 (PAUSED 等) で入ったか** を確認する手段がこれしかない。
|
|
29
|
+
* bytedance は #219 で、google は 2026-08-23 に追加
|
|
30
|
+
* (実 API 検証で「PAUSED で入ったか API から確認できない」と判明したため)。 */
|
|
31
|
+
const ADS_SUPPORTED_PLATFORMS = ['yda', 'google', 'bytedance'];
|
|
26
32
|
/** image-library / video-library は Meta のみ (backend /api/editor/meta/* 由来)。
|
|
27
33
|
* pages / pixels / instagram-accounts も Meta のみ。入稿プランナーの案件設定チップに要る
|
|
28
34
|
* FBページID / pixelId / IGユーザーID は CLI で取れず管理画面のコピペに頼っていたため追加した。 */
|
|
@@ -33,6 +39,10 @@ const META_ONLY_RESOURCES = [
|
|
|
33
39
|
'pixels',
|
|
34
40
|
'instagram-accounts',
|
|
35
41
|
];
|
|
42
|
+
/** bytedance のみ対応のリソース。
|
|
43
|
+
* - identities: 広告主として表示される TikTok アカウント。**これが無いと広告が作れない**
|
|
44
|
+
* (Meta の pages と同じ位置づけ)。動画・画像は resources が両方まとめて返すので分けない。 */
|
|
45
|
+
const BYTEDANCE_ONLY_RESOURCES = ['identities', 'regions', 'adgroups'];
|
|
36
46
|
/**
|
|
37
47
|
* v5 §5.2: platform × resource → backend エンドポイント解決テーブル。
|
|
38
48
|
*
|
|
@@ -46,12 +56,15 @@ const META_ONLY_RESOURCES = [
|
|
|
46
56
|
*
|
|
47
57
|
* media は W6 (新規アカウントインポート) 用の登録画像一覧リソース。yda のみ対応
|
|
48
58
|
* (/yda/media)。他媒体は未対応 (画像一覧 API 未実装)。
|
|
59
|
+
* --approval-status で審査状況を絞れる (カンマ区切り)。メディア単位の否認は広告単位の
|
|
60
|
+
* approvalStatus に出ない (広告は「承認済み」のまま画像だけ掲載不可になる) ため、
|
|
61
|
+
* 掲載不可メディアの検知はここが唯一の窓口になる。
|
|
49
62
|
*
|
|
50
63
|
* image-library / video-library は Meta の登録済みクリエイティブ一覧 (画像 hash / 動画 ID) で、
|
|
51
64
|
* backend の既存 GET /api/editor/meta/{image,video}-library を叩く。meta のみ対応。
|
|
52
65
|
*/
|
|
53
66
|
function resolvePath(input) {
|
|
54
|
-
const { platform, resource, accountId, campaignId, adGroupIds, raw } = input;
|
|
67
|
+
const { platform, resource, accountId, campaignId, adGroupIds, raw, approvalStatus, mode, levelRange, } = input;
|
|
55
68
|
if (!exports.ALLOWED_PLATFORMS.includes(platform)) {
|
|
56
69
|
throw new Error(`unknown platform: ${platform} (allowed: ${exports.ALLOWED_PLATFORMS.join(', ')})`);
|
|
57
70
|
}
|
|
@@ -61,10 +74,21 @@ function resolvePath(input) {
|
|
|
61
74
|
if (YDA_ONLY_RESOURCES.includes(resource) && platform !== 'yda') {
|
|
62
75
|
throw new Error(`${platform} + ${resource} は未対応です (${resource} は yda のみ対応)`);
|
|
63
76
|
}
|
|
77
|
+
if (resource === 'ads' && !ADS_SUPPORTED_PLATFORMS.includes(platform)) {
|
|
78
|
+
throw new Error(`${platform} + ads は未対応です (ads は ${ADS_SUPPORTED_PLATFORMS.join(' / ')} のみ対応)`);
|
|
79
|
+
}
|
|
80
|
+
// 審査状況フィルタは media 一覧にしか無い。他リソースで渡されたら黙って捨てず落とす
|
|
81
|
+
// (絞れたつもりで全件を見てしまうのが一番まずい)。
|
|
82
|
+
if (approvalStatus !== undefined && !(platform === 'yda' && resource === 'media')) {
|
|
83
|
+
throw new Error('--approval-status は yda + media でのみ指定できます');
|
|
84
|
+
}
|
|
64
85
|
// image-library / video-library (Meta の登録済みクリエイティブ一覧) は meta のみ対応。
|
|
65
86
|
if (META_ONLY_RESOURCES.includes(resource) && platform !== 'meta') {
|
|
66
87
|
throw new Error(`${platform} + ${resource} は未対応です (${META_ONLY_RESOURCES.join(' / ')} は meta のみ対応)`);
|
|
67
88
|
}
|
|
89
|
+
if (BYTEDANCE_ONLY_RESOURCES.includes(resource) && platform !== 'bytedance') {
|
|
90
|
+
throw new Error(`${platform} + ${resource} は未対応です (${BYTEDANCE_ONLY_RESOURCES.join(' / ')} は bytedance のみ対応)`);
|
|
91
|
+
}
|
|
68
92
|
if (platform === 'meta') {
|
|
69
93
|
if (resource === 'accounts')
|
|
70
94
|
return { path: '/api/submission/meta/accounts', query: {} };
|
|
@@ -149,7 +173,10 @@ function resolvePath(input) {
|
|
|
149
173
|
if (resource === 'media') {
|
|
150
174
|
if (!accountId)
|
|
151
175
|
throw new Error('--account-id が必要です (yda + media)');
|
|
152
|
-
return {
|
|
176
|
+
return {
|
|
177
|
+
path: '/api/submission/yda/media',
|
|
178
|
+
query: { accountId, ...(approvalStatus ? { approvalStatus } : {}) },
|
|
179
|
+
};
|
|
153
180
|
}
|
|
154
181
|
}
|
|
155
182
|
if (platform === 'google') {
|
|
@@ -165,6 +192,15 @@ function resolvePath(input) {
|
|
|
165
192
|
throw new Error('--account-id が必要です (google + resources)');
|
|
166
193
|
return { path: '/api/submission/google/resources', query: { accountId } };
|
|
167
194
|
}
|
|
195
|
+
if (resource === 'ads') {
|
|
196
|
+
if (!accountId)
|
|
197
|
+
throw new Error('--account-id が必要です (google + ads)');
|
|
198
|
+
// campaignId は任意。省略するとアカウント配下すべて (最大 500 件)
|
|
199
|
+
return {
|
|
200
|
+
path: '/api/submission/google/ads',
|
|
201
|
+
query: campaignId ? { accountId, campaignId } : { accountId },
|
|
202
|
+
};
|
|
203
|
+
}
|
|
168
204
|
}
|
|
169
205
|
if (platform === 'smartnews') {
|
|
170
206
|
if (resource === 'accounts')
|
|
@@ -191,6 +227,57 @@ function resolvePath(input) {
|
|
|
191
227
|
throw new Error('--account-id が必要です (bytedance + resources)');
|
|
192
228
|
return { path: '/api/submission/bytedance/resources', query: { accountId } };
|
|
193
229
|
}
|
|
230
|
+
// identity は広告アカウント配下 (--account-id 必須)。
|
|
231
|
+
// 広告作成に必須なので、プランナーの案件設定に入れる前にここで取る。
|
|
232
|
+
if (resource === 'identities') {
|
|
233
|
+
if (!accountId)
|
|
234
|
+
throw new Error('--account-id が必要です (bytedance + identities)');
|
|
235
|
+
return { path: '/api/submission/bytedance/identities', query: { accountId } };
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* 入稿に使える地域 (location_id)。**地域IDは TikTok 独自で一覧が公開されていない**
|
|
239
|
+
* (実測で 1210023 が invalid だった)。応答のキー名も未確定なので --raw で生を見られる。
|
|
240
|
+
*/
|
|
241
|
+
if (resource === 'regions') {
|
|
242
|
+
if (!accountId)
|
|
243
|
+
throw new Error('--account-id が必要です (bytedance + regions)');
|
|
244
|
+
return {
|
|
245
|
+
path: '/api/submission/bytedance/regions',
|
|
246
|
+
query: {
|
|
247
|
+
accountId,
|
|
248
|
+
...(mode ? { mode } : {}),
|
|
249
|
+
...(levelRange ? { levelRange } : {}),
|
|
250
|
+
...(raw ? { raw: '1' } : {}),
|
|
251
|
+
},
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
// 既存の広告グループ。**location_ids の逆引き**に使う(配信できている値が確実な手がかり)
|
|
255
|
+
if (resource === 'adgroups') {
|
|
256
|
+
if (!accountId)
|
|
257
|
+
throw new Error('--account-id が必要です (bytedance + adgroups)');
|
|
258
|
+
return {
|
|
259
|
+
path: '/api/submission/bytedance/adgroups',
|
|
260
|
+
query: { accountId, ...(campaignId ? { campaignId } : {}) },
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
// 入稿後の確認用。DISABLE で入ったか / 素材と CTA が反映されたかを見る。
|
|
264
|
+
if (resource === 'ads') {
|
|
265
|
+
if (!accountId)
|
|
266
|
+
throw new Error('--account-id が必要です (bytedance + ads)');
|
|
267
|
+
// TikTok の filtering は adgroup_ids を配列で取るが、CLI からは 1 件に絞る。
|
|
268
|
+
// 複数渡されたときに黙って先頭だけ使うと「絞れたつもりで別のものを見る」ため落とす。
|
|
269
|
+
if (adGroupIds && adGroupIds.includes(',')) {
|
|
270
|
+
throw new Error('--ad-group-ids は 1 件だけ指定してください (bytedance + ads)');
|
|
271
|
+
}
|
|
272
|
+
return {
|
|
273
|
+
path: '/api/submission/bytedance/ads',
|
|
274
|
+
query: {
|
|
275
|
+
accountId,
|
|
276
|
+
...(campaignId ? { campaignId } : {}),
|
|
277
|
+
...(adGroupIds ? { adGroupId: adGroupIds.trim() } : {}),
|
|
278
|
+
},
|
|
279
|
+
};
|
|
280
|
+
}
|
|
194
281
|
}
|
|
195
282
|
throw new Error(`unsupported: ${platform} + ${resource}`);
|
|
196
283
|
}
|
|
@@ -208,6 +295,9 @@ async function runList(input) {
|
|
|
208
295
|
campaignId: input.campaignId,
|
|
209
296
|
adGroupIds: input.adGroupIds,
|
|
210
297
|
raw: input.raw,
|
|
298
|
+
approvalStatus: input.approvalStatus,
|
|
299
|
+
mode: input.mode,
|
|
300
|
+
levelRange: input.levelRange,
|
|
211
301
|
});
|
|
212
302
|
}
|
|
213
303
|
catch (e) {
|
package/dist/commands/ads.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ads.js","sourceRoot":"","sources":["../../src/commands/ads.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"ads.js","sourceRoot":"","sources":["../../src/commands/ads.ts"],"names":[],"mappings":"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"}
|
|
@@ -3,9 +3,10 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.SUBMIT_PLAN_PLATFORMS = exports.SUBMIT_PLATFORMS = void 0;
|
|
6
|
+
exports.SUBMIT_PLAN_LIVE_PLATFORMS = exports.SUBMIT_PLAN_PLATFORMS = exports.SUBMIT_PLATFORMS = void 0;
|
|
7
7
|
exports.validateLocalMediaPaths = validateLocalMediaPaths;
|
|
8
8
|
exports.runSubmit = runSubmit;
|
|
9
|
+
exports.buildSubmitPlanBody = buildSubmitPlanBody;
|
|
9
10
|
exports.runSubmitPlan = runSubmitPlan;
|
|
10
11
|
const crypto_1 = __importDefault(require("crypto"));
|
|
11
12
|
const fs_1 = __importDefault(require("fs"));
|
|
@@ -320,6 +321,108 @@ async function resolveGoogleImages(client, body, jsonFile, log) {
|
|
|
320
321
|
}
|
|
321
322
|
}
|
|
322
323
|
}
|
|
324
|
+
/**
|
|
325
|
+
* TikTok のアップロード対象を 1 箇所で決める。
|
|
326
|
+
*
|
|
327
|
+
* **「dry-run の事前検証で集める集合」と「本実行で実際に上げる集合」がズレると
|
|
328
|
+
* 部分アップロードや検証漏れが起きる**(片方だけ条件を直す事故が起きやすい)。
|
|
329
|
+
* 判定はこの 2 関数だけを使い、条件を各所に書かない。
|
|
330
|
+
*/
|
|
331
|
+
function bytedanceVideoPath(ad) {
|
|
332
|
+
// 既に videoId があれば上げ直さない(再開時の二重アップロード防止)
|
|
333
|
+
if (ad?.videoId)
|
|
334
|
+
return null;
|
|
335
|
+
return ad?.videoPath ? String(ad.videoPath) : null;
|
|
336
|
+
}
|
|
337
|
+
/** カバー画像のローカルパス。単数キーと複数キーの両方を受ける。 */
|
|
338
|
+
function bytedanceCoverPaths(ad) {
|
|
339
|
+
if (Array.isArray(ad?.coverImagePaths))
|
|
340
|
+
return ad.coverImagePaths.map(String);
|
|
341
|
+
return ad?.coverImagePath ? [String(ad.coverImagePath)] : [];
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* TikTok の動画をアセットライブラリへアップロードして `videoId` に解決する。
|
|
345
|
+
*
|
|
346
|
+
* Google は「API に動画を上げる手段が無いので YouTube 動画IDを人が用意する」という
|
|
347
|
+
* 例外だったが、TikTok は `/file/video/ad/upload/` で上げられるので Meta / YDA と
|
|
348
|
+
* 同じ「CLI がローカルファイルを上げて ID に変える」型にする。
|
|
349
|
+
*
|
|
350
|
+
* 解決済み (`videoId` がある) 行は素通りする = 再開時に再アップロードしない。
|
|
351
|
+
*/
|
|
352
|
+
async function resolveBytedanceVideos(client, body, jsonFile, log) {
|
|
353
|
+
const baseDir = path_1.default.dirname(path_1.default.resolve(jsonFile));
|
|
354
|
+
const advertiserId = String(body?.advertiserId ?? '');
|
|
355
|
+
for (const cp of body?.campaigns ?? []) {
|
|
356
|
+
for (const ag of cp?.adGroups ?? []) {
|
|
357
|
+
for (const ad of ag?.ads ?? []) {
|
|
358
|
+
const videoPath = bytedanceVideoPath(ad);
|
|
359
|
+
if (!videoPath)
|
|
360
|
+
continue;
|
|
361
|
+
const abs = (0, localPath_1.resolveLocalMediaPath)(videoPath, baseDir, 'video');
|
|
362
|
+
const buf = fs_1.default.readFileSync(abs);
|
|
363
|
+
const fileName = path_1.default.basename(abs);
|
|
364
|
+
const form = new FormData();
|
|
365
|
+
form.append('accountId', advertiserId);
|
|
366
|
+
form.append('video', new Blob([buf]), fileName);
|
|
367
|
+
let res;
|
|
368
|
+
try {
|
|
369
|
+
// 動画アップロードは処理完了までブロックするため既定 15 秒では足りない
|
|
370
|
+
res = await client.post('/api/submission/bytedance/upload-video', form, { timeout: 180000 });
|
|
371
|
+
}
|
|
372
|
+
catch (e) {
|
|
373
|
+
throw new Error(`upload-video 失敗 (${fileName}): ${describeUploadError(e, buf.length)}`);
|
|
374
|
+
}
|
|
375
|
+
const videoId = res.data?.video_id;
|
|
376
|
+
if (!videoId)
|
|
377
|
+
throw new Error(`upload-video が video_id を返しませんでした (${fileName})`);
|
|
378
|
+
ad.videoId = String(videoId);
|
|
379
|
+
delete ad.videoPath;
|
|
380
|
+
log(`[cli/submit-plan] uploaded video ${fileName} → ${ad.videoId}`);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* TikTok のカバー画像をアップロードして `imageIds` に解決する。
|
|
387
|
+
* SINGLE_VIDEO では カバー画像が必須なので、動画だけ上げても広告は作れない。
|
|
388
|
+
*/
|
|
389
|
+
async function resolveBytedanceImages(client, body, jsonFile, log) {
|
|
390
|
+
const baseDir = path_1.default.dirname(path_1.default.resolve(jsonFile));
|
|
391
|
+
const advertiserId = String(body?.advertiserId ?? '');
|
|
392
|
+
for (const cp of body?.campaigns ?? []) {
|
|
393
|
+
for (const ag of cp?.adGroups ?? []) {
|
|
394
|
+
for (const ad of ag?.ads ?? []) {
|
|
395
|
+
const paths = bytedanceCoverPaths(ad);
|
|
396
|
+
if (paths.length === 0)
|
|
397
|
+
continue;
|
|
398
|
+
const uploaded = [];
|
|
399
|
+
for (const rel of paths) {
|
|
400
|
+
const abs = (0, localPath_1.resolveLocalMediaPath)(rel, baseDir, 'image');
|
|
401
|
+
const buf = fs_1.default.readFileSync(abs);
|
|
402
|
+
const fileName = path_1.default.basename(abs);
|
|
403
|
+
const form = new FormData();
|
|
404
|
+
form.append('accountId', advertiserId);
|
|
405
|
+
form.append('image', new Blob([buf]), fileName);
|
|
406
|
+
let res;
|
|
407
|
+
try {
|
|
408
|
+
res = await client.post('/api/submission/bytedance/upload-image', form, { timeout: 120000 });
|
|
409
|
+
}
|
|
410
|
+
catch (e) {
|
|
411
|
+
throw new Error(`upload-image 失敗 (${fileName}): ${describeUploadError(e, buf.length)}`);
|
|
412
|
+
}
|
|
413
|
+
const imageId = res.data?.image_id;
|
|
414
|
+
if (!imageId)
|
|
415
|
+
throw new Error(`upload-image が image_id を返しませんでした (${fileName})`);
|
|
416
|
+
uploaded.push(String(imageId));
|
|
417
|
+
log(`[cli/submit-plan] uploaded image ${fileName} → ${imageId}`);
|
|
418
|
+
}
|
|
419
|
+
ad.imageIds = [...(Array.isArray(ad.imageIds) ? ad.imageIds : []), ...uploaded];
|
|
420
|
+
delete ad.coverImagePath;
|
|
421
|
+
delete ad.coverImagePaths;
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
}
|
|
323
426
|
/**
|
|
324
427
|
* 入稿ボディから、実際にアップロード対象になるローカルパスだけを集める。
|
|
325
428
|
* 既に ID / hash が解決済みの行 (再開時) は resolver 側が素通りするので集めない。
|
|
@@ -360,6 +463,20 @@ function collectMediaRefs(platform, body) {
|
|
|
360
463
|
}
|
|
361
464
|
return refs;
|
|
362
465
|
}
|
|
466
|
+
if (platform === 'bytedance') {
|
|
467
|
+
for (const cp of body?.campaigns ?? []) {
|
|
468
|
+
for (const ag of cp?.adGroups ?? []) {
|
|
469
|
+
for (const ad of ag?.ads ?? []) {
|
|
470
|
+
const videoPath = bytedanceVideoPath(ad);
|
|
471
|
+
if (videoPath)
|
|
472
|
+
refs.push({ value: videoPath, kind: 'video' });
|
|
473
|
+
for (const p of bytedanceCoverPaths(ad))
|
|
474
|
+
refs.push({ value: p, kind: 'image' });
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
return refs;
|
|
479
|
+
}
|
|
363
480
|
if (platform === 'google') {
|
|
364
481
|
const pathFields = ['marketingImagePaths', 'squareMarketingImagePaths', 'logoImagePaths'];
|
|
365
482
|
for (const cp of body?.campaigns ?? []) {
|
|
@@ -503,6 +620,22 @@ async function runSubmit(input) {
|
|
|
503
620
|
return { ok: false, exitCode: 1, error: `image_upload_error: ${e.message}` };
|
|
504
621
|
}
|
|
505
622
|
}
|
|
623
|
+
// 本実行時のみ: TikTok の動画 (videoPath) とカバー画像 (coverImagePath) を先に
|
|
624
|
+
// アップロードして videoId / imageIds へ解決。dry-run では実 API を叩かない
|
|
625
|
+
if (input.platform === 'bytedance' && !input.dryRun) {
|
|
626
|
+
try {
|
|
627
|
+
await resolveBytedanceVideos(client, body, input.jsonFile, log);
|
|
628
|
+
}
|
|
629
|
+
catch (e) {
|
|
630
|
+
return { ok: false, exitCode: 1, error: `video_upload_error: ${e.message}` };
|
|
631
|
+
}
|
|
632
|
+
try {
|
|
633
|
+
await resolveBytedanceImages(client, body, input.jsonFile, log);
|
|
634
|
+
}
|
|
635
|
+
catch (e) {
|
|
636
|
+
return { ok: false, exitCode: 1, error: `image_upload_error: ${e.message}` };
|
|
637
|
+
}
|
|
638
|
+
}
|
|
506
639
|
// POST 実行
|
|
507
640
|
try {
|
|
508
641
|
const params = {};
|
|
@@ -513,6 +646,24 @@ async function runSubmit(input) {
|
|
|
513
646
|
params,
|
|
514
647
|
...(input.dryRun ? {} : { timeout: apiClient_1.SUBMIT_TIMEOUT_MS }),
|
|
515
648
|
});
|
|
649
|
+
/**
|
|
650
|
+
* backend は「広告が全滅した」を **HTTP 200 + success:false** で返す
|
|
651
|
+
* (yda / meta / google 共通)。HTTP ステータスだけを見ると全滅を成功として
|
|
652
|
+
* exit 0 で終えてしまい、スクリプトから使ったときに失敗が伝わらない。
|
|
653
|
+
*/
|
|
654
|
+
if (res.data?.success === false) {
|
|
655
|
+
const detail = Array.isArray(res.data?.adErrors) && res.data.adErrors.length > 0
|
|
656
|
+
? res.data.adErrors.slice(0, 5).join(' / ')
|
|
657
|
+
: '詳細なし';
|
|
658
|
+
const stats = res.data?.adStats;
|
|
659
|
+
const counts = stats ? ` (広告 ${stats.created}/${stats.total} 件成功)` : '';
|
|
660
|
+
return {
|
|
661
|
+
ok: false,
|
|
662
|
+
exitCode: 1,
|
|
663
|
+
data: res.data,
|
|
664
|
+
error: `入稿に失敗しました${counts}: ${detail}`,
|
|
665
|
+
};
|
|
666
|
+
}
|
|
516
667
|
return { ok: true, exitCode: 0, data: res.data };
|
|
517
668
|
}
|
|
518
669
|
catch (err) {
|
|
@@ -532,11 +683,39 @@ async function runSubmit(input) {
|
|
|
532
683
|
}
|
|
533
684
|
}
|
|
534
685
|
/** submit-plan が対応する媒体 (プランナーがスキーマ駆動で書き出せる媒体)。 */
|
|
535
|
-
exports.SUBMIT_PLAN_PLATFORMS = ['yda', 'meta'];
|
|
536
|
-
/**
|
|
686
|
+
exports.SUBMIT_PLAN_PLATFORMS = ['yda', 'meta', 'google', 'bytedance'];
|
|
687
|
+
/**
|
|
688
|
+
* submit-plan の本実行に対応している媒体。
|
|
689
|
+
* SUBMIT_PLAN_PLATFORMS に入っていても、ここに無い媒体は dry-run のみ許可する。
|
|
690
|
+
*/
|
|
691
|
+
exports.SUBMIT_PLAN_LIVE_PLATFORMS = ['yda', 'meta', 'google', 'bytedance'];
|
|
692
|
+
/**
|
|
693
|
+
* 1 キャンペーン分の入稿ボディを媒体別に組み立てる。アカウントIDのキー名が媒体で違う。
|
|
694
|
+
* - yda / meta / smartnews : accountId
|
|
695
|
+
* - google : customerId
|
|
696
|
+
* - bytedance : advertiserId
|
|
697
|
+
*/
|
|
698
|
+
function buildSubmitPlanBody(platform, accountId, campaigns) {
|
|
699
|
+
if (platform === 'google')
|
|
700
|
+
return { customerId: accountId, campaigns };
|
|
701
|
+
if (platform === 'bytedance')
|
|
702
|
+
return { advertiserId: accountId, campaigns };
|
|
703
|
+
return { accountId, campaigns };
|
|
704
|
+
}
|
|
705
|
+
/**
|
|
706
|
+
* キャンペーン応答から識別子を取り出して進捗ファイルに残す。
|
|
707
|
+
*
|
|
708
|
+
* yda / meta は `campaignId`、**google は `campaignResourceName`**
|
|
709
|
+
* (`customers/{cid}/campaigns/{id}`) を返す。google を campaignId だけで見ると常に
|
|
710
|
+
* null になり、ロールバックが失敗して本番に残った CP を手で消すための識別子が消える。
|
|
711
|
+
*/
|
|
537
712
|
function extractCampaignId(data) {
|
|
538
713
|
const r = data?.results?.[0];
|
|
539
|
-
|
|
714
|
+
if (r?.campaignId)
|
|
715
|
+
return String(r.campaignId);
|
|
716
|
+
if (r?.campaignResourceName)
|
|
717
|
+
return String(r.campaignResourceName);
|
|
718
|
+
return null;
|
|
540
719
|
}
|
|
541
720
|
/**
|
|
542
721
|
* 入稿応答からターゲティング設定の失敗を集める (yda submit の results[].adGroups[].targetErrors)。
|
|
@@ -569,8 +748,9 @@ function persistPlan(planFile, plan) {
|
|
|
569
748
|
* 3. 本実行: progress.status==='submitted' のキャンペーンはスキップ (再開・二重入稿防止)。
|
|
570
749
|
* 1件ごとに画像/動画を解決 → POST → progress を計画ファイルへ書き戻して永続化。
|
|
571
750
|
*
|
|
572
|
-
* 対応 platform は SUBMIT_PLAN_PLATFORMS (yda / meta)。plan.platform 未記載は yda 扱い。
|
|
573
|
-
*
|
|
751
|
+
* 対応 platform は SUBMIT_PLAN_PLATFORMS (yda / meta / google)。plan.platform 未記載は yda 扱い。
|
|
752
|
+
* 本実行が許されるのは SUBMIT_PLAN_LIVE_PLATFORMS のみ。
|
|
753
|
+
* campaigns[] の中身 (yda=adGroups / meta=adSets / google=adGroups) は媒体スキーマそのままで透過する。
|
|
574
754
|
*/
|
|
575
755
|
async function runSubmitPlan(input) {
|
|
576
756
|
const log = input.logger ?? ((line) => console.error(line));
|
|
@@ -599,6 +779,13 @@ async function runSubmitPlan(input) {
|
|
|
599
779
|
const plan = parsed.plan;
|
|
600
780
|
// platform 未記載は yda 扱い (platform 導入前に書き出された計画ファイルとの後方互換)
|
|
601
781
|
const platform = (plan.platform ?? 'yda');
|
|
782
|
+
if (!input.dryRun && !exports.SUBMIT_PLAN_LIVE_PLATFORMS.includes(platform)) {
|
|
783
|
+
return {
|
|
784
|
+
ok: false,
|
|
785
|
+
exitCode: 1,
|
|
786
|
+
error: `${platform} の submit-plan は dry-run のみ対応です (本実行は ${exports.SUBMIT_PLAN_LIVE_PLATFORMS.join(' / ')} のみ)`,
|
|
787
|
+
};
|
|
788
|
+
}
|
|
602
789
|
// progress 初期化 (無ければ campaigns から生成)
|
|
603
790
|
if (!Array.isArray(plan.progress) || plan.progress.length !== plan.campaigns.length) {
|
|
604
791
|
plan.progress = plan.campaigns.map((cp, i) => ({
|
|
@@ -616,7 +803,7 @@ async function runSubmitPlan(input) {
|
|
|
616
803
|
for (let i = 0; i < plan.campaigns.length; i++) {
|
|
617
804
|
if (['submitted', 'unknown'].includes(plan.progress[i]?.status))
|
|
618
805
|
continue;
|
|
619
|
-
pathErrors.push(...validateLocalMediaPaths(platform,
|
|
806
|
+
pathErrors.push(...validateLocalMediaPaths(platform, buildSubmitPlanBody(platform, plan.accountId, [plan.campaigns[i]]), input.planFile));
|
|
620
807
|
}
|
|
621
808
|
if (pathErrors.length > 0) {
|
|
622
809
|
return { ok: false, exitCode: 1, error: `image_path_error: ${pathErrors.join(' / ')}` };
|
|
@@ -640,14 +827,29 @@ async function runSubmitPlan(input) {
|
|
|
640
827
|
await resolveMetaImages(client, singleBody, input.planFile, log);
|
|
641
828
|
return;
|
|
642
829
|
}
|
|
643
|
-
|
|
644
|
-
|
|
830
|
+
if (platform === 'yda') {
|
|
831
|
+
await resolveYdaVideos(client, singleBody, input.planFile, log);
|
|
832
|
+
await resolveYdaImages(client, singleBody, input.planFile, log);
|
|
833
|
+
return;
|
|
834
|
+
}
|
|
835
|
+
if (platform === 'google') {
|
|
836
|
+
// 動画は YouTube 動画ID を人間が用意する前提のためアップロード対象は画像だけ。
|
|
837
|
+
await resolveGoogleImages(client, singleBody, input.planFile, log);
|
|
838
|
+
return;
|
|
839
|
+
}
|
|
840
|
+
if (platform === 'bytedance') {
|
|
841
|
+
await resolveBytedanceVideos(client, singleBody, input.planFile, log);
|
|
842
|
+
await resolveBytedanceImages(client, singleBody, input.planFile, log);
|
|
843
|
+
return;
|
|
844
|
+
}
|
|
845
|
+
// 媒体を増やしたとき、未対応の媒体が YDA の upload API へ流れ込む事故を防ぐ。
|
|
846
|
+
throw new Error(`${platform} のメディア解決は未実装です`);
|
|
645
847
|
};
|
|
646
848
|
// ---- dry-run: 全キャンペーンを検証 (実 API を叩かない) ----
|
|
647
849
|
if (input.dryRun) {
|
|
648
850
|
const checks = [];
|
|
649
851
|
for (const cp of plan.campaigns) {
|
|
650
|
-
const singleBody =
|
|
852
|
+
const singleBody = buildSubmitPlanBody(platform, plan.accountId, [cp]);
|
|
651
853
|
try {
|
|
652
854
|
await client.post(submitPath, singleBody, { params: { dry_run: '1' } });
|
|
653
855
|
checks.push({ name: String(cp?.name ?? ''), ok: true });
|
|
@@ -675,7 +877,7 @@ async function runSubmitPlan(input) {
|
|
|
675
877
|
continue;
|
|
676
878
|
}
|
|
677
879
|
const cp = plan.campaigns[i];
|
|
678
|
-
const singleBody =
|
|
880
|
+
const singleBody = buildSubmitPlanBody(platform, plan.accountId, [cp]);
|
|
679
881
|
// 画像/動画を解決 (YDA: imagePaths→mediaIds / videoPath→videoMediaId,
|
|
680
882
|
// Meta: imagePaths→imageHash / videoPath→videoId)
|
|
681
883
|
try {
|
|
@@ -705,10 +907,18 @@ async function runSubmitPlan(input) {
|
|
|
705
907
|
prog.status = 'failed';
|
|
706
908
|
prog.error = stats ? `広告作成失敗 (${stats.failed}/${stats.total}件): ${detail}` : detail;
|
|
707
909
|
prog.submittedAt = nowIso;
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
910
|
+
/**
|
|
911
|
+
* backend が作りかけの CP をどう片付けたか。
|
|
912
|
+
*
|
|
913
|
+
* **「消えた」と「停止で残った」を区別する。** TikTok は DELETE が拒否されると
|
|
914
|
+
* DISABLE にフォールバックするため、混同すると *手動削除が必要な CP を
|
|
915
|
+
* 「削除済み」と表示して見失う*。消えたときだけ campaignId を捨てる
|
|
916
|
+
* (残すと「途中まで入稿済み」と誤読される)。
|
|
917
|
+
*/
|
|
918
|
+
const cps = (res.data?.results ?? []);
|
|
919
|
+
const rolledBack = cps.some((r) => r?.rolledBack === true);
|
|
920
|
+
const disabledLeft = cps.filter((r) => r?.rollbackMode === 'disabled');
|
|
921
|
+
const rollbackFailed = cps.filter((r) => r?.rollbackError);
|
|
712
922
|
if (rolledBack)
|
|
713
923
|
prog.campaignId = null;
|
|
714
924
|
persistPlan(input.planFile, plan);
|
|
@@ -716,8 +926,14 @@ async function runSubmitPlan(input) {
|
|
|
716
926
|
if (rolledBack) {
|
|
717
927
|
log(`[cli/submit-plan] ↩ ${prog.name}: 広告が作れなかったため作りかけのキャンペーンを削除しました`);
|
|
718
928
|
}
|
|
929
|
+
for (const r of disabledLeft) {
|
|
930
|
+
log(`[cli/submit-plan] ⚠ ${prog.name}: 作りかけの CP を削除できず停止しました (campaignId=${r.campaignId}) — 管理画面で削除してください`);
|
|
931
|
+
}
|
|
719
932
|
for (const r of rollbackFailed) {
|
|
720
|
-
|
|
933
|
+
// 識別子のキーは媒体で違う (yda/meta=campaignId / google=campaignResourceName)。
|
|
934
|
+
// 片方だけ見ると undefined になり、手で消すべき CP を特定できない。
|
|
935
|
+
const cpRef = r.campaignId ?? r.campaignResourceName ?? '不明';
|
|
936
|
+
log(`[cli/submit-plan] ⚠ ${prog.name}: 作りかけの CP を削除できませんでした (${cpRef}) — 管理画面で削除してください: ${r.rollbackError}`);
|
|
721
937
|
}
|
|
722
938
|
}
|
|
723
939
|
else {
|