ts-glitter 22.0.1 → 22.0.3
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 +2 -2
- package/lowcode/backend-manager/bg-blog.js +19 -5
- package/lowcode/backend-manager/bg-blog.ts +20 -5
- package/lowcode/backend-manager/bg-line.js +2 -2
- package/lowcode/backend-manager/bg-line.ts +2 -2
- package/lowcode/backend-manager/bg-notify.js +10 -3
- package/lowcode/backend-manager/bg-notify.ts +10 -3
- package/lowcode/backend-manager/bg-sns.js +3 -3
- package/lowcode/backend-manager/bg-sns.ts +3 -3
- package/lowcode/backend-manager/bg-widget.js +77 -75
- package/lowcode/backend-manager/bg-widget.ts +121 -97
- package/lowcode/cms-plugin/auto-fcm-advertise.js +2 -2
- package/lowcode/cms-plugin/auto-fcm-advertise.ts +2 -2
- package/lowcode/cms-plugin/auto-fcm-history.js +2 -2
- package/lowcode/cms-plugin/auto-fcm-history.ts +2 -2
- package/lowcode/cms-plugin/menus-setting.js +2 -2
- package/lowcode/cms-plugin/menus-setting.ts +2 -4
- package/lowcode/cms-plugin/pos-pages/payment-page.js +11 -7
- package/lowcode/cms-plugin/pos-pages/payment-page.ts +17 -11
- package/lowcode/cms-plugin/shopping-discount-setting.js +1 -1
- package/lowcode/cms-plugin/shopping-discount-setting.ts +1 -1
- package/lowcode/cms-plugin/shopping-finance-setting.js +23 -10
- package/lowcode/cms-plugin/shopping-finance-setting.ts +282 -269
- package/lowcode/cms-plugin/shopping-order-manager.js +1 -1
- package/lowcode/cms-plugin/shopping-order-manager.ts +1 -1
- package/lowcode/cms-plugin/shopping-rebate.js +1 -1
- package/lowcode/cms-plugin/shopping-rebate.ts +1 -1
- package/lowcode/cms-plugin/shopping-setting-basic.js +36 -8
- package/lowcode/cms-plugin/shopping-setting-basic.ts +36 -8
- package/lowcode/glitter-base/route/shopping.js +16 -9
- package/lowcode/glitter-base/route/shopping.ts +16 -10
- package/lowcode/glitter-base/route/user.js +19 -9
- package/lowcode/glitter-base/route/user.ts +21 -10
- package/lowcode/glitterBundle/plugins/editor-elem.js +1 -5
- package/lowcode/glitterBundle/plugins/editor-elem.ts +1 -5
- package/package.json +1 -1
- package/src/api-public/services/checkout-event.js +17 -7
- package/src/api-public/services/checkout-event.js.map +1 -1
- package/src/api-public/services/line-message.js +122 -138
- package/src/api-public/services/line-message.js.map +1 -1
- package/src/api-public/services/line-message.ts +1134 -1130
- package/src/api-public/services/shopping.js +13 -6
- package/src/api-public/services/shopping.js.map +1 -1
- package/src/api-public/services/shopping.ts +13 -7
- package/src/api-public/services/strategies/ecpay-strategy.js +0 -1
- package/src/api-public/services/strategies/ecpay-strategy.js.map +1 -1
- package/src/api-public/services/strategies/ecpay-strategy.ts +3 -4
- package/src/api-public/services/strategies/ezpay-strategy.js +0 -1
- package/src/api-public/services/strategies/ezpay-strategy.js.map +1 -1
- package/src/api-public/services/strategies/ezpay-strategy.ts +4 -7
- package/src/modules/firebase.js +0 -4
- package/src/modules/firebase.js.map +1 -1
- package/src/modules/firebase.ts +5 -12
|
@@ -3,7 +3,7 @@ import exception from '../../modules/exception.js';
|
|
|
3
3
|
import db from '../../modules/database.js';
|
|
4
4
|
import { AutoSendEmail } from './auto-send-email.js';
|
|
5
5
|
import config from '../../config.js';
|
|
6
|
-
import axios
|
|
6
|
+
import axios from 'axios';
|
|
7
7
|
import { App } from '../../services/app.js';
|
|
8
8
|
import Tool from '../../modules/tool.js';
|
|
9
9
|
import { Chat } from './chat.js';
|
|
@@ -11,1225 +11,1229 @@ import { User } from './user.js';
|
|
|
11
11
|
import Logger from '../../modules/logger.js';
|
|
12
12
|
import s3bucket from '../../modules/AWSLib.js';
|
|
13
13
|
import { Jimp } from 'jimp';
|
|
14
|
-
import redis from
|
|
15
|
-
import process from
|
|
16
|
-
import {ShopnexLineMessage} from
|
|
14
|
+
import redis from '../../modules/redis.js';
|
|
15
|
+
import process from 'process';
|
|
16
|
+
import { ShopnexLineMessage } from './model/shopnex-line-message';
|
|
17
17
|
|
|
18
18
|
const mime = require('mime');
|
|
19
19
|
interface LineResponse {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
20
|
+
// 定義 response 物件的結構,根據實際 API 回應的格式進行調整
|
|
21
|
+
clientid?: string;
|
|
22
|
+
msgid?: string;
|
|
23
|
+
statuscode: number;
|
|
24
|
+
accountPoint?: number;
|
|
25
|
+
Duplicate?: string;
|
|
26
|
+
smsPoint?: number;
|
|
27
|
+
message?: string;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
export interface ChatRoom {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
31
|
+
chat_id: string;
|
|
32
|
+
type: 'user' | 'group';
|
|
33
|
+
info: any;
|
|
34
|
+
participant: string[];
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
interface Config {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
38
|
+
method: 'post' | 'get';
|
|
39
|
+
url: string;
|
|
40
|
+
headers: Record<string, string>;
|
|
41
|
+
data: any;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
interface LineData {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
45
|
+
username: string;
|
|
46
|
+
password: string;
|
|
47
|
+
dstaddr: string;
|
|
48
|
+
smbody: string;
|
|
49
|
+
smsPointFlag: number;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
export class LineMessage {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
53
|
+
public app;
|
|
54
|
+
public token: IToken | undefined;
|
|
55
|
+
|
|
56
|
+
constructor(app: string, token?: IToken) {
|
|
57
|
+
this.app = app;
|
|
58
|
+
this.token = token ?? undefined;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async chunkSendLine(userList: any, content: any, id: number, date?: string) {
|
|
62
|
+
try {
|
|
63
|
+
// let msgid = ""
|
|
64
|
+
|
|
65
|
+
let check = userList.length;
|
|
66
|
+
await new Promise(resolve => {
|
|
67
|
+
for (const d of userList) {
|
|
68
|
+
this.sendLine({ data: content, lineID: d.lineID }, res => {
|
|
69
|
+
check--;
|
|
70
|
+
if (check === 0) {
|
|
71
|
+
db.query(
|
|
72
|
+
`UPDATE \`${this.app}\`.t_triggers
|
|
73
73
|
SET status = ${date ? 0 : 1},
|
|
74
74
|
content = JSON_SET(content, '$.name', '${res.msgid}')
|
|
75
75
|
WHERE id = ?;`,
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
}
|
|
82
|
-
});
|
|
83
|
-
} catch (e) {
|
|
84
|
-
throw exception.BadRequestError('BAD_REQUEST', 'chunkSendSns Error:' + e, null);
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
async getLineInf(obj: { lineID: string }, callback: (data: any) => void) {
|
|
89
|
-
try {
|
|
90
|
-
const post = new User(this.app, this.token);
|
|
91
|
-
|
|
92
|
-
let tokenData = await post.getConfig({
|
|
93
|
-
key: 'login_line_setting',
|
|
94
|
-
user_id: 'manager',
|
|
95
|
-
});
|
|
96
|
-
let token = `Bearer ${tokenData[0].value.message_token}`;
|
|
97
|
-
const urlConfig: Config = {
|
|
98
|
-
method: 'get',
|
|
99
|
-
url: `https://api.line.me/v2/bot/profile/${obj.lineID}`,
|
|
100
|
-
headers: {
|
|
101
|
-
'Content-Type': 'application/json',
|
|
102
|
-
Authorization: token,
|
|
103
|
-
},
|
|
104
|
-
data: {},
|
|
105
|
-
};
|
|
106
|
-
return new Promise<boolean>((resolve, reject) => {
|
|
107
|
-
axios
|
|
108
|
-
.request(urlConfig)
|
|
109
|
-
.then((response:any) => {
|
|
110
|
-
// let result = response.data.split('\r\n')
|
|
111
|
-
|
|
112
|
-
callback(response.data);
|
|
113
|
-
resolve(response.data);
|
|
114
|
-
})
|
|
115
|
-
.catch((error:any) => {
|
|
116
|
-
console.log('error -- ', error.data);
|
|
117
|
-
resolve(false);
|
|
118
|
-
});
|
|
119
|
-
});
|
|
120
|
-
} catch (e: any) {
|
|
121
|
-
throw exception.BadRequestError('BAD_REQUEST', 'send line Error:' + e.data, null);
|
|
76
|
+
[id]
|
|
77
|
+
);
|
|
78
|
+
resolve(true);
|
|
79
|
+
}
|
|
80
|
+
});
|
|
122
81
|
}
|
|
82
|
+
});
|
|
83
|
+
} catch (e) {
|
|
84
|
+
throw exception.BadRequestError('BAD_REQUEST', 'chunkSendSns Error:' + e, null);
|
|
123
85
|
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async getLineInf(obj: { lineID: string }, callback: (data: any) => void) {
|
|
89
|
+
try {
|
|
90
|
+
const post = new User(this.app, this.token);
|
|
91
|
+
|
|
92
|
+
let tokenData = await post.getConfig({
|
|
93
|
+
key: 'login_line_setting',
|
|
94
|
+
user_id: 'manager',
|
|
95
|
+
});
|
|
96
|
+
let token = `Bearer ${tokenData[0].value.message_token}`;
|
|
97
|
+
const urlConfig: Config = {
|
|
98
|
+
method: 'get',
|
|
99
|
+
url: `https://api.line.me/v2/bot/profile/${obj.lineID}`,
|
|
100
|
+
headers: {
|
|
101
|
+
'Content-Type': 'application/json',
|
|
102
|
+
Authorization: token,
|
|
133
103
|
},
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
let
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
{
|
|
202
|
-
type: 'text',
|
|
203
|
-
text: obj.data.text,
|
|
204
|
-
},
|
|
205
|
-
],
|
|
206
|
-
};
|
|
207
|
-
|
|
208
|
-
const urlConfig: Config = {
|
|
209
|
-
method: 'post',
|
|
210
|
-
url: 'https://api.line.me/v2/bot/message/push',
|
|
211
|
-
headers: {
|
|
212
|
-
'Content-Type': 'application/json',
|
|
213
|
-
Authorization: `Bearer ${token}`,
|
|
104
|
+
data: {},
|
|
105
|
+
};
|
|
106
|
+
return new Promise<boolean>((resolve, reject) => {
|
|
107
|
+
axios
|
|
108
|
+
.request(urlConfig)
|
|
109
|
+
.then((response: any) => {
|
|
110
|
+
// let result = response.data.split('\r\n')
|
|
111
|
+
|
|
112
|
+
callback(response.data);
|
|
113
|
+
resolve(response.data);
|
|
114
|
+
})
|
|
115
|
+
.catch((error: any) => {
|
|
116
|
+
console.log('error -- ', error.data);
|
|
117
|
+
resolve(false);
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
} catch (e: any) {
|
|
121
|
+
throw exception.BadRequestError('BAD_REQUEST', 'send line Error:' + e.data, null);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async sendLine(
|
|
126
|
+
obj: {
|
|
127
|
+
data: {
|
|
128
|
+
text?: string;
|
|
129
|
+
image?: string;
|
|
130
|
+
attachment?: any;
|
|
131
|
+
};
|
|
132
|
+
lineID: string;
|
|
133
|
+
},
|
|
134
|
+
callback: (data: any) => void
|
|
135
|
+
) {
|
|
136
|
+
obj.data.attachment = obj.data.attachment || '';
|
|
137
|
+
try {
|
|
138
|
+
obj.data.text = obj.data.text ? obj.data.text.replace(/<br\s*\/?>/gi, '\n').replace(/<[^>]*>/g, '') : undefined;
|
|
139
|
+
const post = new User(this.app, this.token);
|
|
140
|
+
let tokenData = await post.getConfig({
|
|
141
|
+
key: 'login_line_setting',
|
|
142
|
+
user_id: 'manager',
|
|
143
|
+
});
|
|
144
|
+
let token = `${tokenData[0].value.message_token}`;
|
|
145
|
+
if (obj.data.image) {
|
|
146
|
+
// 你要下載的圖片網址
|
|
147
|
+
const imageUrl = obj.data.image;
|
|
148
|
+
const outputFilePath = 'image_cropped.jpeg';
|
|
149
|
+
|
|
150
|
+
// 下載圖片並讀取
|
|
151
|
+
axios
|
|
152
|
+
.get(imageUrl, { responseType: 'arraybuffer' })
|
|
153
|
+
.then((response: any) => Jimp.read(Buffer.from(response.data)))
|
|
154
|
+
.then(async (image: any) => {
|
|
155
|
+
// 進行裁剪操作
|
|
156
|
+
const small = await image.clone().scaleToFit({ w: 240, h: 240 }).getBuffer('image/jpeg');
|
|
157
|
+
const large = await image.clone().scaleToFit({ w: 1024, h: 1024 }).getBuffer('image/jpeg');
|
|
158
|
+
return [small, large];
|
|
159
|
+
})
|
|
160
|
+
.then((base64: any) => {
|
|
161
|
+
// const base64Data = Buffer.from(base64.replace(/^data:image\/\w+;base64,/, ''), 'base64');
|
|
162
|
+
this.uploadFile(`line/${new Date().getTime()}.jpeg`, base64[0]).then(smallUrl => {
|
|
163
|
+
this.uploadFile(`line/${new Date().getTime()}.jpeg`, base64[1]).then(largeUrl => {
|
|
164
|
+
const message = {
|
|
165
|
+
to: obj.lineID,
|
|
166
|
+
messages: [
|
|
167
|
+
{
|
|
168
|
+
type: 'image',
|
|
169
|
+
originalContentUrl: largeUrl, // 原圖的 URL,必須是 HTTPS
|
|
170
|
+
previewImageUrl: smallUrl, // 縮略圖的 URL,必須是 HTTPS
|
|
214
171
|
},
|
|
215
|
-
|
|
172
|
+
],
|
|
216
173
|
};
|
|
217
|
-
console.log("Config -- " , urlConfig);
|
|
218
|
-
|
|
219
174
|
return new Promise<boolean>((resolve, reject) => {
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
175
|
+
axios
|
|
176
|
+
.post('https://api.line.me/v2/bot/message/push', message, {
|
|
177
|
+
headers: {
|
|
178
|
+
'Content-Type': 'application/json',
|
|
179
|
+
Authorization: `Bearer ${token}`,
|
|
180
|
+
},
|
|
181
|
+
})
|
|
182
|
+
.then((response: any) => {
|
|
183
|
+
console.log('圖片消息已成功發送:', response.data);
|
|
184
|
+
callback(response);
|
|
185
|
+
resolve(response.data);
|
|
186
|
+
})
|
|
187
|
+
.catch((error: any) => {
|
|
188
|
+
console.error('發送圖片消息時發生錯誤:', error.response ? error.response.data : error.message);
|
|
189
|
+
});
|
|
231
190
|
});
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
})
|
|
194
|
+
.catch((err: any) => {
|
|
195
|
+
console.error('處理圖片時發生錯誤:', err);
|
|
196
|
+
});
|
|
197
|
+
} else {
|
|
198
|
+
let postData = {
|
|
199
|
+
to: obj.lineID,
|
|
200
|
+
messages: [
|
|
201
|
+
{
|
|
202
|
+
type: 'text',
|
|
203
|
+
text: obj.data.text,
|
|
204
|
+
},
|
|
205
|
+
],
|
|
206
|
+
};
|
|
237
207
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
};
|
|
208
|
+
const urlConfig: Config = {
|
|
209
|
+
method: 'post',
|
|
210
|
+
url: 'https://api.line.me/v2/bot/message/push',
|
|
211
|
+
headers: {
|
|
212
|
+
'Content-Type': 'application/json',
|
|
213
|
+
Authorization: `Bearer ${token}`,
|
|
214
|
+
},
|
|
215
|
+
data: JSON.stringify(postData),
|
|
216
|
+
};
|
|
248
217
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
218
|
+
return new Promise<boolean>((resolve, reject) => {
|
|
219
|
+
axios
|
|
220
|
+
.request(urlConfig)
|
|
221
|
+
.then((response: any) => {
|
|
222
|
+
callback(response);
|
|
223
|
+
resolve(response.data);
|
|
224
|
+
})
|
|
225
|
+
.catch((error: any) => {
|
|
226
|
+
console.log(error);
|
|
227
|
+
console.log('error -- ', error.data);
|
|
228
|
+
resolve(false);
|
|
260
229
|
});
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
// "timeout": 0,
|
|
266
|
-
// "headers": {
|
|
267
|
-
// "Content-Type": "application/x-www-form-urlencoded"
|
|
268
|
-
// },
|
|
269
|
-
// "data": {
|
|
270
|
-
// "username": `${config.SNSAccount}`,
|
|
271
|
-
// "password": `${config.SNSPWD}`,
|
|
272
|
-
// "dstaddr": `${phone}`,
|
|
273
|
-
// "smbody": `${data}`
|
|
274
|
-
// }
|
|
275
|
-
// };
|
|
276
|
-
//
|
|
277
|
-
// $.ajax(settings).done(function (response:any) {
|
|
278
|
-
// console.log(response);
|
|
279
|
-
// });
|
|
280
|
-
} catch (e) {
|
|
281
|
-
throw exception.BadRequestError('BAD_REQUEST', 'send SNS Error:' + e, null);
|
|
282
|
-
}
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
} catch (e: any) {
|
|
233
|
+
throw exception.BadRequestError('BAD_REQUEST', 'send line Error:' + e.data, null);
|
|
283
234
|
}
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
async deleteSNS(obj: { id: string }, callback: (data: any) => void) {
|
|
238
|
+
try {
|
|
239
|
+
const urlConfig: Config = {
|
|
240
|
+
method: 'post',
|
|
241
|
+
url:
|
|
242
|
+
config.SNS_URL + `/api/mtk/SmCancel?username=${config.SNSAccount}&password=${config.SNSPWD}&msgid=${obj.id}`,
|
|
243
|
+
headers: {
|
|
244
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
245
|
+
},
|
|
246
|
+
data: [],
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
return new Promise<boolean>((resolve, reject) => {
|
|
250
|
+
axios
|
|
251
|
+
.request(urlConfig)
|
|
252
|
+
.then((response: any) => {
|
|
253
|
+
callback(response.data);
|
|
254
|
+
resolve(response.data);
|
|
255
|
+
})
|
|
256
|
+
.catch((error: any) => {
|
|
257
|
+
console.log('error -- ', error);
|
|
258
|
+
resolve(false);
|
|
259
|
+
});
|
|
260
|
+
});
|
|
261
|
+
//
|
|
262
|
+
// var settings = {
|
|
263
|
+
// "url": "https://smsapi.mitake.com.tw/api/mtk/SmSend",
|
|
264
|
+
// "method": "POST",
|
|
265
|
+
// "timeout": 0,
|
|
266
|
+
// "headers": {
|
|
267
|
+
// "Content-Type": "application/x-www-form-urlencoded"
|
|
268
|
+
// },
|
|
269
|
+
// "data": {
|
|
270
|
+
// "username": `${config.SNSAccount}`,
|
|
271
|
+
// "password": `${config.SNSPWD}`,
|
|
272
|
+
// "dstaddr": `${phone}`,
|
|
273
|
+
// "smbody": `${data}`
|
|
274
|
+
// }
|
|
275
|
+
// };
|
|
276
|
+
//
|
|
277
|
+
// $.ajax(settings).done(function (response:any) {
|
|
278
|
+
// console.log(response);
|
|
279
|
+
// });
|
|
280
|
+
} catch (e) {
|
|
281
|
+
throw exception.BadRequestError('BAD_REQUEST', 'send SNS Error:' + e, null);
|
|
288
282
|
}
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
public parseResponse(response: any) {
|
|
286
|
+
const regex = /\[([0-9]+)\]\r\nmsgid=([^\r\n]+)\r\nstatuscode=([0-9]+)\r\nAccountPoint=([0-9]+)\r\n/;
|
|
287
|
+
const match = response.match(regex);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
async getLine(query: {
|
|
291
|
+
type: string;
|
|
292
|
+
page: number;
|
|
293
|
+
limit: number;
|
|
294
|
+
search?: string;
|
|
295
|
+
searchType?: string;
|
|
296
|
+
mailType?: string;
|
|
297
|
+
status?: string;
|
|
298
|
+
}) {
|
|
299
|
+
try {
|
|
300
|
+
const whereList: string[] = ['1 = 1'];
|
|
301
|
+
switch (query.searchType) {
|
|
302
|
+
case 'phone':
|
|
303
|
+
whereList.push(
|
|
304
|
+
`(JSON_SEARCH(content->'$.phone', 'one', '%${query.search ?? ''}%', NULL, '$[*]') IS NOT NULL)`
|
|
305
|
+
);
|
|
306
|
+
break;
|
|
307
|
+
case 'name':
|
|
308
|
+
whereList.push(`(UPPER(JSON_EXTRACT(content, '$.name')) LIKE UPPER('%${query.search ?? ''}%'))`);
|
|
309
|
+
break;
|
|
310
|
+
case 'title':
|
|
311
|
+
whereList.push(`(UPPER(JSON_EXTRACT(content, '$.title')) LIKE UPPER('%${query.search ?? ''}%'))`);
|
|
312
|
+
break;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
if (query.status) {
|
|
316
|
+
whereList.push(`(status in (${query.status}))`);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
if (query.mailType) {
|
|
320
|
+
const maiTypeString = query.mailType.replace(/[^,]+/g, "'$&'");
|
|
321
|
+
whereList.push(`(JSON_EXTRACT(content, '$.type') in (${maiTypeString}))`);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
const whereSQL = `(tag = 'sendLine' OR tag = 'sendLineBySchedule') AND ${whereList.join(' AND ')}`;
|
|
325
|
+
|
|
326
|
+
const emails = await db.query(
|
|
327
|
+
`SELECT *
|
|
318
328
|
FROM \`${this.app}\`.t_triggers
|
|
319
329
|
WHERE ${whereSQL}
|
|
320
330
|
ORDER BY id DESC
|
|
321
331
|
${query.type === 'download' ? '' : `LIMIT ${query.page * query.limit}, ${query.limit}`};`,
|
|
322
|
-
|
|
323
|
-
|
|
332
|
+
[]
|
|
333
|
+
);
|
|
324
334
|
|
|
325
|
-
|
|
326
|
-
|
|
335
|
+
const total = await db.query(
|
|
336
|
+
`SELECT count(id) as c
|
|
327
337
|
FROM \`${this.app}\`.t_triggers
|
|
328
338
|
WHERE ${whereSQL};`,
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
}
|
|
340
|
-
const si = setInterval(() => {
|
|
341
|
-
if (n === emails.length) {
|
|
342
|
-
resolve();
|
|
343
|
-
clearInterval(si);
|
|
344
|
-
}
|
|
345
|
-
}, 300);
|
|
346
|
-
});
|
|
347
|
-
|
|
348
|
-
return { data: emails, total: total[0].c };
|
|
349
|
-
} catch (e) {
|
|
350
|
-
throw exception.BadRequestError('BAD_REQUEST', 'getMail Error:' + e, null);
|
|
339
|
+
[]
|
|
340
|
+
);
|
|
341
|
+
|
|
342
|
+
let n = 0;
|
|
343
|
+
await new Promise<void>(resolve => {
|
|
344
|
+
for (const email of emails) {
|
|
345
|
+
AutoSendEmail.getDefCompare(this.app, email.content.type, 'zh-TW').then(dd => {
|
|
346
|
+
email.content.typeName = dd && dd.tag_name ? dd.tag_name : '手動發送';
|
|
347
|
+
n++;
|
|
348
|
+
});
|
|
351
349
|
}
|
|
350
|
+
const si = setInterval(() => {
|
|
351
|
+
if (n === emails.length) {
|
|
352
|
+
resolve();
|
|
353
|
+
clearInterval(si);
|
|
354
|
+
}
|
|
355
|
+
}, 300);
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
return { data: emails, total: total[0].c };
|
|
359
|
+
} catch (e) {
|
|
360
|
+
throw exception.BadRequestError('BAD_REQUEST', 'getMail Error:' + e, null);
|
|
352
361
|
}
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
async postLine(data: any): Promise<{ result: boolean; message: string }> {
|
|
365
|
+
data.msgid = '';
|
|
366
|
+
try {
|
|
367
|
+
if (Boolean(data.sendTime)) {
|
|
368
|
+
if (isLater(data.sendTime)) {
|
|
369
|
+
return { result: false, message: '排定發送的時間需大於現在時間' };
|
|
370
|
+
}
|
|
371
|
+
const insertData = await db.query(
|
|
372
|
+
`INSERT INTO \`${this.app}\`.\`t_triggers\`
|
|
363
373
|
SET ?;`,
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
374
|
+
[
|
|
375
|
+
{
|
|
376
|
+
tag: 'sendLineBySchedule',
|
|
377
|
+
content: JSON.stringify(data),
|
|
378
|
+
trigger_time: formatDateTime(data.sendTime),
|
|
379
|
+
status: 0,
|
|
380
|
+
},
|
|
381
|
+
]
|
|
382
|
+
);
|
|
373
383
|
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
384
|
+
// this.chunkSendLine(data.userList , data.content , insertData.insertId , formatDateTime(data.sendTime));
|
|
385
|
+
} else {
|
|
386
|
+
const insertData = await db.query(
|
|
387
|
+
`INSERT INTO \`${this.app}\`.\`t_triggers\`
|
|
388
|
+
SET ?;`,
|
|
389
|
+
[
|
|
390
|
+
{
|
|
391
|
+
tag: 'sendLine',
|
|
392
|
+
content: JSON.stringify(data),
|
|
393
|
+
trigger_time: formatDateTime(),
|
|
394
|
+
status: 0,
|
|
395
|
+
},
|
|
396
|
+
]
|
|
397
|
+
);
|
|
398
|
+
this.chunkSendLine(
|
|
399
|
+
data.userList,
|
|
400
|
+
{
|
|
401
|
+
text: data.content,
|
|
402
|
+
},
|
|
403
|
+
insertData.insertId
|
|
404
|
+
);
|
|
405
|
+
}
|
|
406
|
+
return { result: true, message: '寄送成功' };
|
|
407
|
+
} catch (e) {
|
|
408
|
+
throw exception.BadRequestError('BAD_REQUEST', 'postMail Error:' + e, null);
|
|
393
409
|
}
|
|
410
|
+
}
|
|
394
411
|
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
412
|
+
async deleteSns(data: any): Promise<{ result: boolean; message: string }> {
|
|
413
|
+
try {
|
|
414
|
+
const emails = await db.query(
|
|
415
|
+
`SELECT *
|
|
399
416
|
FROM \`${this.app}\`.t_triggers
|
|
400
417
|
WHERE JSON_EXTRACT(content, '$.name') = '${data.id}';`,
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
418
|
+
[]
|
|
419
|
+
);
|
|
420
|
+
await new Promise(resolve => {
|
|
421
|
+
this.deleteSNS({ id: data.id }, res => {
|
|
422
|
+
resolve(true);
|
|
423
|
+
});
|
|
424
|
+
});
|
|
408
425
|
|
|
409
|
-
|
|
410
|
-
|
|
426
|
+
await db.query(
|
|
427
|
+
`UPDATE \`${this.app}\`.t_triggers
|
|
411
428
|
SET status = 2
|
|
412
429
|
WHERE JSON_EXTRACT(content, '$.name') = '${data.id}';`,
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
}
|
|
430
|
+
[]
|
|
431
|
+
);
|
|
432
|
+
return { result: true, message: '取消預約成功' };
|
|
433
|
+
} catch (e) {
|
|
434
|
+
throw exception.BadRequestError('BAD_REQUEST', 'postMail Error:' + e, null);
|
|
419
435
|
}
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
async listenMessage(data: any): Promise<{ result: boolean; message: string }> {
|
|
439
|
+
try {
|
|
440
|
+
const events = data.events;
|
|
441
|
+
//todo 在env上有個line_destination 記得這邊有修改這個log出來的id 隨便發話給機器人就會收到了
|
|
442
|
+
// console.log("data.destination -- " , data.destination);
|
|
443
|
+
if (data.destination == process.env.line_destination) {
|
|
444
|
+
console.log('處理shopnex官方機器人事件');
|
|
445
|
+
for (const event of events) {
|
|
446
|
+
switch (event.type) {
|
|
447
|
+
case 'message':
|
|
448
|
+
let data = await this.getUserProfile('U152cb05f49499386f506867cb6adff96');
|
|
449
|
+
break;
|
|
450
|
+
|
|
451
|
+
case 'postback':
|
|
452
|
+
console.log('收到 Postback 事件');
|
|
453
|
+
await ShopnexLineMessage.handlePostbackEvent(event, this.app);
|
|
454
|
+
// let data = await this.getUserProfile("U152cb05f49499386f506867cb6adff96")
|
|
455
|
+
break;
|
|
456
|
+
|
|
457
|
+
case 'join':
|
|
458
|
+
console.log('機器人被加入群組/聊天室');
|
|
459
|
+
await ShopnexLineMessage.handleJoinEvent(event, this.app);
|
|
460
|
+
break;
|
|
461
|
+
|
|
462
|
+
case 'leave':
|
|
463
|
+
console.log('機器人被移出群組/聊天室');
|
|
464
|
+
break;
|
|
465
|
+
|
|
466
|
+
default:
|
|
467
|
+
console.log('未知事件類型:', event.type);
|
|
468
|
+
break;
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
return { result: true, message: 'accept message' };
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
let message: {
|
|
475
|
+
type: string;
|
|
476
|
+
id: string;
|
|
477
|
+
quoteToken: string;
|
|
478
|
+
text: string;
|
|
479
|
+
} = data.events[0].message;
|
|
480
|
+
let userID = 'line_' + data.events[0].source.userId;
|
|
481
|
+
let chatData: any = {
|
|
482
|
+
chat_id: [userID, 'manager'].sort().join(''),
|
|
483
|
+
type: 'user',
|
|
484
|
+
info: {},
|
|
485
|
+
user_id: userID,
|
|
486
|
+
participant: [userID, 'manager'],
|
|
487
|
+
};
|
|
488
|
+
const post = new User(this.app, this.token);
|
|
489
|
+
|
|
490
|
+
let tokenData = await post.getConfig({
|
|
491
|
+
key: 'login_line_setting',
|
|
492
|
+
user_id: 'manager',
|
|
493
|
+
});
|
|
494
|
+
let token = `${tokenData[0].value.message_token}`;
|
|
495
|
+
|
|
496
|
+
for (const event of events) {
|
|
497
|
+
if (event.source.type == 'group') {
|
|
498
|
+
await this.getGroupInf(data.events[0].source.groupId);
|
|
499
|
+
//圖文輪播按鍵事件處理,這裡預設是點擊我要購買 或是有人喊商品+1
|
|
500
|
+
if (data.events[0]?.postback?.data) {
|
|
501
|
+
console.log('data.events[0] -- ', JSON.stringify(data.events[0]));
|
|
502
|
+
const replyToken = data.events[0].replyToken;
|
|
503
|
+
await this.createOrderWithLineFlexMessage(data.events[0], '您已經購買了商品');
|
|
504
|
+
return { result: true, message: 'accept message' };
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
//
|
|
508
|
+
if (message.text == 'product + 1') {
|
|
509
|
+
}
|
|
510
|
+
if (message.text == 'test') {
|
|
511
|
+
const replyToken = data.events[0].replyToken;
|
|
512
|
+
const multiPageMessage = {
|
|
513
|
+
type: 'flex',
|
|
514
|
+
altText: '這是多頁圖文訊息',
|
|
515
|
+
contents: {
|
|
516
|
+
type: 'carousel',
|
|
517
|
+
contents: [
|
|
518
|
+
{
|
|
519
|
+
type: 'bubble',
|
|
520
|
+
hero: {
|
|
521
|
+
type: 'image',
|
|
522
|
+
url: 'https://d3jnmi1tfjgtti.cloudfront.net/file/122538856/DALL·E2024-11-0514.18.59-AnelegantElizabethsolidwoodwardrobewithaclassic,timelessdesign.Thewardrobefeatureshigh-qualitywoodconstructionwithapolishedfinis.webp',
|
|
523
|
+
size: 'full',
|
|
524
|
+
aspectRatio: '20:13',
|
|
525
|
+
aspectMode: 'cover',
|
|
526
|
+
},
|
|
527
|
+
body: {
|
|
528
|
+
type: 'box',
|
|
529
|
+
layout: 'vertical',
|
|
530
|
+
contents: [
|
|
531
|
+
{
|
|
532
|
+
type: 'text',
|
|
533
|
+
text: '伊麗莎白 實木衣櫃',
|
|
534
|
+
weight: 'bold',
|
|
535
|
+
size: 'xl',
|
|
536
|
+
},
|
|
537
|
+
{
|
|
538
|
+
type: 'text',
|
|
539
|
+
text: '伊麗莎白 實木衣櫃完美結合了實用與美觀,適合多種室內風格,提供黑色、白色及胡桃木色供您選擇。',
|
|
540
|
+
size: 'sm',
|
|
541
|
+
wrap: true,
|
|
542
|
+
},
|
|
543
|
+
{
|
|
544
|
+
type: 'text',
|
|
545
|
+
text: 'NT 3500',
|
|
546
|
+
size: 'sm',
|
|
547
|
+
color: '#111111',
|
|
548
|
+
align: 'end',
|
|
549
|
+
},
|
|
550
|
+
],
|
|
551
|
+
},
|
|
552
|
+
footer: {
|
|
553
|
+
type: 'box',
|
|
554
|
+
layout: 'vertical',
|
|
555
|
+
spacing: 'sm',
|
|
556
|
+
contents: [
|
|
557
|
+
{
|
|
558
|
+
type: 'button',
|
|
559
|
+
style: 'primary',
|
|
560
|
+
action: {
|
|
561
|
+
type: 'postback',
|
|
562
|
+
label: '我要購買商品一',
|
|
563
|
+
data: JSON.stringify({
|
|
564
|
+
id: 709,
|
|
565
|
+
spec: ['深棕', '100cm'],
|
|
566
|
+
title: '伊麗莎白 實木衣櫃',
|
|
567
|
+
}), // 自定義的 Postback 資料
|
|
568
|
+
},
|
|
569
|
+
},
|
|
570
|
+
],
|
|
571
|
+
},
|
|
572
|
+
},
|
|
573
|
+
{
|
|
574
|
+
type: 'bubble',
|
|
575
|
+
hero: {
|
|
576
|
+
type: 'image',
|
|
577
|
+
url: 'https://d3jnmi1tfjgtti.cloudfront.net/file/122538856/DALL·E2024-11-0514.20.13-AsophisticatedWindermerecoffeetablewithamodernyetclassicdesign.Thetablefeaturesasolidwoodconstructionwithasmooth,polishedsurfa.webp',
|
|
578
|
+
size: 'full',
|
|
579
|
+
aspectRatio: '20:13',
|
|
580
|
+
aspectMode: 'cover',
|
|
581
|
+
},
|
|
582
|
+
body: {
|
|
583
|
+
type: 'box',
|
|
584
|
+
layout: 'vertical',
|
|
585
|
+
contents: [
|
|
586
|
+
{
|
|
587
|
+
type: 'text',
|
|
588
|
+
text: '溫德米爾 茶几"',
|
|
589
|
+
weight: 'bold',
|
|
590
|
+
size: 'xl',
|
|
591
|
+
},
|
|
592
|
+
{
|
|
593
|
+
type: 'text',
|
|
594
|
+
text: '選擇溫德米爾茶几,讓您的居家生活更具格調。擁有多種顏色和尺寸,適合各種家庭裝飾需求。',
|
|
595
|
+
size: 'sm',
|
|
596
|
+
wrap: true,
|
|
597
|
+
},
|
|
598
|
+
{
|
|
599
|
+
type: 'text',
|
|
600
|
+
text: 'NT 5200',
|
|
601
|
+
size: 'sm',
|
|
602
|
+
color: '#111111',
|
|
603
|
+
align: 'end',
|
|
604
|
+
},
|
|
605
|
+
],
|
|
606
|
+
},
|
|
607
|
+
footer: {
|
|
608
|
+
type: 'box',
|
|
609
|
+
layout: 'vertical',
|
|
610
|
+
spacing: 'sm',
|
|
611
|
+
contents: [
|
|
612
|
+
{
|
|
613
|
+
type: 'button',
|
|
614
|
+
style: 'primary',
|
|
615
|
+
action: {
|
|
616
|
+
type: 'postback',
|
|
617
|
+
label: '我要購買商品二',
|
|
618
|
+
data: JSON.stringify({
|
|
619
|
+
id: 710,
|
|
620
|
+
sku: '',
|
|
621
|
+
count: 1,
|
|
622
|
+
spec: ['黑色', '小號'],
|
|
623
|
+
title: '溫德米爾 茶几',
|
|
624
|
+
sale_price: 5200,
|
|
625
|
+
}), // 自定義的 Postback 資料
|
|
626
|
+
},
|
|
627
|
+
},
|
|
628
|
+
],
|
|
629
|
+
},
|
|
630
|
+
},
|
|
631
|
+
{
|
|
632
|
+
type: 'bubble',
|
|
633
|
+
hero: {
|
|
634
|
+
type: 'image',
|
|
635
|
+
url: 'https://d3jnmi1tfjgtti.cloudfront.net/file/122538856/DALL·E2024-11-0514.20.13-AsophisticatedWindermerecoffeetablewithamodernyetclassicdesign.Thetablefeaturesasolidwoodconstructionwithasmooth,polishedsurfa.webp',
|
|
636
|
+
size: 'full',
|
|
637
|
+
aspectRatio: '20:13',
|
|
638
|
+
aspectMode: 'cover',
|
|
639
|
+
},
|
|
640
|
+
body: {
|
|
641
|
+
type: 'box',
|
|
642
|
+
layout: 'vertical',
|
|
643
|
+
contents: [
|
|
644
|
+
{
|
|
645
|
+
type: 'text',
|
|
646
|
+
text: '溫德米爾 茶几2"',
|
|
647
|
+
weight: 'bold',
|
|
648
|
+
size: 'xl',
|
|
649
|
+
},
|
|
650
|
+
{
|
|
651
|
+
type: 'text',
|
|
652
|
+
text: '選擇溫德米爾茶几,讓您的居家生活更具格調。擁有多種顏色和尺寸,適合各種家庭裝飾需求。',
|
|
653
|
+
size: 'sm',
|
|
654
|
+
wrap: true,
|
|
655
|
+
},
|
|
656
|
+
{
|
|
657
|
+
type: 'text',
|
|
658
|
+
text: 'NT 5200',
|
|
659
|
+
size: 'sm',
|
|
660
|
+
color: '#111111',
|
|
661
|
+
align: 'end',
|
|
662
|
+
},
|
|
663
|
+
],
|
|
664
|
+
},
|
|
665
|
+
footer: {
|
|
666
|
+
type: 'box',
|
|
667
|
+
layout: 'vertical',
|
|
668
|
+
spacing: 'sm',
|
|
669
|
+
contents: [
|
|
670
|
+
{
|
|
671
|
+
type: 'button',
|
|
672
|
+
style: 'primary',
|
|
673
|
+
action: {
|
|
674
|
+
type: 'postback',
|
|
675
|
+
label: '我要購買商品二',
|
|
676
|
+
data: JSON.stringify({
|
|
677
|
+
id: 710,
|
|
678
|
+
sku: '',
|
|
679
|
+
count: 1,
|
|
680
|
+
spec: ['黑色', '小號'],
|
|
681
|
+
title: '溫德米爾 茶几',
|
|
682
|
+
sale_price: 5200,
|
|
683
|
+
}), // 自定義的 Postback 資料
|
|
684
|
+
},
|
|
685
|
+
},
|
|
686
|
+
],
|
|
687
|
+
},
|
|
688
|
+
},
|
|
689
|
+
{
|
|
690
|
+
type: 'bubble',
|
|
691
|
+
hero: {
|
|
692
|
+
type: 'image',
|
|
693
|
+
url: 'https://d3jnmi1tfjgtti.cloudfront.net/file/122538856/DALL·E2024-11-0514.20.13-AsophisticatedWindermerecoffeetablewithamodernyetclassicdesign.Thetablefeaturesasolidwoodconstructionwithasmooth,polishedsurfa.webp',
|
|
694
|
+
size: 'full',
|
|
695
|
+
aspectRatio: '20:13',
|
|
696
|
+
aspectMode: 'cover',
|
|
697
|
+
},
|
|
698
|
+
body: {
|
|
699
|
+
type: 'box',
|
|
700
|
+
layout: 'vertical',
|
|
701
|
+
contents: [
|
|
702
|
+
{
|
|
703
|
+
type: 'text',
|
|
704
|
+
text: '溫德米爾 茶几"',
|
|
705
|
+
weight: 'bold',
|
|
706
|
+
size: 'xl',
|
|
707
|
+
},
|
|
708
|
+
{
|
|
709
|
+
type: 'text',
|
|
710
|
+
text: '選擇溫德米爾茶几,讓您的居家生活更具格調。擁有多種顏色和尺寸,適合各種家庭裝飾需求。',
|
|
711
|
+
size: 'sm',
|
|
712
|
+
wrap: true,
|
|
713
|
+
},
|
|
714
|
+
{
|
|
715
|
+
type: 'text',
|
|
716
|
+
text: 'NT 5200',
|
|
717
|
+
size: 'sm',
|
|
718
|
+
color: '#111111',
|
|
719
|
+
align: 'end',
|
|
720
|
+
},
|
|
721
|
+
],
|
|
722
|
+
},
|
|
723
|
+
footer: {
|
|
724
|
+
type: 'box',
|
|
725
|
+
layout: 'vertical',
|
|
726
|
+
spacing: 'sm',
|
|
727
|
+
contents: [
|
|
728
|
+
{
|
|
729
|
+
type: 'button',
|
|
730
|
+
style: 'primary',
|
|
731
|
+
action: {
|
|
732
|
+
type: 'postback',
|
|
733
|
+
label: '我要購買商品二',
|
|
734
|
+
data: JSON.stringify({
|
|
735
|
+
id: 710,
|
|
736
|
+
sku: '',
|
|
737
|
+
count: 1,
|
|
738
|
+
spec: ['黑色', '小號'],
|
|
739
|
+
title: '溫德米爾 茶几',
|
|
740
|
+
sale_price: 5200,
|
|
741
|
+
}), // 自定義的 Postback 資料
|
|
742
|
+
},
|
|
743
|
+
},
|
|
744
|
+
],
|
|
745
|
+
},
|
|
746
|
+
},
|
|
747
|
+
],
|
|
748
|
+
},
|
|
749
|
+
};
|
|
750
|
+
try {
|
|
751
|
+
await axios.post(
|
|
752
|
+
'https://api.line.me/v2/bot/message/reply',
|
|
753
|
+
{
|
|
754
|
+
replyToken: replyToken,
|
|
755
|
+
messages: [multiPageMessage],
|
|
756
|
+
},
|
|
757
|
+
{
|
|
758
|
+
headers: {
|
|
759
|
+
'Content-Type': 'application/json',
|
|
760
|
+
Authorization: `Bearer ${token}`,
|
|
761
|
+
},
|
|
455
762
|
}
|
|
456
|
-
|
|
763
|
+
);
|
|
764
|
+
} catch (e: any) {
|
|
765
|
+
console.log('e -- ', e.response.data);
|
|
457
766
|
}
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
type: 'user',
|
|
470
|
-
info: {},
|
|
471
|
-
user_id: userID,
|
|
472
|
-
participant: [userID, 'manager'],
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
return { result: true, message: 'accept message' };
|
|
770
|
+
} else if (event.source.type == 'user') {
|
|
771
|
+
//取得用戶資訊
|
|
772
|
+
await this.getLineInf({ lineID: data.events[0].source.userId }, (data: any) => {
|
|
773
|
+
chatData.info = {
|
|
774
|
+
line: {
|
|
775
|
+
name: data.displayName,
|
|
776
|
+
head: data.pictureUrl,
|
|
777
|
+
},
|
|
473
778
|
};
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
let tokenData = await post.getConfig({
|
|
477
|
-
key: 'login_line_setting',
|
|
478
|
-
user_id: 'manager',
|
|
479
|
-
});
|
|
480
|
-
let token = `${tokenData[0].value.message_token}`;
|
|
779
|
+
chatData.info = JSON.stringify(chatData.info);
|
|
780
|
+
});
|
|
481
781
|
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
console.log("data.events[0] -- " , JSON.stringify(data.events[0]));
|
|
488
|
-
const replyToken = data.events[0].replyToken;
|
|
489
|
-
await this.createOrderWithLineFlexMessage(data.events[0], "您已經購買了商品")
|
|
490
|
-
return { result: true, message: 'accept message' };
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
//
|
|
494
|
-
if (message.text == "product + 1"){
|
|
495
|
-
|
|
496
|
-
}
|
|
497
|
-
if (message.text == "test"){
|
|
498
|
-
const replyToken = data.events[0].replyToken;
|
|
499
|
-
const multiPageMessage = {
|
|
500
|
-
type: 'flex',
|
|
501
|
-
altText: '這是多頁圖文訊息',
|
|
502
|
-
contents: {
|
|
503
|
-
type: 'carousel',
|
|
504
|
-
contents: [
|
|
505
|
-
{
|
|
506
|
-
type: 'bubble',
|
|
507
|
-
hero: {
|
|
508
|
-
type: 'image',
|
|
509
|
-
url: 'https://d3jnmi1tfjgtti.cloudfront.net/file/122538856/DALL·E2024-11-0514.18.59-AnelegantElizabethsolidwoodwardrobewithaclassic,timelessdesign.Thewardrobefeatureshigh-qualitywoodconstructionwithapolishedfinis.webp',
|
|
510
|
-
size: 'full',
|
|
511
|
-
aspectRatio: '20:13',
|
|
512
|
-
aspectMode: 'cover',
|
|
513
|
-
},
|
|
514
|
-
body: {
|
|
515
|
-
type: 'box',
|
|
516
|
-
layout: 'vertical',
|
|
517
|
-
contents: [
|
|
518
|
-
{
|
|
519
|
-
type: 'text',
|
|
520
|
-
text: '伊麗莎白 實木衣櫃',
|
|
521
|
-
weight: 'bold',
|
|
522
|
-
size: 'xl',
|
|
523
|
-
},
|
|
524
|
-
{
|
|
525
|
-
type: 'text',
|
|
526
|
-
text: '伊麗莎白 實木衣櫃完美結合了實用與美觀,適合多種室內風格,提供黑色、白色及胡桃木色供您選擇。',
|
|
527
|
-
size: 'sm',
|
|
528
|
-
wrap: true,
|
|
529
|
-
},
|
|
530
|
-
{
|
|
531
|
-
type: "text",
|
|
532
|
-
text: "NT 3500",
|
|
533
|
-
size: "sm",
|
|
534
|
-
color: "#111111",
|
|
535
|
-
align: "end"
|
|
536
|
-
}
|
|
537
|
-
],
|
|
538
|
-
},
|
|
539
|
-
footer: {
|
|
540
|
-
type: 'box',
|
|
541
|
-
layout: 'vertical',
|
|
542
|
-
spacing: 'sm',
|
|
543
|
-
contents: [
|
|
544
|
-
{
|
|
545
|
-
type: 'button',
|
|
546
|
-
style: 'primary',
|
|
547
|
-
action: {
|
|
548
|
-
type: 'postback',
|
|
549
|
-
label: '我要購買商品一',
|
|
550
|
-
data:JSON.stringify({
|
|
551
|
-
"id": 709,
|
|
552
|
-
"spec": [
|
|
553
|
-
"深棕",
|
|
554
|
-
"100cm"
|
|
555
|
-
],
|
|
556
|
-
"title": "伊麗莎白 實木衣櫃"
|
|
557
|
-
}) , // 自定義的 Postback 資料
|
|
558
|
-
},
|
|
559
|
-
},
|
|
560
|
-
],
|
|
561
|
-
},
|
|
562
|
-
},
|
|
563
|
-
{
|
|
564
|
-
type: 'bubble',
|
|
565
|
-
hero: {
|
|
566
|
-
type: 'image',
|
|
567
|
-
url: 'https://d3jnmi1tfjgtti.cloudfront.net/file/122538856/DALL·E2024-11-0514.20.13-AsophisticatedWindermerecoffeetablewithamodernyetclassicdesign.Thetablefeaturesasolidwoodconstructionwithasmooth,polishedsurfa.webp',
|
|
568
|
-
size: 'full',
|
|
569
|
-
aspectRatio: '20:13',
|
|
570
|
-
aspectMode: 'cover',
|
|
571
|
-
},
|
|
572
|
-
body: {
|
|
573
|
-
type: 'box',
|
|
574
|
-
layout: 'vertical',
|
|
575
|
-
contents: [
|
|
576
|
-
{
|
|
577
|
-
type: 'text',
|
|
578
|
-
text: '溫德米爾 茶几"',
|
|
579
|
-
weight: 'bold',
|
|
580
|
-
size: 'xl',
|
|
581
|
-
},
|
|
582
|
-
{
|
|
583
|
-
type: 'text',
|
|
584
|
-
text: '選擇溫德米爾茶几,讓您的居家生活更具格調。擁有多種顏色和尺寸,適合各種家庭裝飾需求。',
|
|
585
|
-
size: 'sm',
|
|
586
|
-
wrap: true,
|
|
587
|
-
},
|
|
588
|
-
{
|
|
589
|
-
type: "text",
|
|
590
|
-
text: "NT 5200",
|
|
591
|
-
size: "sm",
|
|
592
|
-
color: "#111111",
|
|
593
|
-
align: "end"
|
|
594
|
-
}
|
|
595
|
-
],
|
|
596
|
-
},
|
|
597
|
-
footer: {
|
|
598
|
-
type: 'box',
|
|
599
|
-
layout: 'vertical',
|
|
600
|
-
spacing: 'sm',
|
|
601
|
-
contents: [
|
|
602
|
-
{
|
|
603
|
-
type: 'button',
|
|
604
|
-
style: 'primary',
|
|
605
|
-
action: {
|
|
606
|
-
type: 'postback',
|
|
607
|
-
label: '我要購買商品二',
|
|
608
|
-
data: JSON.stringify({
|
|
609
|
-
"id": 710,
|
|
610
|
-
"sku": "",
|
|
611
|
-
"count": 1,
|
|
612
|
-
"spec": [
|
|
613
|
-
"黑色",
|
|
614
|
-
"小號"
|
|
615
|
-
],
|
|
616
|
-
"title": "溫德米爾 茶几",
|
|
617
|
-
"sale_price": 5200,
|
|
618
|
-
}) , // 自定義的 Postback 資料
|
|
619
|
-
},
|
|
620
|
-
},
|
|
621
|
-
],
|
|
622
|
-
},
|
|
623
|
-
},
|
|
624
|
-
{
|
|
625
|
-
type: 'bubble',
|
|
626
|
-
hero: {
|
|
627
|
-
type: 'image',
|
|
628
|
-
url: 'https://d3jnmi1tfjgtti.cloudfront.net/file/122538856/DALL·E2024-11-0514.20.13-AsophisticatedWindermerecoffeetablewithamodernyetclassicdesign.Thetablefeaturesasolidwoodconstructionwithasmooth,polishedsurfa.webp',
|
|
629
|
-
size: 'full',
|
|
630
|
-
aspectRatio: '20:13',
|
|
631
|
-
aspectMode: 'cover',
|
|
632
|
-
},
|
|
633
|
-
body: {
|
|
634
|
-
type: 'box',
|
|
635
|
-
layout: 'vertical',
|
|
636
|
-
contents: [
|
|
637
|
-
{
|
|
638
|
-
type: 'text',
|
|
639
|
-
text: '溫德米爾 茶几2"',
|
|
640
|
-
weight: 'bold',
|
|
641
|
-
size: 'xl',
|
|
642
|
-
},
|
|
643
|
-
{
|
|
644
|
-
type: 'text',
|
|
645
|
-
text: '選擇溫德米爾茶几,讓您的居家生活更具格調。擁有多種顏色和尺寸,適合各種家庭裝飾需求。',
|
|
646
|
-
size: 'sm',
|
|
647
|
-
wrap: true,
|
|
648
|
-
},
|
|
649
|
-
{
|
|
650
|
-
type: "text",
|
|
651
|
-
text: "NT 5200",
|
|
652
|
-
size: "sm",
|
|
653
|
-
color: "#111111",
|
|
654
|
-
align: "end"
|
|
655
|
-
}
|
|
656
|
-
],
|
|
657
|
-
},
|
|
658
|
-
footer: {
|
|
659
|
-
type: 'box',
|
|
660
|
-
layout: 'vertical',
|
|
661
|
-
spacing: 'sm',
|
|
662
|
-
contents: [
|
|
663
|
-
{
|
|
664
|
-
type: 'button',
|
|
665
|
-
style: 'primary',
|
|
666
|
-
action: {
|
|
667
|
-
type: 'postback',
|
|
668
|
-
label: '我要購買商品二',
|
|
669
|
-
data: JSON.stringify({
|
|
670
|
-
"id": 710,
|
|
671
|
-
"sku": "",
|
|
672
|
-
"count": 1,
|
|
673
|
-
"spec": [
|
|
674
|
-
"黑色",
|
|
675
|
-
"小號"
|
|
676
|
-
],
|
|
677
|
-
"title": "溫德米爾 茶几",
|
|
678
|
-
"sale_price": 5200,
|
|
679
|
-
}) , // 自定義的 Postback 資料
|
|
680
|
-
},
|
|
681
|
-
},
|
|
682
|
-
],
|
|
683
|
-
},
|
|
684
|
-
},
|
|
685
|
-
{
|
|
686
|
-
type: 'bubble',
|
|
687
|
-
hero: {
|
|
688
|
-
type: 'image',
|
|
689
|
-
url: 'https://d3jnmi1tfjgtti.cloudfront.net/file/122538856/DALL·E2024-11-0514.20.13-AsophisticatedWindermerecoffeetablewithamodernyetclassicdesign.Thetablefeaturesasolidwoodconstructionwithasmooth,polishedsurfa.webp',
|
|
690
|
-
size: 'full',
|
|
691
|
-
aspectRatio: '20:13',
|
|
692
|
-
aspectMode: 'cover',
|
|
693
|
-
},
|
|
694
|
-
body: {
|
|
695
|
-
type: 'box',
|
|
696
|
-
layout: 'vertical',
|
|
697
|
-
contents: [
|
|
698
|
-
{
|
|
699
|
-
type: 'text',
|
|
700
|
-
text: '溫德米爾 茶几"',
|
|
701
|
-
weight: 'bold',
|
|
702
|
-
size: 'xl',
|
|
703
|
-
},
|
|
704
|
-
{
|
|
705
|
-
type: 'text',
|
|
706
|
-
text: '選擇溫德米爾茶几,讓您的居家生活更具格調。擁有多種顏色和尺寸,適合各種家庭裝飾需求。',
|
|
707
|
-
size: 'sm',
|
|
708
|
-
wrap: true,
|
|
709
|
-
},
|
|
710
|
-
{
|
|
711
|
-
type: "text",
|
|
712
|
-
text: "NT 5200",
|
|
713
|
-
size: "sm",
|
|
714
|
-
color: "#111111",
|
|
715
|
-
align: "end"
|
|
716
|
-
}
|
|
717
|
-
],
|
|
718
|
-
},
|
|
719
|
-
footer: {
|
|
720
|
-
type: 'box',
|
|
721
|
-
layout: 'vertical',
|
|
722
|
-
spacing: 'sm',
|
|
723
|
-
contents: [
|
|
724
|
-
{
|
|
725
|
-
type: 'button',
|
|
726
|
-
style: 'primary',
|
|
727
|
-
action: {
|
|
728
|
-
type: 'postback',
|
|
729
|
-
label: '我要購買商品二',
|
|
730
|
-
data: JSON.stringify({
|
|
731
|
-
"id": 710,
|
|
732
|
-
"sku": "",
|
|
733
|
-
"count": 1,
|
|
734
|
-
"spec": [
|
|
735
|
-
"黑色",
|
|
736
|
-
"小號"
|
|
737
|
-
],
|
|
738
|
-
"title": "溫德米爾 茶几",
|
|
739
|
-
"sale_price": 5200,
|
|
740
|
-
}) , // 自定義的 Postback 資料
|
|
741
|
-
},
|
|
742
|
-
},
|
|
743
|
-
],
|
|
744
|
-
},
|
|
745
|
-
},
|
|
746
|
-
],
|
|
747
|
-
},
|
|
748
|
-
};
|
|
749
|
-
try {
|
|
750
|
-
await axios.post('https://api.line.me/v2/bot/message/reply', {
|
|
751
|
-
replyToken: replyToken,
|
|
752
|
-
messages: [
|
|
753
|
-
multiPageMessage
|
|
754
|
-
]
|
|
755
|
-
}, {
|
|
756
|
-
headers: {
|
|
757
|
-
'Content-Type': 'application/json',
|
|
758
|
-
'Authorization': `Bearer ${token}`
|
|
759
|
-
}
|
|
760
|
-
});
|
|
761
|
-
}catch (e:any){
|
|
762
|
-
console.log("e -- " , e.response.data);
|
|
763
|
-
}
|
|
764
|
-
}
|
|
765
|
-
|
|
766
|
-
return { result: true, message: 'accept message' };
|
|
767
|
-
}else if(event.source.type == 'user'){
|
|
768
|
-
//取得用戶資訊
|
|
769
|
-
await this.getLineInf({ lineID: data.events[0].source.userId }, (data: any) => {
|
|
770
|
-
chatData.info = {
|
|
771
|
-
line: {
|
|
772
|
-
name: data.displayName,
|
|
773
|
-
head: data.pictureUrl,
|
|
774
|
-
},
|
|
775
|
-
};
|
|
776
|
-
chatData.info = JSON.stringify(chatData.info);
|
|
777
|
-
});
|
|
778
|
-
|
|
779
|
-
let result = await new Chat(this.app).addChatRoom(chatData);
|
|
780
|
-
//若是聊天是未建立 則建立
|
|
781
|
-
if (!result.create) {
|
|
782
|
-
await db.query(
|
|
783
|
-
`
|
|
782
|
+
let result = await new Chat(this.app).addChatRoom(chatData);
|
|
783
|
+
//若是聊天是未建立 則建立
|
|
784
|
+
if (!result.create) {
|
|
785
|
+
await db.query(
|
|
786
|
+
`
|
|
784
787
|
UPDATE \`${this.app}\`.\`t_chat_list\`
|
|
785
788
|
SET ?
|
|
786
789
|
WHERE ?
|
|
787
790
|
`,
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
}
|
|
817
|
-
switch (event.type){
|
|
818
|
-
case "message":
|
|
819
|
-
console.log("收到訊息事件");
|
|
820
|
-
console.log("event -- " , event)
|
|
821
|
-
break;
|
|
822
|
-
|
|
823
|
-
case "postback":
|
|
824
|
-
console.log("收到 Postback 事件");
|
|
825
|
-
break;
|
|
826
|
-
|
|
827
|
-
case "follow":
|
|
828
|
-
console.log("用戶開始追蹤機器人");
|
|
829
|
-
break;
|
|
830
|
-
|
|
831
|
-
case "unfollow":
|
|
832
|
-
console.log("用戶取消追蹤機器人");
|
|
833
|
-
break;
|
|
834
|
-
|
|
835
|
-
case "join":
|
|
836
|
-
console.log("機器人被加入群組/聊天室");
|
|
837
|
-
break;
|
|
838
|
-
|
|
839
|
-
case "leave":
|
|
840
|
-
console.log("機器人被移出群組/聊天室");
|
|
841
|
-
break;
|
|
842
|
-
|
|
843
|
-
case "memberJoined":
|
|
844
|
-
console.log("新成員加入群組/聊天室");
|
|
845
|
-
break;
|
|
846
|
-
|
|
847
|
-
case "memberLeft":
|
|
848
|
-
console.log("成員離開群組/聊天室");
|
|
849
|
-
break;
|
|
850
|
-
|
|
851
|
-
case "reaction":
|
|
852
|
-
console.log("收到 Reaction 事件");
|
|
853
|
-
break;
|
|
854
|
-
|
|
855
|
-
case "videoPlayComplete":
|
|
856
|
-
console.log("影片播放完畢");
|
|
857
|
-
break;
|
|
858
|
-
|
|
859
|
-
case "unsend":
|
|
860
|
-
console.log("用戶撤回訊息");
|
|
861
|
-
break;
|
|
862
|
-
|
|
863
|
-
case "things":
|
|
864
|
-
console.log("收到 LINE Things 物聯網事件");
|
|
865
|
-
break;
|
|
866
|
-
|
|
867
|
-
default:
|
|
868
|
-
console.log("未知事件類型:", event.type);
|
|
869
|
-
break;
|
|
870
|
-
}
|
|
871
|
-
|
|
872
|
-
// if (event.type === "join") {
|
|
873
|
-
// const groupId = event.source.groupId;
|
|
874
|
-
// const inviterUserId = event.source.userId || "Unknown";
|
|
875
|
-
//
|
|
876
|
-
// console.log(`機器人加入群組 ${groupId}, 邀請者: ${inviterUserId}`);
|
|
877
|
-
//
|
|
878
|
-
// // 查詢使用者詳細資訊
|
|
879
|
-
// if (event.source.userId) {
|
|
880
|
-
// const userProfile = await this.getUserProfile(event.source.userId , token);
|
|
881
|
-
// console.log("邀請者資訊:", userProfile);
|
|
882
|
-
// }
|
|
883
|
-
// }
|
|
884
|
-
}
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
return { result: true, message: 'accept message' };
|
|
889
|
-
} catch (e) {
|
|
890
|
-
throw exception.BadRequestError('BAD_REQUEST', 'Error:' + e, null);
|
|
791
|
+
[
|
|
792
|
+
{
|
|
793
|
+
info: chatData.info,
|
|
794
|
+
},
|
|
795
|
+
{
|
|
796
|
+
chat_id: chatData.chat_id,
|
|
797
|
+
},
|
|
798
|
+
]
|
|
799
|
+
);
|
|
800
|
+
}
|
|
801
|
+
//根據訊息的型態 做不同處理
|
|
802
|
+
if (message.type == 'image') {
|
|
803
|
+
const post = new User(this.app, this.token);
|
|
804
|
+
let tokenData = await post.getConfig({
|
|
805
|
+
key: 'login_line_setting',
|
|
806
|
+
user_id: 'manager',
|
|
807
|
+
});
|
|
808
|
+
let token = `${tokenData[0].value.message_token}`;
|
|
809
|
+
let imageUrl = await this.getImageContent(message.id, token);
|
|
810
|
+
chatData.message = {
|
|
811
|
+
image: imageUrl,
|
|
812
|
+
};
|
|
813
|
+
} else {
|
|
814
|
+
chatData.message = {
|
|
815
|
+
text: message.text,
|
|
816
|
+
};
|
|
817
|
+
}
|
|
818
|
+
await new Chat(this.app).addMessage(chatData);
|
|
891
819
|
}
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
820
|
+
switch (event.type) {
|
|
821
|
+
case 'message':
|
|
822
|
+
console.log('收到訊息事件');
|
|
823
|
+
console.log('event -- ', event);
|
|
824
|
+
break;
|
|
825
|
+
|
|
826
|
+
case 'postback':
|
|
827
|
+
console.log('收到 Postback 事件');
|
|
828
|
+
break;
|
|
829
|
+
|
|
830
|
+
case 'follow':
|
|
831
|
+
console.log('用戶開始追蹤機器人');
|
|
832
|
+
break;
|
|
833
|
+
|
|
834
|
+
case 'unfollow':
|
|
835
|
+
console.log('用戶取消追蹤機器人');
|
|
836
|
+
break;
|
|
837
|
+
|
|
838
|
+
case 'join':
|
|
839
|
+
console.log('機器人被加入群組/聊天室');
|
|
840
|
+
break;
|
|
841
|
+
|
|
842
|
+
case 'leave':
|
|
843
|
+
console.log('機器人被移出群組/聊天室');
|
|
844
|
+
break;
|
|
845
|
+
|
|
846
|
+
case 'memberJoined':
|
|
847
|
+
console.log('新成員加入群組/聊天室');
|
|
848
|
+
break;
|
|
849
|
+
|
|
850
|
+
case 'memberLeft':
|
|
851
|
+
console.log('成員離開群組/聊天室');
|
|
852
|
+
break;
|
|
853
|
+
|
|
854
|
+
case 'reaction':
|
|
855
|
+
console.log('收到 Reaction 事件');
|
|
856
|
+
break;
|
|
857
|
+
|
|
858
|
+
case 'videoPlayComplete':
|
|
859
|
+
console.log('影片播放完畢');
|
|
860
|
+
break;
|
|
861
|
+
|
|
862
|
+
case 'unsend':
|
|
863
|
+
console.log('用戶撤回訊息');
|
|
864
|
+
break;
|
|
865
|
+
|
|
866
|
+
case 'things':
|
|
867
|
+
console.log('收到 LINE Things 物聯網事件');
|
|
868
|
+
break;
|
|
869
|
+
|
|
870
|
+
default:
|
|
871
|
+
console.log('未知事件類型:', event.type);
|
|
872
|
+
break;
|
|
903
873
|
}
|
|
904
874
|
|
|
905
|
-
|
|
906
|
-
const
|
|
907
|
-
const
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
let lineItems: {
|
|
917
|
-
id: number;
|
|
918
|
-
spec: string[];
|
|
919
|
-
count: number;
|
|
920
|
-
sale_price: number;
|
|
921
|
-
sku: string;
|
|
922
|
-
}[];
|
|
923
|
-
let tokenData = await post.getConfig({
|
|
924
|
-
key: 'login_line_setting',
|
|
925
|
-
user_id: 'manager',
|
|
926
|
-
});
|
|
927
|
-
|
|
928
|
-
if (cart){
|
|
929
|
-
if (typeof cart === "string") {
|
|
930
|
-
productData = JSON.parse(cart);
|
|
931
|
-
}
|
|
932
|
-
}
|
|
933
|
-
productData.forEach((data:any)=>{
|
|
934
|
-
console.log(data.id , newData.id);
|
|
935
|
-
console.log(data.spec , newData.spec);
|
|
936
|
-
if (data.id == newData.id && areSpecsEqual(data.spec , newData.spec)){
|
|
937
|
-
data.count = data.count??1;
|
|
938
|
-
data.count++;
|
|
939
|
-
}
|
|
940
|
-
})
|
|
941
|
-
productData.push(JSON.parse(messageData.postback.data));
|
|
942
|
-
|
|
943
|
-
await redis.setValue(dataKey , JSON.stringify(productData));
|
|
944
|
-
let token = `${tokenData[0].value.message_token}`;
|
|
945
|
-
// await this.sendMessage(token , userId , message);
|
|
946
|
-
// await axios.post('https://api.line.me/v2/bot/message/reply', {
|
|
947
|
-
// replyToken: replyToken,
|
|
948
|
-
// messages: [
|
|
949
|
-
// {
|
|
950
|
-
// type: 'text',
|
|
951
|
-
// text: message, // 回應訊息
|
|
952
|
-
// },
|
|
953
|
-
// ]
|
|
954
|
-
// }, {
|
|
955
|
-
// headers: {
|
|
956
|
-
// 'Content-Type': 'application/json',
|
|
957
|
-
// 'Authorization': `Bearer ${token}`
|
|
875
|
+
// if (event.type === "join") {
|
|
876
|
+
// const groupId = event.source.groupId;
|
|
877
|
+
// const inviterUserId = event.source.userId || "Unknown";
|
|
878
|
+
//
|
|
879
|
+
// console.log(`機器人加入群組 ${groupId}, 邀請者: ${inviterUserId}`);
|
|
880
|
+
//
|
|
881
|
+
// // 查詢使用者詳細資訊
|
|
882
|
+
// if (event.source.userId) {
|
|
883
|
+
// const userProfile = await this.getUserProfile(event.source.userId , token);
|
|
884
|
+
// console.log("邀請者資訊:", userProfile);
|
|
958
885
|
// }
|
|
959
|
-
// }
|
|
886
|
+
// }
|
|
887
|
+
}
|
|
960
888
|
|
|
889
|
+
return { result: true, message: 'accept message' };
|
|
890
|
+
} catch (e) {
|
|
891
|
+
throw exception.BadRequestError('BAD_REQUEST', 'Error:' + e, null);
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
async createOrderWithLineFlexMessage(messageData: any, message: string) {
|
|
895
|
+
console.log('message -- ', messageData);
|
|
896
|
+
function areSpecsEqual(spec1: string[], spec2: string[]): boolean {
|
|
897
|
+
if (spec1.length !== spec2.length) {
|
|
898
|
+
return false;
|
|
899
|
+
}
|
|
900
|
+
// 比較排序後的內容
|
|
901
|
+
const sortedSpec1 = [...spec1].sort();
|
|
902
|
+
const sortedSpec2 = [...spec2].sort();
|
|
903
|
+
return sortedSpec1.every((value, index) => value === sortedSpec2[index]);
|
|
961
904
|
}
|
|
962
905
|
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
906
|
+
const replyToken = messageData.replyToken;
|
|
907
|
+
const post = new User(this.app, this.token);
|
|
908
|
+
const groupId = messageData.source.groupId;
|
|
909
|
+
const userId = messageData.source.userId || '未知使用者';
|
|
910
|
+
const dataKey = groupId + '-' + userId;
|
|
911
|
+
const cart = await redis.getValue(dataKey);
|
|
912
|
+
const newData = JSON.parse(messageData.postback.data);
|
|
913
|
+
let productData = [];
|
|
914
|
+
|
|
915
|
+
let lineItems: {
|
|
916
|
+
id: number;
|
|
917
|
+
spec: string[];
|
|
918
|
+
count: number;
|
|
919
|
+
sale_price: number;
|
|
920
|
+
sku: string;
|
|
921
|
+
}[];
|
|
922
|
+
let tokenData = await post.getConfig({
|
|
923
|
+
key: 'login_line_setting',
|
|
924
|
+
user_id: 'manager',
|
|
925
|
+
});
|
|
926
|
+
|
|
927
|
+
if (cart) {
|
|
928
|
+
if (typeof cart === 'string') {
|
|
929
|
+
productData = JSON.parse(cart);
|
|
930
|
+
}
|
|
977
931
|
}
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
932
|
+
productData.forEach((data: any) => {
|
|
933
|
+
console.log(data.id, newData.id);
|
|
934
|
+
console.log(data.spec, newData.spec);
|
|
935
|
+
if (data.id == newData.id && areSpecsEqual(data.spec, newData.spec)) {
|
|
936
|
+
data.count = data.count ?? 1;
|
|
937
|
+
data.count++;
|
|
938
|
+
}
|
|
939
|
+
});
|
|
940
|
+
productData.push(JSON.parse(messageData.postback.data));
|
|
941
|
+
|
|
942
|
+
await redis.setValue(dataKey, JSON.stringify(productData));
|
|
943
|
+
let token = `${tokenData[0].value.message_token}`;
|
|
944
|
+
// await this.sendMessage(token , userId , message);
|
|
945
|
+
// await axios.post('https://api.line.me/v2/bot/message/reply', {
|
|
946
|
+
// replyToken: replyToken,
|
|
947
|
+
// messages: [
|
|
948
|
+
// {
|
|
949
|
+
// type: 'text',
|
|
950
|
+
// text: message, // 回應訊息
|
|
951
|
+
// },
|
|
952
|
+
// ]
|
|
953
|
+
// }, {
|
|
954
|
+
// headers: {
|
|
955
|
+
// 'Content-Type': 'application/json',
|
|
956
|
+
// 'Authorization': `Bearer ${token}`
|
|
957
|
+
// }
|
|
958
|
+
// });
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
public async sendCustomerLine(tag: string, order_id: string, lineID: string) {
|
|
962
|
+
const customerMail = await AutoSendEmail.getDefCompare(this.app, tag, 'zh-TW');
|
|
963
|
+
if (customerMail.toggle) {
|
|
964
|
+
await new Promise(async resolve => {
|
|
965
|
+
resolve(
|
|
966
|
+
await this.sendLine(
|
|
967
|
+
{
|
|
968
|
+
data: {
|
|
969
|
+
text: customerMail.content.replace(/@\{\{訂單號碼\}\}/g, order_id),
|
|
970
|
+
},
|
|
971
|
+
lineID: lineID,
|
|
972
|
+
},
|
|
973
|
+
res => {}
|
|
974
|
+
)
|
|
975
|
+
);
|
|
976
|
+
});
|
|
999
977
|
}
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
978
|
+
}
|
|
979
|
+
async sendMessage(token: string, userId: string, message: string) {
|
|
980
|
+
const url = 'https://api.line.me/v2/bot/message/push';
|
|
981
|
+
const headers = {
|
|
982
|
+
'Content-Type': 'application/json',
|
|
983
|
+
Authorization: `Bearer ${token}`,
|
|
984
|
+
};
|
|
985
|
+
const body = {
|
|
986
|
+
to: userId,
|
|
987
|
+
messages: [
|
|
988
|
+
{
|
|
989
|
+
type: 'text',
|
|
990
|
+
text: message,
|
|
991
|
+
},
|
|
992
|
+
],
|
|
993
|
+
};
|
|
994
|
+
try {
|
|
995
|
+
const response = await axios.post(url, body, { headers });
|
|
996
|
+
console.log('訊息發送成功:', response.data);
|
|
997
|
+
} catch (error: any) {
|
|
998
|
+
console.error('發送訊息時出錯:', error.response?.data || error.message);
|
|
1013
999
|
}
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
return `application/x-www-form-urlencoded; charset=UTF-8`;
|
|
1028
|
-
} else {
|
|
1029
|
-
return mime.getType(<string>fullUrl.split('.').pop());
|
|
1030
|
-
}
|
|
1031
|
-
})(),
|
|
1032
|
-
};
|
|
1033
|
-
return new Promise<string>((resolve, reject) => {
|
|
1034
|
-
s3bucket.getSignedUrl('putObject', params, async (err: any, url: any) => {
|
|
1035
|
-
if (err) {
|
|
1036
|
-
logger.error(TAG, String(err));
|
|
1037
|
-
// use console.log here because logger.info cannot log err.stack correctly
|
|
1038
|
-
console.log(err, err.stack);
|
|
1039
|
-
reject(false);
|
|
1040
|
-
} else {
|
|
1041
|
-
axios({
|
|
1042
|
-
method: 'PUT',
|
|
1043
|
-
url: url,
|
|
1044
|
-
data: fileData,
|
|
1045
|
-
headers: {
|
|
1046
|
-
'Content-Type': params.ContentType,
|
|
1047
|
-
},
|
|
1048
|
-
})
|
|
1049
|
-
.then(() => {
|
|
1050
|
-
console.log(fullUrl);
|
|
1051
|
-
resolve(fullUrl);
|
|
1052
|
-
})
|
|
1053
|
-
.catch(() => {
|
|
1054
|
-
console.log(`convertError:${fullUrl}`);
|
|
1055
|
-
});
|
|
1056
|
-
}
|
|
1057
|
-
});
|
|
1058
|
-
});
|
|
1000
|
+
}
|
|
1001
|
+
async getImageContent(messageId: string, accessToken: string): Promise<string> {
|
|
1002
|
+
try {
|
|
1003
|
+
const response = await axios.get(`https://api-data.line.me/v2/bot/message/${messageId}/content`, {
|
|
1004
|
+
headers: {
|
|
1005
|
+
Authorization: `Bearer ${accessToken}`,
|
|
1006
|
+
},
|
|
1007
|
+
responseType: 'arraybuffer', // 指定回應的資料格式為二進位 (Buffer)
|
|
1008
|
+
});
|
|
1009
|
+
return await this.uploadFile(`line/${messageId}/${new Date().getTime()}.png`, response.data); // 回傳圖片的 Buffer
|
|
1010
|
+
} catch (error) {
|
|
1011
|
+
console.error('Failed to get image content:', error);
|
|
1012
|
+
throw error;
|
|
1059
1013
|
}
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1014
|
+
}
|
|
1015
|
+
async uploadFile(file_name: string, fileData: Buffer) {
|
|
1016
|
+
const TAG = `[AWS-S3][Upload]`;
|
|
1017
|
+
const logger = new Logger();
|
|
1018
|
+
const s3bucketName = config.AWS_S3_NAME;
|
|
1019
|
+
const s3path = file_name;
|
|
1020
|
+
const fullUrl = config.AWS_S3_PREFIX_DOMAIN_NAME + s3path;
|
|
1021
|
+
const params = {
|
|
1022
|
+
Bucket: s3bucketName,
|
|
1023
|
+
Key: s3path,
|
|
1024
|
+
Expires: 300,
|
|
1025
|
+
//If you use other contentType will response 403 error
|
|
1026
|
+
ContentType: (() => {
|
|
1027
|
+
if (config.SINGLE_TYPE) {
|
|
1028
|
+
return `application/x-www-form-urlencoded; charset=UTF-8`;
|
|
1029
|
+
} else {
|
|
1030
|
+
return mime.getType(<string>fullUrl.split('.').pop());
|
|
1031
|
+
}
|
|
1032
|
+
})(),
|
|
1033
|
+
};
|
|
1034
|
+
return new Promise<string>((resolve, reject) => {
|
|
1035
|
+
s3bucket.getSignedUrl('putObject', params, async (err: any, url: any) => {
|
|
1036
|
+
if (err) {
|
|
1037
|
+
logger.error(TAG, String(err));
|
|
1038
|
+
// use console.log here because logger.info cannot log err.stack correctly
|
|
1039
|
+
console.log(err, err.stack);
|
|
1040
|
+
reject(false);
|
|
1041
|
+
} else {
|
|
1042
|
+
axios({
|
|
1043
|
+
method: 'PUT',
|
|
1044
|
+
url: url,
|
|
1045
|
+
data: fileData,
|
|
1046
|
+
headers: {
|
|
1047
|
+
'Content-Type': params.ContentType,
|
|
1048
|
+
},
|
|
1049
|
+
})
|
|
1050
|
+
.then(() => {
|
|
1051
|
+
console.log(fullUrl);
|
|
1052
|
+
resolve(fullUrl);
|
|
1053
|
+
})
|
|
1054
|
+
.catch(() => {
|
|
1055
|
+
console.log(`convertError:${fullUrl}`);
|
|
1056
|
+
});
|
|
1078
1057
|
}
|
|
1058
|
+
});
|
|
1059
|
+
});
|
|
1060
|
+
}
|
|
1061
|
+
async getGroupInf(groupId: string) {
|
|
1062
|
+
const post = new User(this.app, this.token);
|
|
1063
|
+
let tokenData = await post.getConfig({
|
|
1064
|
+
key: 'login_line_setting',
|
|
1065
|
+
user_id: 'manager',
|
|
1066
|
+
});
|
|
1067
|
+
let token = `${tokenData[0].value.message_token}`;
|
|
1068
|
+
const url = `https://api.line.me/v2/bot/group/${groupId}/summary`;
|
|
1069
|
+
const headers = {
|
|
1070
|
+
'Content-Type': 'application/json',
|
|
1071
|
+
Authorization: `Bearer ${token}`,
|
|
1072
|
+
};
|
|
1073
|
+
|
|
1074
|
+
try {
|
|
1075
|
+
const response = await axios.get(url, { headers });
|
|
1076
|
+
console.log('取得群組資訊:', response.data);
|
|
1077
|
+
} catch (error: any) {
|
|
1078
|
+
console.error('取得群組資訊錯誤:', error.response?.data || error.message);
|
|
1079
1079
|
}
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1080
|
+
}
|
|
1081
|
+
async handleJoinEvent(event: any, token?: string) {
|
|
1082
|
+
const replyToken = event.replyToken;
|
|
1083
|
+
const groupId = event.source.groupId;
|
|
1084
|
+
|
|
1085
|
+
console.log(`機器人加入群組: ${groupId}`);
|
|
1086
|
+
|
|
1087
|
+
// 透過 Reply API 送出歡迎訊息
|
|
1088
|
+
await axios.post(
|
|
1089
|
+
'https://api.line.me/v2/bot/message/reply',
|
|
1090
|
+
{
|
|
1091
|
+
replyToken: replyToken,
|
|
1092
|
+
messages: [
|
|
1093
|
+
{
|
|
1094
|
+
type: 'text',
|
|
1095
|
+
text: '👋 大家好,我是你的 LINE 機器人!請讓管理員點擊驗證按鈕以啟用機器人功能。',
|
|
1096
|
+
},
|
|
1097
|
+
{
|
|
1098
|
+
type: 'template',
|
|
1099
|
+
altText: '請點擊驗證按鈕來完成綁定',
|
|
1100
|
+
template: {
|
|
1101
|
+
type: 'buttons',
|
|
1102
|
+
text: '請點擊驗證按鈕',
|
|
1103
|
+
actions: [
|
|
1090
1104
|
{
|
|
1091
|
-
|
|
1092
|
-
|
|
1105
|
+
type: 'postback',
|
|
1106
|
+
label: '驗證群組',
|
|
1107
|
+
data: 'action=verify',
|
|
1093
1108
|
},
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
}
|
|
1114
|
-
|
|
1115
|
-
//判斷餘額是否足夠
|
|
1116
|
-
public async checkPoints(message: string, user_count: number) {
|
|
1117
|
-
const brandAndMemberType = await App.checkBrandAndMemberType(this.app);
|
|
1118
|
-
// 判斷錢包是否有餘額
|
|
1119
|
-
const sum =
|
|
1120
|
-
(
|
|
1121
|
-
await db.query(
|
|
1122
|
-
`SELECT sum(money)
|
|
1109
|
+
],
|
|
1110
|
+
},
|
|
1111
|
+
},
|
|
1112
|
+
],
|
|
1113
|
+
},
|
|
1114
|
+
{
|
|
1115
|
+
headers: { Authorization: `Bearer ${process.env.LINE_CHANNEL_ACCESS_TOKEN}` },
|
|
1116
|
+
}
|
|
1117
|
+
);
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
//判斷餘額是否足夠
|
|
1121
|
+
public async checkPoints(message: string, user_count: number) {
|
|
1122
|
+
const brandAndMemberType = await App.checkBrandAndMemberType(this.app);
|
|
1123
|
+
// 判斷錢包是否有餘額
|
|
1124
|
+
const sum =
|
|
1125
|
+
(
|
|
1126
|
+
await db.query(
|
|
1127
|
+
`SELECT sum(money)
|
|
1123
1128
|
FROM \`${brandAndMemberType.brand}\`.t_sms_points
|
|
1124
1129
|
WHERE status in (1, 2)
|
|
1125
1130
|
and userID = ?`,
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1131
|
+
[brandAndMemberType.user_id]
|
|
1132
|
+
)
|
|
1133
|
+
)[0]['sum(money)'] || 0;
|
|
1134
|
+
return sum > this.getUsePoints(message, user_count);
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
//點數扣除
|
|
1138
|
+
public async usePoints(obj: { message: string; user_count: number; order_id?: string; phone: string }) {
|
|
1139
|
+
if (!obj.phone) {
|
|
1140
|
+
return 0;
|
|
1130
1141
|
}
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
return 0;
|
|
1136
|
-
}
|
|
1137
|
-
let total = this.getUsePoints(obj.message, obj.user_count);
|
|
1138
|
-
const brandAndMemberType = await App.checkBrandAndMemberType(this.app);
|
|
1139
|
-
await db.query(
|
|
1140
|
-
`insert into \`${brandAndMemberType.brand}\`.t_sms_points
|
|
1142
|
+
let total = this.getUsePoints(obj.message, obj.user_count);
|
|
1143
|
+
const brandAndMemberType = await App.checkBrandAndMemberType(this.app);
|
|
1144
|
+
await db.query(
|
|
1145
|
+
`insert into \`${brandAndMemberType.brand}\`.t_sms_points
|
|
1141
1146
|
set ?`,
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1147
|
+
[
|
|
1148
|
+
{
|
|
1149
|
+
orderID: obj.order_id || Tool.randomNumber(8),
|
|
1150
|
+
money: total * -1,
|
|
1151
|
+
userID: brandAndMemberType.user_id,
|
|
1152
|
+
status: 1,
|
|
1153
|
+
note: JSON.stringify({
|
|
1154
|
+
message: obj.message,
|
|
1155
|
+
phone: obj.phone,
|
|
1156
|
+
}),
|
|
1157
|
+
},
|
|
1158
|
+
]
|
|
1159
|
+
);
|
|
1160
|
+
return total * -1;
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
public getUsePoints(text: string, user_count: number) {
|
|
1164
|
+
let pointCount = 0;
|
|
1165
|
+
const maxSize = 160;
|
|
1166
|
+
const longSMS = 153;
|
|
1167
|
+
let totalSize = 0;
|
|
1168
|
+
for (let i = 0; i < text.length; i++) {
|
|
1169
|
+
const char = text[i];
|
|
1170
|
+
if (/[\u4e00-\u9fa5\uFF00-\uFFEF]/.test(char)) {
|
|
1171
|
+
totalSize += 2;
|
|
1172
|
+
} else {
|
|
1173
|
+
totalSize += 1;
|
|
1174
|
+
}
|
|
1156
1175
|
}
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
const longSMS = 153;
|
|
1162
|
-
let totalSize = 0;
|
|
1163
|
-
for (let i = 0; i < text.length; i++) {
|
|
1164
|
-
const char = text[i];
|
|
1165
|
-
if (/[\u4e00-\u9fa5\uFF00-\uFFEF]/.test(char)) {
|
|
1166
|
-
totalSize += 2;
|
|
1167
|
-
} else {
|
|
1168
|
-
totalSize += 1;
|
|
1169
|
-
}
|
|
1170
|
-
}
|
|
1171
|
-
if (totalSize < maxSize) {
|
|
1172
|
-
pointCount = 1;
|
|
1173
|
-
} else {
|
|
1174
|
-
pointCount = Math.ceil(totalSize / longSMS);
|
|
1175
|
-
}
|
|
1176
|
-
return pointCount * 15 * user_count;
|
|
1176
|
+
if (totalSize < maxSize) {
|
|
1177
|
+
pointCount = 1;
|
|
1178
|
+
} else {
|
|
1179
|
+
pointCount = Math.ceil(totalSize / longSMS);
|
|
1177
1180
|
}
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
const headers = {
|
|
1190
|
-
"Authorization": `Bearer ${token}`
|
|
1191
|
-
};
|
|
1192
|
-
|
|
1193
|
-
try {
|
|
1194
|
-
const response = await axios.get(url, { headers });
|
|
1195
|
-
return response.data; // 返回使用者資訊
|
|
1196
|
-
} catch (error) {
|
|
1197
|
-
console.error("無法獲取使用者資訊:", error);
|
|
1198
|
-
return null;
|
|
1199
|
-
}
|
|
1181
|
+
return pointCount * 15 * user_count;
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
public async getUserProfile(userId: string, token?: string) {
|
|
1185
|
+
if (!token) {
|
|
1186
|
+
const post = new User(this.app, this.token);
|
|
1187
|
+
let tokenData = await post.getConfig({
|
|
1188
|
+
key: 'login_line_setting',
|
|
1189
|
+
user_id: 'manager',
|
|
1190
|
+
});
|
|
1191
|
+
token = `${tokenData[0].value.message_token}`;
|
|
1200
1192
|
}
|
|
1201
|
-
|
|
1193
|
+
const url = `https://api.line.me/v2/bot/profile/${userId}`;
|
|
1194
|
+
const headers = {
|
|
1195
|
+
Authorization: `Bearer ${token}`,
|
|
1196
|
+
};
|
|
1197
|
+
|
|
1198
|
+
try {
|
|
1199
|
+
const response = await axios.get(url, { headers });
|
|
1200
|
+
return response.data; // 返回使用者資訊
|
|
1201
|
+
} catch (error) {
|
|
1202
|
+
console.error('無法獲取使用者資訊:', error);
|
|
1203
|
+
return null;
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1202
1206
|
}
|
|
1203
1207
|
|
|
1204
1208
|
function formatDate(date: any) {
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1209
|
+
const year = date.getFullYear();
|
|
1210
|
+
const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
1211
|
+
const day = String(date.getDate()).padStart(2, '0');
|
|
1212
|
+
const hours = String(date.getHours()).padStart(2, '0');
|
|
1213
|
+
const minutes = String(date.getMinutes()).padStart(2, '0');
|
|
1214
|
+
const seconds = String(date.getSeconds()).padStart(2, '0');
|
|
1215
|
+
|
|
1216
|
+
return `${year}${month}${day}${hours}${minutes}${seconds}`;
|
|
1213
1217
|
}
|
|
1214
1218
|
|
|
1215
1219
|
function formatDateTime(sendTime?: { date: string; time: string }) {
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1220
|
+
const dateTimeString = sendTime ? sendTime.date + ' ' + sendTime.time : undefined;
|
|
1221
|
+
const dateObject = dateTimeString ? new Date(dateTimeString) : new Date();
|
|
1222
|
+
return formatDate(dateObject);
|
|
1219
1223
|
}
|
|
1220
1224
|
|
|
1221
1225
|
function chunkArray(array: any, groupSize: number) {
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1226
|
+
const result = [];
|
|
1227
|
+
for (let i = 0; i < array.length; i += groupSize) {
|
|
1228
|
+
result.push(array.slice(i, i + groupSize));
|
|
1229
|
+
}
|
|
1230
|
+
return result;
|
|
1227
1231
|
}
|
|
1228
1232
|
|
|
1229
1233
|
function isLater(dateTimeObj: { date: string; time: string }) {
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1234
|
+
const currentDateTime = new Date();
|
|
1235
|
+
const { date, time } = dateTimeObj;
|
|
1236
|
+
const dateTimeString = `${date}T${time}:00`;
|
|
1237
|
+
const providedDateTime = new Date(dateTimeString);
|
|
1238
|
+
return currentDateTime > providedDateTime;
|
|
1235
1239
|
}
|