@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.ru.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
# Универсальная интеграционная платформа для GREEN-API
|
|
2
2
|
|
|
3
3
|
## Поддержка
|
|
4
4
|
|
|
@@ -24,10 +24,9 @@
|
|
|
24
24
|
|
|
25
25
|
- [Установка](#установка)
|
|
26
26
|
- [Основные компоненты](#основные-компоненты)
|
|
27
|
-
- [Руководство разработчика](
|
|
27
|
+
- [Руководство для разработчика](#руководство-для-разработчика)
|
|
28
28
|
- [Рабочий пример](#рабочий-пример)
|
|
29
29
|
- [Реальные примеры](#реальные-примеры)
|
|
30
|
-
- [Лучшие практики](#лучшие-практики)
|
|
31
30
|
|
|
32
31
|
## Установка
|
|
33
32
|
|
|
@@ -43,14 +42,14 @@ npm install @green-api/greenapi-integration
|
|
|
43
42
|
|
|
44
43
|
```typescript
|
|
45
44
|
abstract class BaseAdapter<TPlatformWebhook, TPlatformMessage> {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
45
|
+
public constructor(
|
|
46
|
+
transformer: MessageTransformer<TPlatformWebhook, TPlatformMessage>,
|
|
47
|
+
storage: StorageProvider
|
|
48
|
+
);
|
|
50
49
|
|
|
51
|
-
|
|
50
|
+
public abstract createPlatformClient(params: any): Promise<any>;
|
|
52
51
|
|
|
53
|
-
|
|
52
|
+
public abstract sendToPlatform(message: TPlatformMessage, instance: TInstance): Promise<void>;
|
|
54
53
|
}
|
|
55
54
|
```
|
|
56
55
|
|
|
@@ -100,28 +99,28 @@ await adapter.updateUser(userEmail, updateData);
|
|
|
100
99
|
```typescript
|
|
101
100
|
// Конечная точка для вебхуков платформы
|
|
102
101
|
app.post('/webhook/platform', async (req, res) => {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
102
|
+
try {
|
|
103
|
+
await adapter.handlePlatformWebhook(req.body, instanceId);
|
|
104
|
+
res.status(200).send();
|
|
105
|
+
} catch (error) {
|
|
106
|
+
console.error('Не удалось обработать вебхук платформы:', error);
|
|
107
|
+
res.status(500).send();
|
|
108
|
+
}
|
|
110
109
|
});
|
|
111
110
|
|
|
112
111
|
// Конечная точка для вебхуков GREEN-API
|
|
113
112
|
app.post('/webhook/green-api', async (req, res) => {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
113
|
+
try {
|
|
114
|
+
// Обработка определенных типов вебхуков
|
|
115
|
+
await adapter.handleGreenApiWebhook(req.body, [
|
|
116
|
+
'incomingMessageReceived',
|
|
117
|
+
'outgoingMessageStatus'
|
|
118
|
+
]);
|
|
119
|
+
res.status(200).send();
|
|
120
|
+
} catch (error) {
|
|
121
|
+
console.error('Не удалось обработать вебхук GREEN-API:', error);
|
|
122
|
+
res.status(500).send();
|
|
123
|
+
}
|
|
125
124
|
});
|
|
126
125
|
```
|
|
127
126
|
|
|
@@ -131,9 +130,9 @@ app.post('/webhook/green-api', async (req, res) => {
|
|
|
131
130
|
|
|
132
131
|
```typescript
|
|
133
132
|
abstract class MessageTransformer<TPlatformWebhook, TPlatformMessage> {
|
|
134
|
-
|
|
133
|
+
abstract toPlatformMessage(webhook: GreenApiWebhook): TPlatformMessage;
|
|
135
134
|
|
|
136
|
-
|
|
135
|
+
abstract toGreenApiMessage(message: TPlatformWebhook): Message;
|
|
137
136
|
}
|
|
138
137
|
```
|
|
139
138
|
|
|
@@ -143,17 +142,17 @@ abstract class MessageTransformer<TPlatformWebhook, TPlatformMessage> {
|
|
|
143
142
|
|
|
144
143
|
```typescript
|
|
145
144
|
abstract class StorageProvider<TUser extends BaseUser = BaseUser, TInstance extends BaseInstance = Instance> {
|
|
146
|
-
|
|
145
|
+
abstract createInstance(instance: BaseInstance, userId: bigint | number): Promise<TInstance>;
|
|
147
146
|
|
|
148
|
-
|
|
147
|
+
abstract getInstance(idInstance: number | bigint): Promise<TInstance | null>;
|
|
149
148
|
|
|
150
|
-
|
|
149
|
+
abstract removeInstance(instanceId: number | bigint): Promise<TInstance>;
|
|
151
150
|
|
|
152
|
-
|
|
151
|
+
abstract createUser(data: any): Promise<TUser>;
|
|
153
152
|
|
|
154
|
-
|
|
153
|
+
abstract findUser(identifier: string): Promise<TUser | null>;
|
|
155
154
|
|
|
156
|
-
|
|
155
|
+
abstract updateUser(identifier: string, data: any): Promise<TUser>;
|
|
157
156
|
}
|
|
158
157
|
```
|
|
159
158
|
|
|
@@ -163,10 +162,10 @@ abstract class StorageProvider<TUser extends BaseUser = BaseUser, TInstance exte
|
|
|
163
162
|
|
|
164
163
|
```typescript
|
|
165
164
|
abstract class BaseGreenApiAuthGuard<T extends BaseRequest = BaseRequest> {
|
|
166
|
-
|
|
165
|
+
constructor(protected storage: StorageProvider);
|
|
167
166
|
|
|
168
|
-
|
|
169
|
-
|
|
167
|
+
// Валидация входящих вебхуков
|
|
168
|
+
async validateRequest(request: T): Promise<boolean>;
|
|
170
169
|
}
|
|
171
170
|
```
|
|
172
171
|
|
|
@@ -174,24 +173,24 @@ abstract class BaseGreenApiAuthGuard<T extends BaseRequest = BaseRequest> {
|
|
|
174
173
|
|
|
175
174
|
```typescript
|
|
176
175
|
class YourAuthGuard extends BaseGreenApiAuthGuard<YourRequest> {
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
176
|
+
constructor(storage: StorageProvider) {
|
|
177
|
+
super(storage);
|
|
178
|
+
}
|
|
180
179
|
}
|
|
181
180
|
|
|
182
181
|
// Использование с Express
|
|
183
182
|
app.post('/webhook', async (req, res) => {
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
183
|
+
const guard = new YourAuthGuard(storage);
|
|
184
|
+
try {
|
|
185
|
+
await guard.validateRequest(req);
|
|
186
|
+
// Обработка вебхука ...
|
|
187
|
+
} catch (error) {
|
|
188
|
+
if (error instanceof AuthenticationError) {
|
|
189
|
+
res.status(401).json({error: error.message});
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
res.status(500).json({error: 'Internal server error'});
|
|
193
|
+
}
|
|
195
194
|
});
|
|
196
195
|
```
|
|
197
196
|
|
|
@@ -212,12 +211,12 @@ logger.fatal("Fatal error", {critical: true});
|
|
|
212
211
|
|
|
213
212
|
// Логирование ошибок с контекстом
|
|
214
213
|
try {
|
|
215
|
-
|
|
214
|
+
await someOperation();
|
|
216
215
|
} catch (error) {
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
216
|
+
logger.logErrorResponse(error, "Operation failed", {
|
|
217
|
+
operationId: "123",
|
|
218
|
+
additionalInfo: "some context"
|
|
219
|
+
});
|
|
221
220
|
}
|
|
222
221
|
```
|
|
223
222
|
|
|
@@ -243,20 +242,20 @@ try {
|
|
|
243
242
|
|
|
244
243
|
```json
|
|
245
244
|
{
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
245
|
+
"timestamp": "30/01/2025, 04:34:49",
|
|
246
|
+
"level": "error",
|
|
247
|
+
"context": "CoreService",
|
|
248
|
+
"message": "Operation failed",
|
|
249
|
+
"error": "Failed to process request",
|
|
250
|
+
"stack": [
|
|
251
|
+
"Error: Failed to process request",
|
|
252
|
+
" at CoreService.process (/app/service.js:123:45)",
|
|
253
|
+
" at async Router.handle (/app/router.js:67:89)"
|
|
254
|
+
],
|
|
255
|
+
"additionalContext": {
|
|
256
|
+
"requestId": "abc-123",
|
|
257
|
+
"userId": "user_456"
|
|
258
|
+
}
|
|
260
259
|
}
|
|
261
260
|
```
|
|
262
261
|
|
|
@@ -265,12 +264,12 @@ try {
|
|
|
265
264
|
```typescript
|
|
266
265
|
// Обработка ошибок Axios
|
|
267
266
|
try {
|
|
268
|
-
|
|
267
|
+
await apiRequest();
|
|
269
268
|
} catch (error) {
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
269
|
+
logger.logErrorResponse(error, "API Request failed", {
|
|
270
|
+
endpoint: "/users",
|
|
271
|
+
method: "POST"
|
|
272
|
+
});
|
|
274
273
|
}
|
|
275
274
|
```
|
|
276
275
|
|
|
@@ -278,18 +277,18 @@ try {
|
|
|
278
277
|
|
|
279
278
|
```json
|
|
280
279
|
{
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
280
|
+
"timestamp": "30/01/2025, 04:34:49",
|
|
281
|
+
"level": "error",
|
|
282
|
+
"context": "ApiService",
|
|
283
|
+
"message": "API Request failed - API Error:",
|
|
284
|
+
"status": 400,
|
|
285
|
+
"statusText": "Bad Request",
|
|
286
|
+
"data": {
|
|
287
|
+
"error": "Invalid input"
|
|
288
|
+
},
|
|
289
|
+
"url": "https://api.example.com/users",
|
|
290
|
+
"method": "POST",
|
|
291
|
+
"endpoint": "/users"
|
|
293
292
|
}
|
|
294
293
|
```
|
|
295
294
|
|
|
@@ -300,18 +299,18 @@ try {
|
|
|
300
299
|
```typescript
|
|
301
300
|
// Пример с NestJS
|
|
302
301
|
const app = await NestFactory.create(AppModule, {
|
|
303
|
-
|
|
302
|
+
logger: GreenApiLogger.getInstance("NestJS")
|
|
304
303
|
});
|
|
305
304
|
|
|
306
305
|
// Пример с Express
|
|
307
306
|
app.use((err, req, res, next) => {
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
307
|
+
const logger = GreenApiLogger.getInstance("Express");
|
|
308
|
+
logger.error("Request failed", {
|
|
309
|
+
path: req.path,
|
|
310
|
+
method: req.method,
|
|
311
|
+
error: err.message
|
|
312
|
+
});
|
|
313
|
+
next(err);
|
|
315
314
|
});
|
|
316
315
|
```
|
|
317
316
|
|
|
@@ -325,7 +324,7 @@ app.use((err, req, res, next) => {
|
|
|
325
324
|
```typescript
|
|
326
325
|
// main.ts
|
|
327
326
|
const app = await NestFactory.create(AppModule, {
|
|
328
|
-
|
|
327
|
+
logger: false // Отключение логера NestJS
|
|
329
328
|
});
|
|
330
329
|
```
|
|
331
330
|
|
|
@@ -356,69 +355,17 @@ gaLogger = GreenApiLogger.getInstance(YourClass.name);
|
|
|
356
355
|
- `getInstance(context: string = "Global"): GreenApiLogger`: Получение или создание экземпляра логгера для указанного
|
|
357
356
|
контекста
|
|
358
357
|
|
|
359
|
-
#### Лучшие практики
|
|
360
|
-
|
|
361
|
-
1. **Используйте последовательные имена контекста**
|
|
362
|
-
|
|
363
|
-
```typescript
|
|
364
|
-
// В вашем компоненте/сервисе
|
|
365
|
-
private readonly
|
|
366
|
-
logger = GreenApiLogger.getInstance(YourService.name);
|
|
367
|
-
```
|
|
368
|
-
|
|
369
|
-
2. **Включайте релевантный контекст**
|
|
370
|
-
|
|
371
|
-
```typescript
|
|
372
|
-
logger.info("User action completed", {
|
|
373
|
-
userId: user.id,
|
|
374
|
-
action: "profile_update",
|
|
375
|
-
duration: timeTaken
|
|
376
|
-
});
|
|
377
|
-
```
|
|
378
|
-
|
|
379
|
-
3. **Правильная обработка ошибок**
|
|
380
|
-
|
|
381
|
-
```typescript
|
|
382
|
-
try {
|
|
383
|
-
await complexOperation();
|
|
384
|
-
} catch (error) {
|
|
385
|
-
logger.logErrorResponse(error, "Complex operation failed", {
|
|
386
|
-
operationId: id,
|
|
387
|
-
parameters: params
|
|
388
|
-
});
|
|
389
|
-
}
|
|
390
|
-
```
|
|
391
|
-
|
|
392
|
-
4. **Используйте соответствующие уровни логирования**
|
|
393
|
-
|
|
394
|
-
```typescript
|
|
395
|
-
// Debug для детальной информации
|
|
396
|
-
logger.debug("Processing chunk", {chunkId: 123, size: 1024});
|
|
397
|
-
|
|
398
|
-
// Info для общей информации о работе
|
|
399
|
-
logger.info("User logged in", {userId: 456});
|
|
400
|
-
|
|
401
|
-
// Warn для потенциальных проблем
|
|
402
|
-
logger.warn("High memory usage", {memoryUsage: "85%"});
|
|
403
|
-
|
|
404
|
-
// Error для реальных проблем
|
|
405
|
-
logger.error("Database connection failed", {dbHost: "primary"});
|
|
406
|
-
|
|
407
|
-
// Fatal для критических проблем
|
|
408
|
-
logger.fatal("System shutdown required", {reason: "data corruption"});
|
|
409
|
-
```
|
|
410
|
-
|
|
411
358
|
### 6. GreenApiClient
|
|
412
359
|
|
|
413
360
|
Прямой интерфейс к методам GREEN-API.
|
|
414
361
|
|
|
415
362
|
```typescript
|
|
416
363
|
const client = new GreenApiClient({
|
|
417
|
-
|
|
418
|
-
|
|
364
|
+
idInstance: 'your_instance_id',
|
|
365
|
+
apiTokenInstance: 'your_token'
|
|
419
366
|
});
|
|
420
367
|
|
|
421
|
-
// Примеры
|
|
368
|
+
// Примеры вызовы методов:
|
|
422
369
|
await client.setProfilePicture(fileBlob);
|
|
423
370
|
await client.getAuthorizationCode(phoneNumber);
|
|
424
371
|
await client.getQR();
|
|
@@ -480,17 +427,17 @@ graph TB
|
|
|
480
427
|
```typescript
|
|
481
428
|
// types/types.ts
|
|
482
429
|
export interface YourPlatformWebhook {
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
430
|
+
id: string;
|
|
431
|
+
from: string;
|
|
432
|
+
message: string;
|
|
433
|
+
timestamp: number;
|
|
434
|
+
// Добавьте другие поля, специфичные для вашей платформы
|
|
488
435
|
}
|
|
489
436
|
|
|
490
437
|
export interface YourPlatformMessage {
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
438
|
+
recipient: string;
|
|
439
|
+
content: string;
|
|
440
|
+
// Добавьте другие поля, специфичные для вашей платформы
|
|
494
441
|
}
|
|
495
442
|
```
|
|
496
443
|
|
|
@@ -504,22 +451,22 @@ import { MessageTransformer, Message, GreenApiWebhook } from '@green-api/greenap
|
|
|
504
451
|
import { YourPlatformWebhook, YourPlatformMessage } from '../types/types';
|
|
505
452
|
|
|
506
453
|
export class YourTransformer extends MessageTransformer<YourPlatformWebhook, YourPlatformMessage> {
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
454
|
+
toPlatformMessage(webhook: GreenApiWebhook): YourPlatformMessage {
|
|
455
|
+
// Преобразование вебхука GREEN-API в формат вашей платформы
|
|
456
|
+
return {
|
|
457
|
+
recipient: webhook.senderData.sender,
|
|
458
|
+
content: webhook.messageData.textMessageData?.textMessage || '',
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
toGreenApiMessage(message: YourPlatformWebhook): Message {
|
|
463
|
+
// Преобразование вебхука вашей платформы в формат GREEN-API
|
|
464
|
+
return {
|
|
465
|
+
type: 'text',
|
|
466
|
+
chatId: message.from,
|
|
467
|
+
message: message.message,
|
|
468
|
+
};
|
|
469
|
+
}
|
|
523
470
|
}
|
|
524
471
|
```
|
|
525
472
|
|
|
@@ -534,24 +481,24 @@ 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
|
-
|
|
484
|
+
private db: PrismaClient;
|
|
485
|
+
|
|
486
|
+
constructor() {
|
|
487
|
+
this.db = new PrismaClient();
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
async createInstance(instance: Instance, userId: bigint) {
|
|
491
|
+
return this.db.instance.create({
|
|
492
|
+
data: {
|
|
493
|
+
idInstance: instance.idInstance,
|
|
494
|
+
apiTokenInstance: instance.apiTokenInstance,
|
|
495
|
+
userId,
|
|
496
|
+
settings: instance.settings || {},
|
|
497
|
+
},
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// Остальные методы
|
|
555
502
|
}
|
|
556
503
|
```
|
|
557
504
|
|
|
@@ -566,17 +513,17 @@ import { YourPlatformClient } from 'your-platform-sdk';
|
|
|
566
513
|
import { YourPlatformWebhook, YourPlatformMessage } from '../types/types';
|
|
567
514
|
|
|
568
515
|
export class YourAdapter extends BaseAdapter<YourPlatformWebhook, YourPlatformMessage> {
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
516
|
+
async createPlatformClient(config: { apiKey: string, apiUrl: string }) {
|
|
517
|
+
return new YourPlatformClient({
|
|
518
|
+
baseUrl: config.apiUrl,
|
|
519
|
+
apiKey: config.apiKey,
|
|
520
|
+
});
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
async sendToPlatform(message: YourPlatformMessage, instance: Instance) {
|
|
524
|
+
const client = await this.createPlatformClient(instance.config);
|
|
525
|
+
await client.sendMessage(message);
|
|
526
|
+
}
|
|
580
527
|
}
|
|
581
528
|
```
|
|
582
529
|
|
|
@@ -597,72 +544,72 @@ const transformer = new YourTransformer();
|
|
|
597
544
|
const adapter = new YourAdapter(transformer, storage);
|
|
598
545
|
|
|
599
546
|
class WebhookGuard extends BaseGreenApiAuthGuard {
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
547
|
+
constructor(storage: StorageProvider) {
|
|
548
|
+
super(storage);
|
|
549
|
+
}
|
|
603
550
|
}
|
|
604
551
|
|
|
605
552
|
const guard = new WebhookGuard(storage);
|
|
606
553
|
|
|
607
554
|
// Эндпоинты для вебхуков
|
|
608
555
|
router.post('/green-api', async (req, res) => {
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
556
|
+
try {
|
|
557
|
+
// Проверка вебхука
|
|
558
|
+
await guard.validateRequest(req);
|
|
559
|
+
|
|
560
|
+
// Обработка вебхука после проверки.
|
|
561
|
+
// В списке вторым параметром укажите типы вебхуков, которые необходимо обработать
|
|
562
|
+
await adapter.handleGreenApiWebhook(req.body, ['incomingMessageReceived']);
|
|
563
|
+
res.status(200).json({status: 'ok'});
|
|
564
|
+
} catch (error) {
|
|
565
|
+
if (error instanceof AuthenticationError) {
|
|
566
|
+
res.status(401).json({error: 'Ошибка аутентификации'});
|
|
567
|
+
return;
|
|
568
|
+
}
|
|
569
|
+
console.error('Ошибка обработки вебхука:', error);
|
|
570
|
+
res.status(500).json({error: 'Внутренняя ошибка сервера'});
|
|
571
|
+
}
|
|
625
572
|
});
|
|
626
573
|
|
|
627
574
|
router.post('/platform', async (req, res) => {
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
575
|
+
try {
|
|
576
|
+
const instanceId = req.query.instanceId;
|
|
577
|
+
await adapter.handlePlatformWebhook(req.body, instanceId);
|
|
578
|
+
res.status(200).json({status: 'ok'});
|
|
579
|
+
} catch (error) {
|
|
580
|
+
console.error('Ошибка обработки вебхука платформы:', error);
|
|
581
|
+
res.status(500).json({error: 'Внутренняя ошибка сервера'});
|
|
582
|
+
}
|
|
636
583
|
});
|
|
637
584
|
|
|
638
585
|
router.post('/instance', async (req, res) => {
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
586
|
+
try {
|
|
587
|
+
const {idInstance, apiTokenInstance, userEmail} = req.body;
|
|
588
|
+
|
|
589
|
+
if (!idInstance || !apiTokenInstance || !userEmail) {
|
|
590
|
+
throw new BadRequestError('Отсутствуют обязательные поля');
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
const instance = await adapter.createInstance({
|
|
594
|
+
idInstance: Number(idInstance),
|
|
595
|
+
apiTokenInstance,
|
|
596
|
+
settings: {
|
|
597
|
+
webhookUrl: `${process.env.APP_URL}/webhook/green-api`,
|
|
598
|
+
webhookUrlToken: `token_${Date.now()}`,
|
|
599
|
+
incomingWebhook: 'yes'
|
|
600
|
+
}
|
|
601
|
+
}, userEmail);
|
|
602
|
+
|
|
603
|
+
res.status(200).json({
|
|
604
|
+
status: 'ok',
|
|
605
|
+
data: instance,
|
|
606
|
+
message: 'Инстанс успешно создан. Подождите 2 минуты для применения настроек.'
|
|
607
|
+
});
|
|
608
|
+
|
|
609
|
+
} catch (error) {
|
|
610
|
+
console.error('Ошибка создания инстанса:', error);
|
|
611
|
+
res.status(500).json({error: 'Не удалось создать инстанс'});
|
|
612
|
+
}
|
|
666
613
|
});
|
|
667
614
|
|
|
668
615
|
export default router;
|
|
@@ -686,25 +633,25 @@ import { YourStorage } from './core/storage';
|
|
|
686
633
|
dotenv.config();
|
|
687
634
|
|
|
688
635
|
async function bootstrap() {
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
636
|
+
// Инициализация компонентов
|
|
637
|
+
const storage = new YourStorage();
|
|
638
|
+
const transformer = new YourTransformer();
|
|
639
|
+
const adapter = new YourAdapter(transformer, storage);
|
|
693
640
|
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
641
|
+
// Создание Express приложения
|
|
642
|
+
const app = express();
|
|
643
|
+
app.use(bodyParser.json());
|
|
697
644
|
|
|
698
|
-
|
|
699
|
-
|
|
645
|
+
// Настройка маршрутов для вебхуков
|
|
646
|
+
app.use('/webhook', webhookRouter);
|
|
700
647
|
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
648
|
+
// Запуск сервера
|
|
649
|
+
const port = process.env.PORT || 3000;
|
|
650
|
+
app.listen(port, () => {
|
|
651
|
+
console.log(`Сервер запущен на порту ${port}`);
|
|
652
|
+
});
|
|
706
653
|
|
|
707
|
-
|
|
654
|
+
console.log('Интеграционная платформа готова!');
|
|
708
655
|
}
|
|
709
656
|
|
|
710
657
|
// Обработка ошибок
|
|
@@ -720,10 +667,10 @@ import { AppModule } from './app.module';
|
|
|
720
667
|
import helmet from 'helmet';
|
|
721
668
|
|
|
722
669
|
async function bootstrap() {
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
670
|
+
const app = await NestFactory.create(AppModule);
|
|
671
|
+
app.setGlobalPrefix('api');
|
|
672
|
+
app.use(helmet());
|
|
673
|
+
await app.listen(process.env.PORT ?? 3000);
|
|
727
674
|
}
|
|
728
675
|
|
|
729
676
|
bootstrap();
|
|
@@ -735,20 +682,20 @@ bootstrap();
|
|
|
735
682
|
|
|
736
683
|
```json
|
|
737
684
|
{
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
685
|
+
"name": "greenapi-integration-yourplatform",
|
|
686
|
+
"version": "1.0.0",
|
|
687
|
+
"main": "dist/index.js",
|
|
688
|
+
"types": "dist/index.d.ts",
|
|
689
|
+
"scripts": {
|
|
690
|
+
"build": "tsc",
|
|
691
|
+
"prepublishOnly": "npm run build"
|
|
692
|
+
},
|
|
693
|
+
"dependencies": {
|
|
694
|
+
"@green-api/greenapi-integration": "^0.4.0",
|
|
695
|
+
"@prisma/client": "^5.0.0",
|
|
696
|
+
"express": "^4.18.2"
|
|
697
|
+
// другие зависимости
|
|
698
|
+
}
|
|
752
699
|
}
|
|
753
700
|
```
|
|
754
701
|
|
|
@@ -814,16 +761,16 @@ examples/
|
|
|
814
761
|
|
|
815
762
|
```typescript
|
|
816
763
|
interface SimplePlatformWebhook {
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
764
|
+
messageId: string;
|
|
765
|
+
from: string;
|
|
766
|
+
text: string;
|
|
767
|
+
timestamp: number;
|
|
821
768
|
}
|
|
822
769
|
|
|
823
770
|
interface SimplePlatformMessage {
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
771
|
+
to: string;
|
|
772
|
+
content: string;
|
|
773
|
+
replyTo?: string;
|
|
827
774
|
}
|
|
828
775
|
```
|
|
829
776
|
|
|
@@ -831,35 +778,35 @@ interface SimplePlatformMessage {
|
|
|
831
778
|
|
|
832
779
|
```typescript
|
|
833
780
|
import {
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
781
|
+
MessageTransformer,
|
|
782
|
+
Message,
|
|
783
|
+
GreenApiWebhook,
|
|
784
|
+
formatPhoneNumber,
|
|
785
|
+
IntegrationError
|
|
839
786
|
} from '@green-api/greenapi-integration';
|
|
840
787
|
|
|
841
788
|
export class SimpleTransformer extends MessageTransformer<SimplePlatformWebhook, SimplePlatformMessage> {
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
789
|
+
toPlatformMessage(webhook: GreenApiWebhook): SimplePlatformMessage {
|
|
790
|
+
if (webhook.typeWebhook === "incomingMessageReceived") {
|
|
791
|
+
if (webhook.messageData.typeMessage !== "extendedTextMessage") {
|
|
792
|
+
throw new IntegrationError("Поддерживаются только текстовые сообщения", "BAD_REQUEST_ERROR", 400);
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
return {
|
|
796
|
+
to: webhook.senderData.sender,
|
|
797
|
+
content: webhook.messageData.extendedTextMessageData?.text || "",
|
|
798
|
+
};
|
|
799
|
+
}
|
|
800
|
+
throw new IntegrationError("Поддерживаются только вебхуки вида incomingMessageReceived", "INTEGRATION_ERROR", 500);
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
toGreenApiMessage(message: SimplePlatformWebhook): Message {
|
|
804
|
+
return {
|
|
805
|
+
type: 'text',
|
|
806
|
+
chatId: formatPhoneNumber(message.from),
|
|
807
|
+
message: message.text,
|
|
808
|
+
};
|
|
809
|
+
}
|
|
863
810
|
}
|
|
864
811
|
```
|
|
865
812
|
|
|
@@ -869,44 +816,44 @@ export class SimpleTransformer extends MessageTransformer<SimplePlatformWebhook,
|
|
|
869
816
|
import { StorageProvider, BaseUser, Instance, Settings } from '@green-api/greenapi-integration';
|
|
870
817
|
|
|
871
818
|
export class SimpleStorage extends StorageProvider {
|
|
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
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
819
|
+
private users: Map<string, BaseUser> = new Map();
|
|
820
|
+
private instances: Map<number, Instance> = new Map();
|
|
821
|
+
|
|
822
|
+
async createInstance(instance: Instance, userId: bigint): Promise<Instance> {
|
|
823
|
+
this.instances.set(Number(instance.idInstance), {
|
|
824
|
+
...instance,
|
|
825
|
+
});
|
|
826
|
+
return instance;
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
async getInstance(idInstance: number): Promise<Instance | null> {
|
|
830
|
+
return this.instances.get(idInstance) || null;
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
async removeInstance(instanceId: number): Promise<Instance> {
|
|
834
|
+
const instance = this.instances.get(instanceId);
|
|
835
|
+
if (!instance) throw new Error('Инстанс не найден');
|
|
836
|
+
this.instances.delete(instanceId);
|
|
837
|
+
return instance;
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
async createUser(data: any): Promise<BaseUser> {
|
|
841
|
+
const user = {id: Date.now(), ...data};
|
|
842
|
+
this.users.set(data.email, user);
|
|
843
|
+
return user;
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
async findUser(identifier: string): Promise<BaseUser | null> {
|
|
847
|
+
return this.users.get(identifier) || null;
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
async updateUser(identifier: string, data: any): Promise<BaseUser> {
|
|
851
|
+
const user = await this.findUser(identifier);
|
|
852
|
+
if (!user) throw new Error('Пользователь не найден');
|
|
853
|
+
const updated = {...user, ...data};
|
|
854
|
+
this.users.set(identifier, updated);
|
|
855
|
+
return updated;
|
|
856
|
+
}
|
|
910
857
|
}
|
|
911
858
|
```
|
|
912
859
|
|
|
@@ -917,38 +864,38 @@ import { BaseAdapter, Instance } from "@green-api/greenapi-integration";
|
|
|
917
864
|
import axios from 'axios';
|
|
918
865
|
|
|
919
866
|
export class SimpleAdapter extends BaseAdapter<SimplePlatformWebhook, SimplePlatformMessage> {
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
867
|
+
async createPlatformClient(config: { apiKey: string, apiUrl: string }) {
|
|
868
|
+
return axios.create({
|
|
869
|
+
baseURL: config.apiUrl,
|
|
870
|
+
headers: {
|
|
871
|
+
'Authorization': `Bearer ${config.apiKey}`,
|
|
872
|
+
'Content-Type': 'application/json'
|
|
873
|
+
}
|
|
874
|
+
});
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
async sendToPlatform(message: SimplePlatformMessage, instance: Instance): Promise<void> {
|
|
878
|
+
// В реальной реализации мы бы отправляли сообщение на платформу
|
|
879
|
+
// Для демонстрации просто логируем и симулируем ответ
|
|
880
|
+
console.log('Платформа получила сообщение:', message);
|
|
881
|
+
|
|
882
|
+
// Симулируем обработку и ответ платформы
|
|
883
|
+
setTimeout(() => {
|
|
884
|
+
console.log('Обработка платформой завершена, отправляем ответ...');
|
|
885
|
+
this.simulatePlatformResponse(message, instance.idInstance);
|
|
886
|
+
}, 1000);
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
private async simulatePlatformResponse(originalMessage: SimplePlatformMessage, idInstance: number | bigint) {
|
|
890
|
+
const platformWebhook: SimplePlatformWebhook = {
|
|
891
|
+
messageId: `resp_${Date.now()}`,
|
|
892
|
+
from: originalMessage.to.replace('@c.us', ''),
|
|
893
|
+
text: `Спасибо за ваше сообщение: "${originalMessage.content}". Это автоматический ответ.`,
|
|
894
|
+
timestamp: Date.now()
|
|
895
|
+
};
|
|
896
|
+
|
|
897
|
+
await this.handlePlatformWebhook(platformWebhook, idInstance);
|
|
898
|
+
}
|
|
952
899
|
}
|
|
953
900
|
```
|
|
954
901
|
|
|
@@ -966,92 +913,80 @@ import * as dotenv from "dotenv";
|
|
|
966
913
|
dotenv.config();
|
|
967
914
|
|
|
968
915
|
async function main() {
|
|
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
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
916
|
+
// Инициализация компонентов
|
|
917
|
+
const transformer = new SimpleTransformer();
|
|
918
|
+
const storage = new SimpleStorage();
|
|
919
|
+
const adapter = new SimpleAdapter(transformer, storage);
|
|
920
|
+
|
|
921
|
+
// Конфигурация обоих инстансов
|
|
922
|
+
const visitorInstance = {
|
|
923
|
+
idInstance: Number(process.env.VISITOR_ID_INSTANCE),
|
|
924
|
+
apiTokenInstance: process.env.VISITOR_API_TOKEN!,
|
|
925
|
+
};
|
|
926
|
+
|
|
927
|
+
const agentInstance = {
|
|
928
|
+
idInstance: Number(process.env.AGENT_ID_INSTANCE),
|
|
929
|
+
apiTokenInstance: process.env.AGENT_API_TOKEN!,
|
|
930
|
+
};
|
|
931
|
+
|
|
932
|
+
// Создание клиента GREEN-API для посетителя (для отправки начального сообщения)
|
|
933
|
+
const visitorClient = new GreenApiClient(visitorInstance);
|
|
934
|
+
|
|
935
|
+
// Настройка инстанса агента
|
|
936
|
+
console.log("Настройка инстанса агента...");
|
|
937
|
+
const user = await adapter.createUser("agent@example.com", {
|
|
938
|
+
email: "agent@example.com",
|
|
939
|
+
name: "Agent",
|
|
940
|
+
});
|
|
941
|
+
|
|
942
|
+
const instance = await adapter.createInstance({
|
|
943
|
+
idInstance: agentInstance.idInstance, apiTokenInstance: agentInstance.apiTokenInstance, settings: {
|
|
944
|
+
webhookUrl: process.env.WEBHOOK_URL + "/webhook/green-api",
|
|
945
|
+
webhookUrlToken: "your-secure-token",
|
|
946
|
+
incomingWebhook: "yes",
|
|
947
|
+
},
|
|
948
|
+
}, user.email);
|
|
949
|
+
|
|
950
|
+
console.log("Ожидание 2 минуты для применения настроек...");
|
|
951
|
+
await new Promise(resolve => setTimeout(resolve, 120000));
|
|
952
|
+
console.log("Инстанс готов!");
|
|
953
|
+
|
|
954
|
+
// Настройка веб-сервера
|
|
955
|
+
const app = express();
|
|
956
|
+
app.use(bodyParser.json());
|
|
957
|
+
|
|
958
|
+
// Обработка вебхуков от GREEN-API
|
|
959
|
+
app.post("/webhook/green-api", async (req, res) => {
|
|
960
|
+
try {
|
|
961
|
+
console.log("Получен вебхук от GREEN-API:", req.body);
|
|
962
|
+
await adapter.handleGreenApiWebhook(req.body, ["incomingMessageReceived"]);
|
|
963
|
+
res.status(200).json({status: "ok"});
|
|
964
|
+
} catch (error) {
|
|
965
|
+
console.error("Ошибка обработки вебхука:", error);
|
|
966
|
+
res.status(500).json({error: "Внутренняя ошибка сервера"});
|
|
967
|
+
}
|
|
968
|
+
});
|
|
969
|
+
|
|
970
|
+
// Запуск сервера
|
|
971
|
+
const port = Number(process.env.PORT) || 3000;
|
|
972
|
+
app.listen(port, () => {
|
|
973
|
+
console.log(`Сервер вебхуков запущен на порту ${port}`);
|
|
974
|
+
});
|
|
975
|
+
|
|
976
|
+
// Отправка начального сообщения от посетителя
|
|
977
|
+
console.log("Отправка начального сообщения от посетителя...");
|
|
978
|
+
await visitorClient.sendMessage({
|
|
979
|
+
chatId: formatPhoneNumber(process.env.AGENT_PHONE_NUMBER!),
|
|
980
|
+
message: "Здравствуйте! Это тестовое сообщение от посетителя.",
|
|
981
|
+
type: "text",
|
|
982
|
+
});
|
|
983
|
+
|
|
984
|
+
console.log("Начальное сообщение отправлено! Проверьте WhatsApp агента для просмотра ответа.");
|
|
1038
985
|
}
|
|
1039
|
-
|
|
986
|
+
|
|
1040
987
|
main().catch(console.error);
|
|
1041
988
|
```
|
|
1042
989
|
|
|
1043
|
-
### .env
|
|
1044
|
-
|
|
1045
|
-
```env
|
|
1046
|
-
VISITOR_ID_INSTANCE=your_visitor_instance_id
|
|
1047
|
-
VISITOR_API_TOKEN=your_visitor_instance_token
|
|
1048
|
-
AGENT_ID_INSTANCE=your_agent_instance_id
|
|
1049
|
-
AGENT_API_TOKEN=your_agent_instance_token
|
|
1050
|
-
AGENT_PHONE_NUMBER=your_agent_phone_number
|
|
1051
|
-
WEBHOOK_URL=your_webhook_url
|
|
1052
|
-
PORT=3000
|
|
1053
|
-
```
|
|
1054
|
-
|
|
1055
990
|
## Реальные примеры
|
|
1056
991
|
|
|
1057
992
|
Для полных примеров реальных интеграций, смотрите:
|
|
@@ -1078,10 +1013,10 @@ isValidSettingValue('webhookUrl', 'https://example.com') // Возвращает
|
|
|
1078
1013
|
|
|
1079
1014
|
// Очистка настроек
|
|
1080
1015
|
const input = {
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1016
|
+
webhookUrl: 'https://example.com',
|
|
1017
|
+
outgoingWebhook: 'yes',
|
|
1018
|
+
invalidKey: 'value',
|
|
1019
|
+
delaySendMessagesMilliseconds: 'invalid'
|
|
1085
1020
|
}
|
|
1086
1021
|
validateAndCleanSettings(input) // Возвращает { webhookUrl: 'https://example.com', outgoingWebhook: 'yes' }
|
|
1087
1022
|
```
|
|
@@ -1089,4 +1024,3 @@ validateAndCleanSettings(input) // Возвращает { webhookUrl: 'https://e
|
|
|
1089
1024
|
## Лицензия
|
|
1090
1025
|
|
|
1091
1026
|
MIT
|
|
1092
|
-
]()
|