n8n-nodes-linq 0.1.15 → 0.2.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/LICENSE.md +20 -20
- package/README.md +194 -193
- package/dist/credentials/LinqApi.credentials.js +1 -1
- package/dist/nodes/Linq/Linq.node.js +125 -59
- package/index.js +1 -1
- package/package.json +59 -54
package/LICENSE.md
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2025 Alex
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Alex
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
21
|
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,193 +1,194 @@
|
|
|
1
|
-
# n8n-nodes-linq
|
|
2
|
-
|
|
3
|
-
Linq Partner API (v2) community node for n8n. This node lets you manage chats, messages, phone numbers, webhooks, and contacts in Linq from your n8n workflows.
|
|
4
|
-
|
|
5
|
-
- Package: `n8n-nodes-linq`
|
|
6
|
-
- Node icon: included (SVG)
|
|
7
|
-
- API version: v2
|
|
8
|
-
- Auth: Integration Token (`X-LINQ-INTEGRATION-TOKEN`)
|
|
9
|
-
|
|
10
|
-
## Installation (in n8n)
|
|
11
|
-
|
|
12
|
-
- Using the n8n UI:
|
|
13
|
-
1) Settings → Community Nodes → Install
|
|
14
|
-
2) Enter: `n8n-nodes-linq`
|
|
15
|
-
3) Restart n8n if prompted
|
|
16
|
-
|
|
17
|
-
- Headless / environment variable:
|
|
18
|
-
- Add `n8n-nodes-linq` to `N8N_COMMUNITY_PACKAGES` (or install with npm in your instance) and restart n8n.
|
|
19
|
-
|
|
20
|
-
## Credentials
|
|
21
|
-
|
|
22
|
-
Create a new credential of type "Linq API" and set your Integration Token:
|
|
23
|
-
|
|
24
|
-
- Header used: `X-LINQ-INTEGRATION-TOKEN: <your token>`
|
|
25
|
-
- The field is hidden in the UI (password type).
|
|
26
|
-
|
|
27
|
-
Where it's defined:
|
|
28
|
-
- [class LinqApi implements ICredentialType](credentials/LinqApi.credentials.ts:1)
|
|
29
|
-
|
|
30
|
-
## Node usage
|
|
31
|
-
|
|
32
|
-
Add the "Linq" node to your workflow. The node provides Resources and Operations that map 1:1 to the documented Linq Partner API endpoints.
|
|
33
|
-
|
|
34
|
-
- Node implementation:
|
|
35
|
-
- [class Linq implements INodeType](nodes/Linq/Linq.node.ts:1)
|
|
36
|
-
- Icon is configured at the node-level: `icon: 'file:linq.svg'`
|
|
37
|
-
- SVG file: [linq.svg](nodes/Linq/linq.svg:1)
|
|
38
|
-
|
|
39
|
-
### Supported resources and operations
|
|
40
|
-
|
|
41
|
-
The node implements all endpoints from your Linq Partner API documentation (v2):
|
|
42
|
-
|
|
43
|
-
- Resource: Chat
|
|
44
|
-
- Operations:
|
|
45
|
-
- Get Many → GET `/chats` (
|
|
46
|
-
- Get One → GET `/chats/:id`
|
|
47
|
-
- Find → GET `/chats/find` (
|
|
48
|
-
- Create → POST `/chats` (supports group chats via `phone_numbers[]`,
|
|
49
|
-
- Share Contact → POST `/chats/
|
|
50
|
-
|
|
51
|
-
- Resource: Chat Message
|
|
52
|
-
- Operations:
|
|
53
|
-
- Get Many → GET `/chat_messages
|
|
54
|
-
- Get One → GET `/chat_messages/:id`
|
|
55
|
-
- Create → POST `/chat_messages` (`
|
|
56
|
-
- Delete → DELETE `/chat_messages/:id`
|
|
57
|
-
- Edit → POST `/chat_messages/:id/edit` (`text`)
|
|
58
|
-
- React → POST `/chat_messages/:id/
|
|
59
|
-
- Get Reaction → GET `/
|
|
60
|
-
|
|
61
|
-
- Resource: Phone Number
|
|
62
|
-
- Operations:
|
|
63
|
-
- Get Many → GET `/phone_numbers`
|
|
64
|
-
-
|
|
65
|
-
|
|
66
|
-
- Resource: Webhook Subscription
|
|
67
|
-
- Operations:
|
|
68
|
-
- Get Many → GET `/webhook_subscriptions`
|
|
69
|
-
- Get One → GET `/webhook_subscriptions/:id`
|
|
70
|
-
- Create → POST `/webhook_subscriptions`
|
|
71
|
-
- Update → PUT `/webhook_subscriptions/:id`
|
|
72
|
-
- Delete → DELETE `/webhook_subscriptions/:id`
|
|
73
|
-
|
|
74
|
-
- Resource: Contact
|
|
75
|
-
- Operations:
|
|
76
|
-
- Create → POST `/contacts`
|
|
77
|
-
- Get One → GET `/contacts/:id`
|
|
78
|
-
- Update → PUT `/contacts/:id`
|
|
79
|
-
- Delete → DELETE `/contacts/:id`
|
|
80
|
-
|
|
81
|
-
### Linq Trigger Node
|
|
82
|
-
|
|
83
|
-
Add the "Linq Trigger" node to automatically start workflows when Linq events occur. The node automatically registers a webhook with Linq when the workflow is activated.
|
|
84
|
-
|
|
85
|
-
- Supported Events:
|
|
86
|
-
- Message Sent (`message.sent`)
|
|
87
|
-
- Message Received (`message.received`)
|
|
88
|
-
- Message Read (`message.read`)
|
|
89
|
-
- Call Completed (`call.completed`)
|
|
90
|
-
- Contact Created (`contact.created`)
|
|
91
|
-
- Contact Updated (`contact.updated`)
|
|
92
|
-
- Contact Deleted (`contact.deleted`)
|
|
93
|
-
|
|
94
|
-
- Configuration:
|
|
95
|
-
1. Add the "Linq Trigger" node to your workflow
|
|
96
|
-
2. Select which events should trigger the workflow
|
|
97
|
-
3. Activate the workflow (this registers the webhook with Linq)
|
|
98
|
-
4. Linq will send events to your workflow when they occur
|
|
99
|
-
|
|
100
|
-
- Security:
|
|
101
|
-
- The node verifies the signature of incoming events using HMAC-SHA256
|
|
102
|
-
- Requires the same Linq API credentials as the main node
|
|
103
|
-
|
|
104
|
-
### Example workflows
|
|
105
|
-
|
|
106
|
-
1) Send a group message
|
|
107
|
-
- Resource: Chat
|
|
108
|
-
- Operation: Create
|
|
109
|
-
- Fields:
|
|
110
|
-
- Send From (
|
|
111
|
-
- Display Name (optional): `Project A`
|
|
112
|
-
- Phone Numbers: `+13341234567, +13347654321`
|
|
113
|
-
- Message Text: `Hello from n8n!`
|
|
114
|
-
|
|
115
|
-
2)
|
|
116
|
-
- Resource:
|
|
117
|
-
- Operation:
|
|
118
|
-
-
|
|
119
|
-
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
-
|
|
123
|
-
|
|
124
|
-
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
-
|
|
128
|
-
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
-
|
|
137
|
-
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
-
|
|
144
|
-
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
npm
|
|
151
|
-
npm run
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
-
|
|
166
|
-
-
|
|
167
|
-
-
|
|
168
|
-
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
-
|
|
175
|
-
-
|
|
176
|
-
-
|
|
177
|
-
-
|
|
178
|
-
- `
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
npm
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
-
|
|
192
|
-
|
|
193
|
-
|
|
1
|
+
# n8n-nodes-linq
|
|
2
|
+
|
|
3
|
+
Linq Partner API (v2) community node for n8n. This node lets you manage chats, messages (with attachments), phone numbers, webhooks, and contacts in Linq from your n8n workflows.
|
|
4
|
+
|
|
5
|
+
- Package: `n8n-nodes-linq`
|
|
6
|
+
- Node icon: included (SVG)
|
|
7
|
+
- API version: v2
|
|
8
|
+
- Auth: Integration Token (`X-LINQ-INTEGRATION-TOKEN`)
|
|
9
|
+
|
|
10
|
+
## Installation (in n8n)
|
|
11
|
+
|
|
12
|
+
- Using the n8n UI:
|
|
13
|
+
1) Settings → Community Nodes → Install
|
|
14
|
+
2) Enter: `n8n-nodes-linq`
|
|
15
|
+
3) Restart n8n if prompted
|
|
16
|
+
|
|
17
|
+
- Headless / environment variable:
|
|
18
|
+
- Add `n8n-nodes-linq` to `N8N_COMMUNITY_PACKAGES` (or install with npm in your instance) and restart n8n.
|
|
19
|
+
|
|
20
|
+
## Credentials
|
|
21
|
+
|
|
22
|
+
Create a new credential of type "Linq API" and set your Integration Token:
|
|
23
|
+
|
|
24
|
+
- Header used: `X-LINQ-INTEGRATION-TOKEN: <your token>`
|
|
25
|
+
- The field is hidden in the UI (password type).
|
|
26
|
+
|
|
27
|
+
Where it's defined:
|
|
28
|
+
- [class LinqApi implements ICredentialType](credentials/LinqApi.credentials.ts:1)
|
|
29
|
+
|
|
30
|
+
## Node usage
|
|
31
|
+
|
|
32
|
+
Add the "Linq" node to your workflow. The node provides Resources and Operations that map 1:1 to the documented Linq Partner API endpoints.
|
|
33
|
+
|
|
34
|
+
- Node implementation:
|
|
35
|
+
- [class Linq implements INodeType](nodes/Linq/Linq.node.ts:1)
|
|
36
|
+
- Icon is configured at the node-level: `icon: 'file:linq.svg'`
|
|
37
|
+
- SVG file: [linq.svg](nodes/Linq/linq.svg:1)
|
|
38
|
+
|
|
39
|
+
### Supported resources and operations
|
|
40
|
+
|
|
41
|
+
The node implements all endpoints from your Linq Partner API documentation (v2):
|
|
42
|
+
|
|
43
|
+
- Resource: Chat
|
|
44
|
+
- Operations:
|
|
45
|
+
- Get Many → GET `/chats` (requires `phone_number`; pagination supported)
|
|
46
|
+
- Get One → GET `/chats/:id`
|
|
47
|
+
- Find → GET `/chats/find` (requires your `phone_number` and `phone_numbers[]` of participants)
|
|
48
|
+
- Create → POST `/chats` (requires `send_from`, supports group chats via `phone_numbers[]`, optional display name, initial `message.text`)
|
|
49
|
+
- Share Contact → POST `/chats/{chat_id}/share_contact` (chat_id required; feature must be enabled by Linq)
|
|
50
|
+
|
|
51
|
+
- Resource: Chat Message
|
|
52
|
+
- Operations:
|
|
53
|
+
- Get Many → GET `/chats/{chat_id}/chat_messages`
|
|
54
|
+
- Get One → GET `/chat_messages/:id`
|
|
55
|
+
- Create → POST `/chats/{chat_id}/chat_messages` (supports `text`, optional `attachment_urls[]`, optional `idempotency_key`)
|
|
56
|
+
- Delete → DELETE `/chat_messages/:id`
|
|
57
|
+
- Edit → POST `/chat_messages/:id/edit` (`text`)
|
|
58
|
+
- React → POST `/chat_messages/:id/reactions` (`reaction`)
|
|
59
|
+
- Get Reaction → GET `/chat_message_reactions/:reaction_id`
|
|
60
|
+
|
|
61
|
+
- Resource: Phone Number
|
|
62
|
+
- Operations:
|
|
63
|
+
- Get Many → GET `/phone_numbers`
|
|
64
|
+
- Update → PUT `/phone_numbers/:id` (optional `forward_to`, optional `label`)
|
|
65
|
+
|
|
66
|
+
- Resource: Webhook Subscription
|
|
67
|
+
- Operations:
|
|
68
|
+
- Get Many → GET `/webhook_subscriptions`
|
|
69
|
+
- Get One → GET `/webhook_subscriptions/:id`
|
|
70
|
+
- Create → POST `/webhook_subscriptions`
|
|
71
|
+
- Update → PUT `/webhook_subscriptions/:id`
|
|
72
|
+
- Delete → DELETE `/webhook_subscriptions/:id`
|
|
73
|
+
|
|
74
|
+
- Resource: Contact
|
|
75
|
+
- Operations:
|
|
76
|
+
- Create → POST `/contacts`
|
|
77
|
+
- Get One → GET `/contacts/:id`
|
|
78
|
+
- Update → PUT `/contacts/:id`
|
|
79
|
+
- Delete → DELETE `/contacts/:id`
|
|
80
|
+
|
|
81
|
+
### Linq Trigger Node
|
|
82
|
+
|
|
83
|
+
Add the "Linq Trigger" node to automatically start workflows when Linq events occur. The node automatically registers a webhook with Linq when the workflow is activated.
|
|
84
|
+
|
|
85
|
+
- Supported Events:
|
|
86
|
+
- Message Sent (`message.sent`)
|
|
87
|
+
- Message Received (`message.received`)
|
|
88
|
+
- Message Read (`message.read`)
|
|
89
|
+
- Call Completed (`call.completed`)
|
|
90
|
+
- Contact Created (`contact.created`)
|
|
91
|
+
- Contact Updated (`contact.updated`)
|
|
92
|
+
- Contact Deleted (`contact.deleted`)
|
|
93
|
+
|
|
94
|
+
- Configuration:
|
|
95
|
+
1. Add the "Linq Trigger" node to your workflow
|
|
96
|
+
2. Select which events should trigger the workflow
|
|
97
|
+
3. Activate the workflow (this registers the webhook with Linq)
|
|
98
|
+
4. Linq will send events to your workflow when they occur
|
|
99
|
+
|
|
100
|
+
- Security:
|
|
101
|
+
- The node verifies the signature of incoming events using HMAC-SHA256
|
|
102
|
+
- Requires the same Linq API credentials as the main node
|
|
103
|
+
|
|
104
|
+
### Example workflows
|
|
105
|
+
|
|
106
|
+
1) Send a group message
|
|
107
|
+
- Resource: Chat
|
|
108
|
+
- Operation: Create
|
|
109
|
+
- Fields:
|
|
110
|
+
- Send From (required): `+13175551234`
|
|
111
|
+
- Display Name (optional): `Project A`
|
|
112
|
+
- Phone Numbers: `+13341234567, +13347654321`
|
|
113
|
+
- Message Text: `Hello from n8n!`
|
|
114
|
+
|
|
115
|
+
2) Create a contact
|
|
116
|
+
- Resource: Contact
|
|
117
|
+
- Operation: Create
|
|
118
|
+
- Fields:
|
|
119
|
+
- First Name: `John`
|
|
120
|
+
- Last Name: `Doe`
|
|
121
|
+
- Email: `john@example.com`
|
|
122
|
+
- Phone Number: `+15551234567`
|
|
123
|
+
3) Manage webhook subscriptions
|
|
124
|
+
- Resource: Webhook Subscription
|
|
125
|
+
- Operation: Create
|
|
126
|
+
- Fields:
|
|
127
|
+
- Webhook URL: `https://example.com/webhooks/linq`
|
|
128
|
+
- Events: `message.sent, message.received, contact.created`
|
|
129
|
+
- Version: `2`
|
|
130
|
+
- Active: `true`
|
|
131
|
+
|
|
132
|
+
4) Update phone number forwarding
|
|
133
|
+
- Resource: Phone Number
|
|
134
|
+
- Operation: Update
|
|
135
|
+
- Fields:
|
|
136
|
+
- Phone Number ID: `<id>`
|
|
137
|
+
- Forward To (optional): `+15551230000`
|
|
138
|
+
- Label (optional): `Support Line`
|
|
139
|
+
|
|
140
|
+
## Development
|
|
141
|
+
|
|
142
|
+
Requirements:
|
|
143
|
+
- Node.js ≥ 20
|
|
144
|
+
- npm or pnpm (npm commands shown below)
|
|
145
|
+
- n8n local instance if testing end-to-end
|
|
146
|
+
|
|
147
|
+
Install and build:
|
|
148
|
+
```bash
|
|
149
|
+
cd n8n-nodes-linq
|
|
150
|
+
npm install
|
|
151
|
+
npm run build
|
|
152
|
+
npm run lint
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Local link for testing in a local n8n:
|
|
156
|
+
```bash
|
|
157
|
+
# in this folder
|
|
158
|
+
npm link
|
|
159
|
+
# in your n8n folder
|
|
160
|
+
npm link n8n-nodes-linq
|
|
161
|
+
# restart n8n, then add "Linq" node
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Project files of interest:
|
|
165
|
+
- Node: [Linq.node.ts](nodes/Linq/Linq.node.ts:1)
|
|
166
|
+
- Credentials: [LinqApi.credentials.ts](credentials/LinqApi.credentials.ts:1)
|
|
167
|
+
- Gulp (copies icons): [gulpfile.js](gulpfile.js:1)
|
|
168
|
+
- TypeScript config: [tsconfig.json](tsconfig.json:1)
|
|
169
|
+
- Index shim: [index.js](index.js:1)
|
|
170
|
+
|
|
171
|
+
## Publishing to npm
|
|
172
|
+
|
|
173
|
+
1) Ensure metadata is correct in [package.json](package.json:1)
|
|
174
|
+
- name: `n8n-nodes-linq`
|
|
175
|
+
- version: increment for each release, e.g. `0.1.0`
|
|
176
|
+
- author: `"alexautomates"`
|
|
177
|
+
- keywords include: `n8n-community-node-package`
|
|
178
|
+
- `files: ["dist"]` to publish only built files
|
|
179
|
+
- `n8n` block lists built nodes and credentials in `dist/`
|
|
180
|
+
|
|
181
|
+
2) Build and publish:
|
|
182
|
+
```bash
|
|
183
|
+
npm run build
|
|
184
|
+
npm publish --access public
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
3) Users can install from the n8n UI (Community Nodes) by typing `n8n-nodes-linq`.
|
|
188
|
+
|
|
189
|
+
## Icon / Branding
|
|
190
|
+
|
|
191
|
+
- Icon file is included at: [nodes/Linq/linq.svg](nodes/Linq/linq.svg:1)
|
|
192
|
+
- Node description references it as `icon: 'file:linq.svg'`, so it renders in the n8n UI.
|
|
193
|
+
|
|
194
|
+
## License
|
|
@@ -5,7 +5,7 @@ class LinqApi {
|
|
|
5
5
|
constructor() {
|
|
6
6
|
this.name = 'linqApi';
|
|
7
7
|
this.displayName = 'Linq API';
|
|
8
|
-
this.documentationUrl = 'https://
|
|
8
|
+
this.documentationUrl = 'https://apidocs.linqapp.com/reference/';
|
|
9
9
|
this.properties = [
|
|
10
10
|
{
|
|
11
11
|
displayName: 'Integration Token',
|
|
@@ -2,24 +2,10 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.Linq = void 0;
|
|
4
4
|
const n8n_workflow_1 = require("n8n-workflow");
|
|
5
|
-
// Helper function to
|
|
5
|
+
// Helper function to minimally normalize phone numbers: remove spaces only (user supplies E.164)
|
|
6
6
|
function formatPhoneNumber(phoneNumber) {
|
|
7
|
-
//
|
|
8
|
-
|
|
9
|
-
// If it already starts with +, return as is
|
|
10
|
-
if (cleaned.startsWith('+')) {
|
|
11
|
-
return cleaned;
|
|
12
|
-
}
|
|
13
|
-
// If it starts with 00, replace with +
|
|
14
|
-
if (cleaned.startsWith('00')) {
|
|
15
|
-
return '+' + cleaned.substring(2);
|
|
16
|
-
}
|
|
17
|
-
// If it's all digits and doesn't start with +, assume it needs a +
|
|
18
|
-
if (/^\d+$/.test(cleaned)) {
|
|
19
|
-
return '+' + cleaned;
|
|
20
|
-
}
|
|
21
|
-
// For other cases, just return the cleaned number
|
|
22
|
-
return cleaned;
|
|
7
|
+
// Only remove space characters; do not modify digits/symbols or add prefixes
|
|
8
|
+
return phoneNumber.trim().replace(/ /g, '');
|
|
23
9
|
}
|
|
24
10
|
class Linq {
|
|
25
11
|
constructor() {
|
|
@@ -143,16 +129,16 @@ class Linq {
|
|
|
143
129
|
noDataExpression: true,
|
|
144
130
|
displayOptions: { show: { resource: ['phoneNumber'] } },
|
|
145
131
|
options: [
|
|
146
|
-
{
|
|
147
|
-
name: 'Check iMessage Availability',
|
|
148
|
-
value: 'checkIMessageAvailability',
|
|
149
|
-
action: 'Check imessage availability'
|
|
150
|
-
},
|
|
151
132
|
{
|
|
152
133
|
name: 'Get Many',
|
|
153
134
|
value: 'getAll',
|
|
154
135
|
action: 'Get many phone numbers'
|
|
155
136
|
},
|
|
137
|
+
{
|
|
138
|
+
name: 'Update',
|
|
139
|
+
value: 'update',
|
|
140
|
+
action: 'Update a phone number'
|
|
141
|
+
},
|
|
156
142
|
],
|
|
157
143
|
default: 'getAll',
|
|
158
144
|
},
|
|
@@ -228,7 +214,7 @@ class Linq {
|
|
|
228
214
|
displayName: 'Chat ID',
|
|
229
215
|
name: 'chatId',
|
|
230
216
|
type: 'string',
|
|
231
|
-
displayOptions: { show: { resource: ['chat'], operation: ['getOne'] } },
|
|
217
|
+
displayOptions: { show: { resource: ['chat'], operation: ['getOne', 'shareContact'] } },
|
|
232
218
|
default: '',
|
|
233
219
|
description: 'The ID of the chat to retrieve',
|
|
234
220
|
},
|
|
@@ -238,7 +224,15 @@ class Linq {
|
|
|
238
224
|
type: 'string',
|
|
239
225
|
displayOptions: { show: { resource: ['chat'], operation: ['getAll'] } },
|
|
240
226
|
default: '',
|
|
241
|
-
description: '
|
|
227
|
+
description: 'Required: your Linq phone number (phone_number)',
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
displayName: 'From Phone Number',
|
|
231
|
+
name: 'fromPhoneNumber',
|
|
232
|
+
type: 'string',
|
|
233
|
+
displayOptions: { show: { resource: ['chat'], operation: ['find'] } },
|
|
234
|
+
default: '',
|
|
235
|
+
description: 'Required: your Linq phone number (phone_number)',
|
|
242
236
|
},
|
|
243
237
|
{
|
|
244
238
|
displayName: 'Phone Numbers',
|
|
@@ -302,7 +296,7 @@ class Linq {
|
|
|
302
296
|
displayName: 'Chat Message ID',
|
|
303
297
|
name: 'chatMessageId',
|
|
304
298
|
type: 'string',
|
|
305
|
-
displayOptions: { show: { resource: ['chatMessage'], operation: ['getOne', 'delete', 'edit', 'react'
|
|
299
|
+
displayOptions: { show: { resource: ['chatMessage'], operation: ['getOne', 'delete', 'edit', 'react'] } },
|
|
306
300
|
default: '',
|
|
307
301
|
description: 'The ID of the chat message',
|
|
308
302
|
},
|
|
@@ -322,6 +316,22 @@ class Linq {
|
|
|
322
316
|
default: '',
|
|
323
317
|
description: 'The text of the message',
|
|
324
318
|
},
|
|
319
|
+
{
|
|
320
|
+
displayName: 'Attachment URLs',
|
|
321
|
+
name: 'attachmentUrls',
|
|
322
|
+
type: 'string',
|
|
323
|
+
displayOptions: { show: { resource: ['chatMessage'], operation: ['create'] } },
|
|
324
|
+
default: '',
|
|
325
|
+
description: 'Comma-separated list of attachment URLs (attachment_urls[])',
|
|
326
|
+
},
|
|
327
|
+
{
|
|
328
|
+
displayName: 'Idempotency Key',
|
|
329
|
+
name: 'idempotencyKey',
|
|
330
|
+
type: 'string',
|
|
331
|
+
displayOptions: { show: { resource: ['chatMessage'], operation: ['create'] } },
|
|
332
|
+
default: '',
|
|
333
|
+
description: 'Optional idempotency key for message creation',
|
|
334
|
+
},
|
|
325
335
|
{
|
|
326
336
|
displayName: 'Reaction',
|
|
327
337
|
name: 'reaction',
|
|
@@ -330,14 +340,38 @@ class Linq {
|
|
|
330
340
|
default: '',
|
|
331
341
|
description: 'The reaction to add',
|
|
332
342
|
},
|
|
343
|
+
{
|
|
344
|
+
displayName: 'Reaction ID',
|
|
345
|
+
name: 'reactionId',
|
|
346
|
+
type: 'string',
|
|
347
|
+
displayOptions: { show: { resource: ['chatMessage'], operation: ['getReaction'] } },
|
|
348
|
+
default: '',
|
|
349
|
+
description: 'The reaction ID to retrieve',
|
|
350
|
+
},
|
|
333
351
|
// Phone Number parameters
|
|
334
352
|
{
|
|
335
|
-
displayName: 'Phone Number',
|
|
336
|
-
name: '
|
|
353
|
+
displayName: 'Phone Number ID',
|
|
354
|
+
name: 'phoneNumberId',
|
|
355
|
+
type: 'string',
|
|
356
|
+
displayOptions: { show: { resource: ['phoneNumber'], operation: ['update'] } },
|
|
357
|
+
default: '',
|
|
358
|
+
description: 'The ID of the phone number to update',
|
|
359
|
+
},
|
|
360
|
+
{
|
|
361
|
+
displayName: 'Forward To',
|
|
362
|
+
name: 'forwardTo',
|
|
337
363
|
type: 'string',
|
|
338
|
-
displayOptions: { show: { resource: ['phoneNumber'], operation: ['
|
|
364
|
+
displayOptions: { show: { resource: ['phoneNumber'], operation: ['update'] } },
|
|
339
365
|
default: '',
|
|
340
|
-
description: '
|
|
366
|
+
description: 'Optional forwarding destination phone number',
|
|
367
|
+
},
|
|
368
|
+
{
|
|
369
|
+
displayName: 'Label',
|
|
370
|
+
name: 'label',
|
|
371
|
+
type: 'string',
|
|
372
|
+
displayOptions: { show: { resource: ['phoneNumber'], operation: ['update'] } },
|
|
373
|
+
default: '',
|
|
374
|
+
description: 'Optional label for the phone number',
|
|
341
375
|
},
|
|
342
376
|
// Webhook Subscription parameters
|
|
343
377
|
{
|
|
@@ -465,13 +499,16 @@ class Linq {
|
|
|
465
499
|
const phoneNumber = this.getNodeParameter('phoneNumber', i, '');
|
|
466
500
|
const page = this.getNodeParameter('page', i);
|
|
467
501
|
const perPage = this.getNodeParameter('perPage', i);
|
|
502
|
+
if (!phoneNumber) {
|
|
503
|
+
throw new n8n_workflow_1.ApplicationError('phone_number is required by Linq API');
|
|
504
|
+
}
|
|
468
505
|
const qs = {};
|
|
506
|
+
qs.phone_number = formatPhoneNumber(phoneNumber);
|
|
469
507
|
if (page && page !== 1)
|
|
470
508
|
qs.page = page;
|
|
471
509
|
if (perPage && perPage !== 25)
|
|
472
510
|
qs.per_page = perPage;
|
|
473
|
-
|
|
474
|
-
qs.phone_number = formatPhoneNumber(phoneNumber);
|
|
511
|
+
qs.phone_number = formatPhoneNumber(phoneNumber);
|
|
475
512
|
responseData = await this.helpers.request({
|
|
476
513
|
method: 'GET',
|
|
477
514
|
url: 'https://api.linqapp.com/api/partner/v2/chats',
|
|
@@ -496,19 +533,16 @@ class Linq {
|
|
|
496
533
|
});
|
|
497
534
|
}
|
|
498
535
|
if (operation === 'find') {
|
|
536
|
+
const fromPhoneNumber = this.getNodeParameter('fromPhoneNumber', i, '');
|
|
499
537
|
const phoneNumbers = this.getNodeParameter('phoneNumbers', i, '');
|
|
538
|
+
if (!fromPhoneNumber) {
|
|
539
|
+
throw new n8n_workflow_1.ApplicationError('phone_number is required when finding chats');
|
|
540
|
+
}
|
|
500
541
|
const qs = {};
|
|
501
542
|
if (phoneNumbers) {
|
|
502
543
|
// Check if we have a single phone number or multiple
|
|
503
544
|
const phoneNumbersArray = phoneNumbers.split(',').map(p => formatPhoneNumber(p.trim()));
|
|
504
|
-
|
|
505
|
-
// For single phone number, use 'phone' parameter
|
|
506
|
-
qs.phone = phoneNumbersArray[0];
|
|
507
|
-
}
|
|
508
|
-
else {
|
|
509
|
-
// For multiple phone numbers, use 'phone_numbers[]' parameter
|
|
510
|
-
qs['phone_numbers[]'] = phoneNumbersArray;
|
|
511
|
-
}
|
|
545
|
+
qs['phone_numbers[]'] = phoneNumbersArray;
|
|
512
546
|
}
|
|
513
547
|
responseData = await this.helpers.request({
|
|
514
548
|
method: 'GET',
|
|
@@ -526,6 +560,9 @@ class Linq {
|
|
|
526
560
|
const displayName = this.getNodeParameter('displayName', i);
|
|
527
561
|
const phoneNumbers = this.getNodeParameter('phoneNumbers', i);
|
|
528
562
|
const messageText = this.getNodeParameter('messageText', i);
|
|
563
|
+
if (!sendFrom) {
|
|
564
|
+
throw new n8n_workflow_1.ApplicationError('send_from is required by Linq when creating chats');
|
|
565
|
+
}
|
|
529
566
|
const body = {
|
|
530
567
|
chat: {
|
|
531
568
|
phone_numbers: phoneNumbers.split(',').map(p => formatPhoneNumber(p.trim()))
|
|
@@ -534,9 +571,7 @@ class Linq {
|
|
|
534
571
|
text: messageText
|
|
535
572
|
}
|
|
536
573
|
};
|
|
537
|
-
|
|
538
|
-
body.send_from = formatPhoneNumber(sendFrom);
|
|
539
|
-
}
|
|
574
|
+
body.send_from = formatPhoneNumber(sendFrom);
|
|
540
575
|
if (displayName) {
|
|
541
576
|
body.chat.display_name = displayName;
|
|
542
577
|
}
|
|
@@ -546,16 +581,20 @@ class Linq {
|
|
|
546
581
|
headers: {
|
|
547
582
|
'X-LINQ-INTEGRATION-TOKEN': credentials.integrationToken,
|
|
548
583
|
'Content-Type': 'application/json',
|
|
549
|
-
'Accept': 'application/
|
|
584
|
+
'Accept': 'application/json'
|
|
550
585
|
},
|
|
551
586
|
body: body,
|
|
552
587
|
json: true,
|
|
553
588
|
});
|
|
554
589
|
}
|
|
555
590
|
if (operation === 'shareContact') {
|
|
591
|
+
const chatId = this.getNodeParameter('chatId', i);
|
|
592
|
+
if (!chatId) {
|
|
593
|
+
throw new n8n_workflow_1.ApplicationError('chatId is required to share contact');
|
|
594
|
+
}
|
|
556
595
|
responseData = await this.helpers.request({
|
|
557
596
|
method: 'POST',
|
|
558
|
-
url:
|
|
597
|
+
url: `https://api.linqapp.com/api/partner/v2/chats/${chatId}/share_contact`,
|
|
559
598
|
headers: {
|
|
560
599
|
'X-LINQ-INTEGRATION-TOKEN': credentials.integrationToken,
|
|
561
600
|
'Content-Type': 'application/json',
|
|
@@ -569,10 +608,12 @@ class Linq {
|
|
|
569
608
|
if (resource === 'chatMessage') {
|
|
570
609
|
if (operation === 'getAll') {
|
|
571
610
|
const chatId = this.getNodeParameter('chatId', i);
|
|
611
|
+
if (!chatId) {
|
|
612
|
+
throw new n8n_workflow_1.ApplicationError('chatId is required to list chat messages');
|
|
613
|
+
}
|
|
572
614
|
responseData = await this.helpers.request({
|
|
573
615
|
method: 'GET',
|
|
574
|
-
url: `https://api.linqapp.com/api/partner/v2/chat_messages`,
|
|
575
|
-
qs: { chat_id: chatId },
|
|
616
|
+
url: `https://api.linqapp.com/api/partner/v2/chats/${chatId}/chat_messages`,
|
|
576
617
|
headers: {
|
|
577
618
|
'X-LINQ-INTEGRATION-TOKEN': credentials.integrationToken,
|
|
578
619
|
'Accept': 'application/json'
|
|
@@ -595,13 +636,26 @@ class Linq {
|
|
|
595
636
|
if (operation === 'create') {
|
|
596
637
|
const chatId = this.getNodeParameter('chatId', i);
|
|
597
638
|
const messageText = this.getNodeParameter('messageText', i);
|
|
639
|
+
const attachmentUrls = this.getNodeParameter('attachmentUrls', i, '');
|
|
640
|
+
const idempotencyKey = this.getNodeParameter('idempotencyKey', i, '');
|
|
641
|
+
if (!chatId) {
|
|
642
|
+
throw new n8n_workflow_1.ApplicationError('chatId is required to create a chat message');
|
|
643
|
+
}
|
|
598
644
|
const body = {
|
|
599
|
-
chat_id: chatId,
|
|
600
645
|
text: messageText
|
|
601
646
|
};
|
|
647
|
+
if (attachmentUrls) {
|
|
648
|
+
const parsed = attachmentUrls.split(',').map(u => u.trim()).filter(Boolean);
|
|
649
|
+
if (parsed.length) {
|
|
650
|
+
body.attachment_urls = parsed;
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
if (idempotencyKey) {
|
|
654
|
+
body.idempotency_key = idempotencyKey;
|
|
655
|
+
}
|
|
602
656
|
responseData = await this.helpers.request({
|
|
603
657
|
method: 'POST',
|
|
604
|
-
url:
|
|
658
|
+
url: `https://api.linqapp.com/api/partner/v2/chats/${chatId}/chat_messages`,
|
|
605
659
|
headers: {
|
|
606
660
|
'X-LINQ-INTEGRATION-TOKEN': credentials.integrationToken,
|
|
607
661
|
'Content-Type': 'application/json',
|
|
@@ -649,7 +703,7 @@ class Linq {
|
|
|
649
703
|
};
|
|
650
704
|
responseData = await this.helpers.request({
|
|
651
705
|
method: 'POST',
|
|
652
|
-
url: `https://api.linqapp.com/api/partner/v2/chat_messages/${chatMessageId}/
|
|
706
|
+
url: `https://api.linqapp.com/api/partner/v2/chat_messages/${chatMessageId}/reactions`,
|
|
653
707
|
headers: {
|
|
654
708
|
'X-LINQ-INTEGRATION-TOKEN': credentials.integrationToken,
|
|
655
709
|
'Content-Type': 'application/json',
|
|
@@ -660,10 +714,13 @@ class Linq {
|
|
|
660
714
|
});
|
|
661
715
|
}
|
|
662
716
|
if (operation === 'getReaction') {
|
|
663
|
-
const
|
|
717
|
+
const reactionId = this.getNodeParameter('reactionId', i);
|
|
718
|
+
if (!reactionId) {
|
|
719
|
+
throw new n8n_workflow_1.ApplicationError('reactionId is required to get reaction');
|
|
720
|
+
}
|
|
664
721
|
responseData = await this.helpers.request({
|
|
665
722
|
method: 'GET',
|
|
666
|
-
url: `https://api.linqapp.com/api/partner/v2/
|
|
723
|
+
url: `https://api.linqapp.com/api/partner/v2/chat_message_reactions/${reactionId}`,
|
|
667
724
|
headers: {
|
|
668
725
|
'X-LINQ-INTEGRATION-TOKEN': credentials.integrationToken,
|
|
669
726
|
'Accept': 'application/json'
|
|
@@ -685,20 +742,29 @@ class Linq {
|
|
|
685
742
|
json: true,
|
|
686
743
|
});
|
|
687
744
|
}
|
|
688
|
-
if (operation === '
|
|
689
|
-
const
|
|
690
|
-
const
|
|
691
|
-
|
|
692
|
-
|
|
745
|
+
if (operation === 'update') {
|
|
746
|
+
const phoneNumberId = this.getNodeParameter('phoneNumberId', i);
|
|
747
|
+
const forwardTo = this.getNodeParameter('forwardTo', i, '');
|
|
748
|
+
const label = this.getNodeParameter('label', i, '');
|
|
749
|
+
if (!phoneNumberId) {
|
|
750
|
+
throw new n8n_workflow_1.ApplicationError('phoneNumberId is required to update phone number');
|
|
751
|
+
}
|
|
752
|
+
const body = { phone_number: {} };
|
|
753
|
+
if (forwardTo) {
|
|
754
|
+
body.phone_number.forward_to = formatPhoneNumber(forwardTo);
|
|
755
|
+
}
|
|
756
|
+
if (label) {
|
|
757
|
+
body.phone_number.label = label;
|
|
758
|
+
}
|
|
693
759
|
responseData = await this.helpers.request({
|
|
694
|
-
method: '
|
|
695
|
-
url:
|
|
760
|
+
method: 'PUT',
|
|
761
|
+
url: `https://api.linqapp.com/api/partner/v2/phone_numbers/${phoneNumberId}`,
|
|
696
762
|
headers: {
|
|
697
763
|
'X-LINQ-INTEGRATION-TOKEN': credentials.integrationToken,
|
|
698
764
|
'Content-Type': 'application/json',
|
|
699
765
|
'Accept': 'application/json'
|
|
700
766
|
},
|
|
701
|
-
body
|
|
767
|
+
body,
|
|
702
768
|
json: true,
|
|
703
769
|
});
|
|
704
770
|
}
|
package/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
// This file is intentionally left blank.
|
|
1
|
+
// This file is intentionally left blank.
|
|
2
2
|
// It is required by n8n for community nodes.
|
package/package.json
CHANGED
|
@@ -1,54 +1,59 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "n8n-nodes-linq",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Linq API integration for n8n",
|
|
5
|
-
"keywords": [
|
|
6
|
-
"n8n-community-node-package",
|
|
7
|
-
"n8n",
|
|
8
|
-
"linq",
|
|
9
|
-
"automation"
|
|
10
|
-
],
|
|
11
|
-
"license": "MIT",
|
|
12
|
-
"homepage": "https://www.npmjs.com/package/n8n-nodes-linq",
|
|
13
|
-
"author": {
|
|
14
|
-
"name": "alexautomates"
|
|
15
|
-
},
|
|
16
|
-
"repository": {
|
|
17
|
-
"type": "git",
|
|
18
|
-
"url": "https://github.com/
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
"
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
"
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
"
|
|
39
|
-
|
|
40
|
-
"dist/
|
|
41
|
-
]
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
"
|
|
49
|
-
"typescript": "
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
"
|
|
53
|
-
|
|
54
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "n8n-nodes-linq",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Linq API integration for n8n",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"n8n-community-node-package",
|
|
7
|
+
"n8n",
|
|
8
|
+
"linq",
|
|
9
|
+
"automation"
|
|
10
|
+
],
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"homepage": "https://www.npmjs.com/package/n8n-nodes-linq",
|
|
13
|
+
"author": {
|
|
14
|
+
"name": "alexautomates"
|
|
15
|
+
},
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "git+https://github.com/AutomatorAlex/linq-n8n.git",
|
|
19
|
+
"directory": "n8n-nodes-linq"
|
|
20
|
+
},
|
|
21
|
+
"bugs": {
|
|
22
|
+
"url": "https://github.com/AutomatorAlex/linq-n8n/issues"
|
|
23
|
+
},
|
|
24
|
+
"engines": {
|
|
25
|
+
"node": ">=20.15"
|
|
26
|
+
},
|
|
27
|
+
"main": "index.js",
|
|
28
|
+
"scripts": {
|
|
29
|
+
"build": "npx rimraf dist && tsc && gulp build:icons",
|
|
30
|
+
"lint": "eslint nodes credentials",
|
|
31
|
+
"lintfix": "eslint nodes credentials --fix",
|
|
32
|
+
"prepublishOnly": "npm run build && npm run lint -c .eslintrc.prepublish.js nodes credentials"
|
|
33
|
+
},
|
|
34
|
+
"files": [
|
|
35
|
+
"dist"
|
|
36
|
+
],
|
|
37
|
+
"n8n": {
|
|
38
|
+
"n8nNodesApiVersion": 1,
|
|
39
|
+
"credentials": [
|
|
40
|
+
"dist/credentials/LinqApi.credentials.js"
|
|
41
|
+
],
|
|
42
|
+
"nodes": [
|
|
43
|
+
"dist/nodes/Linq/Linq.node.js",
|
|
44
|
+
"dist/nodes/LinqTrigger/LinqTrigger.node.js"
|
|
45
|
+
]
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@types/node": "^24.3.3",
|
|
49
|
+
"@typescript-eslint/parser": "~8.32.0",
|
|
50
|
+
"eslint": "^8.57.0",
|
|
51
|
+
"eslint-plugin-n8n-nodes-base": "^1.16.3",
|
|
52
|
+
"gulp": "^5.0.0",
|
|
53
|
+
"prettier": "^3.5.3",
|
|
54
|
+
"typescript": "^5.8.2"
|
|
55
|
+
},
|
|
56
|
+
"peerDependencies": {
|
|
57
|
+
"n8n-workflow": "*"
|
|
58
|
+
}
|
|
59
|
+
}
|