@green-api/greenapi-integration 0.7.0 → 0.7.1
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 +406 -471
- package/README.ru.md +416 -482
- package/dist/types/types.d.ts +3 -3
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -27,7 +27,6 @@ third-party services.
|
|
|
27
27
|
- [Developer Guide](#developer-guide)
|
|
28
28
|
- [Working Example](#working-example)
|
|
29
29
|
- [Real-World Examples](#real-world-examples)
|
|
30
|
-
- [Best Practices](#best-practices)
|
|
31
30
|
|
|
32
31
|
## Installation
|
|
33
32
|
|
|
@@ -43,16 +42,16 @@ The foundation of your integration. Handles message & instance management, and p
|
|
|
43
42
|
|
|
44
43
|
```typescript
|
|
45
44
|
abstract class BaseAdapter<TPlatformWebhook, TPlatformMessage, TUser extends BaseUser = BaseUser, TInstance extends Instance = Instance> {
|
|
46
|
-
|
|
45
|
+
private readonly gaLogger = GreenApiLogger.getInstance(this.constructor.name);
|
|
47
46
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
47
|
+
public constructor(
|
|
48
|
+
transformer: MessageTransformer<TPlatformWebhook, TPlatformMessage>,
|
|
49
|
+
storage: StorageProvider<TUser, TInstance>,
|
|
50
|
+
);
|
|
52
51
|
|
|
53
|
-
|
|
52
|
+
public abstract createPlatformClient(params: any): Promise<any>;
|
|
54
53
|
|
|
55
|
-
|
|
54
|
+
public abstract sendToPlatform(message: TPlatformMessage, instance: TInstance): Promise<void>;
|
|
56
55
|
}
|
|
57
56
|
```
|
|
58
57
|
|
|
@@ -102,28 +101,28 @@ await adapter.updateUser(userEmail, updateData);
|
|
|
102
101
|
```typescript
|
|
103
102
|
// Platform webhook endpoint
|
|
104
103
|
app.post('/webhook/platform', async (req, res) => {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
104
|
+
try {
|
|
105
|
+
await adapter.handlePlatformWebhook(req.body, instanceId);
|
|
106
|
+
res.status(200).send();
|
|
107
|
+
} catch (error) {
|
|
108
|
+
console.error('Failed to handle platform webhook:', error);
|
|
109
|
+
res.status(500).send();
|
|
110
|
+
}
|
|
112
111
|
});
|
|
113
112
|
|
|
114
113
|
// GREEN-API webhook endpoint
|
|
115
114
|
app.post('/webhook/green-api', async (req, res) => {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
115
|
+
try {
|
|
116
|
+
// Process specific webhook types
|
|
117
|
+
await adapter.handleGreenApiWebhook(req.body, [
|
|
118
|
+
'incomingMessageReceived',
|
|
119
|
+
'outgoingMessageStatus'
|
|
120
|
+
]);
|
|
121
|
+
res.status(200).send();
|
|
122
|
+
} catch (error) {
|
|
123
|
+
console.error('Failed to handle GREEN-API webhook:', error);
|
|
124
|
+
res.status(500).send();
|
|
125
|
+
}
|
|
127
126
|
});
|
|
128
127
|
```
|
|
129
128
|
|
|
@@ -133,9 +132,9 @@ Handles message format conversion between GREEN-API and your platform.
|
|
|
133
132
|
|
|
134
133
|
```typescript
|
|
135
134
|
abstract class MessageTransformer<TPlatformWebhook, TPlatformMessage> {
|
|
136
|
-
|
|
135
|
+
abstract toPlatformMessage(webhook: GreenApiWebhook): TPlatformMessage;
|
|
137
136
|
|
|
138
|
-
|
|
137
|
+
abstract toGreenApiMessage(message: TPlatformWebhook): Message;
|
|
139
138
|
}
|
|
140
139
|
```
|
|
141
140
|
|
|
@@ -145,22 +144,22 @@ Interface for data persistence operations.
|
|
|
145
144
|
|
|
146
145
|
```typescript
|
|
147
146
|
abstract class StorageProvider<
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
147
|
+
TUser extends BaseUser = BaseUser,
|
|
148
|
+
TInstance extends Instance = Instance,
|
|
149
|
+
TUserCreate extends Record<string, any> = any,
|
|
150
|
+
TUserUpdate extends Record<string, any> = any
|
|
152
151
|
> {
|
|
153
|
-
|
|
152
|
+
abstract createInstance(instance: Instance): Promise<TInstance>;
|
|
154
153
|
|
|
155
|
-
|
|
154
|
+
abstract getInstance(idInstance: number | bigint): Promise<TInstance | null>;
|
|
156
155
|
|
|
157
|
-
|
|
156
|
+
abstract removeInstance(instanceId: number | bigint): Promise<TInstance>;
|
|
158
157
|
|
|
159
|
-
|
|
158
|
+
abstract createUser(data: TUserCreate): Promise<TUser>;
|
|
160
159
|
|
|
161
|
-
|
|
160
|
+
abstract findUser(identifier: string): Promise<TUser | null>;
|
|
162
161
|
|
|
163
|
-
|
|
162
|
+
abstract updateUser(identifier: string, data: Partial<TUserUpdate>): Promise<TUser>;
|
|
164
163
|
}
|
|
165
164
|
```
|
|
166
165
|
|
|
@@ -170,12 +169,12 @@ Handles webhook authentication for incoming GREEN-API requests.
|
|
|
170
169
|
|
|
171
170
|
```typescript
|
|
172
171
|
abstract class BaseGreenApiAuthGuard<T extends BaseRequest = BaseRequest> {
|
|
173
|
-
|
|
172
|
+
private readonly gaLogger = GreenApiLogger.getInstance(this.constructor.name);
|
|
174
173
|
|
|
175
|
-
|
|
174
|
+
constructor(protected storage: StorageProvider);
|
|
176
175
|
|
|
177
|
-
|
|
178
|
-
|
|
176
|
+
// Validates incoming webhook requests
|
|
177
|
+
async validateRequest(request: T): Promise<boolean>;
|
|
179
178
|
}
|
|
180
179
|
```
|
|
181
180
|
|
|
@@ -183,24 +182,24 @@ Example implementation of `BaseGreenApiAuthGuard`:
|
|
|
183
182
|
|
|
184
183
|
```typescript
|
|
185
184
|
class YourAuthGuard extends BaseGreenApiAuthGuard<YourRequest> {
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
185
|
+
constructor(storage: StorageProvider) {
|
|
186
|
+
super(storage);
|
|
187
|
+
}
|
|
189
188
|
}
|
|
190
189
|
|
|
191
190
|
// Using with Express
|
|
192
191
|
app.post('/webhook', async (req, res) => {
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
192
|
+
const guard = new YourAuthGuard(storage);
|
|
193
|
+
try {
|
|
194
|
+
await guard.validateRequest(req);
|
|
195
|
+
// Process webhook
|
|
196
|
+
} catch (error) {
|
|
197
|
+
if (error instanceof AuthenticationError) {
|
|
198
|
+
res.status(401).json({error: error.message});
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
res.status(500).json({error: 'Internal server error'});
|
|
202
|
+
}
|
|
204
203
|
});
|
|
205
204
|
```
|
|
206
205
|
|
|
@@ -221,12 +220,12 @@ logger.fatal("Fatal error", {critical: true});
|
|
|
221
220
|
|
|
222
221
|
// Error logging with full context
|
|
223
222
|
try {
|
|
224
|
-
|
|
223
|
+
await someOperation();
|
|
225
224
|
} catch (error) {
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
225
|
+
logger.logErrorResponse(error, "Operation failed", {
|
|
226
|
+
operationId: "123",
|
|
227
|
+
additionalInfo: "some context"
|
|
228
|
+
});
|
|
230
229
|
}
|
|
231
230
|
```
|
|
232
231
|
|
|
@@ -252,20 +251,20 @@ try {
|
|
|
252
251
|
|
|
253
252
|
```json
|
|
254
253
|
{
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
254
|
+
"timestamp": "30/01/2025, 04:34:49",
|
|
255
|
+
"level": "error",
|
|
256
|
+
"context": "CoreService",
|
|
257
|
+
"message": "Operation failed",
|
|
258
|
+
"error": "Failed to process request",
|
|
259
|
+
"stack": [
|
|
260
|
+
"Error: Failed to process request",
|
|
261
|
+
" at CoreService.process (/app/service.js:123:45)",
|
|
262
|
+
" at async Router.handle (/app/router.js:67:89)"
|
|
263
|
+
],
|
|
264
|
+
"additionalContext": {
|
|
265
|
+
"requestId": "abc-123",
|
|
266
|
+
"userId": "user_456"
|
|
267
|
+
}
|
|
269
268
|
}
|
|
270
269
|
```
|
|
271
270
|
|
|
@@ -274,12 +273,12 @@ try {
|
|
|
274
273
|
```typescript
|
|
275
274
|
// Axios error handling
|
|
276
275
|
try {
|
|
277
|
-
|
|
276
|
+
await apiRequest();
|
|
278
277
|
} catch (error) {
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
278
|
+
logger.logErrorResponse(error, "API Request failed", {
|
|
279
|
+
endpoint: "/users",
|
|
280
|
+
method: "POST"
|
|
281
|
+
});
|
|
283
282
|
}
|
|
284
283
|
```
|
|
285
284
|
|
|
@@ -307,13 +306,13 @@ The logger is framework-agnostic but can be easily integrated with any framework
|
|
|
307
306
|
```typescript
|
|
308
307
|
// Express example
|
|
309
308
|
app.use((err, req, res, next) => {
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
309
|
+
const logger = GreenApiLogger.getInstance("Express");
|
|
310
|
+
logger.error("Request failed", {
|
|
311
|
+
path: req.path,
|
|
312
|
+
method: req.method,
|
|
313
|
+
error: err.message
|
|
314
|
+
});
|
|
315
|
+
next(err);
|
|
317
316
|
});
|
|
318
317
|
```
|
|
319
318
|
|
|
@@ -327,7 +326,7 @@ For example, when using NestJS, you can disable its built-in logger like this:
|
|
|
327
326
|
```typescript
|
|
328
327
|
// main.ts
|
|
329
328
|
const app = await NestFactory.create(AppModule, {
|
|
330
|
-
|
|
329
|
+
logger: false // Disable NestJS logger
|
|
331
330
|
});
|
|
332
331
|
```
|
|
333
332
|
|
|
@@ -357,66 +356,14 @@ gaLogger = GreenApiLogger.getInstance(YourClass.name);
|
|
|
357
356
|
|
|
358
357
|
- `getInstance(context: string = "Global"): GreenApiLogger`: Get or create logger instance for specified context
|
|
359
358
|
|
|
360
|
-
#### Best Practices
|
|
361
|
-
|
|
362
|
-
1. **Use Consistent Context Names**
|
|
363
|
-
|
|
364
|
-
```typescript
|
|
365
|
-
// In your component/service
|
|
366
|
-
private readonly
|
|
367
|
-
logger = GreenApiLogger.getInstance(YourService.name);
|
|
368
|
-
```
|
|
369
|
-
|
|
370
|
-
2. **Include Relevant Context**
|
|
371
|
-
|
|
372
|
-
```typescript
|
|
373
|
-
logger.info("User action completed", {
|
|
374
|
-
userId: user.id,
|
|
375
|
-
action: "profile_update",
|
|
376
|
-
duration: timeTaken
|
|
377
|
-
});
|
|
378
|
-
```
|
|
379
|
-
|
|
380
|
-
3. **Proper Error Handling**
|
|
381
|
-
|
|
382
|
-
```typescript
|
|
383
|
-
try {
|
|
384
|
-
await complexOperation();
|
|
385
|
-
} catch (error) {
|
|
386
|
-
logger.logErrorResponse(error, "Complex operation failed", {
|
|
387
|
-
operationId: id,
|
|
388
|
-
parameters: params
|
|
389
|
-
});
|
|
390
|
-
}
|
|
391
|
-
```
|
|
392
|
-
|
|
393
|
-
4. **Use Appropriate Log Levels**
|
|
394
|
-
|
|
395
|
-
```typescript
|
|
396
|
-
// Debug for detailed information
|
|
397
|
-
logger.debug("Processing chunk", {chunkId: 123, size: 1024});
|
|
398
|
-
|
|
399
|
-
// Info for general operation
|
|
400
|
-
logger.info("User logged in", {userId: 456});
|
|
401
|
-
|
|
402
|
-
// Warn for potential issues
|
|
403
|
-
logger.warn("High memory usage", {memoryUsage: "85%"});
|
|
404
|
-
|
|
405
|
-
// Error for actual problems
|
|
406
|
-
logger.error("Database connection failed", {dbHost: "primary"});
|
|
407
|
-
|
|
408
|
-
// Fatal for critical issues
|
|
409
|
-
logger.fatal("System shutdown required", {reason: "data corruption"});
|
|
410
|
-
```
|
|
411
|
-
|
|
412
359
|
### 6. GreenApiClient
|
|
413
360
|
|
|
414
361
|
Direct interface to GREEN-API methods.
|
|
415
362
|
|
|
416
363
|
```typescript
|
|
417
364
|
const client = new GreenApiClient({
|
|
418
|
-
|
|
419
|
-
|
|
365
|
+
idInstance: 'your_instance_id',
|
|
366
|
+
apiTokenInstance: 'your_token'
|
|
420
367
|
});
|
|
421
368
|
|
|
422
369
|
// Examples:
|
|
@@ -481,17 +428,17 @@ First, define the message types for your platform:
|
|
|
481
428
|
```typescript
|
|
482
429
|
// types/types.ts
|
|
483
430
|
export interface YourPlatformWebhook {
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
431
|
+
id: string;
|
|
432
|
+
from: string;
|
|
433
|
+
message: string;
|
|
434
|
+
timestamp: number;
|
|
435
|
+
// Add other platform-specific fields
|
|
489
436
|
}
|
|
490
437
|
|
|
491
438
|
export interface YourPlatformMessage {
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
439
|
+
recipient: string;
|
|
440
|
+
content: string;
|
|
441
|
+
// Add other platform-specific fields
|
|
495
442
|
}
|
|
496
443
|
```
|
|
497
444
|
|
|
@@ -505,22 +452,22 @@ import { MessageTransformer, Message, GreenApiWebhook } from '@green-api/greenap
|
|
|
505
452
|
import { YourPlatformWebhook, YourPlatformMessage } from '../types/types';
|
|
506
453
|
|
|
507
454
|
export class YourTransformer extends MessageTransformer<YourPlatformWebhook, YourPlatformMessage> {
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
455
|
+
toPlatformMessage(webhook: GreenApiWebhook): YourPlatformMessage {
|
|
456
|
+
// Transform GREEN-API webhook to your platform format
|
|
457
|
+
return {
|
|
458
|
+
recipient: webhook.senderData.sender,
|
|
459
|
+
content: webhook.messageData.textMessageData?.textMessage || '',
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
toGreenApiMessage(message: YourPlatformWebhook): Message {
|
|
464
|
+
// Transform your platform webhook to GREEN-API format
|
|
465
|
+
return {
|
|
466
|
+
type: 'text',
|
|
467
|
+
chatId: message.from,
|
|
468
|
+
message: message.message,
|
|
469
|
+
};
|
|
470
|
+
}
|
|
524
471
|
}
|
|
525
472
|
```
|
|
526
473
|
|
|
@@ -534,28 +481,28 @@ import { StorageProvider, BaseUser, Instance, Settings } from '@green-api/greena
|
|
|
534
481
|
import { PrismaClient } from '@prisma/client'; // Or your database client
|
|
535
482
|
|
|
536
483
|
export class YourStorage extends StorageProvider {
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
484
|
+
private db: PrismaClient;
|
|
485
|
+
|
|
486
|
+
constructor() {
|
|
487
|
+
this.db = new PrismaClient();
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
async findUserByEmail(email: string) {
|
|
491
|
+
return this.db.user.findUnique({where: {email}});
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
async createInstance(instance: Instance) {
|
|
495
|
+
return this.db.instance.create({
|
|
496
|
+
data: {
|
|
497
|
+
idInstance: instance.idInstance,
|
|
498
|
+
apiTokenInstance: instance.apiTokenInstance,
|
|
499
|
+
userId: instance.userId,
|
|
500
|
+
settings: instance.settings || {},
|
|
501
|
+
},
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
// Implement other required methods
|
|
559
506
|
}
|
|
560
507
|
```
|
|
561
508
|
|
|
@@ -570,17 +517,17 @@ import { YourPlatformClient } from 'your-platform-sdk';
|
|
|
570
517
|
import { YourPlatformWebhook, YourPlatformMessage } from '../types/types';
|
|
571
518
|
|
|
572
519
|
export class YourAdapter extends BaseAdapter<YourPlatformWebhook, YourPlatformMessage> {
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
520
|
+
async createPlatformClient(config: { apiKey: string, apiUrl: string }) {
|
|
521
|
+
return new YourPlatformClient({
|
|
522
|
+
baseUrl: config.apiUrl,
|
|
523
|
+
apiKey: config.apiKey,
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
async sendToPlatform(message: YourPlatformMessage, instance: Instance) {
|
|
528
|
+
const client = await this.createPlatformClient(instance.config);
|
|
529
|
+
await client.sendMessage(message);
|
|
530
|
+
}
|
|
584
531
|
}
|
|
585
532
|
```
|
|
586
533
|
|
|
@@ -601,74 +548,74 @@ const transformer = new YourTransformer();
|
|
|
601
548
|
const adapter = new YourAdapter(transformer, storage);
|
|
602
549
|
|
|
603
550
|
class WebhookGuard extends BaseGreenApiAuthGuard {
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
551
|
+
constructor(storage: StorageProvider) {
|
|
552
|
+
super(storage);
|
|
553
|
+
}
|
|
607
554
|
}
|
|
608
555
|
|
|
609
556
|
const guard = new WebhookGuard(storage);
|
|
610
557
|
|
|
611
558
|
// Webhook endpoints
|
|
612
559
|
router.post('/green-api', async (req, res) => {
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
560
|
+
try {
|
|
561
|
+
// Validate webhook first
|
|
562
|
+
await guard.validateRequest(req);
|
|
563
|
+
|
|
564
|
+
// Process webhook if validation passed
|
|
565
|
+
// As the second parameter, specfify the types of webhooks to be processed (otherwise skipped)
|
|
566
|
+
await adapter.handleGreenApiWebhook(req.body, ['incomingMessageReceived']);
|
|
567
|
+
res.status(200).json({status: 'ok'});
|
|
568
|
+
} catch (error) {
|
|
569
|
+
if (error instanceof AuthenticationError) {
|
|
570
|
+
res.status(401).json({error: error.message});
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
573
|
+
console.error('Webhook error:', error);
|
|
574
|
+
res.status(500).json({error: 'Internal server error'});
|
|
575
|
+
}
|
|
629
576
|
});
|
|
630
577
|
|
|
631
578
|
router.post('/platform', async (req, res) => {
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
579
|
+
try {
|
|
580
|
+
const instanceId = req.query.instanceId;
|
|
581
|
+
await adapter.handlePlatformWebhook(req.body, instanceId);
|
|
582
|
+
res.status(200).json({status: 'ok'});
|
|
583
|
+
} catch (error) {
|
|
584
|
+
console.error('Platform webhook error:', error);
|
|
585
|
+
res.status(500).json({error: 'Internal server error'});
|
|
586
|
+
}
|
|
640
587
|
});
|
|
641
588
|
|
|
642
589
|
router.post('/instance', async (req, res) => {
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
590
|
+
try {
|
|
591
|
+
const {idInstance, apiTokenInstance, userEmail} = req.body;
|
|
592
|
+
|
|
593
|
+
if (!idInstance || !apiTokenInstance || !userEmail) {
|
|
594
|
+
throw new BadRequestError('Required fields missing');
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
const user = await storage.findUserByEmail(userEmail);
|
|
598
|
+
const instance = await adapter.createInstance({
|
|
599
|
+
idInstance: Number(idInstance),
|
|
600
|
+
apiTokenInstance,
|
|
601
|
+
settings: {
|
|
602
|
+
webhookUrl: `${process.env.APP_URL}/webhook/green-api`,
|
|
603
|
+
webhookUrlToken: `token_${Date.now()}`,
|
|
604
|
+
incomingWebhook: 'yes'
|
|
605
|
+
},
|
|
606
|
+
userId: user.id
|
|
607
|
+
});
|
|
608
|
+
|
|
609
|
+
res.status(200).json({
|
|
610
|
+
status: 'ok',
|
|
611
|
+
data: instance,
|
|
612
|
+
message: 'Instance created successfully. Please wait 2 minutes for settings to apply.'
|
|
613
|
+
});
|
|
614
|
+
|
|
615
|
+
} catch (error) {
|
|
616
|
+
console.error('Instance creation error:', error);
|
|
617
|
+
res.status(500).json({error: 'Failed to create instance'});
|
|
618
|
+
}
|
|
672
619
|
});
|
|
673
620
|
|
|
674
621
|
export default router;
|
|
@@ -692,25 +639,25 @@ import { YourStorage } from './core/storage';
|
|
|
692
639
|
dotenv.config();
|
|
693
640
|
|
|
694
641
|
async function bootstrap() {
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
642
|
+
// Initialize components
|
|
643
|
+
const storage = new YourStorage();
|
|
644
|
+
const transformer = new YourTransformer();
|
|
645
|
+
const adapter = new YourAdapter(transformer, storage);
|
|
699
646
|
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
647
|
+
// Create Express application
|
|
648
|
+
const app = express();
|
|
649
|
+
app.use(bodyParser.json());
|
|
703
650
|
|
|
704
|
-
|
|
705
|
-
|
|
651
|
+
// Set up webhook routes
|
|
652
|
+
app.use('/webhook', webhookRouter);
|
|
706
653
|
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
654
|
+
// Start server
|
|
655
|
+
const port = process.env.PORT || 3000;
|
|
656
|
+
app.listen(port, () => {
|
|
657
|
+
console.log(`Server running on port ${port}`);
|
|
658
|
+
});
|
|
712
659
|
|
|
713
|
-
|
|
660
|
+
console.log('Integration platform ready!');
|
|
714
661
|
}
|
|
715
662
|
|
|
716
663
|
// Handle errors
|
|
@@ -723,19 +670,19 @@ bootstrap();
|
|
|
723
670
|
|
|
724
671
|
```json
|
|
725
672
|
{
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
673
|
+
"name": "greenapi-integration-yourplatform",
|
|
674
|
+
"version": "1.0.0",
|
|
675
|
+
"main": "dist/index.js",
|
|
676
|
+
"types": "dist/index.d.ts",
|
|
677
|
+
"scripts": {
|
|
678
|
+
"build": "tsc",
|
|
679
|
+
"prepublishOnly": "npm run build"
|
|
680
|
+
},
|
|
681
|
+
"dependencies": {
|
|
682
|
+
"@green-api/greenapi-integration": "^0.4.0",
|
|
683
|
+
"express": "^4.18.2"
|
|
684
|
+
// other dependencies
|
|
685
|
+
}
|
|
739
686
|
}
|
|
740
687
|
```
|
|
741
688
|
|
|
@@ -800,16 +747,16 @@ examples/
|
|
|
800
747
|
|
|
801
748
|
```typescript
|
|
802
749
|
interface SimplePlatformWebhook {
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
750
|
+
messageId: string;
|
|
751
|
+
from: string;
|
|
752
|
+
text: string;
|
|
753
|
+
timestamp: number;
|
|
807
754
|
}
|
|
808
755
|
|
|
809
756
|
interface SimplePlatformMessage {
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
757
|
+
to: string;
|
|
758
|
+
content: string;
|
|
759
|
+
replyTo?: string;
|
|
813
760
|
}
|
|
814
761
|
```
|
|
815
762
|
|
|
@@ -817,36 +764,36 @@ interface SimplePlatformMessage {
|
|
|
817
764
|
|
|
818
765
|
```typescript
|
|
819
766
|
import {
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
767
|
+
MessageTransformer,
|
|
768
|
+
Message,
|
|
769
|
+
GreenApiWebhook,
|
|
770
|
+
formatPhoneNumber,
|
|
771
|
+
IntegrationError,
|
|
825
772
|
} from "@green-api/greenapi-integration";
|
|
826
773
|
import { SimplePlatformMessage, SimplePlatformWebhook } from "./types";
|
|
827
774
|
|
|
828
775
|
export class SimpleTransformer extends MessageTransformer<SimplePlatformWebhook, SimplePlatformMessage> {
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
776
|
+
toPlatformMessage(webhook: GreenApiWebhook): SimplePlatformMessage {
|
|
777
|
+
if (webhook.typeWebhook === "incomingMessageReceived") {
|
|
778
|
+
if (webhook.messageData.typeMessage !== "extendedTextMessage") {
|
|
779
|
+
throw new IntegrationError("Only text messages are supported", "BAD_REQUEST_ERROR", 400);
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
return {
|
|
783
|
+
to: webhook.senderData.sender,
|
|
784
|
+
content: webhook.messageData.extendedTextMessageData?.text || "",
|
|
785
|
+
};
|
|
786
|
+
}
|
|
787
|
+
throw new IntegrationError("Only incomingMessageReceived type webhooks are supported", "INTEGRATION_ERROR", 500);
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
toGreenApiMessage(message: SimplePlatformWebhook): Message {
|
|
791
|
+
return {
|
|
792
|
+
type: "text",
|
|
793
|
+
chatId: formatPhoneNumber(message.from),
|
|
794
|
+
message: message.text,
|
|
795
|
+
};
|
|
796
|
+
}
|
|
850
797
|
}
|
|
851
798
|
```
|
|
852
799
|
|
|
@@ -856,44 +803,44 @@ export class SimpleTransformer extends MessageTransformer<SimplePlatformWebhook,
|
|
|
856
803
|
import { StorageProvider, BaseUser, Instance } from '@green-api/greenapi-integration';
|
|
857
804
|
|
|
858
805
|
export class SimpleStorage extends StorageProvider {
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
806
|
+
private users: Map<string, BaseUser> = new Map();
|
|
807
|
+
private instances: Map<number, Instance> = new Map();
|
|
808
|
+
|
|
809
|
+
async createInstance(instance: Instance, userId: bigint): Promise<Instance> {
|
|
810
|
+
this.instances.set(Number(instance.idInstance), {
|
|
811
|
+
...instance,
|
|
812
|
+
});
|
|
813
|
+
return instance;
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
async getInstance(idInstance: number): Promise<Instance | null> {
|
|
817
|
+
return this.instances.get(idInstance) || null;
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
async removeInstance(instanceId: number): Promise<Instance> {
|
|
821
|
+
const instance = this.instances.get(instanceId);
|
|
822
|
+
if (!instance) throw new Error('Instance not found');
|
|
823
|
+
this.instances.delete(instanceId);
|
|
824
|
+
return instance;
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
async createUser(data: any): Promise<BaseUser> {
|
|
828
|
+
const user = {id: Date.now(), ...data};
|
|
829
|
+
this.users.set(data.email, user);
|
|
830
|
+
return user;
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
async findUser(identifier: string): Promise<BaseUser | null> {
|
|
834
|
+
return this.users.get(identifier) || null;
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
async updateUser(identifier: string, data: any): Promise<BaseUser> {
|
|
838
|
+
const user = await this.findUser(identifier);
|
|
839
|
+
if (!user) throw new Error('User not found');
|
|
840
|
+
const updated = {...user, ...data};
|
|
841
|
+
this.users.set(identifier, updated);
|
|
842
|
+
return updated;
|
|
843
|
+
}
|
|
897
844
|
}
|
|
898
845
|
```
|
|
899
846
|
|
|
@@ -905,38 +852,38 @@ import { SimplePlatformMessage, SimplePlatformWebhook } from "./types";
|
|
|
905
852
|
import axios from 'axios';
|
|
906
853
|
|
|
907
854
|
export class SimpleAdapter extends BaseAdapter<SimplePlatformWebhook, SimplePlatformMessage> {
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
855
|
+
async createPlatformClient(config: { apiKey: string, apiUrl: string }) {
|
|
856
|
+
return axios.create({
|
|
857
|
+
baseURL: config.apiUrl,
|
|
858
|
+
headers: {
|
|
859
|
+
'Authorization': `Bearer ${config.apiKey}`,
|
|
860
|
+
'Content-Type': 'application/json'
|
|
861
|
+
}
|
|
862
|
+
});
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
async sendToPlatform(message: SimplePlatformMessage, instance: Instance): Promise<void> {
|
|
866
|
+
// In a real implementation, we would send to the platform
|
|
867
|
+
// For demo, we'll just log and simulate a response
|
|
868
|
+
console.log('Platform received message:', message);
|
|
869
|
+
|
|
870
|
+
// Simulate platform processing and responding
|
|
871
|
+
setTimeout(() => {
|
|
872
|
+
console.log('Platform processing complete, sending response...');
|
|
873
|
+
this.simulatePlatformResponse(message, instance.idInstance);
|
|
874
|
+
}, 1000);
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
private async simulatePlatformResponse(originalMessage: SimplePlatformMessage, idInstance: number | bigint) {
|
|
878
|
+
const platformWebhook: SimplePlatformWebhook = {
|
|
879
|
+
messageId: `resp_${Date.now()}`,
|
|
880
|
+
from: originalMessage.to.replace('@c.us', ''),
|
|
881
|
+
text: `Thanks for your message: "${originalMessage.content}". This is an automated response.`,
|
|
882
|
+
timestamp: Date.now()
|
|
883
|
+
};
|
|
884
|
+
|
|
885
|
+
await this.handlePlatformWebhook(platformWebhook, idInstance);
|
|
886
|
+
}
|
|
940
887
|
}
|
|
941
888
|
```
|
|
942
889
|
|
|
@@ -954,92 +901,80 @@ import * as dotenv from "dotenv";
|
|
|
954
901
|
dotenv.config();
|
|
955
902
|
|
|
956
903
|
async function main() {
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
904
|
+
// Initialize components
|
|
905
|
+
const transformer = new SimpleTransformer();
|
|
906
|
+
const storage = new SimpleStorage();
|
|
907
|
+
const adapter = new SimpleAdapter(transformer, storage);
|
|
908
|
+
|
|
909
|
+
// Configuration for both instances
|
|
910
|
+
const visitorInstance = {
|
|
911
|
+
idInstance: Number(process.env.VISITOR_ID_INSTANCE),
|
|
912
|
+
apiTokenInstance: process.env.VISITOR_API_TOKEN!,
|
|
913
|
+
};
|
|
914
|
+
|
|
915
|
+
const agentInstance = {
|
|
916
|
+
idInstance: Number(process.env.AGENT_ID_INSTANCE),
|
|
917
|
+
apiTokenInstance: process.env.AGENT_API_TOKEN!,
|
|
918
|
+
};
|
|
919
|
+
console.log(visitorInstance, agentInstance);
|
|
920
|
+
|
|
921
|
+
// Create visitor's GREEN-API client (for sending initial message)
|
|
922
|
+
const visitorClient = new GreenApiClient(visitorInstance);
|
|
923
|
+
|
|
924
|
+
// Set up agent instance
|
|
925
|
+
console.log("Setting up agent instance...");
|
|
926
|
+
const user = await adapter.createUser("agent@example.com", {
|
|
927
|
+
email: "agent@example.com",
|
|
928
|
+
name: "Agent",
|
|
929
|
+
});
|
|
930
|
+
|
|
931
|
+
const instance = await adapter.createInstance({
|
|
932
|
+
idInstance: agentInstance.idInstance, apiTokenInstance: agentInstance.apiTokenInstance, settings: {
|
|
933
|
+
webhookUrl: process.env.WEBHOOK_URL + "/webhook/green-api",
|
|
934
|
+
webhookUrlToken: "your-secure-token",
|
|
935
|
+
incomingWebhook: "yes",
|
|
936
|
+
},
|
|
937
|
+
}, user.email);
|
|
938
|
+
|
|
939
|
+
console.log("Waiting 2 minutes for settings to apply...");
|
|
940
|
+
await new Promise(resolve => setTimeout(resolve, 120000));
|
|
941
|
+
console.log("Instance ready!");
|
|
942
|
+
|
|
943
|
+
// Set up webhook server
|
|
944
|
+
const app = express();
|
|
945
|
+
app.use(bodyParser.json());
|
|
946
|
+
|
|
947
|
+
// Handle GREEN-API webhooks
|
|
948
|
+
app.post("/webhook/green-api", async (req, res) => {
|
|
949
|
+
try {
|
|
950
|
+
console.log("Received webhook from GREEN-API:", req.body);
|
|
951
|
+
await adapter.handleGreenApiWebhook(req.body, ["incomingMessageReceived"]);
|
|
952
|
+
res.status(200).json({status: "ok"});
|
|
953
|
+
} catch (error) {
|
|
954
|
+
console.error("Error handling webhook:", error);
|
|
955
|
+
res.status(500).json({error: "Internal server error"});
|
|
956
|
+
}
|
|
957
|
+
});
|
|
958
|
+
|
|
959
|
+
// Start the server
|
|
960
|
+
const port = Number(process.env.PORT) || 3000;
|
|
961
|
+
app.listen(port, () => {
|
|
962
|
+
console.log(`Webhook server listening on port ${port}`);
|
|
963
|
+
});
|
|
964
|
+
|
|
965
|
+
// Send initial message from visitor
|
|
966
|
+
console.log("Sending initial message from visitor...");
|
|
967
|
+
await visitorClient.sendMessage({
|
|
968
|
+
chatId: formatPhoneNumber(process.env.AGENT_PHONE_NUMBER!),
|
|
969
|
+
message: "Hello! This is a test message from a visitor.",
|
|
970
|
+
});
|
|
971
|
+
|
|
972
|
+
console.log("Initial message sent! Check the agent WhatsApp app to see the response.");
|
|
1026
973
|
}
|
|
1027
974
|
|
|
1028
975
|
main().catch(console.error);
|
|
1029
976
|
```
|
|
1030
977
|
|
|
1031
|
-
### .env
|
|
1032
|
-
|
|
1033
|
-
```env
|
|
1034
|
-
VISITOR_ID_INSTANCE=your_visitor_instance_id
|
|
1035
|
-
VISITOR_API_TOKEN=your_visitor_instance_token
|
|
1036
|
-
AGENT_ID_INSTANCE=your_agent_instance_id
|
|
1037
|
-
AGENT_API_TOKEN=your_agent_instance_token
|
|
1038
|
-
AGENT_PHONE_NUMBER=your_agent_phone_number
|
|
1039
|
-
WEBHOOK_URL=your_webhook_url
|
|
1040
|
-
PORT=3000
|
|
1041
|
-
```
|
|
1042
|
-
|
|
1043
978
|
## Real-World Examples
|
|
1044
979
|
|
|
1045
980
|
For complete real-world integration examples, check out:
|
|
@@ -1066,10 +1001,10 @@ isValidSettingValue('webhookUrl', 'https://example.com') // Returns true
|
|
|
1066
1001
|
|
|
1067
1002
|
// Clean settings
|
|
1068
1003
|
const input = {
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1004
|
+
webhookUrl: 'https://example.com',
|
|
1005
|
+
outgoingWebhook: 'yes',
|
|
1006
|
+
invalidKey: 'value',
|
|
1007
|
+
delaySendMessagesMilliseconds: 'invalid'
|
|
1073
1008
|
}
|
|
1074
1009
|
validateAndCleanSettings(input) // Returns { webhookUrl: 'https://example.com', outgoingWebhook: 'yes' }
|
|
1075
1010
|
```
|