From 790522657a2239b4cb83f10e4e3d086bb8c2007a Mon Sep 17 00:00:00 2001 From: Shigma Date: Wed, 23 Aug 2023 03:04:31 +0800 Subject: [PATCH] feat(whatsapp): support multiple entries --- adapters/matrix/package.json | 2 +- adapters/whatsapp/package.json | 2 +- adapters/whatsapp/src/adapter.ts | 112 ++++++++++++++++++++----------- 3 files changed, 73 insertions(+), 43 deletions(-) diff --git a/adapters/matrix/package.json b/adapters/matrix/package.json index b156166f..6a61a6ea 100644 --- a/adapters/matrix/package.json +++ b/adapters/matrix/package.json @@ -1,7 +1,7 @@ { "name": "@satorijs/adapter-matrix", "description": "Matrix Adapter for Satorijs", - "version": "3.3.0", + "version": "3.4.0", "main": "lib/index.js", "typings": "lib/index.d.ts", "files": [ diff --git a/adapters/whatsapp/package.json b/adapters/whatsapp/package.json index bbb75b9e..e498f3ef 100644 --- a/adapters/whatsapp/package.json +++ b/adapters/whatsapp/package.json @@ -1,7 +1,7 @@ { "name": "@satorijs/adapter-whatsapp", "description": "WhatsApp Adapter for Satorijs", - "version": "1.0.3", + "version": "1.0.4", "main": "lib/index.js", "typings": "lib/index.d.ts", "files": [ diff --git a/adapters/whatsapp/src/adapter.ts b/adapters/whatsapp/src/adapter.ts index 881ee57b..2f057065 100644 --- a/adapters/whatsapp/src/adapter.ts +++ b/adapters/whatsapp/src/adapter.ts @@ -1,4 +1,4 @@ -import { Adapter, Context, Logger, Quester, Schema } from '@satorijs/satori' +import { Adapter, Context, Logger, Quester, remove, Schema } from '@satorijs/satori' import { Internal } from './internal' import { WhatsAppBot } from './bot' import { WebhookBody } from './types' @@ -6,47 +6,26 @@ import { decodeMessage } from './utils' import internal from 'stream' import crypto from 'crypto' -export class WhatsAppAdapter extends Adapter { - static reusable = true - - public bots: WhatsAppBot[] = [] - public logger = new Logger('whatsapp') - - constructor(private ctx: Context, public config: WhatsAppAdapter.Config) { - super() - - const http = ctx.http.extend({ - headers: { - Authorization: `Bearer ${config.systemToken}`, - }, - }).extend(config) - const internal = new Internal(http) - - ctx.on('ready', async () => { - const data = await internal.getPhoneNumbers(config.id) - for (const item of data) { - const bot = new WhatsAppBot(ctx, { - selfId: item.id, - }) - bot.adapter = this - bot.internal = internal - this.bots.push(bot) - bot.online() - } - }) +class HttpServer { + private logger = new Logger('whatsapp') + private adapters: WhatsAppAdapter[] = [] + constructor(private ctx: Context) { // https://developers.facebook.com/docs/graph-api/webhooks/getting-started // https://developers.facebook.com/docs/graph-api/webhooks/getting-started/webhooks-for-whatsapp/ ctx.router.post('/whatsapp', async (ctx) => { - const receivedSignature = ctx.get('X-Hub-Signature-256').split('sha256=')[1] + const received = ctx.get('X-Hub-Signature-256').split('sha256=')[1] + if (!received) return ctx.status = 403 const payload = ctx.request.rawBody - - const generatedSignature = crypto - .createHmac('sha256', this.config.secret) - .update(payload) - .digest('hex') - if (receivedSignature !== generatedSignature) return ctx.status = 403 + const adapters = this.adapters.filter((adapter) => { + const expected = crypto + .createHmac('sha256', adapter.config.secret) + .update(payload) + .digest('hex') + return expected === received + }) + if (!adapters.length) return ctx.status = 403 const parsed = ctx.request.body as WebhookBody this.logger.debug(require('util').inspect(parsed, false, null, true)) @@ -55,7 +34,7 @@ export class WhatsAppAdapter extends Adapter { if (parsed.object !== 'whatsapp_business_account') return for (const entry of parsed.entry) { const phone_number_id = entry.changes[0].value.metadata.phone_number_id - const bot = this.bots.find((bot) => bot.selfId === phone_number_id) + const bot = this.getBot(phone_number_id) const session = await decodeMessage(bot, entry) if (session.length) session.forEach(bot.dispatch.bind(bot)) this.logger.debug('handling bot: %s', bot.sid) @@ -67,15 +46,20 @@ export class WhatsAppAdapter extends Adapter { this.logger.debug(require('util').inspect(ctx.query, false, null, true)) const verifyToken = ctx.query['hub.verify_token'] const challenge = ctx.query['hub.challenge'] - if (verifyToken !== this.config.verifyToken) return ctx.status = 403 - ctx.body = challenge - ctx.status = 200 + for (const adapter of this.adapters) { + if (adapter.config.verifyToken === verifyToken) { + ctx.body = challenge + ctx.status = 200 + return + } + } + return ctx.status = 403 }) ctx.router.get('/whatsapp/assets/:self_id/:media_id', async (ctx) => { const mediaId = ctx.params.media_id const selfId = ctx.params.self_id - const bot = this.bots.find((bot) => bot.selfId === selfId) + const bot = this.getBot(selfId) if (!bot) return ctx.status = 404 const fetched = await bot.internal.getMedia(mediaId) @@ -91,6 +75,52 @@ export class WhatsAppAdapter extends Adapter { ctx.status = 200 }) } + + getBot(selfId: string) { + for (const adapter of this.adapters) { + for (const bot of adapter.bots) { + if (bot.selfId === selfId) return bot + } + } + } + + fork(ctx: Context, adapter: WhatsAppAdapter) { + this.adapters.push(adapter) + ctx.on('dispose', () => { + remove(this.adapters, adapter) + }) + } +} + +export class WhatsAppAdapter extends Adapter { + static reusable = true + + public bots: WhatsAppBot[] = [] + + constructor(ctx: Context, public config: WhatsAppAdapter.Config) { + super() + ctx.plugin(HttpServer, this) + + const http = ctx.http.extend({ + headers: { + Authorization: `Bearer ${config.systemToken}`, + }, + }).extend(config) + const internal = new Internal(http) + + ctx.on('ready', async () => { + const data = await internal.getPhoneNumbers(config.id) + for (const item of data) { + const bot = new WhatsAppBot(ctx, { + selfId: item.id, + }) + this.bots.push(bot) + bot.adapter = this + bot.internal = internal + bot.online() + } + }) + } } export namespace WhatsAppAdapter {