ts-glitter 21.5.3 → 21.5.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.
Files changed (79) hide show
  1. package/lowcode/Entry.js +1 -1
  2. package/lowcode/Entry.ts +1 -1
  3. package/lowcode/cms-plugin/account-info.js +79 -67
  4. package/lowcode/cms-plugin/account-info.ts +327 -311
  5. package/lowcode/cms-plugin/information/information-module.ts +3 -1
  6. package/lowcode/cms-plugin/list-header-option.js +1 -0
  7. package/lowcode/cms-plugin/list-header-option.ts +1 -0
  8. package/lowcode/cms-plugin/shopping-information.js +468 -0
  9. package/lowcode/cms-plugin/shopping-information.ts +557 -0
  10. package/lowcode/cms-plugin/shopping-market-shopee.js +283 -231
  11. package/lowcode/cms-plugin/shopping-market-shopee.ts +401 -361
  12. package/lowcode/cms-plugin/shopping-order-manager.js +16 -4
  13. package/lowcode/cms-plugin/shopping-order-manager.ts +18 -3
  14. package/lowcode/glitter-base/global/language.js +4 -1
  15. package/lowcode/glitter-base/global/language.ts +4 -2
  16. package/lowcode/glitter-base/route/shopee.js +48 -11
  17. package/lowcode/glitter-base/route/shopee.ts +119 -80
  18. package/lowcode/glitterBundle/plugins/html-render.js +121 -90
  19. package/lowcode/glitterBundle/plugins/html-render.ts +367 -318
  20. package/lowcode/modules/image-library.js +2 -3
  21. package/lowcode/modules/image-library.ts +21 -7
  22. package/lowcode/public-components/checkout/index.js +90 -41
  23. package/lowcode/public-components/checkout/index.ts +101 -49
  24. package/lowcode/public-components/footer/footer-initial.js +11 -2
  25. package/lowcode/public-components/footer/footer-initial.ts +29 -18
  26. package/lowcode/public-components/headers/header-class.js +47 -35
  27. package/lowcode/public-components/headers/header-class.ts +54 -38
  28. package/lowcode/public-components/layout-plugin/social-links-01.js +122 -3
  29. package/lowcode/public-components/layout-plugin/social-links-01.ts +135 -10
  30. package/lowcode/public-components/product/pd-card-01.js +23 -14
  31. package/lowcode/public-components/product/pd-card-01.ts +25 -14
  32. package/lowcode/public-components/product/pd-card-02.js +23 -16
  33. package/lowcode/public-components/product/pd-card-02.ts +25 -16
  34. package/lowcode/public-components/product/pd-card-03.js +25 -16
  35. package/lowcode/public-components/product/pd-card-03.ts +27 -16
  36. package/lowcode/public-components/terms-related/index.js +13 -2
  37. package/lowcode/public-components/terms-related/index.ts +15 -2
  38. package/lowcode/public-components/user-manager/um-class.js +490 -501
  39. package/lowcode/public-components/user-manager/um-class.ts +872 -882
  40. package/lowcode/public-components/user-manager/um-info.js +41 -40
  41. package/lowcode/public-components/user-manager/um-info.ts +54 -56
  42. package/lowcode/public-components/user-manager/um-login.js +10 -13
  43. package/lowcode/public-components/user-manager/um-login.ts +15 -23
  44. package/lowcode/public-components/user-manager/um-orderlist.js +60 -51
  45. package/lowcode/public-components/user-manager/um-orderlist.ts +289 -275
  46. package/lowcode/public-components/user-manager/um-rebate.js +104 -82
  47. package/lowcode/public-components/user-manager/um-rebate.ts +294 -267
  48. package/lowcode/public-components/user-manager/um-receive.js +582 -0
  49. package/lowcode/public-components/user-manager/um-receive.ts +599 -0
  50. package/lowcode/public-components/user-manager/um-wishlist.js +72 -68
  51. package/lowcode/public-components/user-manager/um-wishlist.ts +240 -230
  52. package/package.json +1 -1
  53. package/src/api-public/controllers/shopee.js +17 -0
  54. package/src/api-public/controllers/shopee.js.map +1 -1
  55. package/src/api-public/controllers/shopee.ts +32 -0
  56. package/src/api-public/services/monitor.d.ts +1 -0
  57. package/src/api-public/services/post.js +17 -7
  58. package/src/api-public/services/post.js.map +1 -1
  59. package/src/api-public/services/rebate.js +2 -11
  60. package/src/api-public/services/rebate.js.map +1 -1
  61. package/src/api-public/services/rebate.ts +5 -12
  62. package/src/api-public/services/shopee.d.ts +23 -2
  63. package/src/api-public/services/shopee.js +230 -111
  64. package/src/api-public/services/shopee.js.map +1 -1
  65. package/src/api-public/services/shopee.ts +1012 -838
  66. package/src/api-public/services/user.js +2 -2
  67. package/src/api-public/services/user.js.map +1 -1
  68. package/src/api-public/services/user.ts +3 -3
  69. package/src/index.js +17 -7
  70. package/src/index.js.map +1 -1
  71. package/src/modules/tool.d.ts +4 -4
  72. package/src/modules/tool.js +2 -1
  73. package/src/modules/tool.js.map +1 -1
  74. package/src/services/backend-service.js +17 -7
  75. package/src/services/backend-service.js.map +1 -1
  76. package/src/services/template.d.ts +1 -1
  77. package/src/services/template.js +24 -18
  78. package/src/services/template.js.map +1 -1
  79. package/src/services/template.ts +34 -37
@@ -1,933 +1,1107 @@
1
- import {IToken} from '../models/Auth.js';
1
+ import { IToken } from '../models/Auth.js';
2
2
  import db from '../../modules/database.js';
3
- import config, {saasConfig} from '../../config.js';
4
- import axios, {AxiosRequestConfig} from 'axios';
3
+ import config, { saasConfig } from '../../config.js';
4
+ import axios, { AxiosRequestConfig } from 'axios';
5
5
  import Logger from '../../modules/logger.js';
6
6
  import s3bucket from '../../modules/AWSLib.js';
7
- import crypto from "crypto";
8
- import process from "process";
7
+ import crypto from 'crypto';
8
+ import process from 'process';
9
9
  import qs from 'qs';
10
- import {Shopping} from "./shopping.js";
10
+ import { Shopping } from './shopping.js';
11
11
 
12
12
  const mime = require('mime');
13
13
  type ActiveSchedule = {
14
- start_ISO_Date?: string;
15
- end_ISO_Date?: string;
16
- startDate?: string;
17
- startTime?: string;
18
- endDate?: string;
19
- endTime?: string;
14
+ start_ISO_Date?: string;
15
+ end_ISO_Date?: string;
16
+ startDate?: string;
17
+ startTime?: string;
18
+ endDate?: string;
19
+ endTime?: string;
20
20
  };
21
21
 
22
22
  interface Variant {
23
- save_stock?: string;
24
- sale_price: number;
25
- compare_price: number;
26
- cost: number;
27
- spec: string[];
28
- profit: number;
29
- v_length: number;
30
- v_width: number;
31
- v_height: number;
32
- weight: number;
33
- shipment_type: 'weight' | 'none' | 'volume';
34
- sku: string;
35
- barcode: string;
36
- stock: number;
37
- stockList: {};
38
- preview_image: string;
39
- show_understocking: string;
40
- type: string;
23
+ save_stock?: string;
24
+ sale_price: number;
25
+ compare_price: number;
26
+ cost: number;
27
+ spec: string[];
28
+ profit: number;
29
+ v_length: number;
30
+ v_width: number;
31
+ v_height: number;
32
+ weight: number;
33
+ shipment_type: 'weight' | 'none' | 'volume';
34
+ sku: string;
35
+ barcode: string;
36
+ stock: number;
37
+ stockList: {};
38
+ preview_image: string;
39
+ show_understocking: string;
40
+ type: string;
41
41
  }
42
42
 
43
43
  export interface LanguageData {
44
+ title: string;
45
+ seo: {
46
+ domain: string;
44
47
  title: string;
45
- seo: {
46
- domain: string;
47
- title: string;
48
- content: string;
49
- keywords: string;
50
- };
48
+ content: string;
49
+ keywords: string;
50
+ };
51
51
  }
52
52
 
53
- interface Config extends AxiosRequestConfig {
54
- }
53
+ interface Config extends AxiosRequestConfig {}
55
54
 
56
55
  export class Shopee {
57
- public app;
58
- public token: IToken | undefined;
56
+ public app;
57
+ public token: IToken | undefined;
59
58
 
60
- public static get path() {
61
- if (process.env.shopee_beta === 'true') {
62
- return `https://partner.test-stable.shopeemobile.com`
63
- } else {
64
- return `https://partner.shopeemobile.com`
65
- }
59
+ public static get path() {
60
+ if (process.env.shopee_beta === 'true') {
61
+ return `https://partner.test-stable.shopeemobile.com`;
62
+ } else {
63
+ return `https://partner.shopeemobile.com`;
66
64
  }
67
- public static get partner_id() {
68
- if (process.env.shopee_beta === 'true') {
69
- return process.env.shopee_test_partner_id
70
- } else {
71
- return process.env.shopee_partner_id
72
- }
73
- }
74
- public static get partner_key() {
75
- if (process.env.shopee_beta === 'true') {
76
- return process.env.shopee_test_partner_key
77
- } else {
78
- return process.env.shopee_partner_key
79
- }
65
+ }
66
+
67
+ public static get partner_id() {
68
+ if (process.env.shopee_beta === 'true') {
69
+ return process.env.shopee_test_partner_id;
70
+ } else {
71
+ return process.env.shopee_partner_id;
80
72
  }
73
+ }
81
74
 
82
- constructor(app: string, token?: IToken) {
83
- this.app = app;
84
- this.token = token;
75
+ public static get partner_key() {
76
+ if (process.env.shopee_beta === 'true') {
77
+ return process.env.shopee_test_partner_key;
78
+ } else {
79
+ return process.env.shopee_partner_key;
85
80
  }
81
+ }
86
82
 
87
- public generateUrl(partner_id: string, api_path: string, timestamp: number) {
83
+ constructor(app: string, token?: IToken) {
84
+ this.app = app;
85
+ this.token = token;
86
+ }
88
87
 
88
+ public generateUrl(partner_id: string, api_path: string, timestamp: number) {
89
+ const sign = this.cryptoSign(partner_id, api_path, timestamp);
89
90
 
90
- const sign = this.cryptoSign(partner_id, api_path, timestamp);
91
+ return `${Shopee.path}${api_path}?partner_id=${partner_id}&timestamp=${timestamp}&sign=${sign}`;
92
+ }
91
93
 
92
- return `${Shopee.path}${api_path}?partner_id=${partner_id}&timestamp=${timestamp}&sign=${sign}`
94
+ public generateShopUrl(
95
+ partner_id: string,
96
+ api_path: string,
97
+ timestamp: number,
98
+ access_token: string,
99
+ shop_id: number
100
+ ) {
101
+ const sign = this.cryptoSign(partner_id, api_path, timestamp, access_token, shop_id);
93
102
 
94
- }
103
+ return `${Shopee.path}${api_path}?partner_id=${partner_id}&timestamp=${timestamp}&sign=${sign}`;
104
+ // ?partner_id=1249034&sign=528d448cde17720098c8886aafc973c093af54a489ff4ef80198b39de958d484&timestamp=1736322488
105
+ }
95
106
 
96
- public generateShopUrl(partner_id: string, api_path: string, timestamp: number, access_token: string, shop_id: number) {
97
- const sign = this.cryptoSign(partner_id, api_path, timestamp, access_token, shop_id);
107
+ private cryptoSign(partner_id: string, api_path: string, timestamp: number, access_token?: string, shop_id?: number) {
108
+ const baseString = `${partner_id}${api_path}${timestamp}${access_token ?? ''}${shop_id ?? ''}`;
109
+ const partner_key = Shopee.partner_key;
98
110
 
99
- return `${Shopee.path}${api_path}?partner_id=${partner_id}&timestamp=${timestamp}&sign=${sign}`
100
- // ?partner_id=1249034&sign=528d448cde17720098c8886aafc973c093af54a489ff4ef80198b39de958d484&timestamp=1736322488
101
- }
111
+ return crypto
112
+ .createHmac('sha256', partner_key ?? '')
113
+ .update(baseString)
114
+ .digest('hex');
115
+ }
102
116
 
103
- private cryptoSign(partner_id: string, api_path: string, timestamp: number, access_token?: string, shop_id?: number) {
117
+ public generateAuth(redirectUrl: string) {
118
+ const partner_id = Shopee.partner_id;
119
+ const api_path = '/api/v2/shop/auth_partner';
120
+ const timestamp = Math.floor(Date.now() / 1000);
104
121
 
105
- const baseString = `${partner_id}${api_path}${timestamp}${access_token ?? ""}${shop_id ?? ""}`;
106
- const partner_key = Shopee.partner_key
122
+ const baseString = `${partner_id}${api_path}${timestamp}`;
123
+ const signature = this.cryptoSign(partner_id ?? '', api_path, timestamp);
124
+ return `${Shopee.path}${api_path}?partner_id=${partner_id}&timestamp=${timestamp}&redirect=${redirectUrl}&sign=${signature}`;
125
+ }
107
126
 
108
- return crypto.createHmac('sha256', partner_key ?? "").update(baseString).digest('hex');
109
- }
110
127
 
111
- public generateAuth(redirectUrl: string) {
112
- const partner_id = Shopee.partner_id;
113
- const api_path = "/api/v2/shop/auth_partner"
114
- const timestamp = Math.floor(Date.now() / 1000);
115
128
 
116
- const baseString = `${partner_id}${api_path}${timestamp}`;
117
- const signature = this.cryptoSign(partner_id ?? "", api_path, timestamp)
118
- return `${Shopee.path}${api_path}?partner_id=${partner_id}&timestamp=${timestamp}&redirect=${redirectUrl}&sign=${signature}`
129
+ public async getToken(code: string, shop_id: string) {
130
+ const timestamp = Math.floor(Date.now() / 1000);
131
+ const partner_id = Shopee.partner_id ?? '';
132
+ const api_path = '/api/v2/auth/token/get';
133
+ const config = {
134
+ method: 'post',
135
+ url: this.generateUrl(partner_id, api_path, timestamp),
136
+ headers: {
137
+ 'Content-Type': 'application/json',
138
+ },
139
+ data: JSON.stringify({
140
+ code: code,
141
+ partner_id: parseInt(partner_id, 10),
142
+ shop_id: parseInt(shop_id),
143
+ }),
144
+ };
145
+
146
+ try {
147
+ const response = await axios(config);
148
+
149
+ interface ShopeeAuthToken {
150
+ error: string;
151
+ message: string;
152
+ shop_id: string;
153
+ request_id: string;
154
+ access_token: string; // Shopee 提供的 Access Token
155
+ refresh_token: string; // Shopee 提供的 Refresh Token
156
+ expire_in: number; // 剩餘有效秒數 (Shopee 回傳的 `expire_in`)
157
+ expires_at: string; // Token 到期時間 (ISO 8601 格式)
158
+ created_at: string; // 存入時間 (ISO 8601 格式)
159
+ }
160
+
161
+ const data = await db.execute(
162
+ `select *
163
+ from \`${saasConfig.SAAS_NAME}\`.private_config
164
+ where \`app_name\` = '${this.app}'
165
+ and \`key\` = 'shopee_access_token'
166
+ `,
167
+ []
168
+ );
169
+ let passData = {
170
+ ...response.data,
171
+ expires_at: new Date(Date.now() + 14373 * 1000).toISOString(), // 計算到期時間
172
+ created_at: new Date().toISOString(),
173
+ shop_id: shop_id,
174
+ };
175
+ if (data.length == 0) {
176
+ await db.execute(
177
+ `
178
+ INSERT INTO \`${saasConfig.SAAS_NAME}\`.private_config (\`app_name\`, \`key\`, \`value\`, \`updated_at\`)
179
+ VALUES (?, ?, ?, ?);
180
+ `,
181
+
182
+ [this.app, 'shopee_access_token', passData, new Date()]
183
+ );
184
+ } else {
185
+ await db.execute(
186
+ `
187
+ UPDATE \`${saasConfig.SAAS_NAME}\`.\`private_config\`
188
+ SET \`value\` = ?,
189
+ updated_at=?
190
+ where \`app_name\` = '${this.app}'
191
+ and \`key\` = 'shopee_access_token'
192
+ `,
193
+ [passData, new Date()]
194
+ );
195
+ }
196
+ } catch (error: any) {
197
+ if (axios.isAxiosError(error) && error.response) {
198
+ console.error('Error Response:', error.response.data);
199
+ } else {
200
+ console.error('Unexpected Error:', error.message);
201
+ }
119
202
  }
203
+ }
120
204
 
121
- public async getToken(code: string, shop_id: string) {
122
- const timestamp = Math.floor(Date.now() / 1000);
123
- const partner_id = Shopee.partner_id??"";
124
- const api_path = "/api/v2/auth/token/get"
125
- const config = {
126
- method: 'post',
127
- url: this.generateUrl(partner_id, api_path, timestamp),
128
- headers: {
129
- 'Content-Type': 'application/json',
130
- },
131
- data: JSON.stringify({
132
- code: code,
133
- partner_id: parseInt(partner_id, 10),
134
- shop_id: parseInt(shop_id),
135
- })
136
- };
205
+ public static getItemProgress: string[] = [];
137
206
 
138
- try {
139
- const response = await axios(config);
140
- interface ShopeeAuthToken {
141
- error:string;
142
- message:string;
143
- shop_id:string;
144
- request_id:string;
145
- access_token: string; // Shopee 提供的 Access Token
146
- refresh_token: string; // Shopee 提供的 Refresh Token
147
- expire_in: number; // 剩餘有效秒數 (Shopee 回傳的 `expire_in`)
148
- expires_at: string; // Token 到期時間 (ISO 8601 格式)
149
- created_at: string; // 存入時間 (ISO 8601 格式)
150
- }
151
- const data = (await db.execute(
152
- `select *
153
- from \`${saasConfig.SAAS_NAME}\`.private_config
154
- where \`app_name\` = '${this.app}'
155
- and \`key\` = 'shopee_access_token'
156
- `, []));
157
- let passData = {...response.data,
158
- expires_at: new Date(Date.now() + 14373 * 1000).toISOString(), // 計算到期時間
159
- created_at: new Date().toISOString(),
160
- shop_id: shop_id
161
- }
162
- if (data.length == 0) {
163
- await db.execute(`
164
- INSERT INTO \`${saasConfig.SAAS_NAME}\`.private_config (\`app_name\`, \`key\`, \`value\`, \`updated_at\`)
165
- VALUES (?, ?, ?, ?);
166
- `
207
+ public async getItemList(start: string, end: string, index: number = 0) {
208
+ const timestamp = Math.floor(Date.now() / 1000);
209
+ const partner_id = Shopee.partner_id ?? '';
210
+ const api_path = '/api/v2/product/get_item_list';
211
+ await this.fetchShopeeAccessToken();
212
+ const data = await db.execute(
213
+ `select *
214
+ from \`${saasConfig.SAAS_NAME}\`.private_config
215
+ where \`app_name\` = '${this.app}'
216
+ and \`key\` = 'shopee_access_token'
217
+ `,
218
+ []
219
+ );
167
220
 
168
- , [this.app, "shopee_access_token", passData, new Date()])
169
- } else {
170
- await db.execute(`
171
- UPDATE \`${saasConfig.SAAS_NAME}\`.\`private_config\`
172
- SET \`value\` = ? , updated_at=?
173
- where \`app_name\` = '${this.app}'
174
- and \`key\` = 'shopee_access_token'
175
- `
176
- , [passData,new Date()])
177
- }
178
- } catch (error: any) {
179
- if (axios.isAxiosError(error) && error.response) {
180
- console.error('Error Response:', error.response.data);
221
+ const config = {
222
+ method: 'get',
223
+ url: this.generateShopUrl(
224
+ partner_id,
225
+ api_path,
226
+ timestamp,
227
+ data[0].value.access_token,
228
+ parseInt(data[0].value.shop_id)
229
+ ),
230
+ headers: {
231
+ 'Content-Type': 'application/json',
232
+ },
233
+ params: {
234
+ shop_id: parseInt(data[0].value.shop_id),
235
+ access_token: data[0].value.access_token,
236
+ offset: index || 0,
237
+ page_size: 10,
238
+ update_time_from: start,
239
+ update_time_to: Math.floor(Date.now() / 1000),
240
+ item_status: ['NORMAL', 'BANNED', 'UNLIST'],
241
+ },
242
+ paramsSerializer: (params: any) => qs.stringify(params, { arrayFormat: 'repeat' }),
243
+ };
244
+ try {
245
+ const response = await axios(config);
246
+ if (response.data.error.length > 0) {
247
+ return {
248
+ type: 'error',
249
+ message: response.data.error,
250
+ };
251
+ }
252
+ console.log("response -- " , response);
253
+ if (response.data.response.total_count == 0) {
254
+ return {
255
+ type: 'success',
256
+ data: response.data.response,
257
+ message: '該時間區間查無商品',
258
+ }
259
+ }
260
+ const itemList: {
261
+ item_id: number;
262
+ item_status: string;
263
+ update_time: number;
264
+ }[] = response.data.response.item;
265
+
266
+ const productData = await Promise.all(
267
+ itemList.map(async (item, index: number) => {
268
+ console.log('here -- OK');
269
+ try {
270
+ const pd_data = await db.query(
271
+ `SELECT count(1)
272
+ FROM ${this.app}.t_manager_post
273
+ WHERE (content ->>'$.type'='product')
274
+ AND (content ->>'$.shopee_id' = ${item.item_id});`,
275
+ []
276
+ );
277
+ if (pd_data[0]['count(1)'] > 0) {
278
+ return null;
181
279
  } else {
182
- console.error('Unexpected Error:', error.message);
280
+ return await this.getProductDetail(item.item_id); // 返回上傳後的資料
183
281
  }
282
+ } catch (error) {
283
+ return null; // 返回 null 以處理失敗的情況
284
+ }
285
+ })
286
+ );
287
+ const temp: any = {};
288
+ temp.data = productData.reverse().filter(dd => {
289
+ return dd;
290
+ });
291
+ temp.collection = [];
292
+ try {
293
+ await new Shopping(this.app, this.token).postMulProduct(temp);
294
+ if (response.data.response.has_next_page) {
295
+ await this.getItemList(start, end, response.data.response.next_offset);
184
296
  }
297
+ return {
298
+ data: temp.data,
299
+ message: '匯入OK',
300
+ };
301
+ } catch (error: any) {
302
+ console.error(error);
303
+ //失敗繼續跑匯入
304
+ // if (response.data.response.has_next_page) {
305
+ // await this.getItemList(start, end, response.data.response.next_offset)
306
+ // }
307
+ return {
308
+ type: 'error',
309
+ data: temp.data,
310
+ message: '產品匯入資料庫失敗',
311
+ };
312
+ }
313
+ } catch (error: any) {
314
+ if (axios.isAxiosError(error) && error.response) {
315
+ console.log('Try get_item_list error');
316
+ console.error('Error Response:', error.response.data);
317
+
318
+ return {
319
+ type: 'error',
320
+ error: error.response.data.error,
321
+ message: error.response.data.message,
322
+ };
323
+ } else {
324
+ console.error('Unexpected Error:', error.message);
325
+ }
185
326
  }
327
+ }
186
328
 
187
- public static getItemProgress:string[]=[]
188
- public async getItemList(start: string, end: string, index: number = 0) {
189
- const timestamp = Math.floor(Date.now() / 1000);
190
- const partner_id = Shopee.partner_id??"";
191
- const api_path = "/api/v2/product/get_item_list";
192
- await this.fetchShopeeAccessToken();
193
- const data = (await db.execute(
194
- `select *
195
- from \`${saasConfig.SAAS_NAME}\`.private_config
196
- where \`app_name\` = '${this.app}'
197
- and \`key\` = 'shopee_access_token'
198
- `,
199
- []
200
- ));
329
+ public async getProductDetail(
330
+ id: number,
331
+ option?: {
332
+ skip_image_load: boolean;
333
+ }
334
+ ) {
335
+ const that = this;
336
+ const token = await this.fetchShopeeAccessToken();
337
+ if (!token) {
338
+ return false;
339
+ }
201
340
 
202
- const config = {
203
- method: 'get',
204
- url: this.generateShopUrl(partner_id, api_path, timestamp, data[0].value.access_token, parseInt(data[0].value.shop_id)),
205
- headers: {
206
- 'Content-Type': 'application/json',
207
- },
208
- params: {
209
- shop_id: parseInt(data[0].value.shop_id),
210
- access_token: data[0].value.access_token,
211
- offset: index || 0,
212
- page_size: 10,
213
- update_time_from: start,
214
- update_time_to: Math.floor(Date.now() / 1000),
215
- item_status: ['NORMAL', 'BANNED', 'UNLIST'],
216
- },
217
- paramsSerializer: (params: any) => qs.stringify(params, {arrayFormat: 'repeat'}),
341
+ async function getModel(postMD: {
342
+ template: string;
343
+ visible: string;
344
+ preview_image: any[];
345
+ relative_product: any[];
346
+ active_schedule: { endDate: undefined; startTime: string; endTime: undefined; startDate: string };
347
+ content_array: any[];
348
+ channel: string[];
349
+ collection: any[];
350
+ variants: any[];
351
+ title: string;
352
+ ai_description: string;
353
+ content: string;
354
+ specs: any[];
355
+ language_data: {
356
+ 'en-US': {
357
+ content_array: any[];
358
+ title: string;
359
+ seo: { keywords: string; domain: string; title: string; content: string };
360
+ content: string;
218
361
  };
219
- try {
220
- const response = await axios(config);
221
- if (response.data.error.length > 0) {
222
- return {
223
- type: "error",
224
- message: response.data.error
225
- }
226
- }
227
- const itemList: {
228
- item_id: number,
229
- item_status: string,
230
- update_time: number
231
- }[] = response.data.response.item;
232
-
233
- const productData = await Promise.all(
234
- itemList.map(async (item, index: number) => {
235
- try {
236
- const pd_data = (await db.query(`SELECT count(1)
237
- FROM ${this.app}.t_manager_post
238
- WHERE (content ->>'$.type'='product')
239
- AND (content ->>'$.shopee_id' = ${item.item_id});`, []))
240
- if (pd_data[0]['count(1)'] > 0) {
241
- return null
242
- } else {
243
- return await this.getProductDetail(item.item_id); // 返回上傳後的資料
244
- }
245
- } catch (error) {
246
- return null; // 返回 null 以處理失敗的情況
247
- }
248
- })
249
- );
250
- const temp: any = {}
251
- temp.data = productData.reverse().filter((dd) => {
252
- return dd
362
+ 'zh-TW': { title: any; seo: any };
363
+ 'zh-CN': {
364
+ content_array: any[];
365
+ title: string;
366
+ seo: { keywords: string; domain: string; title: string; content: string };
367
+ content: string;
368
+ };
369
+ };
370
+ hideIndex: string;
371
+ seo: { keywords: string; domain: string; title: string; content: string };
372
+ productType: { product: boolean; addProduct: boolean; giveaway: boolean };
373
+ content_json: any[];
374
+ status: string;
375
+ }) {
376
+ const timestamp = Math.floor(Date.now() / 1000);
377
+ const partner_id = Shopee.partner_id ?? '';
378
+ const api_path = '/api/v2/product/get_model_list';
379
+ const config = {
380
+ method: 'get',
381
+ url: that.generateShopUrl(partner_id, api_path, timestamp, token.access_token, parseInt(token.shop_id)),
382
+ headers: {
383
+ 'Content-Type': 'application/json',
384
+ },
385
+ params: {
386
+ shop_id: parseInt(token.shop_id),
387
+ access_token: token.access_token,
388
+ item_id: id,
389
+ },
390
+ };
391
+ try {
392
+ const response = await axios(config);
393
+ let tempVariants: Variant[] = [];
394
+ const tier_variation = response.data.response.tier_variation;
395
+ const model = response.data.response.model;
396
+ const specs: {
397
+ title: string;
398
+ option: {
399
+ title: string;
400
+ expand: false;
401
+ language_title: {};
402
+ }[];
403
+ language_title: {};
404
+ }[] = tier_variation.map((dd: any) => {
405
+ let temp: {
406
+ title: string;
407
+ option: {
408
+ title: string;
409
+ expand: false;
410
+ language_title: {};
411
+ }[];
412
+ language_title: {};
413
+ } = {
414
+ title: dd.name,
415
+ option: [],
416
+ language_title: {},
417
+ };
418
+ dd.option_list.map((option: any) => {
419
+ temp.option.push({
420
+ title: option.option,
421
+ expand: false,
422
+ language_title: {},
253
423
  });
254
- temp.collection = [];
424
+ });
425
+ return temp;
426
+ });
427
+ postMD.specs = specs;
428
+ model.map(async (data: any) => {
429
+ let newVariants: Variant = {
430
+ sale_price: data.price_info[0].current_price,
431
+ compare_price: data.price_info[0].original_price,
432
+ cost: 0,
433
+ spec: data.model_name.split(','),
434
+ profit: 0,
435
+ v_length: 0,
436
+ v_width: 0,
437
+ v_height: 0,
438
+ weight: 0,
439
+ shipment_type: 'none',
440
+ sku: data.model_sku,
441
+ barcode: '',
442
+ stock: data.stock_info_v2.summary_info.total_available_stock,
443
+ stockList: {},
444
+ preview_image: '',
445
+ show_understocking: 'true',
446
+ type: 'product',
447
+ };
448
+ if (!(option && option.skip_image_load) && data?.image?.image_url_list.length > 0) {
255
449
  try {
256
- await new Shopping(this.app, this.token).postMulProduct(temp);
257
- if (response.data.response.has_next_page) {
258
- await this.getItemList(start, end, response.data.response.next_offset)
259
- }
260
- return {
261
- data: temp.data,
262
- message: '匯入OK'
263
- }
264
- } catch (error: any) {
265
- console.error(error)
266
- //失敗繼續跑匯入
267
- // if (response.data.response.has_next_page) {
268
- // await this.getItemList(start, end, response.data.response.next_offset)
269
- // }
270
- return {
271
- type: "error",
272
- data: temp.data,
273
- message: '產品匯入資料庫失敗'
274
- }
275
- }
276
-
277
-
278
- } catch (error: any) {
279
- if (axios.isAxiosError(error) && error.response) {
280
- console.log("Try get_item_list error")
281
- console.error('Error Response:', error.response.data);
450
+ const imageUrl = data.image.image_url_list[0]; // 取得第一個圖片的 URL
451
+ if (imageUrl) {
452
+ const buffer = await that.downloadImage(imageUrl);
453
+ const fileExtension = 'jpg';
454
+ const fileName = `shopee/${postMD.title}/${new Date().getTime()}_0.${fileExtension}`;
282
455
 
283
- return {
284
- type: "error",
285
- error: error.response.data.error,
286
- message: error.response.data.message,
287
- }
288
- } else {
289
- console.error('Unexpected Error:', error.message);
456
+ newVariants.preview_image = await that.uploadFile(fileName, buffer); // 只賦值第一個圖片的上傳結果
457
+ } else {
458
+ console.warn('圖片 URL 列表為空,無法處理');
459
+ newVariants.preview_image = '';
460
+ }
461
+ } catch (error) {
462
+ console.error('下載或上傳失敗:', error);
463
+ newVariants.preview_image = ''; // 若發生錯誤,設為 null
290
464
  }
465
+ }
466
+ (newVariants as any).shopee_model_id = data.model_id;
467
+ tempVariants.push(newVariants);
468
+ });
469
+
470
+ postMD.variants = tempVariants;
471
+ } catch (error: any) {
472
+ if (axios.isAxiosError(error) && error.response) {
473
+ console.error('Error Response:', error.response.data);
474
+ } else {
475
+ console.error('Unexpected Error:', error.message);
291
476
  }
477
+ }
292
478
  }
293
479
 
294
- public async getProductDetail(id: number,option?:{
295
- skip_image_load:boolean
296
- }) {
297
- const that = this
298
- const token=await this.fetchShopeeAccessToken()
299
- if(!token){
300
- return false
301
- }
302
- async function getModel(postMD: {
303
- template: string;
304
- visible: string;
305
- preview_image: any[];
306
- relative_product: any[];
307
- active_schedule: { endDate: undefined; startTime: string; endTime: undefined; startDate: string };
480
+ const timestamp = Math.floor(Date.now() / 1000);
481
+ const partner_id = Shopee.partner_id ?? '';
482
+ const api_path = '/api/v2/product/get_item_base_info';
483
+ const config = {
484
+ method: 'get',
485
+ url: this.generateShopUrl(partner_id, api_path, timestamp, token.access_token, parseInt(token.shop_id)),
486
+ headers: {
487
+ 'Content-Type': 'application/json',
488
+ },
489
+ params: {
490
+ shop_id: parseInt(token.shop_id),
491
+ access_token: token.access_token,
492
+ item_id_list: id,
493
+ },
494
+ };
495
+ try {
496
+ const response = await axios(config);
497
+ const item = response.data.response.item_list[0];
498
+
499
+ //取得是否原本有資料
500
+ let origData: any = {};
501
+ try {
502
+ origData = await db.query(
503
+ `SELECT *
504
+ FROM \`${this.app}\`.t_manager_post
505
+ WHERE (content ->>'$.type'='product')
506
+ AND (content ->>'$.shopee_id' = ?);`,
507
+ [id]
508
+ );
509
+ } catch (e: any) {}
510
+ let postMD: {
511
+ template: string;
512
+ visible: string;
513
+ preview_image: any[];
514
+ relative_product: any[];
515
+ active_schedule: { endDate: undefined; startTime: string; endTime: undefined; startDate: string };
516
+ content_array: any[];
517
+ channel: string[];
518
+ collection: any[];
519
+ variants: any[];
520
+ title: string;
521
+ ai_description: string;
522
+ content: string;
523
+ specs: any[];
524
+ language_data: {
525
+ 'en-US': {
308
526
  content_array: any[];
309
- channel: string[];
310
- collection: any[];
311
- variants: any[];
312
527
  title: string;
313
- ai_description: string;
528
+ seo: { keywords: string; domain: string; title: string; content: string };
314
529
  content: string;
315
- specs: any[];
316
- language_data: {
317
- "en-US": {
318
- content_array: any[];
319
- title: string;
320
- seo: { keywords: string; domain: string; title: string; content: string };
321
- content: string
322
- };
323
- "zh-TW": { title: any; seo: any };
324
- "zh-CN": {
325
- content_array: any[];
326
- title: string;
327
- seo: { keywords: string; domain: string; title: string; content: string };
328
- content: string
329
- }
330
- };
331
- hideIndex: string;
530
+ };
531
+ 'zh-TW': { title: any; seo: any };
532
+ 'zh-CN': {
533
+ content_array: any[];
534
+ title: string;
332
535
  seo: { keywords: string; domain: string; title: string; content: string };
333
- productType: { product: boolean; addProduct: boolean; giveaway: boolean };
334
- content_json: any[];
335
- status: string
336
- }) {
337
- const timestamp = Math.floor(Date.now() / 1000);
338
- const partner_id = Shopee.partner_id ?? "";
339
- const api_path = "/api/v2/product/get_model_list";
340
- const config = {
341
- method: 'get',
342
- url: that.generateShopUrl(partner_id, api_path, timestamp, token.access_token, parseInt(token.shop_id)),
343
- headers: {
344
- 'Content-Type': 'application/json',
345
- },
346
- params: {
347
- shop_id: parseInt(token.shop_id),
348
- access_token: token.access_token,
349
- item_id: id
350
- },
351
- };
536
+ content: string;
537
+ };
538
+ };
539
+ hideIndex: string;
540
+ seo: { keywords: string; domain: string; title: string; content: string };
541
+ productType: { product: boolean; addProduct: boolean; giveaway: boolean };
542
+ content_json: any[];
543
+ status: string;
544
+ };
545
+
546
+ postMD = this.getInitial({});
547
+ if (origData.length > 0) {
548
+ postMD = {
549
+ ...postMD,
550
+ ...origData[0],
551
+ };
552
+ }
553
+
554
+ postMD.title = item.item_name;
555
+ // 兩邊商品介紹結構不同
556
+ if (item.description_info && item.description_info.extended_description.field_list.length > 0) {
557
+ let temp = ``;
558
+ const promises = item.description_info.extended_description.field_list.map(async (item1: any) => {
559
+ if (item1.field_type == 'image' && !(option && option.skip_image_load)) {
352
560
  try {
353
- const response = await axios(config);
354
- let tempVariants: Variant[] = [];
355
- const tier_variation = response.data.response.tier_variation;
356
- const model = response.data.response.model;
357
- const specs: {
358
- title: string,
359
- option: {
360
- title: string,
361
- expand: false,
362
- language_title: {}
363
- }[],
364
- language_title: {}
365
- }[] = tier_variation.map((dd: any) => {
366
- let temp: {
367
- title: string,
368
- option: {
369
- title: string,
370
- expand: false,
371
- language_title: {}
372
- }[],
373
- language_title: {}
374
- } = {
375
- title: dd.name,
376
- option: [],
377
- language_title: {}
378
- }
379
- dd.option_list.map((option: any) => {
380
- temp.option.push({
381
- title: option.option,
382
- expand: false,
383
- language_title: {}
384
- })
385
- })
386
- return temp;
387
- })
388
- postMD.specs = specs;
389
- model.map(async (data: any) => {
390
- let newVariants: Variant = {
391
- sale_price: data.price_info[0].current_price,
392
- compare_price: data.price_info[0].original_price,
393
- cost: 0,
394
- spec: data.model_name.split(','),
395
- profit: 0,
396
- v_length: 0,
397
- v_width: 0,
398
- v_height: 0,
399
- weight: 0,
400
- shipment_type: 'none',
401
- sku: data.model_sku,
402
- barcode: "",
403
- stock: data.stock_info_v2.summary_info.total_available_stock,
404
- stockList: {},
405
- preview_image: "",
406
- show_understocking: "true",
407
- type: "product",
408
- }
409
- if (!(option && option.skip_image_load) && (data?.image?.image_url_list.length > 0)) {
410
- try {
411
- const imageUrl = data.image.image_url_list[0]; // 取得第一個圖片的 URL
412
- if (imageUrl) {
413
- const buffer = await that.downloadImage(imageUrl);
414
- const fileExtension = "jpg";
415
- const fileName = `shopee/${postMD.title}/${new Date().getTime()}_0.${fileExtension}`;
416
-
417
- newVariants.preview_image = await that.uploadFile(fileName, buffer); // 只賦值第一個圖片的上傳結果
418
- } else {
419
- console.warn('圖片 URL 列表為空,無法處理');
420
- newVariants.preview_image = "";
421
- }
422
- } catch (error) {
423
- console.error('下載或上傳失敗:', error);
424
- newVariants.preview_image = ""; // 若發生錯誤,設為 null
425
- }
426
- }
427
- (newVariants as any).shopee_model_id = data.model_id;
428
- tempVariants.push(newVariants);
429
- })
430
-
431
- postMD.variants = tempVariants;
432
-
433
- } catch (error: any) {
434
- if (axios.isAxiosError(error) && error.response) {
435
- console.error('Error Response:', error.response.data);
436
- } else {
437
- console.error('Unexpected Error:', error.message);
438
- }
561
+ const buffer = await this.downloadImage(item1.image_info.image_url);
562
+ const fileExtension = 'jpg';
563
+ const fileName = `shopee/${postMD.title}/${new Date().getTime()}_${item1.image_info.image_id}.${fileExtension}`;
564
+ item1.image_info.s3 = await this.uploadFile(fileName, buffer);
565
+ } catch (error) {
566
+ console.error('下載或上傳失敗:', error);
567
+ // 你可以根据需求选择是否返回 null 或其他处理方式
439
568
  }
569
+ }
570
+ });
571
+ const html = String.raw;
572
+ await Promise.all(promises);
573
+ if (item.description_info && item.description_info.extended_description) {
574
+ item.description_info.extended_description.field_list.map((item: any) => {
575
+ if (item.field_type == 'image' && !(option && option.skip_image_load)) {
576
+ temp += html` <div style="white-space: pre-wrap;">
577
+ <img src="${item.image_info.s3}" alt="${item.image_info.image_id}" />
578
+ </div>`;
579
+ } else if (item.field_type == 'text') {
580
+ temp += html` <div style="white-space: pre-wrap;">${item.text}</div>`;
581
+ }
582
+ });
440
583
  }
441
584
 
585
+ postMD.content = temp;
586
+ }
442
587
 
443
-
444
-
445
- const timestamp = Math.floor(Date.now() / 1000);
446
- const partner_id = Shopee.partner_id ?? "";
447
- const api_path = "/api/v2/product/get_item_base_info";
448
- const config = {
449
- method: 'get',
450
- url: this.generateShopUrl(partner_id, api_path, timestamp, token.access_token, parseInt(token.shop_id)),
451
- headers: {
452
- 'Content-Type': 'application/json',
453
- },
454
- params: {
455
- shop_id: parseInt(token.shop_id),
456
- access_token: token.access_token,
457
- item_id_list: id
458
- },
459
-
588
+ if (item.price_info) {
589
+ //單規格
590
+ let newVariants: Variant = {
591
+ sale_price: item.price_info[0].current_price,
592
+ compare_price: item.price_info[0].original_price,
593
+ cost: 0,
594
+ spec: [],
595
+ profit: 0,
596
+ v_length: item.dimension.package_length,
597
+ v_width: item.dimension.package_width,
598
+ v_height: item.dimension.package_height,
599
+ weight: item.weight,
600
+ shipment_type: 'none',
601
+ sku: '',
602
+ barcode: '',
603
+ stock: item.stock_info_v2.summary_info.total_available_stock,
604
+ stockList: {},
605
+ preview_image: '',
606
+ show_understocking: 'true',
607
+ type: 'product',
460
608
  };
461
- try {
462
- const response = await axios(config);
463
- const item = response.data.response.item_list[0]
464
-
465
- //取得是否原本有資料
466
- let origData: any = {};
609
+ postMD.variants = [];
610
+ postMD.variants.push(newVariants);
611
+ } else {
612
+ //多規格
613
+ await getModel(postMD);
614
+ }
615
+ if (item.image.image_url_list.length > 0 && !(option && option.skip_image_load)) {
616
+ postMD.preview_image = await Promise.all(
617
+ item.image.image_url_list.map(async (imageUrl: string, index: number) => {
467
618
  try {
468
- origData = await db.query(`SELECT *
469
- FROM \`${this.app}\`.t_manager_post
470
- WHERE (content ->>'$.type'='product')
471
- AND (content ->>'$.shopee_id' = ?);`, [id])
472
- } catch (e: any) {
619
+ const buffer = await this.downloadImage(imageUrl);
620
+ const fileExtension = 'jpg';
621
+ const fileName = `shopee/${postMD.title}/${new Date().getTime()}_${index}.${fileExtension}`;
473
622
 
623
+ const uploadedData = await this.uploadFile(fileName, buffer);
624
+ return uploadedData; // 返回上傳後的資料
625
+ } catch (error) {
626
+ console.error('下載或上傳失敗:', error);
627
+ return null; // 返回 null 以處理失敗的情況
474
628
  }
475
- let postMD: {
476
- template: string;
477
- visible: string;
478
- preview_image: any[];
479
- relative_product: any[];
480
- active_schedule: { endDate: undefined; startTime: string; endTime: undefined; startDate: string };
481
- content_array: any[];
482
- channel: string[];
483
- collection: any[];
484
- variants: any[];
485
- title: string;
486
- ai_description: string;
487
- content: string;
488
- specs: any[];
489
- language_data: {
490
- "en-US": {
491
- content_array: any[];
492
- title: string;
493
- seo: { keywords: string; domain: string; title: string; content: string };
494
- content: string
495
- };
496
- "zh-TW": { title: any; seo: any };
497
- "zh-CN": {
498
- content_array: any[];
499
- title: string;
500
- seo: { keywords: string; domain: string; title: string; content: string };
501
- content: string
502
- }
503
- };
504
- hideIndex: string;
505
- seo: { keywords: string; domain: string; title: string; content: string };
506
- productType: { product: boolean; addProduct: boolean; giveaway: boolean };
507
- content_json: any[];
508
- status: string
509
- };
510
-
511
- postMD = this.getInitial({});
512
- if (origData.length > 0) {
513
- postMD = {
514
- ...postMD,
515
- ...origData[0]
516
- }
517
- }
518
-
519
- postMD.title = item.item_name;
520
- // 兩邊商品介紹結構不同
521
- if (item.description_info && item.description_info.extended_description.field_list.length > 0) {
522
- let temp = ``;
523
- const promises = item.description_info.extended_description.field_list.map(async (item1: any) => {
524
- if (item1.field_type == 'image' && !(option && option.skip_image_load)) {
525
- try {
526
- const buffer = await this.downloadImage(item1.image_info.image_url);
527
- const fileExtension = "jpg";
528
- const fileName = `shopee/${postMD.title}/${new Date().getTime()}_${item1.image_info.image_id}.${fileExtension}`;
529
- item1.image_info.s3 = await this.uploadFile(fileName, buffer);
530
- } catch (error) {
531
- console.error('下載或上傳失敗:', error);
532
- // 你可以根据需求选择是否返回 null 或其他处理方式
533
- }
534
- }
535
- });
536
- const html = String.raw;
537
- await Promise.all(promises);
538
- if(item.description_info && item.description_info.extended_description){
539
- item.description_info.extended_description.field_list.map((item: any) => {
540
- if (item.field_type == 'image' && !(option && option.skip_image_load)) {
541
- temp += html`
542
- <div style="white-space: pre-wrap;"><img src="${item.image_info.s3}"
543
- alt='${item.image_info.image_id}'></div>`
544
- } else if (item.field_type == 'text') {
545
- temp += html`
546
- <div style="white-space: pre-wrap;">${item.text}</div>`
547
- }
548
- })
549
- }
550
-
551
- postMD.content = temp;
552
- }
629
+ })
630
+ );
631
+ }
553
632
 
554
- if (item.price_info) {
555
- //單規格
556
- let newVariants: Variant = {
557
- sale_price: item.price_info[0].current_price,
558
- compare_price: item.price_info[0].original_price,
559
- cost: 0,
560
- spec: [],
561
- profit: 0,
562
- v_length: item.dimension.package_length,
563
- v_width: item.dimension.package_width,
564
- v_height: item.dimension.package_height,
565
- weight: item.weight,
566
- shipment_type: 'none',
567
- sku: "",
568
- barcode: "",
569
- stock: item.stock_info_v2.summary_info.total_available_stock,
570
- stockList: {},
571
- preview_image: "",
572
- show_understocking: "true",
573
- type: "product",
574
- };
575
- postMD.variants = [];
576
- postMD.variants.push(newVariants);
577
- } else {
578
- //多規格
579
- await getModel(postMD);
580
- }
581
- if (item.image.image_url_list.length > 0 && !(option && option.skip_image_load)) {
582
- postMD.preview_image = await Promise.all(
583
- item.image.image_url_list.map(async (imageUrl: string, index: number) => {
584
- try {
585
- const buffer = await this.downloadImage(imageUrl);
586
- const fileExtension = "jpg";
587
- const fileName = `shopee/${postMD.title}/${new Date().getTime()}_${index}.${fileExtension}`;
588
-
589
- const uploadedData = await this.uploadFile(fileName, buffer);
590
- return uploadedData; // 返回上傳後的資料
591
- } catch (error) {
592
- console.error('下載或上傳失敗:', error);
593
- return null; // 返回 null 以處理失敗的情況
594
- }
595
- })
596
- );
597
- }
633
+ //把蝦皮的商品id寫回
634
+ (postMD as any).shopee_id = id;
635
+ return postMD;
636
+ } catch (error: any) {
637
+ if (axios.isAxiosError(error) && error.response) {
638
+ console.error('Error Response:', error.response.data);
639
+ } else {
640
+ console.error('Unexpected Error:', error.message);
641
+ }
642
+ }
643
+ }
598
644
 
599
- //把蝦皮的商品id寫回
600
- (postMD as any).shopee_id = id;
601
- return postMD;
602
- } catch (error: any) {
603
- if (axios.isAxiosError(error) && error.response) {
604
- console.error('Error Response:', error.response.data);
605
- } else {
606
- console.error('Unexpected Error:', error.message);
607
- }
608
- }
645
+ public async asyncStockToShopee(obj: {
646
+ product: any;
647
+ access_token?: string;
648
+ shop_id?: string;
649
+ callback: (response?: any) => void;
650
+ }) {
651
+ console.log(`asyncStockToShopee===>`);
652
+ if (!obj.access_token || !obj.shop_id) {
653
+ const access = await new Shopee(this.app, this.token).fetchShopeeAccessToken();
654
+ obj.access_token = access.access_token;
655
+ obj.shop_id = access.shop_id;
656
+ }
657
+ if (!obj.product.content.shopee_id) {
658
+ //沒有shopee_id的話直接回去
659
+ obj.callback();
660
+ return;
609
661
  }
610
- public async asyncStockToShopee(obj:{
611
- product:any,
612
- access_token?:string,
613
- shop_id?:string
614
- callback:(response?:any)=>void
615
- }){
616
- console.log(`asyncStockToShopee===>`)
617
- if(!obj.access_token || !obj.shop_id){
618
- const access = await new Shopee(this.app , this.token).fetchShopeeAccessToken();
619
- obj.access_token=access.access_token
620
- obj.shop_id=access.shop_id
662
+ let basicData: {
663
+ item_id: string;
664
+ stock_list: any[];
665
+ } = {
666
+ item_id: obj.product.content.shopee_id,
667
+ stock_list: [],
668
+ };
669
+ const partner_id = Shopee.partner_id ?? '';
670
+ const api_path = '/api/v2/product/get_model_list';
671
+ const timestamp = Math.floor(Date.now() / 1000);
672
+
673
+ const config = {
674
+ method: 'get',
675
+ url: this.generateShopUrl(partner_id, api_path, timestamp, obj.access_token!!, parseInt(obj.shop_id!!)),
676
+ headers: {
677
+ 'Content-Type': 'application/json',
678
+ },
679
+ params: {
680
+ shop_id: parseInt(obj.shop_id!!),
681
+ access_token: obj.access_token,
682
+ item_id: obj.product.content.shopee_id,
683
+ },
684
+ };
685
+ try {
686
+ const response = await axios(config);
687
+ if (!response.data?.response?.model) {
688
+ obj.callback(response.data);
689
+ }
690
+ //找到兩個名字相同的 把儲存model_id 還有庫存
691
+ obj.product.content.variants.map((variant: any) => {
692
+ let basicStock = {
693
+ model_id: 0,
694
+ seller_stock: [
695
+ {
696
+ stock: 0,
697
+ },
698
+ ],
699
+ };
700
+ let findModel = response.data.response.model.find((item: any) => {
701
+ return item.model_name == variant.spec.join(',');
702
+ });
703
+ console.log(`findModel===>`, findModel);
704
+ if (findModel || response.data.response.model.length == 0) {
705
+ basicStock.model_id = (findModel && findModel.model_id) || 0;
706
+ //shopee 單倉儲的情形
707
+ basicStock.seller_stock[0].stock = variant.stock;
708
+ basicData.stock_list.push(basicStock);
621
709
  }
622
- if (!obj.product.content.shopee_id){
623
- //沒有shopee_id的話直接回去
624
- obj.callback();
625
- return
710
+ });
711
+ // 同步這個商品的庫存
712
+ const updateConfig = {
713
+ method: 'post',
714
+ url: this.generateShopUrl(
715
+ partner_id,
716
+ '/api/v2/product/update_stock',
717
+ timestamp,
718
+ obj.access_token!!,
719
+ parseInt(obj.shop_id!!)
720
+ ),
721
+ params: {
722
+ shop_id: parseInt(obj.shop_id!!),
723
+ access_token: obj.access_token,
724
+ },
725
+ headers: {
726
+ 'Content-Type': 'application/json',
727
+ },
728
+ data: JSON.stringify(basicData),
729
+ };
730
+ try {
731
+ const response = await axios(updateConfig);
732
+ console.log(`update_stock`, JSON.stringify(basicData));
733
+ console.log(`update_stock`, response.data);
734
+ obj.callback(response.data);
735
+ } catch (error: any) {
736
+ if (axios.isAxiosError(error) && error.response) {
737
+ console.error('Error Response:', error.response.data);
738
+ } else {
739
+ console.error('Unexpected Error:', error.message);
626
740
  }
627
- let basicData:{
628
- item_id:string,
629
- stock_list:any[]
630
- } = {
631
- "item_id": obj.product.content.shopee_id,
632
- "stock_list": []
633
- };
634
- const partner_id = Shopee.partner_id ?? "";
635
- const api_path = "/api/v2/product/get_model_list";
636
- const timestamp = Math.floor(Date.now() / 1000);
741
+ }
742
+ } catch (error: any) {
743
+ if (axios.isAxiosError(error) && error.response) {
744
+ console.error('Error get_model_list Response:', error.response.data);
745
+ } else {
746
+ console.error('Unexpected Error:', error.message);
747
+ }
748
+ }
749
+ }
637
750
 
751
+ public async asyncStockFromShopnex(): Promise<any> {
752
+ let origData: any = {};
753
+ try {
754
+ origData = await db.query(
755
+ `SELECT *
756
+ FROM \`${this.app}\`.t_manager_post
757
+ WHERE (content ->>'$.type'='product')
758
+ AND (content ->>'$.shopee_id' IS NOT NULL AND content ->>'$.shopee_id' <> '')`,
759
+ []
760
+ );
761
+ let temp = await this.fetchShopeeAccessToken();
762
+ return Promise.all(
763
+ origData.map(
764
+ (product: any) =>
765
+ new Promise<void>((resolve, reject) => {
766
+ try {
767
+ this.asyncStockToShopee({
768
+ product: product,
769
+ callback: () => {
770
+ resolve(); // 當 `asyncStockToShopee` 執行完畢後標記為完成
771
+ },
772
+ access_token: temp.access_token,
773
+ shop_id: temp.shop_id,
774
+ });
775
+ } catch (e: any) {
776
+ reject(e); // 捕獲錯誤並拒絕該 Promise
777
+ }
778
+ })
779
+ )
780
+ )
781
+ .then(() => {
782
+ console.log('所有產品的庫存同步完成!');
783
+ return {
784
+ result: 'OK',
785
+ };
786
+ })
787
+ .catch(error => {
788
+ console.error('同步庫存時發生錯誤:', error);
789
+ });
790
+ } catch (e: any) {}
791
+ }
792
+
793
+ public async fetchShopeeAccessToken(): Promise<any> {
794
+ try {
795
+ const sqlData = await db.execute(
796
+ `SELECT *
797
+ FROM \`${saasConfig.SAAS_NAME}\`.private_config
798
+ WHERE \`app_name\` = '${this.app}'
799
+ AND \`key\` = 'shopee_access_token'`,
800
+ []
801
+ );
802
+
803
+ const obj: any = {};
804
+ obj.accessToken = sqlData;
805
+ //如果超過4小時,代表過期了需要刷新
806
+ if (new Date().getTime() >= new Date(sqlData[0].updated_at).getTime() + 3.9 * 3600 * 1000) {
807
+ console.log(`確認要刷新token`);
808
+ const partner_id = Shopee.partner_id!!;
809
+ const api_path = '/api/v2/auth/access_token/get';
810
+ const timestamp = Math.floor(Date.now() / 1000);
638
811
  const config = {
639
- method: 'get',
640
- url: this.generateShopUrl(partner_id, api_path, timestamp, obj.access_token!!, parseInt(obj.shop_id!!)),
641
- headers: {
642
- 'Content-Type': 'application/json',
643
- },
644
- params: {
645
- shop_id: parseInt(obj.shop_id!!),
646
- access_token: obj.access_token,
647
- item_id: obj.product.content.shopee_id
648
- },
812
+ method: 'post',
813
+ url: this.generateUrl(partner_id, api_path, timestamp),
814
+ headers: {
815
+ 'Content-Type': 'application/json',
816
+ },
817
+ data: JSON.stringify({
818
+ shop_id: parseInt(obj.accessToken[0].value.shop_id),
819
+ refresh_token: obj.accessToken[0].value.refresh_token,
820
+ partner_id: parseInt(partner_id),
821
+ }),
649
822
  };
650
823
  try {
651
- const response = await axios(config);
652
- if (!response.data?.response?.model){
653
- obj.callback(response.data);
654
- }
655
- //找到兩個名字相同的 把儲存model_id 還有庫存
656
- obj.product.content.variants.map((variant:any)=>{
657
- let basicStock = {
658
- "model_id": 0,
659
- "seller_stock": [
660
- {
661
- "stock": 0
662
- }
663
- ]
664
- };
665
- let findModel = response.data.response.model.find((item:any)=>{return item.model_name == variant.spec.join(',')});
666
- console.log(`findModel===>`,findModel)
667
- if (findModel || response.data.response.model.length == 0){
668
- basicStock.model_id = (findModel && findModel.model_id) || 0;
669
- //shopee 單倉儲的情形
670
- basicStock.seller_stock[0].stock = variant.stock;
671
- basicData.stock_list.push(basicStock);
672
- }
673
- })
674
- // 同步這個商品的庫存
675
- const updateConfig = {
676
- method: 'post',
677
- url: this.generateShopUrl(partner_id, "/api/v2/product/update_stock", timestamp, obj.access_token!!, parseInt(obj.shop_id!!)),
678
- params: {
679
- shop_id: parseInt(obj.shop_id!!),
680
- access_token: obj.access_token,
681
- },
682
- headers: {
683
- 'Content-Type': 'application/json',
684
- },
685
- data: JSON.stringify(basicData)
686
- };
687
- try {
688
- const response = await axios(updateConfig);
689
- console.log(`update_stock`,JSON.stringify(basicData))
690
- console.log(`update_stock`,response.data)
691
- obj.callback(response.data);
692
- } catch (error: any) {
693
- if (axios.isAxiosError(error) && error.response) {
694
- console.error('Error Response:', error.response.data);
695
- } else {
696
- console.error('Unexpected Error:', error.message);
697
- }
698
- }
699
- } catch (error: any) {
700
- if (axios.isAxiosError(error) && error.response) {
701
- console.error('Error get_model_list Response:', error.response.data);
702
- } else {
703
- console.error('Unexpected Error:', error.message);
704
- }
824
+ const response = await axios(config);
825
+ try {
826
+ await db.execute(
827
+ `
828
+ UPDATE \`${saasConfig.SAAS_NAME}\`.\`private_config\`
829
+ SET \`value\` = ?,
830
+ updated_at = ?
831
+ where \`app_name\` = '${this.app}'
832
+ and \`key\` = 'shopee_access_token'
833
+ `,
834
+ [response.data, new Date()]
835
+ );
836
+ return response.data;
837
+ } catch (e: any) {
838
+ console.error('refresh private_config shopee_access_token error : ', e.data);
839
+ }
840
+ } catch (e: any) {
841
+ console.error('Shopee access token API request failed:', e);
705
842
  }
843
+ } else {
844
+ return sqlData[0].value;
845
+ }
846
+ } catch (e: any) {
847
+ console.error('Database query for Shopee access token failed:', e);
706
848
  }
707
- public async asyncStockFromShopnex(): Promise<any> {
708
- let origData: any = {};
709
- try {
710
- origData = await db.query(`SELECT *
711
- FROM \`${this.app}\`.t_manager_post
712
- WHERE (content ->>'$.type'='product')
713
- AND (content ->>'$.shopee_id' IS NOT NULL AND content ->>'$.shopee_id' <> '')`, [])
714
- let temp = await this.fetchShopeeAccessToken();
715
- return Promise.all(
716
- origData.map((product: any) =>
717
- new Promise<void>((resolve, reject) => {
718
- try {
719
- this.asyncStockToShopee({
720
- product: product,
721
- callback: () => {
722
- resolve(); // 當 `asyncStockToShopee` 執行完畢後標記為完成
723
- },
724
- access_token:temp.access_token,
725
- shop_id:temp.shop_id
726
- });
727
- } catch (e: any) {
728
- reject(e); // 捕獲錯誤並拒絕該 Promise
729
- }
730
- })
731
- )
732
- ).then(() => {
733
- console.log("所有產品的庫存同步完成!");
734
- return{
735
- result: "OK",
736
- }
737
-
738
- }).catch((error) => {
739
- console.error("同步庫存時發生錯誤:", error);
740
- });
849
+ }
741
850
 
742
- } catch (e: any) {
851
+ public async getOrderList(start: string, end: string, index: number = 0) {
852
+ const timestamp = Math.floor(Date.now() / 1000);
853
+ const partner_id = Shopee.partner_id ?? '';
854
+ const api_path = '/api/v2/order/get_order_list';
855
+ await this.fetchShopeeAccessToken();
856
+ console.log();
857
+ const data = await db.execute(
858
+ `select *
859
+ from \`${saasConfig.SAAS_NAME}\`.private_config
860
+ where \`app_name\` = '${this.app}'
861
+ and \`key\` = 'shopee_access_token'
862
+ `,
863
+ []
864
+ );
743
865
 
866
+ const config = {
867
+ method: 'get',
868
+ url: this.generateShopUrl(
869
+ partner_id,
870
+ api_path,
871
+ timestamp,
872
+ data[0].value.access_token,
873
+ parseInt(data[0].value.shop_id)
874
+ ),
875
+ headers: {
876
+ 'Content-Type': 'application/json',
877
+ },
878
+ params: {
879
+ shop_id: parseInt(data[0].value.shop_id),
880
+ access_token: data[0].value.access_token,
881
+ offset: index || 0,
882
+ page_size: 10,
883
+ update_time_from: start,
884
+ update_time_to: Math.floor(Date.now() / 1000),
885
+ item_status: ['NORMAL', 'BANNED', 'UNLIST'],
886
+ },
887
+ paramsSerializer: (params: any) => qs.stringify(params, { arrayFormat: 'repeat' }),
888
+ };
889
+ try {
890
+ const response = await axios(config);
891
+ if (response.data.error.length > 0) {
892
+ return {
893
+ type: 'error',
894
+ message: response.data.error,
895
+ };
896
+ }
897
+ console.log("order response -- " , response.data);
898
+ if (response.data.response.total_count == 0) {
899
+ return {
900
+ type: 'success',
901
+ data: response.data.response,
902
+ message: '該時間區間查無商品',
744
903
  }
745
- }
746
- public async fetchShopeeAccessToken(): Promise<any> {
747
- try {
748
- const sqlData = await db.execute(
749
- `SELECT *
750
- FROM \`${saasConfig.SAAS_NAME}\`.private_config
751
- WHERE \`app_name\` = '${this.app}'
752
- AND \`key\` = 'shopee_access_token'`,
753
- []
754
- );
904
+ }
755
905
 
756
- const obj: any = {};
757
- obj.accessToken = sqlData;
758
- //如果超過4小時,代表過期了需要刷新
759
- if (new Date().getTime() >= (new Date(sqlData[0].updated_at).getTime() + (3.9 * 3600 * 1000))){
760
- console.log(`確認要刷新token`)
761
- const partner_id = Shopee.partner_id!!;
762
- const api_path = "/api/v2/auth/access_token/get";
763
- const timestamp = Math.floor(Date.now() / 1000);
764
- const config = {
765
- method: 'post',
766
- url: this.generateUrl(partner_id, api_path, timestamp),
767
- headers: {
768
- 'Content-Type': 'application/json',
769
- },
770
- data: JSON.stringify({
771
- shop_id: parseInt(obj.accessToken[0].value.shop_id),
772
- refresh_token: obj.accessToken[0].value.refresh_token,
773
- partner_id: parseInt(partner_id)
774
- }),
775
- };
776
- try {
777
- const response = await axios(config);
778
- try {
779
- await db.execute(`
780
- UPDATE \`${saasConfig.SAAS_NAME}\`.\`private_config\`
781
- SET \`value\` = ? , updated_at = ?
782
- where \`app_name\` = '${this.app}'
783
- and \`key\` = 'shopee_access_token'
784
- `, [response.data,new Date()])
785
- return response.data
786
- }catch (e:any){
787
- console.error("refresh private_config shopee_access_token error : ", e.data);
788
- }
789
-
790
- } catch (e: any) {
791
- console.error("Shopee access token API request failed:", e);
792
- }
793
- }else {
794
- return sqlData[0].value;
795
- }
906
+ return
907
+ const itemList: {
908
+ item_id: number;
909
+ item_status: string;
910
+ update_time: number;
911
+ }[] = response.data.response.item;
796
912
 
797
-
798
- } catch (e: any) {
799
- console.error("Database query for Shopee access token failed:", e);
800
- }
801
- }
802
- public getInitial(obj: any) {
803
- function getEmptyLanguageData() {
804
- return {
805
- title: '',
806
- seo: {
807
- domain: '',
808
- title: '',
809
- content: '',
810
- keywords: '',
811
- },
812
- content: '',
813
- content_array: [],
814
- };
913
+ const productData = await Promise.all(
914
+ itemList.map(async (item, index: number) => {
915
+ console.log('here -- OK');
916
+ try {
917
+ const pd_data = await db.query(
918
+ `SELECT count(1)
919
+ FROM ${this.app}.t_manager_post
920
+ WHERE (content ->>'$.type'='product')
921
+ AND (content ->>'$.shopee_id' = ${item.item_id});`,
922
+ []
923
+ );
924
+ if (pd_data[0]['count(1)'] > 0) {
925
+ return null;
926
+ } else {
927
+ return await this.getProductDetail(item.item_id); // 返回上傳後的資料
928
+ }
929
+ } catch (error) {
930
+ return null; // 返回 null 以處理失敗的情況
931
+ }
932
+ })
933
+ );
934
+ const temp: any = {};
935
+ temp.data = productData.reverse().filter(dd => {
936
+ return dd;
937
+ });
938
+ temp.collection = [];
939
+ try {
940
+ await new Shopping(this.app, this.token).postMulProduct(temp);
941
+ if (response.data.response.has_next_page) {
942
+ await this.getItemList(start, end, response.data.response.next_offset);
815
943
  }
944
+ return {
945
+ data: temp.data,
946
+ message: '匯入OK',
947
+ };
948
+ } catch (error: any) {
949
+ console.error(error);
950
+ //失敗繼續跑匯入
951
+ // if (response.data.response.has_next_page) {
952
+ // await this.getItemList(start, end, response.data.response.next_offset)
953
+ // }
954
+ return {
955
+ type: 'error',
956
+ data: temp.data,
957
+ message: '產品匯入資料庫失敗',
958
+ };
959
+ }
960
+ } catch (error: any) {
961
+ if (axios.isAxiosError(error) && error.response) {
962
+ console.log('Try get_item_list error');
963
+ console.error('Error Response:', error.response.data);
816
964
 
817
965
  return {
818
- type: 'product',
819
- title: '',
820
- ai_description: '',
821
- language_data: {
822
- 'en-US': getEmptyLanguageData(),
823
- 'zh-CN': getEmptyLanguageData(),
824
- 'zh-TW': {
825
- title: (obj.defData && obj.defData.title) || '',
826
- seo: (obj.defData && obj.defData.seo) || {},
827
- },
828
- },
829
- productType: {
830
- product: true,
831
- addProduct: false,
832
- giveaway: false,
833
- },
834
- content: '',
835
- visible: 'true',
836
- status: 'active',
837
- collection: [],
838
- hideIndex: 'false',
839
- preview_image: [],
840
- specs: [],
841
- variants: [],
842
- seo: {
843
- title: '',
844
- content: '',
845
- keywords: '',
846
- domain: '',
847
- },
848
- relative_product: [],
849
- template: '',
850
- content_array: [],
851
- content_json: [],
852
- active_schedule: {
853
- startDate: this.getDateTime().date,
854
- startTime: this.getDateTime().time,
855
- endDate: undefined,
856
- endTime: undefined,
857
- },
858
- channel: ['normal', 'pos'],
966
+ type: 'error',
967
+ error: error.response.data.error,
968
+ message: error.response.data.message,
859
969
  };
970
+ } else {
971
+ console.error('Unexpected Error:', error.message);
972
+ }
860
973
  }
974
+ }
861
975
 
862
- private getDateTime = (n = 0) => {
863
- const now = new Date();
864
- now.setDate(now.getDate() + n);
865
- const year = now.getFullYear();
866
- const month = (now.getMonth() + 1).toString().padStart(2, '0');
867
- const day = now.getDate().toString().padStart(2, '0');
868
- const hours = now.getHours().toString().padStart(2, '0');
869
- const dateStr = `${year}-${month}-${day}`;
870
- const timeStr = `${hours}:00`;
871
- return {date: dateStr, time: timeStr};
976
+ public getInitial(obj: any) {
977
+ function getEmptyLanguageData() {
978
+ return {
979
+ title: '',
980
+ seo: {
981
+ domain: '',
982
+ title: '',
983
+ content: '',
984
+ keywords: '',
985
+ },
986
+ content: '',
987
+ content_array: [],
988
+ };
989
+ }
990
+
991
+ return {
992
+ type: 'product',
993
+ title: '',
994
+ ai_description: '',
995
+ language_data: {
996
+ 'en-US': getEmptyLanguageData(),
997
+ 'zh-CN': getEmptyLanguageData(),
998
+ 'zh-TW': {
999
+ title: (obj.defData && obj.defData.title) || '',
1000
+ seo: (obj.defData && obj.defData.seo) || {},
1001
+ },
1002
+ },
1003
+ productType: {
1004
+ product: true,
1005
+ addProduct: false,
1006
+ giveaway: false,
1007
+ },
1008
+ content: '',
1009
+ visible: 'true',
1010
+ status: 'active',
1011
+ collection: [],
1012
+ hideIndex: 'false',
1013
+ preview_image: [],
1014
+ specs: [],
1015
+ variants: [],
1016
+ seo: {
1017
+ title: '',
1018
+ content: '',
1019
+ keywords: '',
1020
+ domain: '',
1021
+ },
1022
+ relative_product: [],
1023
+ template: '',
1024
+ content_array: [],
1025
+ content_json: [],
1026
+ active_schedule: {
1027
+ startDate: this.getDateTime().date,
1028
+ startTime: this.getDateTime().time,
1029
+ endDate: undefined,
1030
+ endTime: undefined,
1031
+ },
1032
+ channel: ['normal', 'pos'],
872
1033
  };
1034
+ }
873
1035
 
874
- async uploadFile(file_name: string, fileData: Buffer) {
875
- const TAG = `[AWS-S3][Upload]`;
876
- const logger = new Logger();
877
- const s3bucketName = config.AWS_S3_NAME;
878
- const s3path = file_name;
879
- const fullUrl = config.AWS_S3_PREFIX_DOMAIN_NAME + s3path;
880
- const params = {
881
- Bucket: s3bucketName,
882
- Key: s3path,
883
- Expires: 300,
884
- //If you use other contentType will response 403 error
885
- ContentType: (() => {
886
- if (config.SINGLE_TYPE) {
887
- return `application/x-www-form-urlencoded; charset=UTF-8`;
888
- } else {
889
- return mime.getType(<string>fullUrl.split('.').pop());
890
- }
891
- })(),
892
- };
893
- return new Promise<string>((resolve, reject) => {
894
- s3bucket.getSignedUrl('putObject', params, async (err: any, url: any) => {
895
- if (err) {
896
- logger.error(TAG, String(err));
897
- // use console.log here because logger.info cannot log err.stack correctly
898
- console.log(err, err.stack);
899
- reject(false);
900
- } else {
901
- axios({
902
- method: 'PUT',
903
- url: url,
904
- data: fileData,
905
- headers: {
906
- 'Content-Type': params.ContentType,
907
- },
908
- })
909
- .then(() => {
910
- resolve(fullUrl);
911
- })
912
- .catch(() => {
913
- console.log(`convertError:${fullUrl}`);
914
- });
915
- }
916
- });
917
- });
918
- }
1036
+ private getDateTime = (n = 0) => {
1037
+ const now = new Date();
1038
+ now.setDate(now.getDate() + n);
1039
+ const year = now.getFullYear();
1040
+ const month = (now.getMonth() + 1).toString().padStart(2, '0');
1041
+ const day = now.getDate().toString().padStart(2, '0');
1042
+ const hours = now.getHours().toString().padStart(2, '0');
1043
+ const dateStr = `${year}-${month}-${day}`;
1044
+ const timeStr = `${hours}:00`;
1045
+ return { date: dateStr, time: timeStr };
1046
+ };
919
1047
 
920
- async downloadImage(imageUrl: string): Promise<Buffer> {
921
- try {
922
- const response = await axios.get(imageUrl, {
923
- headers: {},
924
- responseType: 'arraybuffer', // 下載二進制資料
1048
+ async uploadFile(file_name: string, fileData: Buffer) {
1049
+ const TAG = `[AWS-S3][Upload]`;
1050
+ const logger = new Logger();
1051
+ const s3bucketName = config.AWS_S3_NAME;
1052
+ const s3path = file_name;
1053
+ const fullUrl = config.AWS_S3_PREFIX_DOMAIN_NAME + s3path;
1054
+ const params = {
1055
+ Bucket: s3bucketName,
1056
+ Key: s3path,
1057
+ Expires: 300,
1058
+ //If you use other contentType will response 403 error
1059
+ ContentType: (() => {
1060
+ if (config.SINGLE_TYPE) {
1061
+ return `application/x-www-form-urlencoded; charset=UTF-8`;
1062
+ } else {
1063
+ return mime.getType(<string>fullUrl.split('.').pop());
1064
+ }
1065
+ })(),
1066
+ };
1067
+ return new Promise<string>((resolve, reject) => {
1068
+ s3bucket.getSignedUrl('putObject', params, async (err: any, url: any) => {
1069
+ if (err) {
1070
+ logger.error(TAG, String(err));
1071
+ // use console.log here because logger.info cannot log err.stack correctly
1072
+ console.log(err, err.stack);
1073
+ reject(false);
1074
+ } else {
1075
+ axios({
1076
+ method: 'PUT',
1077
+ url: url,
1078
+ data: fileData,
1079
+ headers: {
1080
+ 'Content-Type': params.ContentType,
1081
+ },
1082
+ })
1083
+ .then(() => {
1084
+ resolve(fullUrl);
1085
+ })
1086
+ .catch(() => {
1087
+ console.log(`convertError:${fullUrl}`);
925
1088
  });
926
-
927
- return Buffer.from(response.data);
928
- } catch (error) {
929
- console.error('下載圖片時出錯:', error);
930
- throw error;
931
1089
  }
1090
+ });
1091
+ });
1092
+ }
1093
+
1094
+ async downloadImage(imageUrl: string): Promise<Buffer> {
1095
+ try {
1096
+ const response = await axios.get(imageUrl, {
1097
+ headers: {},
1098
+ responseType: 'arraybuffer', // 下載二進制資料
1099
+ });
1100
+
1101
+ return Buffer.from(response.data);
1102
+ } catch (error) {
1103
+ console.error('下載圖片時出錯:', error);
1104
+ throw error;
932
1105
  }
1106
+ }
933
1107
  }