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.
- package/lowcode/Entry.js +1 -1
- package/lowcode/Entry.ts +1 -1
- package/lowcode/cms-plugin/account-info.js +79 -67
- package/lowcode/cms-plugin/account-info.ts +327 -311
- package/lowcode/cms-plugin/information/information-module.ts +3 -1
- package/lowcode/cms-plugin/list-header-option.js +1 -0
- package/lowcode/cms-plugin/list-header-option.ts +1 -0
- package/lowcode/cms-plugin/shopping-information.js +468 -0
- package/lowcode/cms-plugin/shopping-information.ts +557 -0
- package/lowcode/cms-plugin/shopping-market-shopee.js +283 -231
- package/lowcode/cms-plugin/shopping-market-shopee.ts +401 -361
- package/lowcode/cms-plugin/shopping-order-manager.js +16 -4
- package/lowcode/cms-plugin/shopping-order-manager.ts +18 -3
- package/lowcode/glitter-base/global/language.js +4 -1
- package/lowcode/glitter-base/global/language.ts +4 -2
- package/lowcode/glitter-base/route/shopee.js +48 -11
- package/lowcode/glitter-base/route/shopee.ts +119 -80
- package/lowcode/glitterBundle/plugins/html-render.js +121 -90
- package/lowcode/glitterBundle/plugins/html-render.ts +367 -318
- package/lowcode/modules/image-library.js +2 -3
- package/lowcode/modules/image-library.ts +21 -7
- package/lowcode/public-components/checkout/index.js +90 -41
- package/lowcode/public-components/checkout/index.ts +101 -49
- package/lowcode/public-components/footer/footer-initial.js +11 -2
- package/lowcode/public-components/footer/footer-initial.ts +29 -18
- package/lowcode/public-components/headers/header-class.js +47 -35
- package/lowcode/public-components/headers/header-class.ts +54 -38
- package/lowcode/public-components/layout-plugin/social-links-01.js +122 -3
- package/lowcode/public-components/layout-plugin/social-links-01.ts +135 -10
- package/lowcode/public-components/product/pd-card-01.js +23 -14
- package/lowcode/public-components/product/pd-card-01.ts +25 -14
- package/lowcode/public-components/product/pd-card-02.js +23 -16
- package/lowcode/public-components/product/pd-card-02.ts +25 -16
- package/lowcode/public-components/product/pd-card-03.js +25 -16
- package/lowcode/public-components/product/pd-card-03.ts +27 -16
- package/lowcode/public-components/terms-related/index.js +13 -2
- package/lowcode/public-components/terms-related/index.ts +15 -2
- package/lowcode/public-components/user-manager/um-class.js +490 -501
- package/lowcode/public-components/user-manager/um-class.ts +872 -882
- package/lowcode/public-components/user-manager/um-info.js +41 -40
- package/lowcode/public-components/user-manager/um-info.ts +54 -56
- package/lowcode/public-components/user-manager/um-login.js +10 -13
- package/lowcode/public-components/user-manager/um-login.ts +15 -23
- package/lowcode/public-components/user-manager/um-orderlist.js +60 -51
- package/lowcode/public-components/user-manager/um-orderlist.ts +289 -275
- package/lowcode/public-components/user-manager/um-rebate.js +104 -82
- package/lowcode/public-components/user-manager/um-rebate.ts +294 -267
- package/lowcode/public-components/user-manager/um-receive.js +582 -0
- package/lowcode/public-components/user-manager/um-receive.ts +599 -0
- package/lowcode/public-components/user-manager/um-wishlist.js +72 -68
- package/lowcode/public-components/user-manager/um-wishlist.ts +240 -230
- package/package.json +1 -1
- package/src/api-public/controllers/shopee.js +17 -0
- package/src/api-public/controllers/shopee.js.map +1 -1
- package/src/api-public/controllers/shopee.ts +32 -0
- package/src/api-public/services/monitor.d.ts +1 -0
- package/src/api-public/services/post.js +17 -7
- package/src/api-public/services/post.js.map +1 -1
- package/src/api-public/services/rebate.js +2 -11
- package/src/api-public/services/rebate.js.map +1 -1
- package/src/api-public/services/rebate.ts +5 -12
- package/src/api-public/services/shopee.d.ts +23 -2
- package/src/api-public/services/shopee.js +230 -111
- package/src/api-public/services/shopee.js.map +1 -1
- package/src/api-public/services/shopee.ts +1012 -838
- package/src/api-public/services/user.js +2 -2
- package/src/api-public/services/user.js.map +1 -1
- package/src/api-public/services/user.ts +3 -3
- package/src/index.js +17 -7
- package/src/index.js.map +1 -1
- package/src/modules/tool.d.ts +4 -4
- package/src/modules/tool.js +2 -1
- package/src/modules/tool.js.map +1 -1
- package/src/services/backend-service.js +17 -7
- package/src/services/backend-service.js.map +1 -1
- package/src/services/template.d.ts +1 -1
- package/src/services/template.js +24 -18
- package/src/services/template.js.map +1 -1
- 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
|
|
8
|
-
import process from
|
|
7
|
+
import crypto from 'crypto';
|
|
8
|
+
import process from 'process';
|
|
9
9
|
import qs from 'qs';
|
|
10
|
-
import {Shopping} from
|
|
10
|
+
import { Shopping } from './shopping.js';
|
|
11
11
|
|
|
12
12
|
const mime = require('mime');
|
|
13
13
|
type ActiveSchedule = {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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
|
-
|
|
58
|
-
|
|
56
|
+
public app;
|
|
57
|
+
public token: IToken | undefined;
|
|
59
58
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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
|
-
|
|
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
|
-
|
|
91
|
+
return `${Shopee.path}${api_path}?partner_id=${partner_id}×tamp=${timestamp}&sign=${sign}`;
|
|
92
|
+
}
|
|
91
93
|
|
|
92
|
-
|
|
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}×tamp=${timestamp}&sign=${sign}`;
|
|
104
|
+
// ?partner_id=1249034&sign=528d448cde17720098c8886aafc973c093af54a489ff4ef80198b39de958d484×tamp=1736322488
|
|
105
|
+
}
|
|
95
106
|
|
|
96
|
-
|
|
97
|
-
|
|
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
|
-
|
|
100
|
-
|
|
101
|
-
|
|
111
|
+
return crypto
|
|
112
|
+
.createHmac('sha256', partner_key ?? '')
|
|
113
|
+
.update(baseString)
|
|
114
|
+
.digest('hex');
|
|
115
|
+
}
|
|
102
116
|
|
|
103
|
-
|
|
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
|
-
|
|
106
|
-
|
|
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}×tamp=${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
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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
|
-
|
|
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
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
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
|
-
|
|
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
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
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
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
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
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
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
|
-
|
|
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
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
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
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
}
|
|
289
|
-
|
|
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
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
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
|
-
|
|
528
|
+
seo: { keywords: string; domain: string; title: string; content: string };
|
|
314
529
|
content: string;
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
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
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
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
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
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
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
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
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
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
|
-
|
|
469
|
-
|
|
470
|
-
|
|
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
|
-
|
|
476
|
-
|
|
477
|
-
|
|
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
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
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
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
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
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
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
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
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
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
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
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
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
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
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
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
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
|
-
|
|
819
|
-
|
|
820
|
-
|
|
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
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
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
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
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
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
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
|
}
|