Skip to content

Commit afbe727

Browse files
committed
add: connection with code
endpoint: `GET /instance/connect/:instanceName/code/:phoneNumber`
1 parent 4519ef0 commit afbe727

6 files changed

Lines changed: 114 additions & 146 deletions

File tree

.vscode/settings.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,6 @@
88
"source.fixAll.eslint": "explicit",
99
"source.fixAll": "explicit"
1010
},
11-
"postman.settings.dotenv-detection-notification-visibility": false
11+
"postman.settings.dotenv-detection-notification-visibility": false,
12+
"prisma.pinToPrisma6": true
1213
}

src/validate/router.validate.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,6 @@ export async function dataValidate<T>(args: DataValidate<T>) {
9999
message,
100100
};
101101
});
102-
logger.error([...message]);
103102
throw new BadRequestException(...message);
104103
}
105104

src/whatsapp/controllers/instance.controller.ts

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,13 +49,14 @@ import {
4949
import { InstanceDto } from '../dto/instance.dto';
5050
import { WAMonitoringService } from '../services/monitor.service';
5151
import { Logger } from '../../config/logger.config';
52-
import { Request } from 'express';
52+
import { Request, Response } from 'express';
5353
import { Repository } from '../../repository/repository.service';
5454
import { InstanceService, OldToken } from '../services/instance.service';
5555
import { WAStartupService } from '../services/whatsapp.service';
5656
import { isString } from 'class-validator';
5757
import { ProviderFiles } from '../../provider/sessions';
5858
import { Websocket } from '../../websocket/server';
59+
import { HttpStatus } from '../../app.module';
5960

6061
export class InstanceController {
6162
constructor(
@@ -148,6 +149,64 @@ export class InstanceController {
148149
}
149150
}
150151

152+
public async connectToWhatsappWitchCode(
153+
{ instanceName, phoneNumber }: InstanceDto,
154+
res: Response,
155+
) {
156+
if (!phoneNumber) {
157+
throw new BadRequestException('phoneNumber required');
158+
}
159+
160+
const find = await this.repository.instance.findUnique({
161+
where: { name: instanceName },
162+
});
163+
164+
if (!find) {
165+
throw new NotFoundException('Instance not found');
166+
}
167+
168+
try {
169+
let instance: WAStartupService;
170+
instance = this.waMonitor.waInstances.get(instanceName);
171+
const info = instance?.getInstance();
172+
if (info?.status.state === 'open') {
173+
throw new Error('Instance already connected');
174+
}
175+
176+
const state = info?.status.state || 'close';
177+
178+
if (!instance || !info?.status || info?.status?.state === 'refused') {
179+
instance = new WAStartupService(
180+
this.configService,
181+
this.eventEmitter,
182+
this.repository,
183+
this.providerFiles,
184+
this.ws,
185+
);
186+
await instance.setInstanceName(instanceName);
187+
this.waMonitor.addInstance(instanceName, instance);
188+
}
189+
190+
switch (state) {
191+
case 'close':
192+
await instance.setPhoneNumber(phoneNumber);
193+
await instance.connectToWhatsapp();
194+
this.eventEmitter.once('code.connection', (data: { code: string }) => {
195+
res.status(HttpStatus.OK).json(data);
196+
});
197+
break;
198+
case 'connecting':
199+
res.status(HttpStatus.OK).json(instance.qrCode);
200+
break;
201+
default:
202+
res.status(HttpStatus.OK).json(info?.status || {});
203+
}
204+
} catch (error) {
205+
this.logger.error(error);
206+
throw new BadRequestException(error?.message);
207+
}
208+
}
209+
151210
public async updateInstance(instance: InstanceDto) {
152211
try {
153212
const instanceData = await this.instanceService.updateInstance(instance);

src/whatsapp/dto/instance.dto.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,5 @@ export class InstanceDto {
3636
instanceName: string;
3737
description?: string;
3838
externalAttributes?: any;
39+
phoneNumber?: string;
3940
}

src/whatsapp/routers/instance.router.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,14 @@ export function InstanceRouter(
6565

6666
res.status(HttpStatus.OK).json(response);
6767
})
68+
.get(routerPath('connect') + '/code/:phoneNumber', async (req, res) => {
69+
await dataValidate<InstanceDto>({
70+
request: req,
71+
schema: instanceNameSchema,
72+
execute: (instance) =>
73+
instanceController.connectToWhatsappWitchCode(instance, res),
74+
});
75+
})
6876
.get(routerPath('connectionState'), ...guards, async (req, res) => {
6977
const response = await dataValidate<InstanceDto>({
7078
request: req,

src/whatsapp/services/whatsapp.service.ts

Lines changed: 43 additions & 143 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
import makeWASocket, {
4141
AnyMessageContent,
4242
BaileysEventMap,
43+
Browsers,
4344
BufferedEventData,
4445
CacheStore,
4546
Chat,
@@ -148,6 +149,7 @@ import {
148149
constants,
149150
existsSync,
150151
readFileSync,
152+
rmSync,
151153
unlinkSync,
152154
writeFileSync,
153155
} from 'fs';
@@ -162,7 +164,7 @@ type InstanceQrCode = {
162164
};
163165

164166
type InstanceStateConnection = {
165-
state: 'open' | 'close' | 'refused' | WAConnectionState;
167+
state: 'refused' | WAConnectionState;
166168
statusReason?: number;
167169
};
168170

@@ -194,6 +196,20 @@ export class WAStartupService {
194196
public client: WASocket;
195197
private authState: Partial<AuthState> = {};
196198
private authStateProvider: AuthStateProvider;
199+
private phoneNumber: string;
200+
201+
public async setPhoneNumber(v: string) {
202+
this.phoneNumber = v;
203+
try {
204+
if (this.configService.get<ProviderSession>('PROVIDER')?.ENABLED) {
205+
await this.providerFiles.removeSession(this.instance.name);
206+
} else {
207+
rmSync(join(INSTANCE_DIR, this.instance.name));
208+
}
209+
} catch {
210+
//
211+
}
212+
}
197213

198214
public async setInstanceName(name: string) {
199215
const i = await this.repository.instance.findUnique({
@@ -399,14 +415,21 @@ export class WAStartupService {
399415
color: { light: qrCodeOptions.LIGHT_COLOR, dark: qrCodeOptions.DARK_COLOR },
400416
};
401417

418+
let code: string;
419+
420+
if (this.phoneNumber && !this.client?.authState?.creds?.registered) {
421+
code = await this.client.requestPairingCode(this.phoneNumber);
422+
this.eventEmitter.emit('code.connection', { code });
423+
}
424+
402425
qrcode.toDataURL(qr, optsQrcode, (error, base64) => {
403426
if (error) {
404427
this.logger.error('Qrcode generate failed:' + error.toString());
405428
return;
406429
}
407430

408431
this.instanceQr.base64 = base64;
409-
this.instanceQr.code = qr;
432+
this.instanceQr.code = code ?? qr;
410433

411434
this.ws.send(this.instance.name, 'qrcode.updated', { code: qr, base64 });
412435

@@ -417,8 +440,15 @@ export class WAStartupService {
417440

418441
qrcodeTerminal.generate(qr, { small: true }, (qrcode) =>
419442
this.logger.log(
420-
`\n{ instance: ${this.instance.name}, qrcodeCount: ${this.instanceQr.count} }\n` +
421-
qrcode,
443+
`\n${JSON.stringify(
444+
{
445+
instanceName: this.instance.name,
446+
qrCount: this.instanceQr.count,
447+
code: this.instanceQr.code ?? code,
448+
},
449+
null,
450+
2,
451+
)}\n` + qrcode,
422452
),
423453
);
424454
}
@@ -504,9 +534,7 @@ export class WAStartupService {
504534
}
505535

506536
private async defineAuthState() {
507-
const provider = this.configService.get<ProviderSession>('PROVIDER');
508-
509-
if (provider?.ENABLED) {
537+
if (this.configService.get<ProviderSession>('PROVIDER')?.ENABLED) {
510538
return await this.authStateProvider.authStateProvider(this.instance.name);
511539
}
512540

@@ -520,11 +548,17 @@ export class WAStartupService {
520548

521549
const { version } = await fetchLatestBaileysVersionV2();
522550
const session = this.configService.get<ConfigSessionPhone>('CONFIG_SESSION_PHONE');
523-
const browser: WABrowserDescription = [session.CLIENT, session.NAME, release()];
551+
const browser: WABrowserDescription = !this.phoneNumber
552+
? [session.CLIENT, session.NAME, release()]
553+
: Browsers.macOS('Chrome');
524554

525-
const { EXPIRATION_TIME } = this.configService.get<QrCode>('QRCODE');
555+
let { EXPIRATION_TIME } = this.configService.get<QrCode>('QRCODE');
526556
const CONNECTION_TIMEOUT = this.configService.get<number>('CONNECTION_TIMEOUT');
527557

558+
if (this.phoneNumber) {
559+
EXPIRATION_TIME = CONNECTION_TIMEOUT;
560+
}
561+
528562
const proxy = this.configService.get<EnvProxy>('PROXY');
529563
const agents = createProxyAgents(proxy?.WS, proxy?.FETCH);
530564

@@ -1874,140 +1908,6 @@ export class WAStartupService {
18741908
);
18751909
}
18761910

1877-
public async buttonsMessage(data: SendButtonsDto) {
1878-
const generate = await (async () => {
1879-
if (data.buttonsMessage?.thumbnailUrl) {
1880-
return await this.prepareMediaMessage({
1881-
mediatype: 'image',
1882-
media: data.buttonsMessage.thumbnailUrl,
1883-
});
1884-
}
1885-
})();
1886-
1887-
const message: proto.IMessage = {
1888-
viewOnceMessageV2: {
1889-
message: {
1890-
messageContextInfo: {
1891-
deviceListMetadata: {},
1892-
deviceListMetadataVersion: 2,
1893-
},
1894-
interactiveMessage: {
1895-
body: {
1896-
text: (() => {
1897-
let t = '*' + data.buttonsMessage.title + '*';
1898-
if (data.buttonsMessage?.description) {
1899-
t += '\n\n';
1900-
t += data.buttonsMessage.description;
1901-
t += '\n';
1902-
}
1903-
return t;
1904-
})(),
1905-
},
1906-
footer: {
1907-
text: data.buttonsMessage?.footer,
1908-
},
1909-
header: (() => {
1910-
if (generate?.message?.imageMessage) {
1911-
return {
1912-
hasMediaAttachment: !!generate.message.imageMessage,
1913-
imageMessage: generate.message.imageMessage,
1914-
};
1915-
}
1916-
})(),
1917-
nativeFlowMessage: {
1918-
buttons: data.buttonsMessage.buttons.map((value) => {
1919-
return {
1920-
name: value.typeButton,
1921-
buttonParamsJson: value.toJSONString(),
1922-
};
1923-
}),
1924-
messageParamsJson: JSON.stringify({
1925-
from: 'api',
1926-
templateId: ulid(Date.now()),
1927-
}),
1928-
},
1929-
},
1930-
},
1931-
},
1932-
};
1933-
1934-
return await this.sendMessageWithTyping(data.number, message, data?.options);
1935-
}
1936-
1937-
public async listButtons(data: SendListDto) {
1938-
const generate = await (async () => {
1939-
if (data.listMessage?.thumbnailUrl) {
1940-
return await this.prepareMediaMessage({
1941-
mediatype: 'image',
1942-
media: data.listMessage.thumbnailUrl,
1943-
});
1944-
}
1945-
})();
1946-
1947-
const message: proto.IMessage = {
1948-
viewOnceMessageV2: {
1949-
message: {
1950-
messageContextInfo: {
1951-
deviceListMetadata: {},
1952-
deviceListMetadataVersion: 2,
1953-
},
1954-
interactiveMessage: {
1955-
body: {
1956-
text: (() => {
1957-
let t = '*' + data.listMessage.title + '*';
1958-
if (data.listMessage?.description) {
1959-
t += '\n\n';
1960-
t += data.listMessage.description;
1961-
t += '\n';
1962-
}
1963-
return t;
1964-
})(),
1965-
},
1966-
footer: {
1967-
text: data.listMessage?.footer,
1968-
},
1969-
header: (() => {
1970-
if (generate?.message?.imageMessage) {
1971-
return {
1972-
hasMediaAttachment: !!generate.message.imageMessage,
1973-
imageMessage: generate.message.imageMessage,
1974-
};
1975-
}
1976-
})(),
1977-
nativeFlowMessage: {
1978-
buttons: data.listMessage.sections.map((value) => {
1979-
return {
1980-
name: 'single_select',
1981-
buttonParamsJson: value.toSectionsString(),
1982-
};
1983-
}),
1984-
},
1985-
},
1986-
},
1987-
},
1988-
};
1989-
1990-
return await this.sendMessageWithTyping(data.number, message, data?.options);
1991-
}
1992-
1993-
public async listLegacy(data: SendListLegacyDto) {
1994-
const msg = data.listMessage;
1995-
return await this.sendMessageWithTyping(data.number, {
1996-
viewOnceMessageV2: {
1997-
message: {
1998-
listMessage: {
1999-
title: msg.title,
2000-
description: msg?.description,
2001-
footerText: msg?.footer,
2002-
buttonText: msg.buttonText,
2003-
sections: msg.sections,
2004-
listType: proto.Message.ListMessage.ListType.SINGLE_SELECT,
2005-
},
2006-
},
2007-
},
2008-
});
2009-
}
2010-
20111911
public async linkMessage(data: SendLinkDto) {
20121912
return await this.sendMessageWithTyping(data.number, {
20131913
extendedTextMessage: {

0 commit comments

Comments
 (0)