Skip to content

Commit

Permalink
broadcast qrcode
Browse files Browse the repository at this point in the history
  • Loading branch information
clairton committed Jun 25, 2024
1 parent 5106b2e commit 2bc074f
Show file tree
Hide file tree
Showing 19 changed files with 140 additions and 11 deletions.
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,16 @@ Go to `http://localhost:9876/session/XXX`, when XXX is your phone number, by exa

The qrcode is send to configured webhook to, you can read in chatwoot inbox, in created chat with de same number of connection.

### Qrcode with websocket
Use the endpoint `/ws` and listen event `broadcast`, the object with type `qrcode` has a `content` attribute with de base64 url of qrcode
```js
import { io } from 'socket.io-client';
const socket = io('http://localhost:9876/ws', { path: '/ws' });
socket.on('broadcast', data => {
console.log('broadcast', data);
});
```

## Send a Message

The payload is based on
Expand Down Expand Up @@ -430,6 +440,8 @@ PS: After update JSON, restart de docker container or service

### [Docker compose with chatwoot](examples/chatwoot/README.md)

### [Docker compose with chatwoot and unoapi inbox](examples/chatwoot-uno/README.md)

### [Docker compose with unoapi](examples/docker-compose.yml)

### [Docker compose with chatwoot and unoapi together](examples/unochat/README.md)
Expand Down
5 changes: 4 additions & 1 deletion __tests__/services/listener_baileys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { MediaStore } from '../../src/services/media_store'
import { Config, getConfig, defaultConfig, getMessageMetadataDefault } from '../../src/services/config'
import { ListenerBaileys } from '../../src/services/listener_baileys'
import { Outgoing } from '../../src/services/outgoing'
import { Broadcast } from '../../src/services/broadcast'

let store: Store
let getConfig: getConfig
Expand All @@ -13,6 +14,7 @@ let getStore: getStore
let phone
let outgoing: Outgoing
let service: ListenerBaileys
let broadcast: Broadcast

const textPayload = {
key: {
Expand All @@ -38,11 +40,12 @@ describe('service listener baileys', () => {
return config
}
store = mock<Store>()
broadcast = mock<Broadcast>()
outgoing = mock<Outgoing>()
store.dataStore = mock<DataStore>()
store.mediaStore = mock<MediaStore>()
phone = `${new Date().getMilliseconds()}`
service = new ListenerBaileys(outgoing, getConfig)
service = new ListenerBaileys(outgoing, broadcast, getConfig)
})

test('send call sendOne when text', async () => {
Expand Down
27 changes: 27 additions & 0 deletions examples/chatwoot-uno/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Chatwoot with Unoapi inbox


Up the unoapi service with `https://github.com/clairton/unoapi-cloud/tree/main?tab=readme-ov-file#start-options` or `https://github.com/clairton/unoapi-cloud/#install-as-systemctl`, use version >= 1.17.0


Get the chatwoot in `https://github.com/clairton/chatwoot` ou docker tag `clairton/chatwoot:v3.10.6-uno` change the env `UNOAPI_AUTH_TOKEN` with the same value of unoapi

Got to inboxes and choose whatsapp

![image](prints/channel.png)

Create with provider unoapi

![image](prints/create.png)

After save, edit the channel and in tab configuration

![image](prints/configuration.png)

Click em connect

![image](prints/connect.png)

Read de qrcode

![image](prints/read.png)
Binary file added examples/chatwoot-uno/prints/channel.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added examples/chatwoot-uno/prints/configuration.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added examples/chatwoot-uno/prints/connect.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added examples/chatwoot-uno/prints/create.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added examples/chatwoot-uno/prints/read.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "unoapi-cloud",
"version": "1.16.4",
"version": "1.17.0",
"description": "Unoapi Cloud",
"exports": "./dist/index.js",
"types": "./dist/index.d.ts",
Expand Down
2 changes: 1 addition & 1 deletion src/amqp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ export const amqpConsume = async (
logger.debug('Ack message!')
await channel.ack(payload)
} catch (error) {
logger.error('Error on consume', error)
logger.error('Error on consume %s %s', queue, error)
if (countRetries >= maxRetries) {
logger.info('Reject %s retries', countRetries)
if (options.notifyFailedMessages) {
Expand Down
12 changes: 10 additions & 2 deletions src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@ import injectRoute from './services/inject_route'
import { OnNewLogin } from './services/socket'
import { Server } from 'socket.io'
import { addToBlacklist } from './services/blacklist'
import cors from 'cors'

export class App {
public readonly server: HttpServer
public readonly socket: Server
private app

constructor(
Expand All @@ -29,11 +31,17 @@ export class App {
injectRoute: injectRoute = async (router: Router) => {},
) {
this.app = express()
this.app.use(cors({ origin: ['*'] }))
this.app.use(express.json())
this.app.use(express.urlencoded({ extended: true }))
this.server = createServer(this.app)
const socket: Server = new Server(this.server)
this.router(incoming, outgoing, baseUrl, getConfig, sessionStore, socket, onNewLogin, addToBlacklist, middleware, injectRoute)
this.socket = new Server(this.server, {
path: '/ws',
cors: {
origin: '*'
}
})
this.router(incoming, outgoing, baseUrl, getConfig, sessionStore, this.socket, onNewLogin, addToBlacklist, middleware, injectRoute)
}

private router(
Expand Down
1 change: 1 addition & 0 deletions src/defaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export const UNOAPI_JOB_OUTGOING = `${UNOAPI_QUEUE_NAME}.outgoing`
export const UNOAPI_JOB_CONTACT = `${UNOAPI_QUEUE_NAME}.contact`
export const UNOAPI_JOB_BULK_PARSER = `${UNOAPI_QUEUE_NAME}.bulk.parser`
export const UNOAPI_JOB_RELOAD = `${UNOAPI_QUEUE_NAME}.reload`
export const UNOAPI_JOB_BROADCAST = `${UNOAPI_QUEUE_NAME}.broadcast`
export const UNOAPI_JOB_DISCONNECT = `${UNOAPI_QUEUE_NAME}.disconnect`
export const UNOAPI_JOB_BULK_SENDER = `${UNOAPI_QUEUE_NAME}.bulk.sender`
export const UNOAPI_JOB_BULK_STATUS = `${UNOAPI_QUEUE_NAME}.bulk.status`
Expand Down
5 changes: 4 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { autoConnect } from './services/auto_connect'
import { getConfigByEnv } from './services/config_by_env'
import { getClientBaileys } from './services/client_baileys'
import { onNewLoginAlert } from './services/on_new_login_alert'
import { Broadcast } from './services/broadcast'
import { isInBlacklistInMemory, addToBlacklistInMemory } from './services/blacklist'
import { version } from '../package.json'

Expand All @@ -23,12 +24,14 @@ import { BASE_URL, PORT } from './defaults'

const outgoingCloudApi: Outgoing = new OutgoingCloudApi(getConfigByEnv, isInBlacklistInMemory)

const listenerBaileys: Listener = new ListenerBaileys(outgoingCloudApi, getConfigByEnv)
const broadcast: Broadcast = new Broadcast()
const listenerBaileys: Listener = new ListenerBaileys(outgoingCloudApi, broadcast, getConfigByEnv)
const onNewLoginn = onNewLoginAlert(listenerBaileys)
const incomingBaileys: Incoming = new IncomingBaileys(listenerBaileys, getConfigByEnv, getClientBaileys, onNewLoginn)
const sessionStore: SessionStore = new SessionStoreFile()

const app: App = new App(incomingBaileys, outgoingCloudApi, BASE_URL, getConfigByEnv, sessionStore, onNewLoginn, addToBlacklistInMemory)
broadcast.setSever(app.socket)

app.server.listen(PORT, '0.0.0.0', async () => {
logger.info('Unoapi Cloud version: %s, listening on port: %s', version, PORT)
Expand Down
6 changes: 4 additions & 2 deletions src/jobs/bind.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,17 @@ import logger from '../services/logger'
import { Listener } from '../services/listener'
import { ListenerBaileys } from '../services/listener_baileys'
import { OutgoingAmqp } from '../services/outgoing_amqp'
import { BroadcastAmqp } from '../services/broadcast_amqp'
import { NotificationJob } from '../jobs/notification'
import { isSessionStatusOnline } from '../services/session_store'
import { isInBlacklistInRedis } from '../services/blacklist'
import { Broadcast } from '../services/broadcast'

const outgoingAmqp: Outgoing = new OutgoingAmqp(getConfigRedis)
const incomingAmqp: Incoming = new IncomingAmqp()
const listenerAmqp: Listener = new ListenerAmqp()

const listenerBaileys: Listener = new ListenerBaileys(outgoingAmqp, getConfigRedis)
const broadcastAmqp: Broadcast = new BroadcastAmqp()
const listenerBaileys: Listener = new ListenerBaileys(outgoingAmqp, broadcastAmqp, getConfigRedis)

const getConfig: getConfig = getConfigRedis

Expand Down
13 changes: 13 additions & 0 deletions src/jobs/broadcast.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Broadcast } from '../services/broadcast'

export class BroacastJob {
private broadcast: Broadcast

constructor(broadcast: Broadcast) {
this.broadcast = broadcast
}

async consume(_: string, { phone, type, content }) {
return this.broadcast.send(phone, type, content)
}
}
17 changes: 17 additions & 0 deletions src/services/broadcast.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Server } from 'socket.io'

export class Broadcast {
private server: Server

public setSever(server: Server) {
this.server = server
}

public async send(phone: string, type: string, content: string) {
if (!this.server) {
throw 'Set the socket server'
}
await this.server.emit('broadcast', { phone, type, content })
}
}

17 changes: 17 additions & 0 deletions src/services/broadcast_amqp.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { amqpEnqueue } from '../amqp'
import { UNOAPI_JOB_BROADCAST } from '../defaults'
import { Broadcast } from './broadcast'

export class BroadcastAmqp extends Broadcast {
private queueName: string

constructor(queueName: string = UNOAPI_JOB_BROADCAST) {
super()
this.queueName = queueName
}

public async send(phone: string, type: string, content: string) {
const payload = { phone, type, content }
await amqpEnqueue(this.queueName, '', payload)
}
}
18 changes: 17 additions & 1 deletion src/services/listener_baileys.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Listener } from './listener'
import logger from './logger'
import { Outgoing } from './outgoing'
import { Broadcast } from './broadcast'
import { getConfig } from './config'
import { fromBaileysMessageContent, getMessageType, BindTemplateError, isSaveMedia } from './transformer'
import { WAMessage, delay } from '@whiskeysockets/baileys'
Expand Down Expand Up @@ -29,10 +30,12 @@ const delayFunc = UNOAPI_DELAY_AFTER_FIRST_MESSAGE_MS && UNOAPI_DELAY_BETWEEN_ME
export class ListenerBaileys implements Listener {
private outgoing: Outgoing
private getConfig: getConfig
private broadcast: Broadcast

constructor(outgoing: Outgoing, getConfig: getConfig) {
constructor(outgoing: Outgoing, broadcast: Broadcast, getConfig: getConfig) {
this.outgoing = outgoing
this.getConfig = getConfig
this.broadcast = broadcast
}

async process(phone: string, messages: object[], type: 'qrcode' | 'status' | 'history' | 'append' | 'notify' | 'message' | 'update' | 'delete') {
Expand All @@ -53,6 +56,19 @@ export class ListenerBaileys implements Listener {
return
}
}
if (type == 'qrcode') {
await this.broadcast.send(
phone,
type,
messages[0]['message']['imageMessage']['url']
)
} else if(type === 'status') {
await this.broadcast.send(
phone,
type,
messages[0]['message']['conversation']
)
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const filteredMessages = messages.filter((m: any) => {
return (
Expand Down
14 changes: 12 additions & 2 deletions src/web.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,24 +15,34 @@ import {
UNOAPI_JOB_INCOMING,
UNOAPI_MESSAGE_RETRY_DELAY,
CONFIG_SESSION_PHONE_CLIENT,
CONFIG_SESSION_PHONE_NAME
CONFIG_SESSION_PHONE_NAME,
UNOAPI_JOB_BROADCAST,
} from './defaults'
import { getConfigRedis } from './services/config_redis'
import security from './services/security'
import { amqpGetChannel } from './amqp'
import { amqpConsume, amqpGetChannel } from './amqp'
import logger from './services/logger'
import { version } from '../package.json'
import { onNewLoginGenerateToken } from './services/on_new_login_generate_token'
import { addToBlacklistJob } from './services/blacklist'
import { Broadcast } from './services/broadcast'
import { BroacastJob } from './jobs/broadcast'


const incoming: Incoming = new IncomingAmqp()
const outgoing: Outgoing = new OutgoingAmqp(getConfigRedis)
const sessionStore: SessionStore = new SessionStoreRedis()
const onNewLogin = onNewLoginGenerateToken(outgoing)
const broadcast: Broadcast = new Broadcast();

const app: App = new App(incoming, outgoing, BASE_URL, getConfigRedis, sessionStore, onNewLogin, addToBlacklistJob, security)
broadcast.setSever(app.socket)

const broadcastJob = new BroacastJob(broadcast)

app.server.listen(PORT, '0.0.0.0', async () => {
await amqpGetChannel(UNOAPI_JOB_INCOMING, '', AMQP_URL, { delay: UNOAPI_MESSAGE_RETRY_DELAY, priority: 5 }) // create a channel with priority
logger.info('Starting broadcast consumer')
await amqpConsume(UNOAPI_JOB_BROADCAST, '', broadcastJob.consume.bind(broadcastJob))
logger.info('Unoapi Cloud version: %s, listening on port: %s | Linked Device: %s(%s)', version, PORT, CONFIG_SESSION_PHONE_CLIENT, CONFIG_SESSION_PHONE_NAME)
})

0 comments on commit 2bc074f

Please sign in to comment.