siluzan-tso-cli 1.0.0-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/assets/siluzan-ads/SKILL.md +774 -0
- package/dist/commands/account-history.d.ts +13 -0
- package/dist/commands/account-history.js +87 -0
- package/dist/commands/account-manage.d.ts +71 -0
- package/dist/commands/account-manage.js +217 -0
- package/dist/commands/ad.d.ts +161 -0
- package/dist/commands/ad.js +486 -0
- package/dist/commands/ai-creation.d.ts +54 -0
- package/dist/commands/ai-creation.js +184 -0
- package/dist/commands/balance.d.ts +9 -0
- package/dist/commands/balance.js +68 -0
- package/dist/commands/clue.d.ts +16 -0
- package/dist/commands/clue.js +103 -0
- package/dist/commands/config.d.ts +17 -0
- package/dist/commands/config.js +122 -0
- package/dist/commands/forewarning.d.ts +78 -0
- package/dist/commands/forewarning.js +239 -0
- package/dist/commands/init.d.ts +10 -0
- package/dist/commands/init.js +141 -0
- package/dist/commands/invoice.d.ts +38 -0
- package/dist/commands/invoice.js +187 -0
- package/dist/commands/keyword.d.ts +14 -0
- package/dist/commands/keyword.js +125 -0
- package/dist/commands/list-accounts.d.ts +11 -0
- package/dist/commands/list-accounts.js +83 -0
- package/dist/commands/open-account.d.ts +205 -0
- package/dist/commands/open-account.js +456 -0
- package/dist/commands/optimize.d.ts +39 -0
- package/dist/commands/optimize.js +143 -0
- package/dist/commands/report.d.ts +55 -0
- package/dist/commands/report.js +274 -0
- package/dist/commands/stats.d.ts +13 -0
- package/dist/commands/stats.js +109 -0
- package/dist/commands/transfer.d.ts +13 -0
- package/dist/commands/transfer.js +52 -0
- package/dist/config/defaults.d.ts +6 -0
- package/dist/config/defaults.js +11 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1161 -0
- package/dist/templates/load-templates.d.ts +4 -0
- package/dist/templates/load-templates.js +24 -0
- package/dist/types/ads.d.ts +126 -0
- package/dist/types/ads.js +4 -0
- package/dist/utils/auth.d.ts +37 -0
- package/dist/utils/auth.js +203 -0
- package/package.json +48 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface AccountHistoryOptions {
|
|
2
|
+
token?: string;
|
|
3
|
+
media?: string;
|
|
4
|
+
status?: string;
|
|
5
|
+
keyword?: string;
|
|
6
|
+
startDate?: string;
|
|
7
|
+
endDate?: string;
|
|
8
|
+
page?: number;
|
|
9
|
+
pageSize?: number;
|
|
10
|
+
json?: boolean;
|
|
11
|
+
verbose?: boolean;
|
|
12
|
+
}
|
|
13
|
+
export declare function runAccountHistory(opts: AccountHistoryOptions): Promise<void>;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { loadConfig, apiFetch } from "../utils/auth.js";
|
|
2
|
+
const VALID_MEDIA_TYPES = ["Google", "TikTok", "Yandex", "MetaAd", "BingV2", "Kwai"];
|
|
3
|
+
export async function runAccountHistory(opts) {
|
|
4
|
+
const config = loadConfig(opts.token);
|
|
5
|
+
if (opts.media && !VALID_MEDIA_TYPES.includes(opts.media)) {
|
|
6
|
+
console.error(`\n❌ 不支持的媒体类型:${opts.media}\n 可选:${VALID_MEDIA_TYPES.join(" | ")}\n`);
|
|
7
|
+
process.exit(1);
|
|
8
|
+
}
|
|
9
|
+
const params = new URLSearchParams();
|
|
10
|
+
if (opts.media)
|
|
11
|
+
params.set("MediaType", opts.media);
|
|
12
|
+
if (opts.keyword)
|
|
13
|
+
params.set("keyword", opts.keyword);
|
|
14
|
+
if (opts.startDate)
|
|
15
|
+
params.set("startDate", opts.startDate);
|
|
16
|
+
if (opts.endDate)
|
|
17
|
+
params.set("endDate", opts.endDate);
|
|
18
|
+
params.set("pageIndex", String((opts.page ?? 1) - 1));
|
|
19
|
+
params.set("pageSize", String(opts.pageSize ?? 20));
|
|
20
|
+
// 开户历史复用账户列表接口,通过 mediaAccountState 筛选历史状态
|
|
21
|
+
// Google 包含 Inactive,其他媒体不含
|
|
22
|
+
const historyStates = !opts.media || opts.media === "Google"
|
|
23
|
+
? "Created,Approved,Denied,Inactive"
|
|
24
|
+
: "Created,Approved,Denied";
|
|
25
|
+
// 若调用方传了 status 参数则优先使用(向后兼容)
|
|
26
|
+
params.set("mediaAccountState", opts.status ?? historyStates);
|
|
27
|
+
const url = `${config.apiBaseUrl}/query/media-account/?${params}`;
|
|
28
|
+
// 接口直接返回 MediaAccountItem[] 数组,与 list-accounts 结构一致
|
|
29
|
+
let items;
|
|
30
|
+
try {
|
|
31
|
+
items = await apiFetch(url, config, {}, opts.verbose);
|
|
32
|
+
}
|
|
33
|
+
catch (err) {
|
|
34
|
+
console.error(`\n❌ 查询失败:${err instanceof Error ? err.message : String(err)}\n`);
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
if (opts.json) {
|
|
38
|
+
console.log(JSON.stringify(items, null, 2));
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
if (items.length === 0) {
|
|
42
|
+
console.log("\n暂无开户申请记录。\n");
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
const page = opts.page ?? 1;
|
|
46
|
+
const pageSize = opts.pageSize ?? 20;
|
|
47
|
+
console.log(`\n开户申请历史(第 ${page} 页,本页 ${items.length} 条)\n`);
|
|
48
|
+
// 从 ma / mag 提取展示字段
|
|
49
|
+
const rows = items.map((item) => ({
|
|
50
|
+
mediaType: item.ma.mediaAccountType ?? "",
|
|
51
|
+
mediaCustomerId: item.ma.mediaCustomerId ?? "(未开通)",
|
|
52
|
+
advertiserName: item.mag?.advertiserName ?? "",
|
|
53
|
+
status: item.ma.mediaAccountState ?? "",
|
|
54
|
+
createdAt: item.ma.createdDateTime
|
|
55
|
+
? item.ma.createdDateTime.slice(0, 10)
|
|
56
|
+
: "",
|
|
57
|
+
}));
|
|
58
|
+
const colW = {
|
|
59
|
+
media: Math.max(8, ...rows.map((r) => r.mediaType.length)),
|
|
60
|
+
id: Math.max(10, ...rows.map((r) => r.mediaCustomerId.length)),
|
|
61
|
+
company: Math.max(8, ...rows.map((r) => r.advertiserName.length)),
|
|
62
|
+
status: Math.max(6, ...rows.map((r) => r.status.length)),
|
|
63
|
+
};
|
|
64
|
+
const header = [
|
|
65
|
+
"媒体类型".padEnd(colW.media),
|
|
66
|
+
"账户ID".padEnd(colW.id),
|
|
67
|
+
"广告主".padEnd(colW.company),
|
|
68
|
+
"状态".padEnd(colW.status),
|
|
69
|
+
"创建日期",
|
|
70
|
+
].join(" ");
|
|
71
|
+
console.log(" " + header);
|
|
72
|
+
console.log(" " + "-".repeat(header.length));
|
|
73
|
+
for (const row of rows) {
|
|
74
|
+
const line = [
|
|
75
|
+
row.mediaType.padEnd(colW.media),
|
|
76
|
+
row.mediaCustomerId.padEnd(colW.id),
|
|
77
|
+
row.advertiserName.padEnd(colW.company),
|
|
78
|
+
row.status.padEnd(colW.status),
|
|
79
|
+
row.createdAt,
|
|
80
|
+
].join(" ");
|
|
81
|
+
console.log(" " + line);
|
|
82
|
+
}
|
|
83
|
+
if (items.length >= pageSize) {
|
|
84
|
+
console.log(`\n 使用 --page <n> 翻页,--page-size <n> 调整每页数量。`);
|
|
85
|
+
}
|
|
86
|
+
console.log();
|
|
87
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
export interface AccountDelinkOptions {
|
|
2
|
+
token?: string;
|
|
3
|
+
/** 单个账户 entityId */
|
|
4
|
+
id?: string;
|
|
5
|
+
/** 批量账户 entityId(逗号分隔) */
|
|
6
|
+
ids?: string[];
|
|
7
|
+
verbose?: boolean;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* 断开广告账户与当前账号的关联关系。
|
|
11
|
+
* 单个:PUT /command/media-account/{entityId}/delink
|
|
12
|
+
* 批量:POST /command/media-account/delinkList
|
|
13
|
+
*/
|
|
14
|
+
export declare function runAccountDelink(opts: AccountDelinkOptions): Promise<void>;
|
|
15
|
+
export interface AccountShareOptions {
|
|
16
|
+
token?: string;
|
|
17
|
+
/** 要分享的账户 entityId(来自 list-accounts 的 ma.entityId 字段) */
|
|
18
|
+
id: string;
|
|
19
|
+
/** 被分享人的丝路赞注册手机号 */
|
|
20
|
+
phone: string;
|
|
21
|
+
verbose?: boolean;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* 将 Google 广告账户分享给指定手机号的丝路赞用户。
|
|
25
|
+
*
|
|
26
|
+
* 流程:
|
|
27
|
+
* 1. GET mainApiUrl/query/account/SimpleAccountInfo?phone=xxx → 获取被分享人 accountId
|
|
28
|
+
* 2. POST /command/media-account/TSOMediaAccountShared/{entityId} { AccountId: userId }
|
|
29
|
+
*/
|
|
30
|
+
export declare function runAccountShare(opts: AccountShareOptions): Promise<void>;
|
|
31
|
+
export interface AccountUnshareOptions {
|
|
32
|
+
token?: string;
|
|
33
|
+
/** 被分享的账户 entityId */
|
|
34
|
+
id: string;
|
|
35
|
+
/** 被分享人的账号 ID(通过 list-accounts → accountIds 或分享详情页获取) */
|
|
36
|
+
accountId: string;
|
|
37
|
+
verbose?: boolean;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* 取消 Google 广告账户的分享。
|
|
41
|
+
* POST /command/media-account/TSOMediaAccountSharedRemove/{entityId}
|
|
42
|
+
* Body: { AccountId: "<被分享人的 accountId>" }
|
|
43
|
+
*/
|
|
44
|
+
export declare function runAccountUnshare(opts: AccountUnshareOptions): Promise<void>;
|
|
45
|
+
export interface AccountShareDetailOptions {
|
|
46
|
+
token?: string;
|
|
47
|
+
/** 账户的 mediaCustomerId(来自 list-accounts) */
|
|
48
|
+
mediaCustomerId: string;
|
|
49
|
+
json?: boolean;
|
|
50
|
+
verbose?: boolean;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* 查询指定账户的分享详情(被分享给哪些用户)。
|
|
54
|
+
* GET mainApiUrl/query/account/GetAccountsByMediaAcountId?mediaAcountId=xxx
|
|
55
|
+
*/
|
|
56
|
+
export declare function runAccountShareDetail(opts: AccountShareDetailOptions): Promise<void>;
|
|
57
|
+
export interface AccountAuthOptions {
|
|
58
|
+
token?: string;
|
|
59
|
+
/** Google | TikTok | Meta | Yandex | BingV2 | Kwai */
|
|
60
|
+
media: string;
|
|
61
|
+
verbose?: boolean;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* 发起媒体账户 OAuth 授权。
|
|
65
|
+
*
|
|
66
|
+
* 流程:
|
|
67
|
+
* 1. 调 GET /command/media-account/link/{mediaType}?returnUrl=... 获取 redirectUrl
|
|
68
|
+
* 2. 用系统浏览器打开 redirectUrl(fallback 输出链接让用户手动粘贴)
|
|
69
|
+
* 3. 用户在浏览器完成授权后会跳回丝路赞回调页面,自动完成账户绑定
|
|
70
|
+
*/
|
|
71
|
+
export declare function runAccountAuth(opts: AccountAuthOptions): Promise<void>;
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { execSync } from "node:child_process";
|
|
2
|
+
import { loadConfig, apiFetch } from "../utils/auth.js";
|
|
3
|
+
import { deriveWebUrl } from "./config.js";
|
|
4
|
+
/**
|
|
5
|
+
* 断开广告账户与当前账号的关联关系。
|
|
6
|
+
* 单个:PUT /command/media-account/{entityId}/delink
|
|
7
|
+
* 批量:POST /command/media-account/delinkList
|
|
8
|
+
*/
|
|
9
|
+
export async function runAccountDelink(opts) {
|
|
10
|
+
if (!opts.id && (!opts.ids || opts.ids.length === 0)) {
|
|
11
|
+
console.error("\n❌ 请通过 --id 或 --ids 提供要断开关联的账户 entityId\n");
|
|
12
|
+
process.exit(1);
|
|
13
|
+
}
|
|
14
|
+
const config = loadConfig(opts.token);
|
|
15
|
+
// S-Command-Type 是前端固定传的操作类型头
|
|
16
|
+
const delinkHeader = { "S-Command-Type": "delinkMediaAccount" };
|
|
17
|
+
if (opts.ids && opts.ids.length > 1) {
|
|
18
|
+
// 批量断开
|
|
19
|
+
const url = `${config.apiBaseUrl}/command/media-account/delinkList`;
|
|
20
|
+
try {
|
|
21
|
+
await apiFetch(url, config, {
|
|
22
|
+
method: "POST",
|
|
23
|
+
body: JSON.stringify(opts.ids),
|
|
24
|
+
headers: delinkHeader,
|
|
25
|
+
}, opts.verbose);
|
|
26
|
+
}
|
|
27
|
+
catch (err) {
|
|
28
|
+
console.error(`\n❌ 批量断开关联失败:${err instanceof Error ? err.message : String(err)}\n`);
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
console.log(`\n✅ 已批量断开 ${opts.ids.length} 个账户的关联关系\n`);
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
// 单个断开
|
|
35
|
+
const entityId = opts.id ?? opts.ids[0];
|
|
36
|
+
const url = `${config.apiBaseUrl}/command/media-account/${entityId}/delink`;
|
|
37
|
+
try {
|
|
38
|
+
await apiFetch(url, config, { method: "PUT", body: "{}", headers: delinkHeader }, opts.verbose);
|
|
39
|
+
}
|
|
40
|
+
catch (err) {
|
|
41
|
+
console.error(`\n❌ 断开关联失败:${err instanceof Error ? err.message : String(err)}\n`);
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
console.log(`\n✅ 账户 ${entityId} 已断开关联\n`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* 将 Google 广告账户分享给指定手机号的丝路赞用户。
|
|
49
|
+
*
|
|
50
|
+
* 流程:
|
|
51
|
+
* 1. GET mainApiUrl/query/account/SimpleAccountInfo?phone=xxx → 获取被分享人 accountId
|
|
52
|
+
* 2. POST /command/media-account/TSOMediaAccountShared/{entityId} { AccountId: userId }
|
|
53
|
+
*/
|
|
54
|
+
export async function runAccountShare(opts) {
|
|
55
|
+
const config = loadConfig(opts.token);
|
|
56
|
+
if (!config.mainApiUrl) {
|
|
57
|
+
console.error("\n❌ 无法推导主 API 地址,请检查 apiBaseUrl 配置\n");
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
// 第一步:用手机号查询目标用户 ID
|
|
61
|
+
let userId;
|
|
62
|
+
try {
|
|
63
|
+
const lookupUrl = `${config.mainApiUrl}/query/account/SimpleAccountInfo?phone=${encodeURIComponent(opts.phone)}`;
|
|
64
|
+
const users = await apiFetch(lookupUrl, config, {}, opts.verbose);
|
|
65
|
+
if (!Array.isArray(users) || users.length === 0) {
|
|
66
|
+
console.error(`\n❌ 未找到手机号 ${opts.phone} 对应的丝路赞账号,请确认该用户已完成注册\n`);
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
userId = users[0].id;
|
|
70
|
+
}
|
|
71
|
+
catch (err) {
|
|
72
|
+
console.error(`\n❌ 查询用户信息失败:${err instanceof Error ? err.message : String(err)}\n`);
|
|
73
|
+
process.exit(1);
|
|
74
|
+
}
|
|
75
|
+
// 第二步:执行分享
|
|
76
|
+
const shareUrl = `${config.apiBaseUrl}/command/media-account/TSOMediaAccountShared/${opts.id}`;
|
|
77
|
+
try {
|
|
78
|
+
await apiFetch(shareUrl, config, { method: "POST", body: JSON.stringify({ AccountId: userId }) }, opts.verbose);
|
|
79
|
+
}
|
|
80
|
+
catch (err) {
|
|
81
|
+
console.error(`\n❌ 分享失败:${err instanceof Error ? err.message : String(err)}\n`);
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
84
|
+
console.log(`\n✅ 账户已成功分享给手机号 ${opts.phone} 的用户(AccountId: ${userId})\n`);
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* 取消 Google 广告账户的分享。
|
|
88
|
+
* POST /command/media-account/TSOMediaAccountSharedRemove/{entityId}
|
|
89
|
+
* Body: { AccountId: "<被分享人的 accountId>" }
|
|
90
|
+
*/
|
|
91
|
+
export async function runAccountUnshare(opts) {
|
|
92
|
+
const config = loadConfig(opts.token);
|
|
93
|
+
const url = `${config.apiBaseUrl}/command/media-account/TSOMediaAccountSharedRemove/${opts.id}`;
|
|
94
|
+
try {
|
|
95
|
+
await apiFetch(url, config, { method: "POST", body: JSON.stringify({ AccountId: opts.accountId }) }, opts.verbose);
|
|
96
|
+
}
|
|
97
|
+
catch (err) {
|
|
98
|
+
console.error(`\n❌ 取消分享失败:${err instanceof Error ? err.message : String(err)}\n`);
|
|
99
|
+
process.exit(1);
|
|
100
|
+
}
|
|
101
|
+
console.log(`\n✅ 已取消账户 ${opts.id} 对用户 ${opts.accountId} 的分享\n`);
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* 查询指定账户的分享详情(被分享给哪些用户)。
|
|
105
|
+
* GET mainApiUrl/query/account/GetAccountsByMediaAcountId?mediaAcountId=xxx
|
|
106
|
+
*/
|
|
107
|
+
export async function runAccountShareDetail(opts) {
|
|
108
|
+
const config = loadConfig(opts.token);
|
|
109
|
+
if (!config.mainApiUrl) {
|
|
110
|
+
console.error("\n❌ 无法推导主 API 地址,请检查 apiBaseUrl 配置\n");
|
|
111
|
+
process.exit(1);
|
|
112
|
+
}
|
|
113
|
+
const url = `${config.mainApiUrl}/query/account/GetAccountsByMediaAcountId?mediaAcountId=${encodeURIComponent(opts.mediaCustomerId)}`;
|
|
114
|
+
let data;
|
|
115
|
+
try {
|
|
116
|
+
data = await apiFetch(url, config, {}, opts.verbose);
|
|
117
|
+
}
|
|
118
|
+
catch (err) {
|
|
119
|
+
console.error(`\n❌ 查询失败:${err instanceof Error ? err.message : String(err)}\n`);
|
|
120
|
+
process.exit(1);
|
|
121
|
+
}
|
|
122
|
+
if (opts.json) {
|
|
123
|
+
console.log(JSON.stringify(data, null, 2));
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
const users = Array.isArray(data) ? data : [];
|
|
127
|
+
console.log(`\n账户 ${opts.mediaCustomerId} 的分享详情(共 ${users.length} 人)\n`);
|
|
128
|
+
if (users.length === 0) {
|
|
129
|
+
console.log(" 暂未分享给任何用户。\n");
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
const header = [
|
|
133
|
+
"账号 ID".padEnd(36),
|
|
134
|
+
"姓名".padEnd(16),
|
|
135
|
+
"手机号".padEnd(14),
|
|
136
|
+
"邮箱",
|
|
137
|
+
].join(" ");
|
|
138
|
+
console.log(" " + header);
|
|
139
|
+
console.log(" " + "-".repeat(header.length));
|
|
140
|
+
for (const u of users) {
|
|
141
|
+
console.log(" " + [
|
|
142
|
+
(u.id ?? u.entityId ?? "").padEnd(36),
|
|
143
|
+
(u.name ?? "").padEnd(16),
|
|
144
|
+
(u.phone ?? "").padEnd(14),
|
|
145
|
+
u.email ?? "",
|
|
146
|
+
].join(" "));
|
|
147
|
+
}
|
|
148
|
+
console.log();
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Meta 在 API 调用中使用 "FacebookAds"(与其他媒体的枚举值不同)。
|
|
152
|
+
*/
|
|
153
|
+
function toApiMediaType(media) {
|
|
154
|
+
return media === "Meta" ? "FacebookAds" : media;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* 用系统默认浏览器打开 URL,跨平台(Windows/macOS/Linux)。
|
|
158
|
+
* 失败时静默,让调用方回退到打印 URL。
|
|
159
|
+
*/
|
|
160
|
+
function tryOpenBrowser(url) {
|
|
161
|
+
try {
|
|
162
|
+
if (process.platform === "win32") {
|
|
163
|
+
// Windows:start 命令需要加空字符串作为窗口标题占位
|
|
164
|
+
execSync(`start "" "${url}"`, { stdio: "ignore" });
|
|
165
|
+
}
|
|
166
|
+
else if (process.platform === "darwin") {
|
|
167
|
+
execSync(`open "${url}"`, { stdio: "ignore" });
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
execSync(`xdg-open "${url}"`, { stdio: "ignore" });
|
|
171
|
+
}
|
|
172
|
+
return true;
|
|
173
|
+
}
|
|
174
|
+
catch {
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* 发起媒体账户 OAuth 授权。
|
|
180
|
+
*
|
|
181
|
+
* 流程:
|
|
182
|
+
* 1. 调 GET /command/media-account/link/{mediaType}?returnUrl=... 获取 redirectUrl
|
|
183
|
+
* 2. 用系统浏览器打开 redirectUrl(fallback 输出链接让用户手动粘贴)
|
|
184
|
+
* 3. 用户在浏览器完成授权后会跳回丝路赞回调页面,自动完成账户绑定
|
|
185
|
+
*/
|
|
186
|
+
export async function runAccountAuth(opts) {
|
|
187
|
+
const config = loadConfig(opts.token);
|
|
188
|
+
const apiMediaType = toApiMediaType(opts.media);
|
|
189
|
+
const webUrl = deriveWebUrl(config.apiBaseUrl);
|
|
190
|
+
// returnUrl 与前端生产环境保持一致:{webUrl}/v3/foreign_trade/tso/{mediaType}/accountsAuthorizationBind
|
|
191
|
+
const returnPath = encodeURIComponent(`${webUrl}/v3/foreign_trade/tso/${apiMediaType}/accountsAuthorizationBind`);
|
|
192
|
+
const linkUrl = `${config.apiBaseUrl}/command/media-account/link/${apiMediaType}?returnUrl=${returnPath}`;
|
|
193
|
+
console.log(`\n正在获取 ${opts.media} 授权链接…\n`);
|
|
194
|
+
let redirectUrl;
|
|
195
|
+
try {
|
|
196
|
+
const res = await apiFetch(linkUrl, config, {}, opts.verbose);
|
|
197
|
+
if (!res.redirectUrl) {
|
|
198
|
+
console.error("\n❌ 接口未返回 redirectUrl,该媒体类型可能暂不支持授权。\n");
|
|
199
|
+
process.exit(1);
|
|
200
|
+
}
|
|
201
|
+
redirectUrl = res.redirectUrl;
|
|
202
|
+
}
|
|
203
|
+
catch (err) {
|
|
204
|
+
console.error(`\n❌ 获取授权链接失败:${err instanceof Error ? err.message : String(err)}\n`);
|
|
205
|
+
process.exit(1);
|
|
206
|
+
}
|
|
207
|
+
const opened = tryOpenBrowser(redirectUrl);
|
|
208
|
+
if (opened) {
|
|
209
|
+
console.log(`✅ 已在默认浏览器中打开 ${opts.media} 授权页面。`);
|
|
210
|
+
console.log(` 完成授权后浏览器会自动跳回丝路赞,账户绑定即生效。\n`);
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
console.log(`⚠️ 无法自动打开浏览器,请手动复制以下链接到浏览器中完成授权:\n`);
|
|
214
|
+
}
|
|
215
|
+
// 无论是否自动打开,都打印链接方便用户参考
|
|
216
|
+
console.log(` ${redirectUrl}\n`);
|
|
217
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
export interface AdCampaignsOptions {
|
|
2
|
+
token?: string;
|
|
3
|
+
account: string;
|
|
4
|
+
startDate?: string;
|
|
5
|
+
endDate?: string;
|
|
6
|
+
json?: boolean;
|
|
7
|
+
verbose?: boolean;
|
|
8
|
+
}
|
|
9
|
+
export declare function runAdCampaigns(opts: AdCampaignsOptions): Promise<void>;
|
|
10
|
+
export interface AdGroupsOptions {
|
|
11
|
+
token?: string;
|
|
12
|
+
account: string;
|
|
13
|
+
startDate?: string;
|
|
14
|
+
endDate?: string;
|
|
15
|
+
json?: boolean;
|
|
16
|
+
verbose?: boolean;
|
|
17
|
+
}
|
|
18
|
+
export declare function runAdGroups(opts: AdGroupsOptions): Promise<void>;
|
|
19
|
+
export interface AdListOptions {
|
|
20
|
+
token?: string;
|
|
21
|
+
account: string;
|
|
22
|
+
startDate?: string;
|
|
23
|
+
endDate?: string;
|
|
24
|
+
json?: boolean;
|
|
25
|
+
verbose?: boolean;
|
|
26
|
+
}
|
|
27
|
+
export declare function runAdList(opts: AdListOptions): Promise<void>;
|
|
28
|
+
export interface AdKeywordsOptions {
|
|
29
|
+
token?: string;
|
|
30
|
+
account: string;
|
|
31
|
+
negative?: boolean;
|
|
32
|
+
startDate?: string;
|
|
33
|
+
endDate?: string;
|
|
34
|
+
json?: boolean;
|
|
35
|
+
verbose?: boolean;
|
|
36
|
+
}
|
|
37
|
+
export declare function runAdKeywords(opts: AdKeywordsOptions): Promise<void>;
|
|
38
|
+
export interface AdCampaignStatusOptions {
|
|
39
|
+
token?: string;
|
|
40
|
+
account: string;
|
|
41
|
+
id: string;
|
|
42
|
+
status: "Enabled" | "Paused";
|
|
43
|
+
verbose?: boolean;
|
|
44
|
+
}
|
|
45
|
+
export declare function runAdCampaignStatus(opts: AdCampaignStatusOptions): Promise<void>;
|
|
46
|
+
export interface AdCampaignDeleteOptions {
|
|
47
|
+
token?: string;
|
|
48
|
+
account: string;
|
|
49
|
+
id: string;
|
|
50
|
+
verbose?: boolean;
|
|
51
|
+
}
|
|
52
|
+
export declare function runAdCampaignDelete(opts: AdCampaignDeleteOptions): Promise<void>;
|
|
53
|
+
export interface AdGroupsVerboseOptions {
|
|
54
|
+
token?: string;
|
|
55
|
+
account: string;
|
|
56
|
+
startDate?: string;
|
|
57
|
+
endDate?: string;
|
|
58
|
+
json?: boolean;
|
|
59
|
+
verbose?: boolean;
|
|
60
|
+
}
|
|
61
|
+
export interface AdGroupCreateOptions {
|
|
62
|
+
token?: string;
|
|
63
|
+
account: string;
|
|
64
|
+
campaignId: string;
|
|
65
|
+
campaignName: string;
|
|
66
|
+
name: string;
|
|
67
|
+
/** 最高 CPC,单位:分(最小货币单位),前端乘以 100 后发送 */
|
|
68
|
+
maxCpc: number;
|
|
69
|
+
status?: "ENABLED" | "PAUSED";
|
|
70
|
+
verbose?: boolean;
|
|
71
|
+
}
|
|
72
|
+
export declare function runAdGroupCreate(opts: AdGroupCreateOptions): Promise<void>;
|
|
73
|
+
export interface AdGroupStatusOptions {
|
|
74
|
+
token?: string;
|
|
75
|
+
account: string;
|
|
76
|
+
id: string;
|
|
77
|
+
status: "Enabled" | "Paused";
|
|
78
|
+
startDate?: string;
|
|
79
|
+
endDate?: string;
|
|
80
|
+
verbose?: boolean;
|
|
81
|
+
}
|
|
82
|
+
export declare function runAdGroupStatus(opts: AdGroupStatusOptions): Promise<void>;
|
|
83
|
+
export interface AdGroupDeleteOptions {
|
|
84
|
+
token?: string;
|
|
85
|
+
account: string;
|
|
86
|
+
id: string;
|
|
87
|
+
startDate?: string;
|
|
88
|
+
endDate?: string;
|
|
89
|
+
verbose?: boolean;
|
|
90
|
+
}
|
|
91
|
+
export declare function runAdGroupDelete(opts: AdGroupDeleteOptions): Promise<void>;
|
|
92
|
+
export interface AdCreateOptions {
|
|
93
|
+
token?: string;
|
|
94
|
+
account: string;
|
|
95
|
+
adgroupId: string;
|
|
96
|
+
adgroupName: string;
|
|
97
|
+
campaignId?: string;
|
|
98
|
+
campaignName?: string;
|
|
99
|
+
finalUrl: string;
|
|
100
|
+
/** 至少 3 个标题,逗号分隔 */
|
|
101
|
+
headlines: string[];
|
|
102
|
+
/** 至少 2 个描述,逗号分隔 */
|
|
103
|
+
descriptions: string[];
|
|
104
|
+
path1?: string;
|
|
105
|
+
path2?: string;
|
|
106
|
+
verbose?: boolean;
|
|
107
|
+
}
|
|
108
|
+
export declare function runAdCreate(opts: AdCreateOptions): Promise<void>;
|
|
109
|
+
export interface AdStatusOptions {
|
|
110
|
+
token?: string;
|
|
111
|
+
account: string;
|
|
112
|
+
id: string;
|
|
113
|
+
status: "Enabled" | "Paused";
|
|
114
|
+
startDate?: string;
|
|
115
|
+
endDate?: string;
|
|
116
|
+
verbose?: boolean;
|
|
117
|
+
}
|
|
118
|
+
export declare function runAdStatus(opts: AdStatusOptions): Promise<void>;
|
|
119
|
+
export interface AdDeleteOptions {
|
|
120
|
+
token?: string;
|
|
121
|
+
account: string;
|
|
122
|
+
id: string;
|
|
123
|
+
startDate?: string;
|
|
124
|
+
endDate?: string;
|
|
125
|
+
verbose?: boolean;
|
|
126
|
+
}
|
|
127
|
+
export declare function runAdDelete(opts: AdDeleteOptions): Promise<void>;
|
|
128
|
+
export interface AdKeywordCreateOptions {
|
|
129
|
+
token?: string;
|
|
130
|
+
account: string;
|
|
131
|
+
adgroupId: string;
|
|
132
|
+
adgroupName: string;
|
|
133
|
+
campaignId: string;
|
|
134
|
+
campaignName: string;
|
|
135
|
+
/** 关键词列表,逗号分隔 */
|
|
136
|
+
keywords: string[];
|
|
137
|
+
finalUrl?: string;
|
|
138
|
+
verbose?: boolean;
|
|
139
|
+
}
|
|
140
|
+
export declare function runAdKeywordCreate(opts: AdKeywordCreateOptions): Promise<void>;
|
|
141
|
+
export interface AdNegativeKeywordCreateOptions {
|
|
142
|
+
token?: string;
|
|
143
|
+
account: string;
|
|
144
|
+
campaignId: string;
|
|
145
|
+
campaignName: string;
|
|
146
|
+
adgroupId?: string;
|
|
147
|
+
adgroupName?: string;
|
|
148
|
+
/** 关键词列表,逗号分隔 */
|
|
149
|
+
keywords: string[];
|
|
150
|
+
verbose?: boolean;
|
|
151
|
+
}
|
|
152
|
+
export declare function runAdNegativeKeywordCreate(opts: AdNegativeKeywordCreateOptions): Promise<void>;
|
|
153
|
+
export interface AdNegativeKeywordDeleteOptions {
|
|
154
|
+
token?: string;
|
|
155
|
+
account: string;
|
|
156
|
+
id: string;
|
|
157
|
+
startDate?: string;
|
|
158
|
+
endDate?: string;
|
|
159
|
+
verbose?: boolean;
|
|
160
|
+
}
|
|
161
|
+
export declare function runAdNegativeKeywordDelete(opts: AdNegativeKeywordDeleteOptions): Promise<void>;
|