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.
Files changed (54) hide show
  1. package/lowcode/Entry.js +1 -1
  2. package/lowcode/Entry.ts +2 -2
  3. package/lowcode/backend-manager/bg-blog.js +19 -5
  4. package/lowcode/backend-manager/bg-blog.ts +20 -5
  5. package/lowcode/backend-manager/bg-line.js +2 -2
  6. package/lowcode/backend-manager/bg-line.ts +2 -2
  7. package/lowcode/backend-manager/bg-notify.js +10 -3
  8. package/lowcode/backend-manager/bg-notify.ts +10 -3
  9. package/lowcode/backend-manager/bg-sns.js +3 -3
  10. package/lowcode/backend-manager/bg-sns.ts +3 -3
  11. package/lowcode/backend-manager/bg-widget.js +77 -75
  12. package/lowcode/backend-manager/bg-widget.ts +121 -97
  13. package/lowcode/cms-plugin/auto-fcm-advertise.js +2 -2
  14. package/lowcode/cms-plugin/auto-fcm-advertise.ts +2 -2
  15. package/lowcode/cms-plugin/auto-fcm-history.js +2 -2
  16. package/lowcode/cms-plugin/auto-fcm-history.ts +2 -2
  17. package/lowcode/cms-plugin/menus-setting.js +2 -2
  18. package/lowcode/cms-plugin/menus-setting.ts +2 -4
  19. package/lowcode/cms-plugin/pos-pages/payment-page.js +11 -7
  20. package/lowcode/cms-plugin/pos-pages/payment-page.ts +17 -11
  21. package/lowcode/cms-plugin/shopping-discount-setting.js +1 -1
  22. package/lowcode/cms-plugin/shopping-discount-setting.ts +1 -1
  23. package/lowcode/cms-plugin/shopping-finance-setting.js +23 -10
  24. package/lowcode/cms-plugin/shopping-finance-setting.ts +282 -269
  25. package/lowcode/cms-plugin/shopping-order-manager.js +1 -1
  26. package/lowcode/cms-plugin/shopping-order-manager.ts +1 -1
  27. package/lowcode/cms-plugin/shopping-rebate.js +1 -1
  28. package/lowcode/cms-plugin/shopping-rebate.ts +1 -1
  29. package/lowcode/cms-plugin/shopping-setting-basic.js +36 -8
  30. package/lowcode/cms-plugin/shopping-setting-basic.ts +36 -8
  31. package/lowcode/glitter-base/route/shopping.js +16 -9
  32. package/lowcode/glitter-base/route/shopping.ts +16 -10
  33. package/lowcode/glitter-base/route/user.js +19 -9
  34. package/lowcode/glitter-base/route/user.ts +21 -10
  35. package/lowcode/glitterBundle/plugins/editor-elem.js +1 -5
  36. package/lowcode/glitterBundle/plugins/editor-elem.ts +1 -5
  37. package/package.json +1 -1
  38. package/src/api-public/services/checkout-event.js +17 -7
  39. package/src/api-public/services/checkout-event.js.map +1 -1
  40. package/src/api-public/services/line-message.js +122 -138
  41. package/src/api-public/services/line-message.js.map +1 -1
  42. package/src/api-public/services/line-message.ts +1134 -1130
  43. package/src/api-public/services/shopping.js +13 -6
  44. package/src/api-public/services/shopping.js.map +1 -1
  45. package/src/api-public/services/shopping.ts +13 -7
  46. package/src/api-public/services/strategies/ecpay-strategy.js +0 -1
  47. package/src/api-public/services/strategies/ecpay-strategy.js.map +1 -1
  48. package/src/api-public/services/strategies/ecpay-strategy.ts +3 -4
  49. package/src/api-public/services/strategies/ezpay-strategy.js +0 -1
  50. package/src/api-public/services/strategies/ezpay-strategy.js.map +1 -1
  51. package/src/api-public/services/strategies/ezpay-strategy.ts +4 -7
  52. package/src/modules/firebase.js +0 -4
  53. package/src/modules/firebase.js.map +1 -1
  54. 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, { AxiosRequestConfig, AxiosResponse } from '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 "../../modules/redis.js";
15
- import process from "process";
16
- import {ShopnexLineMessage} from "./model/shopnex-line-message";
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
- // 定義 response 物件的結構,根據實際 API 回應的格式進行調整
21
- clientid?: string;
22
- msgid?: string;
23
- statuscode: number;
24
- accountPoint?: number;
25
- Duplicate?: string;
26
- smsPoint?: number;
27
- message?: string;
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
- chat_id: string;
32
- type: 'user' | 'group';
33
- info: any;
34
- participant: string[];
31
+ chat_id: string;
32
+ type: 'user' | 'group';
33
+ info: any;
34
+ participant: string[];
35
35
  }
36
36
 
37
37
  interface Config {
38
- method: 'post' | 'get';
39
- url: string;
40
- headers: Record<string, string>;
41
- data: any;
38
+ method: 'post' | 'get';
39
+ url: string;
40
+ headers: Record<string, string>;
41
+ data: any;
42
42
  }
43
43
 
44
44
  interface LineData {
45
- username: string;
46
- password: string;
47
- dstaddr: string;
48
- smbody: string;
49
- smsPointFlag: number;
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
- 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
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
- [id]
77
- );
78
- resolve(true);
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
- async sendLine(
126
- obj: {
127
- data: {
128
- text?: string;
129
- image?: string;
130
- attachment?: any;
131
- };
132
- lineID: string;
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
- 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
171
- },
172
- ],
173
- };
174
- return new Promise<boolean>((resolve, reject) => {
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
- });
190
- });
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
- };
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
- data: JSON.stringify(postData),
172
+ ],
216
173
  };
217
- console.log("Config -- " , urlConfig);
218
-
219
174
  return new Promise<boolean>((resolve, reject) => {
220
- axios
221
- .request(urlConfig)
222
- .then((response:any) => {
223
- callback(response);
224
- resolve(response.data);
225
- })
226
- .catch((error:any) => {
227
- console.log(error);
228
- console.log('error -- ', error.data);
229
- resolve(false);
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
- } catch (e: any) {
234
- throw exception.BadRequestError('BAD_REQUEST', 'send line Error:' + e.data, null);
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
- async deleteSNS(obj: { id: string }, callback: (data: any) => void) {
239
- try {
240
- const urlConfig: Config = {
241
- method: 'post',
242
- url: 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
- };
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
- 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
- });
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
- // 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);
282
- }
230
+ });
231
+ }
232
+ } catch (e: any) {
233
+ throw exception.BadRequestError('BAD_REQUEST', 'send line Error:' + e.data, null);
283
234
  }
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);
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
- async getLine(query: { type: string; page: number; limit: number; search?: string; searchType?: string; mailType?: string; status?: string }) {
291
- try {
292
- const whereList: string[] = ['1 = 1'];
293
- switch (query.searchType) {
294
- case 'phone':
295
- whereList.push(`(JSON_SEARCH(content->'$.phone', 'one', '%${query.search ?? ''}%', NULL, '$[*]') IS NOT NULL)`);
296
- break;
297
- case 'name':
298
- whereList.push(`(UPPER(JSON_EXTRACT(content, '$.name')) LIKE UPPER('%${query.search ?? ''}%'))`);
299
- break;
300
- case 'title':
301
- whereList.push(`(UPPER(JSON_EXTRACT(content, '$.title')) LIKE UPPER('%${query.search ?? ''}%'))`);
302
- break;
303
- }
304
-
305
- if (query.status) {
306
- whereList.push(`(status in (${query.status}))`);
307
- }
308
-
309
- if (query.mailType) {
310
- const maiTypeString = query.mailType.replace(/[^,]+/g, "'$&'");
311
- whereList.push(`(JSON_EXTRACT(content, '$.type') in (${maiTypeString}))`);
312
- }
313
-
314
- const whereSQL = `(tag = 'sendLine' OR tag = 'sendLineBySchedule') AND ${whereList.join(' AND ')}`;
315
-
316
- const emails = await db.query(
317
- `SELECT *
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
- const total = await db.query(
326
- `SELECT count(id) as c
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
- let n = 0;
333
- await new Promise<void>((resolve) => {
334
- for (const email of emails) {
335
- AutoSendEmail.getDefCompare(this.app, email.content.type,'zh-TW').then((dd) => {
336
- email.content.typeName = dd && dd.tag_name ? dd.tag_name : '手動發送';
337
- n++;
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
- async postLine(data: any): Promise<{ result: boolean; message: string }> {
355
- data.msgid = '';
356
- try {
357
- if (Boolean(data.sendTime)) {
358
- if (isLater(data.sendTime)) {
359
- return { result: false, message: '排定發送的時間需大於現在時間' };
360
- }
361
- const insertData = await db.query(
362
- `INSERT INTO \`${this.app}\`.\`t_triggers\`
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
- tag: 'sendLineBySchedule',
367
- content: JSON.stringify(data),
368
- trigger_time: formatDateTime(data.sendTime),
369
- status: 0,
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
- // this.chunkSendLine(data.userList , data.content , insertData.insertId , formatDateTime(data.sendTime));
375
- } else {
376
- const insertData = await db.query(`INSERT INTO \`${this.app}\`.\`t_triggers\`
377
- SET ?;`, [
378
- {
379
- tag: 'sendLine',
380
- content: JSON.stringify(data),
381
- trigger_time: formatDateTime(),
382
- status: 0,
383
- },
384
- ]);
385
- this.chunkSendLine(data.userList, {
386
- text:data.content
387
- }, insertData.insertId);
388
- }
389
- return { result: true, message: '寄送成功' };
390
- } catch (e) {
391
- throw exception.BadRequestError('BAD_REQUEST', 'postMail Error:' + e, null);
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
- async deleteSns(data: any): Promise<{ result: boolean; message: string }> {
396
- try {
397
- const emails = await db.query(
398
- `SELECT *
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
- await new Promise((resolve) => {
404
- this.deleteSNS({ id: data.id }, (res) => {
405
- resolve(true);
406
- });
407
- });
418
+ []
419
+ );
420
+ await new Promise(resolve => {
421
+ this.deleteSNS({ id: data.id }, res => {
422
+ resolve(true);
423
+ });
424
+ });
408
425
 
409
- await db.query(
410
- `UPDATE \`${this.app}\`.t_triggers
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
- return { result: true, message: '取消預約成功' };
416
- } catch (e) {
417
- throw exception.BadRequestError('BAD_REQUEST', 'postMail Error:' + e, null);
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
- async listenMessage(data: any): Promise<{ result: boolean; message: string }> {
422
- try {
423
- const events = data.events;
424
- //todo 在env上有個line_destination 記得這邊有修改這個log出來的id 隨便發話給機器人就會收到了
425
- // console.log("data.destination -- " , data.destination);
426
- if (data.destination == process.env.line_destination){
427
-
428
- console.log("處理shopnex官方機器人事件")
429
- for (const event of events) {
430
- switch (event.type){
431
- case "message":
432
- let data = await this.getUserProfile("U152cb05f49499386f506867cb6adff96")
433
- break;
434
-
435
- case "postback":
436
- console.log("收到 Postback 事件");
437
- await ShopnexLineMessage.handlePostbackEvent(event , this.app)
438
- // let data = await this.getUserProfile("U152cb05f49499386f506867cb6adff96")
439
- break;
440
-
441
- case "join":
442
- console.log("機器人被加入群組/聊天室");
443
- await ShopnexLineMessage.handleJoinEvent(event , this.app);
444
- break;
445
-
446
- case "leave":
447
- console.log("機器人被移出群組/聊天室");
448
- break;
449
-
450
- default:
451
- console.log("未知事件類型:", event.type);
452
- break;
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
- return { result: true, message: 'accept message' };
763
+ );
764
+ } catch (e: any) {
765
+ console.log('e -- ', e.response.data);
457
766
  }
458
-
459
-
460
- let message: {
461
- type: string;
462
- id: string;
463
- quoteToken: string;
464
- text: string;
465
- } = data.events[0].message;
466
- let userID = 'line_' + data.events[0].source.userId;
467
- let chatData: any = {
468
- chat_id: [userID, 'manager'].sort().join(''),
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
- const post = new User(this.app, this.token);
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
- for (const event of events) {
483
- if (event.source.type == 'group'){
484
- await this.getGroupInf(data.events[0].source.groupId)
485
- //圖文輪播按鍵事件處理,這裡預設是點擊我要購買 或是有人喊商品+1
486
- if (data.events[0]?.postback?.data){
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
- info: chatData.info,
791
- },
792
- {
793
- chat_id: chatData.chat_id,
794
- },
795
- ]
796
- );
797
- }
798
- //根據訊息的型態 做不同處理
799
- if (message.type == 'image') {
800
- const post = new User(this.app, this.token);
801
- let tokenData = await post.getConfig({
802
- key: 'login_line_setting',
803
- user_id: 'manager',
804
- });
805
- let token = `${tokenData[0].value.message_token}`;
806
- let imageUrl = await this.getImageContent(message.id, token);
807
- chatData.message = {
808
- image: imageUrl,
809
- };
810
- } else {
811
- chatData.message = {
812
- text: message.text,
813
- };
814
- }
815
- await new Chat(this.app).addMessage(chatData);
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
- async createOrderWithLineFlexMessage(messageData:any , message:string){
894
- console.log("message -- " , messageData);
895
- function areSpecsEqual(spec1: string[], spec2: string[]): boolean {
896
- if (spec1.length !== spec2.length) {
897
- return false;
898
- }
899
- // 比較排序後的內容
900
- const sortedSpec1 = [...spec1].sort();
901
- const sortedSpec2 = [...spec2].sort();
902
- return sortedSpec1.every((value, index) => value === sortedSpec2[index]);
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
- const replyToken = messageData.replyToken
906
- const post = new User(this.app, this.token);
907
- const groupId = messageData.source.groupId;
908
- const userId = messageData.source.userId || '未知使用者';
909
- const dataKey = groupId + "-" + userId;
910
- const cart = await redis.getValue(dataKey);
911
- const newData = JSON.parse(messageData.postback.data);
912
- let productData = [];
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
- public async sendCustomerLine(tag: string, order_id: string, lineID: string) {
964
- const customerMail = await AutoSendEmail.getDefCompare(this.app, tag,'zh-TW');
965
- if (customerMail.toggle) {
966
- await new Promise(async (resolve) => {
967
- resolve(await this.sendLine({
968
- data: {
969
- text:customerMail.content.replace(/@\{\{訂單號碼\}\}/g, order_id)
970
- },
971
- lineID: lineID
972
- }, (res) => {
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
- async sendMessage(token:string , userId:string , message:string) {
979
- const url = 'https://api.line.me/v2/bot/message/push';
980
- const headers = {
981
- 'Content-Type': 'application/json',
982
- Authorization: `Bearer ${token}`,
983
- };
984
- const body = {
985
- to: userId,
986
- messages: [
987
- {
988
- type: 'text',
989
- text: message,
990
- },
991
- ],
992
- };
993
- try {
994
- const response = await axios.post(url, body, { headers });
995
- console.log('訊息發送成功:', response.data);
996
- } catch (error:any) {
997
- console.error('發送訊息時出錯:', error.response?.data || error.message);
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
- async getImageContent(messageId: string, accessToken: string): Promise<string> {
1001
- try {
1002
- const response = await axios.get(`https://api-data.line.me/v2/bot/message/${messageId}/content`, {
1003
- headers: {
1004
- Authorization: `Bearer ${accessToken}`,
1005
- },
1006
- responseType: 'arraybuffer', // 指定回應的資料格式為二進位 (Buffer)
1007
- });
1008
- return await this.uploadFile(`line/${messageId}/${new Date().getTime()}.png`, response.data); // 回傳圖片的 Buffer
1009
- } catch (error) {
1010
- console.error('Failed to get image content:', error);
1011
- throw error;
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
- async uploadFile(file_name: string, fileData: Buffer) {
1015
- const TAG = `[AWS-S3][Upload]`;
1016
- const logger = new Logger();
1017
- const s3bucketName = config.AWS_S3_NAME;
1018
- const s3path = file_name;
1019
- const fullUrl = config.AWS_S3_PREFIX_DOMAIN_NAME + s3path;
1020
- const params = {
1021
- Bucket: s3bucketName,
1022
- Key: s3path,
1023
- Expires: 300,
1024
- //If you use other contentType will response 403 error
1025
- ContentType: (() => {
1026
- if (config.SINGLE_TYPE) {
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
- async getGroupInf(groupId: string) {
1061
- const post = new User(this.app, this.token);
1062
- let tokenData = await post.getConfig({
1063
- key: 'login_line_setting',
1064
- user_id: 'manager',
1065
- });
1066
- let token = `${tokenData[0].value.message_token}`;
1067
- const url = `https://api.line.me/v2/bot/group/${groupId}/summary`;
1068
- const headers = {
1069
- 'Content-Type': 'application/json',
1070
- Authorization: `Bearer ${token}`,
1071
- };
1072
-
1073
- try {
1074
- const response = await axios.get(url, { headers });
1075
- console.log('取得群組資訊:', response.data);
1076
- } catch (error:any) {
1077
- console.error('取得群組資訊錯誤:', error.response?.data || error.message);
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
- async handleJoinEvent(event: any , token?:string) {
1081
- const replyToken = event.replyToken;
1082
- const groupId = event.source.groupId;
1083
-
1084
- console.log(`機器人加入群組: ${groupId}`);
1085
-
1086
- // 透過 Reply API 送出歡迎訊息
1087
- await axios.post("https://api.line.me/v2/bot/message/reply", {
1088
- replyToken: replyToken,
1089
- messages: [
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
- type: "text",
1092
- text: "👋 大家好,我是你的 LINE 機器人!請讓管理員點擊驗證按鈕以啟用機器人功能。"
1105
+ type: 'postback',
1106
+ label: '驗證群組',
1107
+ data: 'action=verify',
1093
1108
  },
1094
- {
1095
- type: "template",
1096
- altText: "請點擊驗證按鈕來完成綁定",
1097
- template: {
1098
- type: "buttons",
1099
- text: "請點擊驗證按鈕",
1100
- actions: [
1101
- {
1102
- type: "postback",
1103
- label: "驗證群組",
1104
- data: "action=verify"
1105
- }
1106
- ]
1107
- }
1108
- }
1109
- ]
1110
- }, {
1111
- headers: { Authorization: `Bearer ${process.env.LINE_CHANNEL_ACCESS_TOKEN}` }
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
- [brandAndMemberType.user_id]
1127
- )
1128
- )[0]['sum(money)'] || 0;
1129
- return sum > this.getUsePoints(message, user_count);
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
- public async usePoints(obj: { message: string; user_count: number; order_id?: string; phone: string }) {
1134
- if (!obj.phone) {
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
- orderID: obj.order_id || Tool.randomNumber(8),
1145
- money: total * -1,
1146
- userID: brandAndMemberType.user_id,
1147
- status: 1,
1148
- note: JSON.stringify({
1149
- message: obj.message,
1150
- phone: obj.phone,
1151
- }),
1152
- },
1153
- ]
1154
- );
1155
- return total * -1;
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
- public getUsePoints(text: string, user_count: number) {
1159
- let pointCount = 0;
1160
- const maxSize = 160;
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
- public async getUserProfile(userId: string , token?:string) {
1180
- if (!token){
1181
- const post = new User(this.app, this.token);
1182
- let tokenData = await post.getConfig({
1183
- key: 'login_line_setting',
1184
- user_id: 'manager',
1185
- });
1186
- token = `${tokenData[0].value.message_token}`;
1187
- }
1188
- const url = `https://api.line.me/v2/bot/profile/${userId}`;
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
- const year = date.getFullYear();
1206
- const month = String(date.getMonth() + 1).padStart(2, '0');
1207
- const day = String(date.getDate()).padStart(2, '0');
1208
- const hours = String(date.getHours()).padStart(2, '0');
1209
- const minutes = String(date.getMinutes()).padStart(2, '0');
1210
- const seconds = String(date.getSeconds()).padStart(2, '0');
1211
-
1212
- return `${year}${month}${day}${hours}${minutes}${seconds}`;
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
- const dateTimeString = sendTime ? sendTime.date + ' ' + sendTime.time : undefined;
1217
- const dateObject = dateTimeString ? new Date(dateTimeString) : new Date();
1218
- return formatDate(dateObject);
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
- const result = [];
1223
- for (let i = 0; i < array.length; i += groupSize) {
1224
- result.push(array.slice(i, i + groupSize));
1225
- }
1226
- return result;
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
- const currentDateTime = new Date();
1231
- const { date, time } = dateTimeObj;
1232
- const dateTimeString = `${date}T${time}:00`;
1233
- const providedDateTime = new Date(dateTimeString);
1234
- return currentDateTime > providedDateTime;
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
  }