@shoutkol/n8n-nodes-pumble 0.1.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/README.md ADDED
@@ -0,0 +1,72 @@
1
+ # n8n-nodes-pumble
2
+
3
+ A community node for [n8n](https://n8n.io/) that integrates with the [Pumble API](https://pumble-api-keys.addons.marketplace.cake.com/api-docs/).
4
+
5
+ ## Resources & Operations
6
+
7
+ | Resource | Operations |
8
+ |---|---|
9
+ | **Channel** | List, Get, Create, Add Users, Remove User |
10
+ | **Message** | Send, Reply, Fetch, List, Edit, Delete, Search, DM User, DM Group, Fetch Thread Replies |
11
+ | **Reaction** | Add, Remove |
12
+ | **Scheduled Message** | Create, List, Fetch, Edit, Delete |
13
+ | **User** | Get My Info, List Users, List User Groups, Update Custom Status |
14
+
15
+ ## Installation
16
+
17
+ ### In n8n
18
+
19
+ 1. Go to **Settings > Community Nodes**
20
+ 2. Click **Install**
21
+ 3. Enter `n8n-nodes-pumble`
22
+
23
+ ### Manual
24
+
25
+ ```bash
26
+ cd ~/.n8n/custom
27
+ npm install n8n-nodes-pumble
28
+ ```
29
+
30
+ ## Authentication
31
+
32
+ This node uses **API Key** authentication.
33
+
34
+ 1. Install the **API addon** in your Pumble workspace (+ Add Apps → API)
35
+ 2. Generate a key: type `/api-keys generate` in any Pumble channel
36
+ 3. Copy the ephemeral key that appears
37
+ 4. In n8n, create a new **Pumble API** credential and paste your key
38
+
39
+ ## Project Structure
40
+
41
+ ```
42
+ n8n-nodes-pumble/
43
+ ├── credentials/
44
+ │ └── PumbleApi.credentials.ts # API key credential definition
45
+ ├── nodes/
46
+ │ └── Pumble/
47
+ │ ├── Pumble.node.ts # Main node execute logic
48
+ │ ├── descriptions.ts # All resource/operation/field definitions
49
+ │ └── pumble.svg # Node icon
50
+ ├── package.json
51
+ └── tsconfig.json
52
+ ```
53
+
54
+ ## Development
55
+
56
+ ```bash
57
+ # Install dependencies
58
+ npm install
59
+
60
+ # Build (TypeScript → dist/)
61
+ npm run build
62
+
63
+ # Watch mode
64
+ npm run dev
65
+ ```
66
+
67
+ ## Notes
68
+
69
+ - The Pumble API base URL is `https://pumble-api-keys.addons.marketplace.cake.com`
70
+ - Authentication uses the `ApiKey` request header
71
+ - For channel operations on private channels, the Addon Bot must be a member
72
+ - Bots cannot post to private channels via `sendMessage` unless they are a member
@@ -0,0 +1,9 @@
1
+ import { IAuthenticateGeneric, ICredentialTestRequest, ICredentialType, INodeProperties } from 'n8n-workflow';
2
+ export declare class PumbleApi implements ICredentialType {
3
+ name: string;
4
+ displayName: string;
5
+ documentationUrl: string;
6
+ properties: INodeProperties[];
7
+ authenticate: IAuthenticateGeneric;
8
+ test: ICredentialTestRequest;
9
+ }
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PumbleApi = void 0;
4
+ class PumbleApi {
5
+ constructor() {
6
+ this.name = 'pumbleApi';
7
+ this.displayName = 'Pumble API';
8
+ this.documentationUrl = 'https://pumble-api-keys.addons.marketplace.cake.com/api-docs/';
9
+ this.properties = [
10
+ {
11
+ displayName: 'API Key',
12
+ name: 'apiKey',
13
+ type: 'string',
14
+ typeOptions: { password: true },
15
+ default: '',
16
+ required: true,
17
+ description: 'Your Pumble API key. Generate one via the /api-keys generate command in Pumble.',
18
+ },
19
+ ];
20
+ this.authenticate = {
21
+ type: 'generic',
22
+ properties: {
23
+ headers: {
24
+ ApiKey: '={{$credentials.apiKey}}',
25
+ },
26
+ },
27
+ };
28
+ this.test = {
29
+ request: {
30
+ baseURL: 'https://pumble-api-keys.addons.marketplace.cake.com',
31
+ url: '/myInfo',
32
+ },
33
+ };
34
+ }
35
+ }
36
+ exports.PumbleApi = PumbleApi;
@@ -0,0 +1,5 @@
1
+ import { IExecuteFunctions, INodeExecutionData, INodeType, INodeTypeDescription } from 'n8n-workflow';
2
+ export declare class Pumble implements INodeType {
3
+ description: INodeTypeDescription;
4
+ execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]>;
5
+ }
@@ -0,0 +1,337 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Pumble = void 0;
4
+ const n8n_workflow_1 = require("n8n-workflow");
5
+ const descriptions_1 = require("./descriptions");
6
+ const BASE_URL = 'https://pumble-api-keys.addons.marketplace.cake.com';
7
+ class Pumble {
8
+ constructor() {
9
+ this.description = {
10
+ displayName: 'Pumble',
11
+ name: 'pumble',
12
+ icon: 'file:pumble.svg',
13
+ group: ['transform'],
14
+ version: 1,
15
+ subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
16
+ description: 'Interact with the Pumble API',
17
+ defaults: {
18
+ name: 'Pumble',
19
+ },
20
+ inputs: ['main'],
21
+ outputs: ['main'],
22
+ credentials: [
23
+ {
24
+ name: 'pumbleApi',
25
+ required: true,
26
+ },
27
+ ],
28
+ properties: [
29
+ // ── Resource selector ──
30
+ {
31
+ displayName: 'Resource',
32
+ name: 'resource',
33
+ type: 'options',
34
+ noDataExpression: true,
35
+ options: descriptions_1.resourceOptions,
36
+ default: 'message',
37
+ },
38
+ // ── Operations & fields per resource ──
39
+ ...descriptions_1.channelOperations,
40
+ ...descriptions_1.channelFields,
41
+ ...descriptions_1.messageOperations,
42
+ ...descriptions_1.messageFields,
43
+ ...descriptions_1.reactionOperations,
44
+ ...descriptions_1.reactionFields,
45
+ ...descriptions_1.scheduledMessageOperations,
46
+ ...descriptions_1.scheduledMessageFields,
47
+ ...descriptions_1.userOperations,
48
+ ...descriptions_1.userFields,
49
+ ],
50
+ };
51
+ }
52
+ // ─── Execute ───────────────────────────────────────────────────────────────
53
+ async execute() {
54
+ var _a;
55
+ const items = this.getInputData();
56
+ const returnData = [];
57
+ for (let i = 0; i < items.length; i++) {
58
+ const resource = this.getNodeParameter('resource', i);
59
+ const operation = this.getNodeParameter('operation', i);
60
+ let responseData;
61
+ try {
62
+ // ═══════════════════════════════════════════════════════════════
63
+ // CHANNEL
64
+ // ═══════════════════════════════════════════════════════════════
65
+ if (resource === 'channel') {
66
+ if (operation === 'list') {
67
+ responseData = await pumbleRequest.call(this, 'GET', '/listChannels');
68
+ }
69
+ else if (operation === 'get') {
70
+ const getBy = this.getNodeParameter('getBy', i);
71
+ const qs = {};
72
+ if (getBy === 'channelId') {
73
+ qs.channelId = this.getNodeParameter('channelId', i);
74
+ }
75
+ else {
76
+ qs.channel = this.getNodeParameter('channelName', i);
77
+ }
78
+ responseData = await pumbleRequest.call(this, 'GET', '/getChannel', undefined, qs);
79
+ }
80
+ else if (operation === 'create') {
81
+ const body = {
82
+ name: this.getNodeParameter('name', i),
83
+ type: this.getNodeParameter('type', i),
84
+ };
85
+ const description = this.getNodeParameter('description', i);
86
+ if (description)
87
+ body.description = description;
88
+ responseData = await pumbleRequest.call(this, 'POST', '/createChannel', body);
89
+ }
90
+ else if (operation === 'addUsers') {
91
+ const channelId = this.getNodeParameter('channelId', i);
92
+ const userIdsRaw = this.getNodeParameter('userIds', i);
93
+ const userIds = userIdsRaw.split(',').map((s) => s.trim()).filter(Boolean);
94
+ responseData = await pumbleRequest.call(this, 'POST', '/addUsersToChannel', { channelId, userIds });
95
+ }
96
+ else if (operation === 'removeUser') {
97
+ const channelId = this.getNodeParameter('channelId', i);
98
+ const userId = this.getNodeParameter('userId', i);
99
+ responseData = await pumbleRequest.call(this, 'POST', '/removeUserFromChannel', { channelId, userId });
100
+ }
101
+ }
102
+ // ═══════════════════════════════════════════════════════════════
103
+ // MESSAGE
104
+ // ═══════════════════════════════════════════════════════════════
105
+ else if (resource === 'message') {
106
+ const resolveChannelIdent = (op) => {
107
+ const identType = this.getNodeParameter('channelIdentType', i);
108
+ const result = {};
109
+ if (identType === 'channelId') {
110
+ result.channelId = this.getNodeParameter('channelId', i);
111
+ }
112
+ else {
113
+ result.channel = this.getNodeParameter('channelName', i);
114
+ }
115
+ return result;
116
+ };
117
+ if (operation === 'send') {
118
+ const channelIdent = resolveChannelIdent('send');
119
+ const body = {
120
+ ...channelIdent,
121
+ text: this.getNodeParameter('text', i),
122
+ asBot: this.getNodeParameter('asBot', i),
123
+ };
124
+ responseData = await pumbleRequest.call(this, 'POST', '/sendMessage', body);
125
+ }
126
+ else if (operation === 'reply') {
127
+ const channelIdent = resolveChannelIdent('reply');
128
+ const body = {
129
+ ...channelIdent,
130
+ messageId: this.getNodeParameter('messageId', i),
131
+ text: this.getNodeParameter('text', i),
132
+ asBot: this.getNodeParameter('asBot', i),
133
+ };
134
+ responseData = await pumbleRequest.call(this, 'POST', '/sendReply', body);
135
+ }
136
+ else if (operation === 'fetch') {
137
+ const channelIdent = resolveChannelIdent('fetch');
138
+ const qs = {
139
+ messageId: this.getNodeParameter('messageId', i),
140
+ ...channelIdent,
141
+ };
142
+ responseData = await pumbleRequest.call(this, 'GET', '/fetchMessage', undefined, qs);
143
+ }
144
+ else if (operation === 'delete') {
145
+ const channelIdent = resolveChannelIdent('delete');
146
+ const qs = {
147
+ messageId: this.getNodeParameter('messageId', i),
148
+ ...channelIdent,
149
+ };
150
+ responseData = await pumbleRequest.call(this, 'DELETE', '/deleteMessage', undefined, qs);
151
+ }
152
+ else if (operation === 'edit') {
153
+ const channelIdent = resolveChannelIdent('edit');
154
+ const body = {
155
+ messageId: this.getNodeParameter('messageId', i),
156
+ ...channelIdent,
157
+ text: this.getNodeParameter('text', i),
158
+ };
159
+ responseData = await pumbleRequest.call(this, 'POST', '/editMessage', body);
160
+ }
161
+ else if (operation === 'list') {
162
+ const channelIdent = resolveChannelIdent('list');
163
+ const opts = this.getNodeParameter('listOptions', i, {});
164
+ const qs = { ...channelIdent, ...opts };
165
+ responseData = await pumbleRequest.call(this, 'GET', '/listMessages', undefined, qs);
166
+ }
167
+ else if (operation === 'fetchThreadReplies') {
168
+ const channelIdent = resolveChannelIdent('fetchThreadReplies');
169
+ const rootMessageId = this.getNodeParameter('rootMessageId', i);
170
+ const opts = this.getNodeParameter('threadOptions', i, {});
171
+ const qs = { rootMessageId, ...channelIdent, ...opts };
172
+ responseData = await pumbleRequest.call(this, 'GET', '/fetchThreadReplies', undefined, qs);
173
+ }
174
+ else if (operation === 'search') {
175
+ const body = {};
176
+ const searchText = this.getNodeParameter('searchText', i);
177
+ const searchFrom = this.getNodeParameter('searchFrom', i);
178
+ const searchIn = this.getNodeParameter('searchIn', i);
179
+ if (searchText)
180
+ body.text = searchText;
181
+ if (searchFrom)
182
+ body.from = searchFrom.split(',').map((s) => s.trim()).filter(Boolean);
183
+ if (searchIn)
184
+ body.in = searchIn.split(',').map((s) => s.trim()).filter(Boolean);
185
+ responseData = await pumbleRequest.call(this, 'POST', '/searchMessages', body);
186
+ }
187
+ else if (operation === 'dmUser') {
188
+ const dmBy = this.getNodeParameter('dmUserBy', i);
189
+ const body = {
190
+ text: this.getNodeParameter('text', i),
191
+ };
192
+ if (dmBy === 'userId') {
193
+ body.userId = this.getNodeParameter('userId', i);
194
+ }
195
+ else {
196
+ body.email = this.getNodeParameter('email', i);
197
+ }
198
+ responseData = await pumbleRequest.call(this, 'POST', '/dmUser', body);
199
+ }
200
+ else if (operation === 'dmGroup') {
201
+ const dmBy = this.getNodeParameter('dmGroupBy', i);
202
+ const body = {
203
+ text: this.getNodeParameter('text', i),
204
+ };
205
+ if (dmBy === 'userIds') {
206
+ const raw = this.getNodeParameter('userIds', i);
207
+ body.userIds = raw.split(',').map((s) => s.trim()).filter(Boolean);
208
+ }
209
+ else {
210
+ const raw = this.getNodeParameter('emails', i);
211
+ body.emails = raw.split(',').map((s) => s.trim()).filter(Boolean);
212
+ }
213
+ responseData = await pumbleRequest.call(this, 'POST', '/dmGroup', body);
214
+ }
215
+ }
216
+ // ═══════════════════════════════════════════════════════════════
217
+ // REACTION
218
+ // ═══════════════════════════════════════════════════════════════
219
+ else if (resource === 'reaction') {
220
+ const messageId = this.getNodeParameter('messageId', i);
221
+ const reaction = this.getNodeParameter('reaction', i);
222
+ if (operation === 'add') {
223
+ responseData = await pumbleRequest.call(this, 'POST', '/addReaction', { messageId, reaction });
224
+ }
225
+ else if (operation === 'remove') {
226
+ responseData = await pumbleRequest.call(this, 'DELETE', '/removeReaction', undefined, { messageId, reaction });
227
+ }
228
+ }
229
+ // ═══════════════════════════════════════════════════════════════
230
+ // SCHEDULED MESSAGE
231
+ // ═══════════════════════════════════════════════════════════════
232
+ else if (resource === 'scheduledMessage') {
233
+ if (operation === 'list') {
234
+ const opts = this.getNodeParameter('listOptions', i, {});
235
+ responseData = await pumbleRequest.call(this, 'GET', '/fetchScheduledMessages', undefined, opts);
236
+ }
237
+ else if (operation === 'fetch') {
238
+ const scheduledMessageId = this.getNodeParameter('scheduledMessageId', i);
239
+ responseData = await pumbleRequest.call(this, 'GET', '/fetchScheduledMessage', undefined, { scheduledMessageId });
240
+ }
241
+ else if (operation === 'create') {
242
+ const body = {
243
+ channelId: this.getNodeParameter('channelId', i),
244
+ text: this.getNodeParameter('text', i),
245
+ sendAt: this.getNodeParameter('sendAt', i),
246
+ };
247
+ responseData = await pumbleRequest.call(this, 'POST', '/createScheduledMessage', body);
248
+ }
249
+ else if (operation === 'edit') {
250
+ const body = {
251
+ scheduledMessageId: this.getNodeParameter('scheduledMessageId', i),
252
+ };
253
+ const text = this.getNodeParameter('text', i);
254
+ const sendAt = this.getNodeParameter('sendAt', i);
255
+ const channelId = this.getNodeParameter('channelId', i);
256
+ if (text)
257
+ body.text = text;
258
+ if (sendAt)
259
+ body.sendAt = sendAt;
260
+ if (channelId)
261
+ body.channelId = channelId;
262
+ responseData = await pumbleRequest.call(this, 'POST', '/editScheduledMessage', body);
263
+ }
264
+ else if (operation === 'delete') {
265
+ const scheduledMessageId = this.getNodeParameter('scheduledMessageId', i);
266
+ responseData = await pumbleRequest.call(this, 'DELETE', '/deleteScheduledMessage', undefined, { scheduledMessageId });
267
+ }
268
+ }
269
+ // ═══════════════════════════════════════════════════════════════
270
+ // USER
271
+ // ═══════════════════════════════════════════════════════════════
272
+ else if (resource === 'user') {
273
+ if (operation === 'myInfo') {
274
+ responseData = await pumbleRequest.call(this, 'GET', '/myInfo');
275
+ }
276
+ else if (operation === 'list') {
277
+ responseData = await pumbleRequest.call(this, 'GET', '/listUsers');
278
+ }
279
+ else if (operation === 'listGroups') {
280
+ responseData = await pumbleRequest.call(this, 'GET', '/listUserGroups');
281
+ }
282
+ else if (operation === 'customStatus') {
283
+ const body = {
284
+ code: this.getNodeParameter('code', i),
285
+ status: this.getNodeParameter('status', i),
286
+ expiration: this.getNodeParameter('expiration', i),
287
+ };
288
+ const expiresAt = this.getNodeParameter('expiresAt', i, 0);
289
+ if (expiresAt)
290
+ body.expiresAt = expiresAt;
291
+ responseData = await pumbleRequest.call(this, 'POST', '/customStatus', body);
292
+ }
293
+ }
294
+ // Normalise response into INodeExecutionData
295
+ if (Array.isArray(responseData)) {
296
+ const execData = this.helpers.returnJsonArray(responseData);
297
+ returnData.push(...execData);
298
+ }
299
+ else {
300
+ returnData.push({ json: (_a = responseData) !== null && _a !== void 0 ? _a : {} });
301
+ }
302
+ }
303
+ catch (error) {
304
+ if (this.continueOnFail()) {
305
+ returnData.push({ json: { error: error.message }, pairedItem: { item: i } });
306
+ continue;
307
+ }
308
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), error, { itemIndex: i });
309
+ }
310
+ }
311
+ return [returnData];
312
+ }
313
+ }
314
+ exports.Pumble = Pumble;
315
+ // ─── Private helper (bound via .call) ────────────────────────────────────────
316
+ async function pumbleRequest(method, endpoint, body, qs) {
317
+ const credentials = await this.getCredentials('pumbleApi');
318
+ const cleanQs = {};
319
+ if (qs) {
320
+ for (const [k, v] of Object.entries(qs)) {
321
+ if (v !== undefined && v !== '' && v !== 0) {
322
+ cleanQs[k] = v;
323
+ }
324
+ }
325
+ }
326
+ return this.helpers.request({
327
+ method,
328
+ url: `${BASE_URL}${endpoint}`,
329
+ headers: {
330
+ ApiKey: credentials.apiKey,
331
+ 'Content-Type': 'application/json',
332
+ },
333
+ qs: cleanQs,
334
+ body,
335
+ json: true,
336
+ });
337
+ }