@oomol-lab/open-connector 1.6.3 → 1.6.5

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.
Files changed (108) hide show
  1. package/assets/open-connector/catalog/apps/aisa.json +568 -0
  2. package/assets/open-connector/catalog/apps/bilibili.json +2586 -0
  3. package/assets/open-connector/catalog/apps/bitwarden.json +1423 -0
  4. package/assets/open-connector/catalog/apps/cronfree.json +303 -0
  5. package/assets/open-connector/catalog/apps/cufinder.json +368 -0
  6. package/assets/open-connector/catalog/apps/feishu_app_bot.json +2 -2
  7. package/assets/open-connector/catalog/apps/gmail.json +73 -0
  8. package/assets/open-connector/catalog/apps/investoday_mcp.json +22228 -197
  9. package/assets/open-connector/catalog/apps/ixspy.json +2274 -0
  10. package/assets/open-connector/catalog/apps/jin10.json +1 -1
  11. package/assets/open-connector/catalog/apps/osv.json +663 -0
  12. package/assets/open-connector/catalog/apps/pdf_vector.json +329 -0
  13. package/assets/open-connector/catalog/apps/pulsetic.json +406 -0
  14. package/assets/open-connector/catalog/apps/shopee.json +1785 -0
  15. package/assets/open-connector/catalog/apps/slack.json +81 -0
  16. package/assets/open-connector/catalog/apps/slackbot.json +81 -0
  17. package/assets/open-connector/catalog/apps/speak_ai.json +360 -0
  18. package/assets/open-connector/catalog/apps/tongdaxin.json +11 -440
  19. package/assets/open-connector/catalog/apps/zyhub.json +1585 -0
  20. package/assets/open-connector/catalog/apps-index.json +1 -1
  21. package/package.json +1 -1
  22. package/src/core/json-schema.d.ts +5 -1
  23. package/src/core/json-schema.js +14 -2
  24. package/src/core/types.d.ts +9 -0
  25. package/src/oauth/oauth-credential-refresh-service.js +3 -1
  26. package/src/oauth/oauth-flow-service.js +17 -2
  27. package/src/oauth/oauth-token.d.ts +13 -0
  28. package/src/providers/action-contracts.generated.d.ts +16 -4
  29. package/src/providers/aisa/executors.d.ts +4 -0
  30. package/src/providers/aisa/executors.js +176 -0
  31. package/src/providers/bilibili/articles.d.ts +3 -0
  32. package/src/providers/bilibili/articles.js +369 -0
  33. package/src/providers/bilibili/executors.d.ts +8 -0
  34. package/src/providers/bilibili/executors.js +262 -0
  35. package/src/providers/bilibili/runtime.d.ts +95 -0
  36. package/src/providers/bilibili/runtime.js +238 -0
  37. package/src/providers/bilibili/upload.d.ts +14 -0
  38. package/src/providers/bilibili/upload.js +105 -0
  39. package/src/providers/bitwarden/executors.d.ts +3 -0
  40. package/src/providers/bitwarden/executors.js +146 -0
  41. package/src/providers/cronfree/executors.d.ts +4 -0
  42. package/src/providers/cronfree/executors.js +89 -0
  43. package/src/providers/cufinder/executors.d.ts +4 -0
  44. package/src/providers/cufinder/executors.js +105 -0
  45. package/src/providers/dropbox/executors.js +21 -3
  46. package/src/providers/elevenreader/runtime.js +6 -5
  47. package/src/providers/feishu_app_bot/scopes.js +1 -1
  48. package/src/providers/gmail/attachment-stream.d.ts +2 -0
  49. package/src/providers/gmail/attachment-stream.js +162 -0
  50. package/src/providers/gmail/executors.d.ts +3 -1
  51. package/src/providers/gmail/executors.js +53 -25
  52. package/src/providers/investoday_mcp/official-actions.js +19 -372
  53. package/src/providers/investoday_mcp/official-tool-types.d.ts +10 -0
  54. package/src/providers/investoday_mcp/official-tool-types.js +4 -0
  55. package/src/providers/investoday_mcp/official-tools/charts.d.ts +2 -0
  56. package/src/providers/investoday_mcp/official-tools/charts.js +32 -0
  57. package/src/providers/investoday_mcp/official-tools/fixed-income.d.ts +2 -0
  58. package/src/providers/investoday_mcp/official-tools/fixed-income.js +326 -0
  59. package/src/providers/investoday_mcp/official-tools/funds.d.ts +2 -0
  60. package/src/providers/investoday_mcp/official-tools/funds.js +1085 -0
  61. package/src/providers/investoday_mcp/official-tools/hong-kong.d.ts +2 -0
  62. package/src/providers/investoday_mcp/official-tools/hong-kong.js +275 -0
  63. package/src/providers/investoday_mcp/official-tools/indices-industries.d.ts +2 -0
  64. package/src/providers/investoday_mcp/official-tools/indices-industries.js +642 -0
  65. package/src/providers/investoday_mcp/official-tools/macro-calendar.d.ts +2 -0
  66. package/src/providers/investoday_mcp/official-tools/macro-calendar.js +398 -0
  67. package/src/providers/investoday_mcp/official-tools/other.d.ts +2 -0
  68. package/src/providers/investoday_mcp/official-tools/other.js +131 -0
  69. package/src/providers/investoday_mcp/official-tools/research-news.d.ts +2 -0
  70. package/src/providers/investoday_mcp/official-tools/research-news.js +302 -0
  71. package/src/providers/investoday_mcp/official-tools/stocks.d.ts +2 -0
  72. package/src/providers/investoday_mcp/official-tools/stocks.js +2829 -0
  73. package/src/providers/ixspy/executors.d.ts +3 -0
  74. package/src/providers/ixspy/executors.js +220 -0
  75. package/src/providers/mcp-client.d.ts +1 -0
  76. package/src/providers/mcp-client.js +41 -27
  77. package/src/providers/osv/executors.d.ts +2 -0
  78. package/src/providers/osv/executors.js +11 -0
  79. package/src/providers/osv/runtime.d.ts +9 -0
  80. package/src/providers/osv/runtime.js +66 -0
  81. package/src/providers/pdf_vector/executors.d.ts +4 -0
  82. package/src/providers/pdf_vector/executors.js +108 -0
  83. package/src/providers/provider-runtime.d.ts +3 -0
  84. package/src/providers/provider-runtime.js +4 -1
  85. package/src/providers/pulsetic/executors.d.ts +4 -0
  86. package/src/providers/pulsetic/executors.js +126 -0
  87. package/src/providers/registry.generated.js +12 -0
  88. package/src/providers/sellersprite_mcp/executors.js +2 -1
  89. package/src/providers/shopee/actions.d.ts +2 -0
  90. package/src/providers/shopee/actions.js +474 -0
  91. package/src/providers/shopee/executors.d.ts +3 -0
  92. package/src/providers/shopee/executors.js +85 -0
  93. package/src/providers/shopee/runtime.d.ts +79 -0
  94. package/src/providers/shopee/runtime.js +693 -0
  95. package/src/providers/slack/actions.js +17 -0
  96. package/src/providers/slack/runtime.js +62 -4
  97. package/src/providers/speak_ai/executors.d.ts +3 -0
  98. package/src/providers/speak_ai/executors.js +187 -0
  99. package/src/providers/tongdaxin/executors.js +5 -4
  100. package/src/providers/tongdaxin/named-action-output.js +3 -9
  101. package/src/providers/tongdaxin/named-action-routing.js +1 -1
  102. package/src/providers/tongdaxin/named-actions.js +1 -6
  103. package/src/providers/tushare/runtime.js +22 -9
  104. package/src/providers/twenty_crm/runtime.js +4 -2
  105. package/src/providers/zyhub/executors.d.ts +3 -0
  106. package/src/providers/zyhub/executors.js +240 -0
  107. package/src/server/files/transit-files.d.ts +3 -2
  108. package/src/server/files/transit-files.js +31 -35
@@ -0,0 +1,693 @@
1
+ import { createHmac } from "node:crypto";
2
+ import { looseArray as asArrayOrEmpty, optionalInteger as asOptionalInteger, optionalNumber as asOptionalNumber, optionalRecord as asOptionalObject, optionalString as asOptionalString, optionalBoolean, } from "../../core/cast.js";
3
+ import { createGuardedFetch } from "../../core/guarded-fetch.js";
4
+ import { assertPublicHttpUrl } from "../../core/request.js";
5
+ import { ProviderRequestError, providerUserAgent as connectorUserAgent, runProviderRequest, } from "../provider-runtime.js";
6
+ const shopeeImageMaxBytes = 10 * 1024 * 1024;
7
+ const shopeeImageContentTypes = new Set(["image/jpeg", "image/png"]);
8
+ function createShopeeError(code, message, status) {
9
+ return new ProviderRequestError(status, message, undefined, code);
10
+ }
11
+ function asObjectOrEmpty(value) {
12
+ return asOptionalObject(value) ?? {};
13
+ }
14
+ function createSsrfGuardedFetch(fetcher, options) {
15
+ const guardedFetch = createGuardedFetch({
16
+ fetch: fetcher,
17
+ createError: (message) => createShopeeError("invalid_input", message, 400),
18
+ });
19
+ return async (input, init) => {
20
+ const url = assertPublicHttpUrl(String(input), {
21
+ fieldName: options.fieldName,
22
+ createError: (message) => createShopeeError("invalid_input", message, 400),
23
+ });
24
+ if (options.requireHttps && url.protocol !== "https:") {
25
+ throw createShopeeError("invalid_input", `${options.fieldName} must use HTTPS.`, 400);
26
+ }
27
+ return guardedFetch(url, init);
28
+ };
29
+ }
30
+ export function resolveShopeeApiBaseUrl(clientConfig) {
31
+ const apiBaseUrl = clientConfig.extra.apiBaseUrl?.trim();
32
+ if (!apiBaseUrl) {
33
+ throw createShopeeError("provider_error", "Shopee API Base URL is unavailable.", 500);
34
+ }
35
+ return apiBaseUrl;
36
+ }
37
+ export function parseShopeePartnerId(clientConfig) {
38
+ const partnerId = Number(clientConfig.clientId);
39
+ if (!Number.isSafeInteger(partnerId) || partnerId <= 0) {
40
+ throw createShopeeError("provider_error", "Shopee Partner ID must be a positive integer.", 500);
41
+ }
42
+ return partnerId;
43
+ }
44
+ function getShopeePartnerKey(clientConfig) {
45
+ const partnerKey = clientConfig.clientSecret.trim();
46
+ if (!partnerKey) {
47
+ throw createShopeeError("provider_error", "Shopee Partner Key is unavailable.", 500);
48
+ }
49
+ return partnerKey;
50
+ }
51
+ export function createShopeeSignature(input) {
52
+ const base = [input.partnerId, input.path, input.timestamp, input.accessToken, input.entityId]
53
+ .filter((value) => value != null)
54
+ .join("");
55
+ return createHmac("sha256", input.partnerKey).update(base).digest("hex");
56
+ }
57
+ export function parseShopeeToken(payload, now = new Date()) {
58
+ assertShopeePayloadSuccess(payload, "Shopee token request");
59
+ const accessToken = requireString(payload.access_token, "access_token");
60
+ const refreshToken = requireString(payload.refresh_token, "refresh_token");
61
+ const expiresIn = asOptionalInteger(payload.expire_in);
62
+ if (expiresIn == null || expiresIn <= 0) {
63
+ throw createShopeeError("provider_error", "Shopee token response is missing a valid expire_in value.", 502);
64
+ }
65
+ const shopIds = uniqueEntityIds([
66
+ ...asArrayOrEmpty(payload.shop_id_list),
67
+ ...(payload.shop_id == null ? [] : [payload.shop_id]),
68
+ ]);
69
+ const merchantIds = uniqueEntityIds([
70
+ ...asArrayOrEmpty(payload.merchant_id_list),
71
+ ...(payload.merchant_id == null ? [] : [payload.merchant_id]),
72
+ ]);
73
+ return {
74
+ accessToken,
75
+ refreshToken,
76
+ expiresAt: new Date(now.getTime() + expiresIn * 1_000).toISOString(),
77
+ shopIds,
78
+ merchantIds,
79
+ requestId: asOptionalString(payload.request_id),
80
+ };
81
+ }
82
+ export function createInitialShopeeTokenInventory(token) {
83
+ const entityToken = {
84
+ accessToken: token.accessToken,
85
+ refreshToken: token.refreshToken,
86
+ expiresAt: token.expiresAt,
87
+ };
88
+ return {
89
+ shops: Object.fromEntries(token.shopIds.map((id) => [String(id), entityToken])),
90
+ merchants: Object.fromEntries(token.merchantIds.map((id) => [String(id), entityToken])),
91
+ };
92
+ }
93
+ export function readShopeeTokenInventory(providerSecret) {
94
+ const inventory = asOptionalObject(providerSecret?.tokenInventory);
95
+ return {
96
+ shops: readEntityTokens(inventory?.shops),
97
+ merchants: readEntityTokens(inventory?.merchants),
98
+ };
99
+ }
100
+ export async function refreshShopeeTokenInventory(input) {
101
+ const previous = readShopeeTokenInventory(input.providerSecret);
102
+ const shopEntries = Object.entries(previous.shops);
103
+ const merchantEntries = Object.entries(previous.merchants);
104
+ if (shopEntries.length === 0 && merchantEntries.length === 0) {
105
+ throw createShopeeError("provider_error", "Shopee connection has no authorized shop or merchant tokens to refresh.", 409);
106
+ }
107
+ const shops = {};
108
+ const merchants = {};
109
+ for (const [id, token] of shopEntries) {
110
+ try {
111
+ shops[id] = await refreshShopeeEntityToken({
112
+ clientConfig: input.clientConfig,
113
+ entityType: "shop",
114
+ entityId: Number(id),
115
+ refreshToken: token.refreshToken || input.fallbackRefreshToken,
116
+ fetcher: input.fetcher,
117
+ now: input.now,
118
+ });
119
+ }
120
+ catch {
121
+ // 单个实体刷新失败时保留其他实体已经轮换成功的新令牌。
122
+ }
123
+ }
124
+ for (const [id, token] of merchantEntries) {
125
+ try {
126
+ merchants[id] = await refreshShopeeEntityToken({
127
+ clientConfig: input.clientConfig,
128
+ entityType: "merchant",
129
+ entityId: Number(id),
130
+ refreshToken: token.refreshToken || input.fallbackRefreshToken,
131
+ fetcher: input.fetcher,
132
+ now: input.now,
133
+ });
134
+ }
135
+ catch {
136
+ // 单个实体刷新失败时保留其他实体已经轮换成功的新令牌。
137
+ }
138
+ }
139
+ const representative = Object.values(merchants)[0] ?? Object.values(shops)[0];
140
+ if (!representative) {
141
+ throw createShopeeError("provider_error", "Shopee token refresh returned no tokens.", 502);
142
+ }
143
+ return { inventory: { shops, merchants }, representative };
144
+ }
145
+ export async function requestShopeeToken(input) {
146
+ const payload = await requestShopee({
147
+ path: input.path,
148
+ level: "public",
149
+ clientConfig: input.clientConfig,
150
+ body: input.body,
151
+ fetcher: input.fetcher,
152
+ now: input.now,
153
+ });
154
+ return parseShopeeToken(payload, input.now);
155
+ }
156
+ export async function executeShopeeAction(actionName, input, context) {
157
+ switch (actionName) {
158
+ case "get_merchant_info":
159
+ return executeEntityGet("/api/v2/merchant/get_merchant_info", "merchant", input, context);
160
+ case "list_merchant_shops":
161
+ return executeEntityGet("/api/v2/merchant/get_shop_list_by_merchant", "merchant", input, context, {
162
+ page_no: asOptionalInteger(input.page) ?? 1,
163
+ page_size: asOptionalInteger(input.pageSize) ?? 100,
164
+ });
165
+ case "get_shop_info":
166
+ return executeEntityGet("/api/v2/shop/get_shop_info", "shop", input, context);
167
+ case "list_global_items":
168
+ return executeEntityGet("/api/v2/global_product/get_global_item_list", "merchant", input, context, {
169
+ offset: asOptionalString(input.cursor),
170
+ page_size: asOptionalInteger(input.pageSize) ?? 50,
171
+ update_time_from: asOptionalInteger(input.updatedFrom),
172
+ update_time_to: asOptionalInteger(input.updatedTo),
173
+ });
174
+ case "get_global_items":
175
+ return executeEntityGet("/api/v2/global_product/get_global_item_info", "merchant", input, context, {
176
+ global_item_id_list: asArrayOrEmpty(input.globalItemIds).join(","),
177
+ });
178
+ case "list_global_categories":
179
+ return executeEntityGet("/api/v2/global_product/get_category", "merchant", input, context, {
180
+ language: asOptionalString(input.language) ?? "en",
181
+ });
182
+ case "get_global_attribute_tree":
183
+ return executeEntityGet("/api/v2/global_product/get_attribute_tree", "merchant", input, context, {
184
+ category_id_list: asArrayOrEmpty(input.categoryIds).join(","),
185
+ language: asOptionalString(input.language) ?? "en",
186
+ });
187
+ case "list_global_brands":
188
+ return executeEntityGet("/api/v2/global_product/get_brand_list", "merchant", input, context, {
189
+ category_id: asOptionalInteger(input.categoryId),
190
+ offset: asOptionalInteger(input.offset) ?? 0,
191
+ page_size: asOptionalInteger(input.pageSize) ?? 100,
192
+ status: asOptionalInteger(input.status) ?? 1,
193
+ });
194
+ case "upload_product_image":
195
+ return uploadShopeeProductImage(input, context);
196
+ case "create_global_item":
197
+ return executeEntityPost("/api/v2/global_product/add_global_item", "merchant", input, context, createGlobalItemBody(input));
198
+ case "list_shop_items":
199
+ return executeEntityGet("/api/v2/product/get_item_list", "shop", input, context, {
200
+ offset: asOptionalInteger(input.offset) ?? 0,
201
+ page_size: asOptionalInteger(input.pageSize) ?? 100,
202
+ item_status: asArrayOrEmpty(input.statuses),
203
+ update_time_from: asOptionalInteger(input.updatedFrom),
204
+ update_time_to: asOptionalInteger(input.updatedTo),
205
+ });
206
+ case "get_shop_items":
207
+ return executeEntityGet("/api/v2/product/get_item_base_info", "shop", input, context, {
208
+ item_id_list: asArrayOrEmpty(input.itemIds).join(","),
209
+ need_tax_info: optionalBoolean(input.includeTaxInfo),
210
+ need_complaint_policy: optionalBoolean(input.includeComplaintPolicy),
211
+ });
212
+ case "get_item_models":
213
+ return executeEntityGet("/api/v2/product/get_model_list", "shop", input, context, {
214
+ item_id: asOptionalInteger(input.itemId),
215
+ });
216
+ case "update_item_prices":
217
+ return executeEntityPost("/api/v2/product/update_price", "shop", input, context, createItemPriceBody(input));
218
+ case "update_item_stocks":
219
+ return executeEntityPost("/api/v2/product/update_stock", "shop", input, context, createItemStockBody(input));
220
+ case "list_orders":
221
+ validateOrderTimeRange(input);
222
+ return executeEntityGet("/api/v2/order/get_order_list", "shop", input, context, {
223
+ time_range_field: asOptionalString(input.timeRangeField) ?? "update_time",
224
+ time_from: asOptionalInteger(input.timeFrom),
225
+ time_to: asOptionalInteger(input.timeTo),
226
+ page_size: asOptionalInteger(input.pageSize) ?? 100,
227
+ cursor: asOptionalString(input.cursor),
228
+ order_status: asOptionalString(input.orderStatus),
229
+ });
230
+ case "get_orders":
231
+ return executeEntityGet("/api/v2/order/get_order_detail", "shop", input, context, {
232
+ order_sn_list: asArrayOrEmpty(input.orderSerialNumbers).join(","),
233
+ response_optional_fields: asArrayOrEmpty(input.optionalFields).join(",") || undefined,
234
+ });
235
+ case "get_shipping_parameters":
236
+ return executePackageGet("/api/v2/logistics/get_shipping_parameter", input, context);
237
+ case "ship_order":
238
+ return executeEntityPost("/api/v2/logistics/ship_order", "shop", input, context, createShipOrderBody(input));
239
+ case "get_tracking_number":
240
+ return executePackageGet("/api/v2/logistics/get_tracking_number", input, context, {
241
+ response_optional_fields: asArrayOrEmpty(input.optionalFields).join(",") || undefined,
242
+ });
243
+ case "get_tracking_info":
244
+ return executePackageGet("/api/v2/logistics/get_tracking_info", input, context);
245
+ case "list_returns":
246
+ validateReturnTimeRanges(input);
247
+ return executeEntityGet("/api/v2/returns/get_return_list", "shop", input, context, {
248
+ page_no: asOptionalInteger(input.page) ?? 1,
249
+ page_size: asOptionalInteger(input.pageSize) ?? 40,
250
+ create_time_from: asOptionalInteger(input.createdFrom),
251
+ create_time_to: asOptionalInteger(input.createdTo),
252
+ update_time_from: asOptionalInteger(input.updatedFrom),
253
+ update_time_to: asOptionalInteger(input.updatedTo),
254
+ status: asOptionalString(input.status),
255
+ negotiation_status: asOptionalString(input.negotiationStatus),
256
+ seller_proof_status: asOptionalString(input.sellerProofStatus),
257
+ seller_compensation_status: asOptionalString(input.sellerCompensationStatus),
258
+ });
259
+ case "get_return":
260
+ return executeEntityGet("/api/v2/returns/get_return_detail", "shop", input, context, {
261
+ return_sn: asOptionalString(input.returnSerialNumber),
262
+ });
263
+ case "get_order_escrow":
264
+ return executeEntityGet("/api/v2/payment/get_escrow_detail", "shop", input, context, {
265
+ order_sn: asOptionalString(input.orderSerialNumber),
266
+ });
267
+ }
268
+ throw createShopeeError("invalid_input", `Unknown Shopee action: ${actionName}`, 400);
269
+ }
270
+ function createItemPriceBody(input) {
271
+ return {
272
+ item_id: asOptionalInteger(input.itemId),
273
+ price_list: asArrayOrEmpty(input.prices).map((value) => {
274
+ const price = asObjectOrEmpty(value);
275
+ return {
276
+ model_id: asOptionalInteger(price.modelId) ?? 0,
277
+ original_price: asOptionalNumber(price.originalPrice),
278
+ };
279
+ }),
280
+ };
281
+ }
282
+ function createItemStockBody(input) {
283
+ return {
284
+ item_id: asOptionalInteger(input.itemId),
285
+ stock_list: asArrayOrEmpty(input.stocks).map((value) => {
286
+ const stock = asObjectOrEmpty(value);
287
+ return {
288
+ model_id: asOptionalInteger(stock.modelId) ?? 0,
289
+ seller_stock: asArrayOrEmpty(stock.sellerStock).map((locationValue) => {
290
+ const location = asObjectOrEmpty(locationValue);
291
+ return {
292
+ location_id: asOptionalString(location.locationId),
293
+ stock: asOptionalInteger(location.stock),
294
+ };
295
+ }),
296
+ };
297
+ }),
298
+ };
299
+ }
300
+ function createShipOrderBody(input) {
301
+ const pickup = asOptionalObject(input.pickup);
302
+ const dropoff = asOptionalObject(input.dropoff);
303
+ const nonIntegrated = asOptionalObject(input.nonIntegrated);
304
+ const shippingMethodCount = [pickup, dropoff, nonIntegrated].filter(Boolean).length;
305
+ if (shippingMethodCount !== 1) {
306
+ throw createShopeeError("invalid_input", "Provide exactly one of pickup, dropoff, or nonIntegrated.", 400);
307
+ }
308
+ return {
309
+ order_sn: asOptionalString(input.orderSerialNumber),
310
+ package_number: asOptionalString(input.packageNumber),
311
+ pickup: pickup
312
+ ? {
313
+ address_id: asOptionalInteger(pickup.addressId),
314
+ pickup_time_id: asOptionalString(pickup.pickupTimeId),
315
+ tracking_number: asOptionalString(pickup.trackingNumber),
316
+ }
317
+ : undefined,
318
+ dropoff: dropoff
319
+ ? {
320
+ branch_id: asOptionalInteger(dropoff.branchId),
321
+ sender_real_name: asOptionalString(dropoff.senderRealName),
322
+ tracking_number: asOptionalString(dropoff.trackingNumber),
323
+ slug: asOptionalString(dropoff.slug),
324
+ }
325
+ : undefined,
326
+ non_integrated: nonIntegrated ? { tracking_number: asOptionalString(nonIntegrated.trackingNumber) } : undefined,
327
+ };
328
+ }
329
+ async function uploadShopeeProductImage(input, context) {
330
+ const imageUrl = asOptionalString(input.imageUrl);
331
+ if (!imageUrl) {
332
+ throw createShopeeError("invalid_input", "imageUrl is required.", 400);
333
+ }
334
+ const guardedFetch = createSsrfGuardedFetch(context.fetcher, {
335
+ fieldName: "imageUrl",
336
+ requireHttps: true,
337
+ });
338
+ const response = await runProviderRequest({ label: "Shopee image download" }, (signal) => guardedFetch(imageUrl, { signal }));
339
+ if (!response.ok) {
340
+ throw createShopeeError("provider_error", `Shopee image download failed with HTTP ${response.status}.`, 502);
341
+ }
342
+ const contentType = response.headers.get("content-type")?.split(";", 1)[0]?.trim().toLowerCase();
343
+ if (!contentType || !shopeeImageContentTypes.has(contentType)) {
344
+ throw createShopeeError("invalid_input", "imageUrl must return a JPG, JPEG, or PNG image.", 400);
345
+ }
346
+ const bytes = await readImageBytes(response);
347
+ const extension = contentType === "image/png" ? "png" : "jpg";
348
+ const body = new FormData();
349
+ body.set("image", new File([bytes], `product.${extension}`, { type: contentType }));
350
+ const scene = asOptionalString(input.scene);
351
+ const ratio = asOptionalString(input.ratio);
352
+ if (scene)
353
+ body.set("scene", scene);
354
+ if (ratio)
355
+ body.set("ratio", ratio);
356
+ const inventory = readShopeeTokenInventory(context.providerSecret);
357
+ const entityId = resolveEntityId("shop", asOptionalInteger(input.shopId), inventory.shops);
358
+ const payload = await requestShopee({
359
+ path: "/api/v2/media_space/upload_image",
360
+ level: "shop",
361
+ entityId,
362
+ clientConfig: context.clientConfig,
363
+ providerSecret: context.providerSecret,
364
+ body,
365
+ fetcher: context.fetcher,
366
+ });
367
+ return normalizeShopeeResponse(payload);
368
+ }
369
+ async function readImageBytes(response) {
370
+ const declaredLength = Number(response.headers.get("content-length"));
371
+ if (Number.isFinite(declaredLength) && declaredLength > shopeeImageMaxBytes) {
372
+ throw createShopeeError("invalid_input", "imageUrl exceeds Shopee's 10 MB image limit.", 400);
373
+ }
374
+ if (!response.body) {
375
+ return new Uint8Array();
376
+ }
377
+ const reader = response.body.getReader();
378
+ const chunks = [];
379
+ let total = 0;
380
+ for (;;) {
381
+ const { done, value } = await reader.read();
382
+ if (done)
383
+ break;
384
+ total += value.byteLength;
385
+ if (total > shopeeImageMaxBytes) {
386
+ await reader.cancel();
387
+ throw createShopeeError("invalid_input", "imageUrl exceeds Shopee's 10 MB image limit.", 400);
388
+ }
389
+ chunks.push(value);
390
+ }
391
+ const bytes = new Uint8Array(total);
392
+ let offset = 0;
393
+ for (const chunk of chunks) {
394
+ bytes.set(chunk, offset);
395
+ offset += chunk.byteLength;
396
+ }
397
+ return bytes;
398
+ }
399
+ function createGlobalItemBody(input) {
400
+ const imageIds = asArrayOrEmpty(input.imageIds);
401
+ const attributes = asArrayOrEmpty(input.attributes);
402
+ const sellerStock = asArrayOrEmpty(input.sellerStock);
403
+ const packageLength = asOptionalNumber(input.packageLength);
404
+ const packageWidth = asOptionalNumber(input.packageWidth);
405
+ const packageHeight = asOptionalNumber(input.packageHeight);
406
+ const dimensionCount = [packageLength, packageWidth, packageHeight].filter((value) => value != null).length;
407
+ if (dimensionCount !== 0 && dimensionCount !== 3) {
408
+ throw createShopeeError("invalid_input", "packageLength, packageWidth, and packageHeight must be provided together.", 400);
409
+ }
410
+ const brandId = asOptionalInteger(input.brandId);
411
+ const brandName = asOptionalString(input.brandName);
412
+ return {
413
+ category_id: asOptionalInteger(input.categoryId),
414
+ global_item_name: asOptionalString(input.name),
415
+ description: asOptionalString(input.description),
416
+ global_item_sku: asOptionalString(input.sku),
417
+ image: imageIds.length > 0 ? { image_id_list: imageIds } : undefined,
418
+ original_price: asOptionalNumber(input.originalPrice),
419
+ normal_stock: asOptionalInteger(input.stock),
420
+ weight: asOptionalNumber(input.weight),
421
+ dimension: packageLength != null && packageWidth != null && packageHeight != null
422
+ ? {
423
+ package_length: packageLength,
424
+ package_width: packageWidth,
425
+ package_height: packageHeight,
426
+ }
427
+ : undefined,
428
+ pre_order: { days_to_ship: asOptionalInteger(input.daysToShip) },
429
+ condition: asOptionalString(input.condition),
430
+ brand: brandId != null || brandName != null ? { brand_id: brandId, original_brand_name: brandName } : undefined,
431
+ attribute_list: attributes.length > 0
432
+ ? attributes.map((value) => {
433
+ const attribute = asObjectOrEmpty(value);
434
+ return {
435
+ attribute_id: asOptionalInteger(attribute.attributeId),
436
+ attribute_value_list: asArrayOrEmpty(attribute.values).map((item) => {
437
+ const attributeValue = asObjectOrEmpty(item);
438
+ return {
439
+ value_id: asOptionalInteger(attributeValue.valueId),
440
+ original_value_name: asOptionalString(attributeValue.originalValueName),
441
+ value_unit: asOptionalString(attributeValue.valueUnit),
442
+ };
443
+ }),
444
+ };
445
+ })
446
+ : undefined,
447
+ seller_stock: sellerStock.length > 0
448
+ ? sellerStock.map((value) => {
449
+ const stock = asObjectOrEmpty(value);
450
+ return {
451
+ location_id: asOptionalString(stock.locationId),
452
+ stock: asOptionalInteger(stock.stock),
453
+ };
454
+ })
455
+ : undefined,
456
+ };
457
+ }
458
+ export async function fetchShopeeEntityInfo(input) {
459
+ const path = input.entityType === "shop" ? "/api/v2/shop/get_shop_info" : "/api/v2/merchant/get_merchant_info";
460
+ return requestShopee({
461
+ path,
462
+ level: input.entityType,
463
+ entityId: input.entityId,
464
+ accessToken: input.accessToken,
465
+ clientConfig: input.clientConfig,
466
+ fetcher: input.fetcher,
467
+ });
468
+ }
469
+ async function executeEntityGet(path, entityType, input, context, query) {
470
+ const inventory = readShopeeTokenInventory(context.providerSecret);
471
+ const entities = entityType === "shop" ? inventory.shops : inventory.merchants;
472
+ const requestedId = asOptionalInteger(input[`${entityType}Id`]);
473
+ const entityId = resolveEntityId(entityType, requestedId, entities);
474
+ const payload = await requestShopee({
475
+ path,
476
+ level: entityType,
477
+ entityId,
478
+ clientConfig: context.clientConfig,
479
+ providerSecret: context.providerSecret,
480
+ query,
481
+ fetcher: context.fetcher,
482
+ });
483
+ return normalizeShopeeResponse(payload);
484
+ }
485
+ function executePackageGet(path, input, context, extraQuery) {
486
+ return executeEntityGet(path, "shop", input, context, {
487
+ order_sn: asOptionalString(input.orderSerialNumber),
488
+ package_number: asOptionalString(input.packageNumber),
489
+ ...extraQuery,
490
+ });
491
+ }
492
+ async function executeEntityPost(path, entityType, input, context, body) {
493
+ const inventory = readShopeeTokenInventory(context.providerSecret);
494
+ const entities = entityType === "shop" ? inventory.shops : inventory.merchants;
495
+ const requestedId = asOptionalInteger(input[`${entityType}Id`]);
496
+ const entityId = resolveEntityId(entityType, requestedId, entities);
497
+ const payload = await requestShopee({
498
+ path,
499
+ level: entityType,
500
+ entityId,
501
+ clientConfig: context.clientConfig,
502
+ providerSecret: context.providerSecret,
503
+ body,
504
+ fetcher: context.fetcher,
505
+ });
506
+ return normalizeShopeeResponse(payload);
507
+ }
508
+ async function requestShopee(input) {
509
+ const partnerId = parseShopeePartnerId(input.clientConfig);
510
+ const timestamp = Math.floor((input.now ?? new Date()).getTime() / 1_000);
511
+ const token = input.level === "public"
512
+ ? undefined
513
+ : input.accessToken
514
+ ? { accessToken: input.accessToken }
515
+ : selectEntityToken(input.level, input.entityId, input.providerSecret);
516
+ const sign = createShopeeSignature({
517
+ partnerId,
518
+ partnerKey: getShopeePartnerKey(input.clientConfig),
519
+ path: input.path,
520
+ timestamp,
521
+ accessToken: token?.accessToken,
522
+ entityId: input.entityId,
523
+ });
524
+ const url = new URL(input.path, resolveShopeeApiBaseUrl(input.clientConfig));
525
+ url.searchParams.set("partner_id", String(partnerId));
526
+ url.searchParams.set("timestamp", String(timestamp));
527
+ url.searchParams.set("sign", sign);
528
+ if (token) {
529
+ url.searchParams.set("access_token", token.accessToken);
530
+ }
531
+ if (input.level === "shop") {
532
+ url.searchParams.set("shop_id", String(input.entityId));
533
+ }
534
+ if (input.level === "merchant") {
535
+ url.searchParams.set("merchant_id", String(input.entityId));
536
+ }
537
+ for (const [key, value] of Object.entries(input.query ?? {})) {
538
+ if (Array.isArray(value)) {
539
+ for (const item of value) {
540
+ if (item != null && item !== "")
541
+ url.searchParams.append(key, String(item));
542
+ }
543
+ }
544
+ else if (value != null && value !== "") {
545
+ url.searchParams.set(key, String(value));
546
+ }
547
+ }
548
+ return runProviderRequest({ label: "Shopee Open Platform" }, async (signal) => {
549
+ const isFormData = input.body instanceof FormData;
550
+ let requestBody;
551
+ if (input.body instanceof FormData) {
552
+ requestBody = input.body;
553
+ }
554
+ else if (input.body) {
555
+ requestBody = JSON.stringify(input.body);
556
+ }
557
+ const response = await input.fetcher(url, {
558
+ method: input.body ? "POST" : "GET",
559
+ headers: {
560
+ accept: "application/json",
561
+ ...(!isFormData && input.body ? { "content-type": "application/json" } : {}),
562
+ "user-agent": connectorUserAgent,
563
+ },
564
+ body: requestBody,
565
+ signal,
566
+ });
567
+ const payload = asOptionalObject(await response.json().catch(() => undefined));
568
+ if (!payload) {
569
+ throw createShopeeError("provider_error", `Shopee Open Platform returned a non-JSON response with HTTP ${response.status}.`, response.ok ? 502 : response.status);
570
+ }
571
+ assertShopeePayloadSuccess(payload, "Shopee Open Platform", response.status);
572
+ return payload;
573
+ });
574
+ }
575
+ function validateReturnTimeRanges(input) {
576
+ const createdFrom = asOptionalInteger(input.createdFrom);
577
+ const updatedFrom = asOptionalInteger(input.updatedFrom);
578
+ validateTimeRange(createdFrom, asOptionalInteger(input.createdTo), "Shopee return creation time range");
579
+ validateTimeRange(updatedFrom, asOptionalInteger(input.updatedTo), "Shopee return update time range");
580
+ if (createdFrom != null && updatedFrom != null && updatedFrom < createdFrom) {
581
+ throw createShopeeError("invalid_input", "Shopee return update time must not start before creation time.", 400);
582
+ }
583
+ }
584
+ function validateTimeRange(from, to, label) {
585
+ if (from == null || to == null)
586
+ return;
587
+ if (to < from) {
588
+ throw createShopeeError("invalid_input", `${label} must end after it starts.`, 400);
589
+ }
590
+ if (to - from > 15 * 24 * 60 * 60) {
591
+ throw createShopeeError("invalid_input", `${label} cannot exceed 15 days.`, 400);
592
+ }
593
+ }
594
+ function assertShopeePayloadSuccess(payload, label, httpStatus = 200) {
595
+ const error = asOptionalString(payload.error);
596
+ if (!error && httpStatus < 400) {
597
+ return;
598
+ }
599
+ const message = asOptionalString(payload.message);
600
+ const requestId = asOptionalString(payload.request_id);
601
+ const details = [error, message, requestId ? `request_id=${requestId}` : undefined].filter(Boolean).join(": ");
602
+ throw createShopeeError("provider_error", `${label} failed${details ? `: ${details}` : ""}`, httpStatus >= 400 ? httpStatus : 502);
603
+ }
604
+ function selectEntityToken(entityType, entityId, providerSecret) {
605
+ if (entityId == null) {
606
+ throw createShopeeError("invalid_input", `Shopee ${entityType} ID is required.`, 400);
607
+ }
608
+ const inventory = readShopeeTokenInventory(providerSecret);
609
+ const token = entityType === "shop" ? inventory.shops[String(entityId)] : inventory.merchants[String(entityId)];
610
+ if (!token) {
611
+ throw createShopeeError("invalid_input", `Shopee ${entityType} ${entityId} is not authorized by this connection.`, 400);
612
+ }
613
+ return token;
614
+ }
615
+ function resolveEntityId(entityType, requestedId, entities) {
616
+ if (requestedId != null) {
617
+ if (!entities[String(requestedId)]) {
618
+ throw createShopeeError("invalid_input", `Shopee ${entityType} ${requestedId} is not authorized by this connection.`, 400);
619
+ }
620
+ return requestedId;
621
+ }
622
+ const ids = Object.keys(entities).map(Number);
623
+ if (ids.length === 1) {
624
+ return ids[0];
625
+ }
626
+ throw createShopeeError("invalid_input", ids.length === 0
627
+ ? `This Shopee connection has no authorized ${entityType}.`
628
+ : `This Shopee connection has multiple ${entityType}s; provide ${entityType}Id.`, 400);
629
+ }
630
+ function readEntityTokens(value) {
631
+ const raw = asObjectOrEmpty(value);
632
+ const result = {};
633
+ for (const [id, candidate] of Object.entries(raw)) {
634
+ const token = asOptionalObject(candidate);
635
+ const accessToken = asOptionalString(token?.accessToken);
636
+ const refreshToken = asOptionalString(token?.refreshToken);
637
+ const expiresAt = asOptionalString(token?.expiresAt);
638
+ if (accessToken && refreshToken && expiresAt) {
639
+ result[id] = { accessToken, refreshToken, expiresAt };
640
+ }
641
+ }
642
+ return result;
643
+ }
644
+ async function refreshShopeeEntityToken(input) {
645
+ const token = await requestShopeeToken({
646
+ clientConfig: input.clientConfig,
647
+ path: "/api/v2/auth/access_token/get",
648
+ body: {
649
+ partner_id: parseShopeePartnerId(input.clientConfig),
650
+ [`${input.entityType}_id`]: input.entityId,
651
+ refresh_token: input.refreshToken,
652
+ },
653
+ fetcher: input.fetcher,
654
+ now: input.now,
655
+ });
656
+ return {
657
+ accessToken: token.accessToken,
658
+ refreshToken: token.refreshToken,
659
+ expiresAt: token.expiresAt,
660
+ };
661
+ }
662
+ function uniqueEntityIds(values) {
663
+ const ids = values
664
+ .map((value) => (typeof value === "number" ? value : Number(value)))
665
+ .filter((value) => Number.isSafeInteger(value) && value > 0);
666
+ return [...new Set(ids)];
667
+ }
668
+ function requireString(value, fieldName) {
669
+ const string = asOptionalString(value);
670
+ if (!string) {
671
+ throw createShopeeError("provider_error", `Shopee token response is missing ${fieldName}.`, 502);
672
+ }
673
+ return string;
674
+ }
675
+ function normalizeShopeeResponse(payload) {
676
+ return {
677
+ requestId: asOptionalString(payload.request_id),
678
+ data: payload,
679
+ };
680
+ }
681
+ function validateOrderTimeRange(input) {
682
+ const from = asOptionalInteger(input.timeFrom);
683
+ const to = asOptionalInteger(input.timeTo);
684
+ if (from == null || to == null) {
685
+ throw createShopeeError("invalid_input", "timeFrom and timeTo are required.", 400);
686
+ }
687
+ if (to < from) {
688
+ throw createShopeeError("invalid_input", "timeTo must not be earlier than timeFrom.", 400);
689
+ }
690
+ if (to - from > 15 * 24 * 60 * 60) {
691
+ throw createShopeeError("invalid_input", "Shopee order time range cannot exceed 15 days.", 400);
692
+ }
693
+ }