n8n-nodes-signal-cli-rest-api 0.2.2 → 0.3.0
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/dist/nodes/Signal/Signal.node.js +61 -47
- package/dist/nodes/Signal/SignalTrigger.node.js +25 -20
- package/dist/nodes/Signal/attachments.d.ts +10 -0
- package/dist/nodes/Signal/attachments.js +152 -0
- package/dist/nodes/Signal/messages.js +3 -3
- package/dist/nodes/Signal/tools/messageHistory.d.ts +10 -0
- package/dist/nodes/Signal/tools/messageHistory.js +58 -0
- package/package.json +1 -1
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.Signal = void 0;
|
|
4
|
+
const n8n_workflow_1 = require("n8n-workflow");
|
|
4
5
|
const messages_1 = require("./messages");
|
|
5
6
|
const groups_1 = require("./groups");
|
|
6
7
|
const contacts_1 = require("./contacts");
|
|
8
|
+
const attachments_1 = require("./attachments");
|
|
7
9
|
class Signal {
|
|
8
10
|
constructor() {
|
|
9
11
|
this.description = {
|
|
@@ -56,6 +58,24 @@ class Signal {
|
|
|
56
58
|
description: 'Remove a reaction from a message',
|
|
57
59
|
action: 'Remove a reaction',
|
|
58
60
|
},
|
|
61
|
+
{
|
|
62
|
+
name: 'Attachments: List Attachments',
|
|
63
|
+
value: 'listAttachments',
|
|
64
|
+
description: 'List attachments for the account',
|
|
65
|
+
action: 'List attachments',
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
name: 'Attachments: Download Attachment',
|
|
69
|
+
value: 'downloadAttachment',
|
|
70
|
+
description: 'Download an attachment as binary file',
|
|
71
|
+
action: 'Download attachment',
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
name: 'Attachments: Remove Attachment',
|
|
75
|
+
value: 'removeAttachment',
|
|
76
|
+
description: 'Remove an attachment',
|
|
77
|
+
action: 'Remove attachment',
|
|
78
|
+
},
|
|
59
79
|
{
|
|
60
80
|
name: 'Contacts: Get Contacts',
|
|
61
81
|
value: 'getContacts',
|
|
@@ -174,46 +194,16 @@ class Signal {
|
|
|
174
194
|
allowCustom: true,
|
|
175
195
|
},
|
|
176
196
|
options: [
|
|
177
|
-
{
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
},
|
|
181
|
-
{
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
},
|
|
185
|
-
{
|
|
186
|
-
|
|
187
|
-
value: '😄',
|
|
188
|
-
},
|
|
189
|
-
{
|
|
190
|
-
name: 'Sad',
|
|
191
|
-
value: '😢',
|
|
192
|
-
},
|
|
193
|
-
{
|
|
194
|
-
name: 'Angry',
|
|
195
|
-
value: '😣',
|
|
196
|
-
},
|
|
197
|
-
{
|
|
198
|
-
name: 'Star',
|
|
199
|
-
value: '⭐',
|
|
200
|
-
},
|
|
201
|
-
{
|
|
202
|
-
name: 'Fire',
|
|
203
|
-
value: '🔥',
|
|
204
|
-
},
|
|
205
|
-
{
|
|
206
|
-
name: 'Plus',
|
|
207
|
-
value: '➕',
|
|
208
|
-
},
|
|
209
|
-
{
|
|
210
|
-
name: 'Minus',
|
|
211
|
-
value: '➖',
|
|
212
|
-
},
|
|
213
|
-
{
|
|
214
|
-
name: 'Handshake',
|
|
215
|
-
value: '🤝',
|
|
216
|
-
},
|
|
197
|
+
{ name: 'Thumbs Up', value: '👍' },
|
|
198
|
+
{ name: 'Heart', value: '❤️' },
|
|
199
|
+
{ name: 'Smile', value: '😄' },
|
|
200
|
+
{ name: 'Sad', value: '😢' },
|
|
201
|
+
{ name: 'Angry', value: '😣' },
|
|
202
|
+
{ name: 'Star', value: '⭐' },
|
|
203
|
+
{ name: 'Fire', value: '🔥' },
|
|
204
|
+
{ name: 'Plus', value: '➕' },
|
|
205
|
+
{ name: 'Minus', value: '➖' },
|
|
206
|
+
{ name: 'Handshake', value: '🤝' },
|
|
217
207
|
],
|
|
218
208
|
displayOptions: {
|
|
219
209
|
show: {
|
|
@@ -248,6 +238,20 @@ class Signal {
|
|
|
248
238
|
},
|
|
249
239
|
},
|
|
250
240
|
},
|
|
241
|
+
{
|
|
242
|
+
displayName: 'Attachment ID',
|
|
243
|
+
name: 'attachmentId',
|
|
244
|
+
type: 'string',
|
|
245
|
+
default: '',
|
|
246
|
+
placeholder: 'attachment_id_from_trigger.png',
|
|
247
|
+
description: 'ID of the attachment to download or remove',
|
|
248
|
+
required: true,
|
|
249
|
+
displayOptions: {
|
|
250
|
+
show: {
|
|
251
|
+
operation: ['downloadAttachment', 'removeAttachment'],
|
|
252
|
+
},
|
|
253
|
+
},
|
|
254
|
+
},
|
|
251
255
|
{
|
|
252
256
|
displayName: 'Timeout (seconds)',
|
|
253
257
|
name: 'timeout',
|
|
@@ -256,7 +260,7 @@ class Signal {
|
|
|
256
260
|
description: 'Request timeout in seconds (set higher for Get Groups, e.g., 300)',
|
|
257
261
|
displayOptions: {
|
|
258
262
|
show: {
|
|
259
|
-
operation: ['sendMessage', 'sendAttachment', 'sendReaction', 'removeReaction', 'getContacts', 'getGroups', 'createGroup', 'updateGroup'],
|
|
263
|
+
operation: ['sendMessage', 'sendAttachment', 'sendReaction', 'removeReaction', 'getContacts', 'getGroups', 'createGroup', 'updateGroup', 'listAttachments', 'downloadAttachment', 'removeAttachment'],
|
|
260
264
|
},
|
|
261
265
|
},
|
|
262
266
|
typeOptions: {
|
|
@@ -276,6 +280,7 @@ class Signal {
|
|
|
276
280
|
const apiUrl = credentials.apiUrl;
|
|
277
281
|
const apiToken = credentials.apiToken;
|
|
278
282
|
const phoneNumber = credentials.phoneNumber;
|
|
283
|
+
this.logger.debug(`Signal: Starting execute for operation ${operation}, items length: ${items.length}`);
|
|
279
284
|
for (let i = 0; i < items.length; i++) {
|
|
280
285
|
const timeout = this.getNodeParameter('timeout', i, operation === 'getGroups' ? 300 : 60) * 1000;
|
|
281
286
|
const params = {
|
|
@@ -288,29 +293,38 @@ class Signal {
|
|
|
288
293
|
emoji: this.getNodeParameter('emoji', i, ''),
|
|
289
294
|
targetAuthor: this.getNodeParameter('targetAuthor', i, ''),
|
|
290
295
|
targetSentTimestamp: this.getNodeParameter('targetSentTimestamp', i, 0),
|
|
296
|
+
attachmentId: this.getNodeParameter('attachmentId', i, ''),
|
|
291
297
|
timeout,
|
|
292
298
|
apiUrl,
|
|
293
299
|
apiToken,
|
|
294
300
|
phoneNumber,
|
|
295
301
|
};
|
|
296
302
|
try {
|
|
303
|
+
let result;
|
|
297
304
|
if (['sendMessage', 'sendAttachment', 'sendReaction', 'removeReaction'].includes(operation)) {
|
|
298
|
-
|
|
299
|
-
|
|
305
|
+
result = await messages_1.executeMessagesOperation.call(this, operation, i, params);
|
|
306
|
+
}
|
|
307
|
+
else if (['listAttachments', 'downloadAttachment', 'removeAttachment'].includes(operation)) {
|
|
308
|
+
result = await attachments_1.executeAttachmentsOperation.call(this, operation, i, params);
|
|
300
309
|
}
|
|
301
310
|
else if (['getGroups', 'createGroup', 'updateGroup'].includes(operation)) {
|
|
302
|
-
|
|
303
|
-
returnData.push(result);
|
|
311
|
+
result = await groups_1.executeGroupsOperation.call(this, operation, i, params);
|
|
304
312
|
}
|
|
305
313
|
else if (operation === 'getContacts') {
|
|
306
|
-
|
|
307
|
-
|
|
314
|
+
result = await contacts_1.executeContactsOperation.call(this, operation, i, params);
|
|
315
|
+
}
|
|
316
|
+
else {
|
|
317
|
+
throw new n8n_workflow_1.NodeApiError(this.getNode(), { message: 'Unknown operation' });
|
|
308
318
|
}
|
|
319
|
+
this.logger.info(`Signal: Operation ${operation} result for item ${i}: ${JSON.stringify(result.json || result.binary, null, 2)}`);
|
|
320
|
+
returnData.push(result);
|
|
309
321
|
}
|
|
310
322
|
catch (error) {
|
|
323
|
+
this.logger.error(`Signal: Error in operation ${operation} for item ${i}`, { error });
|
|
311
324
|
throw error;
|
|
312
325
|
}
|
|
313
326
|
}
|
|
327
|
+
this.logger.debug(`Signal: Returning data length: ${returnData.length}`);
|
|
314
328
|
return [returnData];
|
|
315
329
|
}
|
|
316
330
|
}
|
|
@@ -35,25 +35,25 @@ class SignalTrigger {
|
|
|
35
35
|
},
|
|
36
36
|
},
|
|
37
37
|
{
|
|
38
|
-
displayName: '
|
|
39
|
-
name: '
|
|
38
|
+
displayName: 'Ignore Messages',
|
|
39
|
+
name: 'ignoreMessages',
|
|
40
40
|
type: 'boolean',
|
|
41
|
-
default:
|
|
42
|
-
description: '
|
|
41
|
+
default: false,
|
|
42
|
+
description: 'Enable to ignore messages with text content',
|
|
43
43
|
},
|
|
44
44
|
{
|
|
45
|
-
displayName: '
|
|
46
|
-
name: '
|
|
45
|
+
displayName: 'Ignore Attachments',
|
|
46
|
+
name: 'ignoreAttachments',
|
|
47
47
|
type: 'boolean',
|
|
48
48
|
default: false,
|
|
49
|
-
description: '
|
|
49
|
+
description: 'Enable to ignore messages with attachments',
|
|
50
50
|
},
|
|
51
51
|
{
|
|
52
|
-
displayName: '
|
|
53
|
-
name: '
|
|
52
|
+
displayName: 'Ignore Reactions',
|
|
53
|
+
name: 'ignoreReactions',
|
|
54
54
|
type: 'boolean',
|
|
55
55
|
default: false,
|
|
56
|
-
description: '
|
|
56
|
+
description: 'Enable to ignore messages with reactions',
|
|
57
57
|
},
|
|
58
58
|
],
|
|
59
59
|
};
|
|
@@ -64,21 +64,25 @@ class SignalTrigger {
|
|
|
64
64
|
const apiToken = credentials.apiToken;
|
|
65
65
|
const phoneNumber = credentials.phoneNumber;
|
|
66
66
|
const reconnectDelay = this.getNodeParameter('reconnectDelay', 0) * 1000;
|
|
67
|
-
const
|
|
68
|
-
const
|
|
69
|
-
const
|
|
67
|
+
const ignoreMessages = this.getNodeParameter('ignoreMessages', 0);
|
|
68
|
+
const ignoreAttachments = this.getNodeParameter('ignoreAttachments', 0);
|
|
69
|
+
const ignoreReactions = this.getNodeParameter('ignoreReactions', 0);
|
|
70
70
|
const wsUrl = `${apiUrl.replace('http', 'ws')}/v1/receive/${phoneNumber}`;
|
|
71
|
+
this.logger.debug(`SignalTrigger: Attempting to connect to WS URL: ${wsUrl}`);
|
|
71
72
|
const processedMessages = new Set();
|
|
72
73
|
const maxMessages = 1000;
|
|
73
74
|
const connectWebSocket = () => {
|
|
74
75
|
const ws = new ws_1.WebSocket(wsUrl, {
|
|
75
76
|
headers: apiToken ? { Authorization: `Bearer ${apiToken}` } : {},
|
|
76
77
|
});
|
|
78
|
+
ws.on('open', () => {
|
|
79
|
+
this.logger.debug(`SignalTrigger: Successfully connected to ${wsUrl}`);
|
|
80
|
+
});
|
|
77
81
|
ws.on('message', (data) => {
|
|
78
82
|
var _a, _b, _c, _d, _e;
|
|
79
83
|
try {
|
|
80
84
|
const message = JSON.parse(data.toString());
|
|
81
|
-
this.logger.debug(`SignalTrigger: Received message: ${JSON.stringify(message, null, 2)}`);
|
|
85
|
+
this.logger.debug(`SignalTrigger: Received raw message: ${JSON.stringify(message, null, 2)}`);
|
|
82
86
|
const timestamp = ((_a = message.envelope) === null || _a === void 0 ? void 0 : _a.timestamp) || 0;
|
|
83
87
|
if (processedMessages.has(timestamp)) {
|
|
84
88
|
this.logger.debug(`SignalTrigger: Skipping duplicate message with timestamp ${timestamp}`);
|
|
@@ -97,6 +101,7 @@ class SignalTrigger {
|
|
|
97
101
|
timestamp: timestamp,
|
|
98
102
|
account: message.account || '',
|
|
99
103
|
};
|
|
104
|
+
this.logger.debug(`SignalTrigger: Processed message content: ${JSON.stringify(processedMessage, null, 2)}`);
|
|
100
105
|
// Ігнорувати події без вмісту
|
|
101
106
|
if (!processedMessage.messageText &&
|
|
102
107
|
processedMessage.attachments.length === 0 &&
|
|
@@ -104,11 +109,11 @@ class SignalTrigger {
|
|
|
104
109
|
this.logger.debug(`SignalTrigger: Skipping empty message with timestamp ${timestamp}`);
|
|
105
110
|
return;
|
|
106
111
|
}
|
|
107
|
-
//
|
|
108
|
-
if ((
|
|
109
|
-
(
|
|
110
|
-
(
|
|
111
|
-
this.logger.debug(`SignalTrigger:
|
|
112
|
+
// Фільтрація: ігнорувати, якщо увімкнено ignore і відповідний вміст присутній
|
|
113
|
+
if ((ignoreMessages && processedMessage.messageText) ||
|
|
114
|
+
(ignoreAttachments && processedMessage.attachments.length > 0) ||
|
|
115
|
+
(ignoreReactions && processedMessage.reactions.length > 0)) {
|
|
116
|
+
this.logger.debug(`SignalTrigger: Ignoring message with timestamp ${timestamp} due to filter`);
|
|
112
117
|
return;
|
|
113
118
|
}
|
|
114
119
|
const returnData = {
|
|
@@ -134,7 +139,7 @@ class SignalTrigger {
|
|
|
134
139
|
const ws = connectWebSocket();
|
|
135
140
|
return new Promise((resolve, reject) => {
|
|
136
141
|
ws.on('open', () => {
|
|
137
|
-
this.logger.debug(`SignalTrigger:
|
|
142
|
+
this.logger.debug(`SignalTrigger: Initial connection to ${wsUrl}`);
|
|
138
143
|
resolve({
|
|
139
144
|
closeFunction: async () => {
|
|
140
145
|
ws.close();
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
|
|
2
|
+
interface OperationParams {
|
|
3
|
+
attachmentId?: string;
|
|
4
|
+
timeout: number;
|
|
5
|
+
apiUrl: string;
|
|
6
|
+
apiToken: string;
|
|
7
|
+
phoneNumber: string;
|
|
8
|
+
}
|
|
9
|
+
export declare function executeAttachmentsOperation(this: IExecuteFunctions, operation: string, itemIndex: number, params: OperationParams): Promise<INodeExecutionData>;
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.executeAttachmentsOperation = void 0;
|
|
7
|
+
const n8n_workflow_1 = require("n8n-workflow");
|
|
8
|
+
const axios_1 = __importDefault(require("axios"));
|
|
9
|
+
async function executeAttachmentsOperation(operation, itemIndex, params) {
|
|
10
|
+
var _a, _b, _c, _d, _e;
|
|
11
|
+
const { attachmentId, timeout, apiUrl, apiToken, phoneNumber } = params;
|
|
12
|
+
const axiosConfig = {
|
|
13
|
+
headers: apiToken ? { Authorization: `Bearer ${apiToken}` } : {},
|
|
14
|
+
timeout,
|
|
15
|
+
responseType: operation === 'downloadAttachment' ? 'arraybuffer' : 'json',
|
|
16
|
+
};
|
|
17
|
+
const retryRequest = async (request, retries = 2, delay = 5000) => {
|
|
18
|
+
for (let attempt = 1; attempt <= retries; attempt++) {
|
|
19
|
+
try {
|
|
20
|
+
return await request();
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
if (attempt === retries)
|
|
24
|
+
throw error;
|
|
25
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
try {
|
|
30
|
+
if (operation === 'listAttachments') {
|
|
31
|
+
const response = await retryRequest(() => axios_1.default.get(`${apiUrl}/v1/attachments/${phoneNumber}`, axiosConfig));
|
|
32
|
+
return { json: response.data, pairedItem: { item: itemIndex } };
|
|
33
|
+
}
|
|
34
|
+
else if (operation === 'downloadAttachment') {
|
|
35
|
+
if (!attachmentId) {
|
|
36
|
+
throw new n8n_workflow_1.NodeApiError(this.getNode(), { message: 'Attachment ID is required' });
|
|
37
|
+
}
|
|
38
|
+
const endpoint = `${apiUrl}/v1/attachments/${attachmentId}`;
|
|
39
|
+
this.logger.debug(`Attachments: Downloading from endpoint: ${endpoint}`);
|
|
40
|
+
const response = await retryRequest(() => axios_1.default.get(endpoint, axiosConfig));
|
|
41
|
+
this.logger.debug(`Attachments: Download response size: ${response.data.byteLength}, content-type: ${response.headers['content-type']}`);
|
|
42
|
+
if (!response.data || response.data.byteLength === 0) {
|
|
43
|
+
this.logger.warn(`Attachments: Empty response data for attachment ${attachmentId}`);
|
|
44
|
+
return { json: { status: 'Empty attachment' }, pairedItem: { item: itemIndex } };
|
|
45
|
+
}
|
|
46
|
+
const contentType = response.headers['content-type'] || 'application/octet-stream';
|
|
47
|
+
const contentDisposition = response.headers['content-disposition'] || '';
|
|
48
|
+
const fileName = ((_a = contentDisposition.match(/filename="(.+)"/)) === null || _a === void 0 ? void 0 : _a[1]) || `attachment_${attachmentId}`;
|
|
49
|
+
const fileExtension = fileName.split('.').pop() || '';
|
|
50
|
+
// Конвертуємо ArrayBuffer в Buffer для n8n
|
|
51
|
+
const buffer = Buffer.from(response.data);
|
|
52
|
+
this.logger.debug(`Attachments: Created buffer of size: ${buffer.length}`);
|
|
53
|
+
// Збираємо всі доступні headers
|
|
54
|
+
const allHeaders = response.headers || {};
|
|
55
|
+
// Визначаємо тип файлу
|
|
56
|
+
const isImage = contentType.startsWith('image/');
|
|
57
|
+
const isVideo = contentType.startsWith('video/');
|
|
58
|
+
const isAudio = contentType.startsWith('audio/');
|
|
59
|
+
const isDocument = contentType.includes('pdf') || contentType.includes('document') || contentType.includes('text');
|
|
60
|
+
// Форматуємо розмір файлу
|
|
61
|
+
const formatFileSize = (bytes) => {
|
|
62
|
+
if (bytes === 0)
|
|
63
|
+
return '0 Bytes';
|
|
64
|
+
const k = 1024;
|
|
65
|
+
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
|
66
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
67
|
+
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
|
68
|
+
};
|
|
69
|
+
return {
|
|
70
|
+
json: {
|
|
71
|
+
// Основна інформація про файл
|
|
72
|
+
attachmentId,
|
|
73
|
+
fileName,
|
|
74
|
+
fileExtension,
|
|
75
|
+
mimeType: contentType,
|
|
76
|
+
// Розмір файлу
|
|
77
|
+
sizeBytes: buffer.length,
|
|
78
|
+
sizeFormatted: formatFileSize(buffer.length),
|
|
79
|
+
// Тип файлу
|
|
80
|
+
fileType: {
|
|
81
|
+
isImage,
|
|
82
|
+
isVideo,
|
|
83
|
+
isAudio,
|
|
84
|
+
isDocument,
|
|
85
|
+
category: isImage ? 'Image' : isVideo ? 'Video' : isAudio ? 'Audio' : isDocument ? 'Document' : 'Other'
|
|
86
|
+
},
|
|
87
|
+
// HTTP headers від API
|
|
88
|
+
headers: Object.keys(allHeaders).reduce((acc, key) => {
|
|
89
|
+
const value = allHeaders[key];
|
|
90
|
+
if (value !== null && value !== undefined && value !== '') {
|
|
91
|
+
acc[key] = value;
|
|
92
|
+
}
|
|
93
|
+
return acc;
|
|
94
|
+
}, {}),
|
|
95
|
+
// Додаткова інформація
|
|
96
|
+
downloadInfo: {
|
|
97
|
+
endpoint,
|
|
98
|
+
downloadedAt: new Date().toISOString(),
|
|
99
|
+
contentDisposition: contentDisposition || null,
|
|
100
|
+
hasValidContent: buffer.length > 0,
|
|
101
|
+
isEmpty: buffer.length === 0
|
|
102
|
+
},
|
|
103
|
+
// Статус завантаження
|
|
104
|
+
status: buffer.length > 0 ? 'downloaded_successfully' : 'empty_attachment'
|
|
105
|
+
},
|
|
106
|
+
binary: {
|
|
107
|
+
attachment: {
|
|
108
|
+
data: buffer.toString('base64'),
|
|
109
|
+
mimeType: contentType,
|
|
110
|
+
fileName: fileName,
|
|
111
|
+
fileExtension,
|
|
112
|
+
// Додаткові метадані для binary даних
|
|
113
|
+
fileSize: buffer.length,
|
|
114
|
+
id: attachmentId,
|
|
115
|
+
directory: `/attachments/${phoneNumber}`,
|
|
116
|
+
// Додаткові поля які можуть бути корисними
|
|
117
|
+
encoding: 'base64',
|
|
118
|
+
originalName: fileName,
|
|
119
|
+
downloadedFrom: endpoint,
|
|
120
|
+
downloadedAt: new Date().toISOString(),
|
|
121
|
+
// Якщо є content-disposition, додаємо його
|
|
122
|
+
...(contentDisposition && { contentDisposition }),
|
|
123
|
+
// Категорія файлу для зручності
|
|
124
|
+
category: isImage ? 'image' : isVideo ? 'video' : isAudio ? 'audio' : isDocument ? 'document' : 'other',
|
|
125
|
+
// MD5 хеш для ідентифікації (опціонально)
|
|
126
|
+
...(buffer.length > 0 && {
|
|
127
|
+
checksum: require('crypto').createHash('md5').update(buffer).digest('hex')
|
|
128
|
+
})
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
pairedItem: { item: itemIndex }
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
else if (operation === 'removeAttachment') {
|
|
135
|
+
if (!attachmentId) {
|
|
136
|
+
throw new n8n_workflow_1.NodeApiError(this.getNode(), { message: 'Attachment ID is required' });
|
|
137
|
+
}
|
|
138
|
+
const response = await retryRequest(() => axios_1.default.delete(`${apiUrl}/v1/attachments/${attachmentId}`, axiosConfig));
|
|
139
|
+
return { json: response.data || { status: 'Attachment removed' }, pairedItem: { item: itemIndex } };
|
|
140
|
+
}
|
|
141
|
+
throw new n8n_workflow_1.NodeApiError(this.getNode(), { message: 'Unknown operation' });
|
|
142
|
+
}
|
|
143
|
+
catch (error) {
|
|
144
|
+
const axiosError = error;
|
|
145
|
+
throw new n8n_workflow_1.NodeApiError(this.getNode(), {
|
|
146
|
+
message: axiosError.message,
|
|
147
|
+
description: (((_c = (_b = axiosError.response) === null || _b === void 0 ? void 0 : _b.data) === null || _c === void 0 ? void 0 : _c.error) || axiosError.message),
|
|
148
|
+
httpCode: ((_e = (_d = axiosError.response) === null || _d === void 0 ? void 0 : _d.status) === null || _e === void 0 ? void 0 : _e.toString()) || 'unknown',
|
|
149
|
+
}, { itemIndex });
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
exports.executeAttachmentsOperation = executeAttachmentsOperation;
|
|
@@ -32,7 +32,7 @@ async function executeMessagesOperation(operation, itemIndex, params) {
|
|
|
32
32
|
number: phoneNumber,
|
|
33
33
|
recipients: [recipient],
|
|
34
34
|
}, axiosConfig));
|
|
35
|
-
return { json: response.data, pairedItem: { item: itemIndex } };
|
|
35
|
+
return { json: response.data || { status: 'Message sent' }, pairedItem: { item: itemIndex } };
|
|
36
36
|
}
|
|
37
37
|
else if (operation === 'sendAttachment') {
|
|
38
38
|
const response = await retryRequest(() => axios_1.default.post(`${apiUrl}/v1/send`, {
|
|
@@ -41,7 +41,7 @@ async function executeMessagesOperation(operation, itemIndex, params) {
|
|
|
41
41
|
recipients: [recipient],
|
|
42
42
|
attachments: [attachmentUrl],
|
|
43
43
|
}, axiosConfig));
|
|
44
|
-
return { json: response.data, pairedItem: { item: itemIndex } };
|
|
44
|
+
return { json: response.data || { status: 'Attachment sent' }, pairedItem: { item: itemIndex } };
|
|
45
45
|
}
|
|
46
46
|
else if (operation === 'sendReaction') {
|
|
47
47
|
const response = await retryRequest(() => axios_1.default.post(`${apiUrl}/v1/reactions/${phoneNumber}`, {
|
|
@@ -50,7 +50,7 @@ async function executeMessagesOperation(operation, itemIndex, params) {
|
|
|
50
50
|
target_author: targetAuthor,
|
|
51
51
|
timestamp: targetSentTimestamp,
|
|
52
52
|
}, axiosConfig));
|
|
53
|
-
return { json: response.data, pairedItem: { item: itemIndex } };
|
|
53
|
+
return { json: response.data || { status: 'Reaction sent' }, pairedItem: { item: itemIndex } };
|
|
54
54
|
}
|
|
55
55
|
else if (operation === 'removeReaction') {
|
|
56
56
|
const response = await retryRequest(() => axios_1.default.delete(`${apiUrl}/v1/reactions/${phoneNumber}`, {
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
|
|
2
|
+
interface MessageHistoryParams {
|
|
3
|
+
timeout: number;
|
|
4
|
+
fullHistory: boolean;
|
|
5
|
+
apiUrl: string;
|
|
6
|
+
apiToken: string;
|
|
7
|
+
phoneNumber: string;
|
|
8
|
+
}
|
|
9
|
+
export declare function getMessageHistory(this: IExecuteFunctions, params: MessageHistoryParams): Promise<INodeExecutionData[]>;
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.getMessageHistory = void 0;
|
|
7
|
+
const n8n_workflow_1 = require("n8n-workflow");
|
|
8
|
+
const axios_1 = __importDefault(require("axios"));
|
|
9
|
+
async function getMessageHistory(params) {
|
|
10
|
+
var _a, _b, _c, _d;
|
|
11
|
+
const { timeout, fullHistory, apiUrl, apiToken, phoneNumber } = params;
|
|
12
|
+
const axiosConfig = {
|
|
13
|
+
headers: apiToken ? { Authorization: `Bearer ${apiToken}` } : {},
|
|
14
|
+
timeout,
|
|
15
|
+
};
|
|
16
|
+
const retryRequest = async (request, retries = 2, delay = 5000) => {
|
|
17
|
+
for (let attempt = 1; attempt <= retries; attempt++) {
|
|
18
|
+
try {
|
|
19
|
+
return await request();
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
if (attempt === retries)
|
|
23
|
+
throw error;
|
|
24
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
try {
|
|
29
|
+
const url = fullHistory
|
|
30
|
+
? `${apiUrl}/v1/receive/${phoneNumber}`
|
|
31
|
+
: `${apiUrl}/v1/receive/${phoneNumber}?timeout=${timeout / 1000}`;
|
|
32
|
+
const response = await retryRequest(() => axios_1.default.get(url, axiosConfig));
|
|
33
|
+
const messages = Array.isArray(response.data) ? response.data : [];
|
|
34
|
+
return messages.map((message, index) => {
|
|
35
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
36
|
+
return ({
|
|
37
|
+
json: {
|
|
38
|
+
messageText: ((_b = (_a = message.envelope) === null || _a === void 0 ? void 0 : _a.dataMessage) === null || _b === void 0 ? void 0 : _b.message) || '',
|
|
39
|
+
attachments: ((_d = (_c = message.envelope) === null || _c === void 0 ? void 0 : _c.dataMessage) === null || _d === void 0 ? void 0 : _d.attachments) || [],
|
|
40
|
+
reactions: ((_f = (_e = message.envelope) === null || _e === void 0 ? void 0 : _e.dataMessage) === null || _f === void 0 ? void 0 : _f.reactions) || [],
|
|
41
|
+
sourceNumber: ((_g = message.envelope) === null || _g === void 0 ? void 0 : _g.sourceNumber) || '',
|
|
42
|
+
timestamp: ((_h = message.envelope) === null || _h === void 0 ? void 0 : _h.timestamp) || 0,
|
|
43
|
+
account: message.account || '',
|
|
44
|
+
},
|
|
45
|
+
pairedItem: { item: index },
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
const axiosError = error;
|
|
51
|
+
throw new n8n_workflow_1.NodeApiError(this.getNode(), {
|
|
52
|
+
message: axiosError.message,
|
|
53
|
+
description: (((_b = (_a = axiosError.response) === null || _a === void 0 ? void 0 : _a.data) === null || _b === void 0 ? void 0 : _b.error) || axiosError.message),
|
|
54
|
+
httpCode: ((_d = (_c = axiosError.response) === null || _c === void 0 ? void 0 : _c.status) === null || _d === void 0 ? void 0 : _d.toString()) || 'unknown',
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
exports.getMessageHistory = getMessageHistory;
|