feat(apps): add mexican phone number apps
This commit is contained in:
1 parent
0e73561b8e
commit
a48247c2d2
14 files changed
+250
-4
No files matched your search
@@ -4,6 +4,7 @@ import { BrazilianPhoneNumbersAppConfig } from '@waha/apps/brazilian-phone-numbe
|
||||
import { CallsAppConfig } from '@waha/apps/calls/dto/config.dto';
|
||||
import { ChatWootAppConfig } from '@waha/apps/chatwoot/dto/config.dto';
|
||||
import { McpAppConfig } from '@waha/apps/mcp/dto/config.dto';
|
||||
import { MexicanPhoneNumbersAppConfig } from '@waha/apps/mexican-phone-numbers/dto';
|
||||
import { PhoneNumbersAppConfig } from '@waha/apps/phone-numbers/dto/config.dto';
|
||||
|
||||
export enum AppName {
|
||||
@@ -12,6 +13,7 @@ export enum AppName {
|
||||
chatwoot = 'chatwoot',
|
||||
calls = 'calls',
|
||||
mcp = 'mcp',
|
||||
mexicanPhoneNumbers = 'mexican-phone-numbers',
|
||||
phoneNumbers = 'phone-numbers',
|
||||
}
|
||||
|
||||
@@ -27,6 +29,7 @@ export const AppConfigClasses: Record<AppName, Type<any>> = {
|
||||
[AppName.calls]: CallsAppConfig,
|
||||
[AppName.chatwoot]: ChatWootAppConfig,
|
||||
[AppName.mcp]: McpAppConfig,
|
||||
[AppName.mexicanPhoneNumbers]: MexicanPhoneNumbersAppConfig,
|
||||
};
|
||||
|
||||
export function GetAppConfigClass(name: AppName): Type<any> {
|
||||
|
||||
@@ -6,6 +6,7 @@ import { AppApiTag } from '@waha/apps/app_sdk/apps/openapi';
|
||||
import CallsAppModule from '@waha/apps/calls/app.module';
|
||||
import ChatWootAppModule from '@waha/apps/chatwoot/app.module';
|
||||
import McpAppModule from '@waha/apps/mcp/app.module';
|
||||
import MexicanPhoneNumbersAppModule from '@waha/apps/mexican-phone-numbers/app.module';
|
||||
import PhoneNumbersAppModule from '@waha/apps/phone-numbers/app.module';
|
||||
|
||||
/**
|
||||
@@ -18,6 +19,7 @@ const APPS: AppModule[] = [
|
||||
CallsAppModule,
|
||||
ChatWootAppModule,
|
||||
McpAppModule,
|
||||
MexicanPhoneNumbersAppModule,
|
||||
PhoneNumbersAppModule,
|
||||
];
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import { AppName } from '@waha/apps/app_sdk/apps/apps';
|
||||
import { ArgentinePhoneNumbersController } from '@waha/apps/argentine-phone-numbers/controller';
|
||||
import { ArgentinePhoneNumbersAppService } from '@waha/apps/argentine-phone-numbers/services/ArgentinePhoneNumbersAppService';
|
||||
|
||||
const argentinephonenumbersAppModule: AppModule = {
|
||||
const ArgentinePhoneNumbersAppModule: AppModule = {
|
||||
name: AppName.argentinePhoneNumbers,
|
||||
openapi: {
|
||||
title: 'Phone Numbers: Argentina',
|
||||
@@ -25,4 +25,4 @@ const argentinephonenumbersAppModule: AppModule = {
|
||||
Service: ArgentinePhoneNumbersAppService,
|
||||
};
|
||||
|
||||
export default argentinephonenumbersAppModule;
|
||||
export default ArgentinePhoneNumbersAppModule;
|
||||
@@ -3,7 +3,7 @@ import { AppName } from '@waha/apps/app_sdk/apps/apps';
|
||||
import { BrazilianPhoneNumbersController } from '@waha/apps/brazilian-phone-numbers/controller';
|
||||
import { BrazilianPhoneNumbersAppService } from '@waha/apps/brazilian-phone-numbers/services/BrazilianPhoneNumbersAppService';
|
||||
|
||||
const brazilianphonenumbersAppModule: AppModule = {
|
||||
const BrazilianPhoneNumbersAppModule: AppModule = {
|
||||
name: AppName.brazilianPhoneNumbers,
|
||||
openapi: {
|
||||
title: 'Phone Numbers: Brazil',
|
||||
@@ -24,4 +24,4 @@ const brazilianphonenumbersAppModule: AppModule = {
|
||||
Service: BrazilianPhoneNumbersAppService,
|
||||
};
|
||||
|
||||
export default brazilianphonenumbersAppModule;
|
||||
export default BrazilianPhoneNumbersAppModule;
|
||||
@@ -0,0 +1,28 @@
|
||||
import { AppModule } from '@waha/apps/app_sdk/apps/definition';
|
||||
import { AppName } from '@waha/apps/app_sdk/apps/apps';
|
||||
import { MexicanPhoneNumbersController } from '@waha/apps/mexican-phone-numbers/controller';
|
||||
import { MexicanPhoneNumbersAppService } from '@waha/apps/mexican-phone-numbers/services/MexicanPhoneNumbersAppService';
|
||||
|
||||
const MexicanPhoneNumbersAppModule: AppModule = {
|
||||
name: AppName.mexicanPhoneNumbers,
|
||||
openapi: {
|
||||
title: 'Phone Numbers: Mexico',
|
||||
description:
|
||||
'Resolve Mexican phone numbers (with and without the 1 after the country code)',
|
||||
},
|
||||
definition: {
|
||||
plainkey: false,
|
||||
queue: false,
|
||||
migrations: true,
|
||||
restartOnChange: true,
|
||||
unique: true,
|
||||
},
|
||||
nestjs: {
|
||||
imports: [],
|
||||
controllers: [MexicanPhoneNumbersController],
|
||||
providers: [MexicanPhoneNumbersAppService],
|
||||
},
|
||||
Service: MexicanPhoneNumbersAppService,
|
||||
};
|
||||
|
||||
export default MexicanPhoneNumbersAppModule;
|
||||
@@ -0,0 +1,21 @@
|
||||
import { Controller, UseGuards } from '@nestjs/common';
|
||||
import { ApiSecurity } from '@nestjs/swagger';
|
||||
import { AppName } from '@waha/apps/app_sdk/apps/apps';
|
||||
import { UniqueAppResolver } from '@waha/apps/app_sdk/services/UniqueAppResolver';
|
||||
import { MexicanPhoneNumbersAppService } from '@waha/apps/mexican-phone-numbers/services/MexicanPhoneNumbersAppService';
|
||||
import { PhoneNumbersCacheController } from '@waha/apps/phone-numbers/api/PhoneNumbersCacheController';
|
||||
import { SessionManager } from '@waha/core/abc/manager.abc';
|
||||
import { PoliciesGuard } from '@waha/core/auth/policies.guard';
|
||||
|
||||
@ApiSecurity('api_key')
|
||||
@Controller('api/apps/mexican-phone-numbers/:session')
|
||||
@UseGuards(PoliciesGuard)
|
||||
export class MexicanPhoneNumbersController extends PhoneNumbersCacheController {
|
||||
constructor(
|
||||
manager: SessionManager,
|
||||
resolver: UniqueAppResolver,
|
||||
appService: MexicanPhoneNumbersAppService,
|
||||
) {
|
||||
super(manager, resolver, appService, AppName.mexicanPhoneNumbers);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
import { PhoneNumbersBaseConfig } from '@waha/apps/phone-numbers/dto/config.dto';
|
||||
|
||||
export class MexicanPhoneNumbersAppConfig extends PhoneNumbersBaseConfig {}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { migrations_001_init_cache } from '@waha/apps/phone-numbers/storage/migrations_001_init_cache';
|
||||
|
||||
const migration = migrations_001_init_cache({
|
||||
table: 'app_mexican_phone_numbers_cache',
|
||||
index: 'mxphone',
|
||||
});
|
||||
|
||||
exports.up = migration.up;
|
||||
exports.down = migration.down;
|
||||
@@ -0,0 +1,106 @@
|
||||
import { UnprocessableEntityException } from '@nestjs/common';
|
||||
import { MexicanPhoneNumberRules } from '@waha/apps/mexican-phone-numbers/rules/MexicanPhoneNumberRules';
|
||||
import { PhoneNumbersBaseConfig } from '@waha/apps/phone-numbers/dto/config.dto';
|
||||
import {
|
||||
buildPlugin as buildPhoneNumbersPlugin,
|
||||
resolveChat,
|
||||
stubLookup,
|
||||
} from '@waha/apps/phone-numbers/plugins/testing';
|
||||
|
||||
function buildPlugin(config: Partial<PhoneNumbersBaseConfig> = {}) {
|
||||
return buildPhoneNumbersPlugin({
|
||||
config: config,
|
||||
rules: MexicanPhoneNumberRules(),
|
||||
});
|
||||
}
|
||||
|
||||
describe('MexicanPhoneNumbers', () => {
|
||||
it('uses the supplied form when it exists, without checking the other one', async () => {
|
||||
const { session } = buildPlugin();
|
||||
stubLookup(session, { numberExists: true, chatId: '525512345678@c.us' });
|
||||
|
||||
const resolved = await resolveChat(session, '+525512345678');
|
||||
|
||||
expect(resolved).toBe('525512345678@c.us');
|
||||
expect(session.checkNumberStatus).toHaveBeenCalledTimes(1);
|
||||
expect(session.checkNumberStatus).toHaveBeenCalledWith({
|
||||
phone: '525512345678',
|
||||
session: 'test',
|
||||
});
|
||||
});
|
||||
|
||||
it('tries the form with 1 after the supplied one is absent, keeps a LID answer', async () => {
|
||||
const { session } = buildPlugin();
|
||||
session.checkNumberStatus = jest
|
||||
.fn()
|
||||
.mockResolvedValueOnce({ numberExists: false })
|
||||
.mockResolvedValueOnce({ numberExists: true, chatId: '123456@lid' });
|
||||
|
||||
const resolved = await resolveChat(session, '525512345678@c.us');
|
||||
|
||||
expect(resolved).toBe('123456@lid');
|
||||
expect(session.checkNumberStatus).toHaveBeenLastCalledWith({
|
||||
phone: '5215512345678',
|
||||
session: 'test',
|
||||
});
|
||||
// Both forms hit the cache now
|
||||
expect(await resolveChat(session, '5215512345678@c.us')).toBe('123456@lid');
|
||||
expect(session.checkNumberStatus).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('tries the form without 1 as the fallback', async () => {
|
||||
const { session } = buildPlugin();
|
||||
session.checkNumberStatus = jest
|
||||
.fn()
|
||||
.mockResolvedValueOnce({ numberExists: false })
|
||||
.mockResolvedValueOnce({
|
||||
numberExists: true,
|
||||
chatId: '523312345678@c.us',
|
||||
});
|
||||
|
||||
const resolved = await resolveChat(session, '5213312345678@c.us');
|
||||
|
||||
expect(resolved).toBe('523312345678@c.us');
|
||||
});
|
||||
|
||||
it('prefers the pn when the engine answers with both pn and a LID', async () => {
|
||||
const { session } = buildPlugin();
|
||||
stubLookup(session, {
|
||||
numberExists: true,
|
||||
chatId: '123@lid',
|
||||
pn: '5215512345678@c.us',
|
||||
});
|
||||
|
||||
expect(await resolveChat(session, '525512345678@c.us')).toBe(
|
||||
'5215512345678@c.us',
|
||||
);
|
||||
});
|
||||
|
||||
it('soft mode keeps the supplied form when both are absent, strict rejects', async () => {
|
||||
const soft = buildPlugin();
|
||||
stubLookup(soft.session, { numberExists: false });
|
||||
expect(await resolveChat(soft.session, '525512345678@c.us')).toBe(
|
||||
'525512345678@c.us',
|
||||
);
|
||||
expect(soft.session.checkNumberStatus).toHaveBeenCalledTimes(2);
|
||||
|
||||
const strict = buildPlugin({ strict: true });
|
||||
stubLookup(strict.session, { numberExists: false });
|
||||
await expect(
|
||||
resolveChat(strict.session, '525512345678@c.us'),
|
||||
).rejects.toThrow(UnprocessableEntityException);
|
||||
});
|
||||
|
||||
it('leaves numbers without the country code and other countries untouched', async () => {
|
||||
const { session } = buildPlugin();
|
||||
stubLookup(session, { numberExists: false });
|
||||
|
||||
expect(await resolveChat(session, '5512345678@c.us')).toBe(
|
||||
'5512345678@c.us',
|
||||
);
|
||||
expect(await resolveChat(session, '5511912345678@c.us')).toBe(
|
||||
'5511912345678@c.us',
|
||||
);
|
||||
expect(session.checkNumberStatus).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,32 @@
|
||||
import { MexicanPhoneNumberRules } from './MexicanPhoneNumberRules';
|
||||
|
||||
describe('MexicanPhoneNumberRules', () => {
|
||||
const rules = MexicanPhoneNumberRules();
|
||||
|
||||
function resolve(digits: string) {
|
||||
const rule = rules.find((r) => r.matches(digits));
|
||||
return rule?.resolve(digits) ?? null;
|
||||
}
|
||||
|
||||
it.each([
|
||||
['525512345678', ['525512345678', '5215512345678']],
|
||||
['5215512345678', ['5215512345678', '525512345678']],
|
||||
['523312345678', ['523312345678', '5213312345678']],
|
||||
['5216641234567', ['5216641234567', '526641234567']],
|
||||
])('keeps the supplied form first for %s', (digits, expected) => {
|
||||
expect(resolve(digits)).toEqual({ candidates: expected, fallback: digits });
|
||||
});
|
||||
|
||||
it.each([
|
||||
'5512345678',
|
||||
'0445512345678',
|
||||
'52551234567',
|
||||
'525512345678901',
|
||||
'521551234567',
|
||||
'5491112345678',
|
||||
'5511912345678',
|
||||
'',
|
||||
])('does not match %s', (digits) => {
|
||||
expect(resolve(digits)).toBeNull();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,13 @@
|
||||
import { PhoneNumberRule } from '@waha/apps/phone-numbers/rules/PhoneNumberRule';
|
||||
import { RegexpPhoneNumberRule } from '@waha/apps/phone-numbers/rules/RegexpPhoneNumberRule';
|
||||
|
||||
/**
|
||||
* Mexico: since 2019 numbers are dialed as 52 + 10 digits, but accounts registered before that are still stored
|
||||
* on WhatsApp with the old mobile 1 after the country code - check the supplied form first, then the other one
|
||||
*/
|
||||
export function MexicanPhoneNumberRules(): PhoneNumberRule[] {
|
||||
return [
|
||||
new RegexpPhoneNumberRule('^52([2-9]\\d{9})$', '521$1'),
|
||||
new RegexpPhoneNumberRule('^521(\\d{10})$', '52$1'),
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { UniqueAppResolver } from '@waha/apps/app_sdk/services/UniqueAppResolver';
|
||||
import { MexicanPhoneNumbersAppConfig } from '@waha/apps/mexican-phone-numbers/dto';
|
||||
import { MexicanPhoneNumberRules } from '@waha/apps/mexican-phone-numbers/rules/MexicanPhoneNumberRules';
|
||||
import { MexicanPhoneNumbersCacheRepository } from '@waha/apps/mexican-phone-numbers/storage/MexicanPhoneNumbersCacheRepository';
|
||||
import { PhoneNumberRule } from '@waha/apps/phone-numbers/rules/PhoneNumberRule';
|
||||
import { PhoneNumbersAppServiceBase } from '@waha/apps/phone-numbers/services/PhoneNumbersAppServiceBase';
|
||||
|
||||
@Injectable()
|
||||
export class MexicanPhoneNumbersAppService extends PhoneNumbersAppServiceBase<MexicanPhoneNumbersAppConfig> {
|
||||
protected readonly Repository = MexicanPhoneNumbersCacheRepository;
|
||||
|
||||
constructor(resolver: UniqueAppResolver) {
|
||||
super(resolver);
|
||||
}
|
||||
|
||||
protected rules(config: MexicanPhoneNumbersAppConfig): PhoneNumberRule[] {
|
||||
void config;
|
||||
return MexicanPhoneNumberRules();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import { PhoneNumbersCacheRepository } from '@waha/apps/phone-numbers/storage/PhoneNumbersCacheRepository';
|
||||
|
||||
export class MexicanPhoneNumbersCacheRepository extends PhoneNumbersCacheRepository {
|
||||
static tableName = 'app_mexican_phone_numbers_cache';
|
||||
}
|
||||
@@ -7,6 +7,8 @@ const phoneNumbers = require('../migrations/001_init_app');
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const argentine = require('../../argentine-phone-numbers/migrations/001_init_app');
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const mexican = require('../../mexican-phone-numbers/migrations/001_init_app');
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const brazilian = require('../../brazilian-phone-numbers/migrations/001_init_brazilian_phone_numbers');
|
||||
|
||||
describe('migrations_001_init_cache', () => {
|
||||
@@ -28,6 +30,7 @@ describe('migrations_001_init_cache', () => {
|
||||
it('every phone numbers app gets its own table', async () => {
|
||||
await phoneNumbers.up(db);
|
||||
await argentine.up(db);
|
||||
await mexican.up(db);
|
||||
await brazilian.up(db);
|
||||
|
||||
expect(await db.schema.hasTable('app_phone_numbers_cache')).toBe(true);
|
||||
|
||||
Reference in new issue
Block a user