@optima-chat/tiktok-cli 0.7.1 → 0.8.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/index.js +107 -14
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -75,16 +75,60 @@ async function listShops() {
|
|
|
75
75
|
return { ok: false, error: { code: "BACKEND_UNREACHABLE", message: e.message, retryable: true } };
|
|
76
76
|
}
|
|
77
77
|
}
|
|
78
|
-
async function
|
|
78
|
+
async function availableApps() {
|
|
79
79
|
try {
|
|
80
|
-
const r = await fetch(`${BACKEND}/auth/
|
|
80
|
+
const r = await fetch(`${BACKEND}/auth/apps`, { headers: authHeaders() });
|
|
81
81
|
if (r.status === 401) return { ok: false, error: { code: "AUTH_EXPIRED", message: "\u672A\u767B\u5F55\u6216 token \u5931\u6548", retryable: false } };
|
|
82
|
-
const body = await r.json();
|
|
82
|
+
const body = await r.json().catch(() => ({}));
|
|
83
|
+
if (!r.ok || body?.error) {
|
|
84
|
+
return { ok: false, error: { code: "VALIDATION", message: body?.error ?? `HTTP ${r.status}`, retryable: false } };
|
|
85
|
+
}
|
|
86
|
+
return { ok: true, data: body };
|
|
87
|
+
} catch (e) {
|
|
88
|
+
return { ok: false, error: { code: "BACKEND_UNREACHABLE", message: e.message, retryable: true } };
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
async function authLink(app) {
|
|
92
|
+
try {
|
|
93
|
+
const r = await fetch(`${BACKEND}/auth/link`, {
|
|
94
|
+
method: "POST",
|
|
95
|
+
headers: { "Content-Type": "application/json", ...authHeaders() },
|
|
96
|
+
body: JSON.stringify({ app })
|
|
97
|
+
});
|
|
98
|
+
if (r.status === 401) return { ok: false, error: { code: "AUTH_EXPIRED", message: "\u672A\u767B\u5F55\u6216 token \u5931\u6548", retryable: false } };
|
|
99
|
+
const body = await r.json().catch(() => ({}));
|
|
100
|
+
if (!r.ok || body?.error) {
|
|
101
|
+
return { ok: false, error: { code: "VALIDATION", message: body?.error ?? body?.message ?? `HTTP ${r.status}`, retryable: false } };
|
|
102
|
+
}
|
|
83
103
|
return { ok: true, data: { authorize_url: body.authorize_url, expires_in_minutes: 15 } };
|
|
84
104
|
} catch (e) {
|
|
85
105
|
return { ok: false, error: { code: "BACKEND_UNREACHABLE", message: e.message, retryable: true } };
|
|
86
106
|
}
|
|
87
107
|
}
|
|
108
|
+
async function adminApps(method, opts = {}) {
|
|
109
|
+
const url = `${BACKEND}/admin/apps${opts.id ? `/${opts.id}` : ""}`;
|
|
110
|
+
const token = process.env.TIKTOK_ADMIN_TOKEN;
|
|
111
|
+
try {
|
|
112
|
+
const r = await fetch(url, {
|
|
113
|
+
method,
|
|
114
|
+
headers: {
|
|
115
|
+
"Content-Type": "application/json",
|
|
116
|
+
...token ? { "x-admin-token": token } : {}
|
|
117
|
+
},
|
|
118
|
+
body: method === "GET" ? void 0 : JSON.stringify(opts.body ?? {})
|
|
119
|
+
});
|
|
120
|
+
const body = await r.json().catch(() => ({}));
|
|
121
|
+
if (r.status === 401 || r.status === 403) {
|
|
122
|
+
return { ok: false, error: { code: "VALIDATION", message: body?.error ?? "admin token \u65E0\u6548\u6216\u7F3A\u5931", retryable: false } };
|
|
123
|
+
}
|
|
124
|
+
if (!r.ok) {
|
|
125
|
+
return { ok: false, error: { code: "VALIDATION", message: body?.error ?? body?.message ?? `HTTP ${r.status}`, retryable: false } };
|
|
126
|
+
}
|
|
127
|
+
return { ok: true, data: body };
|
|
128
|
+
} catch (e) {
|
|
129
|
+
return { ok: false, error: { code: "BACKEND_UNREACHABLE", message: e.message, retryable: true } };
|
|
130
|
+
}
|
|
131
|
+
}
|
|
88
132
|
async function fetchEvents(shop, query) {
|
|
89
133
|
try {
|
|
90
134
|
const qs = new URLSearchParams(query).toString();
|
|
@@ -145,15 +189,25 @@ function mergeSkus(existing, add) {
|
|
|
145
189
|
}
|
|
146
190
|
var today = () => (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
147
191
|
var daysAgo = (n) => new Date(Date.now() - n * 864e5).toISOString().slice(0, 10);
|
|
192
|
+
var toEpoch = (v) => /^\d+$/.test(v) ? Number(v) : Math.floor(Date.parse(v) / 1e3);
|
|
148
193
|
var COMMANDS = [
|
|
149
194
|
{
|
|
150
195
|
name: "orders list",
|
|
151
|
-
summary: "\u8BA2\u5355\u5217\u8868\uFF08\u9ED8\u8BA4\u8FD130\u5929\
|
|
196
|
+
summary: "\u8BA2\u5355\u5217\u8868\uFF08\u9ED8\u8BA4\u8FD130\u5929\u3001\u6309\u521B\u5EFA\u65F6\u95F4\u5012\u5E8F\uFF1B--from/--to\uFF08unix\u79D2\u6216YYYY-MM-DD\uFF09--status --page-size --sort-order ASC/DESC --sort-field create_time/update_time\uFF09",
|
|
152
197
|
run: ({ shop, args: args2 }) => proxy("POST", shop, "/order/202309/orders/search", {
|
|
153
|
-
query: {
|
|
198
|
+
query: {
|
|
199
|
+
page_size: args2["page-size"] ?? "20",
|
|
200
|
+
// 默认倒序(最新在前)——高单量店查「最近订单」不必翻爆全量
|
|
201
|
+
sort_order: (args2["sort-order"] ?? "DESC").toUpperCase(),
|
|
202
|
+
sort_field: args2["sort-field"] ?? "create_time",
|
|
203
|
+
...args2["page-token"] ? { page_token: args2["page-token"] } : {}
|
|
204
|
+
},
|
|
154
205
|
body: {
|
|
155
206
|
...args2.status ? { order_status: args2.status } : {},
|
|
156
|
-
|
|
207
|
+
// 下界:--from 优先,其次兼容 --since,默认近 30 天
|
|
208
|
+
create_time_ge: args2.from ? toEpoch(args2.from) : args2.since ? toEpoch(args2.since) : toEpoch(daysAgo(30)),
|
|
209
|
+
// 上界:--to(可选)
|
|
210
|
+
...args2.to ? { create_time_lt: toEpoch(args2.to) } : {}
|
|
157
211
|
}
|
|
158
212
|
})
|
|
159
213
|
},
|
|
@@ -826,6 +880,49 @@ var COMMANDS = [
|
|
|
826
880
|
body: { auto_add_product: { commission_rate: Number(args2.rate), enable: args2.enable === "true" } }
|
|
827
881
|
}),
|
|
828
882
|
readback: ({ shop }) => proxy("GET", shop, "/affiliate_seller/202409/open_collaboration_settings", {})
|
|
883
|
+
},
|
|
884
|
+
// ── 多 App 管理(admin 配置操作,鉴权走 x-admin-token,不走商家 --shop,也不走三道阀 dry-run/--apply)──
|
|
885
|
+
{
|
|
886
|
+
name: "apps add",
|
|
887
|
+
summary: "\u767B\u8BB0\u4E00\u4E2A TikTok App\uFF08--service-id --app-key --app-secret --type --label\uFF0C[--markets a,b] [--redirect] [--owner-entity]\uFF09",
|
|
888
|
+
run: ({ args: args2 }) => adminApps("POST", {
|
|
889
|
+
body: {
|
|
890
|
+
serviceId: args2["service-id"],
|
|
891
|
+
appKey: args2["app-key"],
|
|
892
|
+
appSecret: args2["app-secret"],
|
|
893
|
+
appType: args2.type,
|
|
894
|
+
label: args2.label,
|
|
895
|
+
...args2.markets ? { markets: args2.markets.split(",") } : {},
|
|
896
|
+
redirectUri: args2.redirect,
|
|
897
|
+
...args2["owner-entity"] ? { ownerEntity: args2["owner-entity"] } : {}
|
|
898
|
+
}
|
|
899
|
+
})
|
|
900
|
+
},
|
|
901
|
+
{
|
|
902
|
+
name: "apps list",
|
|
903
|
+
summary: "\u5217\u51FA\u5DF2\u767B\u8BB0\u7684 TikTok App\uFF08admin\uFF0C\u542B appKey/status\uFF09",
|
|
904
|
+
run: () => adminApps("GET")
|
|
905
|
+
},
|
|
906
|
+
{
|
|
907
|
+
name: "apps available",
|
|
908
|
+
summary: "\u5217\u51FA\u53EF\u9009 App\uFF08\u7528\u6237\u9274\u6743\uFF0C\u4EC5 active\uFF0C\u65E0 secret\uFF09\u2014\u2014\u9009 serviceId \u4F20\u7ED9 auth link --app",
|
|
909
|
+
run: () => availableApps()
|
|
910
|
+
},
|
|
911
|
+
{
|
|
912
|
+
name: "apps disable",
|
|
913
|
+
summary: "\u505C\u7528\u4E00\u4E2A TikTok App\uFF08--id\uFF09",
|
|
914
|
+
run: ({ args: args2 }) => adminApps("PATCH", { id: args2.id, body: { status: "disabled" } })
|
|
915
|
+
},
|
|
916
|
+
// ── auth link(多 app:--app 必填 serviceId,决定 backend 用哪个 app_key/secret 生成授权链接)──
|
|
917
|
+
{
|
|
918
|
+
name: "auth link",
|
|
919
|
+
summary: "\u751F\u6210\u5E97\u94FA\u7ED1\u5B9A\u6388\u6743\u94FE\u63A5\uFF08--app serviceId \u5FC5\u586B\uFF0C\u53D1\u7ED9\u5546\u5BB6\u70B9\u51FB\uFF09",
|
|
920
|
+
run: async ({ args: args2 }) => {
|
|
921
|
+
if (!args2.app) {
|
|
922
|
+
return { ok: false, error: { code: "VALIDATION", message: "--app \u5FC5\u586B\uFF08serviceId\uFF09", retryable: false } };
|
|
923
|
+
}
|
|
924
|
+
return authLink(args2.app);
|
|
925
|
+
}
|
|
829
926
|
}
|
|
830
927
|
];
|
|
831
928
|
function sampleReviewBody(args2) {
|
|
@@ -891,6 +988,7 @@ function findCommand(tokens) {
|
|
|
891
988
|
}
|
|
892
989
|
|
|
893
990
|
// src/index.ts
|
|
991
|
+
var NO_SHOP_COMMANDS = /* @__PURE__ */ new Set(["auth link", "apps add", "apps list", "apps disable", "apps available"]);
|
|
894
992
|
function parseFlags(tokens) {
|
|
895
993
|
const args2 = {};
|
|
896
994
|
const positional2 = [];
|
|
@@ -914,9 +1012,8 @@ function usage() {
|
|
|
914
1012
|
const lines = ["tiktok <domain> <verb> --shop <shopId> [--flags]", "", "\u547D\u4EE4\uFF1A"];
|
|
915
1013
|
for (const c of COMMANDS) lines.push(` ${c.name.padEnd(22)} ${c.summary}`);
|
|
916
1014
|
lines.push(` ${"shops".padEnd(22)} \u67E5\u5F53\u524D\u767B\u5F55\u7528\u6237\u5DF2\u6388\u6743\u5E97\u94FA`);
|
|
917
|
-
lines.push(` ${"auth link".padEnd(22)} \u751F\u6210\u5E97\u94FA\u7ED1\u5B9A\u6388\u6743\u94FE\u63A5\uFF08\u53D1\u7ED9\u5546\u5BB6\u70B9\u51FB\uFF09`);
|
|
918
1015
|
lines.push(` ${"images upload".padEnd(22)} \u4E0A\u4F20\u672C\u5730\u56FE\u7247\uFF08--file --use-case [--out]\uFF09`);
|
|
919
|
-
lines.push("", "\u901A\u7528\uFF1A--shop \u5FC5\u586B\uFF08\u9664 shops / auth link\uFF09\uFF1B\u9ED8\u8BA4 JSON \u8F93\u51FA\uFF1BTIKTOK_BACKEND_URL \u663E\u5F0F\u6307\u5B9A backend\uFF08\u672A\u8BBE\u5219\u6309 OPTIMA_ENV \u6620\u5C04 cn-stage/cn-prod\uFF0C\u5426\u5219 localhost\uFF09\u3002");
|
|
1016
|
+
lines.push("", "\u901A\u7528\uFF1A--shop \u5FC5\u586B\uFF08\u9664 shops / auth link / apps add|list|disable\uFF09\uFF1B\u9ED8\u8BA4 JSON \u8F93\u51FA\uFF1BTIKTOK_BACKEND_URL \u663E\u5F0F\u6307\u5B9A backend\uFF08\u672A\u8BBE\u5219\u6309 OPTIMA_ENV \u6620\u5C04 cn-stage/cn-prod\uFF0C\u5426\u5219 localhost\uFF09\u3002");
|
|
920
1017
|
process.stderr.write(lines.join("\n") + "\n");
|
|
921
1018
|
}
|
|
922
1019
|
var argv = process.argv.slice(2);
|
|
@@ -929,10 +1026,6 @@ if (positional[0] === "shops") {
|
|
|
929
1026
|
out(await listShops());
|
|
930
1027
|
process.exit(0);
|
|
931
1028
|
}
|
|
932
|
-
if (positional[0] === "auth" && positional[1] === "link") {
|
|
933
|
-
out(await createAuthLink());
|
|
934
|
-
process.exit(0);
|
|
935
|
-
}
|
|
936
1029
|
if (positional[0] === "images" && positional[1] === "upload") {
|
|
937
1030
|
if (!args.file) {
|
|
938
1031
|
out({ ok: false, error: { code: "VALIDATION", message: "\u7F3A\u5C11 --file", retryable: false } });
|
|
@@ -956,11 +1049,11 @@ if (!match) {
|
|
|
956
1049
|
usage();
|
|
957
1050
|
process.exit(1);
|
|
958
1051
|
}
|
|
959
|
-
if (!args.shop) {
|
|
1052
|
+
if (!args.shop && !NO_SHOP_COMMANDS.has(match.cmd.name)) {
|
|
960
1053
|
out({ ok: false, error: { code: "VALIDATION", message: "\u7F3A\u5C11 --shop\uFF08\u591A\u5E97\u663E\u5F0F\u8DEF\u7531\uFF09", retryable: false } });
|
|
961
1054
|
process.exit(1);
|
|
962
1055
|
}
|
|
963
|
-
var ctx = { shop: args.shop, args, actor: args.actor };
|
|
1056
|
+
var ctx = { shop: args.shop ?? "", args, actor: args.actor };
|
|
964
1057
|
if (match.cmd.write && !("apply" in args)) {
|
|
965
1058
|
if (match.cmd.precheck) {
|
|
966
1059
|
const pc = await match.cmd.precheck(ctx);
|