@optima-chat/tiktok-cli 0.7.2 → 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/index.js +119 -11
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -75,16 +75,75 @@ 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 setShopApp(shopId, serviceId, note) {
|
|
79
|
+
const token = process.env.TIKTOK_ADMIN_TOKEN;
|
|
79
80
|
try {
|
|
80
|
-
const r = await fetch(`${BACKEND}/
|
|
81
|
+
const r = await fetch(`${BACKEND}/admin/apps/shop-map`, {
|
|
82
|
+
method: "PUT",
|
|
83
|
+
headers: { "Content-Type": "application/json", ...token ? { "x-admin-token": token } : {} },
|
|
84
|
+
body: JSON.stringify({ shopId, serviceId, note })
|
|
85
|
+
});
|
|
86
|
+
const body = await r.json().catch(() => ({}));
|
|
87
|
+
if (!r.ok || body?.error) return { ok: false, error: { code: r.status === 401 ? "AUTH_EXPIRED" : "VALIDATION", message: body?.error ?? `HTTP ${r.status}`, retryable: false } };
|
|
88
|
+
return { ok: true, data: body };
|
|
89
|
+
} catch (e) {
|
|
90
|
+
return { ok: false, error: { code: "BACKEND_UNREACHABLE", message: e.message, retryable: true } };
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
async function availableApps() {
|
|
94
|
+
try {
|
|
95
|
+
const r = await fetch(`${BACKEND}/auth/apps`, { headers: authHeaders() });
|
|
81
96
|
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();
|
|
97
|
+
const body = await r.json().catch(() => ({}));
|
|
98
|
+
if (!r.ok || body?.error) {
|
|
99
|
+
return { ok: false, error: { code: "VALIDATION", message: body?.error ?? `HTTP ${r.status}`, retryable: false } };
|
|
100
|
+
}
|
|
101
|
+
return { ok: true, data: body };
|
|
102
|
+
} catch (e) {
|
|
103
|
+
return { ok: false, error: { code: "BACKEND_UNREACHABLE", message: e.message, retryable: true } };
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
async function authLink(opts) {
|
|
107
|
+
try {
|
|
108
|
+
const r = await fetch(`${BACKEND}/auth/link`, {
|
|
109
|
+
method: "POST",
|
|
110
|
+
headers: { "Content-Type": "application/json", ...authHeaders() },
|
|
111
|
+
body: JSON.stringify({ shop: opts.shop, app: opts.app })
|
|
112
|
+
});
|
|
113
|
+
if (r.status === 401) return { ok: false, error: { code: "AUTH_EXPIRED", message: "\u672A\u767B\u5F55\u6216 token \u5931\u6548", retryable: false } };
|
|
114
|
+
const body = await r.json().catch(() => ({}));
|
|
115
|
+
if (!r.ok || body?.error) {
|
|
116
|
+
return { ok: false, error: { code: "VALIDATION", message: body?.error ?? body?.message ?? `HTTP ${r.status}`, retryable: false } };
|
|
117
|
+
}
|
|
83
118
|
return { ok: true, data: { authorize_url: body.authorize_url, expires_in_minutes: 15 } };
|
|
84
119
|
} catch (e) {
|
|
85
120
|
return { ok: false, error: { code: "BACKEND_UNREACHABLE", message: e.message, retryable: true } };
|
|
86
121
|
}
|
|
87
122
|
}
|
|
123
|
+
async function adminApps(method, opts = {}) {
|
|
124
|
+
const url = `${BACKEND}/admin/apps${opts.id ? `/${opts.id}` : ""}`;
|
|
125
|
+
const token = process.env.TIKTOK_ADMIN_TOKEN;
|
|
126
|
+
try {
|
|
127
|
+
const r = await fetch(url, {
|
|
128
|
+
method,
|
|
129
|
+
headers: {
|
|
130
|
+
"Content-Type": "application/json",
|
|
131
|
+
...token ? { "x-admin-token": token } : {}
|
|
132
|
+
},
|
|
133
|
+
body: method === "GET" ? void 0 : JSON.stringify(opts.body ?? {})
|
|
134
|
+
});
|
|
135
|
+
const body = await r.json().catch(() => ({}));
|
|
136
|
+
if (r.status === 401 || r.status === 403) {
|
|
137
|
+
return { ok: false, error: { code: "VALIDATION", message: body?.error ?? "admin token \u65E0\u6548\u6216\u7F3A\u5931", retryable: false } };
|
|
138
|
+
}
|
|
139
|
+
if (!r.ok) {
|
|
140
|
+
return { ok: false, error: { code: "VALIDATION", message: body?.error ?? body?.message ?? `HTTP ${r.status}`, retryable: false } };
|
|
141
|
+
}
|
|
142
|
+
return { ok: true, data: body };
|
|
143
|
+
} catch (e) {
|
|
144
|
+
return { ok: false, error: { code: "BACKEND_UNREACHABLE", message: e.message, retryable: true } };
|
|
145
|
+
}
|
|
146
|
+
}
|
|
88
147
|
async function fetchEvents(shop, query) {
|
|
89
148
|
try {
|
|
90
149
|
const qs = new URLSearchParams(query).toString();
|
|
@@ -836,6 +895,59 @@ var COMMANDS = [
|
|
|
836
895
|
body: { auto_add_product: { commission_rate: Number(args2.rate), enable: args2.enable === "true" } }
|
|
837
896
|
}),
|
|
838
897
|
readback: ({ shop }) => proxy("GET", shop, "/affiliate_seller/202409/open_collaboration_settings", {})
|
|
898
|
+
},
|
|
899
|
+
// ── 多 App 管理(admin 配置操作,鉴权走 x-admin-token,不走商家 --shop,也不走三道阀 dry-run/--apply)──
|
|
900
|
+
{
|
|
901
|
+
name: "apps add",
|
|
902
|
+
summary: "\u767B\u8BB0\u4E00\u4E2A TikTok App\uFF08--service-id --app-key --app-secret --type --label\uFF0C[--markets a,b] [--redirect] [--owner-entity]\uFF09",
|
|
903
|
+
run: ({ args: args2 }) => adminApps("POST", {
|
|
904
|
+
body: {
|
|
905
|
+
serviceId: args2["service-id"],
|
|
906
|
+
appKey: args2["app-key"],
|
|
907
|
+
appSecret: args2["app-secret"],
|
|
908
|
+
appType: args2.type,
|
|
909
|
+
label: args2.label,
|
|
910
|
+
...args2.markets ? { markets: args2.markets.split(",") } : {},
|
|
911
|
+
redirectUri: args2.redirect,
|
|
912
|
+
...args2["owner-entity"] ? { ownerEntity: args2["owner-entity"] } : {},
|
|
913
|
+
..."default" in args2 ? { isDefault: true } : {}
|
|
914
|
+
}
|
|
915
|
+
})
|
|
916
|
+
},
|
|
917
|
+
{
|
|
918
|
+
name: "apps set-shop",
|
|
919
|
+
summary: "\u9884\u7F6E shop\u2192app \u6620\u5C04\uFF08--shop shopId --app serviceId [--note]\uFF09\u2014\u2014auth link \u65F6\u6309\u5E97\u89E3\u6790\u7528\u54EA\u4E2A app",
|
|
920
|
+
run: ({ args: args2 }) => {
|
|
921
|
+
if (!args2.shop || !args2.app) return Promise.resolve({ ok: false, error: { code: "VALIDATION", message: "--shop \u548C --app\uFF08serviceId\uFF09\u5FC5\u586B", retryable: false } });
|
|
922
|
+
return setShopApp(args2.shop, args2.app, args2.note);
|
|
923
|
+
}
|
|
924
|
+
},
|
|
925
|
+
{
|
|
926
|
+
name: "apps list",
|
|
927
|
+
summary: "\u5217\u51FA\u5DF2\u767B\u8BB0\u7684 TikTok App\uFF08admin\uFF0C\u542B appKey/status\uFF09",
|
|
928
|
+
run: () => adminApps("GET")
|
|
929
|
+
},
|
|
930
|
+
{
|
|
931
|
+
name: "apps available",
|
|
932
|
+
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",
|
|
933
|
+
run: () => availableApps()
|
|
934
|
+
},
|
|
935
|
+
{
|
|
936
|
+
name: "apps disable",
|
|
937
|
+
summary: "\u505C\u7528\u4E00\u4E2A TikTok App\uFF08--id\uFF09",
|
|
938
|
+
run: ({ args: args2 }) => adminApps("PATCH", { id: args2.id, body: { status: "disabled" } })
|
|
939
|
+
},
|
|
940
|
+
// ── auth link:skill 只传 --shop,backend 按 shop 解析用哪个 app(custom 映射→默认 public)。
|
|
941
|
+
// --app 仅运维显式覆盖(可选)。二者至少给一个。──
|
|
942
|
+
{
|
|
943
|
+
name: "auth link",
|
|
944
|
+
summary: "\u751F\u6210\u5E97\u94FA\u7ED1\u5B9A\u6388\u6743\u94FE\u63A5\uFF08--shop shopId\uFF0Cbackend \u6309\u5E97\u5B9A app\uFF1B--app serviceId \u53EF\u9009\u8986\u76D6\uFF09",
|
|
945
|
+
run: async ({ args: args2 }) => {
|
|
946
|
+
if (!args2.shop && !args2.app) {
|
|
947
|
+
return { ok: false, error: { code: "VALIDATION", message: "--shop\uFF08shopId\uFF09\u5FC5\u586B\uFF08\u6216 --app \u8986\u76D6\uFF09", retryable: false } };
|
|
948
|
+
}
|
|
949
|
+
return authLink({ shop: args2.shop, app: args2.app });
|
|
950
|
+
}
|
|
839
951
|
}
|
|
840
952
|
];
|
|
841
953
|
function sampleReviewBody(args2) {
|
|
@@ -901,6 +1013,7 @@ function findCommand(tokens) {
|
|
|
901
1013
|
}
|
|
902
1014
|
|
|
903
1015
|
// src/index.ts
|
|
1016
|
+
var NO_SHOP_COMMANDS = /* @__PURE__ */ new Set(["auth link", "apps add", "apps list", "apps disable", "apps available", "apps set-shop"]);
|
|
904
1017
|
function parseFlags(tokens) {
|
|
905
1018
|
const args2 = {};
|
|
906
1019
|
const positional2 = [];
|
|
@@ -924,9 +1037,8 @@ function usage() {
|
|
|
924
1037
|
const lines = ["tiktok <domain> <verb> --shop <shopId> [--flags]", "", "\u547D\u4EE4\uFF1A"];
|
|
925
1038
|
for (const c of COMMANDS) lines.push(` ${c.name.padEnd(22)} ${c.summary}`);
|
|
926
1039
|
lines.push(` ${"shops".padEnd(22)} \u67E5\u5F53\u524D\u767B\u5F55\u7528\u6237\u5DF2\u6388\u6743\u5E97\u94FA`);
|
|
927
|
-
lines.push(` ${"auth link".padEnd(22)} \u751F\u6210\u5E97\u94FA\u7ED1\u5B9A\u6388\u6743\u94FE\u63A5\uFF08\u53D1\u7ED9\u5546\u5BB6\u70B9\u51FB\uFF09`);
|
|
928
1040
|
lines.push(` ${"images upload".padEnd(22)} \u4E0A\u4F20\u672C\u5730\u56FE\u7247\uFF08--file --use-case [--out]\uFF09`);
|
|
929
|
-
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");
|
|
1041
|
+
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");
|
|
930
1042
|
process.stderr.write(lines.join("\n") + "\n");
|
|
931
1043
|
}
|
|
932
1044
|
var argv = process.argv.slice(2);
|
|
@@ -939,10 +1051,6 @@ if (positional[0] === "shops") {
|
|
|
939
1051
|
out(await listShops());
|
|
940
1052
|
process.exit(0);
|
|
941
1053
|
}
|
|
942
|
-
if (positional[0] === "auth" && positional[1] === "link") {
|
|
943
|
-
out(await createAuthLink());
|
|
944
|
-
process.exit(0);
|
|
945
|
-
}
|
|
946
1054
|
if (positional[0] === "images" && positional[1] === "upload") {
|
|
947
1055
|
if (!args.file) {
|
|
948
1056
|
out({ ok: false, error: { code: "VALIDATION", message: "\u7F3A\u5C11 --file", retryable: false } });
|
|
@@ -966,11 +1074,11 @@ if (!match) {
|
|
|
966
1074
|
usage();
|
|
967
1075
|
process.exit(1);
|
|
968
1076
|
}
|
|
969
|
-
if (!args.shop) {
|
|
1077
|
+
if (!args.shop && !NO_SHOP_COMMANDS.has(match.cmd.name)) {
|
|
970
1078
|
out({ ok: false, error: { code: "VALIDATION", message: "\u7F3A\u5C11 --shop\uFF08\u591A\u5E97\u663E\u5F0F\u8DEF\u7531\uFF09", retryable: false } });
|
|
971
1079
|
process.exit(1);
|
|
972
1080
|
}
|
|
973
|
-
var ctx = { shop: args.shop, args, actor: args.actor };
|
|
1081
|
+
var ctx = { shop: args.shop ?? "", args, actor: args.actor };
|
|
974
1082
|
if (match.cmd.write && !("apply" in args)) {
|
|
975
1083
|
if (match.cmd.precheck) {
|
|
976
1084
|
const pc = await match.cmd.precheck(ctx);
|