Compare commits

..
1111 Commits
Author SHA1 Message Date
devlikepro 52222a6b98 [core] 2026.2.1
Release / WEBJS - chrome - amd64 - chrome (push) Waiting to run
Release / WEBJS - chromium - amd64 - latest (push) Waiting to run
Release / WEBJS - chromium - linux/arm64 - arm (push) Waiting to run
Release / GOWS - none - amd64 - gows (push) Waiting to run
Release / GOWS - none - linux/arm64 - gows-arm (push) Waiting to run
Release / NOWEB - none - amd64 - noweb (push) Waiting to run
Release / NOWEB - none - linux/arm64 - noweb-arm (push) Waiting to run
2026-02-02 14:10:58 +07:00
devlikepro 99a0a19d8c [core] NOWEB - fix send image using proxy
fix #1859
2026-02-02 14:10:58 +07:00
devlikepro 48ed33bc94 [core] Up WEBJS
Fix GET groups internal error
 - fix #1860

Fix Send message to channel - fix #1863

Fix contact update - fix #1839

[WEBJS] - Get groups count and Get groups failed fix #1879 fix #1873
2026-02-02 14:10:57 +07:00
devlikepro b234e134bf [core] GOWS - exclude all AppState events 2026-02-02 14:10:57 +07:00
devlikepro f6004fd294 [core] openapi - POST /api/{session}/presence chatId param
fix #1842
2026-02-02 14:10:57 +07:00
devlikepro 91d541387b [core] 2026.1.5
Release / NOWEB - none - linux/arm64 - noweb-arm (push) Waiting to run
Release / WEBJS - chrome - amd64 - chrome (push) Waiting to run
Release / WEBJS - chromium - amd64 - latest (push) Waiting to run
Release / WEBJS - chromium - linux/arm64 - arm (push) Waiting to run
Release / GOWS - none - amd64 - gows (push) Waiting to run
Release / GOWS - none - linux/arm64 - gows-arm (push) Waiting to run
Release / NOWEB - none - amd64 - noweb (push) Waiting to run
2026-01-28 17:32:34 +07:00
devlikepro 9dbd45da6e [core] Up WEBJS - fix #1855 fix #1856 2026-01-28 17:32:34 +07:00
devlikepro adc1c3960c [core] for-swagger 2026-01-28 17:32:33 +07:00
devlikepro ff742d2e84 [core] WEBJS - add removeExposedFunction WPage 2026-01-28 17:32:33 +07:00
devlikepro 10bd4d6271 [core] Up Dashboard 2026-01-28 17:32:33 +07:00
devlikepro 874cafe114 [core] GOWS - disable storage
fix #823
2026-01-28 17:32:33 +07:00
devlikepro 0ce2c4b9fa [core] GOWS - Exclude AppState, HistorySync, Contact events 2026-01-28 17:32:33 +07:00
devlikepro a8a7988bfa [core] GOWS - Exclude event from engine.events 2026-01-28 17:32:33 +07:00
devlikepro 2940d93b81 [core] Up GOWS 2026-01-28 17:32:32 +07:00
devlikepro 88707cf72b [core] GOWS - BuildClient 2026-01-28 17:32:32 +07:00
devlikepro b0f883c6a8 [core] ChatWoot - no POSTGRES_HOST_AUTH_METHOD
Release / NOWEB - none - linux/arm64 - noweb-arm (push) Waiting to run
Release / WEBJS - chrome - amd64 - chrome (push) Waiting to run
Release / WEBJS - chromium - amd64 - latest (push) Waiting to run
Release / WEBJS - chromium - linux/arm64 - arm (push) Waiting to run
Release / GOWS - none - amd64 - gows (push) Waiting to run
Release / GOWS - none - linux/arm64 - gows-arm (push) Waiting to run
Release / NOWEB - none - amd64 - noweb (push) Waiting to run
2026-01-26 11:06:54 +07:00
devlikepro 4cddb766c2 [core] 2026.1.4 2026-01-26 10:14:19 +07:00
devlikepro 6675492901 [core] Optimize get me 2026-01-26 10:14:19 +07:00
devlikepro a9d030af2a [core] Up NOWEB 2026-01-26 10:11:07 +07:00
devlikepro 2782a188a9 [core] Webhooks - add worker.id
fix #1840
2026-01-26 10:11:07 +07:00
devlikepro 256688497e [core] WEBJS - get 500 error on getChats
fix #1834
2026-01-26 10:11:07 +07:00
devlikepro 4def193c57 [core] WEBJS - inject on script READY too 2026-01-26 10:08:52 +07:00
devlikepro 7f3a2e6aa5 [core] AGENTS.md 2026-01-26 10:08:52 +07:00
devlikepro 60e4f13b67 [core] Fix DeprecationWarning: url.parse() 2026-01-26 10:08:51 +07:00
devlikepro 2d6cfeb0fa [core] ChatWoot - use chat id from incoming message to use either @lid or @c.us (likely @lid in MOST cases now) 2026-01-26 10:08:50 +07:00
devlikepro 03e85434df [core] GOWS subprocess 2026-01-26 10:08:49 +07:00
devlikepro 0ce4c01c48 [core] Optimize get config 2026-01-26 10:08:49 +07:00
devlikepro 0e95890ca4 [core] Optimize get me 2026-01-26 10:08:48 +07:00
devlikepro 072f4b2ab0 [core] Up GOWS 2026-01-26 10:08:48 +07:00
devlikepro 47a91ef669 [core] websocket - use setImmediate 2026-01-26 10:08:47 +07:00
devlikepro 5143bd1ef8 [core] Webhooks - use setImmediate 2026-01-26 10:08:46 +07:00
devlikepro 0efc0751c9 [core] GOWS - send data to subscribers in a setImmediate 2026-01-26 10:08:45 +07:00
devlikepro 51dda739cb [core] ChatWoot - add delay config
WAHA_APPS_JOBS_ATTEMPTS
WAHA_APPS_JOBS_DELAY
WAHA_APPS_JOBS_BACKOFF_TYPE
WAHA_APPS_JOBS_BACKOFF_DELAY

fix #1828
2026-01-26 10:08:45 +07:00
devlikepro 3b28826679 [core] dev 2026-01-26 10:08:45 +07:00
devlikepro 81646bbaa2 [core] Up Dashboard 2026-01-26 10:08:44 +07:00
devlikepro d8d84107fb [core] yarn:test in ci 2026-01-26 10:08:43 +07:00
devlikepro 58d917d374 [core] Api Keys - admin, per sessions
fix #387
fix #937
fix #1642
fix #1772
2026-01-26 10:08:41 +07:00
devlikepro f8f0063975 [core] Openapi - use pairing tag for qr/code/screenshot, move apps below 2026-01-26 10:08:40 +07:00
devlikepro 73faf330e3 [core] AGENTS.md 2026-01-26 10:08:40 +07:00
devlikepro 5a1ebb186d [core] import crypto 2026-01-26 10:08:40 +07:00
devlikepro 54d6d715b0 [core] Add "chrome" to dev build 2026-01-26 10:08:40 +07:00
devlikepro 5d9d6e29e2 [core] Up WEBJS - fix SendSeen not working
fix #1818
2026-01-26 10:08:40 +07:00
devlikepro cf3385f624 Merge remote-tracking branch 'core/core' into core
Release / WEBJS - chrome - amd64 - chrome (push) Waiting to run
Release / WEBJS - chromium - amd64 - latest (push) Waiting to run
Release / WEBJS - chromium - linux/arm64 - arm (push) Waiting to run
Release / GOWS - none - amd64 - gows (push) Waiting to run
Release / GOWS - none - linux/arm64 - gows-arm (push) Waiting to run
Release / NOWEB - none - amd64 - noweb (push) Waiting to run
Release / NOWEB - none - linux/arm64 - noweb-arm (push) Waiting to run
2026-01-15 12:19:57 +07:00
devlikepro 0346c5e7b9 [core] 2026.1.3 2026-01-15 12:15:15 +07:00
devlikepro 443652b0a7 [core] Apps - ignore no type in headers 2026-01-15 12:15:14 +07:00
devlikepro 1e73c3dc6a [core] WEBJS - disable-metrics 2026-01-15 12:15:13 +07:00
devlikepro afa9d4208f [core] ChatWoot - fix delete message on WEBJS
fix #1769
2026-01-15 12:15:13 +07:00
devlikepro 64eda25b5d [core] WEBJS - get chats use AUTHENTICATED
fix #1782
2026-01-15 12:15:12 +07:00
devlikepro cfc6c931a3 [core] Up WEBJS
fix #1810
2026-01-15 12:15:11 +07:00
devlikepro 35bc56c948 [core] Up GOWS - 1.0.30 2026-01-15 12:15:11 +07:00
devlikepro 371bde4c3e [core] Up NOWEB - proto 1031716404 2026-01-15 12:15:09 +07:00
devlikepro 9497e2900e [core] .env 2026-01-15 12:15:08 +07:00
devlikepro 51bbc9998a [core] GitHub Actions - retry 2026-01-15 12:15:07 +07:00
devlikepro 057f7bce56 [core] ChatWoot - fix sending attachments with no caption in a group chat
fix #1808
2026-01-15 12:15:06 +07:00
devlikepro 52412fb4cc [core] ChatWoot - remove @lid when mention it
fix #1803
2026-01-15 12:15:06 +07:00
devlikepro a57f3443de [core] Resolve media absolute path 2026-01-15 12:15:06 +07:00
devlikepro 9b2537e7c7 [core] Add retries to docker build 2026-01-15 12:15:05 +07:00
Cameron Jackson a378ad4c3d [core] Sidestep existing .env from being overwritten (#1770)
Following docker instructions blindly currently wipes .env if it's already configure
2026-01-12 16:54:56 +07:00
devlikepro 8f867e0b5d [core] 2026.1.2
Release / WEBJS - chrome - amd64 - chrome (push) Waiting to run
Release / WEBJS - chromium - amd64 - latest (push) Waiting to run
Release / WEBJS - chromium - linux/arm64 - arm (push) Waiting to run
Release / GOWS - none - amd64 - gows (push) Waiting to run
Release / GOWS - none - linux/arm64 - gows-arm (push) Waiting to run
Release / NOWEB - none - amd64 - noweb (push) Waiting to run
Release / NOWEB - none - linux/arm64 - noweb-arm (push) Waiting to run
2026-01-12 16:52:10 +07:00
devlikepro d4f6ec18fd [core] GOWS - receive and send video note
fix #1798
2026-01-12 16:52:10 +07:00
devlikepro ac05dd83e9 [core] NOWEB - handle video note media 2026-01-12 16:50:35 +07:00
devlikepro 17c22f0cd4 [core] Up WEBSJ - fix send media to channels
fix #1775
2026-01-12 16:50:34 +07:00
devlikepro 971979c13f [core] Do not send double session.status
fix #1784
2026-01-12 16:50:34 +07:00
devlikepro 845e203637 [core] GOWS - stop sessions first, then grpc
fix #1784
2026-01-12 16:50:34 +07:00
devlikepro 7a2e1cd731 [core] GOWS - start session in the background 2026-01-12 16:50:34 +07:00
devlikepro 84ba011a4c [core] Up GOWS - add profiling 2026-01-12 16:50:33 +07:00
devlikepro 50399f1192 [core] Add associatedChildMessage to edited message
fix #1773
2026-01-12 16:50:33 +07:00
devlikepro a2517b248d [core] ChatWoot - handle not found app
fix #1290
fix #1790
2026-01-12 16:50:33 +07:00
devlikepro f02d95331a [core] ChatWoot - support @lid mention directly
fix #1767
closes devlikeapro/waha-plus#36
2026-01-12 16:50:32 +07:00
Berg Pinheiro cb1faff88c [core] ChatWoot - support @mention and @all in group messages
fix #1767
closes devlikeapro/waha-plus#36
2026-01-12 16:50:32 +07:00
devlikepro 3ff1c06eea [core] ChatWoot - add session info api in client 2026-01-12 16:50:32 +07:00
devlikepro c91d2c59eb [core] ChatWoot - add group participants api in client 2026-01-12 16:50:32 +07:00
devlikepro 4cf5a74d23 [core] Groups - allow @all be with other mentions - we resolve group participants anyway 2026-01-12 16:50:32 +07:00
devlikepro 13de42dbe0 [core] Up GOWS
Release / WEBJS - chrome - amd64 - chrome (push) Waiting to run
Release / WEBJS - chromium - amd64 - latest (push) Waiting to run
Release / WEBJS - chromium - linux/arm64 - arm (push) Waiting to run
Release / GOWS - none - amd64 - gows (push) Waiting to run
Release / GOWS - none - linux/arm64 - gows-arm (push) Waiting to run
Release / NOWEB - none - amd64 - noweb (push) Waiting to run
Release / NOWEB - none - linux/arm64 - noweb-arm (push) Waiting to run
fix #1749
2026-01-02 19:47:30 +07:00
devlikepro f99b86ed18 [core] WEBJS - can not read properties of null - client
fix #1763
closes #1764
2026-01-02 19:27:50 +07:00
devlikepro 07f6c78372 [core] 2026.1.1 2026-01-02 19:27:50 +07:00
devlikepro 83678553e3 [core] 2025.12.3
Release / WEBJS - chrome - amd64 - chrome (push) Waiting to run
Release / WEBJS - chromium - amd64 - latest (push) Waiting to run
Release / WEBJS - chromium - linux/arm64 - arm (push) Waiting to run
Release / GOWS - none - amd64 - gows (push) Waiting to run
Release / GOWS - none - linux/arm64 - gows-arm (push) Waiting to run
Release / NOWEB - none - amd64 - noweb (push) Waiting to run
Release / NOWEB - none - linux/arm64 - noweb-arm (push) Waiting to run
2025-12-30 17:38:14 +07:00
devlikepro 9d1bbd780e [core] NOWEB - use device name if no browser 2025-12-30 17:38:14 +07:00
devlikepro 899f9f2e84 [core] WAHA_CLIENT_DEVICE_NAME in .env 2025-12-30 17:38:14 +07:00
devlikepro 1d1a083464 [core] GOWS, NOWEB - motion_video (WhatsApp Live Photo / Motion Photo) messages are not supported
fix #175
2025-12-30 17:38:14 +07:00
devlikepro fabc6753e6 [core] Up Dashboard 2025-12-30 17:38:14 +07:00
devlikepro a16f3b67fe [core] Add device and browser config - WEBJS, GOWS, NOWEB
fix #1109
2025-12-30 17:38:13 +07:00
devlikepro c0c3381a36 [core] WEBJS - Up user agent 2025-12-30 17:38:13 +07:00
devlikepro 212a27d9df [core] Up GOWS
fix #1746
2025-12-30 17:38:13 +07:00
devlikepro a18c1d82c1 [core] GOWS - use a new grpc client for each session 2025-12-30 17:38:13 +07:00
devlikepro 67349e4509 [core] WEBJS - fix empty /me when WORKING session
fix #1735

[core] WEBJS
2025-12-30 17:38:13 +07:00
devlikepro 293267536b [core] Add platform to /version 2025-12-30 17:38:12 +07:00
devlikepro 10d103a09a [core] 2025.12.2
Release / WEBJS - chrome - amd64 - chrome (push) Waiting to run
Release / WEBJS - chromium - amd64 - latest (push) Waiting to run
Release / WEBJS - chromium - linux/arm64 - arm (push) Waiting to run
Release / GOWS - none - amd64 - gows (push) Waiting to run
Release / GOWS - none - linux/arm64 - gows-arm (push) Waiting to run
Release / NOWEB - none - amd64 - noweb (push) Waiting to run
Release / NOWEB - none - linux/arm64 - noweb-arm (push) Waiting to run
2025-12-17 16:15:37 +07:00
devlikepro 1a6ff4dbde [core] WEBJS - poll.vote 2025-12-17 16:15:37 +07:00
devlikepro 321310934d [core] WEBJS - send poll 2025-12-17 16:15:37 +07:00
devlikepro 22312d775a [core] Up dashboard 2025-12-17 16:15:37 +07:00
devlikepro b71d83ce48 [core] message.ack.group 2025-12-17 16:15:37 +07:00
Berg 1a91e7d722 [core] Separate group ACK events from message.ack to group.ack (#29) 2025-12-17 16:15:37 +07:00
devlikepro 7a54b6b599 [core] Up NOWEB - fix yarn error 2025-12-17 16:15:37 +07:00
devlikepro 821c9f7b00 [core] yarn 4.9.2 2025-12-17 16:15:37 +07:00
devlikepro e7f300b4bc [core] Fix docker build
can't be found in the cache and will be fetched from the remote repository
Cannot apply hunk 1
2025-12-17 16:15:37 +07:00
devlikepro a5bff80b3a [core] WEBJS - delete status message 2025-12-17 16:15:37 +07:00
devlikepro 89dd3aa9e1 [core] WEBJS - send text status 2025-12-17 16:15:37 +07:00
devlikepro 8fc5658e9d [core] Up NOWEB 2025-12-17 16:15:37 +07:00
devlikepro eb3879bf65 [core] Up WEBJS 2025-12-17 16:15:37 +07:00
devlikepro daf56433a3 [core] WEBJS - puppeteer 24.31.0 2025-12-17 16:15:37 +07:00
devlikepro e4cb5d5652 [core] Webhook - ignore SSL errors
mention #1717
2025-12-17 16:15:37 +07:00
devlikepro a2e0119e61 [core] Up GOWS 2025-12-17 16:15:37 +07:00
devlikepro f8bcaebf4a [core] GOWS - make grpc pool for clients
fix #1715
2025-12-17 16:15:37 +07:00
devlikepro 935a2b515f [core] GOWS - make grpc client per session
fix #1715
2025-12-17 16:15:37 +07:00
devlikepro 2313915ade [core] NOWEB - poll.vote
fix #1688
2025-12-17 16:15:37 +07:00
devlikepro dfc1c6dde4 [core] AGENTS.md 2025-12-17 16:15:37 +07:00
mahmouds12 62d1b3dbf6 [core] Implement message deduplication to prevent duplicate webhooks in GOWS (#1718)
fix #1564
2025-12-17 14:48:27 +07:00
Davide Truffa 7272f0e669 [core] Add italian translation (#1722) 2025-12-17 14:42:28 +07:00
devlikepro 96da96281d [core] 2025.12.1
Release / WEBJS - chrome - amd64 - chrome (push) Waiting to run
Release / WEBJS - chromium - amd64 - latest (push) Waiting to run
Release / WEBJS - chromium - linux/arm64 - arm (push) Waiting to run
Release / GOWS - none - amd64 - gows (push) Waiting to run
Release / GOWS - none - linux/arm64 - gows-arm (push) Waiting to run
Release / NOWEB - none - amd64 - noweb (push) Waiting to run
Release / NOWEB - none - linux/arm64 - noweb-arm (push) Waiting to run
2025-12-05 00:15:53 -08:00
devlikepro 50e09c3680 [core] Up dashboard 2025-12-05 00:15:53 -08:00
devlikepro 1da6a16640 [core] for-swagger 2025-12-05 00:15:52 -08:00
devlikepro 10f80132f8 [core] NOWEB - poll.vote iterate over creators/votes
fix #1688
2025-12-05 00:15:52 -08:00
devlikepro 750b2b86b5 [core] WEBJS - WORKING as AUTHENTICATED
fix #1690
close #1695
2025-12-05 00:15:51 -08:00
devlikepro 2fc2caed85 [core] ChatWoot - mark as read flag
fix #1650
2025-12-05 00:15:50 -08:00
devlikepro 1431e8c0f1 [core] Apps allow to remove app if not enabled 2025-12-05 00:15:50 -08:00
devlikepro 01c9c2ca56 [core] session FAILED if apps not started 2025-12-05 00:15:49 -08:00
devlikepro f5d216df6f [core] apps in sessions 2025-12-05 00:15:49 -08:00
devlikepro 7614822d2b [core] Apps - upsert to service 2025-12-05 00:15:49 -08:00
devlikepro d2eb463d32 [core] Apps on and off 2025-12-05 00:15:49 -08:00
devlikepro 07b1e50534 [core] Remove apps by session 2025-12-05 00:15:49 -08:00
devlikepro 41b6b3459e [core] Calls App
fix #1694
2025-12-05 00:13:15 -08:00
devlikepro 0f5fcf60f6 [core] ChatWoot - markdown fix for symbols in the middle of text
fix #1643
2025-12-05 00:13:14 -08:00
devlikepro 18d8330ab3 [core] Up GOWS 2025-12-05 00:13:14 -08:00
devlikepro 7de5f461aa [core] ChatWoot - call messages 2025-12-05 00:13:13 -08:00
devlikepro caa055abed [core] ChatWoot - weaken the type for handler 2025-12-05 00:13:13 -08:00
devlikepro 49af88dea3 [core] ChatWoot - type based on private flag or fromMe 2025-12-05 00:13:13 -08:00
devlikepro 05d856d8ae [core] WEBJS - call.rejected for API rejection 2025-12-05 00:13:12 -08:00
devlikepro c5c99136fa [core] Reject Calls - WEBJS, NOWEB, GOWS
fix #608
fix #1258
2025-12-05 00:13:12 -08:00
devlikepro a8b520012f [core] GOWS - add call events
fix #1673
2025-12-05 00:13:12 -08:00
devlikepro 449df53c19 [core] Add _data to calls event - WEBJS, NOWEB 2025-12-05 00:13:12 -08:00
devlikepro 69ac19b5c0 [core] AGENTS.md 2025-12-05 00:13:12 -08:00
devlikepro 163b11aa36 [core] 2025.11.4
Release / WEBJS - chrome - amd64 - chrome (push) Waiting to run
Release / WEBJS - chromium - amd64 - latest (push) Waiting to run
Release / WEBJS - chromium - linux/arm64 - arm (push) Waiting to run
Release / GOWS - none - amd64 - gows (push) Waiting to run
Release / GOWS - none - linux/arm64 - gows-arm (push) Waiting to run
Release / NOWEB - none - amd64 - noweb (push) Waiting to run
Release / NOWEB - none - linux/arm64 - noweb-arm (push) Waiting to run
2025-12-01 11:46:58 +07:00
devlikepro f4171a9086 [core] Up WEBJS
fix #1678
2025-12-01 11:46:57 +07:00
devlikepro dfbae9c71e [core] GOWS - 128Mb max back in grpc (shouldn't happen tho) 2025-12-01 11:46:57 +07:00
devlikepro 91caa7ba25 [core] nodejs 24.11 2025-12-01 11:46:56 +07:00
devlikepro 2ac57bb6c3 [core] GOWS - send media via file 2025-12-01 11:46:56 +07:00
devlikepro e01647eb48 [core] GOWS - fetch media via file 2025-12-01 11:46:55 +07:00
devlikepro 90bc05ecf3 [core] Use uniqid in webhook for request id 2025-12-01 11:46:54 +07:00
devlikepro 21998cbfae [core] log event 2025-12-01 11:46:53 +07:00
devlikepro 87c49a8766 [core] downgrade google-protobug 2025-12-01 11:46:53 +07:00
devlikepro 4e78d559a8 [core] Disable cpu-prof when WAHA_DEBUG_MODE=true
fix #1681
2025-12-01 11:46:53 +07:00
devlikepro 18ce902c60 [core] Up GOWS to 1.0.24 2025-12-01 11:46:53 +07:00
devlikepro 5784db8055 [core] 2025.11.3
Release / WEBJS - chrome - amd64 - chrome (push) Waiting to run
Release / WEBJS - chromium - amd64 - latest (push) Waiting to run
Release / WEBJS - chromium - linux/arm64 - arm (push) Waiting to run
Release / GOWS - none - amd64 - gows (push) Waiting to run
Release / GOWS - none - linux/arm64 - gows-arm (push) Waiting to run
Release / NOWEB - none - amd64 - noweb (push) Waiting to run
Release / NOWEB - none - linux/arm64 - noweb-arm (push) Waiting to run
2025-11-28 14:02:15 +07:00
devlikepro 6862063654 [core] Up GOWS 2025-11-28 14:02:14 +07:00
devlikepro 4aea282356 [core] ChatWoot - do not log binary 2025-11-28 14:02:13 +07:00
devlikepro 82ff15ae56 [core] GOWS - 10 mins deadline for media fetch 2025-11-28 14:02:12 +07:00
devlikepro 6cbf8cded2 [core] Up Dashboard - app id is uuid now 2025-11-28 14:02:11 +07:00
devlikepro 35e9418988 [core] add cpu profiling for troubleshooting 2025-11-28 14:02:10 +07:00
devlikepro a58be84e56 [core] GOWS - Use one grpc client to handle all sessions (but per session stream) 2025-11-28 14:02:09 +07:00
devlikepro 2994e0a54d [core] ignore tmp 2025-11-28 14:02:09 +07:00
devlikepro f16100e1d9 [core] GOWS - logs for grpc client closing 2025-11-28 14:02:07 +07:00
devlikepro e57be58d48 [core] GOWS - up grpc 2025-11-28 14:02:06 +07:00
devlikepro 7909910dc6 [core] ChatWoot - limit names to 255
fix #1556
2025-11-28 14:02:05 +07:00
devlikepro 7f1778ceb0 [core] ChatWoot - do not create new conversation when closed on message.ack
close devlikeapro/waha-plus#28
fix #1646
2025-11-28 14:02:05 +07:00
devlikepro aba90574d8 [core] ChatWoot - remove unused imports 2025-11-28 14:02:05 +07:00
devlikepro 33c945bf95 [core] NOWEB - Up engine 2025-11-28 14:02:04 +07:00
devlikepro d40e82eb02 [core] WEBJS - up engine 2025-11-28 14:02:04 +07:00
devlikepro 6e82d6f40b [core] GOWS - force to use cgo dns
fix #1649
2025-11-28 14:02:03 +07:00
devlikepro e193eb5696 [core] Up go 1.24
Tho we don't built anything here
2025-11-28 14:02:03 +07:00
devlikepro 8c63c4b097 [core] WEBJS - fix code pairing
fix #1651
2025-11-28 14:02:03 +07:00
devlikepro 4127d609cf [core] for-swagger 2025-11-28 14:02:03 +07:00
devlikepro 57f6db4252 [core] 2025.11.2
Release / WEBJS - chrome - amd64 - chrome (push) Waiting to run
Release / WEBJS - chromium - amd64 - latest (push) Waiting to run
Release / WEBJS - chromium - linux/arm64 - arm (push) Waiting to run
Release / GOWS - none - amd64 - gows (push) Waiting to run
Release / GOWS - none - linux/arm64 - gows-arm (push) Waiting to run
Release / NOWEB - none - amd64 - noweb (push) Waiting to run
Release / NOWEB - none - linux/arm64 - noweb-arm (push) Waiting to run
2025-11-14 10:23:33 +07:00
devlikepro 68a91cb55c [core] up GOWS v1.0.21 2025-11-14 10:23:32 +07:00
devlikepro 869a343e43 [core] API - mentions for send image, file, video 2025-11-14 10:23:32 +07:00
devlikepro fd2b22b339 [core] Up better-sqlite3 to 12.4.1 2025-11-14 10:23:31 +07:00
devlikepro 90d3a5b1ad [core] NOWEB - add pool for sqlite3 2025-11-14 10:23:31 +07:00
devlikepro b4a2ad6b96 [core] WAHA_SQLITE_ENGINE - Add a way to use better-sqlite3 (sync sqlite3 engine) 2025-11-14 10:23:31 +07:00
devlikepro 27ff7e7a21 [core] Reset presence on engine restart (end) 2025-11-14 10:23:30 +07:00
devlikepro 94ae624662 [core] Set to OFFLINE always 2025-11-14 10:23:30 +07:00
devlikepro 52e018aa93 [core] Use timestamps.activity 2025-11-14 10:23:29 +07:00
Berg Pinheiro 8bd13a233e [core] Add automatic presence management and activity tracking
fix #1584
closes #1586

This prevents WhatsApp from disconnecting sessions due to inactivity by
signaling activity through presence updates while avoiding constant online
status that could generate unnecessary notifications.
2025-11-14 10:23:28 +07:00
Emir Muhammadzadeh 87f79c1dc5 [core] NOWEB, GOWS - add location and vcards
closes #1609
2025-11-14 10:23:28 +07:00
devlikepro 31533b5d19 [core] GOWS - move waproto helper to engine 2025-11-14 10:23:27 +07:00
devlikepro 27cc9f4764 [core] Add opustags 2025-11-14 10:23:27 +07:00
devlikepro 4fdfce3944 [core] 2025.11.1
Release / GOWS - none - linux/arm64 - gows-arm (push) Waiting to run
Release / NOWEB - none - amd64 - noweb (push) Waiting to run
Release / NOWEB - none - linux/arm64 - noweb-arm (push) Waiting to run
Release / WEBJS - chrome - amd64 - chrome (push) Waiting to run
Release / WEBJS - chromium - amd64 - latest (push) Waiting to run
Release / WEBJS - chromium - linux/arm64 - arm (push) Waiting to run
Release / GOWS - none - amd64 - gows (push) Waiting to run
2025-11-07 13:55:03 +07:00
devlikepro 1e12a83cdf [core] Up GOWS
Fixes for poll.vote using @lid
fix #1561
fix #1570

Ignore status@broadcast for ack updates
fix #1557

Increase gRPC limit to 512MB
fix #1365
2025-11-07 13:55:03 +07:00
devlikepro 1c41b8be65 [core] Add mentions: ["all"] to /api/sendText in groups - WEBJS, GOWS, NOWEB
fix #1377
2025-11-07 13:55:03 +07:00
devlikepro f72ee7c1f8 [core] Add /api/:session/groups/:id/participants/v2 - WEBJS, GOWS, NOWEB 2025-11-07 13:55:03 +07:00
devlikepro e75912435b [core] GOWS - support mentions
fix #1372
2025-11-07 13:55:02 +07:00
devlikepro da294a58a6 [core] GOWS - increase gRPC limits to 512MB
fix #1365
2025-11-07 13:55:01 +07:00
devlikepro 7689199e11 [core] ChatWoot - no chatwoot/waha mention in links for jobs 2025-11-07 13:55:01 +07:00
devlikepro 324ab11f09 [core] Fix Chat UI on dashboard (sortBy: messageTimestamp in /messages)
fix #1602
2025-11-07 13:55:00 +07:00
devlikepro b1f2e17e3d [core] Up NOWEB
fix #1590
fix #1605
2025-11-07 13:55:00 +07:00
devlikepro 90822cd582 [core] Up WEBJS
fix #1583
fix #1546
2025-11-07 13:54:59 +07:00
devlikepro 9eec72b641 [core] dotenv 2025-11-07 13:54:59 +07:00
devlikepro 050c652480 [core] Exclude /ping and /health using WAHA_API_KEY_EXCLUDE_PATH from all auth
fix #1562
2025-11-07 13:54:59 +07:00
devlikepro c44680c987 [core] 2025.10.5
Release / WEBJS - chromium - linux/arm64 - arm (push) Waiting to run
Release / GOWS - none - amd64 - gows (push) Waiting to run
Release / GOWS - none - linux/arm64 - gows-arm (push) Waiting to run
Release / WEBJS - chrome - amd64 - chrome (push) Waiting to run
Release / WEBJS - chromium - amd64 - latest (push) Waiting to run
Release / NOWEB - none - amd64 - noweb (push) Waiting to run
Release / NOWEB - none - linux/arm64 - noweb-arm (push) Waiting to run
2025-11-04 14:00:34 +07:00
devlikepro 9394509a07 [core] ChatWoot - timestamp for old messages 2025-11-04 14:00:33 +07:00
devlikepro 63890dc499 [core] ChatWoot - hour12 2025-11-04 14:00:33 +07:00
devlikepro e2330901e4 [core] ChatWoot - resolve conversations option 2025-11-04 14:00:32 +07:00
devlikepro 58672fa1e1 [core] ChatWoot - sent from at the top 2025-11-04 14:00:32 +07:00
devlikepro 3890071f77 [core] ChatWoot - Album Message
fix #1405

closes devlikeapro/waha-plus#24

(cherry picked from bergpinheiro/waha-plus@947fc000bb)

Thanks @bergpinheiro
Co-authored-by: bergpinheiro <berg.pinheiro2009@gmail.com>
2025-11-04 14:00:31 +07:00
devlikepro d60dfba90a [core] ChatWoot - no from me tag for history (from whatsapp or API) 2025-11-04 14:00:31 +07:00
devlikepro 4e58713e7e [core] Up GOWS - sort messages API, ctx 2025-11-04 14:00:31 +07:00
devlikepro 6f0af3c7cc [core] Up dashboard - chatwoot queue commands 2025-11-04 14:00:30 +07:00
devlikepro 23bac2e7b1 [core] ChatWoot - no additional content if we know it sent from CW 2025-11-04 14:00:30 +07:00
devlikepro e839203dbd [core] i18n 2025-11-04 14:00:30 +07:00
devlikepro 304b0037ec [core] ChatWoot - queue management 2025-11-04 14:00:28 +07:00
devlikepro 482dbbeb0e [core] ChatWoot - messages pull
[core] ChatWoot - HasMediaNoMedia message

fix #1231
2025-11-04 14:00:26 +07:00
devlikepro 8f4c79baf4 [core] Up ChatWoot to 4.7.0 2025-11-04 14:00:26 +07:00
devlikepro 31038a9318 [core] redis lock - trace log 2025-11-04 14:00:26 +07:00
devlikepro 86fde1f670 [core] WEBJS - fix get messages offset 2025-11-04 14:00:26 +07:00
devlikepro 984ef0c6a1 [core] NOWEB - Ignore history sync notification 2025-11-04 14:00:25 +07:00
devlikepro 744ee1abb1 [core] ChatWoot - Increase CONVERSATION_MESSAGE_PER_MINUTE_LIMIT to 3000 by default for "messages pull" 2025-11-04 14:00:25 +07:00
devlikepro 47f4f2670d [core] NOWEB - sqlite3 increase timeouts 2025-11-04 14:00:24 +07:00
devlikepro f16eaed935 [core] ChatWoot - keep mapping for 365 days 2025-11-04 14:00:24 +07:00
devlikepro dfe9454a72 [core] ChatWoot - debug logging for API requests 2025-11-04 14:00:24 +07:00
devlikepro 4aef38ff04 [core] Add sortOrder, sortBy to /messages 2025-11-04 14:00:22 +07:00
devlikepro ab8de51022 [core] ChatWoot - no chatwoot in user notifications 2025-11-04 14:00:22 +07:00
devlikepro c001d7fed6 [core] ChatWoot - contacts pull
fix #1231
2025-11-04 14:00:21 +07:00
devlikepro cc056dca9c [core] ChatWoot - timeZoneName short 2025-11-04 14:00:19 +07:00
devlikepro 47f60a1626 [core] cli 2025-11-04 14:00:18 +07:00
devlikepro 98721051da [core] parse jid - normalize it before
fix #1545
2025-11-04 14:00:18 +07:00
devlikepro 5d7a6f4ff7 [core] WEBJS - normalize "from" in message.reaction
fix #1545
2025-11-04 14:00:18 +07:00
devlikepro dcb7bf033f [core] ChatWoot WEBJS - fix message.reaction
fix #1545
2025-11-04 14:00:17 +07:00
devlikepro 7d29be2d69 [core] ChatWoot - mark read only if chatwoot message found 2025-11-04 14:00:17 +07:00
devlikepro 41b669a35c [core] 2025.10.4
Release / WEBJS - chrome - amd64 - chrome (push) Waiting to run
Release / WEBJS - chromium - amd64 - latest (push) Waiting to run
Release / WEBJS - chromium - linux/arm64 - arm (push) Waiting to run
Release / GOWS - none - amd64 - gows (push) Waiting to run
Release / GOWS - none - linux/arm64 - gows-arm (push) Waiting to run
Release / NOWEB - none - amd64 - noweb (push) Waiting to run
Release / NOWEB - none - linux/arm64 - noweb-arm (push) Waiting to run
2025-10-19 17:08:06 +07:00
devlikepro 7b5ca3bc57 [core] Up Dashboard 2025-10-19 17:08:06 +07:00
devlikepro bb0c7fd22b [core] NOWEB, GOWS - better handle message.edited
fix #1394
2025-10-19 17:08:05 +07:00
devlikepro c79958541e [core] GOWS - use proto type 2025-10-19 17:08:05 +07:00
devlikepro 94ee8863d6 [core] Up Dashboard - headers, agent name 2025-10-19 17:08:04 +07:00
devlikepro 011477262d [core] Up NOWEB
fix #1538
2025-10-19 17:08:04 +07:00
devlikeproandbergpinheiro 2e8e4cefb0 [core] Fixes inverted logic of messages read in ChatWoot
closes devlikeapro/waha-plus#20

Adapted from: bergpinheiro/read-chatwoot-invertida@ef2ff1b9de

(cherry picked from commit ef2ff1b9deceeea3c26747531e26d66e96f931fd)
(cherry picked from commit 3dc3440d2d5077279934577007f89cd640f60d9e)

Co-authored-by: bergpinheiro <bergpinheiro@users.noreply.github.com>"
2025-10-19 17:08:04 +07:00
devlikepro 5e2802c098 [core] metadata is Record 2025-10-19 17:08:04 +07:00
devlikepro 9a6250a055 [core] Add jid to MeInfo type 2025-10-19 17:08:03 +07:00
devlikepro f8cb56525a [core] has recent 2025-10-19 17:08:03 +07:00
devlikepro b5b491cb7b [core] en in waha 2025-10-19 17:08:02 +07:00
devlikepro 7eb6be70f9 [core] dev image 2025-10-19 17:08:02 +07:00
devlikepro 0de8a5fabc [core] NOWEB - Fix sending media to channels
fix #1523
2025-10-19 17:08:02 +07:00
Berg Pinheiro 9fbc18fb70 [core] Fix URL breaking in ChatWoot integration
- Fix WhatsappToMarkdown function to preserve URLs with underscores
- URLs are now protected before markdown transformations
- Prevents Sarah_Jessica_Parker from becoming Sarah*Jessica*Parker
- Add comprehensive tests for URL preservation scenarios
- Maintains existing markdown formatting for non-URL text
- All comments in English for code consistency

Fixes: Links with underscores being broken in ChatWoot messages

fix devlikeapro/waha#1522
closes devlikeapro/waha-plus#19
2025-10-19 17:08:02 +07:00
devlikepro e7ce3368b0 [core] Fix tests 2025-10-19 17:08:02 +07:00
devlikepro 729600e9c6 [core] voice 2025-10-17 11:44:55 +07:00
devlikepro 54886f385b [core] Disable pt 2025-10-17 10:41:35 +07:00
devlikepro 81854ad7ec [core] Fix smoke test
Release / WEBJS - chrome - amd64 - chrome (push) Waiting to run
Release / WEBJS - chromium - amd64 - latest (push) Waiting to run
Release / WEBJS - chromium - linux/arm64 - arm (push) Waiting to run
Release / GOWS - none - amd64 - gows (push) Waiting to run
Release / GOWS - none - linux/arm64 - gows-arm (push) Waiting to run
Release / NOWEB - none - amd64 - noweb (push) Waiting to run
Release / NOWEB - none - linux/arm64 - noweb-arm (push) Waiting to run
2025-10-16 17:07:47 +07:00
devlikepro 5261d4f281 [core] 2025.10.3 2025-10-16 16:57:01 +07:00
devlikepro 72d9d94342 [core] NOWEB - use upstream libsignal 2025-10-16 16:57:01 +07:00
devlikepro b782ad27a2 [core] ChatWoot - mark read fix one more time 2025-10-16 16:56:36 +07:00
devlikepro d7dad5e6c2 [core] rm unused imports 2025-10-16 16:56:35 +07:00
devlikepro 8765bb4412 [core] init-waha 2025-10-16 16:56:35 +07:00
devlikepro 984be25d51 [core] Replace 111 as well 2025-10-16 16:56:30 +07:00
devlikepro 70620207dd [core] Adjust .env 2025-10-16 16:56:29 +07:00
devlikepro a96200f981 [core] Adjust message 2025-10-16 16:56:29 +07:00
devlikepro 4978abf3d8 [core] Up GOWS
fix #1503
fix #1511
2025-10-16 16:56:27 +07:00
devlikepro a67d84f21e [core] ChatWoot - List Message 2025-10-16 16:56:27 +07:00
devlikepro 91f59b38fd [core] Dashboard - Chats
fix #1454
2025-10-16 16:56:27 +07:00
devlikepro d0ec351910 [core] Fix /api/sendImage when using proxy
fix #1449
fix #1510
2025-10-16 16:56:26 +07:00
devlikepro a7b12874fb [core] Require auth by default 2025-10-16 16:56:24 +07:00
devlikepro cd2c7bdd86 [core] Up creds in .env 2025-10-16 16:56:24 +07:00
devlikepro 045077143f [core] no warning about plain key 2025-10-16 16:56:24 +07:00
devlikepro bcd2199687 [core] ChatWoot - remove (WAHA) from contact name 2025-10-16 16:56:20 +07:00
devlikepro af108eede4 [core] NOWEB - normalize @lid
fix #1493
2025-10-16 16:56:19 +07:00
devlikepro ee1cf838b8 [core] NOWEB - use IPv4 by default 2025-10-16 16:56:19 +07:00
devlikepro 0fc74895da [core] Up NOWEB - add more logs for media 2025-10-16 16:56:19 +07:00
devlikepro 394937c154 [core] 2025.10.2
Release / WEBJS - chrome - amd64 - chrome (push) Waiting to run
Release / WEBJS - chromium - amd64 - latest (push) Waiting to run
Release / WEBJS - chromium - linux/arm64 - arm (push) Waiting to run
Release / GOWS - none - amd64 - gows (push) Waiting to run
Release / GOWS - none - linux/arm64 - gows-arm (push) Waiting to run
Release / NOWEB - none - amd64 - noweb (push) Waiting to run
Release / NOWEB - none - linux/arm64 - noweb-arm (push) Waiting to run
2025-10-14 15:21:40 +07:00
devlikepro 71f9adeff4 [core] ChatWoot - ignore fromMe in ack
mention #1492
2025-10-14 15:21:40 +07:00
devlikepro e13ccdab9c [core] ChatWoot - disable error/succeeded messages
mention #1492
mention #1395
2025-10-14 15:21:40 +07:00
devlikepro 2074748a7a [core] ChatWoot - no message.ack error
mention #1492
2025-10-14 15:21:39 +07:00
devlikepro 283d63394e [core] Up NOWEB
fix #1495
fix #1499
2025-10-14 15:21:39 +07:00
devlikepro 5cfb53c367 [core] 2025.10.1
Release / WEBJS - chrome - amd64 - chrome (push) Waiting to run
Release / WEBJS - chromium - amd64 - latest (push) Waiting to run
Release / WEBJS - chromium - linux/arm64 - arm (push) Waiting to run
Release / GOWS - none - amd64 - gows (push) Waiting to run
Release / GOWS - none - linux/arm64 - gows-arm (push) Waiting to run
Release / NOWEB - none - amd64 - noweb (push) Waiting to run
Release / NOWEB - none - linux/arm64 - noweb-arm (push) Waiting to run
2025-10-12 18:45:57 +07:00
devlikepro 9466407b8a [core] ChatWoot - update last seen (on message.ack)
closes #1436
fixes #1319
2025-10-12 18:45:56 +07:00
devlikepro 4b65f0c462 [core] ChatWoot - up to v4.6.0 2025-10-12 18:45:56 +07:00
devlikepro 23504b35c8 [core] ChatWoot - remove /n at the end of any templated 2025-10-12 18:45:55 +07:00
devlikepro dd6ec4ca3a [core] ChatWoot - Pix fixes 2025-10-12 18:45:55 +07:00
devlikepro 8235e5a111 [core] ChatWoot - i18n - pt enable timeZone Sao Paulo 2025-10-12 18:45:55 +07:00
devlikepro 55499a6144 [core] ChatWoot - i18n - datetime 2025-10-12 18:45:54 +07:00
devlikepro 9dbc4a6085 [core] ChatWoot - i18n - poll, event, pix 2025-10-12 18:45:54 +07:00
devlikepro f7c6ce847c [core] ChatWoot - i18n - poll no empty line 2025-10-12 18:45:53 +07:00
devlikepro 84eed00415 [core] ChatWoot - i18n - location url (not URL) 2025-10-12 18:45:53 +07:00
devlikepro 51aa0e3154 [core] ChatWoot - i18n - datetime 2025-10-12 18:45:52 +07:00
devlikepro 77893a0eb0 [core] ChatWoot - i18n - locale base 2025-10-12 18:45:52 +07:00
devlikepro 4a866d8a08 [core] ChatWoot - PIX
(cherry picked from bergpinheiro/waha@696de94859)

Thanks @bergpinheiro
Co-authored-by: bergpinheiro <berg.pinheiro2009@gmail.com>
2025-10-12 18:45:52 +07:00
devlikepro f912353a5c [core] ChatWoot - Event Message
(cherry picked from bergpinheiro/waha@696de94859)

Thanks @bergpinheiro
Co-authored-by: bergpinheiro <berg.pinheiro2009@gmail.com>
2025-10-12 18:45:51 +07:00
devlikepro a6a1d8dc28 [core] ChatWoot - Poll message
(cherry picked from bergpinheiro/waha@696de94859)

Thanks @bergpinheiro
Co-authored-by: bergpinheiro <berg.pinheiro2009@gmail.com>
2025-10-12 18:45:50 +07:00
devlikepro 150e73aa74 [core] ChatWoot - adjust location message
(cherry picked from bergpinheiro/waha@696de94859)

Thanks @bergpinheiro
Co-authored-by: bergpinheiro <berg.pinheiro2009@gmail.com>
2025-10-12 18:45:49 +07:00
devlikepro a9300b3134 [core] ChatWoot add URL test 2025-10-12 18:45:48 +07:00
devlikepro bbe3859085 [core] ChatWoot - Facebook Ads Message Template
closes #1461

(cherry picked from bergpinheiro/waha@5863590208)

Thanks @bergpinheiro
Co-authored-by: bergpinheiro <berg.pinheiro2009@gmail.com>
2025-10-12 18:45:48 +07:00
devlikepro b420548dd9 [core] export fetchBuffer 2025-10-12 18:45:47 +07:00
devlikepro 741483eec7 [core] ChatWoot - remove unused getAttachments in message.reaction 2025-10-12 18:45:47 +07:00
devlikepro 2acf7e4e17 [core] ChatWoot - add name for location message 2025-10-12 18:45:46 +07:00
devlikepro 9bca6d904d [core] ChatWoot - Refactor message.any 2025-10-12 18:45:45 +07:00
devlikepro 857c586df6 [core] AGENTS.md 2025-10-12 18:45:45 +07:00
devlikepro df8cb7cecb [core] NOWEB - fix ssl issue
fix #1474
2025-10-12 18:45:45 +07:00
devlikepro 9d0050adb8 [core] NOWEB - filter PEER_DATA_OPERATION_REQUEST_RESPONSE_MESSAGE
fix #1459
2025-10-12 18:45:44 +07:00
devlikepro df927ab04a [core] Remove + at the start
fix #1475
2025-10-12 18:45:43 +07:00
devlikepro 02e5934e8f [core] Up NOWEB 2025-10-10 - fixes 2025-10-12 18:45:42 +07:00
devlikepro 825a4152db [core] Up NOWEB 2025-10-10 2025-10-12 18:45:41 +07:00
devlikepro 81b10099af [core] Up WEBJS
fix #1469
fix #1468
2025-10-12 18:45:41 +07:00
devlikepro 3be5f328b0 [core] Up GOWS 2025-10-12 18:45:41 +07:00
devlikepro 5caa3b8b81 [core] do not rename title 2025-10-10 10:07:02 +07:00
devlikepro 8231bf7d8e [core] GitHub - en/pt by default 2025-10-06 18:30:15 +07:00
devlikepro 22d94808fb [core] Fix lint
Release / WEBJS - chrome - amd64 - chrome (push) Waiting to run
Release / WEBJS - chromium - amd64 - latest (push) Waiting to run
Release / WEBJS - chromium - linux/arm64 - arm (push) Waiting to run
Release / GOWS - none - amd64 - gows (push) Waiting to run
Release / GOWS - none - linux/arm64 - gows-arm (push) Waiting to run
Release / NOWEB - none - amd64 - noweb (push) Waiting to run
Release / NOWEB - none - linux/arm64 - noweb-arm (push) Waiting to run
2025-10-04 17:12:34 +07:00
devlikepro 57daefa680 [core] 2025.9.8 2025-10-04 17:00:06 +07:00
devlikepro 5cf8704973 [core] NOWEB - no await in start
fix #1397
2025-10-04 17:00:05 +07:00
devlikepro 28da9c34bb [core] Fix NOWEB Apps on start
fix #1397
2025-10-04 17:00:05 +07:00
devlikepro 51ba5e3266 [core] Up GOWS
fix #1293
fix #1412
fix #1406
2025-10-04 17:00:05 +07:00
devlikepro f696794ee1 [core] Up WEBJS
fix #1276
fix #1407
2025-10-04 17:00:04 +07:00
devlikepro c6eca9510b [core] NOWEB - 2025-10-03 2025-10-04 17:00:04 +07:00
devlikepro 9a2fd2fccd [core] make up* 2025-10-04 17:00:04 +07:00
devlikepro c0a2d02f0d [core] NOWEB - send poll to newsletters
fix #1270
fix #1415
2025-10-04 17:00:04 +07:00
devlikepro 6493d16fd2 [core] NOWEB - try to get message just by id for poll
fix #1396
2025-10-04 17:00:04 +07:00
devlikeapro 9f96c678dc [core] support CSAT messages in webhook processing
fix(chatwoot): support CSAT messages in webhook processing
2025-10-04 16:59:36 +07:00
Berg Pinheiro a81f2ab553 fix(chatwoot): support CSAT messages in webhook processing
- Add support for 'input_csat' content_type in MessageHandler
- Previously CSAT messages were being ignored due to strict content_type check
- CSAT messages now processed as text messages and sent to WhatsApp
- Fixes issue where Chatwoot CSAT surveys were not being delivered to users

Changes:
- Modified content_type validation in message_created.ts to accept both 'text' and 'input_csat'
- CSAT messages will now be sent to WhatsApp with their survey content and links
- Maintains existing behavior for all other message types
2025-10-01 22:23:36 -03:00
devlikepro 8fbf7fa0e5 [core] ChatWoot - brazil phone number
Release / WEBJS - chrome - amd64 - chrome (push) Waiting to run
Release / WEBJS - chromium - amd64 - latest (push) Waiting to run
Release / WEBJS - chromium - linux/arm64 - arm (push) Waiting to run
Release / GOWS - none - amd64 - gows (push) Waiting to run
Release / GOWS - none - linux/arm64 - gows-arm (push) Waiting to run
Release / NOWEB - none - amd64 - noweb (push) Waiting to run
Release / NOWEB - none - linux/arm64 - noweb-arm (push) Waiting to run
fix #1261
2025-09-29 12:10:09 +07:00
devlikepro af0bba313a [core] 2025.9.7 2025-09-29 12:10:09 +07:00
devlikepro 76307dec12 [core] 2025.9.6
Release / WEBJS - chrome - amd64 - chrome (push) Waiting to run
Release / WEBJS - chromium - amd64 - latest (push) Waiting to run
Release / WEBJS - chromium - linux/arm64 - arm (push) Waiting to run
Release / GOWS - none - amd64 - gows (push) Waiting to run
Release / GOWS - none - linux/arm64 - gows-arm (push) Waiting to run
Release / NOWEB - none - amd64 - noweb (push) Waiting to run
Release / NOWEB - none - linux/arm64 - noweb-arm (push) Waiting to run
2025-09-29 09:09:47 +07:00
devlikepro 0b441054ae [core] Up dashboard 2025-09-29 09:09:46 +07:00
devlikepro 2e8b78aebc [core] ChatWoot fix duplicating messages for transcribe feature
fix #1371
2025-09-29 09:09:45 +07:00
devlikepro d01b221a24 [core] Add ChatWootAppConfig to openapi
fix #1416
2025-09-29 09:09:44 +07:00
devlikepro 61bb111768 [core] ChatWoot - conversation selector
fix #1216
fix #1357
fix #1237
close #1343
close #1213
2025-09-29 09:09:43 +07:00
devlikepro de47d6e445 [core] ChatWoot - list custom attributes once on upsert 2025-09-29 09:09:41 +07:00
devlikepro 59fe4b30f0 [core] ChatWoot - use Service suffix 2025-09-29 09:09:40 +07:00
devlikepro 166dae8e27 [core] ChatWoot - fix brazil numbers
fix #1261
2025-09-29 09:09:39 +07:00
devlikepro 5ce64fd30d [core] ChatWoot - check JID format 2025-09-29 09:09:37 +07:00
devlikepro 1a4513f437 [core] GOWS - do not fail in case of wrong numbers
fix #1173
2025-09-29 09:09:37 +07:00
devlikepro 59be4a780f [core] ChatWoot - check attributes before updating
fix #1173
2025-09-29 09:09:37 +07:00
devlikepro 74001c2b49 [core] ChatWoot - get group name
fix #1245
2025-09-29 09:09:37 +07:00
devlikepro a9ce0fb2d9 [core] ChatWoot - fix /jobs auth
fix #1398
2025-09-29 09:09:36 +07:00
devlikepro 4c3fd1c214 [core] ChatWoot - do not send STOPPED if server restarts 2025-09-29 09:09:34 +07:00
devlikepro a9459fe746 [core] ChatWoot - logging 2025-09-29 09:09:32 +07:00
devlikepro 5a3228f05b [core] ChatWoot - safe get avatar from WhatsApp 2025-09-29 09:09:31 +07:00
devlikepro befc238db9 [core] ChatWoot - fix 500 Error - use Account API to fetch contact conversations list
fix #1389
2025-09-29 09:09:31 +07:00
devlikepro ae75a1f8d4 [core] ChatWoot - use JobLink for success 2025-09-29 09:09:30 +07:00
devlikepro d1c50bfd73 [core] ChatWoot - add WAHA_PUBLIC_URL 2025-09-29 09:09:30 +07:00
devlikepro 6f540366f4 [core] ChatWoot - add RACK_TIMEOUT_SERVICE_TIMEOUT 60s
mention #1389
2025-09-29 09:09:29 +07:00
devlikepro 151fc760fe [core] ChatWoot log additional errors 2025-09-29 09:09:29 +07:00
devlikepro be4391a4b6 [core] ChatWoot fix reply if not found 2025-09-29 09:09:29 +07:00
devlikepro 76ae3e0f6b [core] 2025.9.5
Release / WEBJS - chrome - amd64 - chrome (push) Waiting to run
Release / WEBJS - chromium - amd64 - latest (push) Waiting to run
Release / WEBJS - chromium - linux/arm64 - arm (push) Waiting to run
Release / GOWS - none - amd64 - gows (push) Waiting to run
Release / GOWS - none - linux/arm64 - gows-arm (push) Waiting to run
Release / NOWEB - none - amd64 - noweb (push) Waiting to run
Release / NOWEB - none - linux/arm64 - noweb-arm (push) Waiting to run
2025-09-23 15:51:01 +07:00
devlikepro f37b6edaa6 [core] Up Dashboard 2025-09-23 15:51:00 +07:00
devlikepro 7095f77f93 [core] GOWS - v1.0.15 2025-09-23 15:51:00 +07:00
devlikepro 5853b778f2 [core] GOWS - fix "typing" after message
fix #1364
2025-09-23 15:51:00 +07:00
devlikepro 6405725ce6 [core] WEBJS - send vcard
fix #1341
2025-09-23 15:50:59 +07:00
devlikepro 111d6141aa [core] WEBJS - fix login via code on new version 2025-09-23 15:50:58 +07:00
devlikepro 5560b3c8e6 [core] NOWEB - login via phone
fix #1385
2025-09-23 15:50:58 +07:00
devlikepro d528d541ea [core] NOWEB - fix fromMe in group messages
fix #1350
2025-09-23 15:50:57 +07:00
devlikepro ab6b51cc1f [core] NOWEB - normalized lid 2025-09-23 15:50:57 +07:00
devlikepro 35aa4f2658 [core] WEBJS - fix status messages 2025-09-23 15:50:56 +07:00
devlikepro 36db3c37e8 [core] WEBJS - fix channel role 2025-09-23 15:50:55 +07:00
devlikepro b0c9232153 [core] Update WEBJS 2025-09-23 15:50:55 +07:00
devlikepro 0070fb9e5c [core] Update NOWEB, use ESM wrapper 2025-09-23 15:50:53 +07:00
devlikepro aa33ac4b1d [core] NOWEB - handle lid-mapping.update 2025-09-23 15:50:52 +07:00
devlikepro 1c2bc5b395 [core] NOWEB - no clean storage on history sync 2025-09-23 15:50:51 +07:00
devlikepro f2fe18a4c5 [core] NOWEB Handle lottie stickers using .zip 2025-09-23 15:50:51 +07:00
devlikepro 87fdcbebf6 [core] NOWEB Use directPath for newsletters 2025-09-23 15:50:51 +07:00
devlikepro 68a4f651ea [core] pin postgres:17.6
fix #1380
2025-09-23 15:50:50 +07:00
devlikepro 243caa9626 [core] ChatWoot - group participant at the bottom
fix #1369
2025-09-23 15:50:50 +07:00
devlikepro 863684b916 [core] ChatWoot - fix statuses.at
Release / WEBJS - chrome - amd64 - chrome (push) Waiting to run
Release / WEBJS - chromium - amd64 - latest (push) Waiting to run
Release / WEBJS - chromium - linux/arm64 - arm (push) Waiting to run
Release / GOWS - none - amd64 - gows (push) Waiting to run
Release / GOWS - none - linux/arm64 - gows-arm (push) Waiting to run
Release / NOWEB - none - amd64 - noweb (push) Waiting to run
Release / NOWEB - none - linux/arm64 - noweb-arm (push) Waiting to run
2025-09-15 15:49:13 +07:00
devlikepro 2d543f3728 [core] Fix WAHA Dashboard /jobs creds 2025-09-15 15:49:13 +07:00
devlikepro 47088cfbee [core] 2025.9.4 2025-09-15 15:40:29 +07:00
devlikepro 66c75cbf79 [core] ChatWoot - add |- in yaml i18n - remove \n
fix #1161
2025-09-15 15:40:21 +07:00
devlikepro b2c68d88ae [core] ChatWoot - show attachments preview
fix #1340
2025-09-15 15:40:21 +07:00
devlikepro 675e998cba [core] ChatWoot - avoid WORKING => STARTING => WORKING status changes every N minutes
fix #1342
2025-09-15 15:40:20 +07:00
devlikepro db1d20ae36 [core] Add 'statuses' points to session.status event 2025-09-15 15:40:20 +07:00
devlikepro 59b40dda4e [core] ChatWoot - fix "TypeError: Cannot read properties of undefined (reading 'send')" error 2025-09-15 15:40:20 +07:00
devlikepro 317fab4dfe [core] 2025.9.3
Release / WEBJS - chrome - amd64 - chrome (push) Waiting to run
Release / WEBJS - chromium - amd64 - latest (push) Waiting to run
Release / WEBJS - chromium - linux/arm64 - arm (push) Waiting to run
Release / GOWS - none - amd64 - gows (push) Waiting to run
Release / GOWS - none - linux/arm64 - gows-arm (push) Waiting to run
Release / NOWEB - none - amd64 - noweb (push) Waiting to run
Release / NOWEB - none - linux/arm64 - noweb-arm (push) Waiting to run
2025-09-14 17:48:58 +07:00
devlikepro 6c83ab68b4 [core] Up Dashboard 2025-09-14 17:48:57 +07:00
devlikepro 2194465757 [core] Up GOWS 2025-09-14 17:48:57 +07:00
devlikepro a0e70f88ad [core] NOWEB - update proto to v1025985905 2025-09-14 17:48:56 +07:00
devlikepro 9102a3d852 [core] WEBJS - Add WAHA_WEBJS_PUPPETER_ARGS to test --single-process
fix #1348
2025-09-14 17:48:55 +07:00
devlikepro b05cba896e [core] WEBJS - Update Chrome, Puppeter
fix #1348
2025-09-14 17:48:55 +07:00
devlikepro 6716a31b6c [core] WEBJS - --disable-blink-features=AutomationControlled
fix #1348
2025-09-14 17:48:54 +07:00
devlikepro 2e42d0e7eb [core] ChatWoot - receive messages from broadcast list
fix #1321
2025-09-14 17:48:54 +07:00
devlikepro dee9864157 [core] ChatWoot API Error 2025-09-14 17:48:53 +07:00
devlikepro 0293fc8c6f [core] Fix cases when message.media constains _data when no media attached 2025-09-14 17:48:52 +07:00
devlikepro 725fdc03a6 [core] ChatWoot - i18n 2025-09-14 17:48:49 +07:00
devlikepro 4557cb695f [core] ChatWoot - refactoring 2025-09-14 17:48:49 +07:00
devlikepro 172403a061 [core] ChatWoot - share contacts
fix #1175
2025-09-14 17:48:48 +07:00
devlikepro d03c3c185e [core] ChatWoot - share location (current and live)
fix #1205
2025-09-14 17:48:47 +07:00
devlikepro 50a111343c [core] ChatWoot - fix audio and pdf preview
fix #1330
fix #1340
2025-09-14 17:48:46 +07:00
devlikepro f01693adc3 [core] ChatWoot - Unsupported messages from WhatsApp
fix #1267
2025-09-14 17:48:46 +07:00
devlikepro d2bbbb4448 [core] Apps - JobLink 2025-09-14 17:48:45 +07:00
devlikepro 38c0aadff2 [core] ChatWoot - ignore not set WAHA_DASHBOARD_ENABLED - treat as true 2025-09-14 17:48:44 +07:00
devlikepro 79d593b155 [core] ChatWoot - customize templates via API
fix #1160
fix #1161
2025-09-14 17:48:43 +07:00
devlikepro ddc84cfecc [core] ChatWoot - customize messages
fix #1161
2025-09-14 17:48:42 +07:00
devlikepro b620383d94 [core] ChatWoot - do not send empty text messages 2025-09-14 17:48:42 +07:00
devlikepro 2319dba68d [core] ChatWoot - load languages WAHA_APPS_CHATWOOT_LANGUAGES_FOLDER
fix #1160
2025-09-14 17:48:41 +07:00
devlikepro 0c388ee3a7 [core] ChatWoot - set link preview
fix #1162
fix #1274
2025-09-14 17:48:40 +07:00
devlikepro fb5b3e2964 [core] Use ChatWootConfig 2025-09-14 17:48:39 +07:00
devlikepro d44eaa5b41 [core] Disable commands.server
fix #1182
fix #1176
2025-09-14 17:48:38 +07:00
devlikepro 2eaf05df27 [core] Update i18n 2025-09-14 17:48:37 +07:00
devlikepro 9e5431e205 [core] Update typescript 2025-09-14 17:48:35 +07:00
devlikepro 26c3102af8 [core] ChatWoot - disable app
fix #1260
2025-09-14 17:48:34 +07:00
devlikepro 104f06ef2b [core] ChatWoot - docker compose extra_hosts 2025-09-14 17:48:34 +07:00
devlikepro 93c8654e84 [core] Up chatwoot 4.5.2 2025-09-14 17:48:34 +07:00
devlikepro a7261790bd [core] Add ignore.broadcast
fix #1346
2025-09-14 17:48:34 +07:00
devlikepro d8d6e521f6 [core] Add excel to examples 2025-09-14 16:57:51 +07:00
devlikepro 02497bcd40 [core] 2025.9.2
Release / WEBJS - chromium - linux/arm64 - arm (push) Waiting to run
Release / GOWS - none - amd64 - gows (push) Waiting to run
Release / GOWS - none - linux/arm64 - gows-arm (push) Waiting to run
Release / NOWEB - none - amd64 - noweb (push) Waiting to run
Release / NOWEB - none - linux/arm64 - noweb-arm (push) Waiting to run
Release / WEBJS - chrome - amd64 - chrome (push) Waiting to run
Release / WEBJS - chromium - amd64 - latest (push) Waiting to run
2025-09-05 11:19:04 +07:00
devlikepro b498e15620 [core] NOWEB - Fix message.any fromMe: 1
fix #1318
fix #1338
2025-09-05 11:19:04 +07:00
devlikepro 1288d312c0 [core] Update Dashboard - WAHA_API_KEY warning 2025-09-05 11:19:04 +07:00
devlikepro 84cd350c42 [core] 2025.9.1
Release / WEBJS - chrome - amd64 - chrome (push) Waiting to run
Release / WEBJS - chromium - amd64 - latest (push) Waiting to run
Release / WEBJS - chromium - linux/arm64 - arm (push) Waiting to run
Release / GOWS - none - amd64 - gows (push) Waiting to run
Release / GOWS - none - linux/arm64 - gows-arm (push) Waiting to run
Release / NOWEB - none - amd64 - noweb (push) Waiting to run
Release / NOWEB - none - linux/arm64 - noweb-arm (push) Waiting to run
2025-09-02 18:14:14 +07:00
devlikepro 100bf89074 [core] WEBJS - fix STARTING session
fix #1301
fix #1315
fix #1314
fix #1312
2025-09-02 18:14:14 +07:00
devlikepro d66c80f95a [core] 2025.8.3
Release / WEBJS - chrome - amd64 - chrome (push) Waiting to run
Release / WEBJS - chromium - amd64 - latest (push) Waiting to run
Release / WEBJS - chromium - linux/arm64 - arm (push) Waiting to run
Release / GOWS - none - amd64 - gows (push) Waiting to run
Release / GOWS - none - linux/arm64 - gows-arm (push) Waiting to run
Release / NOWEB - none - amd64 - noweb (push) Waiting to run
Release / NOWEB - none - linux/arm64 - noweb-arm (push) Waiting to run
2025-08-30 18:30:07 +07:00
devlikepro 26f04bbd94 [core] Up Dashboard 2025-08-30 18:30:06 +07:00
devlikepro 6582c03823 [core] Up GOWS 2025-08-30 18:30:06 +07:00
devlikepro abacaf2e02 [core] WEBJS - fix STARTING session
fix #1298
fix #1299
fix #1300
2025-08-30 18:30:05 +07:00
devlikepro c65d8b9856 [core] GOWS - chat /unread 2025-08-30 18:30:05 +07:00
devlikepro 8f94cf06de [core] WEBJS - add webjs.tagsEventsOn config 2025-08-30 18:30:04 +07:00
devlikepro 94c1c16de8 [core] Use oxlint instead of eslint 2025-08-30 18:30:03 +07:00
devlikepro e69d992725 [core] Ignore - Status, Groups, Broadcast
fix #1142
fix #1259
fix #1190
2025-08-30 18:30:03 +07:00
devlikepro e6c5dd6e22 [core] eslint - no project 2025-08-30 18:30:02 +07:00
devlikepro 5486d4874a [core] Refactor media and processIncomingMessage 2025-08-30 18:30:02 +07:00
devlikepro 9d9d8e03fe [core] Add @types 2025-08-30 18:30:02 +07:00
devlikepro 6ee50243a2 [core] 2025.8.2
Release / GOWS - none - linux/arm64 - gows-arm (push) Waiting to run
Release / NOWEB - none - amd64 - noweb (push) Waiting to run
Release / NOWEB - none - linux/arm64 - noweb-arm (push) Waiting to run
Release / WEBJS - chrome - amd64 - chrome (push) Waiting to run
Release / WEBJS - chromium - amd64 - latest (push) Waiting to run
Release / WEBJS - chromium - linux/arm64 - arm (push) Waiting to run
Release / GOWS - none - amd64 - gows (push) Waiting to run
2025-08-25 10:31:26 +07:00
devlikepro 3ec078f9c1 [core] Update Dashboard 2025-08-25 10:31:26 +07:00
devlikepro 4c3209be6d [core] GOWS v1.0.12 2025-08-25 10:31:26 +07:00
devlikepro 7fe8f9a8dc [core] put "stack" last in errors 2025-08-25 10:31:25 +07:00
devlikepro 7fa2b29418 [core] GOWS - /api/sendPollVote
fix #1077
2025-08-25 10:31:25 +07:00
devlikepro dd4edd5a85 [core] show the engine name in errors 2025-08-25 10:31:24 +07:00
devlikepro b85df5a0d3 [core] GOWS - /api/sendLocation
fix #1250
2025-08-25 10:31:24 +07:00
devlikepro 1a7bec3d4e [core] NOWEB - add title for location, convert to right jid 2025-08-25 10:31:23 +07:00
devlikepro 727c4b454f [core] GOWS - Fix poll key
fix #1028
2025-08-25 10:31:23 +07:00
devlikepro 4ba86b34a9 [core] Add me.lid - GOWS, NOWEB 2025-08-25 10:31:22 +07:00
devlikepro 277a35aed9 [core] Add /api/sendList - NOWEB, GOWS
fix #393
fix #482
fix #715
fix #938
2025-08-25 10:31:21 +07:00
devlikepro 28c1ffa09f [core] ChatWoot - fix link preview for big files issue
fix #1162
2025-08-25 10:31:21 +07:00
devlikepro 10595680a9 [core] Dashboard - add zh - Simplified Chinese
fix #1193
2025-08-25 10:31:21 +07:00
devlikepro 577298dbc5 [core] ChatWoot - add zh-CN - Simplified Chinese
fix #1193
2025-08-25 10:31:21 +07:00
devlikepro 49ca4dfbf7 [core] rm LocaleCode 2025-08-25 10:31:21 +07:00
devlikepro 89ec47deec [core] 2025.8.1
Release / WEBJS - chrome - amd64 - chrome (push) Waiting to run
Release / WEBJS - chromium - amd64 - latest (push) Waiting to run
Release / WEBJS - chromium - linux/arm64 - arm (push) Waiting to run
Release / GOWS - none - amd64 - gows (push) Waiting to run
Release / GOWS - none - linux/arm64 - gows-arm (push) Waiting to run
Release / NOWEB - none - amd64 - noweb (push) Waiting to run
Release / NOWEB - none - linux/arm64 - noweb-arm (push) Waiting to run
2025-08-05 15:53:42 +07:00
devlikepro a043a46457 [core] API - limited characters in the session name
fix #1163
2025-08-05 15:53:42 +07:00
devlikepro eb8e3a0fd1 [core] WEBJS - fix create group
fix #1145
2025-08-05 15:53:41 +07:00
devlikepro fc25df8104 [core] NOWEB - fix channel list
fix #1177
2025-08-05 15:53:41 +07:00
devlikepro d8ffd3e543 [core] 2025.7.11
Release / WEBJS - chrome - amd64 - chrome (push) Waiting to run
Release / WEBJS - chromium - amd64 - latest (push) Waiting to run
Release / WEBJS - chromium - linux/arm64 - arm (push) Waiting to run
Release / GOWS - none - amd64 - gows (push) Waiting to run
Release / GOWS - none - linux/arm64 - gows-arm (push) Waiting to run
Release / NOWEB - none - amd64 - noweb (push) Waiting to run
Release / NOWEB - none - linux/arm64 - noweb-arm (push) Waiting to run
2025-08-03 13:59:23 +07:00
devlikepro ea4ec9310d [core] WEBJS - fix Caught error, dropping value from, event: 'message.ack'
fix #1178
2025-08-03 13:59:22 +07:00
devlikepro 479ec85e0d [core] Dashboard - i18n 2025-08-03 13:59:22 +07:00
devlikepro 3e4b450260 [core] ChatWoot - find chat id - jid, lid, chat id 2025-08-03 13:59:22 +07:00
devlikepro f6a81949d0 [core] ChatWoot - Urdu 2025-08-03 13:59:21 +07:00
devlikepro 6896962606 [core] Always show list of chatwoot languages
fix #1138
2025-08-03 13:59:21 +07:00
devlikepro 9b6d6ab4b0 [core] Inline LOCALE_NAME in yaml 2025-08-03 13:59:21 +07:00
devlikepro d5f399f7d5 [core] 2025.7.10
Release / WEBJS - chrome - amd64 - chrome (push) Waiting to run
Release / WEBJS - chromium - amd64 - latest (push) Waiting to run
Release / WEBJS - chromium - linux/arm64 - arm (push) Waiting to run
Release / GOWS - none - amd64 - gows (push) Waiting to run
Release / GOWS - none - linux/arm64 - gows-arm (push) Waiting to run
Release / NOWEB - none - amd64 - noweb (push) Waiting to run
Release / NOWEB - none - linux/arm64 - noweb-arm (push) Waiting to run
2025-07-27 17:39:17 +07:00
devlikepro dbd3bd024c [core] Up GOWS
Fix #1100
2025-07-27 17:39:16 +07:00
devlikepro 9ba7e19da3 [core] Use sqlite3 instead of better-sqlite3
better-sqlite3 can freeze the whole system
2025-07-27 17:39:16 +07:00
devlikepro 5f6d1412cf [core] Add sqlite3 2025-07-27 17:39:15 +07:00
devlikepro 0b5af3145e [core] Always save config, not null 2025-07-27 17:39:15 +07:00
devlikepro ad10ea4e96 [core] 2025.7.9
Release / WEBJS - chrome - amd64 - chrome (push) Waiting to run
Release / WEBJS - chromium - amd64 - latest (push) Waiting to run
Release / WEBJS - chromium - linux/arm64 - arm (push) Waiting to run
Release / GOWS - none - amd64 - gows (push) Waiting to run
Release / GOWS - none - linux/arm64 - gows-arm (push) Waiting to run
Release / NOWEB - none - amd64 - noweb (push) Waiting to run
Release / NOWEB - none - linux/arm64 - noweb-arm (push) Waiting to run
2025-07-25 12:46:33 +07:00
devlikepro 9de53a972a [core] Up NOWEB
fix #1143
2025-07-25 12:46:33 +07:00
devlikepro 4043efbb9a [core] Up Dashboard 2025-07-25 12:46:33 +07:00
devlikepro 42019644f0 [core] cpus*2 for UV_THREADPOOL_SIZE 2025-07-25 12:46:33 +07:00
devlikepro bcc25ef42f [core] Adjust UV_THREADPOOL_SIZE logic 2025-07-25 12:46:33 +07:00
devlikepro 602bbf3b60 [core] WEBJS - WAHA_RUN_XVFB
fix #1151
2025-07-25 12:46:32 +07:00
devlikeaproandsnyk-bot 585ac6074f [core] fix: package.json to reduce vulnerabilities (#16)
The following vulnerabilities are fixed with an upgrade:
- https://snyk.io/vuln/SNYK-JS-MULTER-10773732

Co-authored-by: snyk-bot <snyk-bot@snyk.io>
2025-07-24 10:37:37 +07:00
devlikepro 5bd1a43df6 [core] 2025.7.8
Release / WEBJS - chrome - amd64 - chrome (push) Waiting to run
Release / WEBJS - chromium - amd64 - latest (push) Waiting to run
Release / WEBJS - chromium - linux/arm64 - arm (push) Waiting to run
Release / GOWS - none - amd64 - gows (push) Waiting to run
Release / GOWS - none - linux/arm64 - gows-arm (push) Waiting to run
Release / NOWEB - none - amd64 - noweb (push) Waiting to run
Release / NOWEB - none - linux/arm64 - noweb-arm (push) Waiting to run
2025-07-23 15:02:05 +07:00
devlikepro c9279a4b59 [core] ChatWoot - send png as images 2025-07-23 15:02:04 +07:00
devlikepro 9ff8eed6ba [core] Add, edit, and remove contact API
fix #1124
2025-07-23 15:02:03 +07:00
devlikepro 96ffdd251f [core] Up GOWS 2025-07-23 15:01:53 +07:00
devlikepro 35b8b7d01c [core] 2025.7.7
Release / WEBJS - chrome - amd64 - chrome (push) Waiting to run
Release / WEBJS - chromium - amd64 - latest (push) Waiting to run
Release / WEBJS - chromium - linux/arm64 - arm (push) Waiting to run
Release / GOWS - none - amd64 - gows (push) Waiting to run
Release / GOWS - none - linux/arm64 - gows-arm (push) Waiting to run
Release / NOWEB - none - amd64 - noweb (push) Waiting to run
Release / NOWEB - none - linux/arm64 - noweb-arm (push) Waiting to run
2025-07-22 14:53:35 +07:00
devlikepro 4e2e2b2b3c [core] WEBJS - add 1GB cache back
fix #1131 fix #1140
2025-07-22 14:53:35 +07:00
devlikepro bfff0c8cf4 [core] 2025.7.6
Release / WEBJS - chrome - amd64 - chrome (push) Waiting to run
Release / WEBJS - chromium - amd64 - latest (push) Waiting to run
Release / WEBJS - chromium - linux/arm64 - arm (push) Waiting to run
Release / GOWS - none - amd64 - gows (push) Waiting to run
Release / GOWS - none - linux/arm64 - gows-arm (push) Waiting to run
Release / NOWEB - none - amd64 - noweb (push) Waiting to run
Release / NOWEB - none - linux/arm64 - noweb-arm (push) Waiting to run
2025-07-20 14:06:59 +07:00
devlikepro e253f445b9 [core] WEBJS - fix sendSeen
fix #1117
2025-07-20 14:06:58 +07:00
devlikepro 73ed663c9c [core] Dashboard - show tier 2025-07-20 14:06:58 +07:00
devlikepro 2236150b83 [core] ChatWoot - adjust commands list text 2025-07-20 14:06:58 +07:00
devlikepro da24feb390 [core] Fix "Not implemented by engine" in status
fix #1115
2025-07-20 14:06:58 +07:00
devlikepro 44a5ada162 [core] Fix "You provided 'null' where a stream was expected" 2025-07-20 14:06:58 +07:00
devlikepro e57cc2587c [core] WEBJS - share media properly 2025-07-20 14:06:58 +07:00
Alan Frigo 38bfb9baa5 Added WHATSAPP_HOOK_CUSTOM_HEADERS to env example 2025-07-20 13:57:46 +07:00
devlikepro 540636d59b [core] Fix MongoDB
Release / GOWS - none - amd64 - gows (push) Waiting to run
Release / GOWS - none - linux/arm64 - gows-arm (push) Waiting to run
Release / NOWEB - none - amd64 - noweb (push) Waiting to run
Release / NOWEB - none - linux/arm64 - noweb-arm (push) Waiting to run
Release / WEBJS - chrome - amd64 - chrome (push) Waiting to run
Release / WEBJS - chromium - amd64 - latest (push) Waiting to run
Release / WEBJS - chromium - linux/arm64 - arm (push) Waiting to run
2025-07-13 18:40:19 +07:00
devlikepro 3cc784353e [core] 2025.7.5 2025-07-13 18:32:54 +07:00
devlikepro 44946ee784 [core] Add ChatWoot App
fix #441
2025-07-13 18:32:53 +07:00
devlikepro a9d77487c6 [core] Fix LegacyKeyValueFormat in Dockerfile 2025-07-13 18:32:53 +07:00
devlikepro 70d4f27abd [core] WEBJS - ignore old reactions
fixes #494
2025-07-13 18:32:53 +07:00
devlikepro 9fca09661d [core] WEBJS - render process 2 2025-07-13 18:32:53 +07:00
devlikepro 2f0efd0223 [core] 2025.7.4
Release / GOWS - none - amd64 - gows (push) Waiting to run
Release / GOWS - none - linux/arm64 - gows-arm (push) Waiting to run
Release / NOWEB - none - amd64 - noweb (push) Waiting to run
Release / NOWEB - none - linux/arm64 - noweb-arm (push) Waiting to run
Release / WEBJS - chrome - amd64 - chrome (push) Waiting to run
Release / WEBJS - chromium - amd64 - latest (push) Waiting to run
Release / WEBJS - chromium - linux/arm64 - arm (push) Waiting to run
2025-07-06 16:48:47 +07:00
devlikepro 5c31ed53df [core] WEBJS - fix error for message.ack
TypeError: You provided 'null' where a stream was expected.
2025-07-06 16:48:47 +07:00
devlikepro 87640ea238 [core] WEBJS - avoid STARTING => FAILED cycles
fix #1071
2025-07-06 16:48:47 +07:00
devlikepro eb8f771b06 [core] WEBJS - protocolTimeout to 300s (5m)
fix #1103
2025-07-06 16:48:46 +07:00
devlikepro b080b5862e [core] NOWEB - no lock on 'lids' updates 2025-07-06 16:48:46 +07:00
devlikepro df50e7527e [core] NOWEB - save @lid to @c.us from the first message
fix #1073
fix #1097
2025-07-06 16:48:46 +07:00
devlikepro e2b43575c0 [core] Up NOWEB - 2025-07-04
fix #531
fix #1098
2025-07-06 16:48:45 +07:00
devlikepro 2059793562 [core] Log update for unknown jid 2025-07-06 16:48:45 +07:00
devlikepro e5df21e165 [core] 2025.7.3
Release / WEBJS - chrome - amd64 - chrome (push) Waiting to run
Release / WEBJS - chromium - amd64 - latest (push) Waiting to run
Release / WEBJS - chromium - linux/arm64 - arm (push) Waiting to run
Release / GOWS - none - amd64 - gows (push) Waiting to run
Release / GOWS - none - linux/arm64 - gows-arm (push) Waiting to run
Release / NOWEB - none - amd64 - noweb (push) Waiting to run
Release / NOWEB - none - linux/arm64 - noweb-arm (push) Waiting to run
2025-07-03 17:13:06 +07:00
devlikepro 792029b13f [core] Up GOWS
fix #1043
fix #1068
fix #1094
2025-07-03 17:13:06 +07:00
devlikepro 0ef49d7116 [core] GOWS - fallback to storage in case of JSON errors
2 UNKNOWN: json: cannot unmarshal object into Go struct field InteractiveAnnotation.imageMessage.annotations. Action of type waE2
E.isInteractiveAnnotation_Action
2025-07-03 17:13:06 +07:00
devlikepro 600d3291eb [core] add dns in waha by default 2025-07-03 17:13:06 +07:00
devlikepro b78413190c [core] WEBJS - disable GPU 2025-07-03 17:13:06 +07:00
devlikepro c9729af139 [core] 2025.7.2
Release / WEBJS - chromium - amd64 - latest (push) Waiting to run
Release / WEBJS - chromium - linux/arm64 - arm (push) Waiting to run
Release / GOWS - none - amd64 - gows (push) Waiting to run
Release / WEBJS - chrome - amd64 - chrome (push) Waiting to run
Release / GOWS - none - linux/arm64 - gows-arm (push) Waiting to run
Release / NOWEB - none - amd64 - noweb (push) Waiting to run
Release / NOWEB - none - linux/arm64 - noweb-arm (push) Waiting to run
2025-07-02 14:47:05 +07:00
devlikepro 697f267fc3 [core] Add - in prefix 2025-07-02 14:47:05 +07:00
devlikepro ac49ce3cc5 [core] trace 2025-07-02 14:47:05 +07:00
devlikepro 555daf6888 [core] fix heap API 2025-07-02 14:47:05 +07:00
devlikepro 8f0c2994a6 [core] One build-all 2025-07-02 14:47:05 +07:00
devlikepro 873e66db19 [core] Add browser tracing debug API 2025-07-02 14:45:52 +07:00
devlikepro 38b542def4 [core] Add TmpDir (from PLUS) 2025-07-02 14:45:51 +07:00
devlikepro a646e5efc0 [core] remove healthcheck 2025-07-02 14:45:50 +07:00
devlikepro 31590c9229 [core] Add server.debug.controller.ts 2025-07-02 14:45:50 +07:00
devlikepro 98c2172908 [core] WEBJS - onTag handler
related #1092
2025-07-02 14:45:49 +07:00
devlikepro 314b02daf1 [core] WEBJS add waha arguments to chrome to identify it better 2025-07-02 14:45:49 +07:00
devlikepro d2ab78b65c [core] WEBJS Up user agent 2025-07-02 14:45:49 +07:00
devlikepro db2c352fd1 [core] WEBJS --renderer-process-limit=1 2025-07-02 14:45:49 +07:00
devlikepro 6e79ad2793 [core] WEBJS - rm --single-process at all 2025-07-02 14:45:49 +07:00
devlikepro 77eaa287fe [core] WEBJS - remove disable-background-networking, it's default puppeter flag 2025-07-02 14:45:49 +07:00
devlikepro a64820048c [core] Add health-cmd
Release / WEBJS - chrome - amd64 - chrome (push) Waiting to run
Release / WEBJS - chromium - amd64 - latest (push) Waiting to run
Release / WEBJS - chromium - linux/arm64 - arm (push) Waiting to run
Release / GOWS - none - amd64 - gows (push) Waiting to run
Release / GOWS - none - linux/arm64 - gows-arm (push) Waiting to run
Release / NOWEB - none - amd64 - noweb (push) Waiting to run
Release / NOWEB - none - linux/arm64 - noweb-arm (push) Waiting to run
2025-07-01 13:35:00 +07:00
devlikepro 4ff4a5a893 [core] 2025.7.1 2025-07-01 13:23:38 +07:00
devlikepro 5ea5681d2b [core] Use PORT first
fix #1090
2025-07-01 13:23:38 +07:00
devlikepro 2bb327b747 [core] move HEALTHCHECK from Dockerfile to docker-compose.yaml
fix #1090
2025-07-01 13:23:38 +07:00
devlikepro 4dcdf60309 [core] make build-all 2025-07-01 13:23:37 +07:00
devlikepro 009fd5b2ec [core] NOWEB - share() events 2025-07-01 13:23:37 +07:00
devlikepro 091e7e5bc1 [core] Use this.knex or query methods directly 2025-07-01 13:23:37 +07:00
devlikepro ea1202bb65 [core] Use knex by for sqlite3, not database connection 2025-07-01 13:23:32 +07:00
devlikepro cb413febaa [core] Handle WhatsApp News profile picture 2025-07-01 13:23:32 +07:00
devlikepro 364e55d477 [core] Add KnexEngine (copy paste from psql) 2025-07-01 13:23:32 +07:00
devlikepro 680a35a26d [core] rm start-interval from docker run - docker 24 doesn't have it (used in CI)
Release / WEBJS - chrome - amd64 - chrome (push) Waiting to run
Release / WEBJS - chromium - amd64 - latest (push) Waiting to run
Release / WEBJS - chromium - linux/arm64 - arm (push) Waiting to run
Release / GOWS - none - amd64 - gows (push) Waiting to run
Release / GOWS - none - linux/arm64 - gows-arm (push) Waiting to run
Release / NOWEB - none - amd64 - noweb (push) Waiting to run
Release / NOWEB - none - linux/arm64 - noweb-arm (push) Waiting to run
2025-06-29 11:31:54 +07:00
devlikepro 08ff59e463 [core] 2025.6.7 2025-06-29 11:17:51 +07:00
devlikepro 9db4c8c333 [core] Check docker health in CI
fix #1085
2025-06-29 11:17:51 +07:00
devlikepro 2489c2da46 [core] Install curl in all images
fix #1085
2025-06-29 11:17:51 +07:00
devlikepro fbc58b0707 [core] 2025.6.6
Release / WEBJS - chrome - amd64 - chrome (push) Waiting to run
Release / WEBJS - chromium - amd64 - latest (push) Waiting to run
Release / WEBJS - chromium - linux/arm64 - arm (push) Waiting to run
Release / GOWS - none - amd64 - gows (push) Waiting to run
Release / GOWS - none - linux/arm64 - gows-arm (push) Waiting to run
Release / NOWEB - none - amd64 - noweb (push) Waiting to run
Release / NOWEB - none - linux/arm64 - noweb-arm (push) Waiting to run
2025-06-27 11:09:33 +07:00
devlikepro d5a525789e [core] NOWEB - show poll on the device
fix #988
2025-06-27 11:09:32 +07:00
devlikepro 34066921e1 [core] run xvfb only for WEBJS engine 2025-06-27 11:09:32 +07:00
devlikepro 901c594311 [core] WEBJS - fix pin message
fix #1081
2025-06-27 11:09:32 +07:00
devlikepro 5f619e608b [core] WEBJS - return response for a message
fix #1083
2025-06-27 11:09:31 +07:00
devlikepro 49295090ad [core] Use 501 Not Implemented HTTP status for NotImplemented by engine error
fix #1047
2025-06-27 11:09:31 +07:00
devlikepro a6e43adb0a [core] use xvfb-run 2025-06-27 11:09:30 +07:00
devlikepro 1f165bb6cb [core] Puppeteer 24.10.0 2025-06-27 11:09:30 +07:00
devlikepro 8fc9d88d2e [core] No security reminder, it creates a zombie process
fix #1078
2025-06-27 11:09:29 +07:00
devlikepro d7e5a5ce05 [core] Add ping check 2025-06-27 11:09:29 +07:00
devlikepro 09d446fdeb [core] Install wget for chrome 2025-06-27 11:09:28 +07:00
devlikepro c0bbd60e15 [core] Up Dashboard 2025-06-27 11:09:28 +07:00
devlikepro af3fc137be [core] fix del usage 2025-06-27 11:09:27 +07:00
devlikepro 0ee5e1bd88 [core] Up del, rimraf 2025-06-27 11:09:27 +07:00
devlikepro f92ea18651 [core] Use Debian Bookworm 2025-06-27 11:09:27 +07:00
devlikepro 3cce2bb35f [core] 2025.6.5
Release / WEBJS - chrome - amd64 - chrome (push) Waiting to run
Release / WEBJS - chromium - amd64 - latest (push) Waiting to run
Release / WEBJS - chromium - linux/arm64 - arm (push) Waiting to run
Release / GOWS - none - amd64 - gows (push) Waiting to run
Release / GOWS - none - linux/arm64 - gows-arm (push) Waiting to run
Release / NOWEB - none - amd64 - noweb (push) Waiting to run
Release / NOWEB - none - linux/arm64 - noweb-arm (push) Waiting to run
2025-06-25 13:38:56 +07:00
devlikepro e080ce42ee [core] no dashboard username by default 2025-06-25 13:38:56 +07:00
devlikepro 5dbe41da78 [core] postgresql 127.0.0.1 2025-06-25 13:38:55 +07:00
devlikepro eef3957d4f [core] Warning about no API key 2025-06-25 13:38:55 +07:00
devlikepro 680f4596b9 [core] Up Dashboard 2025-06-25 13:38:55 +07:00
devlikepro 8de344f220 [core] Add enabled vars for dashboard and swagger 2025-06-25 13:38:54 +07:00
devlikepro 8005846955 [core] admin/admin by default for dashboard 2025-06-25 13:38:54 +07:00
devlikepro 3edd60448b [core] format .env better 2025-06-25 13:38:54 +07:00
devlikepro 4a2fa5c076 [core] message about plain key 2025-06-25 13:38:54 +07:00
devlikepro 96e9c2cc4e [core] hash key in entrypoint.sh always 2025-06-25 13:38:53 +07:00
devlikepro 2089a1bb35 [core] Handle empty key in hash case 2025-06-25 13:38:52 +07:00
devlikepro 023c2718e2 [core] Move websocket.gateway.core.ts to /api 2025-06-25 13:38:52 +07:00
devlikepro f7aacb193a [core] Add Auth methods - NoAuth, PlainApiKeyAuth, Hash 2025-06-25 13:38:52 +07:00
devlikepro e710040904 [core] Add base providers 2025-06-25 13:38:52 +07:00
devlikepro fb12826801 [core] Refactor websocket authorization - use DI 2025-06-25 13:38:51 +07:00
devlikepro 833a70c6e0 [core] Remove hot fix for closing websockets 2025-06-25 13:38:50 +07:00
devlikepro c807f9021c [core] Support WAHA_API_KEY as well as env variable
gpt often suggests this env variable, IDK why
2025-06-25 13:38:49 +07:00
devlikepro 46b66bcdf0 [core] Move built-in SSL (DEPRECATED) to WAHA Core
fix #1069
2025-06-25 13:38:48 +07:00
devlikepro 2ebade817e [core] add swagger security to WAHA Core
fix #1069
2025-06-25 13:38:48 +07:00
devlikepro 080342e619 [core] add /ws authentication
fix #1069
2025-06-25 13:38:48 +07:00
devlikepro 75b06d3ba1 [core] Copy security file to WAHA Core
fix #1069
2025-06-25 13:38:48 +07:00
devlikepro 3b6fa3343d [core] 2025.6.4
Release / WEBJS - chrome - amd64 - chrome (push) Waiting to run
Release / WEBJS - chromium - amd64 - latest (push) Waiting to run
Release / WEBJS - chromium - linux/arm64 - arm (push) Waiting to run
Release / GOWS - none - amd64 - gows (push) Waiting to run
Release / GOWS - none - linux/arm64 - gows-arm (push) Waiting to run
Release / NOWEB - none - amd64 - noweb (push) Waiting to run
Release / NOWEB - none - linux/arm64 - noweb-arm (push) Waiting to run
2025-06-21 17:34:41 +07:00
devlikepro 469cdc9b1d [core] no xvfb if not installed (noweb/gows image) 2025-06-21 17:34:41 +07:00
devlikepro 8a179e7194 [core] NOWEB - sleep for sqlite3 in batch 2025-06-21 17:34:41 +07:00
devlikepro c133795753 [core] NOWEB - log length, not json
fix devlikeapro/waha#1054
2025-06-21 17:34:41 +07:00
devlikepro 941ace7461 [core] 2025.6.3
Release / WEBJS - chrome - amd64 - chrome (push) Waiting to run
Release / WEBJS - chromium - amd64 - latest (push) Waiting to run
Release / WEBJS - chromium - linux/arm64 - arm (push) Waiting to run
Release / GOWS - none - amd64 - gows (push) Waiting to run
Release / GOWS - none - linux/arm64 - gows-arm (push) Waiting to run
Release / NOWEB - none - amd64 - noweb (push) Waiting to run
Release / NOWEB - none - linux/arm64 - noweb-arm (push) Waiting to run
2025-06-16 13:05:20 +07:00
devlikepro 8589420f5d [core] do not stop ws socket in gateway 2025-06-16 13:05:20 +07:00
devlikepro ece24e182f [core] WEBJS - xvfb 2025-06-16 13:05:19 +07:00
devlikepro 38e5bb7bdf [core] NOWEB - Add timeout 5s for async lock 2025-06-16 13:05:19 +07:00
devlikepro d09e931827 [core] chrome 137.0.7151.103-1 2025-06-16 13:05:19 +07:00
devlikepro 3f3f1e1c20 [core] Inline WAMessage in message.edited 2025-06-16 13:05:18 +07:00
devlikepro 32f2468ba8 [core] Add WAHAWebhookMessageEdited to webhooks list 2025-06-16 13:05:17 +07:00
devlikepro 1d6540e0d9 [core] editedMessageId 2025-06-16 13:05:17 +07:00
devlikepro 31da4bfca1 [core] WEBJS - message.edited
fix #1041
2025-06-16 13:05:17 +07:00
devlikepro 41d49894e2 [core] WEBJS - message.revoked add revokedMessageId 2025-06-16 13:05:17 +07:00
devlikepro 6dcdd15b1c [core] message.revoked - add revokedMessageId - NOWEB, GOWS 2025-06-16 13:05:17 +07:00
devlikepro c2b275fd90 [core] NOWEB - message.edited 2025-06-16 13:05:16 +07:00
devlikepro e4838ef3ef [core] GOWS - message.edited
fix #916
2025-06-16 13:05:16 +07:00
devlikepro 55ca4660d7 [core] GOWS - message.revoked
fix #917
2025-06-16 13:05:15 +07:00
devlikepro c16c903ec4 [core] node 22.16 2025-06-16 13:05:15 +07:00
devlikepro 937cf38ff6 [core] Up libs 2025-06-16 13:05:15 +07:00
devlikepro 07f76e854d [core] Update axios 2025-06-16 13:05:15 +07:00
devlikepro cde1d084c6 [core] NOWEB fix issue with can not read property of null 2025-06-16 13:05:14 +07:00
devlikepro 9362c5d6ae [core] 2025.6.2
Release / WEBJS - chrome - amd64 - chrome (push) Waiting to run
Release / WEBJS - chromium - amd64 - latest (push) Waiting to run
Release / WEBJS - chromium - linux/arm64 - arm (push) Waiting to run
Release / GOWS - none - amd64 - gows (push) Waiting to run
Release / GOWS - none - linux/arm64 - gows-arm (push) Waiting to run
Release / NOWEB - none - amd64 - noweb (push) Waiting to run
Release / NOWEB - none - linux/arm64 - noweb-arm (push) Waiting to run
2025-06-11 15:41:56 +07:00
devlikepro 117a9108c0 [core] puppeter 24.10.0 2025-06-11 15:41:56 +07:00
devlikepro 5214e461f2 [core] chrome 137.0.7151.55 2025-06-11 15:41:56 +07:00
devlikepro 9bdb178b7d [core] Use github examples 2025-06-11 15:41:56 +07:00
devlikepro 9c526ffdba [core] Add converting voice and video files
fix #724
2025-06-11 15:41:56 +07:00
devlikepro f80a74edd1 [core] Add avi and mp3 in examples 2025-06-11 12:20:58 +07:00
devlikepro 7d8c6c0a62 [core] 2025.6.1
Release / WEBJS - chrome - amd64 - chrome (push) Waiting to run
Release / WEBJS - chromium - amd64 - latest (push) Waiting to run
Release / WEBJS - chromium - linux/arm64 - arm (push) Waiting to run
Release / GOWS - none - amd64 - gows (push) Waiting to run
Release / GOWS - none - linux/arm64 - gows-arm (push) Waiting to run
Release / NOWEB - none - amd64 - noweb (push) Waiting to run
Release / NOWEB - none - linux/arm64 - noweb-arm (push) Waiting to run
2025-06-09 14:01:26 +07:00
devlikepro 17b26b869b [core] Add --pprof-host listen to 0.0.0.0 if debug mode enabled 2025-06-09 14:01:26 +07:00
devlikepro dfbe54e92f [core] Up GOWS
fix #919
2025-06-09 14:01:25 +07:00
devlikepro 2321de409b [core] Up WEBJS
fix #1023
fix #1024
fix #1025
fix #1026
fix #1027
2025-06-09 14:01:25 +07:00
devlikepro 502287a258 [core] WEBJS - add exposeFunctionIfAbsent 2025-06-09 14:01:25 +07:00
devlikepro 052ff0ea6e [core] WEBJS - fork-main-2025-06-09 branch 2025-06-09 14:01:24 +07:00
devlikepro 2b76789cdf [core] Run GOWS with pprof if WAHA_DEBUG_MODE=true 2025-06-09 14:01:24 +07:00
devlikepro 7bbf5df231 [core] Update grpc 2025-06-09 14:01:24 +07:00
devlikepro 794a04e7d3 [core] Update grpc-js and grpc-tools 2025-06-09 14:01:23 +07:00
devlikepro 645467f13a [core] echo -e 2025-06-09 14:01:23 +07:00
devlikepro 7e87637341 [core] 2025.5.6
Release / WEBJS - chrome - amd64 - chrome (push) Waiting to run
Release / WEBJS - chromium - amd64 - latest (push) Waiting to run
Release / WEBJS - chromium - linux/arm64 - arm (push) Waiting to run
Release / GOWS - none - amd64 - gows (push) Waiting to run
Release / GOWS - none - linux/arm64 - gows-arm (push) Waiting to run
Release / NOWEB - none - amd64 - noweb (push) Waiting to run
Release / NOWEB - none - linux/arm64 - noweb-arm (push) Waiting to run
2025-05-30 11:18:16 +07:00
devlikepro b7abeed54a [core] 2025.5.5
Release / WEBJS - chrome - amd64 - chrome (push) Waiting to run
Release / WEBJS - chromium - amd64 - latest (push) Waiting to run
Release / WEBJS - chromium - linux/arm64 - arm (push) Waiting to run
Release / GOWS - none - amd64 - gows (push) Waiting to run
Release / GOWS - none - linux/arm64 - gows-arm (push) Waiting to run
Release / NOWEB - none - amd64 - noweb (push) Waiting to run
Release / NOWEB - none - linux/arm64 - noweb-arm (push) Waiting to run
2025-05-28 16:42:04 +07:00
devlikepro d53646eef2 [core] docker-compose.yaml - set logging by default 100m/10 files 2025-05-28 16:42:04 +07:00
devlikepro c54743de6c [core] Up GOWS 2025-05-28 16:42:03 +07:00
devlikepro e1fdc41132 [core] NOWEB - skip updates with no id 2025-05-28 16:42:03 +07:00
devlikepro 060aa82294 [core] WEBJS - Presence - API and Events
fix #636
2025-05-28 16:42:02 +07:00
devlikepro 2df08eac3f [core] NOWEB - hosted account pairing
fix #864
2025-05-28 16:42:02 +07:00
devlikepro 0ecdb9d3d0 [core] GOWS - Always subscribe to presence 2025-05-28 16:42:02 +07:00
devlikepro 6a7fe1bd1f [core] NOWEB - do not resubscribe to presences 2025-05-28 16:42:02 +07:00
devlikepro 87c69cb3d0 [core] NOWEB - Always subscribe to presence 2025-05-28 16:42:01 +07:00
devlikepro c654855c22 [core] Add ids filter to /overview
fix #791
2025-05-28 16:42:00 +07:00
devlikepro 11bb006801 [core] WEBJS - adjust logging on stop 2025-05-28 16:42:00 +07:00
devlikepro e5551f3f64 [core] Refactor path handling to improve security and safety.
Replaced `path.join` with `safeJoin` to validate and normalize paths, ensuring they stay within the base directory and prevent malicious access. Added `safeJoin` implementation in `utils/files.ts`, enforcing strict path validation rules. This improves robustness against path traversal vulnerabilities.
2025-05-28 16:41:59 +07:00
devlikepro 0de148005f [core] require session "name" for deprecated API 2025-05-28 16:41:58 +07:00
devlikepro 27e4ce628e [core] NOWEB - handle no content found messages: TypeError: Cannot read properties of undefined (reading 'pollUpdateMessage') 2025-05-28 16:41:58 +07:00
devlikepro 19b752bf15 [core] WEBJS - avoid "Can not get engine info" warning in logs 2025-05-28 16:41:58 +07:00
devlikepro 6528e5bba9 [core] WEBJS - hide "Fresh look" modal form
fix #987
2025-05-28 16:41:57 +07:00
devlikepro 37e1183e87 [core] Update nestjs 2025-05-28 16:41:56 +07:00
devlikepro 824512cf8e [core] add tini to Dockerfile
fix #990
2025-05-28 16:41:56 +07:00
devlikepro 82f296b17d [core] GOWS - Polls
fix devlikeapro/waha#899
2025-05-28 16:41:56 +07:00
devlikepro 8302530954 [core] 2025.5.4
Release / WEBJS - chrome - amd64 - chrome (push) Waiting to run
Release / WEBJS - chromium - amd64 - latest (push) Waiting to run
Release / WEBJS - chromium - linux/arm64 - arm (push) Waiting to run
Release / GOWS - none - amd64 - gows (push) Waiting to run
Release / GOWS - none - linux/arm64 - gows-arm (push) Waiting to run
Release / NOWEB - none - amd64 - noweb (push) Waiting to run
Release / NOWEB - none - linux/arm64 - noweb-arm (push) Waiting to run
2025-05-23 14:47:37 +07:00
devlikepro 6c271829aa [core] Up Dashboard 2025-05-23 14:47:37 +07:00
devlikepro 3b30d339ee [core] Up GOWS 2025-05-23 14:47:37 +07:00
devlikepro c5a268bdbd [core] Up NOWEB - lid support
fix #957
2025-05-23 14:47:36 +07:00
devlikepro 8457ea8e47 [core] Find a phone number by lid - do not require @lid 2025-05-23 14:47:36 +07:00
devlikepro 72d69eeab5 [core] Add lid to phone number mapping - all engines
fix #957
2025-05-23 14:47:35 +07:00
devlikepro 31885e41af [core] Use maxExecutionTime instead of timeout in async-lock to avoid "async-lock timed out in queue" error
fix #831
2025-05-23 14:47:34 +07:00
devlikepro e63726d18b [core] GOWS - WhatsApp Event Message 2025-05-23 14:47:33 +07:00
devlikepro 3d2ab45d25 [core] GOWS - Labels support
fix #922
2025-05-23 14:47:33 +07:00
devlikepro 246478e1d8 [core] GOWS - add send vcard
fix #902
2025-05-23 14:47:32 +07:00
devlikepro 9418de3b23 [core] add reply_to to sendContactVcard 2025-05-23 14:47:32 +07:00
devlikepro c6b68cad37 [core] add types for toVcard 2025-05-23 14:47:32 +07:00
devlikepro ef36e4d3ac [core] 2025.5.3
Release / WEBJS - chrome - amd64 - chrome (push) Waiting to run
Release / WEBJS - chromium - amd64 - latest (push) Waiting to run
Release / WEBJS - chromium - linux/arm64 - arm (push) Waiting to run
Release / GOWS - none - amd64 - gows (push) Waiting to run
Release / GOWS - none - linux/arm64 - gows-arm (push) Waiting to run
Release / NOWEB - none - amd64 - noweb (push) Waiting to run
Release / NOWEB - none - linux/arm64 - noweb-arm (push) Waiting to run
2025-05-16 18:33:10 +07:00
devlikepro ba2ddd62ad [core] NOWEB - fix poll.vote event
fix #960
2025-05-16 18:33:10 +07:00
devlikepro 6a03e1488c [core] 2025.5.2
Release / WEBJS - chrome - amd64 - chrome (push) Waiting to run
Release / WEBJS - chromium - amd64 - latest (push) Waiting to run
Release / WEBJS - chromium - linux/arm64 - arm (push) Waiting to run
Release / GOWS - none - amd64 - gows (push) Waiting to run
Release / GOWS - none - linux/arm64 - gows-arm (push) Waiting to run
Release / NOWEB - none - amd64 - noweb (push) Waiting to run
Release / NOWEB - none - linux/arm64 - noweb-arm (push) Waiting to run
2025-05-16 17:42:09 +07:00
devlikepro cabd2ed562 [core] GOWS - pairing with official account
fix #864
2025-05-16 17:42:09 +07:00
devlikepro 4a9296276d [core] NOWEB - fix pairing code issue
fix #961
2025-05-16 17:42:09 +07:00
devlikepro 1fd84d824c [core] senderKeyDistributionMessage - ignore undefined messages 2025-05-16 17:42:08 +07:00
devlikepro faa55ffe2d [core] 2025.5.1
Release / WEBJS - chrome - amd64 - chrome (push) Waiting to run
Release / WEBJS - chromium - amd64 - latest (push) Waiting to run
Release / WEBJS - chromium - linux/arm64 - arm (push) Waiting to run
Release / GOWS - none - amd64 - gows (push) Waiting to run
Release / GOWS - none - linux/arm64 - gows-arm (push) Waiting to run
Release / NOWEB - none - amd64 - noweb (push) Waiting to run
Release / NOWEB - none - linux/arm64 - noweb-arm (push) Waiting to run
2025-05-10 15:55:42 +07:00
devlikepro d45af4191f [core] WEBJS - properly close remote auth storage 2025-05-10 15:55:41 +07:00
devlikepro 9f8dead8b8 [core] NOWEB + SQL - speedup Contacts upsert
fix #952
2025-05-10 15:55:41 +07:00
devlikepro 564037cd53 [core] NOWEB + SQL - speedup Chats upsert
fix #952
2025-05-10 15:55:40 +07:00
devlikepro 434de6292c [core] SQL - Fix inserting duplicate entities in one batch 2025-05-10 15:55:40 +07:00
devlikepro a12e6990e5 [core] WEBJS - fix sorting groups in /chats and /chats/overview
fix #915
2025-05-10 15:55:40 +07:00
devlikepro ce0c4a094b [core] WEBJS - fix groups participant management (add/remove)
fix #944
2025-05-10 15:55:39 +07:00
devlikepro e36728ec08 [core] NOWEB - use ILogger 2025-05-10 15:55:39 +07:00
devlikepro 2c1f3090a0 [core] NOWEB - fix participant for message.ack in groups 2025-05-10 15:55:39 +07:00
devlikepro 0c6d4b3581 [core] NOWEB - update status only if not old 2025-05-10 15:55:39 +07:00
devlikepro 1d5b9eb241 [core] NOWEB - DistinctAck
fix #948
2025-05-10 15:55:38 +07:00
devlikepro b0567dafe3 [core] DistinctAck function 2025-05-10 15:55:37 +07:00
devlikepro 518f78f067 [core] Add StatusToAck and AckToStatus methods 2025-05-10 15:55:37 +07:00
devlikepro 47a2c21824 [core] Up GOWS
Release / WEBJS - chrome - amd64 - chrome (push) Waiting to run
Release / WEBJS - chromium - amd64 - latest (push) Waiting to run
Release / WEBJS - chromium - linux/arm64 - arm (push) Waiting to run
Release / GOWS - none - amd64 - gows (push) Waiting to run
Release / GOWS - none - linux/arm64 - gows-arm (push) Waiting to run
Release / NOWEB - none - amd64 - noweb (push) Waiting to run
Release / NOWEB - none - linux/arm64 - noweb-arm (push) Waiting to run
2025-05-05 13:08:45 +07:00
devlikepro aca5547965 [core] 2025.4.2 2025-05-05 13:08:45 +07:00
devlikepro 0e07876630 [core] Use POST in /api/chats/messages/read 2025-05-05 13:08:45 +07:00
devlikepro a43901b4a4 [core] /api/sendSeen - If no message id provided - switch to readChatMessages 2025-05-05 13:08:44 +07:00
devlikepro 6515fac16d [core] WEBJS - add message._data.receipts in group/status messages 2025-05-05 13:08:44 +07:00
devlikepro fdaa7e9f48 [core] Up Dashboard 2025-05-05 13:08:44 +07:00
devlikepro cd779850de [core] GOWS - fix missing DEVICE ack for groups 2025-05-05 13:08:44 +07:00
devlikepro ef939f4357 [core] Add "message.ack" event for groups and status
fix #495
fix #900
2025-05-05 13:08:41 +07:00
devlikepro bfb9dd7009 [core] WEBJS - Update chrome 2025-05-05 13:08:39 +07:00
devlikepro f282f14eff [core] WEBJS - Update puppeter 2025-05-05 13:08:38 +07:00
devlikepro 992b289500 [core] WEBJS - catch puppeter ProtocolError and "Promise was collected" and restart the browser
fix #244 - Promise was collected
fix #714 - Promise was collected
fix #685 - ProtocolError
fix #936 - ProtocolError
2025-05-05 13:08:37 +07:00
devlikepro 9dcdebcab2 [core] Add /api/chats/messages/read
fix #783
2025-05-05 13:08:36 +07:00
devlikepro c0b8bacab0 [core] Fix missing first message - GOWS, NOWEB 2025-05-05 13:08:35 +07:00
devlikepro f96e6420fc [core] Get message by id - filter by 'all' 2025-05-05 13:08:35 +07:00
devlikepro d7f97480cc [core] Do not send seen on empty keys 2025-05-05 13:08:35 +07:00
devlikepro 26df41b571 [core] Add isJidCus method 2025-05-05 13:08:34 +07:00
devlikepro 4ca336017d [core] GOWS - /api/sendSeen messageIds 2025-05-05 13:08:33 +07:00
devlikepro 94252ea54c [core] NOWEB - /api/sendSeen messageIds 2025-05-05 13:08:32 +07:00
devlikepro f8220fec1f [core] GOWS - send seen use messageIds 2025-05-05 13:08:32 +07:00
devlikepro a5ded396c9 [core] Add "audio-decode" to dependencies 2025-05-05 13:08:31 +07:00
devlikepro c457cf8064 [core] Add "node-cache" to dependencies 2025-05-05 13:08:30 +07:00
devlikepro 3079f90118 [core] Up NOWEB 2025-05-05 13:08:29 +07:00
devlikepro 4fbb331015 [core] Copy-paste in-memory store
Got removed from the upstream
2025-05-05 13:08:28 +07:00
devlikepro 640c0d305c [core] - Add 'filter.ack' to /messages 2025-05-05 13:08:26 +07:00
devlikepro 0b044c67bb [core] NOWEB fix ListMessages "Cannot read properties of undefined (reading 'protocolMessage')"
fix #932
2025-05-05 13:08:26 +07:00
devlikepro 7a37ba8145 [core] WEBJS - stop processing if we reach out the earliest message we need 2025-05-05 13:08:25 +07:00
devlikepro b447863d9f [core] NOWEB - update message status on /api/sendSeen API call
fix #635
2025-05-05 13:08:25 +07:00
devlikepro 6dd7e14f6f [core] NOWEB - set DEVICE message status for incoming messages if no status found 2025-05-05 13:08:24 +07:00
devlikepro b09ef98dce [core] default limit to 10 in messages 2025-05-05 13:08:24 +07:00
devlikepro ff1cf8a288 [core] gpt 2025-05-05 13:08:24 +07:00
devlikepro 603793662a [core] Wait 60 times in STARTING, approximately 2 minutes
fix #904
2025-05-05 13:08:24 +07:00
devlikepro 4cdab0dbc7 [core] admin/admin 2025-04-16 09:26:32 +07:00
devlikepro d745279fec [core] 2025.4.1
Release / WEBJS - chrome - amd64 - chrome (push) Waiting to run
Release / WEBJS - chromium - amd64 - latest (push) Waiting to run
Release / WEBJS - chromium - linux/arm64 - arm (push) Waiting to run
Release / GOWS - none - amd64 - gows (push) Waiting to run
Release / GOWS - none - linux/arm64 - gows-arm (push) Waiting to run
Release / NOWEB - none - amd64 - noweb (push) Waiting to run
Release / NOWEB - none - linux/arm64 - noweb-arm (push) Waiting to run
2025-04-09 16:05:03 +07:00
devlikepro df34c9f2fd [core] Up GOWS 2025-04-09 16:05:03 +07:00
devlikepro 37da9aabe3 [core] Add POST /api/send/link-review
fix #880
fix #596
2025-04-09 16:05:02 +07:00
devlikepro 3a2205147c [core] API - deprecate /sendLinkPreview 2025-04-09 16:05:02 +07:00
devlikepro 5c6acaf10a [core] NOWEB - filter out sender key distribution msg
fix #891
2025-04-09 16:05:02 +07:00
devlikepro d916f2dea5 [core] NOWEB - return error in case of malformed xml 2025-04-09 16:05:01 +07:00
devlikepro 254a1aedcb [core] NOWEB - send reaction to channel
fix #889
2025-04-09 16:05:01 +07:00
devlikepro dce3175388 [core] GOWS - fix sending reaction to DM/Groups for other participants 2025-04-09 16:05:01 +07:00
devlikepro e2b6b45418 [core] GOWS - send reaction to channel
fix #889
2025-04-09 16:05:01 +07:00
devlikepro e210d1e9fc [core] 2025.3.3
Release / WEBJS - chrome - amd64 - chrome (push) Waiting to run
Release / NOWEB - none - linux/arm64 - noweb-arm (push) Waiting to run
Release / WEBJS - chromium - amd64 - latest (push) Waiting to run
Release / WEBJS - chromium - linux/arm64 - arm (push) Waiting to run
Release / GOWS - none - amd64 - gows (push) Waiting to run
Release / GOWS - none - linux/arm64 - gows-arm (push) Waiting to run
Release / NOWEB - none - amd64 - noweb (push) Waiting to run
2025-03-31 15:39:56 +07:00
devlikepro df0f449e13 [core] use isJidBroadcast 2025-03-31 15:39:56 +07:00
devlikepro 895105c900 [core] Ignore @broadcast for picture
fix #878
fix #881
2025-03-31 15:39:56 +07:00
devlikepro 209538709b [core] Up Dashboard 2025-03-31 15:39:56 +07:00
devlikepro ef7c35286b [core] NOWEB - revert presence optimizations 2025-03-31 15:39:56 +07:00
devlikepro 70af0828cb [core] NOWEB - cache presence for 1 minute 2025-03-31 15:39:56 +07:00
devlikepro ef18e311b1 [core] 2025.3.2
Release / WEBJS - chrome - amd64 - chrome (push) Waiting to run
Release / WEBJS - chromium - amd64 - latest (push) Waiting to run
Release / WEBJS - chromium - linux/arm64 - arm (push) Waiting to run
Release / GOWS - none - amd64 - gows (push) Waiting to run
Release / GOWS - none - linux/arm64 - gows-arm (push) Waiting to run
Release / NOWEB - none - amd64 - noweb (push) Waiting to run
Release / NOWEB - none - linux/arm64 - noweb-arm (push) Waiting to run
2025-03-27 14:36:38 +07:00
devlikepro 68de84973a [core] GOWS - extract replyTo
fix #876
2025-03-27 14:36:38 +07:00
devlikepro ef6d4f9998 [core] Add _data (original message) to replyTo field 2025-03-27 14:36:37 +07:00
devlikepro 9f5413963b [core] NOWEB - log errors on profile picture change
fix #873
2025-03-27 14:36:36 +07:00
devlikepro ec7c0a5cb7 [core] check session name in waitUntilStatus 2025-03-27 14:36:36 +07:00
devlikepro f82bd9eadf [core] no browser for non webjs based engines 2025-03-27 14:36:35 +07:00
devlikepro 843c064ebe [core] Up GOWS 2025-03-27 14:36:35 +07:00
devlikepro 8a52e1d1d0 [core] Up Dashboard 2025-03-27 14:36:34 +07:00
devlikepro 8ec220dc9a [core] Status to 50K and more contacts
fix #858
2025-03-27 14:36:33 +07:00
devlikepro 9c4b9f4433 [core] NOWEB - fix scan qr issue 2025-03-27 14:36:33 +07:00
devlikepro 6059a29660 [core] fix message reactions empty for channels preview 2025-03-27 14:36:32 +07:00
devlikepro e65e6764fa [core] NOWEB - presence optimizations
Apply 1 hour ttl for presence information
2025-03-27 14:36:32 +07:00
devlikepro 1e77cb234a [core] NOWEB - use {} for presence 2025-03-27 14:36:31 +07:00
devlikepro 4a357087d1 [core] NOWEB - do not restart after SCAN_QR_CODE expires 2025-03-27 14:36:31 +07:00
devlikepro 6a3823cdff [core] NOWEB - fix the case when STARTING session fails the container
fix #821
fix #860
fix #869
2025-03-27 14:36:30 +07:00
devlikepro c2372ce96e [core] GOWS - fix get contact issue
fix #866
2025-03-27 14:36:30 +07:00
devlikepro 0f5e79b163 [core] Enable all features in Global Webhook
fix #855
2025-03-27 14:36:30 +07:00
devlikepro fa4026c83f [core] Allow only http/https for webhooks 2025-03-27 14:36:30 +07:00
devlikepro 3e4d66ca27 [core] Reply to button
fix #848
2025-03-27 14:36:30 +07:00
devlikepro 2ec94dc13c [core] 2025.3.1
Release / WEBJS - chrome - amd64 - chrome (push) Waiting to run
Release / WEBJS - chromium - amd64 - latest (push) Waiting to run
Release / WEBJS - chromium - linux/arm64 - arm (push) Waiting to run
Release / GOWS - none - amd64 - gows (push) Waiting to run
Release / GOWS - none - linux/arm64 - gows-arm (push) Waiting to run
Release / NOWEB - none - amd64 - noweb (push) Waiting to run
Release / NOWEB - none - linux/arm64 - noweb-arm (push) Waiting to run
2025-03-16 14:05:33 +07:00
devlikepro 6b11e08f3d [core] UP GOWS - v1.0.1 2025-03-16 14:05:33 +07:00
devlikepro e884e56b47 [core] Up Dashboard 2025-03-16 14:05:32 +07:00
devlikepro 2d74affed4 [core] NOWEB - send message save ids 2025-03-16 14:05:31 +07:00
devlikepro 3572b17049 [core] NOWEB up engine 2025-03-16 14:05:30 +07:00
devlikepro 4ee7091cd1 [core] Add /api/groups/count
fix #752
2025-03-16 14:05:29 +07:00
devlikepro ff5e89d97c [core] GOWS - no sender key dis message 2025-03-16 14:05:29 +07:00
devlikepro c4e82afc0c [core] log as debug engine info errors (not working perfectly in WEBJS)
fix #850
2025-03-16 14:05:29 +07:00
devlikepro 52c5d85487 [core] source: api|app for messages.
fix #787
2025-03-16 14:05:28 +07:00
devlikepro fd0438f80b [core] GOWS send _data with full message 2025-03-16 14:05:26 +07:00
devlikepro cb5781ca65 [core] NOWEB disappearing messages
fix #736
2025-03-16 14:05:25 +07:00
devlikepro 4d8aad9dfe [core] Remove calls from message events
fix #840
2025-03-16 14:05:24 +07:00
devlikepro 39429a2e44 [core] Add event.timestamp
fix #755
2025-03-16 14:05:23 +07:00
devlikepro 0a3514d81e [core] GOWS reply on message
fix #799
2025-03-16 14:05:22 +07:00
devlikepro 4e5e0a7c34 [core] GOWS fix filename in sendFile
fix #853
2025-03-16 14:05:22 +07:00
devlikepro f6cc6a9bef [core] GOWS edit message
fix #797
2025-03-16 14:05:20 +07:00
devlikepro 2e545e9fb9 [core] GOWS delete status message
fix #754
2025-03-16 14:05:19 +07:00
devlikepro d49ef64e7e [core] API - description for message id 2025-03-16 14:05:18 +07:00
devlikepro b1e6eaef90 [core] GOWS - delete message
fix #796
2025-03-16 14:05:16 +07:00
devlikepro 3420d0199b [core] NOWEB - fix getting message by id 2025-03-16 14:05:16 +07:00
devlikepro 671db35571 [core] NOWEB - link preview HQ 2025-03-16 14:05:16 +07:00
devlikepro d724f87726 [core] GOWS - link preview 2025-03-16 14:05:14 +07:00
devlikepro 5ab8565205 [core] NOWEB - link preview in status 2025-03-16 14:05:14 +07:00
devlikepro 660c567d1b [core] GOWS - link preview
fix #763
2025-03-16 14:05:14 +07:00
devlikepro 2c68d13c56 [core] Send session name back
fix #835
2025-03-16 14:05:14 +07:00
devlikeapro b9fc155b4f Update feature_request.md 2025-02-28 10:55:19 +07:00
devlikeapro 8dd5aefc81 Update feature_request.md 2025-02-28 10:55:05 +07:00
devlikeapro f9aaf5dcef Update bug_report.md 2025-02-28 10:53:52 +07:00
devlikepro 34a6e773df [core] 2025.2.8
Release / WEBJS - chrome - amd64 - chrome (push) Waiting to run
Release / WEBJS - chromium - amd64 - latest (push) Waiting to run
Release / WEBJS - chromium - linux/arm64 - arm (push) Waiting to run
Release / GOWS - none - amd64 - gows (push) Waiting to run
Release / GOWS - none - linux/arm64 - gows-arm (push) Waiting to run
Release / NOWEB - none - amd64 - noweb (push) Waiting to run
Release / NOWEB - none - linux/arm64 - noweb-arm (push) Waiting to run
2025-02-28 10:22:33 +07:00
devlikepro f0beb03344 [core] Link preview on edit
fix #833
2025-02-28 10:22:33 +07:00
devlikepro 1ff9a81ab3 [core] Fix channels API
fix #832
2025-02-28 10:22:33 +07:00
devlikepro 9b1771c693 [core] 2025.2.7
Release / WEBJS - chrome - amd64 - chrome (push) Waiting to run
Release / WEBJS - chromium - amd64 - latest (push) Waiting to run
Release / WEBJS - chromium - linux/arm64 - arm (push) Waiting to run
Release / GOWS - none - amd64 - gows (push) Waiting to run
Release / GOWS - none - linux/arm64 - gows-arm (push) Waiting to run
Release / NOWEB - none - amd64 - noweb (push) Waiting to run
Release / NOWEB - none - linux/arm64 - noweb-arm (push) Waiting to run
2025-02-27 14:34:23 +07:00
devlikepro 823e604737 [core] GOWS v1.0.0 2025-02-27 14:34:23 +07:00
devlikepro 97a31bdf17 [core] NOWEB - ignore EPHEMERAL_SYNC_RESPONSE 2025-02-27 14:34:22 +07:00
devlikepro ee7a198ae6 [core] GOWS - startTyping/stopTyping 2025-02-27 14:34:22 +07:00
devlikepro f143a51010 [core] group id in picture 2025-02-27 14:34:21 +07:00
devlikepro e483185b2c [core] fields.exclude optional 2025-02-27 14:34:21 +07:00
devlikepro 40e1c75746 [core] GOWS - v1.0.0-dev.1 2025-02-27 14:34:21 +07:00
devlikepro 456bd4693c [core] GOWS - fetch proto from ref with no v 2025-02-27 14:34:21 +07:00
devlikepro 5ac9c0f760 [core] NOWEB - no auto restart 2025-02-27 14:34:20 +07:00
devlikepro f621d84b83 [core] Exclude groups participants
fix #242
2025-02-27 14:34:20 +07:00
devlikepro 0dd001e95a [core] GOWS Groups API 2025-02-27 14:34:19 +07:00
devlikepro 69b5235db9 [core] Groups API - return 200 on POST in not create API 2025-02-27 14:34:19 +07:00
devlikepro a673d63b33 [core] Groups Picture API - Get, Update, Remove
fix #802
2025-02-27 14:34:18 +07:00
devlikepro 36485e10f2 [core] NOWEB add admin only / messages admin settings API
fix #695
fix #808
2025-02-27 14:34:18 +07:00
devlikepro 8a1a062127 [core] Place Channels Search API at the end 2025-02-27 14:34:18 +07:00
devlikepro 5961e0de08 [core] Fix group.participants undefined
fix #810
2025-02-27 14:34:18 +07:00
devlikepro 60bda521a0 [core] Wait until session is running and check if exist after 2025-02-27 14:34:18 +07:00
devlikepro 49f63c0f0d [core] 2025.2.6
Release / WEBJS - chrome - amd64 - chrome (push) Waiting to run
Release / WEBJS - chromium - amd64 - latest (push) Waiting to run
Release / WEBJS - chromium - linux/arm64 - arm (push) Waiting to run
Release / GOWS - none - amd64 - gows (push) Waiting to run
Release / GOWS - none - linux/arm64 - gows-arm (push) Waiting to run
Release / NOWEB - none - amd64 - noweb (push) Waiting to run
Release / NOWEB - none - linux/arm64 - noweb-arm (push) Waiting to run
fix #820
2025-02-21 10:06:27 +07:00
devlikepro 33910aedb8 [core] 2025.2.5
Release / WEBJS - chrome - amd64 - chrome (push) Waiting to run
Release / WEBJS - chromium - amd64 - latest (push) Waiting to run
Release / WEBJS - chromium - linux/arm64 - arm (push) Waiting to run
Release / GOWS - none - amd64 - gows (push) Waiting to run
Release / GOWS - none - linux/arm64 - gows-arm (push) Waiting to run
Release / NOWEB - none - amd64 - noweb (push) Waiting to run
Release / NOWEB - none - linux/arm64 - noweb-arm (push) Waiting to run
2025-02-19 09:20:27 +07:00
devlikepro 3359a81a14 [core] NOWEB add message.revoked event
fix #813
2025-02-19 09:20:26 +07:00
devlikepro e27f850e68 [core] NOWEB Fix body extract from messages
fix #813
2025-02-19 09:20:26 +07:00
devlikepro 07340b8f53 [core] Up NOWEB
Release / WEBJS - chromium - linux/arm64 - arm (push) Waiting to run
Release / GOWS - none - amd64 - gows (push) Waiting to run
Release / WEBJS - chrome - amd64 - chrome (push) Waiting to run
Release / WEBJS - chromium - amd64 - latest (push) Waiting to run
Release / GOWS - none - linux/arm64 - gows-arm (push) Waiting to run
Release / NOWEB - none - amd64 - noweb (push) Waiting to run
Release / NOWEB - none - linux/arm64 - noweb-arm (push) Waiting to run
fix #809
2025-02-17 16:53:08 +07:00
devlikepro 2f9dd7faa8 [core] catch uncaughtException
fix #809
2025-02-17 16:53:07 +07:00
devlikepro 3f81fd4a87 [core] 2025.2.4 2025-02-17 16:53:07 +07:00
devlikepro 4dd961fc66 [core] Up dashboard 2025-02-17 16:53:07 +07:00
devlikepro 0bd70bfb32 [core] Deprecate webhooks: state.change, group.join, group.leave
fix #404
2025-02-17 16:53:06 +07:00
devlikepro 3ab845d2d6 [core] Groups V2 Events
fix #583
fix #527
fix #404
2025-02-17 16:53:06 +07:00
devlikepro 443f09d5a2 [core] 2025.2.3
Release / WEBJS - chrome - amd64 - chrome (push) Waiting to run
Release / WEBJS - chromium - amd64 - latest (push) Waiting to run
Release / WEBJS - chromium - linux/arm64 - arm (push) Waiting to run
Release / GOWS - none - amd64 - gows (push) Waiting to run
Release / GOWS - none - linux/arm64 - gows-arm (push) Waiting to run
Release / NOWEB - none - amd64 - noweb (push) Waiting to run
Release / NOWEB - none - linux/arm64 - noweb-arm (push) Waiting to run
2025-02-12 17:18:00 +07:00
devlikepro 3500952bb7 [core] Up GOWS 2025-02-12 17:18:00 +07:00
devlikepro e98182c4b5 [core] Add /profile API
fix #529
2025-02-12 17:18:00 +07:00
devlikepro 2337827744 [core] GOWS hasMedia fix 2025-02-12 17:18:00 +07:00
devlikepro 556dd66af3 [core] NOWEB hasMedia fix 2025-02-12 17:18:00 +07:00
devlikepro 8bdad3f4f9 [core] WEBJS hasMedia fix 2025-02-12 17:17:59 +07:00
devlikepro 6346d4143d [core] 2025.2.2
Release / WEBJS - chrome - amd64 - chrome (push) Waiting to run
Release / WEBJS - chromium - amd64 - latest (push) Waiting to run
Release / WEBJS - chromium - linux/arm64 - arm (push) Waiting to run
Release / GOWS - none - amd64 - gows (push) Waiting to run
Release / GOWS - none - linux/arm64 - gows-arm (push) Waiting to run
Release / NOWEB - none - amd64 - noweb (push) Waiting to run
Release / NOWEB - none - linux/arm64 - noweb-arm (push) Waiting to run
2025-02-10 08:57:14 +07:00
devlikepro fd8dc88760 [core] rm auth/authorize-code 2025-02-10 08:57:14 +07:00
devlikepro b2810f390e [core] max connections 3000 2025-02-10 08:57:14 +07:00
devlikepro 23d3cd0f4b [core] Up GOWS - build proto 2025-02-10 08:57:13 +07:00
devlikepro 4a3c76773d [core] Up GOWS 2025-02-10 08:57:12 +07:00
devlikepro d23e7604f0 [core] NOWEB Channels Search 2025-02-10 08:57:12 +07:00
devlikepro 0b3f6c6eff [core] GOWS - Search Channels 2025-02-10 08:57:12 +07:00
devlikepro f2869c892b [core] WEBJS - ensure suffix 2025-02-10 08:57:12 +07:00
devlikepro 3755a9529b [core] GOWS - Channels Search, Messages Preview 2025-02-10 08:57:11 +07:00
devlikepro 890f0593ee [core] NOWEB - Channels Search, Messages Preview 2025-02-10 08:57:11 +07:00
devlikepro e6d666b5b0 [core] WEBJS Channels Search, Messages Preview 2025-02-10 08:57:10 +07:00
devlikepro f440acf145 [core] Channels Search, Messages Preview - API 2025-02-10 08:57:10 +07:00
devlikepro 1ad0e0c6dd [core] Add proto files
fixes #784
2025-02-07 08:41:37 +07:00
devlikepro bb8691ddc0 [core] Use waha docker image
Release / WEBJS - chromium - amd64 - latest (push) Waiting to run
Release / WEBJS - chromium - linux/arm64 - arm (push) Waiting to run
Release / GOWS - none - amd64 - gows (push) Waiting to run
Release / GOWS - none - linux/arm64 - gows-arm (push) Waiting to run
Release / NOWEB - none - amd64 - noweb (push) Waiting to run
Release / NOWEB - none - linux/arm64 - noweb-arm (push) Waiting to run
Release / WEBJS - chrome - amd64 - chrome (push) Waiting to run
2025-02-02 13:25:24 +07:00
devlikepro cf3041d07b [core] swagger 2025-02-02 13:23:24 +07:00
devlikepro 313b3d5d6b [core] up NOWEB 2025-02-02 13:23:24 +07:00
devlikepro 122e7336be [core] NOWEB - use channels-6712 branch 2025-02-02 13:23:24 +07:00
devlikepro e954453f6b [core] up GOWS 2025-02-02 13:23:24 +07:00
devlikepro 55ef3d09f3 [core] 2025.2.1 2025-01-30 14:35:08 +07:00
devlikepro 88b9cc5ac9 [core] Up dashboard 2025-01-30 14:35:08 +07:00
devlikepro f07cb7470d [core] GOWS - contacts, chats 2025-01-30 14:33:50 +07:00
devlikepro bd874a0232 [core] Up NOWEB 2025-01-30 14:33:50 +07:00
devlikepro 7cc4cccb9c [core] Fetch gows bin and proto from gows releases 2025-01-30 14:33:50 +07:00
devlikepro e85a3bacb7 [core] GOWS - Add ack to incoming messages 2025-01-30 14:33:50 +07:00
devlikepro 530dfa758f [core] Handle keep alive restored 2025-01-30 14:33:50 +07:00
devlikepro 8bc3489060 [core] 2025.1.6
Release / WEBJS - chrome - amd64 - chrome (push) Waiting to run
Release / WEBJS - chromium - amd64 - latest (push) Waiting to run
Release / WEBJS - chromium - linux/arm64 - arm (push) Waiting to run
Release / GOWS - none - amd64 - gows (push) Waiting to run
Release / GOWS - none - linux/arm64 - gows-arm (push) Waiting to run
Release / NOWEB - none - amd64 - noweb (push) Waiting to run
Release / NOWEB - none - linux/arm64 - noweb-arm (push) Waiting to run
2025-01-22 09:20:20 +07:00
devlikepro 6914cb171a [core] WEBJS send image to channel 2025-01-22 09:20:12 +07:00
devlikepro 176c2bbd4c [core] 2025.1.5
Release / WEBJS - chrome - amd64 - chrome (push) Waiting to run
Release / WEBJS - chromium - amd64 - latest (push) Waiting to run
Release / WEBJS - chromium - linux/arm64 - arm (push) Waiting to run
Release / GOWS - none - amd64 - gows (push) Waiting to run
Release / GOWS - none - linux/arm64 - gows-arm (push) Waiting to run
Release / NOWEB - none - amd64 - noweb (push) Waiting to run
Release / NOWEB - none - linux/arm64 - noweb-arm (push) Waiting to run
2025-01-21 11:57:40 +07:00
devlikepro 7771954455 [core] build proto on package install 2025-01-21 11:57:28 +07:00
devlikepro a66743bb20 [core] rm old scripts 2025-01-21 11:57:27 +07:00
devlikepro 851c7b75f9 [core] GOWS Channels 2025-01-21 11:57:26 +07:00
devlikepro ed0b406027 [core] GOWS Send Status 2025-01-21 11:57:26 +07:00
devlikepro 71985972a2 [core] GOWS check exists 2025-01-21 11:57:26 +07:00
devlikepro 2dbeef9290 [core] font 0 by default in status 2025-01-21 11:57:25 +07:00
devlikepro a79453270d [core] WEBJS handle status 2025-01-21 11:57:25 +07:00
devlikepro ae71808c61 [core] ws heartbeat do not return after closing one client 2025-01-21 11:57:25 +07:00
devlikepro 5c195ba37b [core] ws 60 seconds heartbeat 2025-01-21 11:57:24 +07:00
devlikepro 607ee17a86 [core] perf tests 2025-01-21 11:57:23 +07:00
devlikepro a15aacc5b3 [core] perf tests 2025-01-21 11:57:23 +07:00
devlikepro 78fb4b1a65 [core] Init store
Release / WEBJS - chrome - amd64 - chrome (push) Waiting to run
Release / WEBJS - chromium - amd64 - latest (push) Waiting to run
Release / WEBJS - chromium - linux/arm64 - arm (push) Waiting to run
Release / GOWS - none - amd64 - gows (push) Waiting to run
Release / GOWS - none - linux/arm64 - gows-arm (push) Waiting to run
Release / NOWEB - none - amd64 - noweb (push) Waiting to run
Release / NOWEB - none - linux/arm64 - noweb-arm (push) Waiting to run
2025-01-19 15:18:07 +07:00
devlikepro 8e566cba72 [core] 2025.1.4 2025-01-19 14:44:06 +07:00
devlikepro 36184288de [core] NOWEB PostgreSQL 2025-01-19 14:42:59 +07:00
devlikepro 6fc5bf38a7 [core] NOWEB - handle not found profile picture 2025-01-19 14:42:59 +07:00
devlikepro e146ecde70 [core] Add local store to WAHA Core
fix #741
2025-01-19 14:42:58 +07:00
devlikepro 72b796019a [core] PostgreSQL Media 2025-01-19 14:42:58 +07:00
devlikepro 0f66d15ba8 [core] PSQL Media 2025-01-19 14:42:57 +07:00
devlikepro 4695e933b8 [core] Up GOWS
[core] GOWS up
2025-01-19 14:42:57 +07:00
devlikepro 5e1da6cd6f [core] PostgreSQL docker-compose.yaml 2025-01-19 14:42:56 +07:00
devlikepro 0c3bbc0980 [core] PostgreSQL 2025-01-19 14:42:56 +07:00
devlikepro d2bb5bea63 [core] Add session config schema + migrations 2025-01-19 14:42:55 +07:00
devlikepro c76e191d4d [core] Extract general SqlKVRepository 2025-01-19 14:42:54 +07:00
devlikepro 171594d0ae [core] NOWEB Update contact if not found 2025-01-19 14:42:53 +07:00
devlikepro 4e5f499b48 [core] Rename db max name limit 2025-01-19 14:42:53 +07:00
devlikepro bb75ad8b8e [core] Add pg 2025-01-19 14:42:52 +07:00
devlikepro 482cc7028b [core] Move sqlite3 2025-01-19 14:42:51 +07:00
devlikepro 735bdb3854 [core] Websocket - close server and then terminate clients 2025-01-19 14:42:51 +07:00
devlikepro 1ee1bd78af [core] Stop events after we stop the session 2025-01-19 14:42:51 +07:00
devlikepro 00463c729d [core] SIGTERM to GOWS 2025-01-19 14:42:51 +07:00
devlikepro dac636b3bb [core] detached GOWS subprocess 2025-01-19 14:42:51 +07:00
devlikepro ad87121506 [core] make copy-dashboard (dist) 2025-01-19 14:42:51 +07:00
devlikepro 55d802f005 [core] Disable some flag for WEBJS stability 2025-01-19 14:42:51 +07:00
devlikepro f17164a3d9 [core] 2025.1.3
Release / WEBJS - chrome - amd64 - chrome (push) Waiting to run
Release / WEBJS - chromium - amd64 - latest (push) Waiting to run
Release / WEBJS - chromium - linux/arm64 - arm (push) Waiting to run
Release / GOWS - none - amd64 - gows (push) Waiting to run
Release / GOWS - none - linux/arm64 - gows-arm (push) Waiting to run
Release / NOWEB - none - amd64 - noweb (push) Waiting to run
Release / NOWEB - none - linux/arm64 - noweb-arm (push) Waiting to run
2025-01-10 11:01:11 +07:00
devlikepro 0b9f9b2335 [core] Add GOWS engine 2025-01-10 11:00:56 +07:00
devlikepro 71d3e24241 [core] nestjs forceCloseConnections 2025-01-10 11:00:56 +07:00
devlikepro ccd4512e8f [core] Get QR PNG on demand 2025-01-10 11:00:55 +07:00
devlikepro fe6b77d67d [core] Use UV_THREADPOOL_SIZE as cpu*1 2025-01-10 11:00:55 +07:00
devlikepro 58d12f4e70 [core] NOWEB - sleep 1s to get latest presence 2025-01-10 11:00:55 +07:00
devlikepro 17303039b8 [core] NOWEB subscribe presence 2025-01-10 11:00:55 +07:00
devlikepro f70d5203a3 [core] Websocket logs verbose 2025-01-10 11:00:55 +07:00
devlikepro efd6b1db65 [core] WEBJS Remove in-process-gpu
Release / WEBJS - chrome - amd64 - chrome (push) Waiting to run
Release / WEBJS - chromium - amd64 - latest (push) Waiting to run
Release / WEBJS - chromium - linux/arm64 - arm (push) Waiting to run
Release / NOWEB - none - amd64 - noweb (push) Waiting to run
Release / NOWEB - none - linux/arm64 - noweb-arm (push) Waiting to run
fix #725
2025-01-09 16:23:27 +07:00
devlikepro dcca0fa921 [core] 2025.1.2 2025-01-09 16:03:04 +07:00
devlikepro e5bc447807 [core] Load name from contact
fix #732
2025-01-09 14:31:22 +07:00
devlikepro cd6b4b5ab1 [core] Use atomic writes
fix #731
2025-01-09 14:21:38 +07:00
devlikepro fb79ae6138 [core] 2025.1.1
Release / NOWEB - none - amd64 - noweb (push) Waiting to run
Release / WEBJS - chrome - amd64 - chrome (push) Waiting to run
Release / WEBJS - chromium - amd64 - latest (push) Waiting to run
Release / WEBJS - chromium - linux/arm64 - arm (push) Waiting to run
Release / NOWEB - none - linux/arm64 - noweb-arm (push) Waiting to run
2025-01-08 09:46:15 +07:00
devlikepro 17801b0b13 [core] Set log level to "trace" if specified 2025-01-08 09:45:52 +07:00
devlikepro 9010388de0 [core] Add websocket send error handler 2025-01-08 09:45:52 +07:00
devlikepro 2da901f994 [core] Add lock timeout 10s to session operations 2025-01-08 09:45:52 +07:00
devlikepro 7832cc7a5d [core] NOWEB - save creds if any 2025-01-08 09:41:20 +07:00
devlikepro 13ab2e8d0e [core] downloadMedia=false in list messages 2024-12-29 14:52:53 +07:00
devlikepro 868d23a278 [core] Mark /api/messages as deprecated 2024-12-29 14:48:47 +07:00
devlikepro eaf2bca44e [core] 2024.12.6
Release / WEBJS - chrome - amd64 - chrome (push) Waiting to run
Release / WEBJS - chromium - amd64 - latest (push) Waiting to run
Release / WEBJS - chromium - linux/arm64 - arm (push) Waiting to run
Release / NOWEB - none - amd64 - noweb (push) Waiting to run
Release / NOWEB - none - linux/arm64 - noweb-arm (push) Waiting to run
2024-12-24 13:35:25 +07:00
devlikepro af8f537f58 [core] Up dashboard 2024-12-24 13:34:21 +07:00
devlikepro bc7ff472be [core] Chats overview API 2024-12-24 13:33:41 +07:00
devlikepro 9b9a2b5203 [core] NOWEB - listen update profile image 2024-12-24 13:33:40 +07:00
devlikepro 347259394b [core] Cache null picture to avoid additional requests 2024-12-24 13:33:40 +07:00
devlikepro ead61b5e37 [core] Add GET /api/:session/chats/:chatId/picture 2024-12-24 13:33:40 +07:00
devlikepro c9f7814f5d [core] Refresh profile picture 2024-12-24 13:33:39 +07:00
devlikepro 3ee3355e81 [core] Add in-memory cache for profile picture 2024-12-24 13:33:39 +07:00
devlikepro ce8f8b0ce9 [core] Add dedicated fetch contact profile picture
fix #381
2024-12-24 13:33:39 +07:00
devlikepro cae35cf3b9 [core] Up WEBJS
Fix #576 - add retries and send regular message anyway

fix #698
2024-12-24 13:33:39 +07:00
devlikepro 900e3c23bc [core] 2024.12.5
Release / WEBJS - chrome - amd64 - chrome (push) Waiting to run
Release / WEBJS - chromium - amd64 - latest (push) Waiting to run
Release / WEBJS - chromium - linux/arm64 - arm (push) Waiting to run
Release / NOWEB - none - amd64 - noweb (push) Waiting to run
Release / NOWEB - none - linux/arm64 - noweb-arm (push) Waiting to run
2024-12-16 11:56:50 +07:00
devlikepro aaeffeb983 [core] Update libsignal 2024-12-16 11:56:39 +07:00
devlikepro 15600324be [core] Update NOWEB 2024-12-16 11:56:39 +07:00
devlikepro da4913edb5 [core] Use libsignal fork
Add logging
2024-12-16 11:56:39 +07:00
devlikepro e8cc1e4906 [core] 2024.12.4
Release / WEBJS - chrome - amd64 - chrome (push) Waiting to run
Release / WEBJS - chromium - amd64 - latest (push) Waiting to run
Release / WEBJS - chromium - linux/arm64 - arm (push) Waiting to run
Release / NOWEB - none - amd64 - noweb (push) Waiting to run
Release / NOWEB - none - linux/arm64 - noweb-arm (push) Waiting to run
2024-12-14 19:15:09 +07:00
devlikepro 801fc3f9fd [core] Join group via code
fix #688
2024-12-14 19:14:56 +07:00
devlikepro 703b0b4cde [core] Fix groups sort by
Release / WEBJS - chrome - amd64 - chrome (push) Waiting to run
Release / WEBJS - chromium - amd64 - latest (push) Waiting to run
Release / WEBJS - chromium - linux/arm64 - arm (push) Waiting to run
Release / NOWEB - none - amd64 - noweb (push) Waiting to run
Release / NOWEB - none - linux/arm64 - noweb-arm (push) Waiting to run
2024-12-09 11:30:39 +07:00
devlikepro d55895389a [core] 2024.12.3 2024-12-09 10:37:35 +07:00
devlikepro 56009e482b [core] Add dedicated POST /api/{session}/groups/refresh endpoint 2024-12-09 10:19:38 +07:00
devlikepro 767d1bb3e3 [core] Fix 'rate-overlimit' error for groups
fix #462
2024-12-08 18:23:39 +07:00
devlikepro 05132ac0f5 [core] Update NOWEB 1018710061 2024-12-08 18:23:39 +07:00
devlikepro fb372f8c28 [core] Set WHATSAPP_DEFAULT_ENGINE at build time
fix #414
2024-12-08 13:52:43 +07:00
devlikepro a2dc863b7a [core] 12.2
Release / amd64 - chrome - chrome (push) Waiting to run
Release / amd64 - chromium - latest (push) Waiting to run
Release / linux/arm64 - chromium - arm (push) Waiting to run
Release / amd64 - none - noweb (push) Waiting to run
Release / linux/arm64 - none - noweb-arm (push) Waiting to run
2024-12-07 00:15:49 +07:00
devlikepro 15671c6781 [core] NOWEB - potential fix for #671 2024-12-07 00:15:34 +07:00
devlikepro 6296115adb [core] Cache keys between restart 2024-12-07 00:15:34 +07:00
devlikepro 0a099a91d0 [core] 2024.12.1
Release / amd64 - chrome - chrome (push) Waiting to run
Release / amd64 - chromium - latest (push) Waiting to run
Release / linux/arm64 - chromium - arm (push) Waiting to run
Release / amd64 - none - noweb (push) Waiting to run
Release / linux/arm64 - none - noweb-arm (push) Waiting to run
2024-12-06 10:09:04 +07:00
devlikepro 5f060fe022 [core] Fix channel messages
fix #683
2024-12-06 10:09:03 +07:00
devlikepro a4bbe1cf7b [core] Fix GET channel by id
fix #682
2024-12-06 10:09:03 +07:00
devlikepro dce74ae7d5 [core] 2024.11.11
Release / amd64 - chrome - chrome (push) Waiting to run
Release / amd64 - chromium - latest (push) Waiting to run
Release / linux/arm64 - chromium - arm (push) Waiting to run
Release / amd64 - none - noweb (push) Waiting to run
Release / linux/arm64 - none - noweb-arm (push) Waiting to run
2024-12-01 16:23:50 +07:00
devlikepro 871c509493 [core] Up WEBJS
fix #664
2024-12-01 16:23:39 +07:00
devlikepro 9333470e3b [core] Fix 403 on media
fix #667
2024-12-01 15:56:40 +07:00
devlikepro 0c108f5232 [core] 2024.11.10
Release / amd64 - chrome - chrome (push) Waiting to run
Release / amd64 - chromium - latest (push) Waiting to run
Release / linux/arm64 - chromium - arm (push) Waiting to run
Release / amd64 - none - noweb (push) Waiting to run
Release / linux/arm64 - none - noweb-arm (push) Waiting to run
2024-12-01 15:37:21 +07:00
devlikepro 39d298c867 [core] WEBJS fix groups
fix #674 #670
2024-12-01 15:35:00 +07:00
devlikepro 376e804a28 [core] Up NOWEB 2024-12-01 15:16:47 +07:00
devlikepro 79e5c0589d [core] Up NOWEB
Release / amd64 - chrome - chrome (push) Waiting to run
Release / amd64 - chromium - latest (push) Waiting to run
Release / linux/arm64 - chromium - arm (push) Waiting to run
Release / amd64 - none - noweb (push) Waiting to run
Release / linux/arm64 - none - noweb-arm (push) Waiting to run
2024-11-29 14:51:16 +07:00
devlikepro f6679b7368 [core] 2024.11.9 2024-11-29 09:38:06 +07:00
devlikepro a3c0b86b67 [core] Add explicit placeholder cache 2024-11-29 09:37:49 +07:00
devlikepro c8f48dd62b [core] 11.8
Release / amd64 - chrome - chrome (push) Waiting to run
Release / amd64 - chromium - latest (push) Waiting to run
Release / linux/arm64 - chromium - arm (push) Waiting to run
Release / amd64 - none - noweb (push) Waiting to run
Release / linux/arm64 - none - noweb-arm (push) Waiting to run
2024-11-25 12:57:23 +07:00
devlikepro aed157697a [core] WEBJS - Fix QR code
fix #659
2024-11-25 12:56:51 +07:00
devlikepro 913b8606d9 [core] 2024.11.7
Release / amd64 - chrome - chrome (push) Waiting to run
Release / amd64 - chromium - latest (push) Waiting to run
Release / linux/arm64 - chromium - arm (push) Waiting to run
Release / amd64 - none - noweb (push) Waiting to run
Release / linux/arm64 - none - noweb-arm (push) Waiting to run
2024-11-24 14:06:33 +07:00
devlikepro 7cadbad36e [core] Add WebJSEngineConfigService.ts
fix #654 fix #653
2024-11-24 14:06:28 +07:00
devlikepro abb5bdffcc [core] 2024.11.6
Release / amd64 - chrome - chrome (push) Waiting to run
Release / amd64 - chromium - latest (push) Waiting to run
Release / linux/arm64 - chromium - arm (push) Waiting to run
Release / amd64 - none - noweb (push) Waiting to run
Release / linux/arm64 - none - noweb-arm (push) Waiting to run
2024-11-20 16:38:59 +07:00
devlikepro 6a44c15dd3 [core] fix undefined.get
fix #645
2024-11-20 16:38:43 +07:00
devlikepro e669dfe533 [core] Update dashboard
Release / amd64 - chrome - chrome (push) Waiting to run
Release / amd64 - chromium - latest (push) Waiting to run
Release / linux/arm64 - chromium - arm (push) Waiting to run
Release / amd64 - none - noweb (push) Waiting to run
Release / linux/arm64 - none - noweb-arm (push) Waiting to run
2024-11-19 13:17:40 +07:00
devlikepro be888f195d [core] NOWEB - fix timestamp 2024-11-19 13:17:39 +07:00
devlikepro d87cdef311 [core] Add participant for group 2024-11-19 13:17:39 +07:00
devlikepro ecf07cb520 [core] Pin Unpin message
fix #613
2024-11-19 13:17:39 +07:00
devlikepro 10829d53b9 [core] Check quoted message better 2024-11-19 13:17:38 +07:00
devlikepro 71093f2f31 [core] Rm unused code 2024-11-19 13:17:38 +07:00
devlikepro e88192002a [core] Fix log ctx 2024-11-19 13:17:38 +07:00
devlikepro 3e14282603 [core] 2024.11.5 2024-11-19 13:17:37 +07:00
devlikepro 5f16f4ca77 [core] Update dashboard 2024-11-19 13:17:37 +07:00
devlikepro 7b4fa4331f [core] Websockets 2024-11-19 13:17:36 +07:00
devlikepro 3ad0a463b5 [core] Lazy message and message.any 2024-11-19 13:17:36 +07:00
devlikepro d28d640f77 [core] Use EventWildUmask 2024-11-19 13:17:35 +07:00
devlikepro 594c0a6122 [core] Emit events in engine 2024-11-19 13:17:35 +07:00
devlikepro 843a826437 [core] Merge plus webhooks to core 2024-11-19 13:17:35 +07:00
devlikepro c4660bc824 [core] 2024.11.4
Release / amd64 - chrome - chrome (push) Waiting to run
Release / amd64 - chromium - latest (push) Waiting to run
Release / amd64 - none - noweb (push) Waiting to run
Release / linux/arm64 - none - noweb-arm (push) Waiting to run
Release / linux/arm64 - chromium - arm (push) Waiting to run
2024-11-10 12:48:54 +07:00
devlikepro 6053d38e4d [core] Handle WEBJS 429 error
fix #633
2024-11-10 12:48:54 +07:00
devlikepro 3865254a1c [core] NOWEB - get contacts list for status from store if not provided 2024-11-10 12:48:54 +07:00
devlikepro 65b61c09cb [core] Fix 10K status problem
fix #390 fix #413
2024-11-10 12:48:54 +07:00
devlikepro cce1618117 [core] add mongo auth by default 2024-11-08 18:14:35 +07:00
devlikepro 8b4b49a34b [core] waha-plus image 2024-11-08 17:19:50 +07:00
devlikepro 048c5dc2ec [core] send video back
Release / amd64 - chrome - chrome (push) Waiting to run
Release / amd64 - chromium - latest (push) Waiting to run
Release / linux/arm64 - chromium - arm (push) Waiting to run
Release / amd64 - none - noweb (push) Waiting to run
Release / linux/arm64 - none - noweb-arm (push) Waiting to run
fix #629
2024-11-07 11:43:42 +07:00
devlikepro eb218da8fb [core] linkPreview: false|true
fix #596 #598
2024-11-06 15:46:55 +07:00
devlikepro 65942a4ed4 [core] 2024.11.3 2024-11-06 15:46:55 +07:00
devlikepro 1444155c09 [core] Use WAL for sqlite3
fix #628
2024-11-06 15:46:55 +07:00
devlikepro 84ac1d8cfc [core] 2024.11.2
Release / amd64 - chrome - chrome (push) Waiting to run
Release / amd64 - chromium - latest (push) Waiting to run
Release / linux/arm64 - chromium - arm (push) Waiting to run
Release / amd64 - none - noweb (push) Waiting to run
Release / linux/arm64 - none - noweb-arm (push) Waiting to run
2024-11-05 18:39:40 +07:00
devlikepro 440e1e879a [core] WEBJS - fix injection 2024-11-05 18:39:40 +07:00
devlikepro bcecf793d4 [core] WEBJS - only issue WORKING on READY event 2024-11-05 18:39:40 +07:00
devlikepro 16c0d827e1 [core] Add some fonts 2024-11-05 18:39:40 +07:00
devlikepro 94f16a9583 [core] WEBJS - --disable-software-rasterizer 2024-11-05 18:39:40 +07:00
devlikepro 4bdac3bbc2 [core] use sessions folder (no dot) so it's visible via ls 2024-11-05 18:39:40 +07:00
devlikepro 2bc4a59aaf [core] 2024.11.1
Release / amd64 - chrome - chrome (push) Waiting to run
Release / amd64 - chromium - latest (push) Waiting to run
Release / linux/arm64 - chromium - arm (push) Waiting to run
Release / amd64 - none - noweb (push) Waiting to run
Release / linux/arm64 - none - noweb-arm (push) Waiting to run
2024-11-04 12:08:16 +07:00
devlikepro cf886de5ce [core] Add status for WEBJS 2024-11-04 12:08:15 +07:00
devlikepro 9969966ac6 [core] Add labels CRUD
fix #607
2024-11-04 12:08:15 +07:00
devlikepro fb809338e3 [core] 2024.10.14
Release / amd64 - chrome - chrome (push) Waiting to run
Release / amd64 - chromium - latest (push) Waiting to run
Release / linux/arm64 - chromium - arm (push) Waiting to run
Release / amd64 - none - noweb (push) Waiting to run
Release / linux/arm64 - none - noweb-arm (push) Waiting to run
2024-10-31 16:45:38 +07:00
devlikepro c6284eb994 [core] fix /api/messages
fix #622
2024-10-31 16:45:15 +07:00
devlikepro 454bda4c82 [core] fix /api/messages
Release / amd64 - chrome - chrome (push) Waiting to run
Release / amd64 - chromium - latest (push) Waiting to run
Release / linux/arm64 - chromium - arm (push) Waiting to run
Release / amd64 - none - noweb (push) Waiting to run
Release / linux/arm64 - none - noweb-arm (push) Waiting to run
fix #620
2024-10-31 13:07:30 +07:00
devlikepro 26e21441a0 [core] 2024.10.13 2024-10-31 13:04:25 +07:00
devlikepro 2edef0166e [core] add waves on voice back
fix #621
2024-10-31 13:03:46 +07:00
devlikepro 05cb89779b [core] 2024.10.12
Release / amd64 - chrome - chrome (push) Waiting to run
Release / amd64 - chromium - latest (push) Waiting to run
Release / linux/arm64 - chromium - arm (push) Waiting to run
Release / amd64 - none - noweb (push) Waiting to run
Release / linux/arm64 - none - noweb-arm (push) Waiting to run
2024-10-30 16:56:44 +07:00
devlikepro c0b2524952 [core] remove single process 2024-10-30 16:56:30 +07:00
devlikepro f1bb445593 [core] 2024.10.11
Release / amd64 - chrome - chrome (push) Waiting to run
Release / amd64 - chromium - latest (push) Waiting to run
Release / linux/arm64 - chromium - arm (push) Waiting to run
Release / amd64 - none - noweb (push) Waiting to run
Release / linux/arm64 - none - noweb-arm (push) Waiting to run
2024-10-29 17:09:58 +07:00
devlikepro 7f777f338f [core] WEBJS - window size 1280x720
Release / amd64 - chrome - chrome (push) Waiting to run
Release / amd64 - chromium - latest (push) Waiting to run
Release / linux/arm64 - chromium - arm (push) Waiting to run
Release / amd64 - none - noweb (push) Waiting to run
Release / linux/arm64 - none - noweb-arm (push) Waiting to run
2024-10-28 16:37:26 +07:00
devlikepro 4449dfdbd6 [core] 2024.10.10 2024-10-28 15:58:01 +07:00
devlikepro 6226e65e97 [core] Fix Singleton Issues 2024-10-28 15:57:39 +07:00
devlikepro be1783c029 [core] Fix Singleton 2024-10-28 15:57:36 +07:00
devlikepro 83a002ae5b [core] format question.md 2024-10-28 15:57:36 +07:00
devlikepro f0fcb2b329 [core] Update chrome version 2024-10-28 15:57:36 +07:00
devlikepro 5ff695a5b3 [core] Install zip unzip only for WEBJS 2024-10-28 15:57:36 +07:00
devlikepro 43fe1f076e [core] Update puppeter 2024-10-28 15:57:36 +07:00
devlikepro 1afd7e042c [core] fix channels swagger 2024-10-27 17:34:41 +07:00
devlikepro ba6474f97e [core] bufferutil is optional (no arm)
Release / amd64 - chrome - chrome (push) Waiting to run
Release / amd64 - chromium - latest (push) Waiting to run
Release / linux/arm64 - chromium - arm (push) Waiting to run
Release / amd64 - none - noweb (push) Waiting to run
Release / linux/arm64 - none - noweb-arm (push) Waiting to run
2024-10-27 16:52:03 +07:00
devlikepro d8eed114aa Merge remote-tracking branch 'core/core' into core 2024-10-27 15:18:07 +07:00
devlikepro e61ad6f79c [core] Up dashboard 2024-10-27 14:48:32 +07:00
devlikepro 9d27616003 [core] Add UNPAIRING status 2024-10-27 14:47:59 +07:00
devlikepro 0f2641909f [core] Adjust logs 2024-10-27 14:47:58 +07:00
devlikepro 9ca96acda2 [core] Update WEBJS 2024-10-25 17:15:39 +07:00
devlikepro 77db6c5e98 [core] Fix logout (unpair) from device
fix #148 fix #327 fix #194 fix #211
2024-10-25 16:56:06 +07:00
devlikepro c4aa9804d2 [core] 2024.10.9 2024-10-25 15:18:56 +07:00
devlikepro eb7bfe3a62 [core] API - messages filters
fix #449, fix #140, fix #458
2024-10-25 15:18:55 +07:00
devlikepro 01fd7a8d56 [core] WEBJS --disable-features=site-per-process 2024-10-25 15:18:55 +07:00
devlikepro 81fb93e5d6 [core] WEBJS puppeteer headless: new 2024-10-25 15:18:54 +07:00
devlikepro 64716ec6e6 [core] poll message attributes 2024-10-25 15:18:54 +07:00
devlikepro e0f787be32 [core] Update NOWEB
fix #586
2024-10-25 15:18:54 +07:00
devlikepro 138da79599 [core] Pin whatwg-url 2024-10-25 15:18:54 +07:00
devlikepro b8b290c2a5 [core] Update puppeter and ws 2024-10-25 15:18:53 +07:00
devlikepro f35dcc0ec6 [core] Get message by id
fix #159 #484
2024-10-25 15:18:52 +07:00
devlikepro 62704e261a [core] Lint templates 2024-10-25 15:18:52 +07:00
devlikepro f16ace6169 [core] API limit, offset, sort 2024-10-25 15:18:52 +07:00
devlikepro 35628aa93b [core] Fix WEBJS restart on state change 2024-10-25 15:18:52 +07:00
devlikepro 64bf42dc1c [core] Add chat unread
fix #498
2024-10-25 15:18:52 +07:00
devlikeapro 55fc6a4282 Create question.md 2024-10-23 18:23:25 +07:00
devlikepro 1a59300cd7 [core] 2024.10.8
Release / linux/arm64 - none - noweb-arm (push) Waiting to run
Release / amd64 - chrome - chrome (push) Waiting to run
Release / amd64 - chromium - latest (push) Waiting to run
Release / linux/arm64 - chromium - arm (push) Waiting to run
Release / amd64 - none - noweb (push) Waiting to run
2024-10-20 16:11:06 +07:00
devlikepro 9c7d6d835e [core] Add WAHA_LOCAL_STORE_BASE_DIR
fix #597
2024-10-20 16:10:31 +07:00
devlikeapro 2a20242654 Update feature_request.md 2024-10-20 13:39:19 +07:00
devlikeapro 171f9de073 Update bug_report.md 2024-10-20 13:38:37 +07:00
devlikepro 3b4d896bcd [core] 2024.10.7
Release / amd64 - chrome - chrome (push) Waiting to run
Release / amd64 - chromium - latest (push) Waiting to run
Release / linux/arm64 - chromium - arm (push) Waiting to run
Release / amd64 - none - noweb (push) Waiting to run
Release / linux/arm64 - none - noweb-arm (push) Waiting to run
2024-10-19 22:49:09 +07:00
devlikepro 4b602333bc [core] fix webhooks send
fix #595
2024-10-19 22:49:08 +07:00
devlikepro 347cd193dd [core] 2024.10.6
Release / amd64 - chrome - chrome (push) Waiting to run
Release / amd64 - chromium - latest (push) Waiting to run
Release / linux/arm64 - chromium - arm (push) Waiting to run
Release / amd64 - none - noweb (push) Waiting to run
Release / linux/arm64 - none - noweb-arm (push) Waiting to run
2024-10-17 13:22:09 +07:00
devlikepro 964d8dde9b [core] await start
Release / amd64 - chrome - chrome (push) Waiting to run
Release / amd64 - chromium - latest (push) Waiting to run
Release / linux/arm64 - chromium - arm (push) Waiting to run
Release / amd64 - none - noweb (push) Waiting to run
Release / linux/arm64 - none - noweb-arm (push) Waiting to run
2024-10-16 14:04:59 +07:00
devlikepro bf794ce67f [core] default 0 seconds delay 2024-10-16 14:03:32 +07:00
devlikepro 51dc9ea8c1 [core] Add restart back 2024-10-16 13:56:58 +07:00
devlikepro 791a1fd475 [core] 2024.10.5 2024-10-16 13:54:38 +07:00
devlikepro 244eb5f692 [core] Dashboard 2024-10-16 13:54:20 +07:00
devlikepro 003bc33c25 [core] Adjust logs 2024-10-16 13:53:16 +07:00
devlikepro b488b9f1df [core] Get body for buttons response 2024-10-16 13:53:15 +07:00
devlikepro ff4f0a59af [core] Provide button.id 2024-10-16 13:53:14 +07:00
devlikepro 10e2c4bbd4 [core] WEBJS - handle possible stuck STARTING
fix #586
2024-10-16 13:53:14 +07:00
devlikepro 18536330ce [core] Add WAHA_AUTO_START_DELAY_SECONDS
fix #586
2024-10-16 13:53:14 +07:00
devlikepro e894069d4e [core] fix NOWEB stuck in STARTING
fix #586
2024-10-16 13:53:13 +07:00
devlikepro 5be2ca0deb [core] delay policy
fix #587
2024-10-16 13:53:12 +07:00
devlikepro 83e2405151 [core] Button messages
fix #544
2024-10-16 13:53:12 +07:00
devlikepro 220c0dfb01 [core] Use waha logo 2024-10-16 13:53:10 +07:00
devlikepro 5474f1ec0e [core] Forward message
fix #158
2024-10-16 13:53:10 +07:00
devlikepro dbd01abde8 [core] Set FAILED before end 2024-10-16 13:53:10 +07:00
devlikepro 83bbab0969 [core] Add request id for webhooks in logs
fix #580
2024-10-16 13:53:09 +07:00
devlikepro cc6808a6cd [core] add asNote for /api/sendVideo
fix #585
2024-10-16 13:53:09 +07:00
devlikepro 191fd89cfb [core] Dashboard at the top 2024-10-16 13:53:09 +07:00
devlikepro 42e5b46365 [core] move docker-compose workers 2024-10-16 13:53:06 +07:00
devlikepro f444ce86f9 [core] no RESTART_ALL by default 2024-10-16 13:53:06 +07:00
devlikepro ce05b3d087 [core] Restore sessions 2024-10-16 13:53:06 +07:00
devlikepro ab4dd28c51 [core] docker-compose.workers.yaml 2024-10-16 13:53:05 +07:00
devlikepro 9e23b287ff [core] Use Sqlite3SchemaValidation for me repo 2024-10-16 13:53:05 +07:00
devlikepro 135c1ab10b [core] typo 2024-10-16 13:53:04 +07:00
devlikepro de680c93f6 [core] Do not check current session status 2024-10-16 13:53:04 +07:00
devlikepro 57ac57824f [core] Move startPredefinedSessions to SessionManager 2024-10-16 13:53:04 +07:00
devlikepro 38df64c191 [core] Move config to SessionManager 2024-10-16 13:53:04 +07:00
devlikepro 575969f938 [core] Move log to SessionManager 2024-10-16 13:53:04 +07:00
devlikepro 42c388e5c9 [core] Add waha logo in examples 2024-10-14 15:56:49 +07:00
devlikepro 6346433d45 [core] wait 10 seconds and stop
Release / amd64 - chrome - chrome (push) Waiting to run
Release / amd64 - chromium - latest (push) Waiting to run
Release / linux/arm64 - chromium - arm (push) Waiting to run
Release / amd64 - none - noweb (push) Waiting to run
Release / linux/arm64 - none - noweb-arm (push) Waiting to run
2024-10-08 18:04:44 +07:00
devlikepro 44e63e00c3 [core] always 2024-10-08 17:55:36 +07:00
devlikepro 8648b44583 [core] stop all jobs before saving creds 2024-10-08 17:49:50 +07:00
devlikepro 086d540ee2 [core] 2024.10.4 2024-10-08 17:46:22 +07:00
devlikepro adf6836ee5 [core] no get app anymore 2024-10-08 17:46:03 +07:00
devlikepro 62f4406c51 [core] send SIGTERM 2024-10-08 17:46:03 +07:00
devlikepro e4c201e105 [core] exec node dist/main 2024-10-08 17:46:03 +07:00
devlikepro b6b0b1e7b3 [core] enable hooks before start listening 2024-10-08 17:46:03 +07:00
devlikepro 9d857d015f [core] Log signals 2024-10-08 17:46:03 +07:00
devlikepro 2055c595bb [core] NOWEB - save creds before stopping the session 2024-10-08 17:46:03 +07:00
devlikepro 00e33ceac7 [core] NOWEB - reuse store 2024-10-08 17:46:02 +07:00
devlikepro 77ef1ecebf [core] Update NOWEB
fix #567
2024-10-08 17:45:56 +07:00
devlikepro 57672a2a9c [core] 2024.10.3
Release / amd64 - chrome - chrome (push) Waiting to run
Release / amd64 - chromium - latest (push) Waiting to run
Release / linux/arm64 - chromium - arm (push) Waiting to run
Release / amd64 - none - noweb (push) Waiting to run
Release / linux/arm64 - none - noweb-arm (push) Waiting to run
2024-10-03 14:50:52 +07:00
devlikepro 0ae594b198 [core] Add more logging in WEBJS 2024-10-03 14:50:27 +07:00
devlikepro 14a7e1c72d .env.example 2024-10-01 18:35:46 +07:00
devlikepro e570cbef12 [core] Up dashboard
Release / amd64 - chrome - chrome (push) Waiting to run
Release / amd64 - chromium - latest (push) Waiting to run
Release / linux/arm64 - chromium - arm (push) Waiting to run
Release / amd64 - none - noweb (push) Waiting to run
Release / linux/arm64 - none - noweb-arm (push) Waiting to run
2024-10-01 10:51:25 +07:00
devlikepro c6d732a723 [core] 2024.10.2 2024-10-01 10:50:36 +07:00
devlikepro 9cb0131a37 [core] Close store properly at the end 2024-10-01 10:50:19 +07:00
devlikepro d72b665cfe [core] update mongodb driver 2024-10-01 10:50:18 +07:00
devlikepro d6b87585f7 [core] log errors during periodic and one time jobs 2024-10-01 10:50:18 +07:00
devlikepro 2e01692e92 [core] comment mongo and minio 2024-09-29 18:20:22 +07:00
devlikepro 78ad79c08f [core] comment mongo and minio 2024-09-29 18:16:44 +07:00
devlikepro 725446a022 [core] fix .env 2024-09-29 17:48:23 +07:00
devlikepro 3bd7594b7f [core] no ssl 2024-09-29 17:41:14 +07:00
devlikepro 2bd314732a [core] Do not expose by default 2024-09-29 17:22:48 +07:00
devlikepro f4d7776bca [core] Adjust .env 2024-09-29 16:53:09 +07:00
devlikepro 80e13beb2b [core] Split .env 2024-09-29 16:51:22 +07:00
devlikepro 5c37826710 [core] protected 2024-09-29 16:35:24 +07:00
devlikepro 693a288839 [core] Move lock to SessionManager 2024-09-29 16:00:09 +07:00
devlikepro ecc0e1b60d [core] Allow to stop sessions even with no config 2024-09-29 15:43:52 +07:00
devlikepro 1d60a4ca42 [core] Update dashboard
Release / amd64 - chrome - chrome (push) Waiting to run
Release / amd64 - chromium - latest (push) Waiting to run
Release / linux/arm64 - chromium - arm (push) Waiting to run
Release / amd64 - none - noweb (push) Waiting to run
Release / linux/arm64 - none - noweb-arm (push) Waiting to run
2024-09-27 16:35:19 +07:00
devlikepro 7bf343d071 [core] 2024.10.1 2024-09-27 16:34:56 +07:00
devlikepro 0430b0ed53 [core] WEBJS - cancel check engine state job 2024-09-27 16:34:39 +07:00
devlikepro 9147dd9925 [core] jobs - do not log if it's not started 2024-09-27 16:34:39 +07:00
devlikepro b6e7fac619 [core] Up NOWEB 2024-09-27 16:34:38 +07:00
devlikepro c1d15e570d [core] Up WEBJS 2024-09-27 16:34:38 +07:00
devlikepro 72b067c8a7 [core] WEBJS restart session on WAState.OPENING
fix #552
2024-09-27 16:34:38 +07:00
devlikepro 94df9a681d [core] Add message.waiting event
fix #554
2024-09-27 16:34:38 +07:00
devlikepro 1d30bb7e8e [core] add WAHA_BASE_URL to override media URLs 2024-09-27 16:34:37 +07:00
devlikepro 0779e326d7 [core] add PORT for heroku support
fix #555
2024-09-27 16:34:37 +07:00
devlikepro 3ad5b67fef [core] Receive media in WAHA Core 2024-09-27 16:34:37 +07:00
devlikepro d747bf72fd [core] Use axios 2024-09-27 16:34:37 +07:00
devlikepro 8474cb5789 [core] Ignore pycache 2024-09-27 16:34:37 +07:00
devlikepro 83650c7dc9 [core] update dashboard 2024-09-19 17:48:38 +07:00
devlikepro 74bbbc4b1d [core] Update dashboard
Release / amd64 - chrome - chrome (push) Waiting to run
Release / amd64 - chromium - latest (push) Waiting to run
Release / linux/arm64 - chromium - arm (push) Waiting to run
Release / amd64 - none - noweb (push) Waiting to run
Release / linux/arm64 - none - noweb-arm (push) Waiting to run
2024-09-18 14:00:48 +07:00
devlikepro 743e4f3e31 [core] markOnline NOWEB
fix #412
2024-09-18 13:55:24 +07:00
devlikepro d7a699c714 [core] 2024.9.6 2024-09-17 15:24:36 +07:00
devlikepro 366ab8355a [core] fix auto restart issues - NOWEB, WEBJS 2024-09-17 15:23:33 +07:00
devlikepro e721270343 [core] Update dashboard 2024-09-17 15:02:40 +07:00
devlikepro 2ba7d1a425 [core] WEBJS pairing code 2024-09-17 11:30:30 +07:00
devlikepro b1a5dd3b3c [core] Update dashboard 2024-09-17 10:40:26 +07:00
devlikepro cbc2d1ef9b [core] me for stopped sessions 2024-09-17 10:39:58 +07:00
devlikepro 95f58df3c2 [core] Fix error message 2024-09-16 15:14:57 +07:00
devlikepro 01da700458 [core] wait check session in the right status 2024-09-16 14:47:20 +07:00
devlikepro 62f9a000f2 [core] fix api in example 2024-09-16 13:05:06 +07:00
devlikepro 90f32638be [core] Fix default session behavior 2024-09-16 12:59:51 +07:00
devlikepro 6aa141bb33 [core] engine info only in
GET /api/sessions/{name}
2024-09-16 12:53:27 +07:00
devlikepro 6d46ad44a1 [core] Fix clearInterval on WebsocketHeartbeatJob.ts 2024-09-16 12:53:27 +07:00
devlikepro afea71cfeb [core] move docker-compose to docker-compose 2024-09-16 10:38:21 +07:00
devlikepro 866b3c1f65 [core] Issue STARTING status in NOWEB on auto restarts 2024-09-16 10:22:05 +07:00
devlikepro 858f89a7b9 [core] TZ 2024-09-16 09:21:56 +07:00
devlikepro 98d68d4f6e [core] TZ 2024-09-16 09:19:06 +07:00
devlikepro ea69e725eb [core] TZ 2024-09-16 09:18:50 +07:00
devlikepro 7a27078532 [core] Put dashboard first 2024-09-16 09:08:42 +07:00
devlikepro 2ef48bf6ce [core] log exact error 2024-09-13 18:34:07 +07:00
devlikepro 588a209a6e [core] FROM ... AS ... 2024-09-13 12:41:45 +07:00
devlikepro 8103c4f50a add yarn/releases 2024-09-13 12:35:35 +07:00
devlikepro cb74c63474 Revert "rm .yarnrc.yml"
This reverts commit b6bd0c3d6ab5bbf58098cf692f67e35a10c2802a.
2024-09-13 12:35:30 +07:00
devlikepro 1e9711d58b rm .yarnrc.yml 2024-09-13 12:35:29 +07:00
devlikepro 48876d2726 [core] rm GOSS_PATH 2024-09-13 11:47:12 +07:00
devlikepro 56da84052c [core] sleep, docker logs 2024-09-13 11:46:47 +07:00
devlikepro e2e5bd54a1 [core] add goss 2024-09-13 11:42:24 +07:00
devlikepro c071776215 [core] adjust build matrix 2024-09-13 11:42:22 +07:00
devlikepro a06361f246 [core] rm manual-push.yaml 2024-09-13 10:18:56 +07:00
devlikepro 3a7b68430c [core] Add /api/server/stop response
fix #533
2024-09-12 11:30:53 +07:00
devlikepro d85e2498fd [core] Up dashboard
Release / amd64 - chrome - chrome (push) Waiting to run
Release / amd64 - chromium - latest (push) Waiting to run
Release / linux/arm64 - chromium - arm (push) Waiting to run
Release / amd64 - none - noweb (push) Waiting to run
Release / linux/arm64 - none - noweb-arm (push) Waiting to run
2024-09-11 13:14:22 +07:00
devlikepro 507382b6d2 [core] 2024.9.5 2024-09-11 09:13:28 +07:00
devlikepro f343cf8549 [core] Show better error for screenshot
fix #538
2024-09-11 09:12:54 +07:00
devlikepro 832d5baf35 [core] WEBJS optimizations 2024-09-11 09:02:36 +07:00
devlikepro 0253f32574 [core] node 22.8 2024-09-11 09:02:36 +07:00
devlikepro 6173180272 [core] remove VENOM 2024-09-11 09:02:31 +07:00
devlikepro 2d21931fb1 [core] log stack 2024-09-11 09:02:30 +07:00
devlikepro bdaa7187fd [core] accept phone as chatId in typing and presense 2024-09-10 16:12:03 +07:00
devlikepro e7ad6e06ec [core] use node dist/main 2024-09-10 10:33:43 +07:00
devlikepro 7c14483852 [core] YARN_CHECKSUM_BEHAVIOR=update 2024-09-10 10:10:58 +07:00
devlikepro 8b4b1f934e [core] Add corepack 2024-09-10 09:49:03 +07:00
devlikepro 713eb3f169 [core] Up nodejs 20.17.0 2024-09-09 16:33:34 +07:00
devlikepro 48297c80d1 [core] remove listeners 2024-09-09 15:58:52 +07:00
devlikepro 315ffdac92 [core] set timeout null at finally 2024-09-09 15:47:56 +07:00
devlikepro 7ddc17e6e5 [core] Add /api/server/debug/heapsnapshot 2024-09-09 15:33:24 +07:00
devlikepro e3b8073c82 [core] force null timeout and interval 2024-09-09 14:23:07 +07:00
devlikepro 80517e416c [core] WAHA_LOG_FORMAT=JSON in examples 2024-09-09 10:58:04 +07:00
devlikepro 4b71aade9d [core] gitignore heapsnapshot 2024-09-09 10:44:42 +07:00
devlikepro 1b52698c54 [core] remove session run time 2024-09-09 10:43:52 +07:00
devlikepro b8f372d83f [core] n8n docker-compose 2024-09-08 12:24:01 +07:00
devlikepro 7bc5f55fb2 [core] n8n docker-compose 2024-09-08 11:11:46 +07:00
devlikepro 7eeafd03b3 [core] fix mongodb
Release / amd64 - chrome - chrome (push) Waiting to run
Release / amd64 - chromium - latest (push) Waiting to run
Release / linux/arm64 - chromium - arm (push) Waiting to run
Release / amd64 - none - noweb (push) Waiting to run
Release / linux/arm64 - none - noweb-arm (push) Waiting to run
2024-09-06 09:32:18 +07:00
devlikepro f88480d271 [core] 2024.9.4 2024-09-06 09:24:10 +07:00
devlikepro 9b8cce85bd [core] Fix operations for empty config sessions - local files 2024-09-06 09:23:45 +07:00
devlikepro aa364b57e8 [core] up NOWEB
Release / amd64 - chrome - chrome (push) Waiting to run
Release / amd64 - chromium - latest (push) Waiting to run
Release / linux/arm64 - chromium - arm (push) Waiting to run
Release / amd64 - none - noweb (push) Waiting to run
Release / linux/arm64 - none - noweb-arm (push) Waiting to run
2024-09-04 10:53:01 +07:00
devlikepro ea973d0de4 [core] up NOWEB 2024-09-04 10:36:14 +07:00
devlikepro 1a1b64cbb2 [core] 2024.9.3 2024-09-04 10:14:24 +07:00
devlikepro 09c7e11dbc [core] up NOWEB - end() issues 2024-09-04 10:14:12 +07:00
devlikepro 3450a56756 [core] swagger participants 2024-09-03 19:30:01 +07:00
devlikepro f37f4466ae [core] swagger noweb store 2024-09-03 19:23:37 +07:00
devlikepro 451834b219 [core] swagger caption not required 2024-09-03 19:13:27 +07:00
devlikepro 91924353e2 [core] swagger caption not required 2024-09-03 19:13:21 +07:00
devlikepro 67cf94fc95 [core] swagger version 2024-09-03 18:44:38 +07:00
devlikepro 36a343c09f [core] swagger status 2024-09-03 18:43:52 +07:00
devlikepro 4a37aebbf1 [core] swagger server 2024-09-03 18:41:25 +07:00
devlikepro d5fa9f8d72 [core] swagger presence 2024-09-03 18:40:48 +07:00
devlikepro 08c244334b [core] swagger ping 2024-09-03 18:38:17 +07:00
devlikepro 23dd1c2726 [core] swagger labels 2024-09-03 18:37:59 +07:00
devlikepro 23a309d394 [core] swagger health 2024-09-03 18:37:05 +07:00
devlikepro 3c1bb4e145 [core] swagger groups 2024-09-03 18:36:47 +07:00
devlikepro 87b320a3e3 [core] swagger contacts 2024-09-03 18:29:03 +07:00
devlikepro e4fb9615c7 [core] swagger chatting 2024-09-03 18:26:50 +07:00
devlikepro efad4de0af [core] swagger chats 2024-09-03 18:20:33 +07:00
devlikepro 04f62700a2 [core] swagger auth channels fixes 2024-09-03 18:16:52 +07:00
devlikepro 08415f7931 [core] no code 2024-09-03 18:12:42 +07:00
devlikepro 8bc86af3c8 [core] fix swagger 2024-09-01 19:46:18 +07:00
devlikepro fa2c5e42dc [core] fix swagger 2024-09-01 19:43:23 +07:00
devlikepro e3d3952b81 [core] fix participant cus in reply 2024-08-30 21:32:27 +07:00
489 changed files with 109437 additions and 11962 deletions

No files matched your search

+6
View File
@@ -1,5 +1,9 @@
tmp/*
Dockerfile
docker-compose
src/core/engines/gows/proto
.*sessions
sessions
files
venv
.wwebjs_auth
@@ -7,6 +11,8 @@ docs
examples
.github
.git
.envs/*
.env
# compiled output
/dist
+133
View File
@@ -0,0 +1,133 @@
# WAHA - WhatsApp HTTP API
#
# https://waha.devlike.pro/docs/how-to/config/
#
# ====================
# ===== SECURITY =====
# ====================
WAHA_API_KEY=00000000000000000000000000000000
WAHA_DASHBOARD_USERNAME=admin
WAHA_DASHBOARD_PASSWORD=11111111111111111111111111111111
WHATSAPP_SWAGGER_USERNAME=admin
WHATSAPP_SWAGGER_PASSWORD=11111111111111111111111111111111
# Disable Dashboard or Swagger
WAHA_DASHBOARD_ENABLED=True
WHATSAPP_SWAGGER_ENABLED=True
# ==================
# ===== COMMON =====
# ==================
# WhatsApp engine (WEBJS is default, GOWS or NOWEB for better performance)
WHATSAPP_DEFAULT_ENGINE=WEBJS
# "Firefox (YourApp)" in Linked Devices
# WAHA_CLIENT_DEVICE_NAME=YourApp
# Base URL for the API (used for webhooks, file URLs, etc.)
WAHA_BASE_URL=http://localhost:3000
# WAHA_PUBLIC_URL=http://localhost:3000
# Server configuration (if you need to customize hostname/port)
# WHATSAPP_API_SCHEMA=http
# WHATSAPP_API_PORT=3000
# WHATSAPP_API_HOSTNAME=localhost
# ===================
# ====== GOWS =======
# ===================
# By default, GOWS uses cgo (libc) DNS resolver
# You can ask to use golang one, but it's less reliable
# GODEBUG=netdns=go
# ===================
# ===== LOGGING =====
# ===================
# Log format: JSON (for log management systems) or PRETTY (for development)
WAHA_LOG_FORMAT=JSON
# Log level: info, debug, error, warn
WAHA_LOG_LEVEL=info
# Don't print QR codes in logs
WAHA_PRINT_QR=False
# =========================
# ===== MEDIA STORAGE =====
# =========================
# Local storage (default)
WAHA_MEDIA_STORAGE=LOCAL
WHATSAPP_FILES_LIFETIME=0
WHATSAPP_FILES_FOLDER=/app/.media
# Media download settings
# WHATSAPP_DOWNLOAD_MEDIA=true
# WHATSAPP_FILES_MIMETYPES=image/jpeg,image/png
# S3 storage (uncomment to use)
# WAHA_MEDIA_STORAGE=S3
# WAHA_S3_REGION=eu-west-2
# WAHA_S3_BUCKET=waha
# WAHA_S3_ACCESS_KEY_ID=minioadmin
# WAHA_S3_SECRET_ACCESS_KEY=minioadmin
# WAHA_S3_ENDPOINT=http://minio:9000
# WAHA_S3_FORCE_PATH_STYLE=True
# WAHA_S3_PROXY_FILES=True
# PostgreSQL storage (uncomment to use)
# WAHA_MEDIA_STORAGE=POSTGRESQL
# WAHA_MEDIA_POSTGRESQL_URL=postgres://postgres:postgres@postgres:5432/postgres?sslmode=disable
# ===========================
# ===== SESSION STORAGE =====
# ===========================
# PostgreSQL for sessions (uncomment to use)
# WHATSAPP_SESSIONS_POSTGRESQL_URL=postgres://postgres:postgres@postgres:5432/postgres?sslmode=disable
# MongoDB for sessions (uncomment to use)
# WHATSAPP_SESSIONS_MONGO_URL=mongodb://mongouser:mongopassword@mongodb:27017
# =================
# ===== APPS ======
# =================
# WAHA_APPS_ENABLED=True
# REDIS_URL=redis://:redis@redis:6379
# Apps Jobs configuration - *_AGE in seconds
# WAHA_APPS_JOBS_REMOVE_ON_COMPLETE_AGE=
# WAHA_APPS_JOBS_REMOVE_ON_COMPLETE_COUNT=
# WAHA_APPS_JOBS_REMOVE_ON_FAIL_AGE=
# WAHA_APPS_JOBS_REMOVE_ON_FAIL_COUNT
# ==================================
# ===== ADVANCED CONFIGURATION =====
# ==================================
# Timezone for screenshots and logs
# TZ=Europe/Dublin
# Session management
# WHATSAPP_START_SESSION=session1,session2
# WHATSAPP_RESTART_ALL_SESSIONS=False
# Webhooks
# WHATSAPP_HOOK_URL=https://webhook.site/11111111-1111-1111-1111-11111111
# WHATSAPP_HOOK_CUSTOM_HEADERS=custom_header_key:custom_header_value
# WHATSAPP_HOOK_EVENTS=session.status,message,message.reaction
# Proxy configuration
# WHATSAPP_PROXY_SERVER=proxy.example.com:3128
# WHATSAPP_PROXY_SERVER_USERNAME=user
# WHATSAPP_PROXY_SERVER_PASSWORD=pass
# HTTPS configuration
# !DEPRECATED!
# Setup nginx reverse proxy to handle TLS connection
# using Let's encrypt or self-issued certificate
# WAHA_HTTPS_ENABLED=true
# WAHA_HTTPS_PATH_KEY=/etc/letsencrypt/live/waha.example.pro/privkey.pem
# WAHA_HTTPS_PATH_CERT=/etc/letsencrypt/live/waha.example.pro/cert.pem
# WAHA_HTTPS_PATH_CA=/etc/letsencrypt/live/waha.example.pro/chain.pem
+3
View File
@@ -0,0 +1,3 @@
src/core/engines/gows/grpc/*
src/core/engines/gows/proto/*
src/core/engines/webjs/_lodash.js
-36
View File
@@ -1,36 +0,0 @@
module.exports = {
parser: '@typescript-eslint/parser',
parserOptions: {
project: 'tsconfig.json',
sourceType: 'module',
},
plugins: ['@typescript-eslint/eslint-plugin', 'simple-import-sort'],
extends: [
'plugin:@typescript-eslint/eslint-recommended',
'plugin:@typescript-eslint/recommended',
'prettier',
'prettier/@typescript-eslint',
],
root: true,
env: {
node: true,
jest: true,
},
rules: {
'simple-import-sort/imports': 'error',
'simple-import-sort/exports': 'error',
'@typescript-eslint/interface-name-prefix': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-inferrable-types': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/ban-ts-ignore': 'off',
'@typescript-eslint/ban-ts-comment': 'off',
'@typescript-eslint/no-unused-vars': 'off',
'lines-between-class-members': [
'error',
'always',
{ exceptAfterSingleLine: true },
],
},
};
+27 -29
View File
@@ -1,30 +1,34 @@
---
name: Bug report
about: Create a report to help us improve
title: '[BUG][WEBJS] - '
title: '[WEBJS] - '
labels: bug
assignees: ''
type: 'BUG'
---
**Describe the bug**
### Describe the bug
> A clear and concise description of what the bug is. Feel free to remove
> sections that you don't feel to make the text shorter!
A clear and concise description of what the bug is. Feel free to remove sections
that you don't feel to make the text shorter!
**Version**
### Version
Get the WAHA version by calling `GET /api/version`
```json
{
"version": "2023.11.1",
"engine": "NOWEB",
"tier": "PLUS",
"browser": "/usr/bin/google-chrome-stable"
"version": "YYYY.MM.DD",
"engine": "ENGINE",
"tier": "TIER"
}
```
> You can get the WAHA version by calling `GET /api/version` Try to update to
> [the latest version](https://github.com/devlikeapro/waha/releases) before
> creating an issue!
Try to update to
[the latest version](https://github.com/devlikeapro/waha/releases) before
creating an issue!
## Steps
**To Reproduce** Steps to reproduce the behavior:
@@ -33,29 +37,23 @@ assignees: ''
3. Scroll down to '....'
4. See error
**Expected behavior**
### Expected behavior
> A clear and concise description of what you expected to happen.
A clear and concise description of what you expected to happen.
**Engine** I'm using **WEBJS** engine.
### Requests - Responses
> If you set `WHATSAPP_DEFAULT_ENGINE` explicitly to
> [one of the engine](https://waha.devlike.pro/docs/how-to/engines/) - please
> provide that information here and in the issue title.
Please attach all sent requests, responses that you sent.
**Requests - Responses**
### Docker Logs
> Please attach all sent requests, responses that you sent.
Collect and attach related docker logs if you have any
https://waha.devlike.pro/docs/how-to/deploy/#viewing-the-logs
**Docker Logs**
### Screenshots
> Collect and attach related docker logs if you have any
> https://waha.devlike.pro/docs/how-to/deploy/#viewing-the-logs
If applicable, add screenshots to help explain your problem.
**Screenshots**
### Additional context
> If applicable, add screenshots to help explain your problem.
**Additional context**
> Add any other context about the problem here.
Add any other context about the problem here.
+6 -5
View File
@@ -1,24 +1,25 @@
---
name: Feature request
about: Suggest an idea for this project
title: '[Feature Request]'
title: ''
labels: ''
assignees: ''
type: 'Feature'
---
**Is your feature request related to a problem? Please describe.** I'm always
frustrated when [...]
> A clear and concise description of what the problem is. Ex.
A clear and concise description of what the problem is. Ex.
**Describe the solution you'd like**
> A clear and concise description of what you want to happen.
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
> A clear and concise description of any alternative solutions or features
> you've considered.
A clear and concise description of any alternative solutions or features you've
considered.
**Additional context** Add any other context or screenshots about the feature
request here.
+11
View File
@@ -0,0 +1,11 @@
---
name: Question
about: I have a question about the project or feature
title: '[Question] '
labels: 'question'
assignees: ''
---
👉 Kindly use
[GitHub Discussions](https://github.com/devlikeapro/waha/discussions) to ask
questions!
+128 -10
View File
@@ -8,38 +8,74 @@ on:
jobs:
docker-build:
runs-on: ${{ matrix.runner }}
name: ${{ matrix.platform }} - ${{ matrix.browser }} - ${{ matrix.tag }}
name:
${{ matrix.engine }} - ${{ matrix.browser }} - ${{ matrix.platform }} -
${{ matrix.tag }}
strategy:
matrix:
include:
# Chromium
# Chromium - x86
- runner: 'buildjet-4vcpu-ubuntu-2204'
tag: 'latest'
platform: 'amd64'
browser: 'chromium'
engine: 'WEBJS'
goss: 'goss-linux-amd64'
# Chromium - ARM
- runner: 'buildjet-4vcpu-ubuntu-2204-arm'
tag: 'arm'
platform: 'linux/arm64'
browser: 'chromium'
# Chrome
engine: 'WEBJS'
goss: 'goss-linux-arm'
# Chrome - x86
- runner: 'buildjet-4vcpu-ubuntu-2204'
tag: 'chrome'
platform: 'amd64'
browser: 'chrome'
# Chrome is not supported on arm64
# - runner: "buildjet-4vcpu-ubuntu-2204-arm"
# tag: "chrome-arm"
# platform: "linux/arm64"
# browser: "chrome"
# No browser
engine: 'WEBJS'
goss: 'goss-linux-amd64'
# Chrome - ARM (Chrome is not available for ARM)
#- runner: "buildjet-4vcpu-ubuntu-2204-arm"
# tag: "chrome-arm"
# platform: "linux/arm64"
# browser: "chrome"
# goss: 'goss-linux-arm'
# NOWEB - x86
- runner: 'buildjet-4vcpu-ubuntu-2204'
tag: 'noweb'
platform: 'amd64'
browser: 'none'
engine: 'NOWEB'
goss: 'goss-linux-amd64'
# NOWEB - ARM
- runner: 'buildjet-4vcpu-ubuntu-2204-arm'
tag: 'noweb-arm'
platform: 'linux/arm64'
browser: 'none'
engine: 'NOWEB'
goss: 'goss-linux-arm'
# GOWS - x86
- runner: 'buildjet-4vcpu-ubuntu-2204'
tag: 'gows'
platform: 'amd64'
browser: 'none'
engine: 'GOWS'
goss: 'goss-linux-amd64'
# No browser - ARM
- runner: 'buildjet-4vcpu-ubuntu-2204-arm'
tag: 'gows-arm'
platform: 'linux/arm64'
browser: 'none'
engine: 'GOWS'
goss: 'goss-linux-arm'
steps:
- name: Checkout
@@ -64,7 +100,9 @@ jobs:
username: ${{ secrets.DOCKER_USER }}
password: ${{ secrets.DOCKER_TOKEN }}
- name: Build and push
- name: Build (attempt 1)
id: build1
continue-on-error: true
uses: docker/build-push-action@v4
with:
context: .
@@ -72,6 +110,86 @@ jobs:
platforms: ${{ matrix.platform }}
build-args: |
USE_BROWSER=${{ matrix.browser }}
WHATSAPP_DEFAULT_ENGINE=${{ matrix.engine }}
push: false
load: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
- name: Wait to retry build
if: steps.build1.outcome == 'failure'
run: |
sleep 10
- name: Build (attempt 2)
id: build2
if: steps.build1.outcome == 'failure'
continue-on-error: true
uses: docker/build-push-action@v4
with:
context: .
file: Dockerfile
platforms: ${{ matrix.platform }}
build-args: |
USE_BROWSER=${{ matrix.browser }}
WHATSAPP_DEFAULT_ENGINE=${{ matrix.engine }}
push: false
load: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
- name: Wait to retry build
if: steps.build2.outcome == 'failure'
run: |
sleep 10
- name: Build (attempt 3)
if: steps.build2.outcome == 'failure'
uses: docker/build-push-action@v4
with:
context: .
file: Dockerfile
platforms: ${{ matrix.platform }}
build-args: |
USE_BROWSER=${{ matrix.browser }}
WHATSAPP_DEFAULT_ENGINE=${{ matrix.engine }}
push: false
load: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
- name: Install goss
env:
GOSS_DOWNLOAD: 'https://github.com/goss-org/goss/releases/download'
GOSS_VERSION: 'v0.4.8'
run: |
curl -L https://github.com/goss-org/goss/releases/download/${{ env.GOSS_VERSION }}/${{ matrix.goss }} -o /usr/local/bin/goss
chmod +rx /usr/local/bin/goss
- name: Run smoke tests
working-directory: tests/smoke
timeout-minutes: 2
run: |
docker run -d \
--name smoke \
--rm -p3000:3000 \
-e WAHA_API_KEY=test \
${{ vars.DOCKER_IMAGE }}:${{ matrix.tag }}
sleep 3
docker logs smoke
goss validate --retry-timeout 30s --sleep 1s
docker rm --force smoke
- name: Push
uses: docker/build-push-action@v4
with:
context: .
file: Dockerfile
platforms: ${{ matrix.platform }}
build-args: |
USE_BROWSER=${{ matrix.browser }}
WHATSAPP_DEFAULT_ENGINE=${{ matrix.engine }}
push: ${{ github.ref_type == 'tag' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
+218
View File
@@ -0,0 +1,218 @@
name: Dev
on:
schedule:
- cron: '0 0 * * *'
workflow_dispatch:
jobs:
unit-tests:
if: github.repository == 'devlikeapro/waha-plus'
runs-on: ubuntu-22.04
name: Unit tests
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Node
uses: actions/setup-node@v4
with:
node-version: 22
- name: Enable Corepack
run: |
corepack enable
corepack prepare yarn@4.9.2 --activate
- name: Install dependencies
run: |
unset GIT_CONFIG_PARAMETERS
yarn install --immutable
- name: Run unit tests
run: yarn test:unit
docker-build:
if: github.repository == 'devlikeapro/waha-plus'
needs: unit-tests
runs-on: ${{ matrix.runner }}
name:
${{ matrix.engine }} - ${{ matrix.browser }} - ${{ matrix.platform }} -
${{ matrix.tag }}
strategy:
matrix:
include:
# Chromium - x86
- runner: 'buildjet-4vcpu-ubuntu-2204'
tag: 'dev'
platform: 'amd64'
browser: 'chromium'
engine: 'WEBJS'
goss: 'goss-linux-amd64'
# Chrome - x86
- runner: 'buildjet-4vcpu-ubuntu-2204'
tag: 'dev-chrome'
platform: 'amd64'
browser: 'chrome'
engine: 'WEBJS'
goss: 'goss-linux-amd64'
# Chromium - ARM
- runner: 'buildjet-4vcpu-ubuntu-2204-arm'
tag: 'dev-arm'
platform: 'linux/arm64'
browser: 'chromium'
engine: 'WEBJS'
goss: 'goss-linux-arm'
steps:
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Check recent changes
id: recent
run: |
count=$(git rev-list --after="24 hours ago" --count HEAD || true)
if [ "$count" -gt 0 ]; then
echo "has_recent=true" >> "$GITHUB_OUTPUT"
else
echo "has_recent=false" >> "$GITHUB_OUTPUT"
fi
- name: No recent changes summary
if: ${{ steps.recent.outputs.has_recent != 'true' }}
run:
echo "No commits in the last 24 hours; skipping build." >>
"$GITHUB_STEP_SUMMARY"
- name: Set up Docker Buildx
if: ${{ steps.recent.outputs.has_recent == 'true' }}
uses: docker/setup-buildx-action@v2
- name: Docker meta
if: ${{ steps.recent.outputs.has_recent == 'true' }}
id: meta
uses: docker/metadata-action@v4
with:
images: ${{ vars.DOCKER_IMAGE }}
flavor: latest=false
tags: |
type=raw,value=${{ matrix.tag }}
- name: Login to image repository
if: ${{ steps.recent.outputs.has_recent == 'true' }}
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USER }}
password: ${{ secrets.DOCKER_TOKEN }}
- name: Build (attempt 1)
id: build1
if: ${{ steps.recent.outputs.has_recent == 'true' }}
continue-on-error: true
uses: docker/build-push-action@v4
with:
context: .
file: Dockerfile
platforms: ${{ matrix.platform }}
build-args: |
USE_BROWSER=${{ matrix.browser }}
WHATSAPP_DEFAULT_ENGINE=${{ matrix.engine }}
push: false
load: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
- name: Wait to retry build
if:
${{ steps.recent.outputs.has_recent == 'true' && steps.build1.outcome
== 'failure' }}
run: |
sleep 10
- name: Build (attempt 2)
id: build2
if:
${{ steps.recent.outputs.has_recent == 'true' && steps.build1.outcome
== 'failure' }}
continue-on-error: true
uses: docker/build-push-action@v4
with:
context: .
file: Dockerfile
platforms: ${{ matrix.platform }}
build-args: |
USE_BROWSER=${{ matrix.browser }}
WHATSAPP_DEFAULT_ENGINE=${{ matrix.engine }}
push: false
load: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
- name: Wait to retry build
if:
${{ steps.recent.outputs.has_recent == 'true' && steps.build2.outcome
== 'failure' }}
run: |
sleep 10
- name: Build (attempt 3)
if:
${{ steps.recent.outputs.has_recent == 'true' && steps.build2.outcome
== 'failure' }}
uses: docker/build-push-action@v4
with:
context: .
file: Dockerfile
platforms: ${{ matrix.platform }}
build-args: |
USE_BROWSER=${{ matrix.browser }}
WHATSAPP_DEFAULT_ENGINE=${{ matrix.engine }}
push: false
load: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
- name: Install goss
if: ${{ steps.recent.outputs.has_recent == 'true' }}
env:
GOSS_DOWNLOAD: 'https://github.com/goss-org/goss/releases/download'
GOSS_VERSION: 'v0.4.8'
run: |
curl -L https://github.com/goss-org/goss/releases/download/${{ env.GOSS_VERSION }}/${{ matrix.goss }} -o /usr/local/bin/goss
chmod +rx /usr/local/bin/goss
- name: Run smoke tests
if: ${{ steps.recent.outputs.has_recent == 'true' }}
working-directory: tests/smoke
timeout-minutes: 2
run: |
docker run -d \
--name smoke \
--rm -p3000:3000 \
-e WAHA_API_KEY=test \
${{ vars.DOCKER_IMAGE }}:${{ matrix.tag }}
sleep 3
docker logs smoke
goss validate --retry-timeout 30s --sleep 1s
docker rm --force smoke
- name: Push
if: ${{ steps.recent.outputs.has_recent == 'true' }}
uses: docker/build-push-action@v4
with:
context: .
file: Dockerfile
platforms: ${{ matrix.platform }}
build-args: |
USE_BROWSER=${{ matrix.browser }}
WHATSAPP_DEFAULT_ENGINE=${{ matrix.engine }}
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
+51
View File
@@ -0,0 +1,51 @@
name: 'github-translate'
on:
issues:
types: [opened]
issue_comment:
types: [created]
discussion:
types: [created]
discussion_comment:
types: [created]
# pull_request_target:
# types: [opened, edited]
# pull_request_review_comment:
# types: [created, edited]
jobs:
en:
if: github.repository == 'devlikeapro/waha'
permissions:
issues: write
discussions: write
pull-requests: write
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: devlikeapro/github-translate-action@main
env:
# GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
IS_MODIFY_TITLE: true
CUSTOM_BOT_NOTE: 'English'
TARGET_LANGUAGE: 'eng'
TRANSLATED_LANGUAGE: 'en'
# pt:
# permissions:
# issues: write
# discussions: write
# pull-requests: write
# runs-on: ubuntu-latest
# steps:
# - uses: actions/checkout@v4
# - uses: devlikeapro/github-translate-action@main
# env:
# # GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# with:
# IS_MODIFY_TITLE: false
# CUSTOM_BOT_NOTE: 'Portuguese'
# TARGET_LANGUAGE: 'por'
# TRANSLATED_LANGUAGE: 'pt'
-84
View File
@@ -1,84 +0,0 @@
name: Manual push
on:
workflow_dispatch:
inputs:
tag:
description: 'Tag to push'
required: true
default: 'dev'
push:
description: 'Push to registry'
required: true
type: boolean
default: true
jobs:
docker-build:
runs-on: ${{ matrix.runner }}
name: ${{ matrix.platform }} - ${{ matrix.browser }} - ${{ matrix.tag }}
strategy:
matrix:
include:
# Chromium
- runner: 'buildjet-4vcpu-ubuntu-2204'
tag: 'latest'
platform: 'amd64'
browser: 'chromium'
- runner: 'buildjet-4vcpu-ubuntu-2204-arm'
tag: 'arm'
platform: 'linux/arm64'
browser: 'chromium'
# Chrome
- runner: 'buildjet-4vcpu-ubuntu-2204'
tag: 'chrome'
platform: 'amd64'
browser: 'chrome'
# Chrome is not supported on arm64
# - runner: "buildjet-4vcpu-ubuntu-2204-arm"
# tag: "chrome-arm"
# platform: "linux/arm64"
# browser: "chrome"
# No browser
- runner: 'buildjet-4vcpu-ubuntu-2204'
tag: 'noweb'
platform: 'amd64'
browser: 'none'
- runner: 'buildjet-4vcpu-ubuntu-2204-arm'
tag: 'noweb-arm'
platform: 'linux/arm64'
browser: 'none'
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Docker meta
id: meta
uses: docker/metadata-action@v4
with:
images: ${{ vars.DOCKER_IMAGE }}
flavor: latest=false
tags: |
type=raw,value=${{ inputs.tag }}-${{ matrix.tag }}
- name: Login to image repository
if: github.ref_type == 'tag'
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USER }}
password: ${{ secrets.DOCKER_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v4
with:
context: .
file: Dockerfile
platforms: ${{ matrix.platform }}
build-args: |
USE_BROWSER=${{ matrix.browser }}
push: ${{ inputs.push }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
+7
View File
@@ -1,6 +1,12 @@
tmp/*
src/core/engines/gows/proto
.env
.envs/*
*.heapsnapshot
.yarn
example.ts
.*sessions
sessions
.secrets
.minio
.media
@@ -14,6 +20,7 @@ test.js
.wwebjs_cache
src/dashboard
.eslintcache
__pycache__
# compiled output
/dist
+1 -1
View File
@@ -1 +1 @@
v20.5.0
v24.11
+15
View File
@@ -0,0 +1,15 @@
{
"$schema": "./node_modules/oxlint/configuration_schema.json",
"plugins": ["typescript"],
"env": {
"builtin": true
},
"rules": {
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/explicit-module-boundary-types": "off",
"@typescript-eslint/no-inferrable-types": "off",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/ban-ts-comment": "off",
"no-unused-vars": "off"
}
}
+15 -12
View File
@@ -2,7 +2,10 @@ exclude: |
(?x)^(
^src/dashboard/.*|
^src/core/engines/webjs/.*html|
^src/plus/engines/webjs/.*html
^src/core/engines/gows/grpc/.*|
^src/core/engines/gows/proto/.*|
^src/plus/engines/webjs/.*html|
^.yarn/.*
)$
repos:
@@ -14,18 +17,12 @@ repos:
language: python
stages: [commit-msg]
- repo: local
hooks:
- id: lint
stages: [pre-commit]
name: lint
- id: oxlint
name: oxlint
entry: npx oxlint --fix --deny-warnings
language: system
entry: bash -c '$HOME/.nvm/nvm-exec npm run lint-cache'
pass_filenames: false
files: |
(?x)^(
src/.*|
)$
pass_filenames: true
files: \.(ts|tsx|js|jsx)$
- repo: https://github.com/pre-commit/mirrors-prettier
rev: 'v3.1.0'
@@ -36,6 +33,7 @@ repos:
(?x)^(
docs/.*|
README.md|
tests/smoke/goss.yaml|
)$
- repo: https://github.com/Lucas-C/pre-commit-hooks-nodejs
rev: v1.1.2
@@ -69,6 +67,11 @@ repos:
exclude: |
(?x)^(
entrypoint.sh|
AGENTS.md|
.pre-commit-config.yaml|
^src/core/engines/webjs/session.webjs.core.ts|
^src/core/engines/webjs/_lodash.js|
^tests/perf/.*|
^scripts/.*|
^scripts/init-waha.js|
)$
+4 -2
View File
@@ -1,3 +1,5 @@
nodeLinker: node-modules
compressionLevel: mixed
yarnPath: .yarn/releases/yarn-3.6.3.cjs
enableGlobalCache: false
nodeLinker: node-modules
+139
View File
@@ -0,0 +1,139 @@
# WAHA Agent Playbook
This guide summarizes how to explore, modify, and validate the WhatsApp HTTP API
(WAHA) codebase when assisting as an automation or coding agent.
- When you asked to refactor or "apply new lib" instead of current one - do not
change the behavior, make a minimum amount of changes possible. If you see
some edge case during that process that haven't been covered - tell it,
suggest fix, but don't change the code.
## Product & Variants
- WAHA ships in **Core** and **Plus** editions. Core lives under `src/core` and
only supports the default session plus minimal media features. Plus extends
core via `src/plus` to add multi-session orchestration, richer media handling,
and external storage integrations.
- Core code must remain free from Plus-only references. A pre-commit hook
rejects the word `plus` inside core files; reuse abstractions exposed through
`@waha/core/**` instead of importing Plus modules from Core.
- Commit subjects are validated: changes that touch `src/plus` require a
`[PLUS] …` prefix and must not include non-Plus files; everything else must
use `[core] …`. Verify against `./.precommit/validate_commit_message.py` if
unsure.
## Tech Stack Snapshot
- **Runtime**: Node.js 22.x, Yarn 3.6 (Berry). Always install packages and run
scripts with Yarn.
- **Framework**: NestJS v11 with dependency injection, modular controllers in
`src/api`, and Pino-based logging via `nestjs-pino`.
- **Engines**: WhatsApp engines are abstracted (`WEBJS`, `GOWS`, `NOWEB`). Core
uses `SessionManagerCore`; Plus swaps to `SessionManagerPlus` with extra
storage backends (Mongo/Postgres/SQLite).
- **ESM Bridge**: ESM-only dependencies (Baileys) load through
`src/vendor/esm.ts`. Add new ESM modules there to ensure they load exactly
once.
- **Utilities**: RxJS streams (`SwitchObservable`, `DefaultMap`) drive webhook
event fan-out. Prefer existing helpers in `src/utils` and `src/core/utils`
before adding bespoke logic.
## Repository Landmarks
- `src/main.ts`: runtime entry point; dynamically loads the correct AppModule
(Core vs Plus) and configures global interceptors/filters.
- `src/api/**`: REST controllers and WebSocket gateway. Keep handlers thin;
delegate to managers/services. HTTP routes follow `/api/{sessionName}/…`;
always thread the session name through request DTOs and guards instead of
hardcoding `default`.
- `src/core/**`: shared abstractions (config services, engine bootstrap,
storage, session management). Core only allows the `default` session and
cleans up storage on boot.
- `src/plus/**`: multi-session orchestration, advanced media services, and
external persistence layers (Mongo, Postgres). Reuse this layer when adding
Plus-only capabilities.
- `src/apps/**`: integrations (e.g., ChatWoot) and application-specific
services.
- `src/structures/**` and `src/utils/**`: DTOs, enums (event names follow
`domain.action`), helper utilities. Maintain naming consistency when
introducing new events.
- `tests/**`: Jest-based suites; do not add new tests unless explicitly asked,
but keep existing tests working.
## Coding Expectations
- Favor composable, long-lived solutions. If a helper already exists (e.g.,
`parseBool`, `DefaultMap`, media factories), extend it instead of reinventing
logic. Lodash ships with the project—prefer its utilities over hand-rolled
helpers. Import lodash as a namespace (`import * as lodash from 'lodash';`) so
helpers like `lodash.camelCase` stay consistent across files. When a new
third-party library seems necessary, confirm with the user, especially if the
package looks stale.
- Stick to NestJS patterns: inject dependencies through constructors, expose
provider tokens from modules, and keep controllers free from business logic.
- Logging goes through injected `PinoLogger` or helpers in
`src/utils/logging.ts`. `console.log` is blocked by pre-commit.
- Respect path aliases (`@waha/...`) defined in `tsconfig.json`; keep imports
consistent (use absolute aliases, not relative `../../../`).
- Prefer named function declarations over `const` arrow functions when possible.
- Avoid naming unused variables with a leading underscore; if a parameter is
required by a signature, explicitly `void` it instead.
- For configs, prefer runtime configurability over constants. Environment keys
follow `WAHA_*` for global values and `WAHA_SESSION_CONFIG_*` /
`session.config.*` for per-session overrides. If both env and config are
supported, honor both (`WAHA_WEBJS_CONFIG_*` vs. `session.webjs.config.*`).
## Language & Localization
- Keep identifiers (classes, methods, variables) and code comments in English so
the codebase stays consistent for the global team.
- Route user-visible strings through the existing i18n structure rather than
hardcoding text. ChatWoot copy belongs in `src/apps/chatwoot/i18n` with
English as the source locale before adding translations.
## ChatWoot Integration Notes
- Avoid introducing synthetic events; map onto existing webhook or engine events
whenever plausible. Always use the official ChatWoot API client located in
`src/apps/chatwoot/client`.
- When adding events, align consumer names with webhook/event identifiers (e.g.,
`message.any`).
## Workflow Checklist
1. Identify whether work targets Core, Plus, or shared layers. If Plus-only,
isolate changes to `src/plus` and ensure the commit prefix matches.
2. Lean on existing services/managers; extend the appropriate session manager
rather than branching logic inline.
3. After edits run:
- `pre-commit run --all-files`
- `yarn build`
- `yarn test` Use Node 22. Address lint or formatting issues before
proceeding.
4. Do **not** start the application yourself; ask the user to run it if runtime
validation is required.
5. Capture any assumptions or open questions for the user, especially when
touching configs, introducing dependencies, or modifying public APIs.
## Additional Tips
- Concurrency-sensitive sections (session start/stop) rely on `async-lock`. When
adding async flows, reuse `SessionManager.withLock` or existing retry
utilities (`promiseTimeout`, `waitUntil`).
- Media features centralize through `MediaManager` and `MediaStorageFactory`.
When altering media behavior, update both Core and Plus variants where
applicable.
- Keep docs and code ASCII unless a file already uses other characters. When
updating documentation, mirror the concise, actionable tone used here.
## Related Sources Code
You can find related source code in the following paths:
- WEBJS: `../whatsapp-web.js`
- NOWEB: `../WhiskeySockets-Baileys`
- GOWS: `../gows` + `../whatsmeow`
- ChatWoot: `../chatwoot`
Following this playbook keeps contributions aligned with WAHA’s structure,
automation hooks, and release process.
+156 -17
View File
@@ -1,64 +1,155 @@
ARG NODE_IMAGE_TAG=24.11-bookworm-slim
ARG GOLANG_IMAGE_TAG=1.24-bookworm
#
# Build
#
ARG NODE_VERSION=20.12.2-bullseye
FROM node:${NODE_VERSION} as build
FROM node:${NODE_IMAGE_TAG} AS build
ENV PUPPETEER_SKIP_DOWNLOAD=True
# git + build toolchain for git deps
RUN apt-get update && apt-get install -y git python3 build-essential && rm -rf /var/lib/apt/lists/*
# npm packages
WORKDIR /src
WORKDIR /git
COPY package.json .
COPY yarn.lock .
RUN yarn set version 3.6.3
ENV YARN_CHECKSUM_BEHAVIOR=update
RUN npm install -g corepack && corepack enable
RUN yarn set version 4.9.2
RUN yarn install
# App
WORKDIR /src
ADD . /src
WORKDIR /git
ADD . /git
RUN yarn install
RUN yarn build && find ./dist -name "*.d.ts" -delete
#
# Dashboard
#
FROM node:${NODE_VERSION} as dashboard
FROM node:${NODE_IMAGE_TAG} AS dashboard
# Download WAHA Dashboard
ENV WAHA_DASHBOARD_SHA c1a91f879a35f0ae32d46ddb2e061ac1819a3d4f
# jq to parse json
RUN apt-get update && apt-get install -y jq && rm -rf /var/lib/apt/lists/*
# wget, unzip
RUN apt-get update && apt-get install -y wget unzip && rm -rf /var/lib/apt/lists/*
COPY waha.config.json /tmp/waha.config.json
RUN \
wget https://github.com/devlikeapro/dashboard/archive/${WAHA_DASHBOARD_SHA}.zip \
WAHA_DASHBOARD_GITHUB_REPO=$(jq -r '.waha.dashboard.repo' /tmp/waha.config.json) && \
WAHA_DASHBOARD_SHA=$(jq -r '.waha.dashboard.ref' /tmp/waha.config.json) && \
wget https://github.com/${WAHA_DASHBOARD_GITHUB_REPO}/archive/${WAHA_DASHBOARD_SHA}.zip \
&& unzip ${WAHA_DASHBOARD_SHA}.zip -d /tmp/dashboard \
&& mkdir -p /dashboard \
&& mv /tmp/dashboard/dashboard-${WAHA_DASHBOARD_SHA}/* /dashboard/ \
&& rm -rf ${WAHA_DASHBOARD_SHA}.zip \
&& rm -rf /tmp/dashboard/dashboard-${WAHA_DASHBOARD_SHA}
#
# GOWS
#
FROM golang:${GOLANG_IMAGE_TAG} AS gows
# jq to parse json
RUN apt-get update && apt-get install -y jq && rm -rf /var/lib/apt/lists/*
# install protoc
RUN apt-get update && \
apt-get install protobuf-compiler -y
# Image processing for thumbnails
RUN apt-get update \
&& apt-get install -y libvips-dev \
&& rm -rf /var/lib/apt/lists/*
COPY waha.config.json /tmp/waha.config.json
WORKDIR /go/gows
RUN \
GOWS_GITHUB_REPO=$(jq -r '.waha.gows.repo' /tmp/waha.config.json) && \
GOWS_SHA=$(jq -r '.waha.gows.ref' /tmp/waha.config.json) && \
ARCH=$(uname -m) && \
if [ "$ARCH" = "x86_64" ]; then ARCH="amd64"; \
elif [ "$ARCH" = "aarch64" ]; then ARCH="arm64"; \
else echo "Unsupported architecture: $ARCH" && exit 1; fi && \
mkdir -p /go/gows/bin && \
wget -O /go/gows/bin/gows https://github.com/${GOWS_GITHUB_REPO}/releases/download/${GOWS_SHA}/gows-${ARCH} && \
chmod +x /go/gows/bin/gows
#
# Final
#
FROM node:${NODE_VERSION} as release
FROM node:${NODE_IMAGE_TAG} AS release
ENV PUPPETEER_SKIP_DOWNLOAD=True
# Quick fix for memory potential memory leaks
# https://github.com/devlikeapro/waha/issues/347
ENV NODE_OPTIONS="--max-old-space-size=16384"
ARG USE_BROWSER=chromium
ARG WHATSAPP_DEFAULT_ENGINE
RUN echo "USE_BROWSER=$USE_BROWSER"
# Install ffmpeg to generate previews for videos
RUN apt-get update && apt-get install -y ffmpeg --no-install-recommends && rm -rf /var/lib/apt/lists/*
# Install zip and unzip
RUN apt-get update && apt-get install -y zip unzip --no-install-recommends && rm -rf /var/lib/apt/lists/*
# Image processing for thumbnails
RUN apt-get update \
&& apt-get install -y libvips \
&& rm -rf /var/lib/apt/lists/*
# Install zip and unzip - either for chromium or chrome
RUN if [ "$USE_BROWSER" = "chromium" ] || [ "$USE_BROWSER" = "chrome" ]; then \
apt-get update \
&& apt-get install -y zip unzip \
&& rm -rf /var/lib/apt/lists/*; \
fi
# Install wget - either for chromium or chrome
RUN if [ "$USE_BROWSER" = "chromium" ] || [ "$USE_BROWSER" = "chrome" ]; then \
apt-get update \
&& apt-get install -y wget \
&& rm -rf /var/lib/apt/lists/*; \
fi
# Install fonts if using either chromium or chrome
RUN if [ "$USE_BROWSER" = "chromium" ] || [ "$USE_BROWSER" = "chrome" ]; then \
apt-get update \
&& apt-get install -y fonts-ipafont-gothic fonts-wqy-zenhei fonts-thai-tlwg fonts-kacst fonts-freefont-ttf \
&& apt-get install -y \
fontconfig \
fonts-freefont-ttf \
fonts-gfs-neohellenic \
fonts-indic \
fonts-ipafont-gothic \
fonts-kacst \
fonts-liberation \
fonts-noto-cjk \
fonts-noto-color-emoji \
fonts-roboto \
fonts-thai-tlwg \
fonts-wqy-zenhei \
fonts-open-sans \
--no-install-recommends \
&& rm -rf /var/lib/apt/lists/*; \
fi
# Install xvfb, xauth
RUN if [ "$USE_BROWSER" = "chromium" ] || [ "$USE_BROWSER" = "chrome" ]; then \
apt-get update && apt-get install -y --no-install-recommends \
xvfb \
xauth \
libnss3 \
libxss1 \
libasound2 \
libatk-bridge2.0-0 \
libgtk-3-0 \
libdrm2 \
ca-certificates \
&& rm -rf /var/lib/apt/lists/*; \
fi
# Install Chromium
RUN if [ "$USE_BROWSER" = "chromium" ]; then \
apt-get update \
@@ -71,7 +162,8 @@ RUN if [ "$USE_BROWSER" = "chromium" ]; then \
# Install Chrome
# Available versions:
# https://www.ubuntuupdates.org/package/google_chrome/stable/main/base/google-chrome-stable
ARG CHROME_VERSION="126.0.6478.182-1"
ARG CHROME_VERSION="140.0.7339.80-1"
ARG OPUSTAGS_VERSION="1.10.1"
RUN if [ "$USE_BROWSER" = "chrome" ]; then \
wget --no-verbose -O /tmp/chrome.deb https://dl.google.com/linux/chrome/deb/pool/main/g/google-chrome-stable/google-chrome-stable_${CHROME_VERSION}_amd64.deb \
&& apt-get update \
@@ -80,12 +172,54 @@ RUN if [ "$USE_BROWSER" = "chrome" ]; then \
&& rm -rf /var/lib/apt/lists/*; \
fi
# curl
RUN apt-get update \
&& apt-get install -y curl \
&& rm -rf /var/lib/apt/lists/*
# Build and install opustags so audio metadata can be cleaned up inside the container
RUN set -eux; \
buildDeps='build-essential cmake pkg-config libogg-dev'; \
apt-get update; \
apt-get install -y --no-install-recommends ${buildDeps}; \
mkdir -p /tmp/opustags; \
curl -L https://github.com/fmang/opustags/archive/refs/tags/${OPUSTAGS_VERSION}.tar.gz \
| tar -xz -C /tmp/opustags; \
cd /tmp/opustags/opustags-${OPUSTAGS_VERSION}; \
cmake -S . -B build -DCMAKE_INSTALL_PREFIX=/usr/local -DCMAKE_BUILD_TYPE=Release; \
cmake --build build --config Release; \
cmake --install build; \
rm -rf /tmp/opustags; \
apt-get purge -y --auto-remove ${buildDeps}; \
rm -rf /var/lib/apt/lists/*
# GOWS requirements
# libc6
RUN apt-get update \
&& apt-get install -y libc6 \
&& rm -rf /var/lib/apt/lists/*
# Install tini for proper init process
RUN apt-get update && apt-get install -y tini && rm -rf /var/lib/apt/lists/*
# Set the ENV for docker image
ENV WHATSAPP_DEFAULT_ENGINE=$WHATSAPP_DEFAULT_ENGINE
# Attach sources, install packages
WORKDIR /app
COPY package.json ./
COPY --from=build /src/node_modules ./node_modules
COPY --from=build /src/dist ./dist
COPY --from=build /git/node_modules ./node_modules
COPY --from=build /git/dist ./dist
COPY --from=dashboard /dashboard ./dist/dashboard
COPY --from=gows /go/gows/bin/gows /app/gows
COPY .env.example ./.env.example
COPY scripts/init-waha.js ./scripts/init-waha.js
RUN chmod +x ./scripts/init-waha.js \
&& printf '%s\n' '#!/bin/sh' 'exec node /app/scripts/init-waha.js "$@"' > /usr/local/bin/init-waha \
&& chmod +x /usr/local/bin/init-waha
ENV WAHA_GOWS_PATH=/app/gows
ENV WAHA_GOWS_SOCKET=/tmp/gows.sock
COPY entrypoint.sh /entrypoint.sh
# Chokidar options to monitor file changes
@@ -95,6 +229,11 @@ ENV CHOKIDAR_INTERVAL=5000
# WAHA variables
ENV WAHA_ZIPPER=ZIPUNZIP
# GOWS - use libc DNS resolver
ENV GODEBUG netdns=cgo
# Run command, etc
EXPOSE 3000
# Use tini as init system to handle zombie processes properly
ENTRYPOINT ["/usr/bin/tini", "--"]
CMD ["/entrypoint.sh"]
+27 -8
View File
@@ -1,16 +1,19 @@
build-all: build-plus build-chrome build-gows build-noweb
build:
docker build . -t devlikeapro/waha
build-plus:
docker build . -t devlikeapro/waha-plus
build-chrome:
docker build . -t devlikeapro/waha-plus:chrome --build-arg USE_BROWSER=chrome
build-noweb:
docker build . -t devlikeapro/waha:noweb --build-arg USE_BROWSER=none
docker build . -t devlikeapro/waha-plus:noweb --build-arg USE_BROWSER=none --build-arg WHATSAPP_DEFAULT_ENGINE=NOWEB
build-all: build build-chrome build-noweb
build-plus:
docker build . -t devlikeapro/waha-plus
build-gows:
docker build . -t devlikeapro/waha-plus:gows --build-arg USE_BROWSER=none --build-arg WHATSAPP_DEFAULT_ENGINE=GOWS
build-ssh:
# check IMAGE provided
@@ -29,13 +32,29 @@ push:
docker push devlikeapro/waha
for-swagger:
WHATSAPP_SWAGGER_CONFIG_ADVANCED=true . ${NVM_DIR}/nvm.sh && nvm exec yarn start
export WHATSAPP_SWAGGER_CONFIG_ADVANCED=true && export WHATSAPP_SWAGGER_PASSWORD=666 && yarn start
up-noweb:
. ${NVM_DIR}/nvm.sh && nvm exec yarn up @adiwajshing/baileys@github:devlikeapro/Baileys#fork-master-channels-2
yarn up @adiwajshing/baileys@github:devlikeapro/Baileys#fork-master-2026-01-25
up-noweb-libsignal:
yarn up libsignal@github:devlikeapro/libsignal-node#fork-master
up-webjs:
. ${NVM_DIR}/nvm.sh && nvm exec yarn up whatsapp-web.js@github:devlikeapro/whatsapp-web.js#fork-webpack-exodus-channels
yarn up whatsapp-web.js@github:devlikeapro/whatsapp-web.js#fork-main-2026-02-01
start-proxy:
docker run --rm -d --name squid-container -e TZ=UTC -p 3128:3128 ubuntu/squid:5.2-22.04_beta
proto-gows:
node scripts/gows-proto.js build --dir ../gows/proto
gows:
cd ../gows && \
export PATH=${HOME}/go/bin:${PATH} && \
make all
copy-dashboard:
cd ../waha-hub/ui && \
make copy-waha
+21 -59
View File
@@ -6,22 +6,16 @@
**WAHA** - **W**hats**A**pp **H**TTP **A**PI (REST API) that you can install on your own server and run in less than 5 minutes!
[![Docker Pulls](https://img.shields.io/docker/pulls/devlikeapro/whatsapp-http-api)](https://hub.docker.com/r/devlikeapro/waha)
[![Docker Pulls](https://img.shields.io/docker/pulls/devlikeapro/waha)](https://hub.docker.com/r/devlikeapro/waha)
- Documentation: [https://waha.devlike.pro/](https://waha.devlike.pro/)
- Dashboard Example: [https://waha.devlike.pro/dashboard](https://waha.devlike.pro/dashboard)
- Swagger Example: [https://waha.devlike.pro/swagger](https://waha.devlike.pro/swagger)
# Tables of Contents
<!-- toc -->
- [Features](#features)
* [Messages](#messages)
* [Sessions](#sessions)
* [Security](#security)
* [Updates](#updates)
* [Groups](#groups)
* [Contacts](#contacts)
- [Quick start](#quick-start)
* [Requirements](#requirements)
* [Send your first message](#send-your-first-message)
@@ -32,52 +26,11 @@
+ [5. Get the screenshot](#5-get-the-screenshot)
+ [6. Send a text message](#6-send-a-text-message)
* [What is next?](#what-is-next)
- [Development](#development)
* [Start the project](#start-the-project)
<!-- tocstop -->
# Features
## Messages
- Send messages
- Receive messages
- Message replies
- Send location
- Receive location
- React to messages
- Send media (images/documents/files)
- Send voice messages
- Receive media (images/audio/video/documents)
- Receive messages - webhook retries
## Sessions
- Multi Device
- Get the screenshot
- Session saving (don't have to scan QR on every restart)
- Single WhatsApp account running inside one container
- Multiple WhatsApp account running inside one container
## Security
- Swagger panel authentication
- API authentication
## Updates
- Bug fixes and updates come as soon as they are implemented.
## Groups
- Create a group
- Get invite for group
- Modify group info (subject, description)
- Add group participants
- Kick group participants
- Promote/demote group participants
## Contacts
- Mute/unmute chats
- Block/unblock contacts
- Get contact info
- Get profile pictures
- Get presence (online\offline\typing) status
# Quick start
## Requirements
@@ -87,13 +40,6 @@ instruction <a href="https://docs.docker.com/get-docker/" target="_blank" rel="n
When you are ready - come back and follows the below steps to send the first text message to WhatsApp via HTTP API!
{{< details "Why Docker?" >}}
Docker makes it easy to ship all-in-one solution with the runtime and dependencies. You don't have to worry about
language-specific libraries or chrome installation.
Also Docker makes installation and update processes so simple, just one command!
{{< /details >}}
## Send your first message
Let's go over steps that allow you to send your first text message via WhatsApp HTTP API!
@@ -114,7 +60,7 @@ docker pull devlikeapro/waha-plus
docker logout
```
Read more about how to get `PASSWORD` for [Plus Version →]({{< relref "plus-version" >}})
Read more about how to get `PASSWORD` for [**➕ WAHA Plus**](https://waha.devlike.pro/docs/how-to/waha-plus/)
### 2. Run WhatsApp HTTP API
@@ -195,3 +141,19 @@ curl -d "{\"chatId\": \"${PHONE}@c.us\", \"text\": \"Hello from WhatsApp HTTP AP
## What is next?
[Go and read the full documentation!](https://waha.devlike.pro/docs/overview/introduction/)
# Development
## Start the project
1. Clone the repository
2. Make sure you're using node>=22 (check [.nvmrc](/.nvmrc) to get the version)
3. Run the following commands:
```bash
# Install dependencies
yarn install
# Fetch and compile proto files
yarn gows:proto
# Run
yarn start
# open http://localhost:3000
```
+84 -223
View File
@@ -1,243 +1,104 @@
#
# https://waha.devlike.pro/docs/how-to/install/
# Here's we provide a simple example of how to install WAHA using Docker Compose.
#
# We're assuming you have Docker and Docker Compose installed.
# If you don't have Docker Compose installed, you can follow the official guide:
# https://docs.docker.com/compose/install/
#
services:
waha:
# Always restart the container on failure
restart: always
#
# Image
#
image: devlikeapro/waha-plus:latest # or devlikeapro/waha:latest if you're using WAHA Core
#
# We strongly recommend using the pinned version of WAHA
# https://waha.devlike.pro/docs/how-to/engines/#versions
# image: devlikeapro/waha-plus:latest-2024.7.6 # 'latest' is not mistaken here
#
# Multiple images are available
# https://waha.devlike.pro/docs/how-to/engines/#docker-images
# image: devlikeapro/waha-plus:chrome
#
# https://portal.devlike.pro/docker-image
image: devlikeapro/waha-plus
# WAHA Core
# image: devlikeapro/waha:latest
# Add "dns" if you have a problem with resolving "web.whatsapp.com"
dns:
- 1.1.1.1
- 8.8.8.8
logging:
driver: 'json-file'
options:
max-size: '100m'
max-file: '10'
# The first port can be any free port you want
# The second port must be the same as WHATSAPP_API_PORT or 3000 by default
# Consider using the same port for simplicity
ports:
- '3000:3000/tcp'
# NOTE: if you're using HTTP, you can use the following port mapping
#- '80:3000/tcp'
# NOTE: if you're using HTTPS, you can use the following port mapping
#- '443:3000/tcp'
- '127.0.0.1:3000:3000/tcp'
volumes:
# Store sessions in the .sessions folder (comment it if you're using MongoDB)
# Doesn't work with WAHA Core
- './.sessions:/app/.sessions'
- './sessions:/app/.sessions'
# Save media files
# https://waha.devlike.pro/docs/how-to/storages/#save-media-files-between-the-container-restarts
- './.media:/app/.media'
- './media:/app/.media'
# If you're using certbot for HTTPS, you can use the following volume mapping
# https://waha.devlike.pro/blog/setting-up-https-for-waha/#lets-encrypt-certbot--waha
# - '/etc/letsencrypt:/etc/letsencrypt'
env_file:
- .env
# WAHA Configuration
# https://waha.devlike.pro/docs/how-to/config/
environment:
#
# Common
#
# If you know the right URL, you can use it here.
# Use any port in WHATSAPP_API_PORT and domain in WHATSAPP_API_HOSTNAME
# https://waha.devlike.pro/docs/how-to/config/#common
#- WHATSAPP_API_SCHEMA=http
#- WHATSAPP_API_PORT=3000
#- WHATSAPP_API_HOSTNAME=localhost
# NOTE: Only if you're using PostgreSQL to save sessions
# https://waha.devlike.pro/docs/how-to/storages/#media---postgresql
# postgres:
# image: postgres:17.6
# restart: always
# environment:
# POSTGRES_USER: postgres
# POSTGRES_PASSWORD: postgres
# POSTGRES_DB: postgres
# ports:
# - "127.0.0.1:5432:5432"
# volumes:
# - pg_data:/var/lib/postgresql/data
# command:
# - postgres
# - "-c"
# - "max_connections=3000"
# logging:
# driver: "json-file"
# options:
# max-size: "100m"
# max-file: "10"
#
# Security
#
# https://waha.devlike.pro/docs/how-to/security/
#
- WHATSAPP_API_KEY=321
- WAHA_DASHBOARD_USERNAME=waha
- WAHA_DASHBOARD_PASSWORD=waha
#- WHATSAPP_SWAGGER_USERNAME=admin
#- WHATSAPP_SWAGGER_PASSWORD=admin
# NOTE: Only if you're using MongoDB
# https://waha.devlike.pro/docs/how-to/storages/#sessions---mongodb
# Uncomment this block if you're using MongoDB
# mongodb:
# image: mongo
# container_name: mongodb
# ports:
# - '127.0.0.1:27017:27017/tcp'
# volumes:
# - mongodb_data:/data/db
# environment:
# - MONGO_INITDB_ROOT_USERNAME=mongouser
# - MONGO_INITDB_ROOT_PASSWORD=mongopassword
# logging:
# driver: "json-file"
# options:
# max-size: "100m"
# max-file: "10"
#
# Dashboard
#
# https://waha.devlike.pro/docs/how-to/waha-dashboard/#configuration
# Enabled by default
#- WAHA_DASHBOARD_ENABLED=true
#- WAHA_DASHBOARD_USERNAME=waha
#- WAHA_DASHBOARD_PASSWORD=waha
#
# Logging
# https://waha.devlike.pro/docs/how-to/observability/#logging
#
- WAHA_LOG_FORMAT=PRETTY
# Set log format to JSON if you consume logs with log management systems
#- WAHA_LOG_FORMAT=JSON
- WAHA_LOG_LEVEL=info
# If you want to see more logs, you can set the log level to debug
#- WAHA_LOG_LEVEL=debug
# DEBUG=1 # shortcut for setting log level to debug
#
# Engine
#
# Choose the right engine for your needs (WEBJS by default)
# https://waha.devlike.pro/docs/how-to/engines/
- WHATSAPP_DEFAULT_ENGINE=WEBJS
# Use NOWEB engine for the fastest performance
#- WHATSAPP_DEFAULT_ENGINE=NOWEB
#
# Sessions
#
# https://waha.devlike.pro/docs/how-to/config/#sessions
# Restart all sessions when container starts
- WHATSAPP_RESTART_ALL_SESSIONS=True
# Do not print QR codes in logs
- WAHA_PRINT_QR=False
#
# OR you can specify sessions by name, if you have handful amount of sessions
#- WHATSAPP_START_SESSION=session1,session2
# Do not use both WHATSAPP_RESTART_ALL_SESSIONS and WHATSAPP_START_SESSION parameters
#
#
# Swagger
#
# https://waha.devlike.pro/docs/how-to/swagger/
#- WHATSAPP_SWAGGER_USERNAME=admin
#- WHATSAPP_SWAGGER_PASSWORD=admin
# Disable Swagger
#- WHATSAPP_SWAGGER_ENABLED=false
# Enable advanced Swagger configuration
#- WHATSAPP_SWAGGER_CONFIG_ADVANCED=true
#
# Swagger White Label customization
# https://waha.devlike.pro/docs/how-to/swagger/#white-label
#- WHATSAPP_SWAGGER_TITLE=WAHA API
#
#
# Global Proxy Settings
# https://waha.devlike.pro/docs/how-to/config/#global-proxy-configuration
# You can also set proxy settings for each session later when starting it
# https://waha.devlike.pro/docs/how-to/sessions/#configure-proxy
#- WHATSAPP_PROXY_SERVER=proxy.example.com:3128
#-WHATSAPP_PROXY_SERVER_USERNAME=user
#-WHATSAPP_PROXY_SERVER_PASSWORD=pass
#
#
# HTTPS Configuration
#
# https://waha.devlike.pro/docs/how-to/config/#https
# Consider using certbot for HTTPS
# https://waha.devlike.pro/blog/setting-up-https-for-waha/#lets-encrypt-certbot--waha
#- WAHA_HTTPS_ENABLED=true
#- WAHA_HTTPS_PATH_KEY=/etc/letsencrypt/live/waha.example.pro/privkey.pem
#- WAHA_HTTPS_PATH_CERT=/etc/letsencrypt/live/waha.example.pro/cert.pem \
#- WAHA_HTTPS_PATH_CA=/etc/letsencrypt/live/waha.example.pro/chain.pem
#- WHATSAPP_API_SCHEMA=https
#- WHATSAPP_API_PORT=3000
#- WHATSAPP_API_HOSTNAME=waha.example.pro
#
#
# Media Configuration
#
# https://waha.devlike.pro/docs/how-to/config/#files
#
# Disable media (images, videos, files) download for incoming messages
#- WHATSAPP_DOWNLOAD_MEDIA=false
#
# Download only specific media types
#- WHATSAPP_FILES_MIMETYPES=image/jpeg,image/png
#
# Media - Local Storage
# https://waha.devlike.pro/docs/how-to/storages/#media---local
#
# Set to 0 to keep media files forever
# https://waha.devlike.pro/docs/how-to/storages/#save-media-files-between-the-container-restarts
- WAHA_MEDIA_STORAGE=LOCAL
- WHATSAPP_FILES_LIFETIME=0
- WHATSAPP_FILES_FOLDER=/app/.media
# Keep media files for 180 seconds (3 minutes)
#- WHATSAPP_FILES_LIFETIME=180
#
# Media - S3 Storage
# https://waha.devlike.pro/docs/how-to/storages/#media---s3
#- WAHA_MEDIA_STORAGE=S3
#- WAHA_S3_REGION=eu-west-2
#- WAHA_S3_BUCKET=waha
#- WAHA_S3_ACCESS_KEY_ID=minioadmin
#- WAHA_S3_SECRET_ACCESS_KEY=minioadmin
#- WAHA_S3_ENDPOINT=http://minio:9000 # Not required if you're using AWS S3
#- WAHA_S3_FORCE_PATH_STYLE=True # Required for Minio
#- WAHA_S3_PROXY_FILES=True # Required for docker-compose setup
#
# Global Webhooks
#
# https://waha.devlike.pro/docs/how-to/config/#webhooks
# https://waha.devlike.pro/docs/how-to/webhooks/
#- WHATSAPP_HOOK_URL=https://webhook.site/11111111-1111-1111-1111-11111111
#- WHATSAPP_HOOK_EVENTS=session.status,message,message.reaction
#
#
# MongoDB Configuration
# https://waha.devlike.pro/docs/how-to/storages/#sessions---mongodb
#- WHATSAPP_SESSIONS_MONGO_URL=mongodb://mongodb:27017
#
# NOTE: Only if you're using MongoDB
# https://waha.devlike.pro/docs/how-to/storages/#sessions---mongodb
# Comment this block if you're using MongoDB
mongodb:
image: mongo
container_name: mongodb
ports:
- '27017:27017/tcp'
volumes:
- mongodb_data:/data/db
restart: always
# NOTE: Only if you're using S3 to save media files
# https://waha.devlike.pro/docs/how-to/storages/#media---s3
# Comment this block if you're using AWS S3
minio:
image: quay.io/minio/minio
container_name: minio
restart: always
ports:
- '9000:9000'
- '9001:9001'
environment:
MINIO_REGION: 'eu-west-2'
MINIO_ROOT_USER: 'minioadmin'
MINIO_ROOT_PASSWORD: 'minioadmin'
volumes:
- minio_data:/data
command: server /data --console-address ":9001"
# NOTE: Only if you're using S3 to save media files
# https://waha.devlike.pro/docs/how-to/storages/#media---s3
# Uncomment this block if you're using AWS S3
# minio:
# image: quay.io/minio/minio
# container_name: minio
# restart: always
# ports:
# - '127.0.0.1:9000:9000'
# - '127.0.0.1:9001:9001'
# environment:
# MINIO_REGION: 'eu-west-2'
# MINIO_ROOT_USER: 'minioadmin'
# MINIO_ROOT_PASSWORD: 'minioadmin'
# volumes:
# - minio_data:/data
# command: server /data --console-address ":9001"
# logging:
# driver: "json-file"
# options:
# max-size: "100m"
# max-file: "10"
volumes:
# NOTE: Only if you're using MongoDB
mongodb_data: {}
minio_data: {}
pg_data: {}
+265
View File
@@ -0,0 +1,265 @@
# Learn about the various environment variables at
# https://www.chatwoot.com/docs/self-hosted/configuration/environment-variables/#rails-production-variables
# Increase rack default timeout (30s) to avoid timeout errors
# Workaround for https://github.com/devlikeapro/waha/issues/1389
RACK_TIMEOUT_SERVICE_TIMEOUT=60
# Used to verify the integrity of signed cookies. so ensure a secure value is set
# SECRET_KEY_BASE should be alphanumeric. Avoid special characters or symbols.
# Use `rake secret` to generate this variable
SECRET_KEY_BASE=replace_with_lengthy_secure_hex
# Replace with the URL you are planning to use for your app
FRONTEND_URL=http://chatwoot:3009
# To use a dedicated URL for help center pages
# HELPCENTER_URL=http://0.0.0.0:3000
# If the variable is set, all non-authenticated pages would fallback to the default locale.
# Whenever a new account is created, the default language will be DEFAULT_LOCALE instead of en
# DEFAULT_LOCALE=en
# If you plan to use CDN for your assets, set Asset CDN Host
ASSET_CDN_HOST=
# Force all access to the app over SSL, default is set to false
FORCE_SSL=false
# This lets you control new sign ups on your chatwoot installation
# true : default option, allows sign ups
# false : disables all the end points related to sign ups
# api_only: disables the UI for signup, but you can create sign ups via the account apis
ENABLE_ACCOUNT_SIGNUP=false
# Redis config
# specify the configs via single URL or individual variables
# ref: https://www.iana.org/assignments/uri-schemes/prov/redis
# You can also use the following format for the URL: redis://:password@host:port/db_number
REDIS_URL=redis://redis:6379
# If you are using docker-compose, set this variable's value to be any string,
# which will be the password for the redis service running inside the docker-compose
# to make it secure
REDIS_PASSWORD=redis
# Redis Sentinel can be used by passing list of sentinel host and ports e,g. sentinel_host1:port1,sentinel_host2:port2
REDIS_SENTINELS=
# Redis sentinel master name is required when using sentinel, default value is "mymaster".
# You can find list of master using "SENTINEL masters" command
REDIS_SENTINEL_MASTER_NAME=
# By default Chatwoot will pass REDIS_PASSWORD as the password value for sentinels
# Use the following environment variable to customize passwords for sentinels.
# Use empty string if sentinels are configured with out passwords
# REDIS_SENTINEL_PASSWORD=
# Redis premium breakage in heroku fix
# enable the following configuration
# ref: https://github.com/chatwoot/chatwoot/issues/2420
# REDIS_OPENSSL_VERIFY_MODE=none
# Postgres Database config variables
# You can leave POSTGRES_DATABASE blank. The default name of
# the database in the production environment is chatwoot_production
POSTGRES_DATABASE=chatwoot
POSTGRES_HOST=postgres
POSTGRES_USERNAME=postgres
POSTGRES_PASSWORD=postgres
RAILS_ENV=development
# Changes the Postgres query timeout limit. The default is 14 seconds. Modify only when required.
# POSTGRES_STATEMENT_TIMEOUT=14s
RAILS_MAX_THREADS=5
# The email from which all outgoing emails are sent
# could user either `email@yourdomain.com` or `BrandName <email@yourdomain.com>`
MAILER_SENDER_EMAIL=Chatwoot <accounts@chatwoot.com>
#SMTP domain key is set up for HELO checking
SMTP_DOMAIN=chatwoot.com
# Set the value to "mailhog" if using docker-compose for development environments,
# Set the value as "localhost" or your SMTP address in other environments
# If SMTP_ADDRESS is empty, Chatwoot would try to use sendmail(postfix)
SMTP_ADDRESS=
SMTP_PORT=1025
SMTP_USERNAME=
SMTP_PASSWORD=
# plain,login,cram_md5
SMTP_AUTHENTICATION=
SMTP_ENABLE_STARTTLS_AUTO=true
# Can be: 'none', 'peer', 'client_once', 'fail_if_no_peer_cert', see http://api.rubyonrails.org/classes/ActionMailer/Base.html
SMTP_OPENSSL_VERIFY_MODE=peer
# Comment out the following environment variables if required by your SMTP server
# SMTP_TLS=
# SMTP_SSL=
# SMTP_OPEN_TIMEOUT
# SMTP_READ_TIMEOUT
# Mail Incoming
# This is the domain set for the reply emails when conversation continuity is enabled
MAILER_INBOUND_EMAIL_DOMAIN=
# Set this to the appropriate ingress channel with regards to incoming emails
# Possible values are :
# relay for Exim, Postfix, Qmail
# mailgun for Mailgun
# mandrill for Mandrill
# postmark for Postmark
# sendgrid for Sendgrid
RAILS_INBOUND_EMAIL_SERVICE=
# Use one of the following based on the email ingress service
# Ref: https://edgeguides.rubyonrails.org/action_mailbox_basics.html
# Set this to a password of your choice and use it in the Inbound webhook
RAILS_INBOUND_EMAIL_PASSWORD=
MAILGUN_INGRESS_SIGNING_KEY=
MANDRILL_INGRESS_API_KEY=
# Creating Your Inbound Webhook Instructions for Postmark and Sendgrid:
# Inbound webhook URL format:
# https://actionmailbox:[YOUR_RAILS_INBOUND_EMAIL_PASSWORD]@[YOUR_CHATWOOT_DOMAIN.COM]/rails/action_mailbox/[RAILS_INBOUND_EMAIL_SERVICE]/inbound_emails
# Note: Replace the values inside the brackets; do not include the brackets themselves.
# Example: https://actionmailbox:mYRandomPassword3@chatwoot.example.com/rails/action_mailbox/postmark/inbound_emails
# For Postmark
# Ensure the 'Include raw email content in JSON payload' checkbox is selected in the inbound webhook section.
# Storage
ACTIVE_STORAGE_SERVICE=local
# Amazon S3
# documentation: https://www.chatwoot.com/docs/configuring-s3-bucket-as-cloud-storage
S3_BUCKET_NAME=
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_REGION=
# Log settings
# Disable if you want to write logs to a file
RAILS_LOG_TO_STDOUT=true
LOG_LEVEL=info
LOG_SIZE=500
# Configure this environment variable if you want to use lograge instead of rails logger
#LOGRAGE_ENABLED=true
### This environment variables are only required if you are setting up social media channels
# Facebook
# documentation: https://www.chatwoot.com/docs/facebook-setup
FB_VERIFY_TOKEN=
FB_APP_SECRET=
FB_APP_ID=
# https://developers.facebook.com/docs/messenger-platform/instagram/get-started#app-dashboard
IG_VERIFY_TOKEN=
# Twitter
# documentation: https://www.chatwoot.com/docs/twitter-app-setup
TWITTER_APP_ID=
TWITTER_CONSUMER_KEY=
TWITTER_CONSUMER_SECRET=
TWITTER_ENVIRONMENT=
#slack integration
SLACK_CLIENT_ID=
SLACK_CLIENT_SECRET=
# Google OAuth
GOOGLE_OAUTH_CLIENT_ID=
GOOGLE_OAUTH_CLIENT_SECRET=
GOOGLE_OAUTH_CALLBACK_URL=
### Change this env variable only if you are using a custom build mobile app
## Mobile app env variables
IOS_APP_ID=L7YLMN4634.com.chatwoot.app
ANDROID_BUNDLE_ID=com.chatwoot.app
# https://developers.google.com/android/guides/client-auth (use keytool to print the fingerprint in the first section)
ANDROID_SHA256_CERT_FINGERPRINT=AC:73:8E:DE:EB:56:EA:CC:10:87:02:A7:65:37:7B:38:D4:5D:D4:53:F8:3B:FB:D3:C6:28:64:1D:AA:08:1E:D8
### Smart App Banner
# https://developer.apple.com/library/archive/documentation/AppleApplications/Reference/SafariWebContent/PromotingAppswithAppBanners/PromotingAppswithAppBanners.html
# You can find your app-id in https://itunesconnect.apple.com
#IOS_APP_IDENTIFIER=1495796682
## Push Notification
## generate a new key value here : https://d3v.one/vapid-key-generator/
# VAPID_PUBLIC_KEY=
# VAPID_PRIVATE_KEY=
#
# for mobile apps
# FCM_SERVER_KEY=
### APM and Error Monitoring configurations
## Elastic APM
## https://www.elastic.co/guide/en/apm/agent/ruby/current/getting-started-rails.html
# ELASTIC_APM_SERVER_URL=
# ELASTIC_APM_SECRET_TOKEN=
## Sentry
# SENTRY_DSN=
## Scout
## https://scoutapm.com/docs/ruby/configuration
# SCOUT_KEY=YOURKEY
# SCOUT_NAME=YOURAPPNAME (Production)
# SCOUT_MONITOR=true
## NewRelic
# https://docs.newrelic.com/docs/agents/ruby-agent/configuration/ruby-agent-configuration/
# NEW_RELIC_LICENSE_KEY=
# Set this to true to allow newrelic apm to send logs.
# This is turned off by default.
# NEW_RELIC_APPLICATION_LOGGING_ENABLED=
## Datadog
## https://github.com/DataDog/dd-trace-rb/blob/master/docs/GettingStarted.md#environment-variables
# DD_TRACE_AGENT_URL=
# MaxMindDB API key to download GeoLite2 City database
# IP_LOOKUP_API_KEY=
## Rack Attack configuration
## To prevent and throttle abusive requests
# ENABLE_RACK_ATTACK=true
# RACK_ATTACK_LIMIT=300
# ENABLE_RACK_ATTACK_WIDGET_API=true
CONVERSATION_MESSAGE_PER_MINUTE_LIMIT=3000
## Running chatwoot as an API only server
## setting this value to true will disable the frontend dashboard endpoints
# CW_API_ONLY_SERVER=false
## Development Only Config
# if you want to use letter_opener for local emails
# LETTER_OPENER=true
# meant to be used in github codespaces
# WEBPACKER_DEV_SERVER_PUBLIC=
# If you want to use official mobile app,
# the notifications would be relayed via a Chatwoot server
ENABLE_PUSH_RELAY_SERVER=true
# Stripe API key
STRIPE_SECRET_KEY=
STRIPE_WEBHOOK_SECRET=
# Set to true if you want to upload files to cloud storage using the signed url
# Make sure to follow https://edgeguides.rubyonrails.org/active_storage_overview.html#cross-origin-resource-sharing-cors-configuration on the cloud storage after setting this to true.
DIRECT_UPLOADS_ENABLED=
#MS OAUTH creds
AZURE_APP_ID=
AZURE_APP_SECRET=
## Advanced configurations
## Change these values to fine tune performance
# control the concurrency setting of sidekiq
# SIDEKIQ_CONCURRENCY=10
# AI powered features
## OpenAI key
# OPENAI_API_KEY=
# Housekeeping/Performance related configurations
# Set to true if you want to remove stale contact inboxes
# contact_inboxes with no conversation older than 90 days will be removed
# REMOVE_STALE_CONTACT_INBOX_JOB_STATUS=false
+118
View File
@@ -0,0 +1,118 @@
# WAHA - WhatsApp HTTP API
#
# https://waha.devlike.pro/docs/how-to/config/
#
# ====================
# ===== SECURITY =====
# ====================
WAHA_API_KEY_PLAIN=00000000000000000000000000000000
WAHA_API_KEY=00000000000000000000000000000000
WAHA_DASHBOARD_USERNAME=admin
WAHA_DASHBOARD_PASSWORD=11111111111111111111111111111111
WHATSAPP_SWAGGER_USERNAME=admin
WHATSAPP_SWAGGER_PASSWORD=11111111111111111111111111111111
# Disable Dashboard or Swagger
WAHA_DASHBOARD_ENABLED=True
WHATSAPP_SWAGGER_ENABLED=True
# ==================
# ===== COMMON =====
# ==================
# Base URL for the API (used for webhooks, file URLs, etc.)
WAHA_BASE_URL=http://waha:3000
WAHA_PUBLIC_URL=http://localhost:3000
# Server configuration (if you need to customize hostname/port)
# WHATSAPP_API_SCHEMA=http
# WHATSAPP_API_PORT=3000
# WHATSAPP_API_HOSTNAME=localhost
# WhatsApp engine (WEBJS is default, GOWS or NOWEB for better performance)
WHATSAPP_DEFAULT_ENGINE=GOWS
# WAHA_CLIENT_DEVICE_NAME=YourApp
# =================
# ===== APPS ======
# =================
WAHA_APPS_ENABLED=True
REDIS_URL=redis://:redis@redis:6379
# ===================
# ===== LOGGING =====
# ===================
# Log format: JSON (for log management systems) or PRETTY (for development)
WAHA_LOG_FORMAT=JSON
# Log level: info, debug, error, warn
WAHA_LOG_LEVEL=info
# Don't print QR codes in logs
WAHA_PRINT_QR=False
# =========================
# ===== MEDIA STORAGE =====
# =========================
# Local storage (default)
WAHA_MEDIA_STORAGE=LOCAL
WHATSAPP_FILES_LIFETIME=1800
WHATSAPP_FILES_FOLDER=/app/.media
# Media download settings
# WHATSAPP_DOWNLOAD_MEDIA=true
# WHATSAPP_FILES_MIMETYPES=image/jpeg,image/png
# S3 storage (uncomment to use)
# WAHA_MEDIA_STORAGE=S3
# WAHA_S3_REGION=eu-west-2
# WAHA_S3_BUCKET=waha
# WAHA_S3_ACCESS_KEY_ID=minioadmin
# WAHA_S3_SECRET_ACCESS_KEY=minioadmin
# WAHA_S3_ENDPOINT=http://minio:9000
# WAHA_S3_FORCE_PATH_STYLE=True
# WAHA_S3_PROXY_FILES=True
# PostgreSQL storage (uncomment to use)
# WAHA_MEDIA_STORAGE=POSTGRESQL
# WAHA_MEDIA_POSTGRESQL_URL=postgres://postgres:postgres@postgres:5432/postgres?sslmode=disable
# ===========================
# ===== SESSION STORAGE =====
# ===========================
# PostgreSQL for sessions (uncomment to use)
WHATSAPP_SESSIONS_POSTGRESQL_URL=postgres://postgres:postgres@postgres:5432/postgres?sslmode=disable
# MongoDB for sessions (uncomment to use)
# WHATSAPP_SESSIONS_MONGO_URL=mongodb://mongouser:mongopassword@mongodb:27017
# ==================================
# ===== ADVANCED CONFIGURATION =====
# ==================================
# Timezone for screenshots and logs
# TZ=Europe/Dublin
# Session management
# WHATSAPP_START_SESSION=session1,session2
# WHATSAPP_RESTART_ALL_SESSIONS=False
# Webhooks
# WHATSAPP_HOOK_URL=https://webhook.site/11111111-1111-1111-1111-11111111
# WHATSAPP_HOOK_EVENTS=session.status,message,message.reaction
# Proxy configuration
# WHATSAPP_PROXY_SERVER=proxy.example.com:3128
# WHATSAPP_PROXY_SERVER_USERNAME=user
# WHATSAPP_PROXY_SERVER_PASSWORD=pass
# HTTPS configuration
# !DEPRECATED!
# Setup nginx reverse proxy to handle TLS connection
# using Let's encrypt or self-issued certificate
# WAHA_HTTPS_ENABLED=true
# WAHA_HTTPS_PATH_KEY=/etc/letsencrypt/live/waha.example.pro/privkey.pem
# WAHA_HTTPS_PATH_CERT=/etc/letsencrypt/live/waha.example.pro/cert.pem
# WAHA_HTTPS_PATH_CA=/etc/letsencrypt/live/waha.example.pro/chain.pem
@@ -0,0 +1,98 @@
services:
waha:
restart: always
image: devlikeapro/waha-plus
ports:
- '127.0.0.1:3000:3000/tcp'
volumes:
- './sessions:/app/.sessions' # required for WAHA Core only
- './media:/app/.media'
env_file:
- .waha.env
depends_on:
- postgres
- redis
- chatwoot
- chatwoot-sidekiq
logging:
driver: 'json-file'
options:
max-size: '100m'
max-file: '10'
dns:
- 1.1.1.1
- 8.8.8.8
base: &base
image: chatwoot/chatwoot:v4.7.0
env_file: .chatwoot.env ## Change this file for customized env variables
volumes:
- chatwoot_storage:/app/storage
extra_hosts:
- 'host.docker.internal:host-gateway'
logging:
driver: 'json-file'
options:
max-size: '100m'
max-file: '10'
chatwoot:
<<: *base
depends_on:
- postgres
- redis
ports:
- '127.0.0.1:3009:3009'
environment:
- NODE_ENV=production
- RAILS_ENV=production
- INSTALLATION_ENV=docker
entrypoint: docker/entrypoints/rails.sh
command: ['bundle', 'exec', 'rails', 's', '-p', '3009', '-b', '0.0.0.0']
restart: always
chatwoot-sidekiq:
<<: *base
depends_on:
- postgres
- redis
environment:
- NODE_ENV=production
- RAILS_ENV=production
- INSTALLATION_ENV=docker
command: ['bundle', 'exec', 'sidekiq', '-C', 'config/sidekiq.yml']
restart: always
postgres:
image: pgvector/pgvector:pg16
restart: always
ports:
- '127.0.0.1:5432:5432'
volumes:
- chatwoot_pg_data:/var/lib/postgresql/data
environment:
- POSTGRES_DB=chatwoot
- POSTGRES_USER=postgres
# Please provide your own password.
- POSTGRES_PASSWORD=postgres
redis:
image: redis:alpine
restart: always
command:
- 'sh'
- '-c'
- >
redis-server --bind 0.0.0.0 --port 6379 --requirepass
${REDIS_PASSWORD:-redis} --timeout 300 --tcp-keepalive 60 --save 900 1
--save 300 10 --save 60 10000 --appendonly yes --appendfsync everysec
env_file: .chatwoot.env
volumes:
- chatwoot_redis:/data
ports:
- '127.0.0.1:6379:6379'
volumes:
chatwoot_pg_data: {}
chatwoot_redis: {}
chatwoot_storage: {}
@@ -0,0 +1,97 @@
# https://waha.devlike.pro/docs/how-to/install/
services:
# Just to connect
waha-dashboard:
restart: unless-stopped
image: devlikeapro/waha-plus:latest
ports:
- '127.0.0.1:3000:3000/tcp'
env_file:
- .env
environment:
- WAHA_WORKER_ID=waha-dashboard
waha1:
restart: unless-stopped
image: devlikeapro/waha-plus:latest
ports:
- '127.0.0.1:3001:3000/tcp'
volumes:
- './media:/app/.media'
env_file:
- .env
environment:
- WAHA_WORKER_ID=waha1
- WAHA_BASE_URL=http://localhost:3001
- WHATSAPP_DEFAULT_ENGINE=NOWEB
# MongoDB
- WHATSAPP_SESSIONS_MONGO_URL=mongodb://mongodb:27017
# S3
- WAHA_MEDIA_STORAGE=S3
- WAHA_S3_REGION=eu-west-2
- WAHA_S3_BUCKET=waha
- WAHA_S3_ACCESS_KEY_ID=minioadmin
- WAHA_S3_SECRET_ACCESS_KEY=minioadmin
- WAHA_S3_ENDPOINT=http://minio:9000 # Not required if you're using AWS S3
- WAHA_S3_FORCE_PATH_STYLE=True # Required for Minio
- WAHA_S3_PROXY_FILES=True # Required for docker-compose setup
waha2:
restart: unless-stopped
image: devlikeapro/waha-plus:latest
ports:
- '127.0.0.1:3002:3000/tcp'
volumes:
- './media:/app/.media'
env_file:
- .env
environment:
- WAHA_WORKER_ID=waha2
- WAHA_BASE_URL=http://localhost:3002
- WHATSAPP_DEFAULT_ENGINE=NOWEB
# MongoDB
- WHATSAPP_SESSIONS_MONGO_URL=mongodb://mongodb:27017
# S3
- WAHA_MEDIA_STORAGE=S3
- WAHA_S3_REGION=eu-west-2
- WAHA_S3_BUCKET=waha
- WAHA_S3_ACCESS_KEY_ID=minioadmin
- WAHA_S3_SECRET_ACCESS_KEY=minioadmin
- WAHA_S3_ENDPOINT=http://minio:9000 # Not required if you're using AWS S3
- WAHA_S3_FORCE_PATH_STYLE=True # Required for Minio
- WAHA_S3_PROXY_FILES=True # Required for docker-compose setup
# NOTE: Only if you're using MongoDB
# https://waha.devlike.pro/docs/how-to/storages/#sessions---mongodb
# Comment this block if you're not using MongoDB
mongodb:
image: mongo
container_name: mongodb
ports:
- '127.0.0.1:27017:27017/tcp'
volumes:
- mongodb_data:/data/db
restart: always
# NOTE: Only if you're using S3 to save media files
# https://waha.devlike.pro/docs/how-to/storages/#media---s3
# Comment this block if you're using S3
minio:
image: quay.io/minio/minio
container_name: minio
restart: always
ports:
- '127.0.0.1:9000:9000'
- '127.0.0.1:9001:9001'
environment:
MINIO_REGION: 'eu-west-2'
MINIO_ROOT_USER: 'minioadmin'
MINIO_ROOT_PASSWORD: 'minioadmin'
volumes:
- minio_data:/data
command: server /data --console-address ":9001"
volumes:
# NOTE: Only if you're using MongoDB
mongodb_data: {}
minio_data: {}
+34
View File
@@ -0,0 +1,34 @@
services:
waha:
restart: always
container_name: waha
# image: devlikeapro/waha:latest
image: devlikeapro/waha-plus:latest
ports:
- '3000:3000/tcp'
volumes:
- './sessions:/app/.sessions'
- './media:/app/.media'
environment:
- WAHA_API_KEY=321
- WAHA_DASHBOARD_USERNAME=admin
- WAHA_DASHBOARD_PASSWORD=admin
- WHATSAPP_DEFAULT_ENGINE=WEBJS
- WAHA_PRINT_QR=False
- WAHA_MEDIA_STORAGE=LOCAL
- WHATSAPP_FILES_LIFETIME=0
- WHATSAPP_FILES_FOLDER=/app/.media
n8n:
image: docker.n8n.io/n8nio/n8n
container_name: n8n
restart: always
environment:
- WEBHOOK_URL=http://n8n:5678
ports:
- '5678:5678'
volumes:
- n8n_data:/home/node/.n8n
volumes:
n8n_data: {}
@@ -25,20 +25,19 @@ services:
- './.sessions:/app/.sessions'
environment:
# Environment variables from https://waha.devlike.pro/docs/how-to/config/
# - WHATSAPP_RESTART_ALL_SESSIONS=True
# - WHATSAPP_HOOK_URL=https://webhook.site/11111111-1111-1111-1111-11111111
# - WHATSAPP_HOOK_EVENTS=message
# - WHATSAPP_API_HOSTNAME=localhost
- WHATSAPP_DEFAULT_ENGINE=WEBJS
- WHATSAPP_API_KEY=321
- WAHA_API_KEY=321
# Username and password for Swagger
- WHATSAPP_SWAGGER_USERNAME=swagger
- WHATSAPP_SWAGGER_PASSWORD=admin
# Username and password for WAHA Dashboard
# you can use the same username and password for both Swagger and Dashboard
# Default - waha / waha
- WAHA_DASHBOARD_USERNAME=waha
- WAHA_DASHBOARD_PASSWORD=waha
- WAHA_DASHBOARD_USERNAME=admin
- WAHA_DASHBOARD_PASSWORD=admin
# Engine specific environment variables
# - WAHA_WEBJS_WEB_VERSION=2.2412.54-videofix
restart: always
@@ -54,10 +53,9 @@ services:
- './.sessions:/app/.sessions'
environment:
# Environment variables from https://waha.devlike.pro/docs/how-to/config/
# - WHATSAPP_RESTART_ALL_SESSIONS=True
# - WHATSAPP_HOOK_URL=https://webhook.site/11111111-1111-1111-1111-11111111
# - WHATSAPP_HOOK_EVENTS=message
# - WHATSAPP_API_KEY=321
# - WAHA_API_KEY=321
# - WHATSAPP_SWAGGER_USERNAME=admin
# - WHATSAPP_SWAGGER_PASSWORD=123
# - WAHA_DASHBOARD_USERNAME=admin
+74 -3
View File
@@ -1,11 +1,82 @@
#!/bin/sh
#
# Calculate UV_THREADPOOL_SIZE based on number of CPUs
cpus=$(node -e "const os = require('os'); console.log(os.cpus().length);")
#
# Try to get CPU count using Node.js, fallback to 1 if it fails
cpus=$(node -e "const os = require('os'); console.log(os.cpus().length);" 2>/dev/null) || cpus=1
# Make sure cpus is a number, default to 1 if not
case $cpus in
''|*[!0-9]*) cpus=1 ;;
esac
uv_threadpool_size=$(($cpus * 2))
# Check if uv_threadpool_size is less than 4 (default), set it to 4 if it is
if [ "$uv_threadpool_size" -lt 4 ]; then
uv_threadpool_size=4
fi
# Set UV_THREADPOOL_SIZE as an environment variable
export UV_THREADPOOL_SIZE="${UV_THREADPOOL_SIZE:-$uv_threadpool_size}"
# Start your application using yarn
yarn start:prod
#
# Handle API key hashing
#
# Save WHATSAPP_API_KEY or WAHA_API_KEY in a variable (WHATSAPP_API_KEY has priority)
if [ -n "$WHATSAPP_API_KEY" ]; then
key="$WHATSAPP_API_KEY"
elif [ -n "$WAHA_API_KEY" ]; then
key="$WAHA_API_KEY"
fi
# Unset both environment variables
unset WHATSAPP_API_KEY
unset WAHA_API_KEY
# Process the key if it exists
if [ -n "$key" ]; then
# Check if key is already hashed
if echo "$key" | grep -q "^sha512:"; then
# If already hashed, use it as is
export WAHA_API_KEY="$key"
else
# Hash the key using sha512sum
HASHED_KEY=$(echo -n "$key" | sha512sum | awk '{print $1}')
export WAHA_API_KEY="sha512:$HASHED_KEY"
fi
fi
#
# xvfb-run
#
USE_XVFB=false
# Check WAHA_RUN_XVFB parameter - only test for "false" case, treat all others as True
if [ "$WAHA_RUN_XVFB" = "false" ] || [ "$WAHA_RUN_XVFB" = "False" ] || [ "$WAHA_RUN_XVFB" = "0" ]; then
# Explicitly disabled by user
echo "WAHA_RUN_XVFB value: $WAHA_RUN_XVFB - xvfb is disabled"
USE_XVFB=false
else
# Check engine and run test if it's WEBJS or not specified
if [ -z "$WHATSAPP_DEFAULT_ENGINE" ] || [ "$WHATSAPP_DEFAULT_ENGINE" = "WEBJS" ]; then
# Try to run xvfb-run with a test command
if xvfb-run --auto-servernum echo "xvfb-run is working!"; then
USE_XVFB=true
else
echo "xvfb-run test failed, do not run it"
USE_XVFB=false
fi
fi
fi
#
# Start your application using node with exec to ensure proper signal handling
#
if [ "$USE_XVFB" = "true" ]; then
echo "Executing node with xvfb-run..."
exec xvfb-run --auto-servernum node dist/main
else
echo "Executing node without xvfb-run..."
exec node dist/main
fi
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

+7 -1
View File
@@ -3,7 +3,13 @@
"sourceRoot": "src",
"compilerOptions": {
"plugins": ["@nestjs/swagger"],
"assets": ["dashboard/**", "core/engines/webjs/*", "plus/engines/webjs/*"],
"assets": [
"dashboard/**",
"core/engines/webjs/*",
"plus/engines/webjs/*",
"apps/chatwoot/i18n/locales/*.yaml",
"apps/chatwoot/i18n/locales/*.yml"
],
"watchAssets": true
}
}
+127 -50
View File
@@ -14,108 +14,185 @@
"start:debug": "nest start --debug --watch",
"start:prod": "node dist/main",
"start:prod-exit": "node dist/main",
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix --max-warnings=0",
"lint-cache": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix --cache --max-warnings=0",
"test": "jest",
"test:watch": "jest --watch",
"test:cov": "jest --coverage",
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
"test:e2e": "jest --config ./test/jest-e2e.json"
"lint": "oxlint --deny-warnings",
"lint-fix": "oxlint --fix --deny-warnings",
"test": "yarn test:unit",
"test:watch": "jest --selectProjects unit --watch",
"test:cov": "jest --selectProjects unit --coverage",
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --selectProjects unit --runInBand",
"test:unit": "jest --selectProjects unit",
"test:e2e": "jest --selectProjects e2e",
"test:e8e": "yarn test:e2e",
"test:all": "yarn test:unit && yarn test:e8e",
"gows:proto:fetch": "node scripts/gows-proto.js fetch",
"gows:proto:build": "node scripts/gows-proto.js build",
"gows:proto": "yarn gows:proto:fetch && yarn gows:proto:build"
},
"dependencies": {
"@adiwajshing/baileys": "github:devlikeapro/Baileys#fork-master-channels-2",
"@adiwajshing/baileys": "github:devlikeapro/Baileys#fork-master-2026-01-25",
"@adiwajshing/keyed-db": "^0.2.4",
"@aws-sdk/client-s3": "^3.633.0",
"@aws-sdk/s3-request-presigner": "^3.633.0",
"@bull-board/api": "^6.9.1",
"@bull-board/express": "^6.9.1",
"@bull-board/nestjs": "^6.9.1",
"@casl/ability": "^6.8.0",
"@figuro/chatwoot-sdk": "^1.1.17",
"@liaoliaots/nestjs-redis": "^9",
"@nestjs/axios": "^3.0.2",
"@nestjs/common": "^9.0.9",
"@nestjs/bullmq": "^11.0.2",
"@nestjs/common": "^11.0.0",
"@nestjs/config": "^3.2.3",
"@nestjs/core": "^9.0.9",
"@nestjs/passport": "^9.0.0",
"@nestjs/platform-express": "^9.0.9",
"@nestjs/platform-ws": "^9.0.9",
"@nestjs/serve-static": "^2.1.3",
"@nestjs/core": "^11.0.0",
"@nestjs/passport": "^11.0.0",
"@nestjs/platform-express": "^11.1.5",
"@nestjs/platform-ws": "^11.0.0",
"@nestjs/serve-static": "^5.0.0",
"@nestjs/swagger": "^7.1.11",
"@nestjs/terminus": "^10.2.3",
"@nestjs/websockets": "^9.0.9",
"@nestjs/websockets": "^11.0.0",
"@types/better-sqlite3": "^7.6.10",
"@types/lodash": "^4.14.194",
"@types/mustache": "^4.2.5",
"@types/passport": "^1.0.17",
"@types/sqlite3": "^5.1.0",
"@types/ws": "^8.5.4",
"adm-zip": "0.5.10",
"agentkeepalive": "^4.5.0",
"async-lock": "^1.4.1",
"better-sqlite3": "9.6.0",
"audio-decode": "^2.2.2",
"axios": "^1.9.0",
"axios-retry": "^4.5.0",
"better-sqlite3": "^12.4.1",
"bullmq": "^5.48.1",
"check-disk-space": "^3.4.0",
"chokidar": "^3.6.0",
"class-transformer": "^0.5.1",
"class-validator": "0.14.0",
"del": "^6.0.0",
"commander": "^14.0.1",
"del": "^8.0.0",
"express-basic-auth": "^1.2.1",
"file-type": "16.5.4",
"fs-extra": "^11.2.0",
"google-protobuf": "^3.21.4",
"https-proxy-agent": "^7.0.0",
"ical.js": "^2.2.1",
"ioredis": "^5",
"joi": "^17.13.3",
"knex": "^3.1.0",
"libphonenumber-js": "^1.10.36",
"libsignal": "github:WhiskeySockets/libsignal-node#master",
"link-preview-js": "^3.0.4",
"lodash": "^4.17.21",
"long": "^5.3.2",
"mime-types": "^2.1.27",
"mongodb": "^6.3.0",
"mongodb": "6.9.0",
"ms": "^2.1.3",
"mustache": "^4.2.0",
"nestjs-pino": "^4.1.0",
"passport": "^0.6.0",
"node-cache": "5.1.2",
"passport": "^0.7.0",
"passport-headerapikey": "^1.2.2",
"pg": "^8.13.1",
"pino-http": "^10.2.0",
"pino-pretty": "^11.2.1",
"pretty-bytes": "5.6.0",
"promise-retry": "^2.0.1",
"puppeteer": "^21.0.0",
"puppeteer": "^24.31.0",
"qrcode": "^1.5.1",
"qrcode-terminal": "^0.12.0",
"reflect-metadata": "^0.1.13",
"request": "^2.88.2",
"requestretry": "^4.1.1",
"rimraf": "^3.0.2",
"rxjs": "^7.1.0",
"rimraf": "^4.3.0",
"rxjs": "^7.8.1",
"sharp": "^0.33.4",
"shell-quote": "^1.8.3",
"sqlite3": "^5.1.7",
"swagger-ui-express": "^4.1.4",
"venom-bot": "5.0.1",
"whatsapp-web.js": "github:devlikeapro/whatsapp-web.js#fork-webpack-exodus-channels"
"ulid": "^2.3.0",
"undici": "^7.16.0",
"uniqid": "^5.4.0",
"whatsapp-web.js": "github:devlikeapro/whatsapp-web.js#fork-main-2026-02-01",
"write-file-atomic": "^6.0.0",
"yaml": "^2.7.1"
},
"optionalDependencies": {
"bufferutil": "^4.0.8"
},
"resolutions": {
"typescript": "5.9.2",
"bufferutil": "^4.0.8",
"ws": "^8.18.0",
"puppeteer": "^24.31.0",
"whatwg-url": "13.0.0",
"axios": "^1.9.0"
},
"devDependencies": {
"@nestjs/cli": "^9.0.0",
"@nestjs/schematics": "^9.0.1",
"@nestjs/testing": "^9.0.9",
"@grpc/grpc-js": "^1.14.1",
"@grpc/proto-loader": "^0.8.0",
"@nestjs/cli": "^11.0.0",
"@nestjs/schematics": "^11.0.0",
"@nestjs/testing": "^11.0.0",
"@types/express": "^4.17.3",
"@types/jest": "26.0.10",
"@types/node": "^18.14.2",
"@types/node-fetch": "^2.6.13",
"@types/semver": "^7.7.0",
"@types/supertest": "^2.0.8",
"@typescript-eslint/eslint-plugin": "3.9.1",
"@typescript-eslint/parser": "3.9.1",
"eslint": "7.7.0",
"eslint-config-prettier": "^6.10.0",
"eslint-plugin-import": "^2.20.1",
"eslint-plugin-simple-import-sort": "^10.0.0",
"dotenv": "^17.2.3",
"grpc-tools": "^1.13.0",
"jest": "^29.7.0",
"oxlint": "^1.13.0",
"prettier": "^1.19.1",
"protoc-gen-ts": "^0.8.7",
"supertest": "^4.0.2",
"ts-jest": "^29.1.3",
"ts-loader": "^6.2.1",
"ts-node": "^10.9.1",
"ts-node": "^10.9.2",
"tsconfig-paths": "^4.1.0",
"typescript": "^4.8.4"
"typescript": "5.9.2"
},
"jest": {
"moduleFileExtensions": [
"js",
"json",
"ts"
],
"rootDir": "src",
"testRegex": ".test.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
},
"coverageDirectory": "../coverage",
"testEnvironment": "node"
"projects": [
{
"displayName": "unit",
"moduleFileExtensions": [
"js",
"json",
"ts"
],
"rootDir": "src",
"moduleNameMapper": {
"^@waha/(.*)$": "<rootDir>/$1"
},
"testRegex": ".test.ts$",
"testPathIgnorePatterns": [
"/__tests__/e2e/"
],
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
},
"coverageDirectory": "../coverage",
"testEnvironment": "node"
},
{
"displayName": "e2e",
"moduleFileExtensions": [
"js",
"json",
"ts"
],
"rootDir": "src",
"moduleNameMapper": {
"^@waha/(.*)$": "<rootDir>/$1"
},
"testRegex": "__tests__/e2e/.*\\.test\\.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
},
"coverageDirectory": "../coverage",
"testEnvironment": "node"
}
]
},
"packageManager": "yarn@3.6.3"
"packageManager": "yarn@4.9.2"
}
+22
View File
@@ -0,0 +1,22 @@
#!/bin/bash
# Set source and output file
SOURCE_DIR="src"
OUTPUT_DIR="$HOME/Downloads/chatgpt"
OUTPUT_FILE="$OUTPUT_DIR/waha.ts"
# Create or clear the output file
rm -f "$OUTPUT_FILE"
touch "$OUTPUT_FILE"
# Find all .md files in the source directory and combine them
find "$SOURCE_DIR" -type f -name "*.ts" | while read -r file; do
# Add file name to OUTPUT_FILE
echo "\n-------" >> "$OUTPUT_FILE"
echo "File: $file" >> "$OUTPUT_FILE"
echo "-------\n" >> "$OUTPUT_FILE"
# Append the content of the file to the output file
cat "$file" >> "$OUTPUT_FILE"
done
echo "All .ts files combined into $OUTPUT_FILE with separators."
+72
View File
@@ -0,0 +1,72 @@
#!/usr/bin/env node
const { readFileSync } = require('fs');
const { spawnSync } = require('child_process');
const path = require('path');
const YAML = require('yaml');
const repoRoot = path.resolve(__dirname, '..');
const enPath = path.join(repoRoot, 'src/apps/chatwoot/i18n/locales/en-US.yaml');
const contents = readFileSync(enPath, 'utf8');
const document = YAML.parse(contents);
const flatten = (node, prefix = '') => {
if (node && typeof node === 'object' && !Array.isArray(node)) {
return Object.entries(node).reduce((acc, [key, value]) => {
const next = prefix ? `${prefix}.${key}` : key;
return acc.concat(flatten(value, next));
}, []);
}
return [prefix];
};
const allKeys = flatten(document).filter(Boolean);
const ignoredPrefixes = ['datetime.', 'locale.'];
const ignored = new Set();
const unused = [];
const searchRoots = ['src', 'tests'];
for (const key of allKeys) {
if (
ignoredPrefixes.some((prefix) => key.startsWith(prefix)) ||
!key.includes('.')
) {
ignored.add(key);
continue;
}
const result = spawnSync(
'rg',
[
'--files-with-matches',
'--fixed-strings',
key,
...searchRoots,
'--glob',
'!apps/chatwoot/i18n/locales/*.yaml',
],
{
cwd: repoRoot,
encoding: 'utf8',
},
);
if (result.status !== 0 || !result.stdout.trim()) {
unused.push(key);
}
}
const message = [
`Scanned ${allKeys.length} keys.`,
`Ignored ${ignored.size} keys (datetime.*, locale.*, or without dot).`,
unused.length
? `Found ${unused.length} potentially unused keys:\n - ${unused.join(
'\n - ',
)}`
: 'No unused keys detected.',
];
console.log(message.join('\n'));
+144
View File
@@ -0,0 +1,144 @@
// eslint-disable-next-line @typescript-eslint/no-var-requires
const fs = require('fs');
// eslint-disable-next-line @typescript-eslint/no-var-requires
const path = require('path');
// eslint-disable-next-line @typescript-eslint/no-var-requires
const axios = require('axios');
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { execSync } = require('child_process');
// eslint-disable-next-line @typescript-eslint/no-var-requires
const yargs = require('yargs');
// Defaults
const CONFIG_FILE = 'waha.config.json';
// Load defaults from package.json
gows = (() => {
try {
const config = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
return config.waha.gows || {};
} catch (error) {
return {};
}
})();
const DEFAULT_REPO = gows.repo;
if (!DEFAULT_REPO) {
throw new Error(`Missing default repo in ${CONFIG_FILE}`);
}
const DEFAULT_REF = gows.ref;
if (!DEFAULT_REF) {
throw new Error('Missing default ref in ${CONFIG_FILE}');
}
const DEFAULT_DIR = './src/core/engines/gows/proto';
const PROTO_FILES = ['gows.proto'];
const PROTO_OUTPUT = './src/core/engines/gows/grpc';
// Helper function to clean directory
function cleanDirectory(directory, suffix) {
if (!fs.existsSync(directory)) {
fs.mkdirSync(directory, { recursive: true });
return;
}
const files = fs.readdirSync(directory);
for (const file of files) {
const filePath = path.join(directory, file);
if (file.endsWith(suffix)) {
fs.unlinkSync(filePath);
}
}
}
// Helper function to download files
async function downloadFiles(repo, ref, directory) {
for (const file of PROTO_FILES) {
const url = `https://github.com/${repo}/releases/download/${ref}/${file}`;
const filePath = path.join(directory, file);
try {
const response = await axios.get(url, { responseType: 'arraybuffer' });
fs.writeFileSync(filePath, response.data);
console.log(`Downloaded: ${file}`);
} catch (error) {
console.error(`Failed to download ${file}: ${error.message}`);
}
}
}
// Handler for fetch command
async function handleFetch(repo, ref, dir) {
console.log(`Fetching .proto files from ${repo}@${ref} to ${dir}...`);
cleanDirectory(dir, '.proto');
await downloadFiles(repo, ref, dir);
}
// Handler for build command
function handleBuild(dir) {
console.log('Building gRPC files...');
cleanDirectory(PROTO_OUTPUT);
const command = `grpc_tools_node_protoc \
--plugin=protoc-gen-ts=node_modules/.bin/protoc-gen-ts \
--plugin=protoc-gen-grpc=node_modules/.bin/grpc_tools_node_protoc_plugin \
--js_out=import_style=commonjs,binary:${PROTO_OUTPUT} \
--grpc_out=grpc_js:${PROTO_OUTPUT} \
--ts_out=grpc_js:${PROTO_OUTPUT} \
-I ${dir} ${dir}/gows.proto`;
try {
execSync(command, { stdio: 'inherit' });
console.log('gRPC files built successfully.');
} catch (error) {
console.error(`Failed to build gRPC files: ${error.message}`);
}
}
//
// Commands
//
// fetch
yargs.command(
'fetch',
'Fetch .proto files from GitHub',
(yargs) => {
yargs
.option('repo', {
describe: 'GitHub repository (owner/repo)',
type: 'string',
default: DEFAULT_REPO,
})
.option('ref', {
describe: 'Git reference (branch or commit SHA)',
type: 'string',
default: DEFAULT_REF,
})
.option('dir', {
describe: 'Directory to output .proto files',
type: 'string',
default: DEFAULT_DIR,
});
},
async (argv) => {
await handleFetch(argv.repo, argv.ref, argv.dir);
},
);
// build
yargs.command(
'build',
'Build gRPC files from .proto files',
(yargs) => {
yargs.option('dir', {
describe: 'Directory containing .proto files',
type: 'string',
default: DEFAULT_DIR,
});
},
(argv) => {
handleBuild(argv.dir);
},
);
// Parse arguments
yargs.parse();
+92
View File
@@ -0,0 +1,92 @@
#!/usr/bin/env node
'use strict';
const fs = require('fs');
const path = require('path');
const crypto = require('crypto');
const projectRoot = path.resolve(__dirname, '..');
const args = process.argv.slice(2);
const forceIndex = args.indexOf('--force');
const forceOverwrite = forceIndex !== -1;
if (forceOverwrite) {
args.splice(forceIndex, 1);
}
const folderArg = args[0] || '.';
const sourceArgRaw = args[1];
const targetArg = args[2] || '.env';
const envDir = path.resolve(process.cwd(), folderArg);
const sourceArg = sourceArgRaw || '.env.example';
const templateBaseDir = sourceArgRaw ? envDir : projectRoot;
const templatePath = path.isAbsolute(sourceArg)
? sourceArg
: path.resolve(templateBaseDir, sourceArg);
const targetPath = path.isAbsolute(targetArg)
? targetArg
: path.resolve(envDir, targetArg);
function exitWithMessage(message, code = 1) {
console.error(message);
process.exit(code);
}
if (!fs.existsSync(envDir)) {
exitWithMessage(`Target directory does not exist: ${envDir}`);
}
if (!fs.existsSync(templatePath)) {
exitWithMessage(`Missing template file: ${templatePath}`);
}
if (!forceOverwrite && fs.existsSync(targetPath)) {
const stats = fs.statSync(targetPath);
if (stats.size > 0) {
exitWithMessage(
`${targetPath} already exists and is not empty. Remove it manually or rerun with --force to overwrite.`,
);
}
}
const exampleContent = fs.readFileSync(templatePath, 'utf8');
function generateSecret() {
return crypto.randomUUID({ disableEntropyCache: true }).replace(/-/g, '');
}
const apiKey = generateSecret();
const adminPassword = generateSecret();
const updatedContent = exampleContent
.replace(/^WAHA_API_KEY=.*/m, `WAHA_API_KEY=${apiKey}`)
.replace(/^WAHA_API_KEY_PLAIN=.*/m, `WAHA_API_KEY_PLAIN=${apiKey}`)
.replace(/^WAHA_DASHBOARD_USERNAME=.*/m, 'WAHA_DASHBOARD_USERNAME=admin')
.replace(
/^WAHA_DASHBOARD_PASSWORD=.*/m,
`WAHA_DASHBOARD_PASSWORD=${adminPassword}`,
)
.replace(/^WHATSAPP_SWAGGER_USERNAME=.*/m, 'WHATSAPP_SWAGGER_USERNAME=admin')
.replace(
/^WHATSAPP_SWAGGER_PASSWORD=.*/m,
`WHATSAPP_SWAGGER_PASSWORD=${adminPassword}`,
);
fs.writeFileSync(targetPath, updatedContent, { encoding: 'utf8' });
const lines = [
'Credentials generated.',
'',
'Dashboard and Swagger:',
' - Username: admin',
` - Password: ${adminPassword}`,
'',
'API key: ',
` - ${apiKey}`,
'',
'Use it in the Dashboard connection flow: https://waha.devlike.pro/docs/how-to/dashboard/#api-key',
];
console.log(lines.join('\n'));
-23
View File
@@ -1,23 +0,0 @@
#!/bin/bash
set -e
echo "Starting WAHA..."
docker run -d \
--restart=always \
--name waha \
-p 3000:3000/tcp \
-v ./.sessions:/app/.sessions \
-v ./.media:/app/.media \
--env WHATSAPP_API_KEY=321 \
--env WAHA_DASHBOARD_USERNAME=waha \
--env WAHA_DASHBOARD_PASSWORD=waha \
--env WAHA_LOG_FORMAT=PRETTY \
--env WAHA_LOG_LEVEL=info \
--env WHATSAPP_DEFAULT_ENGINE=WEBJS \
--env WHATSAPP_RESTART_ALL_SESSIONS=True \
--env WAHA_PRINT_QR=False \
--env WHATSAPP_FILES_LIFETIME=0 \
--env WHATSAPP_FILES_FOLDER=/app/.media \
devlikeapro/waha-plus:latest
echo "WAHA started."
-21
View File
@@ -1,21 +0,0 @@
set -e
echo "Updating WAHA..."
echo "Logging in to Docker..."
docker login -u devlikeapro # -p {KEY} - you can hard code key here https://portal.devlike.pro/
echo "Pulling the latest image..."
docker pull devlikeapro/waha-plus
echo "Logging out of Docker..."
docker logout
# Just pull if you're using WAHA Core
# docker pull devlikeapro/waha
echo "Stopping the current container..."
docker stop waha || true
echo "Removing the current container..."
docker rm waha || true
echo "Running WAHA..."
sh ./waha-run.sh
+391
View File
@@ -0,0 +1,391 @@
import axios from 'axios';
import WebSocket = require('ws');
const VALID_API_KEY = '666';
const VALID_USERNAME = 'admin';
const VALID_PASSWORD = '666';
const VALID_SESSION_API_KEY = 'key_0fQfI3n3rFVsczBbBfknLXKUreY2my6J';
const BASE_URL = (process.env.WAHA_BASE_URL ?? 'http://localhost:3000').replace(
/\/$/,
'',
);
const WS_BASE_URL = BASE_URL.replace(/^http/, 'ws');
const HTTP_OK = 200;
const HTTP_CREATED = 201;
const HTTP_UNAUTHORIZED = 401;
const HTTP_FORBIDDEN = 403;
const HTTP_NOT_FOUND = 404;
const HTTP_UNPROCESSABLE = 422;
const WS_POLICY_VIOLATION = 1008;
const WS_OK_CODES = [1000, 1005];
describe('admin - GET /api/sessions/{name}', () => {
test('no api key is unauthorized', async () => {
const response = await axios.get(`${BASE_URL}/api/sessions/default`, {
validateStatus: () => true,
});
expect(response.status).toBe(HTTP_UNAUTHORIZED);
});
test('valid api key is ok', async () => {
const response = await axios.get(`${BASE_URL}/api/sessions/default`, {
headers: { 'X-Api-Key': VALID_API_KEY },
validateStatus: () => true,
});
expect(response.status).toBe(HTTP_OK);
});
test('invalid api key is unauthorized', async () => {
const response = await axios.get(`${BASE_URL}/api/sessions/default`, {
headers: { 'X-Api-Key': '123' },
validateStatus: () => true,
});
expect(response.status).toBe(HTTP_UNAUTHORIZED);
});
});
describe('admin - POST /api/sessions', () => {
test('admin key can create a session without name and remove it', async () => {
const createResponse = await axios.post(
`${BASE_URL}/api/sessions`,
{},
{
headers: { 'X-Api-Key': VALID_API_KEY },
validateStatus: () => true,
},
);
expect(createResponse.status).toBe(HTTP_CREATED);
expect(createResponse.data?.name).toBeTruthy();
const deleteResponse = await axios.delete(
`${BASE_URL}/api/sessions/${createResponse.data.name}`,
{
headers: { 'X-Api-Key': VALID_API_KEY },
validateStatus: () => true,
},
);
expect(deleteResponse.status).toBe(HTTP_OK);
});
});
describe('GET /api/sessions?all=true', () => {
beforeAll(async () => {
const createResponse = await axios.post(
`${BASE_URL}/api/sessions`,
{ name: 'another' },
{
headers: { 'X-Api-Key': VALID_API_KEY },
validateStatus: () => true,
},
);
if (![HTTP_CREATED, HTTP_UNPROCESSABLE].includes(createResponse.status)) {
throw new Error(
`Unexpected status for creating "another" session: ${createResponse.status}`,
);
}
});
afterAll(async () => {
const deleteResponse = await axios.delete(
`${BASE_URL}/api/sessions/another`,
{
headers: { 'X-Api-Key': VALID_API_KEY },
validateStatus: () => true,
},
);
if (![HTTP_OK, HTTP_NOT_FOUND].includes(deleteResponse.status)) {
throw new Error(
`Unexpected status for deleting "another" session: ${deleteResponse.status}`,
);
}
});
test('admin key returns two sessions', async () => {
const response = await axios.get(`${BASE_URL}/api/sessions?all=true`, {
headers: { 'X-Api-Key': VALID_API_KEY },
validateStatus: () => true,
});
expect(response.status).toBe(HTTP_OK);
expect(response.data).toHaveLength(2);
expect(response.data.map((session) => session.name).sort()).toEqual([
'another',
'default',
]);
});
test('no api key is unauthorized', async () => {
const response = await axios.get(`${BASE_URL}/api/sessions?all=true`, {
validateStatus: () => true,
});
expect(response.status).toBe(HTTP_UNAUTHORIZED);
});
test('session key returns one session', async () => {
const response = await axios.get(`${BASE_URL}/api/sessions?all=true`, {
headers: { 'X-Api-Key': VALID_SESSION_API_KEY },
validateStatus: () => true,
});
expect(response.status).toBe(HTTP_OK);
expect(response.data).toHaveLength(1);
expect(response.data[0]?.name).toBe('default');
});
});
describe('GET /api/server/version', () => {
test('no api key is unauthorized', async () => {
const response = await axios.get(`${BASE_URL}/api/server/version`, {
validateStatus: () => true,
});
expect(response.status).toBe(HTTP_UNAUTHORIZED);
});
test('admin api key is ok', async () => {
const response = await axios.get(`${BASE_URL}/api/server/version`, {
headers: { 'X-Api-Key': VALID_API_KEY },
validateStatus: () => true,
});
expect(response.status).toBe(HTTP_OK);
});
test('session api key is ok', async () => {
const response = await axios.get(`${BASE_URL}/api/server/version`, {
headers: { 'X-Api-Key': VALID_SESSION_API_KEY },
validateStatus: () => true,
});
expect(response.status).toBe(HTTP_OK);
});
});
describe('GET /api/server/environment', () => {
test('no api key is unauthorized', async () => {
const response = await axios.get(`${BASE_URL}/api/server/environment`, {
validateStatus: () => true,
});
expect(response.status).toBe(HTTP_UNAUTHORIZED);
});
test('admin api key is ok', async () => {
const response = await axios.get(`${BASE_URL}/api/server/environment`, {
headers: { 'X-Api-Key': VALID_API_KEY },
validateStatus: () => true,
});
expect(response.status).toBe(HTTP_OK);
});
test('session api key is forbidden', async () => {
const response = await axios.get(`${BASE_URL}/api/server/environment`, {
headers: { 'X-Api-Key': VALID_SESSION_API_KEY },
validateStatus: () => true,
});
expect(response.status).toBe(HTTP_FORBIDDEN);
});
});
describe('admin - GET /health', () => {
test('no api key is unauthorized', async () => {
const response = await axios.get(`${BASE_URL}/health`, {
validateStatus: () => true,
});
expect(response.status).toBe(HTTP_UNAUTHORIZED);
});
test('valid api key is ok', async () => {
const response = await axios.get(`${BASE_URL}/health`, {
headers: { 'X-Api-Key': VALID_API_KEY },
validateStatus: () => true,
});
expect(response.status).toBe(HTTP_OK);
});
test('invalid api key is unauthorized', async () => {
const response = await axios.get(`${BASE_URL}/health`, {
headers: { 'X-Api-Key': '123' },
validateStatus: () => true,
});
expect(response.status).toBe(HTTP_UNAUTHORIZED);
});
});
describe('/ws', () => {
const buildWsUrl = (apiKey?: string) => {
const wsUrl = new URL('/ws', WS_BASE_URL);
wsUrl.searchParams.set('session', '*');
wsUrl.searchParams.set('events', '*');
if (apiKey) {
wsUrl.searchParams.set('x-api-key', apiKey);
}
return wsUrl.toString();
};
const waitForOpen = (socket: WebSocket, timeoutMs = 5_000) =>
new Promise<void>((resolve, reject) => {
const timeout = setTimeout(() => {
reject(new Error('WebSocket open timeout'));
}, timeoutMs);
socket.once('open', () => {
clearTimeout(timeout);
resolve();
});
socket.once('error', (error) => {
clearTimeout(timeout);
reject(error);
});
});
const waitForClose = (socket: WebSocket, timeoutMs = 5_000) =>
new Promise<{ code: number; reason: string }>((resolve, reject) => {
const timeout = setTimeout(() => {
reject(new Error('WebSocket close timeout'));
}, timeoutMs);
socket.once('close', (code, reason) => {
clearTimeout(timeout);
resolve({ code, reason: reason.toString() });
});
socket.once('error', (error) => {
clearTimeout(timeout);
reject(error);
});
});
test('no api key is unauthorized', async () => {
const socket = new WebSocket(buildWsUrl());
const { code } = await waitForClose(socket);
expect(code).toBe(WS_POLICY_VIOLATION);
});
test('admin api key is ok', async () => {
const socket = new WebSocket(buildWsUrl(VALID_API_KEY));
await waitForOpen(socket);
socket.close();
const { code } = await waitForClose(socket);
expect(WS_OK_CODES).toContain(code);
});
});
describe('GET /api/files/test.txt', () => {
// create "test.txt" in current dir/.media/test.txt
let file = null;
beforeAll(() => {
const fs = require('fs');
const path = require('path');
const mediaDir = path.resolve('./.media');
if (!fs.existsSync(mediaDir)) {
fs.mkdirSync(mediaDir);
}
const filePath = path.join(mediaDir, 'test.txt');
fs.writeFileSync(filePath, 'This is a test file.');
file = filePath;
});
test('no api key is unauthorized', async () => {
const response = await axios.get(`${BASE_URL}/api/files/test.txt`, {
validateStatus: () => true,
});
expect(response.status).toBe(HTTP_UNAUTHORIZED);
});
test('valid api key is ok', async () => {
const response = await axios.get(`${BASE_URL}/api/files/test.txt`, {
headers: { 'X-Api-Key': VALID_API_KEY },
validateStatus: () => true,
});
expect(response.status).toBe(HTTP_OK);
});
test('invalid api key is unauthorized', async () => {
const response = await axios.get(`${BASE_URL}/api/files/test.txt`, {
headers: { 'X-Api-Key': '123' },
validateStatus: () => true,
});
expect(response.status).toBe(HTTP_UNAUTHORIZED);
});
});
describe('GET /', () => {
test('no auth is unauthorized', async () => {
const response = await axios.get(`${BASE_URL}/`, {
validateStatus: () => true,
});
expect(response.status).toBe(HTTP_UNAUTHORIZED);
});
test('valid auth is ok', async () => {
const response = await axios.get(`${BASE_URL}/`, {
auth: { username: VALID_USERNAME, password: VALID_PASSWORD },
validateStatus: () => true,
});
expect(response.status).toBe(HTTP_OK);
});
test('wrong auth is unauthorized', async () => {
const response = await axios.get(`${BASE_URL}/`, {
auth: { username: VALID_USERNAME, password: 'password-another' },
validateStatus: () => true,
});
expect(response.status).toBe(HTTP_UNAUTHORIZED);
});
});
describe('GET /dashboard', () => {
test('no auth is unauthorized', async () => {
const response = await axios.get(`${BASE_URL}/dashboard/`, {
validateStatus: () => true,
});
expect(response.status).toBe(HTTP_UNAUTHORIZED);
});
test('valid auth is ok', async () => {
const response = await axios.get(`${BASE_URL}/dashboard/`, {
auth: { username: VALID_USERNAME, password: VALID_PASSWORD },
validateStatus: () => true,
});
expect(response.status).toBe(HTTP_OK);
});
test('wrong auth is unauthorized', async () => {
const response = await axios.get(`${BASE_URL}/dashboard/`, {
auth: { username: VALID_USERNAME, password: 'password-another' },
validateStatus: () => true,
});
expect(response.status).toBe(HTTP_UNAUTHORIZED);
});
});
describe('session key - GET /api/sessions/{name}', () => {
test('default key - default session - is authorized', async () => {
const response = await axios.get(`${BASE_URL}/api/sessions/default`, {
headers: { 'X-Api-Key': VALID_SESSION_API_KEY },
validateStatus: () => true,
});
expect(response.status).toBe(HTTP_OK);
});
test('default key - create a new sessions - forbidden', async () => {
const response = await axios.post(
`${BASE_URL}/api/sessions`,
{ name: 'newone' },
{
headers: { 'X-Api-Key': VALID_SESSION_API_KEY },
validateStatus: () => true,
},
);
expect(response.status).toBe(HTTP_FORBIDDEN);
});
test('another key - default session - unauthorized', async () => {
const response = await axios.get(`${BASE_URL}/api/sessions/default`, {
headers: { 'X-Api-Key': 'key_another' },
validateStatus: () => true,
});
expect(response.status).toBe(HTTP_UNAUTHORIZED);
});
test('default key - another session - forbidden', async () => {
const response = await axios.get(`${BASE_URL}/api/sessions/another`, {
headers: { 'X-Api-Key': VALID_SESSION_API_KEY },
validateStatus: () => true,
});
expect(response.status).toBe(HTTP_FORBIDDEN);
});
});
+139
View File
@@ -0,0 +1,139 @@
import {
Body,
Controller,
Delete,
Get,
NotFoundException,
Param,
Post,
Put,
UnprocessableEntityException,
UseGuards,
UsePipes,
} from '@nestjs/common';
import { ApiOperation, ApiSecurity, ApiTags } from '@nestjs/swagger';
import { SessionManager } from '@waha/core/abc/manager.abc';
import { Action } from '@waha/core/auth/casl.types';
import { CanServer } from '@waha/core/auth/policies';
import { CheckPolicies } from '@waha/core/auth/policies.decorator';
import { PoliciesGuard } from '@waha/core/auth/policies.guard';
import { ApiKey, CheckInvariant } from '@waha/core/storage/IApiKeyRepository';
import { WAHAValidationPipe } from '@waha/nestjs/pipes/WAHAValidationPipe';
import { ApiKeyDTO, ApiKeyRequest } from '@waha/structures/apikeys.dto';
import { generatePrefixedId, generateSecret } from '@waha/utils/ids';
@ApiSecurity('api_key')
@Controller('api/keys')
@ApiTags('🔑 Api Keys')
@UseGuards(PoliciesGuard)
@CheckPolicies(CanServer(Action.Manage))
export class ApiKeysController {
constructor(private manager: SessionManager) {}
@Post('/')
@ApiOperation({ summary: 'Create a new API key' })
@UsePipes(new WAHAValidationPipe())
async create(@Body() body: ApiKeyRequest): Promise<ApiKeyDTO> {
CheckInvariant(body);
if (body.session) {
const exists = await this.manager.exists(body.session);
if (!exists) {
throw new UnprocessableEntityException(
`Session "${body.session}" does not exist`,
);
}
}
let apikey: ApiKey | null = null;
// Try 5 times to check there's no conflict on id and key
for (let i = 0; i < 5; i++) {
apikey = {
id: generatePrefixedId('key_id'),
key: `key_${generateSecret(32)}`,
isActive: body.isActive,
isAdmin: body.isAdmin,
session: body.session,
rules: null,
};
const idExists = await this.manager.apiKeyRepository.getById(apikey.id);
if (idExists) {
continue;
}
const keyExists = await this.manager.apiKeyRepository.getByKey(
apikey.key,
);
if (keyExists) {
continue;
}
break;
}
if (!apikey) {
throw new UnprocessableEntityException(
`Failed to generate API key, try again`,
);
}
CheckInvariant(apikey);
await this.manager.apiKeyRepository.upsert(apikey);
return ApiKeyToDTO(apikey);
}
@Get('/')
@ApiOperation({ summary: 'Get all API keys' })
async list(): Promise<ApiKeyDTO[]> {
const keys = await this.manager.apiKeyRepository.list();
return keys.map((key) => ApiKeyToDTO(key));
}
@Put('/:id')
@ApiOperation({ summary: 'Update an API key' })
@UsePipes(new WAHAValidationPipe())
async update(
@Param('id') id: string,
@Body() body: ApiKeyRequest,
): Promise<ApiKeyDTO> {
const existing = await this.manager.apiKeyRepository.getById(id);
if (!existing) {
throw new NotFoundException('API key not found');
}
const apikey: ApiKey = {
...existing,
isActive: body.isActive,
isAdmin: body.isAdmin,
session: body.session,
};
CheckInvariant(apikey);
if (apikey.session) {
const exists = await this.manager.exists(apikey.session);
if (!exists) {
throw new UnprocessableEntityException(
`Session "${apikey.session}" does not exist`,
);
}
}
CheckInvariant(apikey);
await this.manager.apiKeyRepository.upsert(apikey);
return ApiKeyToDTO(apikey);
}
@Delete('/:id')
@ApiOperation({ summary: 'Delete an API key' })
async delete(@Param('id') id: string): Promise<{ result: true }> {
const existing = await this.manager.apiKeyRepository.getById(id);
if (!existing) {
throw new NotFoundException('API key not found');
}
await this.manager.apiKeyRepository.deleteById(id);
return { result: true };
}
}
function ApiKeyToDTO(apikey: ApiKey): ApiKeyDTO {
return {
id: apikey.id,
key: apikey.key,
isActive: apikey.isActive,
isAdmin: apikey.isAdmin,
session: apikey.session,
};
}
+14 -49
View File
@@ -5,11 +5,12 @@ import {
Post,
Query,
UseInterceptors,
UseGuards,
} from '@nestjs/common';
import { UnprocessableEntityException } from '@nestjs/common/exceptions/unprocessable-entity.exception';
import { ApiOperation, ApiSecurity, ApiTags } from '@nestjs/swagger';
import { ApiFileAcceptHeader } from '@waha/nestjs/ApiFileAcceptHeader';
import {
QRCodeSessionParam,
SessionApiParam,
SessionParam,
} from '@waha/nestjs/params/SessionApiParam';
@@ -18,37 +19,37 @@ import { SessionManager } from '../core/abc/manager.abc';
import { WhatsappSession } from '../core/abc/session.abc';
import { BufferResponseInterceptor } from '../nestjs/BufferResponseInterceptor';
import {
CaptchaBody,
OTPRequest,
QRCodeFormat,
QRCodeQuery,
QRCodeValue,
RequestCodeRequest,
} from '../structures/auth.dto';
import { WAHASessionStatus } from '../structures/enums.dto';
import { Base64File } from '../structures/files.dto';
import { PoliciesGuard } from '@waha/core/auth/policies.guard';
import { CheckPolicies } from '@waha/core/auth/policies.decorator';
import { CanSession, FromParam } from '@waha/core/auth/policies';
import { Action } from '@waha/core/auth/casl.types';
@ApiSecurity('api_key')
@Controller('api/:session/auth')
@ApiTags('🔑 Auth')
@ApiTags('📱 Pairing')
@UseGuards(PoliciesGuard)
@CheckPolicies(CanSession(Action.Use, FromParam('session')))
class AuthController {
constructor(private manager: SessionManager) {}
@Get('qr')
@ApiOperation({
summary: 'Get QR code for pairing WhatsApp Web.',
summary: 'Get QR code for pairing WhatsApp API.',
})
@SessionApiParam
@ApiFileAcceptHeader(Base64File, QRCodeValue)
@UseInterceptors(new BufferResponseInterceptor())
@ApiFileAcceptHeader('image/png', Base64File, QRCodeValue)
@UseInterceptors(new BufferResponseInterceptor('image/png'))
async getQR(
@SessionParam session: WhatsappSession,
@QRCodeSessionParam session: WhatsappSession,
@Query() query: QRCodeQuery,
): Promise<Buffer | QRCodeValue> {
if (session.status != WAHASessionStatus.SCAN_QR_CODE) {
const err = `Can get QR code only in SCAN_QR_CODE status. The current status is '${session.status}'`;
throw new UnprocessableEntityException(err);
}
const qr = session.getQR();
if (query.format == QRCodeFormat.RAW) {
return { value: qr.raw };
@@ -67,42 +68,6 @@ class AuthController {
) {
return session.requestCode(request.phoneNumber, request.method, request);
}
@Post('authorize-code')
@SessionApiParam
@ApiOperation({
summary: 'Send OTP authentication code.',
})
authorizeCode(
@SessionParam session: WhatsappSession,
@Body() request: OTPRequest,
) {
return session.authorizeCode(request.code);
}
@Get('captcha')
@SessionApiParam
@ApiFileAcceptHeader()
@UseInterceptors(new BufferResponseInterceptor())
@ApiOperation({
summary: 'Get captcha image.',
})
async getCaptcha(@SessionParam session: WhatsappSession): Promise<Buffer> {
const image = await session.getCaptcha();
return image.get();
}
@Post('captcha')
@SessionApiParam
@ApiOperation({
summary: 'Enter captcha code.',
})
saveCaptcha(
@SessionParam session: WhatsappSession,
@Body() body: CaptchaBody,
) {
return session.saveCaptcha(body.code);
}
}
export { AuthController };
+42
View File
@@ -0,0 +1,42 @@
import {
Body,
Controller,
Post,
UseGuards,
UsePipes,
ValidationPipe,
} from '@nestjs/common';
import { ApiOperation, ApiSecurity, ApiTags } from '@nestjs/swagger';
import {
SessionApiParam,
WorkingSessionParam,
} from '@waha/nestjs/params/SessionApiParam';
import { SessionManager } from '../core/abc/manager.abc';
import { WhatsappSession } from '../core/abc/session.abc';
import { RejectCallRequest } from '../structures/calls.dto';
import { PoliciesGuard } from '@waha/core/auth/policies.guard';
import { CheckPolicies } from '@waha/core/auth/policies.decorator';
import { CanSession, FromParam } from '@waha/core/auth/policies';
import { Action } from '@waha/core/auth/casl.types';
@ApiSecurity('api_key')
@Controller('api/:session/calls')
@ApiTags('📞 Calls')
@UseGuards(PoliciesGuard)
@CheckPolicies(CanSession(Action.Use, FromParam('session')))
export class CallsController {
constructor(private manager: SessionManager) {}
@Post('reject')
@SessionApiParam
@ApiOperation({ summary: 'Reject incoming call' })
@UsePipes(new ValidationPipe({ transform: true, whitelist: true }))
rejectCall(
@WorkingSessionParam session: WhatsappSession,
@Body() request: RejectCallRequest,
) {
return session.rejectCall(request.from, request.id);
}
}
+131 -20
View File
@@ -3,37 +3,65 @@ import {
Controller,
Delete,
Get,
HttpCode,
HttpStatus,
Param,
Post,
Query,
UseGuards,
UsePipes,
} from '@nestjs/common';
import { ApiOperation, ApiSecurity, ApiTags } from '@nestjs/swagger';
import { ApiOperation, ApiParam, ApiSecurity, ApiTags } from '@nestjs/swagger';
import { ChannelsInfoServiceCore } from '@waha/core/services/ChannelsInfoServiceCore';
import { isJidNewsletter } from '@waha/core/utils/jids';
import {
SessionApiParam,
SessionParam,
WorkingSessionParam,
} from '@waha/nestjs/params/SessionApiParam';
import { WAHAValidationPipe } from '@waha/nestjs/pipes/WAHAValidationPipe';
import {
Channel,
ChannelCategory,
ChannelCountry,
ChannelListResult,
ChannelMessage,
ChannelSearchByText,
ChannelSearchByView,
ChannelView,
CreateChannelRequest,
ListChannelsQuery,
NewsletterIdApiParam,
NewsletterIdOrInviteCodeApiParam,
PreviewChannelMessages,
} from '@waha/structures/channels.dto';
import { SessionManager } from '../core/abc/manager.abc';
import { isNewsletter, WhatsappSession } from '../core/abc/session.abc';
import {
parseChannelInviteLink,
WhatsappSession,
} from '../core/abc/session.abc';
import { PoliciesGuard } from '@waha/core/auth/policies.guard';
import { CheckPolicies } from '@waha/core/auth/policies.decorator';
import { CanSession, FromParam } from '@waha/core/auth/policies';
import { Action } from '@waha/core/auth/casl.types';
@ApiSecurity('api_key')
@Controller('api/:session/channels')
@ApiTags('📢 Channels')
@UseGuards(PoliciesGuard)
@CheckPolicies(CanSession(Action.Use, FromParam('session')))
export class ChannelsController {
constructor(private manager: SessionManager) {}
constructor(
private manager: SessionManager,
private channelsInfoService: ChannelsInfoServiceCore,
) {}
@Get('')
@SessionApiParam
@ApiOperation({ summary: 'Get list of know channels' })
async list(
@SessionParam session: WhatsappSession,
@WorkingSessionParam session: WhatsappSession,
@Query() query: ListChannelsQuery,
): Promise<Channel[]> {
return session.channelsList(query);
@@ -43,7 +71,7 @@ export class ChannelsController {
@SessionApiParam
@ApiOperation({ summary: 'Create a new channel.' })
create(
@SessionParam session: WhatsappSession,
@WorkingSessionParam session: WhatsappSession,
@Body() request: CreateChannelRequest,
): Promise<Channel> {
return session.channelsCreateChannel(request);
@@ -53,7 +81,10 @@ export class ChannelsController {
@SessionApiParam
@NewsletterIdApiParam
@ApiOperation({ summary: 'Delete the channel.' })
delete(@SessionParam session: WhatsappSession, @Param('id') id: string) {
delete(
@WorkingSessionParam session: WhatsappSession,
@Param('id') id: string,
) {
return session.channelsDeleteChannel(id);
}
@@ -61,62 +92,142 @@ export class ChannelsController {
@SessionApiParam
@NewsletterIdOrInviteCodeApiParam
@ApiOperation({
summary: 'Get the channel info - either by id @newsletter OR invite code.',
summary: 'Get the channel info',
description:
'You can use either id (123@newsletter) ' +
'OR invite code (https://www.whatsapp.com/channel/123)',
})
get(
@SessionParam session: WhatsappSession,
@WorkingSessionParam session: WhatsappSession,
@Param('id') id: string,
): Promise<Channel> {
if (isNewsletter(id)) {
if (isJidNewsletter(id)) {
return session.channelsGetChannel(id);
} else {
// https://www.whatsapp.com/channel/123 => 123
const inviteCode = id.split('/').pop();
const inviteCode = parseChannelInviteLink(id);
return session.channelsGetChannelByInviteCode(inviteCode);
}
}
@Post('follow')
@Get(':id/messages/preview')
@SessionApiParam
@UsePipes(new WAHAValidationPipe())
@ApiParam({
name: 'id',
description: 'Channel id or invite code',
required: true,
type: 'string',
schema: {
default: '0029Va4K0PZ5a245NkngBA2M',
},
})
@ApiOperation({
summary: 'Preview channel messages',
description:
'You can use either ' +
'invite code (https://www.whatsapp.com/channel/123) or (123)' +
'OR' +
'Channel ID (123@newsletter).',
})
async previewChannelMessages(
@WorkingSessionParam session: WhatsappSession,
@Param('id') code: string,
@Query() query: PreviewChannelMessages,
): Promise<ChannelMessage[]> {
if (isJidNewsletter(code)) {
const channel = await session.channelsGetChannel(code);
code = parseChannelInviteLink(channel.invite);
}
const inviteCode = parseChannelInviteLink(code);
return session.previewChannelMessages(inviteCode, query);
}
@Post(':id/follow')
@SessionApiParam
@NewsletterIdApiParam
@ApiOperation({ summary: 'Follow the channel.' })
follow(
@SessionParam session: WhatsappSession,
@WorkingSessionParam session: WhatsappSession,
@Param('id') id: string,
): Promise<void> {
return session.channelsFollowChannel(id);
}
@Post('unfollow')
@Post(':id/unfollow')
@SessionApiParam
@NewsletterIdApiParam
@ApiOperation({ summary: 'Unfollow the channel.' })
unfollow(
@SessionParam session: WhatsappSession,
@WorkingSessionParam session: WhatsappSession,
@Param('id') id: string,
): Promise<void> {
return session.channelsUnfollowChannel(id);
}
@Post('mute')
@Post(':id/mute')
@SessionApiParam
@NewsletterIdApiParam
@ApiOperation({ summary: 'Mute the channel.' })
mute(
@SessionParam session: WhatsappSession,
@WorkingSessionParam session: WhatsappSession,
@Param('id') id: string,
): Promise<void> {
return session.channelsMuteChannel(id);
}
@Post('unmute')
@Post(':id/unmute')
@SessionApiParam
@NewsletterIdApiParam
@ApiOperation({ summary: 'Unmute the channel.' })
unmute(
@SessionParam session: WhatsappSession,
@WorkingSessionParam session: WhatsappSession,
@Param('id') id: string,
): Promise<void> {
return session.channelsUnmuteChannel(id);
}
@Post('/search/by-view')
@HttpCode(HttpStatus.OK)
@SessionApiParam
@UsePipes(new WAHAValidationPipe())
@ApiOperation({ summary: 'Search for channels (by view)' })
async searchByView(
@WorkingSessionParam session: WhatsappSession,
@Body() request: ChannelSearchByView,
): Promise<ChannelListResult> {
return session.searchChannelsByView(request);
}
@Post('/search/by-text')
@HttpCode(HttpStatus.OK)
@SessionApiParam
@UsePipes(new WAHAValidationPipe())
@ApiOperation({ summary: 'Search for channels (by text)' })
async searchByText(
@WorkingSessionParam session: WhatsappSession,
@Body() request: ChannelSearchByText,
): Promise<ChannelListResult> {
return session.searchChannelsByText(request);
}
@Get('/search/views')
@SessionApiParam
@ApiOperation({ summary: 'Get list of views for channel search' })
getSearchViews(): Promise<ChannelView[]> {
return this.channelsInfoService.getViews();
}
@Get('/search/countries')
@SessionApiParam
@ApiOperation({ summary: 'Get list of countries for channel search' })
getSearchCountries(): Promise<ChannelCountry[]> {
return this.channelsInfoService.getCountries();
}
@Get('/search/categories')
@SessionApiParam
@ApiOperation({ summary: 'Get list of categories for channel search' })
getSearchCategories(): Promise<ChannelCategory[]> {
return this.channelsInfoService.getCategories();
}
}
+160 -15
View File
@@ -3,10 +3,12 @@ import {
Controller,
Delete,
Get,
NotFoundException,
Param,
Post,
Put,
Query,
UseGuards,
UsePipes,
ValidationPipe,
} from '@nestjs/common';
@@ -15,19 +17,42 @@ import { ChatIdApiParam } from '@waha/nestjs/params/ChatIdApiParam';
import { MessageIdApiParam } from '@waha/nestjs/params/MessageIdApiParam';
import {
SessionApiParam,
SessionParam,
WorkingSessionParam,
} from '@waha/nestjs/params/SessionApiParam';
import { SessionManager } from '../core/abc/manager.abc';
import { WhatsappSession } from '../core/abc/session.abc';
import { parseBool } from '../helpers';
import { GetChatMessagesQuery, GetChatsQuery } from '../structures/chats.dto';
import {
ChatPictureQuery,
ChatPictureResponse,
ChatsPaginationParams,
ChatSummary,
GetChatMessageQuery,
GetChatMessagesFilter,
GetChatMessagesQuery,
MessageSortField,
OverviewBodyRequest,
OverviewFilter,
OverviewPaginationParams,
PinMessageRequest,
ReadChatMessagesQuery,
ReadChatMessagesResponse,
transformAck,
} from '../structures/chats.dto';
import { EditMessageRequest } from '../structures/chatting.dto';
import { SortOrder } from '@waha/structures/pagination.dto';
import { PoliciesGuard } from '@waha/core/auth/policies.guard';
import { CheckPolicies } from '@waha/core/auth/policies.decorator';
import { CanSession, FromParam } from '@waha/core/auth/policies';
import { Action } from '@waha/core/auth/casl.types';
@ApiSecurity('api_key')
@Controller('api/:session/chats')
@ApiTags('💬 Chats')
@UsePipes(new ValidationPipe({ transform: true }))
@UseGuards(PoliciesGuard)
@CheckPolicies(CanSession(Action.Use, FromParam('session')))
class ChatsController {
constructor(private manager: SessionManager) {}
@@ -35,39 +60,148 @@ class ChatsController {
@SessionApiParam
@ApiOperation({ summary: 'Get chats' })
getChats(
@SessionParam session: WhatsappSession,
@Query() query: GetChatsQuery,
@WorkingSessionParam session: WhatsappSession,
@Query() pagination: ChatsPaginationParams,
) {
return session.getChats(query);
return session.getChats(pagination);
}
@Get('overview')
@SessionApiParam
@ApiOperation({
summary:
'Get chats overview. Includes all necessary things to build UI "your chats overview" page - chat id, name, picture, last message. Sorting by last message timestamp',
})
@UsePipes(new ValidationPipe({ transform: true, whitelist: true }))
getChatsOverview(
@WorkingSessionParam session: WhatsappSession,
@Query() pagination: OverviewPaginationParams,
@Query() filter: OverviewFilter,
): Promise<ChatSummary[]> {
return session.getChatsOverview(pagination, filter);
}
@Post('overview')
@SessionApiParam
@ApiOperation({
summary:
'Get chats overview. Use POST if you have too many "ids" params - GET can limit it',
})
@UsePipes(new ValidationPipe({ transform: true, whitelist: true }))
postChatsOverview(
@WorkingSessionParam session: WhatsappSession,
@Body() body: OverviewBodyRequest,
): Promise<ChatSummary[]> {
return session.getChatsOverview(body.pagination, body.filter);
}
@Delete(':chatId')
@SessionApiParam
@ApiOperation({ summary: 'Deletes the chat' })
@ChatIdApiParam
deleteChat(
@SessionParam session: WhatsappSession,
@WorkingSessionParam session: WhatsappSession,
@Param('chatId') chatId: string,
) {
return session.deleteChat(chatId);
}
@Get(':chatId/picture')
@SessionApiParam
@ApiOperation({ summary: 'Gets chat picture' })
@UsePipes(new ValidationPipe({ transform: true, whitelist: true }))
async getChatPicture(
@WorkingSessionParam session: WhatsappSession,
@Param('chatId') chatId: string,
@Query() query: ChatPictureQuery,
): Promise<ChatPictureResponse> {
const url = await session.getContactProfilePicture(chatId, query.refresh);
return { url: url };
}
@Get(':chatId/messages')
@SessionApiParam
@ApiOperation({ summary: 'Gets messages in the chat' })
@ChatIdApiParam
@UsePipes(new ValidationPipe({ transform: true, whitelist: true }))
getChatMessages(
@Query() query: GetChatMessagesQuery,
@SessionParam session: WhatsappSession,
@Query() filter: GetChatMessagesFilter,
@WorkingSessionParam session: WhatsappSession,
@Param('chatId') chatId: string,
) {
const downloadMedia = parseBool(query.downloadMedia);
return session.getChatMessages(chatId, query.limit, downloadMedia);
if (query.sortBy == MessageSortField.MESSAGE_TIMESTAMP) {
query.sortBy = MessageSortField.TIMESTAMP;
}
query.sortBy = query.sortBy || MessageSortField.TIMESTAMP;
query.sortOrder = query.sortOrder || SortOrder.DESC;
filter = transformAck(filter);
return session.getChatMessages(chatId, query, filter);
}
@Post(':chatId/messages/read')
@SessionApiParam
@ApiOperation({ summary: 'Read unread messages in the chat' })
@ChatIdApiParam
@UsePipes(new ValidationPipe({ transform: true, whitelist: true }))
readChatMessages(
@Query() query: ReadChatMessagesQuery,
@WorkingSessionParam session: WhatsappSession,
@Param('chatId') chatId: string,
): Promise<ReadChatMessagesResponse> {
return session.readChatMessages(chatId, query);
}
@Get(':chatId/messages/:messageId')
@SessionApiParam
@ApiOperation({ summary: 'Gets message by id' })
@ChatIdApiParam
async getChatMessage(
@Query() query: GetChatMessageQuery,
@WorkingSessionParam session: WhatsappSession,
@Param('chatId') chatId: string,
@Param('messageId') messageId: string,
) {
const message = await session.getChatMessage(chatId, messageId, query);
if (!message) {
throw new NotFoundException('Message not found');
}
return message;
}
@Post(':chatId/messages/:messageId/pin')
@SessionApiParam
@ApiOperation({ summary: 'Pins a message in the chat' })
@ChatIdApiParam
async pinMessage(
@WorkingSessionParam session: WhatsappSession,
@Param('chatId') chatId: string,
@Param('messageId') messageId: string,
@Body() body: PinMessageRequest,
) {
const result = await session.pinMessage(chatId, messageId, body.duration);
return { success: result };
}
@Post(':chatId/messages/:messageId/unpin')
@SessionApiParam
@ApiOperation({ summary: 'Unpins a message in the chat' })
@ChatIdApiParam
async unpinMessage(
@WorkingSessionParam session: WhatsappSession,
@Param('chatId') chatId: string,
@Param('messageId') messageId: string,
) {
const result = await session.unpinMessage(chatId, messageId);
return { success: result };
}
@Delete(':chatId/messages')
@SessionApiParam
@ApiOperation({ summary: 'Clears all messages from the chat' })
@ChatIdApiParam
clearMessages(
@SessionParam session: WhatsappSession,
@WorkingSessionParam session: WhatsappSession,
@Param('chatId') chatId: string,
) {
return session.clearMessages(chatId);
@@ -79,7 +213,7 @@ class ChatsController {
@MessageIdApiParam
@ApiOperation({ summary: 'Deletes a message from the chat' })
deleteMessage(
@SessionParam session: WhatsappSession,
@WorkingSessionParam session: WhatsappSession,
@Param('chatId') chatId: string,
@Param('messageId') messageId: string,
) {
@@ -92,7 +226,7 @@ class ChatsController {
@MessageIdApiParam
@ApiOperation({ summary: 'Edits a message in the chat' })
editMessage(
@SessionParam session: WhatsappSession,
@WorkingSessionParam session: WhatsappSession,
@Param('chatId') chatId: string,
@Param('messageId') messageId: string,
@Body() body: EditMessageRequest,
@@ -105,7 +239,7 @@ class ChatsController {
@ChatIdApiParam
@ApiOperation({ summary: 'Archive the chat' })
archiveChat(
@SessionParam session: WhatsappSession,
@WorkingSessionParam session: WhatsappSession,
@Param('chatId') chatId: string,
) {
return session.chatsArchiveChat(chatId);
@@ -116,11 +250,22 @@ class ChatsController {
@ChatIdApiParam
@ApiOperation({ summary: 'Unarchive the chat' })
unarchiveChat(
@SessionParam session: WhatsappSession,
@WorkingSessionParam session: WhatsappSession,
@Param('chatId') chatId: string,
) {
return session.chatsUnarchiveChat(chatId);
}
@Post(':chatId/unread')
@SessionApiParam
@ChatIdApiParam
@ApiOperation({ summary: 'Unread the chat' })
unreadChat(
@WorkingSessionParam session: WhatsappSession,
@Param('chatId') chatId: string,
) {
return session.chatsUnreadChat(chatId);
}
}
export { ChatsController };
+242 -71
View File
@@ -1,17 +1,39 @@
import { Body, Controller, Get, Post, Put, Query } from '@nestjs/common';
import {
Body,
Controller,
Get,
Post,
Put,
Query,
UseGuards,
UsePipes,
ValidationPipe,
} from '@nestjs/common';
import { ApiOperation, ApiSecurity, ApiTags } from '@nestjs/swagger';
import { WAHAValidationPipe } from '@waha/nestjs/pipes/WAHAValidationPipe';
import {
GetChatMessagesFilter,
ReadChatMessagesQuery,
transformAck,
} from '@waha/structures/chats.dto';
import { SendButtonsRequest } from '@waha/structures/chatting.buttons.dto';
import { SendListRequest } from '@waha/structures/chatting.list.dto';
import { SessionManager } from '../core/abc/manager.abc';
import {
ChatRequest,
CheckNumberStatusQuery,
GetMessageQuery,
MessageButtonReply,
MessageContactVcardRequest,
MessageFileRequest,
MessageForwardRequest,
MessageImageRequest,
MessageLinkCustomPreviewRequest,
MessageLinkPreviewRequest,
MessageLocationRequest,
MessagePollRequest,
MessagePollVoteRequest,
MessageReactionRequest,
MessageReplyRequest,
MessageStarRequest,
@@ -23,157 +45,306 @@ import {
WANumberExistResult,
} from '../structures/chatting.dto';
import { WAMessage } from '../structures/responses.dto';
import {
mentionsAll,
validateRequestMentions,
} from '@waha/core/utils/mentions.all';
import { PoliciesGuard } from '@waha/core/auth/policies.guard';
import { CheckPolicies } from '@waha/core/auth/policies.decorator';
import { CanSession, FromBody, FromQuery } from '@waha/core/auth/policies';
import { Action } from '@waha/core/auth/casl.types';
@ApiSecurity('api_key')
@Controller('api')
@ApiTags('📤 Chatting')
@UseGuards(PoliciesGuard)
export class ChattingController {
constructor(private manager: SessionManager) {}
@Get('/checkNumberStatus')
@ApiOperation({ summary: 'Check number status' })
async checkNumberStatus(
@Query() request: CheckNumberStatusQuery,
): Promise<WANumberExistResult> {
const whatsapp = this.manager.getSession(request.session);
return whatsapp.checkNumberStatus(request);
}
@Post('/sendContactVcard')
sendContactVcard(@Body() request: MessageContactVcardRequest) {
const whatsapp = this.manager.getSession(request.session);
return whatsapp.sendContactVCard(request);
}
@Post('/sendText')
@ApiOperation({ summary: 'Send a text message' })
sendText(@Body() request: MessageTextRequest): Promise<WAMessage> {
const whatsapp = this.manager.getSession(request.session);
@CheckPolicies(CanSession(Action.Use, FromBody('session')))
async sendText(@Body() request: MessageTextRequest): Promise<WAMessage> {
const whatsapp = await this.manager.getWorkingSession(request.session);
if (mentionsAll(request)) {
validateRequestMentions(request);
request.mentions = await whatsapp.resolveMentionsAll(request.chatId);
}
return whatsapp.sendText(request);
}
@Post('/sendPoll')
@ApiOperation({
summary: 'Send a poll with options. You can use it as buttons replace',
})
sendPoll(@Body() request: MessagePollRequest) {
const whatsapp = this.manager.getSession(request.session);
return whatsapp.sendPoll(request);
}
@Post('/sendLocation')
sendLocation(@Body() request: MessageLocationRequest) {
const whatsapp = this.manager.getSession(request.session);
return whatsapp.sendLocation(request);
}
@Post('/sendLinkPreview')
sendLinkPreview(@Body() request: MessageLinkPreviewRequest) {
const whatsapp = this.manager.getSession(request.session);
return whatsapp.sendLinkPreview(request);
}
@Post('/sendImage')
@ApiOperation({
summary:
'Send an image. Either from an URL or base64 data - look at the request schemas for details.',
summary: 'Send an image',
description:
'Either from an URL or base64 data - look at the request schemas for details.',
})
sendImage(@Body() request: MessageImageRequest) {
const whatsapp = this.manager.getSession(request.session);
@CheckPolicies(CanSession(Action.Use, FromBody('session')))
async sendImage(@Body() request: MessageImageRequest) {
const whatsapp = await this.manager.getWorkingSession(request.session);
if (mentionsAll(request)) {
validateRequestMentions(request);
request.mentions = await whatsapp.resolveMentionsAll(request.chatId);
}
return whatsapp.sendImage(request);
}
@Post('/sendFile')
@ApiOperation({
summary:
'Send a file. Either from an URL or base64 data - look at the request schemas for details.',
summary: 'Send a file',
description:
'Either from an URL or base64 data - look at the request schemas for details.',
})
sendFile(@Body() request: MessageFileRequest) {
const whatsapp = this.manager.getSession(request.session);
@CheckPolicies(CanSession(Action.Use, FromBody('session')))
async sendFile(@Body() request: MessageFileRequest) {
const whatsapp = await this.manager.getWorkingSession(request.session);
if (mentionsAll(request)) {
validateRequestMentions(request);
request.mentions = await whatsapp.resolveMentionsAll(request.chatId);
}
return whatsapp.sendFile(request);
}
@Post('/sendVoice')
@ApiOperation({
summary:
'Send an voice message. Either from an URL or base64 data - look at the request schemas for details.',
summary: 'Send an voice message',
description:
'Either from an URL or base64 data - look at the request schemas for details.',
})
sendVoice(@Body() request: MessageVoiceRequest) {
const whatsapp = this.manager.getSession(request.session);
@CheckPolicies(CanSession(Action.Use, FromBody('session')))
async sendVoice(@Body() request: MessageVoiceRequest) {
const whatsapp = await this.manager.getWorkingSession(request.session);
return whatsapp.sendVoice(request);
}
@Post('/sendVideo')
@ApiOperation({
summary:
'Send a video. Either from an URL or base64 data - look at the request schemas for details.',
summary: 'Send a video',
description:
'Either from an URL or base64 data - look at the request schemas for details.',
})
sendVideo(@Body() request: MessageVideoRequest) {
const whatsapp = this.manager.getSession(request.session);
@CheckPolicies(CanSession(Action.Use, FromBody('session')))
async sendVideo(@Body() request: MessageVideoRequest) {
const whatsapp = await this.manager.getWorkingSession(request.session);
if (mentionsAll(request)) {
validateRequestMentions(request);
request.mentions = await whatsapp.resolveMentionsAll(request.chatId);
}
return whatsapp.sendVideo(request);
}
@Post('/send/link-custom-preview')
@ApiOperation({
summary: 'Send a text message with a CUSTOM link preview.',
description:
'You can use regular /api/sendText if you wanna send auto-generated link preview.',
})
@CheckPolicies(CanSession(Action.Use, FromBody('session')))
@UsePipes(new WAHAValidationPipe())
async sendLinkCustomPreview(
@Body() request: MessageLinkCustomPreviewRequest,
): Promise<any> {
const whatsapp = await this.manager.getWorkingSession(request.session);
if (!request.text.includes(request.preview.url)) {
throw new Error(
'"text" must include the URL provided in the "preview.url"',
);
}
return whatsapp.sendLinkCustomPreview(request);
}
@Post('/sendButtons')
@ApiOperation({
summary: 'Send buttons message (interactive)',
description: 'Send Buttons',
deprecated: true,
})
@CheckPolicies(CanSession(Action.Use, FromBody('session')))
@UsePipes(new WAHAValidationPipe())
async sendButtons(@Body() request: SendButtonsRequest) {
const whatsapp = await this.manager.getWorkingSession(request.session);
return whatsapp.sendButtons(request);
}
@Post('/sendList')
@ApiOperation({
summary: 'Send a list message (interactive)',
description: 'Send a List message with sections and rows',
})
@CheckPolicies(CanSession(Action.Use, FromBody('session')))
@UsePipes(new WAHAValidationPipe())
async sendList(@Body() request: SendListRequest) {
const whatsapp = await this.manager.getWorkingSession(request.session);
return whatsapp.sendList(request);
}
@Post('/forwardMessage')
@CheckPolicies(CanSession(Action.Use, FromBody('session')))
async forwardMessage(
@Body() request: MessageForwardRequest,
): Promise<WAMessage> {
const whatsapp = await this.manager.getWorkingSession(request.session);
return await whatsapp.forwardMessage(request);
}
@Post('/sendSeen')
sendSeen(@Body() chat: SendSeenRequest) {
const whatsapp = this.manager.getSession(chat.session);
@CheckPolicies(CanSession(Action.Use, FromBody('session')))
async sendSeen(@Body() chat: SendSeenRequest) {
const hasMessageId = chat.messageIds?.length > 0 || Boolean(chat.messageId);
if (!hasMessageId) {
const whatsapp = await this.manager.getWorkingSession(chat.session);
const query: ReadChatMessagesQuery = {
messages: null,
days: 7,
};
return whatsapp.readChatMessages(chat.chatId, query);
}
const whatsapp = await this.manager.getWorkingSession(chat.session);
return whatsapp.sendSeen(chat);
}
@Post('/startTyping')
@CheckPolicies(CanSession(Action.Use, FromBody('session')))
async startTyping(@Body() chat: ChatRequest) {
// It's infinitive action
const whatsapp = this.manager.getSession(chat.session);
const whatsapp = await this.manager.getWorkingSession(chat.session);
await whatsapp.startTyping(chat);
return { result: true };
}
@Post('/stopTyping')
@CheckPolicies(CanSession(Action.Use, FromBody('session')))
async stopTyping(@Body() chat: ChatRequest) {
const whatsapp = this.manager.getSession(chat.session);
const whatsapp = await this.manager.getWorkingSession(chat.session);
await whatsapp.stopTyping(chat);
return { result: true };
}
@Put('/reaction')
@ApiOperation({ summary: 'React to a message with an emoji' })
setReaction(@Body() request: MessageReactionRequest) {
const whatsapp = this.manager.getSession(request.session);
@CheckPolicies(CanSession(Action.Use, FromBody('session')))
async setReaction(@Body() request: MessageReactionRequest) {
const whatsapp = await this.manager.getWorkingSession(request.session);
return whatsapp.setReaction(request);
}
@Put('/star')
@ApiOperation({ summary: 'Star or unstar a message' })
@CheckPolicies(CanSession(Action.Use, FromBody('session')))
async setStar(@Body() request: MessageStarRequest) {
const whatsapp = this.manager.getSession(request.session);
const whatsapp = await this.manager.getWorkingSession(request.session);
await whatsapp.setStar(request);
return;
}
@Get('/messages')
@ApiOperation({ summary: 'Get messages in a chat' })
getMessages(@Query() query: GetMessageQuery) {
const whatsapp = this.manager.getSession(query.session);
return whatsapp.getMessages(query);
@Post('/sendPoll')
@ApiOperation({
summary: 'Send a poll with options',
description: 'You can use it as buttons or list replacement',
})
@CheckPolicies(CanSession(Action.Use, FromBody('session')))
async sendPoll(@Body() request: MessagePollRequest) {
const whatsapp = await this.manager.getWorkingSession(request.session);
return whatsapp.sendPoll(request);
}
@Post('/sendPollVote')
@ApiOperation({
summary: 'Vote on a poll',
description: 'Cast vote(s) on an existing poll message',
})
@CheckPolicies(CanSession(Action.Use, FromBody('session')))
@UsePipes(new WAHAValidationPipe())
async sendPollVote(@Body() request: MessagePollVoteRequest) {
const whatsapp = await this.manager.getWorkingSession(request.session);
return whatsapp.sendPollVote(request);
}
@Post('/sendLocation')
@CheckPolicies(CanSession(Action.Use, FromBody('session')))
async sendLocation(@Body() request: MessageLocationRequest) {
const whatsapp = await this.manager.getWorkingSession(request.session);
return whatsapp.sendLocation(request);
}
@Post('/sendContactVcard')
@CheckPolicies(CanSession(Action.Use, FromBody('session')))
async sendContactVcard(@Body() request: MessageContactVcardRequest) {
const whatsapp = await this.manager.getWorkingSession(request.session);
return whatsapp.sendContactVCard(request);
}
@Post('/send/buttons/reply')
@ApiOperation({
summary: 'Reply on a button message',
})
@CheckPolicies(CanSession(Action.Use, FromBody('session')))
@UsePipes(new ValidationPipe({ transform: true, whitelist: true }))
async sendButtonsReply(@Body() request: MessageButtonReply) {
const whatsapp = await this.manager.getWorkingSession(request.session);
return whatsapp.sendButtonsReply(request);
}
@Get('/sendText')
@ApiOperation({ summary: 'Send a text message', deprecated: true })
sendTextGet(@Query() query: MessageTextQuery) {
const whatsapp = this.manager.getSession(query.session);
@CheckPolicies(CanSession(Action.Use, FromQuery('session')))
async sendTextGet(@Query() query: MessageTextQuery) {
const whatsapp = await this.manager.getWorkingSession(query.session);
const msg = new MessageTextRequest();
msg.chatId = query.phone;
msg.text = query.text;
return whatsapp.sendText(msg);
}
@Get('/messages')
@ApiOperation({
summary: 'Get messages in a chat',
description: 'DEPRECATED. Use "GET /api/chats/{id}/messages" instead',
deprecated: true,
})
@CheckPolicies(CanSession(Action.Use, FromQuery('session')))
@UsePipes(new ValidationPipe({ transform: true, whitelist: true }))
async getMessages(
@Query() query: GetMessageQuery,
@Query() filter: GetChatMessagesFilter,
) {
filter = transformAck(filter);
const whatsapp = await this.manager.getWorkingSession(query.session);
return whatsapp.getChatMessages(query.chatId, query, filter);
}
@Get('/checkNumberStatus')
@ApiOperation({
summary: 'Check number status',
description: 'DEPRECATED. Use "POST /contacts/check-exists" instead',
deprecated: true,
})
@CheckPolicies(CanSession(Action.Use, FromQuery('session')))
async DEPRECATED_checkNumberStatus(
@Query() request: CheckNumberStatusQuery,
): Promise<WANumberExistResult> {
const whatsapp = await this.manager.getWorkingSession(request.session);
return whatsapp.checkNumberStatus(request);
}
@Post('/reply')
@ApiOperation({
summary:
'DEPRECATED - you can set "reply_to" field when sending text, image, etc',
deprecated: true,
})
reply(@Body() request: MessageReplyRequest) {
const whatsapp = this.manager.getSession(request.session);
@CheckPolicies(CanSession(Action.Use, FromBody('session')))
async reply(@Body() request: MessageReplyRequest) {
const whatsapp = await this.manager.getWorkingSession(request.session);
return whatsapp.reply(request);
}
@Post('/sendLinkPreview')
@ApiOperation({ deprecated: true })
@CheckPolicies(CanSession(Action.Use, FromBody('session')))
async sendLinkPreview_DEPRECATED(@Body() request: MessageLinkPreviewRequest) {
const whatsapp = await this.manager.getWorkingSession(request.session);
return whatsapp.sendLinkPreview(request);
}
}
+68 -29
View File
@@ -1,4 +1,13 @@
import { Body, Controller, Get, Post, Query } from '@nestjs/common';
import {
Body,
Controller,
Get,
Post,
Query,
UseGuards,
UsePipes,
ValidationPipe,
} from '@nestjs/common';
import { ApiOperation, ApiSecurity, ApiTags } from '@nestjs/swagger';
import { SessionManager } from '../core/abc/manager.abc';
@@ -7,71 +16,101 @@ import {
CheckNumberStatusQuery,
WANumberExistResult,
} from '../structures/chatting.dto';
import { ContactQuery, ContactRequest } from '../structures/contacts.dto';
import {
ContactProfilePictureQuery,
ContactQuery,
ContactRequest,
ContactsPaginationParams,
} from '../structures/contacts.dto';
import { PoliciesGuard } from '@waha/core/auth/policies.guard';
import { CheckPolicies } from '@waha/core/auth/policies.decorator';
import { CanSession, FromBody, FromQuery } from '@waha/core/auth/policies';
import { Action } from '@waha/core/auth/casl.types';
@ApiSecurity('api_key')
@Controller('api/contacts')
@ApiTags('👤 Contacts')
@UseGuards(PoliciesGuard)
export class ContactsController {
constructor(private manager: SessionManager) {}
@Get('/')
@ApiOperation({
summary:
'Get contact basic info. The method always return result, even if the phone number is not registered in WhatsApp. For that - use /check-exists endpoint below.',
})
get(@Query() query: ContactQuery) {
const whatsapp = this.manager.getSession(query.session);
return whatsapp.getContact(query);
}
@Get('/all')
@ApiOperation({ summary: 'Get all contacts' })
getAll(@Query() query: SessionQuery) {
const whatsapp = this.manager.getSession(query.session);
return whatsapp.getContacts();
@CheckPolicies(CanSession(Action.Use, FromQuery('session')))
@UsePipes(new ValidationPipe({ transform: true, whitelist: true }))
async getAll(
@Query() query: SessionQuery,
@Query() pagination: ContactsPaginationParams,
) {
const whatsapp = await this.manager.getWorkingSession(query.session);
return whatsapp.getContacts(pagination);
}
@Get('/')
@ApiOperation({
summary: 'Get contact basic info',
description:
'The method always return result, even if the phone number is not registered in WhatsApp. For that - use /contacts/check-exists endpoint below.',
})
@CheckPolicies(CanSession(Action.Use, FromQuery('session')))
async get(@Query() query: ContactQuery) {
const whatsapp = await this.manager.getWorkingSession(query.session);
return whatsapp.getContact(query);
}
@Get('/check-exists')
@ApiOperation({ summary: 'Check phone number is registered in WhatsApp.' })
@CheckPolicies(CanSession(Action.Use, FromQuery('session')))
async checkExists(
@Query() request: CheckNumberStatusQuery,
): Promise<WANumberExistResult> {
const whatsapp = this.manager.getSession(request.session);
const whatsapp = await this.manager.getWorkingSession(request.session);
return whatsapp.checkNumberStatus(request);
}
@Get('/about')
@ApiOperation({
summary:
'Gets the Contact\'s current "about" info. Returns null if you don\'t have permission to read their status.',
summary: 'Gets the Contact\'s "about" info',
description:
'Returns null if you do not have permission to read their status.',
})
getAbout(@Query() query: ContactQuery) {
const whatsapp = this.manager.getSession(query.session);
@CheckPolicies(CanSession(Action.Use, FromQuery('session')))
async getAbout(@Query() query: ContactQuery) {
const whatsapp = await this.manager.getWorkingSession(query.session);
return whatsapp.getContactAbout(query);
}
@Get('/profile-picture')
@ApiOperation({
summary:
"Returns the contact's profile picture URL, if privacy settings allow it.",
summary: "Get contact's profile picture URL",
description:
'If privacy settings do not allow to get the picture, the method will return null.',
})
getProfilePicture(@Query() query: ContactQuery) {
const whatsapp = this.manager.getSession(query.session);
return whatsapp.getContactProfilePicture(query);
@CheckPolicies(CanSession(Action.Use, FromQuery('session')))
@UsePipes(new ValidationPipe({ transform: true, whitelist: true }))
async getProfilePicture(@Query() query: ContactProfilePictureQuery) {
const whatsapp = await this.manager.getWorkingSession(query.session);
const url = await whatsapp.getContactProfilePicture(
query.contactId,
query.refresh,
);
return { profilePictureURL: url };
}
@Post('/block')
@ApiOperation({ summary: 'Block contact' })
block(@Body() request: ContactRequest) {
const whatsapp = this.manager.getSession(request.session);
@CheckPolicies(CanSession(Action.Use, FromBody('session')))
async block(@Body() request: ContactRequest) {
const whatsapp = await this.manager.getWorkingSession(request.session);
return whatsapp.blockContact(request);
}
@Post('/unblock')
@ApiOperation({ summary: 'Unblock contact' })
unblock(@Body() request: ContactRequest) {
const whatsapp = this.manager.getSession(request.session);
@CheckPolicies(CanSession(Action.Use, FromBody('session')))
async unblock(@Body() request: ContactRequest) {
const whatsapp = await this.manager.getWorkingSession(request.session);
return whatsapp.unblockContact(request);
}
}
+51
View File
@@ -0,0 +1,51 @@
import {
Body,
Controller,
Param,
Put,
UseGuards,
UsePipes,
} from '@nestjs/common';
import { ApiOperation, ApiSecurity, ApiTags } from '@nestjs/swagger';
import { PoliciesGuard } from '@waha/core/auth/policies.guard';
import { CheckPolicies } from '@waha/core/auth/policies.decorator';
import { CanSession, FromParam } from '@waha/core/auth/policies';
import { WhatsappSession } from '@waha/core/abc/session.abc';
import { ChatIdApiParam } from '@waha/nestjs/params/ChatIdApiParam';
import {
SessionApiParam,
WorkingSessionParam,
} from '@waha/nestjs/params/SessionApiParam';
import { WAHAValidationPipe } from '@waha/nestjs/pipes/WAHAValidationPipe';
import { SessionManager } from '../core/abc/manager.abc';
import { Result } from '../structures/base.dto';
import { ContactUpdateBody } from '../structures/contacts.dto';
import { Action } from '@waha/core/auth/casl.types';
@ApiSecurity('api_key')
@Controller('api/:session/contacts')
@ApiTags('👤 Contacts')
@UseGuards(PoliciesGuard)
@CheckPolicies(CanSession(Action.Use, FromParam('session')))
export class ContactsSessionController {
constructor(private manager: SessionManager) {}
@Put('/:chatId')
@SessionApiParam
@ChatIdApiParam
@ApiOperation({
summary: 'Create or update contact',
description:
'Create or update contact on the phone address book. May not work if you have installed many WhatsApp apps on the same phone',
})
@UsePipes(new WAHAValidationPipe())
async put(
@WorkingSessionParam session: WhatsappSession,
@Param('chatId') chatId: string,
@Body() body: ContactUpdateBody,
): Promise<Result> {
await session.upsertContact(chatId, body);
return { success: true };
}
}
+58
View File
@@ -0,0 +1,58 @@
import {
Body,
Controller,
Param,
Post,
UseGuards,
UsePipes,
ValidationPipe,
} from '@nestjs/common';
import { ApiOperation, ApiSecurity, ApiTags } from '@nestjs/swagger';
import { WhatsappSession } from '@waha/core/abc/session.abc';
import {
SessionApiParam,
WorkingSessionParam,
} from '@waha/nestjs/params/SessionApiParam';
import { SessionManager } from '../core/abc/manager.abc';
import {
EventCancelRequest,
EventMessageRequest,
} from '../structures/events.dto';
import { WAMessage } from '../structures/responses.dto';
import { PoliciesGuard } from '@waha/core/auth/policies.guard';
import { CheckPolicies } from '@waha/core/auth/policies.decorator';
import { CanSession, FromParam } from '@waha/core/auth/policies';
import { Action } from '@waha/core/auth/casl.types';
@ApiSecurity('api_key')
@Controller('api/:session/events')
@ApiTags('📅 Events')
@UseGuards(PoliciesGuard)
@CheckPolicies(CanSession(Action.Use, FromParam('session')))
export class EventsController {
constructor(private manager: SessionManager) {}
@Post()
@ApiOperation({ summary: 'Send an event message' })
@UsePipes(new ValidationPipe())
@SessionApiParam
async sendEvent(
@WorkingSessionParam session: WhatsappSession,
@Body() request: EventMessageRequest,
): Promise<WAMessage> {
return session.sendEvent(request);
}
// @Post(':id/cancel')
// @ApiOperation({ summary: 'Cancel an event by ID' })
// @UsePipes(new ValidationPipe())
// @SessionApiParam
// async cancelEvent(
// @WorkingSessionParam session: WhatsappSession,
// @Param('id') id: string,
// ): Promise<WAMessage> {
// return session.cancelEvent(id);
// }
}
+247 -82
View File
@@ -3,29 +3,55 @@ import {
Controller,
Delete,
Get,
HttpCode,
HttpStatus,
Param,
Post,
Put,
Query,
UseGuards,
UsePipes,
ValidationPipe,
} from '@nestjs/common';
import { ApiOperation, ApiSecurity, ApiTags } from '@nestjs/swagger';
import { GroupIdApiParam } from '@waha/nestjs/params/ChatIdApiParam';
import {
SessionApiParam,
SessionParam,
WorkingSessionParam,
} from '@waha/nestjs/params/SessionApiParam';
import { CountResponse, Result } from '@waha/structures/base.dto';
import {
ChatPictureQuery,
ChatPictureResponse,
} from '@waha/structures/chats.dto';
import { ProfilePictureRequest } from '@waha/structures/profile.dto';
import { SessionManager } from '../core/abc/manager.abc';
import { WhatsappSession } from '../core/abc/session.abc';
import { parseGroupInviteLink, WhatsappSession } from '../core/abc/session.abc';
import {
CreateGroupRequest,
DescriptionRequest,
GroupField,
GroupParticipant,
GroupsListFields,
GroupsPaginationParams,
JoinGroupRequest,
JoinGroupResponse,
ParticipantsRequest,
SettingsSecurityChangeInfo,
SubjectRequest,
} from '../structures/groups.dto';
import { PoliciesGuard } from '@waha/core/auth/policies.guard';
import { CheckPolicies } from '@waha/core/auth/policies.decorator';
import { CanSession, FromParam } from '@waha/core/auth/policies';
import { Action } from '@waha/core/auth/casl.types';
@ApiSecurity('api_key')
@Controller('api/:session/groups')
@ApiTags('👥 Groups')
@UseGuards(PoliciesGuard)
@CheckPolicies(CanSession(Action.Use, FromParam('session')))
export class GroupsController {
constructor(private manager: SessionManager) {}
@@ -33,102 +59,156 @@ export class GroupsController {
@SessionApiParam
@ApiOperation({ summary: 'Create a new group.' })
createGroup(
@SessionParam session: WhatsappSession,
@WorkingSessionParam session: WhatsappSession,
@Body() request: CreateGroupRequest,
) {
return session.createGroup(request);
}
@Get('join-info')
@SessionApiParam
@ApiOperation({ summary: 'Get info about the group before joining.' })
async joinInfoGroup(
@WorkingSessionParam session: WhatsappSession,
@Query() query: JoinGroupRequest,
): Promise<any> {
const code = parseGroupInviteLink(query.code);
return session.joinInfoGroup(code);
}
@Post('join')
@HttpCode(HttpStatus.OK)
@SessionApiParam
@ApiOperation({ summary: 'Join group via code' })
async joinGroup(
@WorkingSessionParam session: WhatsappSession,
@Body() request: JoinGroupRequest,
): Promise<JoinGroupResponse> {
const code = parseGroupInviteLink(request.code);
const id = await session.joinGroup(code);
return { id: id };
}
@Get('')
@SessionApiParam
@ApiOperation({ summary: 'Get all groups.' })
getGroups(@SessionParam session: WhatsappSession) {
return session.getGroups();
@UsePipes(new ValidationPipe({ transform: true, whitelist: true }))
async getGroups(
@WorkingSessionParam session: WhatsappSession,
@Query() pagination: GroupsPaginationParams,
@Query() fields: GroupsListFields,
) {
let groups: any = await session.getGroups(pagination);
groups = session.filterGroupsFields(groups, fields);
return groups;
}
@Get('/count')
@SessionApiParam
@ApiOperation({ summary: 'Get the number of groups.' })
@UsePipes(new ValidationPipe({ transform: true, whitelist: true }))
async getGroupsCount(
@WorkingSessionParam session: WhatsappSession,
): Promise<CountResponse> {
const data: any = await session.getGroups({});
const groups: any[] = Array.isArray(data) ? data : Object.values(data);
return {
count: groups.length,
};
}
@Post('refresh')
@HttpCode(HttpStatus.OK)
@SessionApiParam
@ApiOperation({ summary: 'Refresh groups from the server.' })
@UsePipes(new ValidationPipe({ transform: true, whitelist: true }))
async refreshGroups(@WorkingSessionParam session: WhatsappSession) {
return { success: await session.refreshGroups() };
}
@Get(':id')
@GroupIdApiParam
@SessionApiParam
@ApiOperation({ summary: 'Get the group.' })
getGroup(@SessionParam session: WhatsappSession, @Param('id') id: string) {
getGroup(
@WorkingSessionParam session: WhatsappSession,
@Param('id') id: string,
) {
return session.getGroup(id);
}
@Put(':id/settings/security/info-admin-only')
@SessionApiParam
@ApiOperation({
summary:
'Updates the group settings to only allow admins to edit group info (title, description, photo).',
})
setInfoAdminOnly(
@SessionParam session: WhatsappSession,
@Param('id') id: string,
@Body() request: SettingsSecurityChangeInfo,
) {
return session.setInfoAdminsOnly(id, request.adminsOnly);
}
@Get(':id/settings/security/info-admin-only')
@SessionApiParam
@ApiOperation({
summary:
'Gets the group settings to only allow admins to edit group info (title, description, photo).',
})
getInfoAdminOnly(
@SessionParam session: WhatsappSession,
@Param('id') id: string,
): Promise<SettingsSecurityChangeInfo> {
return session.getInfoAdminsOnly(id);
}
@Put(':id/settings/security/messages-admin-only')
@SessionApiParam
@ApiOperation({
summary:
'Updates the group settings to only allow admins to send messages.',
})
setMessagesAdminOnly(
@SessionParam session: WhatsappSession,
@Param('id') id: string,
@Body() request: SettingsSecurityChangeInfo,
) {
return session.setMessagesAdminsOnly(id, request.adminsOnly);
}
@Get(':id/settings/security/messages-admin-only')
@SessionApiParam
@ApiOperation({
summary: 'Gets the group settings to only allow admins to send messages.',
})
getMessagesAdminOnly(
@SessionParam session: WhatsappSession,
@Param('id') id: string,
): Promise<SettingsSecurityChangeInfo> {
return session.getMessagesAdminsOnly(id);
}
@Delete(':id')
@SessionApiParam
@GroupIdApiParam
@ApiOperation({ summary: 'Delete the group.' })
deleteGroup(@SessionParam session: WhatsappSession, @Param('id') id: string) {
deleteGroup(
@WorkingSessionParam session: WhatsappSession,
@Param('id') id: string,
) {
return session.deleteGroup(id);
}
@Post(':id/leave')
@HttpCode(HttpStatus.OK)
@SessionApiParam
@GroupIdApiParam
@ApiOperation({ summary: 'Leave the group.' })
leaveGroup(@SessionParam session: WhatsappSession, @Param('id') id: string) {
leaveGroup(
@WorkingSessionParam session: WhatsappSession,
@Param('id') id: string,
) {
return session.leaveGroup(id);
}
@Get(':id/picture')
@SessionApiParam
@GroupIdApiParam
@ApiOperation({ summary: 'Get group picture' })
@UsePipes(new ValidationPipe({ transform: true, whitelist: true }))
async getChatPicture(
@WorkingSessionParam session: WhatsappSession,
@Param('id') id: string,
@Query() query: ChatPictureQuery,
): Promise<ChatPictureResponse> {
const url = await session.getContactProfilePicture(id, query.refresh);
return { url: url };
}
@Put(':id/picture')
@SessionApiParam
@GroupIdApiParam
@ApiOperation({ summary: 'Set group picture' })
async setPicture(
@Param('id') id: string,
@WorkingSessionParam session: WhatsappSession,
@Body() request: ProfilePictureRequest,
): Promise<Result> {
const success = await session.updateGroupPicture(id, request.file);
return { success: success ?? true };
}
@Delete(':id/picture')
@SessionApiParam
@GroupIdApiParam
@ApiOperation({ summary: 'Delete group picture' })
async deletePicture(
@Param('id') id: string,
@WorkingSessionParam session: WhatsappSession,
): Promise<Result> {
const success = await session.updateGroupPicture(id, null);
return { success: success || true };
}
@Put(':id/description')
@ApiOperation({
summary:
'Updates the group description.\n' +
'Returns true if the subject was properly updated. This can return false if the user does not have the necessary permissions.\n',
summary: 'Updates the group description.',
description:
'Returns "true" if the subject was properly updated. This can return "false" if the user does not have the necessary permissions.',
})
@SessionApiParam
@GroupIdApiParam
setDescription(
@SessionParam session: WhatsappSession,
@WorkingSessionParam session: WhatsappSession,
@Param('id') id: string,
@Body() request: DescriptionRequest,
) {
@@ -137,37 +217,102 @@ export class GroupsController {
@Put(':id/subject')
@SessionApiParam
@GroupIdApiParam
@ApiOperation({
summary:
'Updates the group subject.\n' +
'Returns true if the subject was properly updated. This can return false if the user does not have the necessary permissions.\n',
summary: 'Updates the group subject',
description:
'Returns "true" if the subject was properly updated. This can return "false" if the user does not have the necessary permissions.',
})
setSubject(
@SessionParam session: WhatsappSession,
@WorkingSessionParam session: WhatsappSession,
@Param('id') id: string,
@Body() request: SubjectRequest,
) {
return session.setSubject(id, request.subject);
}
@Put(':id/settings/security/info-admin-only')
@SessionApiParam
@GroupIdApiParam
@ApiOperation({
summary: 'Updates the group "info admin only" settings.',
description:
'You can allow only admins to edit group info (title, description, photo).',
})
setInfoAdminOnly(
@WorkingSessionParam session: WhatsappSession,
@Param('id') id: string,
@Body() request: SettingsSecurityChangeInfo,
) {
return session.setInfoAdminsOnly(id, request.adminsOnly);
}
@Get(':id/settings/security/info-admin-only')
@SessionApiParam
@GroupIdApiParam
@ApiOperation({
summary: "Get the group's 'info admin only' settings.",
description:
'You can allow only admins to edit group info (title, description, photo).',
})
getInfoAdminOnly(
@WorkingSessionParam session: WhatsappSession,
@Param('id') id: string,
): Promise<SettingsSecurityChangeInfo> {
return session.getInfoAdminsOnly(id);
}
@Put(':id/settings/security/messages-admin-only')
@SessionApiParam
@GroupIdApiParam
@ApiOperation({
summary: 'Update settings - who can send messages',
description:
'Updates the group settings to only allow admins to send messages.',
})
setMessagesAdminOnly(
@WorkingSessionParam session: WhatsappSession,
@Param('id') id: string,
@Body() request: SettingsSecurityChangeInfo,
) {
return session.setMessagesAdminsOnly(id, request.adminsOnly);
}
@Get(':id/settings/security/messages-admin-only')
@SessionApiParam
@GroupIdApiParam
@ApiOperation({
summary: 'Get settings - who can send messages',
description: 'The group settings to only allow admins to send messages.',
})
getMessagesAdminOnly(
@WorkingSessionParam session: WhatsappSession,
@Param('id') id: string,
): Promise<SettingsSecurityChangeInfo> {
return session.getMessagesAdminsOnly(id);
}
@Get(':id/invite-code')
@SessionApiParam
@ApiOperation({ summary: 'Gets the invite code for a specific group.' })
@GroupIdApiParam
@ApiOperation({ summary: 'Gets the invite code for the group.' })
getInviteCode(
@SessionParam session: WhatsappSession,
@WorkingSessionParam session: WhatsappSession,
@Param('id') id: string,
): Promise<string> {
return session.getInviteCode(id);
}
@Post(':id/invite-code/revoke')
@HttpCode(HttpStatus.OK)
@SessionApiParam
@GroupIdApiParam
@ApiOperation({
summary:
'Invalidates the current group invite code and generates a new one.',
})
revokeInviteCode(
@SessionParam session: WhatsappSession,
@WorkingSessionParam session: WhatsappSession,
@Param('id') id: string,
): Promise<string> {
return session.revokeInviteCode(id);
@@ -175,19 +320,33 @@ export class GroupsController {
@Get(':id/participants/')
@SessionApiParam
@ApiOperation({ summary: 'Get a list of participants by in the group.' })
@GroupIdApiParam
@ApiOperation({ summary: 'Get participants' })
getParticipants(
@SessionParam session: WhatsappSession,
@WorkingSessionParam session: WhatsappSession,
@Param('id') id: string,
) {
return session.getParticipants(id);
}
@Post(':id/participants/add')
@Get(':id/participants/v2')
@GroupIdApiParam
@SessionApiParam
@ApiOperation({ summary: 'Adds a list of participants by ID to the group.' })
@ApiOperation({ summary: 'Get group participants.' })
getGroupParticipants(
@WorkingSessionParam session: WhatsappSession,
@Param('id') id: string,
): Promise<GroupParticipant[]> {
return session.getGroupParticipants(id);
}
@Post(':id/participants/add')
@HttpCode(HttpStatus.OK)
@SessionApiParam
@GroupIdApiParam
@ApiOperation({ summary: 'Add participants' })
addParticipants(
@SessionParam session: WhatsappSession,
@WorkingSessionParam session: WhatsappSession,
@Param('id') id: string,
@Body() request: ParticipantsRequest,
) {
@@ -195,12 +354,14 @@ export class GroupsController {
}
@Post(':id/participants/remove')
@HttpCode(HttpStatus.OK)
@SessionApiParam
@GroupIdApiParam
@ApiOperation({
summary: 'Removes a list of participants by ID to the group.',
summary: 'Remove participants',
})
removeParticipants(
@SessionParam session: WhatsappSession,
@WorkingSessionParam session: WhatsappSession,
@Param('id') id: string,
@Body() request: ParticipantsRequest,
) {
@@ -208,10 +369,12 @@ export class GroupsController {
}
@Post(':id/admin/promote')
@HttpCode(HttpStatus.OK)
@SessionApiParam
@GroupIdApiParam
@ApiOperation({ summary: 'Promote participants to admin users.' })
promoteToAdmin(
@SessionParam session: WhatsappSession,
@WorkingSessionParam session: WhatsappSession,
@Param('id') id: string,
@Body() request: ParticipantsRequest,
) {
@@ -219,10 +382,12 @@ export class GroupsController {
}
@Post(':id/admin/demote')
@HttpCode(HttpStatus.OK)
@SessionApiParam
@ApiOperation({ summary: 'Demotes participants by to regular users.' })
@GroupIdApiParam
@ApiOperation({ summary: 'Demotes participants to regular users.' })
demoteToAdmin(
@SessionParam session: WhatsappSession,
@WorkingSessionParam session: WhatsappSession,
@Param('id') id: string,
@Body() request: ParticipantsRequest,
) {
+11 -2
View File
@@ -1,19 +1,28 @@
import { Controller, Get } from '@nestjs/common';
import { Controller, Get, UseGuards } from '@nestjs/common';
import { ApiOperation, ApiSecurity, ApiTags } from '@nestjs/swagger';
import { HealthCheck } from '@nestjs/terminus';
import { WAHAHealthCheckService } from '../core/abc/WAHAHealthCheckService';
import { PoliciesGuard } from '@waha/core/auth/policies.guard';
import { CheckPolicies } from '@waha/core/auth/policies.decorator';
import { CanServer } from '@waha/core/auth/policies';
import { Action } from '@waha/core/auth/casl.types';
@ApiSecurity('api_key')
@Controller('health')
@ApiTags('🔍 Observability')
@UseGuards(PoliciesGuard)
@CheckPolicies(CanServer(Action.Manage))
export class HealthController {
constructor(private wahaHealth: WAHAHealthCheckService) {}
@Get()
@HealthCheck()
@ApiOperation({
summary: 'Check the health of the server, performing all health checks.',
summary: 'Check the health of the server',
description:
"Perform all health checks and return the server's health status.",
})
async check() {
return this.wahaHealth.check();
+107 -7
View File
@@ -1,32 +1,131 @@
import { Body, Controller, Get, Param, Put } from '@nestjs/common';
import {
Body,
Controller,
Delete,
Get,
NotFoundException,
Param,
Post,
Put,
UnprocessableEntityException,
UseGuards,
UsePipes,
ValidationPipe,
} from '@nestjs/common';
import { ApiOperation, ApiSecurity, ApiTags } from '@nestjs/swagger';
import { WhatsappSession } from '@waha/core/abc/session.abc';
import { ChatIdApiParam } from '@waha/nestjs/params/ChatIdApiParam';
import {
SessionApiParam,
SessionParam,
WorkingSessionParam,
} from '@waha/nestjs/params/SessionApiParam';
import { Label, LabelID, SetLabelsRequest } from '@waha/structures/labels.dto';
import {
Label,
LabelBody,
SetLabelsRequest,
} from '@waha/structures/labels.dto';
import * as lodash from 'lodash';
import { SessionManager } from '../core/abc/manager.abc';
import { PoliciesGuard } from '@waha/core/auth/policies.guard';
import { CheckPolicies } from '@waha/core/auth/policies.decorator';
import { CanSession, FromParam } from '@waha/core/auth/policies';
import { Action } from '@waha/core/auth/casl.types';
@ApiSecurity('api_key')
@Controller('api/:session/labels')
@ApiTags('🏷️ Labels')
@UseGuards(PoliciesGuard)
@CheckPolicies(CanSession(Action.Use, FromParam('session')))
export class LabelsController {
constructor(private manager: SessionManager) {}
@Get('/')
@SessionApiParam
@ApiOperation({ summary: 'Get all labels' })
getAll(@SessionParam session: WhatsappSession): Promise<Label[]> {
getAll(@WorkingSessionParam session: WhatsappSession): Promise<Label[]> {
return session.getLabels();
}
@Post('/')
@SessionApiParam
@ApiOperation({ summary: 'Create a new label' })
@UsePipes(new ValidationPipe({ transform: true, whitelist: true }))
async create(
@WorkingSessionParam session: WhatsappSession,
@Body() body: LabelBody,
): Promise<Label> {
const labelDto = body.toDTO();
const labels = await session.getLabels();
if (labels.length >= 20) {
throw new UnprocessableEntityException('Maximum 20 labels allowed');
}
const labelByName = lodash.find(labels, { name: labelDto.name });
if (labelByName) {
throw new UnprocessableEntityException(
`Label with '${labelByName.name}' already exists, id: ${labelByName.id}`,
);
}
return session.createLabel(labelDto);
}
@Put('/:labelId')
@SessionApiParam
@ApiOperation({ summary: 'Update a label' })
@UsePipes(new ValidationPipe({ transform: true, whitelist: true }))
async update(
@WorkingSessionParam session: WhatsappSession,
@Param('labelId') labelId: string,
@Body() body: LabelBody,
): Promise<Label> {
const label = await session.getLabel(labelId);
if (!label) {
throw new NotFoundException('Label not found');
}
const labelDto = body.toDTO();
const labels = await session.getLabels();
const labelByName = lodash.find(labels, { name: labelDto.name });
if (labelByName) {
throw new UnprocessableEntityException(
`Label with '${labelByName.name}' already exists, id: ${labelByName.id}`,
);
}
label.name = labelDto.name;
label.color = labelDto.color;
label.colorHex = Label.toHex(label.color);
await session.updateLabel(label);
return label;
}
@Delete('/:labelId')
@SessionApiParam
@ApiOperation({ summary: 'Delete a label' })
@UsePipes(new ValidationPipe({ transform: true, whitelist: true }))
async delete(
@WorkingSessionParam session: WhatsappSession,
@Param('labelId') labelId: string,
): Promise<any> {
const label = await session.getLabel(labelId);
if (!label) {
throw new NotFoundException('Label not found');
}
await session.deleteLabel(label);
return { result: true };
}
@Get('/chats/:chatId')
@SessionApiParam
@ChatIdApiParam
@ApiOperation({ summary: 'Get labels for the chat' })
getChatLabels(
@SessionParam session: WhatsappSession,
@WorkingSessionParam session: WhatsappSession,
@Param('chatId') chatId: string,
): Promise<Label[]> {
return session.getChatLabels(chatId);
@@ -34,9 +133,10 @@ export class LabelsController {
@Put('/chats/:chatId')
@SessionApiParam
@ChatIdApiParam
@ApiOperation({ summary: 'Save labels for the chat' })
putChatLabels(
@SessionParam session: WhatsappSession,
@WorkingSessionParam session: WhatsappSession,
@Param('chatId') chatId: string,
@Body() request: SetLabelsRequest,
) {
@@ -47,7 +147,7 @@ export class LabelsController {
@SessionApiParam
@ApiOperation({ summary: 'Get chats by label' })
getChatsByLabel(
@SessionParam session: WhatsappSession,
@WorkingSessionParam session: WhatsappSession,
@Param('labelId') labelId: string,
) {
return session.getChatsByLabelId(labelId);
+110
View File
@@ -0,0 +1,110 @@
import {
Controller,
Get,
Param,
Query,
UnprocessableEntityException,
UseGuards,
UsePipes,
ValidationPipe,
} from '@nestjs/common';
import { ApiOperation, ApiSecurity, ApiTags } from '@nestjs/swagger';
import { WhatsappSession } from '@waha/core/abc/session.abc';
import {
SessionApiParam,
WorkingSessionParam,
} from '@waha/nestjs/params/SessionApiParam';
import { CountResponse } from '@waha/structures/base.dto';
import {
LidsListQueryParams,
LidToPhoneNumber,
} from '@waha/structures/lids.dto';
import { PaginationParams, SortOrder } from '@waha/structures/pagination.dto';
import { SessionManager } from '../core/abc/manager.abc';
import { isLidUser } from '@waha/core/utils/jids';
import { PoliciesGuard } from '@waha/core/auth/policies.guard';
import { CheckPolicies } from '@waha/core/auth/policies.decorator';
import { CanSession, FromParam } from '@waha/core/auth/policies';
import { Action } from '@waha/core/auth/casl.types';
@ApiSecurity('api_key')
@Controller('api/:session/lids')
@ApiTags('👤 Contacts')
@UseGuards(PoliciesGuard)
@CheckPolicies(CanSession(Action.Use, FromParam('session')))
export class LidsController {
constructor(private manager: SessionManager) {}
@Get('/')
@SessionApiParam
@ApiOperation({ summary: 'Get all known lids to phone number mapping' })
@UsePipes(new ValidationPipe({ transform: true, whitelist: true }))
async getAll(
@WorkingSessionParam session: WhatsappSession,
@Query() params: LidsListQueryParams,
): Promise<Array<LidToPhoneNumber>> {
// Always lid
const pagination: PaginationParams = params;
pagination.sortBy = 'lid';
pagination.sortOrder = SortOrder.ASC;
const lids = await session.getAllLids(pagination);
return lids;
}
@Get('/count')
@SessionApiParam
@ApiOperation({ summary: 'Get the number of known lids' })
@UsePipes(new ValidationPipe({ transform: true, whitelist: true }))
async getLidsCount(
@WorkingSessionParam session: WhatsappSession,
): Promise<CountResponse> {
const count = await session.getLidsCount();
return {
count: count,
};
}
@Get('/:lid')
@SessionApiParam
@ApiOperation({ summary: 'Get phone number by lid' })
@UsePipes(new ValidationPipe({ transform: true, whitelist: true }))
async findPNByLid(
@WorkingSessionParam session: WhatsappSession,
@Param('lid') lid: string,
): Promise<LidToPhoneNumber> {
if (!lid.includes('@')) {
lid = lid + '@lid';
}
if (!isLidUser(lid)) {
throw new UnprocessableEntityException(
'Invalid LID - it must end with @lid',
);
}
const result = await session.findPNByLid(lid);
result.pn = result.pn || null;
return result;
}
@Get('/pn/:phoneNumber')
@SessionApiParam
@ApiOperation({ summary: 'Get lid by phone number (chat id)' })
@UsePipes(new ValidationPipe({ transform: true, whitelist: true }))
async findLIDByPhoneNumber(
@WorkingSessionParam session: WhatsappSession,
@Param('phoneNumber') phoneNumber: string,
): Promise<LidToPhoneNumber> {
if (isLidUser(phoneNumber)) {
return {
lid: phoneNumber,
pn: null,
};
}
const result = await session.findLIDByPhoneNumber(phoneNumber);
result.lid = result.lid || null;
return result;
}
}
+91
View File
@@ -0,0 +1,91 @@
import {
Body,
Controller,
Post,
UnprocessableEntityException,
UseGuards,
UseInterceptors,
} from '@nestjs/common';
import { ApiOperation, ApiSecurity, ApiTags } from '@nestjs/swagger';
import { SessionManager } from '@waha/core/abc/manager.abc';
import { WAMimeType } from '@waha/core/media/WAMimeType';
import { ApiFileAcceptHeader } from '@waha/nestjs/ApiFileAcceptHeader';
import {
SessionApiParam,
WorkingSessionParam,
} from '@waha/nestjs/params/SessionApiParam';
import {
FileDTO,
VideoFileDTO,
VoiceFileDTO,
} from '@waha/structures/media.dto';
import { WhatsappSession } from '../core/abc/session.abc';
import { BufferResponseInterceptor } from '../nestjs/BufferResponseInterceptor';
import { PoliciesGuard } from '@waha/core/auth/policies.guard';
import { CheckPolicies } from '@waha/core/auth/policies.decorator';
import { CanSession, FromParam } from '@waha/core/auth/policies';
import { Action } from '@waha/core/auth/casl.types';
@ApiSecurity('api_key')
@Controller('api/:session/media')
@ApiTags('🖼️ Media')
@UseGuards(PoliciesGuard)
@CheckPolicies(CanSession(Action.Use, FromParam('session')))
class MediaController {
constructor(private manager: SessionManager) {}
@Post('convert/voice')
@ApiOperation({
summary: 'Convert voice to WhatsApp format (opus)',
})
@SessionApiParam
@UseInterceptors(
new BufferResponseInterceptor(WAMimeType.VOICE, 'output.opus'),
)
@ApiFileAcceptHeader(WAMimeType.VOICE)
async convertVoice(
@WorkingSessionParam session: WhatsappSession,
@Body() file: VoiceFileDTO,
): Promise<Buffer> {
const data = await this.buffer(session, file);
const content = await session.mediaConverter.voice(data);
return content;
}
@Post('convert/video')
@ApiOperation({
summary: 'Convert video to WhatsApp format (mp4)',
})
@SessionApiParam
@UseInterceptors(
new BufferResponseInterceptor(WAMimeType.VIDEO, 'output.mp4'),
)
@ApiFileAcceptHeader(WAMimeType.VIDEO)
async convertVideo(
@WorkingSessionParam session: WhatsappSession,
@Body() file: VideoFileDTO,
): Promise<Buffer> {
const data = await this.buffer(session, file);
const content = await session.mediaConverter.video(data);
return content;
}
private async buffer(
session: WhatsappSession,
file: FileDTO,
): Promise<Buffer> {
if ('url' in file) {
return session.fetch(file.url);
} else if ('data' in file) {
return Buffer.from(file.data, 'base64');
} else {
throw new UnprocessableEntityException(
'Either "url" or "data" must be specified.',
);
}
}
}
export { MediaController };
+4 -2
View File
@@ -2,12 +2,14 @@ import { Controller, Get } from '@nestjs/common';
import { ApiOperation, ApiSecurity, ApiTags } from '@nestjs/swagger';
import { PingResponse } from '@waha/structures/ping.dto';
@ApiSecurity('api_key')
@Controller('ping')
@ApiTags('🔍 Observability')
export class PingController {
@Get()
@ApiOperation({ summary: 'Ping the server, perform no operations.' })
@ApiOperation({
summary: 'Ping the server',
description: 'Check if the server is alive and responding to requests.',
})
ping(): PingResponse {
return { message: 'pong' };
}
+16 -5
View File
@@ -5,11 +5,13 @@ import {
Get,
Param,
Post,
UseGuards,
} from '@nestjs/common';
import { ApiOperation, ApiSecurity, ApiTags } from '@nestjs/swagger';
import { ChatIdApiParam } from '@waha/nestjs/params/ChatIdApiParam';
import {
SessionApiParam,
SessionParam,
WorkingSessionParam,
} from '@waha/nestjs/params/SessionApiParam';
import { SessionManager } from '../core/abc/manager.abc';
@@ -19,10 +21,17 @@ import {
WAHAChatPresences,
WAHASessionPresence,
} from '../structures/presence.dto';
import { PoliciesGuard } from '@waha/core/auth/policies.guard';
import { CheckPolicies } from '@waha/core/auth/policies.decorator';
import { CanSession, FromParam } from '@waha/core/auth/policies';
import { Action } from '@waha/core/auth/casl.types';
@ApiSecurity('api_key')
@Controller('api/:session/presence')
@ApiTags('✅ Presence')
@UseGuards(PoliciesGuard)
@CheckPolicies(CanSession(Action.Use, FromParam('session')))
export class PresenceController {
constructor(private manager: SessionManager) {}
@@ -30,7 +39,7 @@ export class PresenceController {
@SessionApiParam
@ApiOperation({ summary: 'Set session presence' })
setPresence(
@SessionParam session: WhatsappSession,
@WorkingSessionParam session: WhatsappSession,
@Body() request: WAHASessionPresence,
) {
// Validate request
@@ -60,19 +69,20 @@ export class PresenceController {
@SessionApiParam
@ApiOperation({ summary: 'Get all subscribed presence information.' })
getPresenceAll(
@SessionParam session: WhatsappSession,
@WorkingSessionParam session: WhatsappSession,
): Promise<WAHAChatPresences[]> {
return session.getPresences();
}
@Get(':chatId')
@SessionApiParam
@ChatIdApiParam
@ApiOperation({
summary:
"Get the presence for the chat id. If it hasn't been subscribed - it also subscribes to it.",
})
getPresence(
@SessionParam session: WhatsappSession,
@WorkingSessionParam session: WhatsappSession,
@Param('chatId') chatId: string,
): Promise<WAHAChatPresences> {
return session.getPresence(chatId);
@@ -80,11 +90,12 @@ export class PresenceController {
@Post(':chatId/subscribe')
@SessionApiParam
@ChatIdApiParam
@ApiOperation({
summary: 'Subscribe to presence events for the chat.',
})
subscribe(
@SessionParam session: WhatsappSession,
@WorkingSessionParam session: WhatsappSession,
@Param('chatId') chatId: string,
): Promise<void> {
return session.subscribePresence(chatId);
+102
View File
@@ -0,0 +1,102 @@
import {
Body,
Controller,
Delete,
Get,
Put,
UnprocessableEntityException,
UseGuards,
UsePipes,
} from '@nestjs/common';
import { ApiOperation, ApiSecurity, ApiTags } from '@nestjs/swagger';
import { SessionManager } from '@waha/core/abc/manager.abc';
import { WhatsappSession } from '@waha/core/abc/session.abc';
import {
SessionApiParam,
WorkingSessionParam,
} from '@waha/nestjs/params/SessionApiParam';
import { WAHAValidationPipe } from '@waha/nestjs/pipes/WAHAValidationPipe';
import { Result } from '@waha/structures/base.dto';
import {
MyProfile,
ProfileNameRequest,
ProfilePictureRequest,
ProfileStatusRequest,
} from '@waha/structures/profile.dto';
import { PoliciesGuard } from '@waha/core/auth/policies.guard';
import { CheckPolicies } from '@waha/core/auth/policies.decorator';
import { CanSession, FromParam } from '@waha/core/auth/policies';
import { Action } from '@waha/core/auth/casl.types';
@ApiSecurity('api_key')
@Controller('api/:session/profile')
@ApiTags('🆔 Profile')
@UseGuards(PoliciesGuard)
@CheckPolicies(CanSession(Action.Use, FromParam('session')))
export class ProfileController {
constructor(private manager: SessionManager) {}
@Get('')
@SessionApiParam
@ApiOperation({ summary: 'Get my profile' })
async getMyProfile(
@WorkingSessionParam session: WhatsappSession,
): Promise<MyProfile> {
const me = session.getSessionMeInfo();
if (!me) {
throw new UnprocessableEntityException('No profile found');
}
const picture = await session.getContactProfilePicture(me.id, false);
return {
id: me.id,
name: me.pushName,
picture: picture || null,
};
}
@Put('/name')
@SessionApiParam
@UsePipes(new WAHAValidationPipe())
@ApiOperation({ summary: 'Set my profile name' })
async setProfileName(
@WorkingSessionParam session: WhatsappSession,
@Body() request: ProfileNameRequest,
): Promise<Result> {
const success = await session.setProfileName(request.name);
return { success: success ?? true };
}
@Put('/status')
@SessionApiParam
@UsePipes(new WAHAValidationPipe())
@ApiOperation({ summary: 'Set profile status (About)' })
async setProfileStatus(
@WorkingSessionParam session: WhatsappSession,
@Body() request: ProfileStatusRequest,
): Promise<Result> {
const success = await session.setProfileStatus(request.status);
return { success: success ?? true };
}
@Put('/picture')
@SessionApiParam
@ApiOperation({ summary: 'Set profile picture' })
async setProfilePicture(
@WorkingSessionParam session: WhatsappSession,
@Body() request: ProfilePictureRequest,
): Promise<Result> {
const success = await session.updateProfilePicture(request.file);
return { success: success ?? true };
}
@Delete('/picture')
@SessionApiParam
@ApiOperation({ summary: 'Delete profile picture' })
async deleteProfilePicture(
@WorkingSessionParam session: WhatsappSession,
): Promise<Result> {
const success = await session.updateProfilePicture(null);
return { success: success || true };
}
}
+16 -4
View File
@@ -4,25 +4,37 @@ import {
Query,
Res,
StreamableFile,
UseGuards,
UseInterceptors,
} from '@nestjs/common';
import { ApiSecurity, ApiTags } from '@nestjs/swagger';
import { ApiOperation, ApiSecurity, ApiTags } from '@nestjs/swagger';
import { ApiFileAcceptHeader } from '@waha/nestjs/ApiFileAcceptHeader';
import { Response } from 'express';
import { SessionManager } from '../core/abc/manager.abc';
import { BufferResponseInterceptor } from '../nestjs/BufferResponseInterceptor';
import { SessionQuery } from '../structures/base.dto';
import { PoliciesGuard } from '@waha/core/auth/policies.guard';
import { CheckPolicies } from '@waha/core/auth/policies.decorator';
import { CanSession, FromQuery } from '@waha/core/auth/policies';
import { Action } from '@waha/core/auth/casl.types';
@ApiSecurity('api_key')
@Controller('api')
@ApiTags('🖼️ Screenshot')
@ApiTags('📱 Pairing')
@UseGuards(PoliciesGuard)
@CheckPolicies(CanSession(Action.Use, FromQuery('session')))
export class ScreenshotController {
constructor(private manager: SessionManager) {}
@Get('/screenshot')
@UseInterceptors(new BufferResponseInterceptor())
@ApiFileAcceptHeader()
@ApiOperation({
summary:
'Get a screenshot of the current WhatsApp session (**WEBJS** only)',
})
@UseInterceptors(new BufferResponseInterceptor('image/jpeg'))
@ApiFileAcceptHeader('image/jpeg')
async screenshot(
@Res({ passthrough: true }) res: Response,
@Query() sessionQuery: SessionQuery,
+25 -10
View File
@@ -7,38 +7,49 @@ import {
Logger,
Post,
Query,
UseGuards,
UsePipes,
} from '@nestjs/common';
import { ApiOperation, ApiSecurity, ApiTags } from '@nestjs/swagger';
import { getApp } from '@waha/main';
import { WhatsappConfigService } from '@waha/config.service';
import { WAHAValidationPipe } from '@waha/nestjs/pipes/WAHAValidationPipe';
import { WAHAEnvironment } from '@waha/structures/environment.dto';
import {
EnvironmentQuery,
ServerStatusResponse,
StopRequest,
StopResponse,
} from '@waha/structures/server.dto';
import { sleep } from '@waha/utils/promiseTimeout';
import { VERSION } from '@waha/version';
import * as lodash from 'lodash';
import { PoliciesGuard } from '@waha/core/auth/policies.guard';
import { CheckPolicies } from '@waha/core/auth/policies.decorator';
import { CanServer } from '@waha/core/auth/policies';
import { Action } from '@waha/core/auth/casl.types';
@ApiSecurity('api_key')
@Controller('api/server')
@ApiTags('🔍 Observability')
@UseGuards(PoliciesGuard)
export class ServerController {
private logger: Logger;
constructor() {
constructor(private config: WhatsappConfigService) {
this.logger = new Logger('ServerController');
}
@Get('version')
@ApiOperation({ summary: 'Get the version of the server' })
@CheckPolicies(CanServer(Action.Read))
get(): WAHAEnvironment {
return VERSION;
}
@Get('environment')
@ApiOperation({ summary: 'Return the server environment' })
@ApiOperation({ summary: 'Get the server environment' })
@CheckPolicies(CanServer(Action.Manage))
environment(
@Query(new WAHAValidationPipe()) query: EnvironmentQuery,
// eslint-disable-next-line @typescript-eslint/ban-types
@@ -64,7 +75,8 @@ export class ServerController {
}
@Get('status')
@ApiOperation({ summary: 'The server status' })
@ApiOperation({ summary: 'Get the server status' })
@CheckPolicies(CanServer(Action.Read))
async status(): Promise<ServerStatusResponse> {
const now = Date.now();
const uptime = Math.floor(process.uptime() * 1000);
@@ -72,6 +84,9 @@ export class ServerController {
return {
startTimestamp: startTimestamp,
uptime: uptime,
worker: {
id: this.config.workerId,
},
};
}
@@ -82,26 +97,26 @@ export class ServerController {
"If you're using docker, after calling this endpoint Docker will start a new container, " +
'so you can use this endpoint to restart the server',
})
@CheckPolicies(CanServer(Action.Manage))
@UsePipes(new WAHAValidationPipe())
async stop(@Body() request: StopRequest) {
async stop(@Body() request: StopRequest): Promise<StopResponse> {
const timeout = 1_000;
if (request.force) {
this.logger.log(`Force stopping the server in ${timeout}ms`);
setTimeout(() => {
this.logger.log('Force stopping the server');
process.kill(process.pid, 'SIGKILL');
process.exit(0);
}, timeout);
} else {
this.logger.log(`Gracefully stopping the server in ${timeout}ms`);
setTimeout(async () => {
this.logger.log('Gracefully closing the application...');
const app = getApp();
if (app) {
await app.close();
}
this.logger.log('Application closed');
process.kill(process.pid, 'SIGTERM');
await sleep(10_000);
process.exit(0);
}, timeout);
}
return { stopping: true };
}
}
+142
View File
@@ -0,0 +1,142 @@
import * as inspector from 'node:inspector';
import * as v8 from 'node:v8';
import { Readable } from 'node:stream';
import {
Controller,
Get,
Logger,
NotFoundException,
Query,
StreamableFile,
UseGuards,
UsePipes,
} from '@nestjs/common';
import { ApiOperation, ApiSecurity, ApiTags } from '@nestjs/swagger';
import { WhatsappConfigService } from '@waha/config.service';
import { SessionManager } from '@waha/core/abc/manager.abc';
import { WhatsappSession } from '@waha/core/abc/session.abc';
import {
SessionApiParam,
WorkingSessionParam,
} from '@waha/nestjs/params/SessionApiParam';
import { WAHAValidationPipe } from '@waha/nestjs/pipes/WAHAValidationPipe';
import {
BrowserTraceQuery,
CpuProfileQuery,
} from '@waha/structures/server.debug.dto';
import { createReadStream } from 'fs';
import { PoliciesGuard } from '@waha/core/auth/policies.guard';
import { CheckPolicies } from '@waha/core/auth/policies.decorator';
import { CanServer } from '@waha/core/auth/policies';
import { Action } from '@waha/core/auth/casl.types';
@ApiSecurity('api_key')
@Controller('api/server/debug')
@ApiTags('🔍 Observability')
@UseGuards(PoliciesGuard)
@CheckPolicies(CanServer(Action.Manage))
export class ServerDebugController {
private logger: Logger;
private readonly enabled: boolean;
constructor(
private config: WhatsappConfigService,
private manager: SessionManager,
) {
this.logger = new Logger('ServerDebugController');
this.enabled = this.config.debugModeEnabled;
}
@Get('cpu')
@ApiOperation({
summary: 'Collect and return a CPU profile for the current nodejs process',
description: 'Uses the Node.js inspector profiler to capture a .cpuprofile',
})
@UsePipes(new WAHAValidationPipe())
async cpuProfile(@Query() query: CpuProfileQuery) {
if (!this.enabled) {
throw new NotFoundException('WAHA_DEBUG_MODE is disabled');
}
const { seconds } = query;
this.logger.log(`Collecting CPU profile for ${seconds}s...`);
const session = new inspector.Session();
session.connect();
const profile = await new Promise<inspector.Profiler.Profile>(
(resolve, reject) => {
session.post('Profiler.enable', (enableError) => {
if (enableError) {
session.disconnect();
return reject(enableError);
}
session.post('Profiler.start', (startError) => {
if (startError) {
session.disconnect();
return reject(startError);
}
setTimeout(() => {
session.post('Profiler.stop', (stopError, params) => {
session.disconnect();
if (stopError) {
return reject(stopError);
}
resolve(params.profile);
});
}, seconds * 1000);
});
});
},
);
const filename = `CPU.${Date.now()}.${process.pid}.cpuprofile`;
const stream = Readable.from([JSON.stringify(profile)]);
return new StreamableFile(stream, {
type: 'application/json',
disposition: `attachment; filename=${filename}`,
});
}
@Get('heapsnapshot')
@ApiOperation({
summary: 'Return a heapsnapshot for the current nodejs process',
description: "Return a heapsnapshot of the server's memory",
})
async heapsnapshot() {
if (!this.enabled) {
throw new NotFoundException('WAHA_DEBUG_MODE is disabled');
}
this.logger.log('Creating a heap snapshot...');
const heap = v8.getHeapSnapshot();
const fileName = `${Date.now()}.heapsnapshot`;
return new StreamableFile(heap, {
type: 'application/octet-stream',
disposition: `attachment; filename=${fileName}`,
});
}
@Get('browser/trace/:session')
@ApiOperation({
summary: 'Collect and get a trace.json for Chrome DevTools ',
description: 'Uses https://pptr.dev/api/puppeteer.tracing',
})
@SessionApiParam
@UsePipes(new WAHAValidationPipe())
async browserTrace(
@WorkingSessionParam session: WhatsappSession,
@Query() query: BrowserTraceQuery,
) {
if (!this.enabled) {
throw new NotFoundException('WAHA_DEBUG_MODE is disabled');
}
const filepath = await session.browserTrace(query);
const stream = createReadStream(filepath);
const filename = `trace - ${session.name} - ${new Date()}.json`;
return new StreamableFile(stream, {
type: 'application/octet-stream',
disposition: `attachment; filename=${filename}`,
});
}
}
+117 -42
View File
@@ -3,28 +3,39 @@ import {
Controller,
Delete,
Get,
Inject,
NotFoundException,
Param,
Post,
Put,
Query,
Req,
UseGuards,
UsePipes,
} from '@nestjs/common';
import { UnprocessableEntityException } from '@nestjs/common/exceptions/unprocessable-entity.exception';
import { ApiOperation, ApiSecurity, ApiTags } from '@nestjs/swagger';
import {
ApiBody,
ApiOAuth2,
ApiOperation,
ApiSecurity,
ApiTags,
} from '@nestjs/swagger';
import {
SessionApiParam,
SessionParam,
} from '@waha/nestjs/params/SessionApiParam';
import { WAHAValidationPipe } from '@waha/nestjs/pipes/WAHAValidationPipe';
import { WAHASessionStatus } from '@waha/structures/enums.dto';
import {
AppsService,
IAppsService,
} from '@waha/apps/app_sdk/services/IAppsService';
import {
SessionLogoutDeprecatedRequest,
SessionStartDeprecatedRequest,
SessionStopDeprecatedRequest,
} from '@waha/structures/sessions.deprecated.dto';
import { generatePrefixedId } from '@waha/utils/ids';
import { sleep } from '@waha/utils/promiseTimeout';
import { SessionManager } from '../core/abc/manager.abc';
import { WhatsappSession } from '../core/abc/session.abc';
@@ -33,50 +44,77 @@ import {
MeInfo,
SessionCreateRequest,
SessionDTO,
SessionExpand,
SessionInfo,
SessionInfoQuery,
SessionUpdateRequest,
} from '../structures/sessions.dto';
// eslint-disable-next-line @typescript-eslint/no-var-requires
const AsyncLock = require('async-lock');
import { SessionExamples } from './sessions.examples';
import { FilterSessions } from '../core/auth/casl.ability';
import { CheckPolicies } from '../core/auth/policies.decorator';
import { PoliciesGuard } from '../core/auth/policies.guard';
import { CanSession, FromBody, FromParam } from '../core/auth/policies';
import { Action } from '@waha/core/auth/casl.types';
@ApiSecurity('api_key')
@Controller('api/sessions')
@ApiTags('🖥️ Sessions')
@UseGuards(PoliciesGuard)
class SessionsController {
private lock: any;
constructor(private manager: SessionManager) {
this.lock = new AsyncLock({ maxPending: Infinity });
}
constructor(
private manager: SessionManager,
@Inject(AppsService) private appsService: IAppsService,
) {}
private withLock(name: string, fn: () => any) {
return this.lock.acquire(name, fn);
return this.manager.withLock(name, fn);
}
@Get('/')
@ApiOperation({ summary: 'List all sessions' })
list(
@ApiOperation({
summary: 'List all sessions',
})
@CheckPolicies(CanSession(Action.List))
@ApiOAuth2(['read:items'])
async list(
@Query(new WAHAValidationPipe()) query: ListSessionsQuery,
@Req() req,
): Promise<SessionInfo[]> {
return this.manager.getSessions(query.all);
let sessions = await this.manager.getSessions(query.all);
if (!req.user.isAdmin) {
sessions = FilterSessions(req.ability, Action.Read, sessions);
}
if (query.expand?.includes(SessionExpand.apps)) {
for (const session of sessions) {
session.apps = await this.appsService.list(this.manager, session.name);
}
}
return sessions;
}
@Get('/:session')
@ApiOperation({ summary: 'Get session information' })
@SessionApiParam
@CheckPolicies(CanSession(Action.Use, FromParam('session')))
@UsePipes(new WAHAValidationPipe())
async get(@Param('session') name: string): Promise<SessionInfo> {
const session = this.manager.getSessionInfo(name);
async get(
@Param('session') name: string,
@Query() query: SessionInfoQuery,
): Promise<SessionInfo> {
const session = await this.manager.getSessionInfo(name);
if (session === null) {
throw new NotFoundException('Session not found');
}
if (query.expand?.includes(SessionExpand.apps)) {
session.apps = await this.appsService.list(this.manager, name);
}
return session;
}
@Get(':session/me')
@SessionApiParam
@ApiOperation({ summary: 'Get information about the authenticated account' })
@CheckPolicies(CanSession(Action.Use, FromParam('session')))
getMe(@SessionParam session: WhatsappSession): MeInfo | null {
return session.getSessionMeInfo();
}
@@ -87,6 +125,8 @@ class SessionsController {
description:
'Create session a new session (and start it at the same time if required).',
})
@ApiBody({ type: SessionCreateRequest, examples: SessionExamples })
@CheckPolicies(CanSession(Action.Create))
@UsePipes(new WAHAValidationPipe())
async create(@Body() request: SessionCreateRequest): Promise<SessionDTO> {
const name = request.name || generatePrefixedId('session');
@@ -95,15 +135,26 @@ class SessionsController {
const msg = `Session '${name}' already exists. Use PUT to update it.`;
throw new UnprocessableEntityException(msg);
}
const config = request.config;
const start = request.start || false;
await this.manager.upsert(name, config);
if (request.apps) {
await this.appsService.syncSessionApps(
this.manager,
name,
request.apps,
);
}
if (start) {
await this.manager.assign(name);
await this.manager.start(name);
}
});
return await this.manager.getSessionInfo(name);
const session = await this.manager.getSessionInfo(name);
if (request.apps) {
session.apps = await this.appsService.list(this.manager, name);
}
return session;
}
@Put(':session')
@@ -112,7 +163,9 @@ class SessionsController {
description: '',
})
@SessionApiParam
@UsePipes(new WAHAValidationPipe())
@ApiBody({ type: SessionUpdateRequest, examples: SessionExamples })
@CheckPolicies(CanSession(Action.Use, FromParam('session')))
@UsePipes(new WAHAValidationPipe({ forbidNonWhitelisted: false }))
async update(
@Param('session') name: string,
@Body() request: SessionUpdateRequest,
@@ -125,11 +178,22 @@ class SessionsController {
const isRunning = this.manager.isRunning(name);
await this.manager.stop(name, true);
await this.manager.upsert(name, config);
if (request.apps) {
await this.appsService.syncSessionApps(
this.manager,
name,
request.apps,
);
}
if (isRunning) {
await this.manager.start(name);
}
});
return await this.manager.getSessionInfo(name);
const session = await this.manager.getSessionInfo(name);
if (request.apps) {
session.apps = await this.appsService.list(this.manager, name);
}
return session;
}
@Delete(':session')
@@ -139,9 +203,12 @@ class SessionsController {
description:
'Delete the session with the given name. Stop and logout as well. Idempotent operation.',
})
@CheckPolicies(CanSession(Action.Delete, FromParam('session')))
@UsePipes(new WAHAValidationPipe())
async delete(@Param('session') name: string): Promise<void> {
await this.withLock(name, async () => {
await this.manager.unassign(name);
await this.manager.unpair(name);
await this.manager.stop(name, true);
await this.manager.logout(name);
await this.manager.delete(name);
@@ -153,8 +220,9 @@ class SessionsController {
@ApiOperation({
summary: 'Start the session',
description:
'Start the session with the given name. The session must exist. Identity operation.',
'Start the session with the given name. The session must exist. Idempotent operation.',
})
@CheckPolicies(CanSession(Action.Use, FromParam('session')))
@UsePipes(new WAHAValidationPipe())
async start(@Param('session') name: string): Promise<SessionDTO> {
await this.withLock(name, async () => {
@@ -162,10 +230,8 @@ class SessionsController {
if (!exists) {
throw new NotFoundException('Session not found');
}
const isRunning = this.manager.isRunning(name);
if (!isRunning) {
await this.manager.start(name);
}
await this.manager.assign(name);
await this.manager.start(name);
});
return await this.manager.getSessionInfo(name);
}
@@ -176,13 +242,11 @@ class SessionsController {
summary: 'Stop the session',
description: 'Stop the session with the given name. Idempotent operation.',
})
@CheckPolicies(CanSession(Action.Use, FromParam('session')))
@UsePipes(new WAHAValidationPipe())
async stop(@Param('session') name: string): Promise<SessionDTO> {
await this.withLock(name, async () => {
const exists = await this.manager.exists(name);
if (!exists) {
throw new NotFoundException('Session not found');
}
await this.manager.unassign(name);
await this.manager.stop(name, false);
});
return await this.manager.getSessionInfo(name);
@@ -194,6 +258,7 @@ class SessionsController {
summary: 'Logout from the session',
description: 'Logout the session, restart a session if it was not STOPPED',
})
@CheckPolicies(CanSession(Action.Use, FromParam('session')))
@UsePipes(new WAHAValidationPipe())
async logout(@Param('session') name: string): Promise<SessionDTO> {
await this.withLock(name, async () => {
@@ -202,6 +267,7 @@ class SessionsController {
throw new NotFoundException('Session not found');
}
const isRunning = this.manager.isRunning(name);
await this.manager.unpair(name);
await this.manager.stop(name, true);
await this.manager.logout(name);
if (isRunning) {
@@ -217,17 +283,10 @@ class SessionsController {
summary: 'Restart the session',
description: 'Restart the session with the given name.',
})
@CheckPolicies(CanSession(Action.Use, FromParam('session')))
@UsePipes(new WAHAValidationPipe())
async restart(@Param('session') name: string): Promise<SessionDTO> {
await this.withLock(name, async () => {
const exists = await this.manager.exists(name);
if (!exists) {
throw new NotFoundException('Session not found');
}
await this.manager.stop(name, true);
await this.manager.start(name);
});
await this.manager.restart(name);
return await this.manager.getSessionInfo(name);
}
@@ -238,10 +297,14 @@ class SessionsController {
'Create session (if not exists) or update a config (if exists) and start it.',
deprecated: true,
})
@CheckPolicies(CanSession(Action.Use, FromBody('name')))
async DEPRACATED_start(
@Body() request: SessionStartDeprecatedRequest,
): Promise<SessionDTO> {
const name = request.name;
if (!request.name) {
throw new UnprocessableEntityException('Session name is required');
}
if (this.manager.isRunning(name)) {
const msg = `Session '${name}' is already started.`;
throw new UnprocessableEntityException(msg);
@@ -249,9 +312,8 @@ class SessionsController {
return await this.withLock(name, async () => {
const config = request.config;
if (config) {
await this.manager.upsert(name, config);
}
await this.manager.upsert(name, config);
await this.manager.assign(name);
return await this.manager.start(name);
});
}
@@ -262,19 +324,26 @@ class SessionsController {
description: 'Stop session and Logout by default.',
deprecated: true,
})
@CheckPolicies(CanSession(Action.Use, FromBody('name')))
async DEPRECATED_stop(
@Body() request: SessionStopDeprecatedRequest,
): Promise<void> {
if (!request.name) {
throw new UnprocessableEntityException('Session name is required');
}
const name = request.name;
if (request.logout) {
// Old API did remove the session complete
await this.withLock(name, async () => {
await this.manager.unassign(name);
await this.manager.unpair(name);
await this.manager.stop(name, true);
await this.manager.logout(name);
await this.manager.delete(name);
});
} else {
await this.withLock(name, async () => {
await this.manager.unassign(name);
await this.manager.stop(name, false);
});
}
@@ -287,11 +356,17 @@ class SessionsController {
description: 'Stop, Logout and Delete session.',
deprecated: true,
})
@CheckPolicies(CanSession(Action.Use, FromBody('name')))
async DEPRECATED_logout(
@Body() request: SessionLogoutDeprecatedRequest,
): Promise<void> {
if (!request.name) {
throw new UnprocessableEntityException('Session name is required');
}
const name = request.name;
await this.withLock(name, async () => {
await this.manager.unassign(name);
await this.manager.unpair(name);
await this.manager.stop(name, true);
await this.manager.logout(name);
await this.manager.delete(name);
+102
View File
@@ -0,0 +1,102 @@
import { AppName } from '@waha/apps/app_sdk/apps/name';
export const SessionExamples = {
basic: {
summary: 'Basic Session',
value: {
name: 'default',
},
},
withWebhooks: {
summary: 'Session Webhooks',
value: {
name: 'default',
config: {
webhooks: [
{
url: 'https://example.com/webhooks/waha',
events: ['message', 'session.status'],
hmac: {
key: 'super-secret',
},
retries: {
delaySeconds: 2,
attempts: 5,
policy: 'linear',
},
customHeaders: [
{
name: 'X-Request-ID',
value: '123',
},
],
},
],
},
},
},
withApps: {
summary: 'Session Apps',
value: {
name: 'default',
apps: [
{
app: AppName.calls,
id: 'app_unique_id',
session: '',
config: {
dm: {
reject: true,
message:
"📞❌ We don't take calls right now.\n🎤 Please send a voice message or 📝 text — we'll reply ASAP!",
},
group: {
reject: true,
message:
"📞❌ We don't take calls right now.\n🎤 Please send a voice message or 📝 text — we'll reply ASAP!",
},
},
},
],
start: true,
},
},
full: {
summary: 'Full Session Example',
value: {
name: 'default',
start: true,
config: {
metadata: {
'user.id': '123',
},
debug: true,
webhooks: [
{
url: 'https://example.com/webhooks/waha',
events: ['message', 'session.status'],
hmac: {
key: 'super-secret',
},
},
],
},
apps: [
{
app: AppName.calls,
enabled: true,
config: {
dm: {
reject: true,
message:
"📞❌ We don't take calls right now.\n🎤 Please send a voice message or 📝 text — we'll reply ASAP!",
},
group: {
reject: true,
},
},
},
],
},
},
};
+32 -20
View File
@@ -1,17 +1,10 @@
import {
Body,
Controller,
Delete,
Get,
Param,
Post,
Query,
} from '@nestjs/common';
import { Body, Controller, Get, Post, UseGuards } from '@nestjs/common';
import { ApiOperation, ApiSecurity, ApiTags } from '@nestjs/swagger';
import {
SessionApiParam,
SessionParam,
WorkingSessionParam,
} from '@waha/nestjs/params/SessionApiParam';
import { NewMessageIDResponse } from '@waha/structures/chatting.dto';
import { SessionManager } from '../core/abc/manager.abc';
import { WhatsappSession } from '../core/abc/session.abc';
@@ -22,18 +15,25 @@ import {
VideoStatus,
VoiceStatus,
} from '../structures/status.dto';
import { PoliciesGuard } from '@waha/core/auth/policies.guard';
import { CheckPolicies } from '@waha/core/auth/policies.decorator';
import { CanSession, FromParam } from '@waha/core/auth/policies';
import { Action } from '@waha/core/auth/casl.types';
@ApiSecurity('api_key')
@Controller('api/:session/status')
@ApiTags('🟢 Status')
@UseGuards(PoliciesGuard)
@CheckPolicies(CanSession(Action.Use, FromParam('session')))
class StatusController {
constructor(private manager: SessionManager) {}
@Post('text')
@SessionApiParam
@ApiOperation({ summary: 'Send text status.' })
@ApiOperation({ summary: 'Send text status' })
sendTextStatus(
@SessionParam session: WhatsappSession,
@WorkingSessionParam session: WhatsappSession,
@Body() status: TextStatus,
) {
return session.sendTextStatus(status);
@@ -41,9 +41,9 @@ class StatusController {
@Post('image')
@SessionApiParam
@ApiOperation({ summary: 'Send image status.' })
@ApiOperation({ summary: 'Send image status' })
sendImageStatus(
@SessionParam session: WhatsappSession,
@WorkingSessionParam session: WhatsappSession,
@Body() status: ImageStatus,
) {
return session.sendImageStatus(status);
@@ -51,9 +51,9 @@ class StatusController {
@Post('voice')
@SessionApiParam
@ApiOperation({ summary: 'Send voice status.' })
@ApiOperation({ summary: 'Send voice status' })
sendVoiceStatus(
@SessionParam session: WhatsappSession,
@WorkingSessionParam session: WhatsappSession,
@Body() status: VoiceStatus,
) {
return session.sendVoiceStatus(status);
@@ -61,9 +61,9 @@ class StatusController {
@Post('video')
@SessionApiParam
@ApiOperation({ summary: 'Send video status.' })
@ApiOperation({ summary: 'Send video status' })
sendVideoStatus(
@SessionParam session: WhatsappSession,
@WorkingSessionParam session: WhatsappSession,
@Body() status: VideoStatus,
) {
return session.sendVideoStatus(status);
@@ -71,13 +71,25 @@ class StatusController {
@Post('delete')
@SessionApiParam
@ApiOperation({ summary: 'DELETE sent status.' })
@ApiOperation({ summary: 'DELETE sent status' })
deleteStatus(
@SessionParam session: WhatsappSession,
@WorkingSessionParam session: WhatsappSession,
@Body() status: DeleteStatusRequest,
) {
return session.deleteStatus(status);
}
@Get('new-message-id')
@SessionApiParam
@ApiOperation({
summary: 'Generate message ID you can use to batch contacts',
})
async getNewMessageId(
@WorkingSessionParam session: WhatsappSession,
): Promise<NewMessageIDResponse> {
const id = await session.generateNewMessageId();
return { id: id };
}
}
export { StatusController };
+14 -8
View File
@@ -1,20 +1,26 @@
import { Controller, Get } from '@nestjs/common';
import {
ApiExtraModels,
ApiOperation,
ApiSecurity,
ApiTags,
} from '@nestjs/swagger';
import { Controller, Get, UseGuards } from '@nestjs/common';
import { ApiOperation, ApiSecurity, ApiTags } from '@nestjs/swagger';
import { WAHAEnvironment } from '../structures/environment.dto';
import { VERSION } from '../version';
import { PoliciesGuard } from '@waha/core/auth/policies.guard';
import { CheckPolicies } from '@waha/core/auth/policies.decorator';
import { CanServer } from '@waha/core/auth/policies';
import { Action } from '@waha/core/auth/casl.types';
@ApiSecurity('api_key')
@Controller('api/version')
@ApiTags('🔍 Observability')
@UseGuards(PoliciesGuard)
@CheckPolicies(CanServer(Action.Read))
export class VersionController {
@Get('')
@ApiOperation({ summary: 'Get the version of the server', deprecated: true })
@ApiOperation({
summary: 'Get the server version ',
deprecated: true,
description: "Use 'GET /api/server/version' instead ",
})
get(): WAHAEnvironment {
return VERSION;
}
+156
View File
@@ -0,0 +1,156 @@
import {
BeforeApplicationShutdown,
Logger,
LoggerService,
} from '@nestjs/common';
import { sleep } from '@nestjs/terminus/dist/utils';
import {
OnGatewayConnection,
OnGatewayDisconnect,
OnGatewayInit,
WebSocketGateway,
WebSocketServer,
} from '@nestjs/websockets';
import { SessionManager } from '@waha/core/abc/manager.abc';
import { WebSocketAuth } from '@waha/core/auth/WebSocketAuth';
import { WebsocketHeartbeatJob } from '@waha/nestjs/ws/WebsocketHeartbeatJob';
import { WebSocket } from '@waha/nestjs/ws/ws';
import { WAHAEvents, WAHAEventsWild } from '@waha/structures/enums.dto';
import { EventWildUnmask } from '@waha/utils/events';
import { generatePrefixedId } from '@waha/utils/ids';
import { IncomingMessage } from 'http';
import { URL } from 'url';
import { Server } from 'ws';
import { CaslAbilityFactory } from '@waha/core/auth/casl.ability';
import { Action, session as SessionName } from '@waha/core/auth/casl.types';
export enum WebSocketCloseCode {
NORMAL = 1000,
GOING_AWAY = 1001,
PROTOCOL_ERROR = 1002,
UNSUPPORTED_DATA = 1003,
POLICY_VIOLATION = 1008,
INTERNAL_ERROR = 1011,
}
@WebSocketGateway({
path: '/ws',
cors: true,
})
export class WebsocketGatewayCore
implements
OnGatewayInit,
OnGatewayConnection,
OnGatewayDisconnect,
BeforeApplicationShutdown
{
HEARTBEAT_INTERVAL = 60_000;
@WebSocketServer()
server: Server;
private readonly logger: LoggerService;
private heartbeat: WebsocketHeartbeatJob;
private eventUnmask = new EventWildUnmask(WAHAEvents, WAHAEventsWild);
constructor(
private manager: SessionManager,
private auth: WebSocketAuth,
private readonly casl: CaslAbilityFactory,
) {
this.logger = new Logger('WebsocketGateway');
this.heartbeat = new WebsocketHeartbeatJob(
this.logger,
this.HEARTBEAT_INTERVAL,
);
}
async handleConnection(
socket: WebSocket,
request: IncomingMessage,
...args
): Promise<any> {
// wsc - websocket client
socket.id = generatePrefixedId('wsc');
const user = await this.auth.validateRequest(request);
if (!user) {
// Not authorized - close connection
socket.close(WebSocketCloseCode.POLICY_VIOLATION, 'Unauthorized');
this.logger.debug(
`Unauthorized websocket connection attempt: ${request.url} - ${socket.id}`,
);
return;
}
const params = this.getParams(request);
let session: string = params.session;
const ability = this.casl.createForUser(user);
if (session == '*' && !ability.can(Action.Use, 'all')) {
// Limit user to listen only the session events
session = user.session;
}
if (!ability.can(Action.Use, new SessionName(session))) {
socket.close(WebSocketCloseCode.POLICY_VIOLATION, 'Forbidden');
}
this.logger.debug(`New client connected: ${request.url} - ${socket.id}`);
const events: WAHAEvents[] = params.events;
this.logger.debug(
`Client connected to session: '${session}', events: ${events}, ${socket.id}`,
);
const sub = this.manager
.getSessionEvents(session, events)
.subscribe((data) => {
setImmediate(() => {
this.logger.debug(
`Sending data to client, event.id: ${data.id}`,
data,
);
socket.send(JSON.stringify(data), (err) => {
if (!err) {
return;
}
this.logger.error(`Error sending data to client: ${err}`);
});
});
});
socket.on('close', () => {
this.logger.debug(`Client disconnected - ${socket.id}`);
sub.unsubscribe();
});
}
private getParams(request: IncomingMessage) {
// We need only search params, so localhost is fine here
const query = new URL(request.url, 'http://localhost').searchParams;
const session = query.get('session') || '*';
const paramsEvents = query.getAll('events');
const eventsRaw = paramsEvents.length > 0 ? paramsEvents : ['*'];
const eventsList = eventsRaw.flatMap((value) => value.split(','));
const events = this.eventUnmask.unmask(eventsList);
return { session, events };
}
handleDisconnect(socket: WebSocket): any {
this.logger.debug(`Client disconnected - ${socket.id}`);
}
async beforeApplicationShutdown(signal?: string) {
this.logger.log('Shutting down websocket server');
this.heartbeat?.stop();
// Allow pending messages to be sent, it can be even 1ms, just to release the event loop
await sleep(100);
this.logger.log('Websocket server is down');
}
afterInit(server: Server) {
this.logger.debug('Websocket server initialized');
this.logger.debug('Starting heartbeat service...');
this.heartbeat.start(server);
this.logger.debug('Heartbeat service started');
}
}
+74
View File
@@ -0,0 +1,74 @@
import { JOB_DELAY, JOB_LOCK_TTL } from '@waha/apps/app_sdk/constants';
import { JobLoggerWrapper } from '@waha/apps/app_sdk/JobLoggerWrapper';
import { RMutexService } from '@waha/modules/rmutex/rmutex.service';
import { BaseWorkerHost } from '@waha/utils/bull/BaseWorkerHost';
import { Job } from 'bullmq';
import { DelayedError } from 'bullmq';
import { PinoLogger } from 'nestjs-pino';
import { Logger } from 'pino';
export interface JobDataTimeout {
timeout?: {
job?: number;
};
}
/**
* Base class for app consumers that provides common functionality
* like mutex locking, logging setup, and error handling.
*/
export abstract class AppConsumer extends BaseWorkerHost {
protected readonly logger: Logger;
constructor(
appName: string,
componentName: string,
log: PinoLogger,
protected readonly rmutex: RMutexService,
) {
super();
this.logger = log.logger.child({
app: appName,
component: componentName,
});
}
protected signal(job) {
if (job.data?.timeout?.job != null) {
return AbortSignal.timeout(job.data?.timeout?.job);
}
// Aka never abort
return new AbortController().signal;
}
/**
* Executes a job with mutex locking to ensure only one job processes a specific resource at a time.
* @param job The job to process
* @param mutexKey The key to use for mutex locking
* @param processor The function that processes the job
* @returns The result of the processor function
*/
protected async withMutex<T, R>(
job: Job<T, R, any>,
mutexKey: string,
processor: () => Promise<R>,
): Promise<R> {
const mutex = this.rmutex.get(mutexKey, JOB_LOCK_TTL);
const lock = await mutex.lock();
if (!lock) {
const logger = new JobLoggerWrapper(job, this.logger);
logger.debug(
`Postponing job '${job.id}' for ${JOB_DELAY}ms, another job is already running the mutex.key='${mutexKey}'`,
);
await job.moveToDelayed(Date.now() + JOB_DELAY);
throw new DelayedError();
}
try {
return await processor();
} finally {
await lock.release();
}
}
}
+67
View File
@@ -0,0 +1,67 @@
import { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios';
import { ILogger } from './ILogger';
export class AxiosLogging {
constructor(private readonly logger: ILogger) {}
applyTo(instance: AxiosInstance) {
instance.interceptors.request.use(this.onRequest.bind(this));
instance.interceptors.response.use(
this.onResponse.bind(this),
this.onError.bind(this),
);
}
private onRequest(config: AxiosRequestConfig) {
const method = config.method?.toUpperCase() ?? 'GET';
const url = config.url ?? '';
const query = config.params
? `?${new URLSearchParams(config.params).toString()}`
: '';
const body = config.data ? JSON.stringify(config.data) : '';
this.logger.debug(`${method} ${url}${query} ${body}`);
return config;
}
private onResponse(response: AxiosResponse) {
const { method, url } = response.config;
const status = response.status;
const methodStr = method?.toUpperCase() ?? 'GET';
const type: any = response.headers['content-type'];
// Log warnings for non-2xx/3xx responses
if (status < 200 || status >= 400) {
const data = JSON.stringify(response.data);
this.logger.warn(`${methodStr} ${status} ${url} ${data}`);
return response;
}
// Log debug for 2xx/3xx responses
this.logger.debug(`${methodStr} ${status}:OK ${url}`);
if (type?.startsWith('application/json')) {
const data = JSON.stringify(response.data);
this.logger.trace(`${methodStr} ${status}:OK ${url} ${data}`);
} else {
this.logger.trace(`${methodStr} ${status}:OK ${url} [${type}]`);
}
return response;
}
private onError(error: any) {
if (error.response) {
const { method, url } = error.config;
const status = error.response.status;
const message = `${
method?.toUpperCase() ?? 'GET'
} ${status} ${url} ${JSON.stringify(error.response.data)}`;
this.logger.error(message);
} else {
this.logger.error(`Axios error: ${error.message}`);
}
return Promise.reject(error);
}
}
+26
View File
@@ -0,0 +1,26 @@
import { BullMQAdapter } from '@bull-board/api/bullMQAdapter';
import { BullBoardModule } from '@bull-board/nestjs';
import { BullModule } from '@nestjs/bullmq';
import { RegisterQueueOptions } from '@nestjs/bullmq/dist/interfaces/register-queue-options.interface';
import { DynamicModule } from '@nestjs/common';
/**
* Registers a queue with both BullModule and BullBoardModule
* This ensures that whenever a queue is registered, it's also added to the Bull Board
*
* @param options Queue options including name
* @returns An array of dynamic modules for both BullModule and BullBoardModule
*/
export function RegisterAppQueue(
options: RegisterQueueOptions,
): DynamicModule[] {
return [
BullModule.registerQueue({
...options,
}),
BullBoardModule.forFeature({
name: options.name,
adapter: BullMQAdapter,
}),
];
}
+8
View File
@@ -0,0 +1,8 @@
export interface ILogger {
fatal(log: string): void;
error(log: string): void;
warn(log: string): void;
info(log: string): void;
debug(log: string): void;
trace(log: string): void;
}
+58
View File
@@ -0,0 +1,58 @@
import { Job } from 'bullmq';
import { Logger } from 'pino';
import { ILogger } from './ILogger';
/**
* JobLoggerWrapper is a wrapper around the BullMQ Job class that provides
* a logger with the job ID as a child context.
* It allows logging messages to the job's log
* and also to the logger itself.
*/
export class JobLoggerWrapper implements ILogger {
private readonly logger: Logger;
constructor(
private job: Job,
logger: Logger,
) {
this.logger = logger.child({ jobId: job.id });
}
fatal(log: string): void {
this.log(log, 'fatal');
}
error(log: string): void {
this.log(log, 'error');
}
warn(log: string): void {
this.log(log, 'warn');
}
info(log: string): void {
this.log(log, 'info');
}
debug(log: string): void {
this.log(log, 'debug');
}
trace(log: string): void {
this.log(log, 'trace');
}
private log(msg: string, level: string) {
if (!this.logger.isLevelEnabled(level)) {
return;
}
this.logger[level](msg);
const timestamp = new Date().toISOString();
this.job
.log(`[${timestamp}] ${level.toUpperCase()}: ${msg}`)
.catch((err) => {
this.logger.error('Error logging message to job', err);
});
}
}
+108
View File
@@ -0,0 +1,108 @@
import { Backoffs, Job } from 'bullmq';
import { FlowJob } from 'bullmq/dist/esm/interfaces';
/**
* Calculates the delay until the next attempt for a job.
*
* @param job The job to calculate the next attempt delay for
* @returns The delay in milliseconds, or null if there is no next attempt or backoff is not set
*/
export function NextAttemptDelayInMs(job: Job): number | null {
const attemptsMade = job.attemptsMade + 1;
const maxAttempts = job.opts?.attempts || 1;
// If this is the last attempt, return null
if (attemptsMade >= maxAttempts) {
return null;
}
// Normalize the backoff options
const backoff = Backoffs.normalize(job.opts.backoff);
if (!backoff) {
return null;
}
// Calculate the delay using Backoffs.calculate
const delay = Backoffs.calculate(backoff, attemptsMade, new Error(), job);
// The delay can be a Promise, a number, or undefined
if (delay === undefined) {
return null;
}
// If it's a Promise, we can't handle it synchronously, so return null
if (delay instanceof Promise) {
return null;
}
return delay;
}
export function NextAttemptDelayInWholeSeconds(job: Job) {
const delay = NextAttemptDelayInMs(job);
if (delay == null) {
return null;
}
return Math.round(delay / 1000);
}
/**
* Checks if a job has been retried (not on its first attempt).
*
* @param job The job to check
* @returns True if the job has been retried (attemptsMade > 1), false otherwise
*/
export function HasBeenRetried(job: Job): boolean {
const attemptsMade = job.attemptsMade + 1;
return attemptsMade > 1;
}
/**
* Hide chatwoot and waha from queue name for user faced messages
*/
export function QueueNameRepr(name: string): string {
// No "chatwoot" in the name always (it start with chatwoot)
name = name.replace('chatwoot.', '');
// Replace "waha" with "whatsapp"
name = name.replace('waha |', 'whatsapp |');
return name;
}
let base =
process.env.WAHA_PUBLIC_URL ||
process.env.WAHA_BASE_URL ||
'http://localhost:3000';
// cut / at the end
base = base.replace(/\/+$/, '');
export function JobLink(job: Job): { text: string; url: string } {
// Use repr name for text
const name = QueueNameRepr(job.queueName);
const text = `${name} => ${job.id}`;
// Use original queue name in the URL
const queue = encodeURIComponent(job.queueName);
const id = encodeURIComponent(job.id);
const url = `${base}/jobs/queue/${queue}/${id}`;
return { text: text, url: url };
}
/**
* Chain jobs so that they run one at a time
* A | B | C
* to
* A -> B -> C
*/
export function ChainJobsOneAtATime(jobs: FlowJob[]): FlowJob {
if (jobs.length == 0) {
throw new Error('No jobs provided');
}
for (const job of jobs) {
if (job.children.length != 0) {
throw new Error('Jobs with children are not supported');
}
}
const left = [...jobs];
const root = left.pop();
let parent = root;
while (left.length != 0) {
const job = left.pop();
parent.children = [job];
parent = job;
}
return root;
}
+132
View File
@@ -0,0 +1,132 @@
import {
Body,
Controller,
Delete,
ForbiddenException,
Get,
Inject,
NotFoundException,
Param,
Post,
Put,
Query,
Req,
UseGuards,
UsePipes,
} from '@nestjs/common';
import { ApiOperation, ApiSecurity, ApiTags } from '@nestjs/swagger';
import {
AppsService,
IAppsService,
} from '@waha/apps/app_sdk/services/IAppsService';
import { SessionManager } from '@waha/core/abc/manager.abc';
import { CheckPolicies } from '@waha/core/auth/policies.decorator';
import { PoliciesGuard } from '@waha/core/auth/policies.guard';
import { CanSession, FromBody, FromQuery } from '@waha/core/auth/policies';
import { Action, session as SessionName } from '@waha/core/auth/casl.types';
import { WAHAValidationPipe } from '@waha/nestjs/pipes/WAHAValidationPipe';
import { App } from '../dto/app.dto';
import { ListAppsQuery } from '../dto/query.dto';
@ApiSecurity('api_key')
@Controller('api/apps')
@ApiTags('🧩 Apps')
@UseGuards(PoliciesGuard)
export class AppsController {
constructor(
@Inject(AppsService)
private appsService: IAppsService,
private manager: SessionManager,
) {}
@Get('/')
@ApiOperation({ summary: 'List all apps for a session' })
@CheckPolicies(CanSession(Action.Use, FromQuery('session')))
@UsePipes(new WAHAValidationPipe())
async list(
@Query(new WAHAValidationPipe()) query: ListAppsQuery,
): Promise<App[]> {
return this.appsService.list(this.manager, query.session);
}
@Post('/')
@ApiOperation({ summary: 'Create a new app' })
@CheckPolicies(CanSession(Action.Use, FromBody('session')))
@UsePipes(new WAHAValidationPipe())
async create(@Body() app: App): Promise<App> {
const result = await this.appsService.create(this.manager, app);
const isRunning = this.manager.isRunning(app.session);
if (isRunning && app.enabled) {
await this.manager.restart(app.session);
}
return result;
}
@Get('/:id')
@ApiOperation({ summary: 'Get app by ID' })
@UsePipes(new WAHAValidationPipe())
async get(@Param('id') id: string, @Req() req: any): Promise<App> {
const app = await this.appsService.get(this.manager, id);
if (!app) {
throw new NotFoundException(`App '${id}' not found`);
}
if (!req.ability?.can(Action.Use, new SessionName(app.session))) {
throw new ForbiddenException();
}
return app;
}
@Put('/:id')
@ApiOperation({ summary: 'Update an existing app' })
@UsePipes(new WAHAValidationPipe())
async update(
@Param('id') id: string,
@Body() app: App,
@Req() req: any,
): Promise<App> {
const existing = await this.appsService.get(this.manager, id);
if (existing) {
if (!req.ability?.can(Action.Use, new SessionName(existing.session))) {
throw new ForbiddenException();
}
} else {
if (!req.ability?.can(Action.Use, new SessionName(app.session))) {
throw new ForbiddenException();
}
}
if (!app.id) {
app.id = id;
} else if (app.id !== id) {
throw new NotFoundException(
`App ID in path (${id}) does not match ID in body (${app.id})`,
);
}
const result = await this.appsService.upsert(this.manager, app);
const isRunning = this.manager.isRunning(app.session);
if (isRunning) {
await this.manager.restart(app.session);
}
return result;
}
@Delete('/:id')
@ApiOperation({ summary: 'Delete an app' })
@UsePipes(new WAHAValidationPipe())
async delete(@Param('id') id: string, @Req() req: any): Promise<void> {
const existing = await this.appsService.get(this.manager, id);
if (!existing) {
throw new NotFoundException(`App '${id}' not found`);
}
if (!req.ability?.can(Action.Use, new SessionName(existing.session))) {
throw new ForbiddenException();
}
const app = await this.appsService.delete(this.manager, id);
const isRunning = this.manager.isRunning(app.session);
if (isRunning) {
await this.manager.restart(app.session);
}
}
}
+57
View File
@@ -0,0 +1,57 @@
import { AppEnv } from '@waha/apps/app_sdk/env';
import { AppDefinition, APPS } from '@waha/apps/app_sdk/apps/definition';
import { AppName } from '@waha/apps/app_sdk/apps/name';
class AppRuntimeConfigC {
private constructor(private apps: AppDefinition[] | null) {}
static FromEnv(env: typeof AppEnv) {
if (!env.enabled) {
return new AppRuntimeConfigC(null);
}
let apps = Object.values(APPS);
// Include
if (env.on && env.on.length > 0) {
apps = apps.filter((app) => env.on!.includes(app.name));
}
// Exclude
if (env.off && env.off.length > 0) {
apps = apps.filter((app) => !env.off!.includes(app.name));
}
return new AppRuntimeConfigC(apps);
}
Enabled() {
return this.apps !== null;
}
GetApps() {
return this.apps || [];
}
GetAppsWithMigration() {
return this.GetApps().filter((app) => app.migrations);
}
GetAppsRequiringPlainKey() {
return this.GetApps().filter((app) => app.plainkey);
}
GetAppsRequiringQueue() {
return this.GetApps().filter((app) => app.queue);
}
HasApp(name: AppName) {
return this.GetApps().some((app) => app.name === name);
}
HasAppsRequiringPlainKey() {
return this.GetAppsRequiringPlainKey().length > 0;
}
HasAppsRequiringQueue() {
return this.GetAppsRequiringQueue().length > 0;
}
}
export const AppRuntimeConfig = AppRuntimeConfigC.FromEnv(AppEnv);
+28
View File
@@ -0,0 +1,28 @@
import { AppName } from '@waha/apps/app_sdk/apps/name';
export interface AppDefinition {
// App name
name: AppName;
// If app requires WAHA_API_KEY_PLAIN to work
plainkey: boolean;
// If app requires queue to work
queue: boolean;
// If app has any migrations
migrations: boolean;
}
// All Apps
export const APPS: Record<AppName, AppDefinition> = {
[AppName.calls]: {
name: AppName.calls,
plainkey: false,
queue: false,
migrations: false,
},
[AppName.chatwoot]: {
name: AppName.chatwoot,
plainkey: true,
queue: true,
migrations: true,
},
};
+4
View File
@@ -0,0 +1,4 @@
export enum AppName {
chatwoot = 'chatwoot',
calls = 'calls',
}
+25
View File
@@ -0,0 +1,25 @@
import * as process from 'node:process';
import { parseBool } from '@waha/helpers';
import * as basicAuth from 'express-basic-auth';
import { Auth } from '@waha/core/auth/config';
export function BullAuthMiddleware() {
let username = Auth.dashboard.username.value || '';
let password = Auth.dashboard.password.value || '';
if (process.env.WAHA_DASHBOARD_ENABLED) {
const enabled = parseBool(process.env.WAHA_DASHBOARD_ENABLED);
if (!enabled) {
// Generate a random uuid4 username/password to effectively disable access
username = 'admin';
password = crypto.randomUUID();
}
}
return basicAuth({
challenge: true,
users: {
[String(username)]: String(password),
},
});
}
+47
View File
@@ -0,0 +1,47 @@
import { DefaultJobOptions } from 'bullmq';
export const JOB_DELAY = 1_000;
export const JOB_LOCK_TTL = 20_000;
export const JOB_CONCURRENCY =
parseInt(process.env.WAHA_APPS_JOBS_CONCURRENCY) || 50;
function jobRemoveOptions() {
// in seconds
const HOUR = 60 * 60;
const DAY = 24 * HOUR;
return {
removeOnComplete: {
age:
parseInt(process.env.WAHA_APPS_JOBS_REMOVE_ON_COMPLETE_AGE) || 3 * DAY,
count:
parseInt(process.env.WAHA_APPS_JOBS_REMOVE_ON_COMPLETE_COUNT) || 1000,
},
removeOnFail: {
age: parseInt(process.env.WAHA_APPS_JOBS_REMOVE_ON_FAIL_AGE) || 31 * DAY,
count: parseInt(process.env.WAHA_APPS_JOBS_REMOVE_ON_FAIL_COUNT) || 1000,
},
};
}
export const JobRemoveOptions = jobRemoveOptions();
function exponentialRetriesJobOptions(): DefaultJobOptions {
return {
attempts: parseInt(process.env.WAHA_APPS_JOBS_ATTEMPTS) || 3,
delay: parseInt(process.env.WAHA_APPS_JOBS_DELAY) || 0,
backoff: {
type: process.env.WAHA_APPS_JOBS_BACKOFF_TYPE || 'exponential',
delay: parseInt(process.env.WAHA_APPS_JOBS_BACKOFF_DELAY) || 1000,
},
};
}
export const ExponentialRetriesJobOptions = exponentialRetriesJobOptions();
export const NoRetriesJobOptions: DefaultJobOptions = {
attempts: 1,
};
export function merge(...args: any[]) {
return Object.assign({}, ...args);
}
+65
View File
@@ -0,0 +1,65 @@
import { ChatWootAppConfig } from '@waha/apps/chatwoot/dto/config.dto';
import { CallsAppConfig } from '@waha/apps/calls/dto/config.dto';
import { Type } from 'class-transformer';
import {
IsBoolean,
IsEnum,
IsOptional,
IsString,
ValidateNested,
} from 'class-validator';
import { ApiExtraModels, ApiProperty } from '@nestjs/swagger';
import { AppName } from '@waha/apps/app_sdk/apps/name';
export type AllowedAppConfig = ChatWootAppConfig | CallsAppConfig;
@ApiExtraModels(ChatWootAppConfig, CallsAppConfig)
export class App<T extends AllowedAppConfig = any> {
@IsString()
id: string;
@IsString()
session: string;
// App name (aka type)
@IsEnum(AppName)
app: AppName;
@ApiProperty({
description:
'Enable or disable this app without deleting it. If omitted, treated as enabled (true).',
required: false,
default: true,
})
@IsOptional()
@IsBoolean()
enabled?: boolean = true;
@ValidateNested()
@Type((options) => {
if (options && options.object && options.object.app) {
switch (options.object.app) {
case AppName.chatwoot:
return ChatWootAppConfig;
case AppName.calls:
return CallsAppConfig;
default:
return Object;
}
}
return Object;
})
config: T;
}
export class ChatWootAppDto extends App<ChatWootAppConfig> {
@Type(() => ChatWootAppConfig)
config: ChatWootAppConfig;
}
export class CallsAppDto extends App<CallsAppConfig> {
@Type(() => CallsAppConfig)
config: CallsAppConfig;
}
export type AppDto = ChatWootAppDto | CallsAppDto;
+13
View File
@@ -0,0 +1,13 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsNotEmpty, IsString } from 'class-validator';
export class ListAppsQuery {
@ApiProperty({
example: 'default',
required: true,
description: 'Session name to list apps for',
})
@IsString()
@IsNotEmpty()
session: string;
}
+14
View File
@@ -0,0 +1,14 @@
import { parseBool } from '@waha/helpers';
function parseCommaSeparatedList(value: string | undefined): string[] {
if (!value) {
return null;
}
return value.split(',').map((item) => item.trim());
}
export const AppEnv = {
enabled: parseBool(process.env.WAHA_APPS_ENABLED),
on: parseCommaSeparatedList(process.env.WAHA_APPS_ON),
off: parseCommaSeparatedList(process.env.WAHA_APPS_OFF),
};
+12
View File
@@ -0,0 +1,12 @@
import { ILogger } from '@waha/apps/app_sdk/ILogger';
export function JestLogger(): ILogger {
return {
fatal: jest.fn(),
error: jest.fn(),
warn: jest.fn(),
info: jest.fn(),
debug: jest.fn(),
trace: jest.fn(),
};
}
+32
View File
@@ -0,0 +1,32 @@
import * as path from 'node:path';
import { Knex } from 'knex';
import { AppRuntimeConfig } from '@waha/apps/app_sdk/apps/AppRuntime';
function migrateSDK(knex: Knex): Promise<any> {
const config = {
directory: `${__dirname}/migrations`,
tableName: 'apps_migrations',
loadExtensions: ['.js'],
};
return knex.migrate.latest(config);
}
function migrateApp(knex: Knex, app: string): Promise<any> {
// one level upp
const appsDirectory = path.join(__dirname, '..', '..', 'apps');
const directory = path.join(appsDirectory, app, 'migrations');
const config = {
directory: directory,
tableName: `app_${app}_migrations`,
loadExtensions: ['.js'],
};
return knex.migrate.latest(config);
}
export async function migrate(knex: Knex) {
await migrateSDK(knex);
for (const app of AppRuntimeConfig.GetAppsWithMigration()) {
await migrateApp(knex, app.name);
}
}
@@ -0,0 +1,22 @@
import { Knex } from 'knex';
exports.up = function (knex: Knex) {
return knex.schema
.createTable('apps', function (table) {
table.increments('pk');
table.string('id', 64);
table.string('session', 64);
table.string('app', 64);
table.json('config');
})
.table('apps', function (table) {
table.unique(['pk']);
table.index(['session']);
table.index(['id']);
table.unique(['id']);
});
};
exports.down = function (knex: Knex) {
return knex.schema.dropTable('apps');
};
@@ -0,0 +1,26 @@
import { Knex } from 'knex';
exports.up = async function (knex: Knex) {
// Add a boolean column 'enabled' defaulting to true and not nullable.
const hasColumn = await knex.schema.hasColumn('apps', 'enabled');
if (!hasColumn) {
await knex.schema.alterTable('apps', (table) => {
table.boolean('enabled').defaultTo(true);
});
// Ensure existing rows have enabled=true
await knex('apps').update({ enabled: true });
// Make the column NOT NULL
await knex.schema.alterTable('apps', (table) => {
table.boolean('enabled').notNullable().defaultTo(true).alter();
});
}
};
exports.down = async function (knex: Knex) {
const hasColumn = await knex.schema.hasColumn('apps', 'enabled');
if (hasColumn) {
await knex.schema.alterTable('apps', (table) => {
table.dropColumn('enabled');
});
}
};
@@ -0,0 +1,67 @@
import { Injectable, UnprocessableEntityException } from '@nestjs/common';
import { IAppsService } from '@waha/apps/app_sdk/services/IAppsService';
import { DataStore } from '@waha/core/abc/DataStore';
import { SessionManager } from '@waha/core/abc/manager.abc';
import { WhatsappSession } from '@waha/core/abc/session.abc';
import { Knex } from 'knex';
import { App } from '../dto/app.dto';
class AppsIsDisabledError extends UnprocessableEntityException {
constructor() {
super(
"Apps are disabled. Enable it by setting 'WAHA_APPS_ENABLED=True' environment variable.",
);
}
}
@Injectable()
export class AppsDisabledService implements IAppsService {
async list(manager: SessionManager, session: string): Promise<App[]> {
throw new AppsIsDisabledError();
}
async get(manager: SessionManager, appId: string): Promise<App | null> {
throw new AppsIsDisabledError();
}
async create(manager: SessionManager, app: App): Promise<App> {
throw new AppsIsDisabledError();
}
async upsert(manager: SessionManager, app: App): Promise<App> {
throw new AppsIsDisabledError();
}
async update(manager: SessionManager, app: App): Promise<App> {
throw new AppsIsDisabledError();
}
async delete(manager: SessionManager, appId: string): Promise<App> {
throw new AppsIsDisabledError();
}
async removeBySession(manager: SessionManager, session: string) {
return;
}
migrate(knex: Knex<any, any[]>): Promise<void> {
return;
}
async beforeSessionStart(session: WhatsappSession, store: DataStore) {
return;
}
async afterSessionStart(session: WhatsappSession, store: DataStore) {
return;
}
async syncSessionApps(
manager: SessionManager,
sessionName: string,
apps?: App[] | null,
): Promise<void> {
throw new AppsIsDisabledError();
}
}
@@ -0,0 +1,274 @@
import {
Injectable,
NotFoundException,
Optional,
UnprocessableEntityException,
} from '@nestjs/common';
import { migrate } from '@waha/apps/app_sdk/migrations';
import { IAppService } from '@waha/apps/app_sdk/services/IAppService';
import { IAppsService } from '@waha/apps/app_sdk/services/IAppsService';
import { ChatWootAppService } from '@waha/apps/chatwoot/services/ChatWootAppService';
import { CallsAppService } from '@waha/apps/calls/services/CallsAppService';
import { DataStore } from '@waha/core/abc/DataStore';
import { SessionManager } from '@waha/core/abc/manager.abc';
import { WhatsappSession } from '@waha/core/abc/session.abc';
import { generatePrefixedId } from '@waha/utils/ids';
import { Knex } from 'knex';
import { InjectPinoLogger, PinoLogger } from 'nestjs-pino';
import { App } from '../dto/app.dto';
import { AppRepository } from '../storage/AppRepository';
import { AppName } from '@waha/apps/app_sdk/apps/name';
import { AppRuntimeConfig } from '@waha/apps/app_sdk/apps/AppRuntime';
export class AppDisableError extends UnprocessableEntityException {
constructor(app: string) {
super(
`App '${app}' is disabled in runtime configuration - adjust WAHA_APPS_ON / WAHA_APPS_OFF environment variables to enable it.`,
);
}
}
@Injectable()
export class AppsEnabledService implements IAppsService {
constructor(
@InjectPinoLogger('AppsService')
protected logger: PinoLogger,
@Optional() protected readonly chatwootService: ChatWootAppService,
@Optional() protected readonly callsAppService: CallsAppService,
) {}
async list(manager: SessionManager, session: string): Promise<App[]> {
const knex = manager.store.getWAHADatabase();
const repo = new AppRepository(knex);
const apps = await repo.getAllBySession(session);
apps.forEach((app) => {
delete app.pk;
});
return apps;
}
async create(manager: SessionManager, app: App): Promise<App> {
await this.checkSessionExists(manager, app.session);
app.id = app.id || generatePrefixedId('app');
const knex = manager.store.getWAHADatabase();
const repo = new AppRepository(knex);
const existingApp = await repo.getById(app.id);
if (existingApp) {
throw new Error(`App with ID '${app.id}' already exists.`);
}
let existingApps: App[] = [];
if (app.app === AppName.chatwoot || app.app === AppName.calls) {
existingApps = await repo.getAllBySession(app.session);
}
// Validate only one Chatwoot app per session
if (app.app === AppName.chatwoot) {
const existingChatwootApp = existingApps.find(
(existingApp) => existingApp.app === AppName.chatwoot,
);
if (existingChatwootApp) {
throw new Error(
`Only one Chatwoot app is allowed per session. Session '${app.session}' already has a Chatwoot app with ID '${existingChatwootApp.id}'.`,
);
}
}
// Validate only one Calls app per session
if (app.app === AppName.calls) {
const existingCallsApp = existingApps.find(
(existingApp) => existingApp.app === AppName.calls,
);
if (existingCallsApp) {
throw new Error(
`Only one Calls app is allowed per session. Session '${app.session}' already has a Calls app with ID '${existingCallsApp.id}'.`,
);
}
}
const service = this.getAppService(app);
if (!service && !AppRuntimeConfig.HasApp(app.app)) {
throw new AppDisableError(app.app);
}
service.validate(app);
// Only run beforeCreated when app is enabled (default true if omitted)
if (app.enabled !== false) {
await service.beforeCreated(app);
}
const result = await repo.save(app);
delete result.pk;
return result;
}
async get(manager: SessionManager, appId: string): Promise<App | null> {
const knex = manager.store.getWAHADatabase();
const repo = new AppRepository(knex);
const app = await repo.getById(appId);
if (!app) {
return null;
}
delete (app as any).pk;
return app;
}
async upsert(manager: SessionManager, app: App) {
return await this.update(manager, app, true);
}
async update(
manager: SessionManager,
app: App,
upsert: boolean = false,
): Promise<App> {
await this.checkSessionExists(manager, app.session);
const knex = manager.store.getWAHADatabase();
const repo = new AppRepository(knex);
const savedApp = await repo.getById(app.id);
if (!savedApp && upsert) {
return this.create(manager, app);
}
if (!savedApp) {
throw new NotFoundException(`App '${app.id}' not found`);
}
if (savedApp.app != app.app) {
throw new Error(
`Can not change app type. Delete and create a new app. Before type: '${savedApp.app}' After type: '${app.app}'`,
);
}
if (savedApp.session != app.session) {
throw new Error(
`Can not change session. Delete and create a new app. Before session: '${savedApp.session}' After session: '${app.session}'`,
);
}
const service = this.getAppService(app);
if (!service && !AppRuntimeConfig.HasApp(app.app)) {
throw new AppDisableError(app.app);
}
service.validate(app);
const hasEnabledChange = savedApp.enabled !== app.enabled;
if (hasEnabledChange) {
if (app.enabled) {
await service.beforeEnabled(savedApp, app);
} else {
await service.beforeDisabled(savedApp, app);
}
} else {
await service.beforeUpdated(savedApp, app);
}
await repo.update(app.id, app);
const updated = await repo.getById(app.id);
delete (updated as any)?.pk;
return updated!;
}
async delete(manager: SessionManager, appId: string): Promise<App> {
const knex = manager.store.getWAHADatabase();
const repo = new AppRepository(knex);
const app = await repo.getById(appId);
if (!app) {
throw new NotFoundException(`App '${appId}' not found`);
}
const service = this.getAppService(app);
await service?.beforeDeleted(app);
await repo.delete(app.id);
delete app.pk;
return app;
}
async removeBySession(manager: SessionManager, session: string) {
const knex = manager.store.getWAHADatabase();
const repo = new AppRepository(knex);
await repo.deleteBySession(session);
}
async beforeSessionStart(session: WhatsappSession, store: DataStore) {
const knex = store.getWAHADatabase();
const repo = new AppRepository(knex);
const apps = await repo.getEnabledBySession(session.name);
for (const app of apps) {
const service = this.getAppService(app);
if (!service && !AppRuntimeConfig.HasApp(app.app)) {
throw new AppDisableError(app.app);
}
service.beforeSessionStart(app, session);
}
}
async afterSessionStart(session: WhatsappSession, store: DataStore) {
const knex = store.getWAHADatabase();
const repo = new AppRepository(knex);
const apps = await repo.getEnabledBySession(session.name);
for (const app of apps) {
const service = this.getAppService(app);
if (!service && !AppRuntimeConfig.HasApp(app.app)) {
throw new AppDisableError(app.app);
}
service.afterSessionStart(app, session);
}
}
async syncSessionApps(
manager: SessionManager,
session: string,
apps: App[],
): Promise<void> {
const existing = await this.list(manager, session);
const ids = new Set<string>();
// Upsert provided apps
for (const app of apps) {
if (!app.id) {
// Try to find the app by type
const found = existing.find((a) => a.app === app.app);
if (found) {
app.id = found.id;
}
}
// Force session
app.session = session;
await this.upsert(manager, app);
ids.add(app.id);
}
// Remove apps that are not in the provided list
for (const app of existing) {
if (ids.has(app.id)) {
continue;
}
await this.delete(manager, app.id);
}
}
async migrate(knex: Knex): Promise<void> {
await migrate(knex);
}
private getAppService(app: App): IAppService | null {
switch (app.app) {
case AppName.chatwoot:
return this.chatwootService;
case AppName.calls:
return this.callsAppService;
default:
throw new Error(`App '${app.app}' not supported`);
}
}
private async checkSessionExists(
manager: SessionManager,
sessionName: string,
) {
const session = await manager.exists(sessionName);
if (session === null) {
throw new NotFoundException('Session not found');
}
}
}
+29
View File
@@ -0,0 +1,29 @@
import { App } from '@waha/apps/app_sdk/dto/app.dto';
import { WhatsappSession } from '@waha/core/abc/session.abc';
/**
* Exact App service
*/
export interface IAppService {
validate(app: App): void;
beforeCreated(app: App): Promise<void>;
/**
* Called only when the app transitions from disabled -> enabled.
*/
beforeEnabled(savedApp: App, newApp: App): Promise<void>;
/**
* Called only when the app transitions from enabled -> disabled.
*/
beforeDisabled(savedApp: App, newApp: App): Promise<void>;
beforeUpdated(savedApp: App, newApp: App): Promise<void>;
beforeDeleted(app: App): Promise<void>;
beforeSessionStart(app: App, session: WhatsappSession): void;
afterSessionStart(app: App, session: WhatsappSession): void;
}
+35
View File
@@ -0,0 +1,35 @@
import { App } from '@waha/apps/app_sdk/dto/app.dto';
import { DataStore } from '@waha/core/abc/DataStore';
import { SessionManager } from '@waha/core/abc/manager.abc';
import { WhatsappSession } from '@waha/core/abc/session.abc';
import { Knex } from 'knex';
export interface IAppsService {
list(manager: SessionManager, session: string): Promise<App[]>;
get(manager: SessionManager, appId: string): Promise<App | null>;
create(manager: SessionManager, app: App): Promise<App>;
upsert(manager: SessionManager, app: App): Promise<App>;
update(manager: SessionManager, app: App): Promise<App>;
delete(manager: SessionManager, appId: string): Promise<App>;
removeBySession(manager: SessionManager, session: string): Promise<void>;
beforeSessionStart(session: WhatsappSession, store: DataStore): Promise<void>;
afterSessionStart(session: WhatsappSession, store: DataStore): Promise<void>;
syncSessionApps(
manager: SessionManager,
sessionName: string,
apps?: App[] | null,
): Promise<void>;
migrate(knex: Knex): Promise<void>;
}
export const AppsService = Symbol('AppsService');
+133
View File
@@ -0,0 +1,133 @@
import { Knex } from 'knex';
import { App } from '../dto/app.dto';
import { AppDB } from './types';
export class AppRepository {
static tableName = 'apps';
constructor(private readonly knex: Knex) {}
get tableName() {
return AppRepository.tableName;
}
/**
* Saves an app to the database
*/
async save(app: Omit<App, 'pk'>): Promise<AppDB> {
const appToSave: any = { ...app };
if (appToSave.config && typeof appToSave.config === 'object') {
appToSave.config = JSON.stringify(appToSave.config);
}
const [pk] = await this.knex(this.tableName)
.insert(appToSave)
.returning('pk');
return { ...app, pk: pk };
}
private deserialize(app: AppDB): AppDB {
const parsedConfig =
typeof app.config === 'string' ? JSON.parse(app.config) : app.config;
// Ensure 'enabled' is boolean (SQLite may return 0/1)
let enabled: boolean;
switch (app.enabled as any) {
case undefined:
case true:
case 1:
case '1':
enabled = true;
break;
case false:
case 0:
enabled = false;
break;
default:
enabled = true;
}
return { ...(app as any), enabled: enabled, config: parsedConfig } as AppDB;
}
private serialize<T extends Partial<App>>(app: T): T {
const appCopy = { ...app };
if (appCopy.config && typeof appCopy.config === 'object') {
appCopy.config = JSON.stringify(appCopy.config);
}
return appCopy;
}
/**
* Gets an app by its id
*/
async getById(id: string): Promise<AppDB | null> {
const app = await this.knex(this.tableName).where('id', id).first();
if (!app) {
return null;
}
return this.deserialize(app);
}
/**
* Gets an enabled app by id, or null if not found or disabled.
*/
async findEnabledAppById(id: string): Promise<AppDB | null> {
const app = await this.knex(this.tableName)
.where('id', id)
.andWhere('enabled', true)
.first();
if (!app) {
return null;
}
return this.deserialize(app);
}
/**
* Gets all apps
*/
async getAllBySession(session: string): Promise<AppDB[]> {
return this.knex(this.tableName)
.select('*')
.where('session', session)
.orderBy('id', 'asc')
.then((apps) => apps.map((app) => this.deserialize(app)));
}
/**
* Gets only enabled apps for a session.
*/
async getEnabledBySession(session: string): Promise<AppDB[]> {
return this.knex(this.tableName)
.select('*')
.where('session', session)
.andWhere('enabled', true)
.orderBy('id', 'asc')
.then((apps) => apps.map((app) => this.deserialize(app)));
}
/**
* Updates an app
*/
async update(id: string, app: Partial<Omit<App, 'id'>>): Promise<void> {
const appToUpdate: any = this.serialize(app);
if (appToUpdate.enabled === undefined) {
delete appToUpdate.enabled; // keep current value
}
await this.knex(this.tableName).where('id', id).update(appToUpdate);
}
/**
* Deletes an app
*/
async delete(id: string): Promise<void> {
await this.knex(this.tableName).where('id', id).delete();
}
/**
* Deletes all apps for a session
*/
async deleteBySession(session: string): Promise<void> {
await this.knex(this.tableName).where('session', session).delete();
}
}
+6
View File
@@ -0,0 +1,6 @@
import { App } from '@waha/apps/app_sdk/dto/app.dto';
export interface AppDB extends App {
// Internal database number
pk: number;
}
+43
View File
@@ -0,0 +1,43 @@
import * as lodash from 'lodash';
type Call<Record> = (processed: number) => Promise<Record[]>;
export interface PaginatorParams {
processed?: number;
max?: number;
}
const DefaultParams: PaginatorParams = {
processed: 0,
max: Infinity,
};
/**
* Generic paginator for APIs that return arrays of records with limits and offsets.
*/
export class ArrayPaginator<Record> {
private readonly params: PaginatorParams;
constructor(params: PaginatorParams = DefaultParams) {
this.params = lodash.merge({}, DefaultParams, params) as PaginatorParams;
}
async *iterate(call: Call<Record>): AsyncGenerator<Record> {
let processed = this.params.processed;
let records: Record[] = [];
while (true) {
records = await call(processed);
if (records.length === 0) {
return;
}
for (const record of records) {
yield record;
processed += 1;
if (processed >= this.params.max) {
return;
}
}
}
}
}
+481
View File
@@ -0,0 +1,481 @@
import { Channel } from '@waha/structures/channels.dto';
import {
GetChatMessagesFilter,
GetChatMessagesQuery,
} from '@waha/structures/chats.dto';
import {
ChatRequest,
MessageFileRequest,
MessageImageRequest,
MessageTextRequest,
MessageVideoRequest,
MessageVoiceRequest,
WANumberExistResult,
} from '@waha/structures/chatting.dto';
import { SessionInfo } from '@waha/structures/sessions.dto';
import axios, { AxiosInstance } from 'axios';
import { Auth } from '@waha/core/auth/config';
import { ContactSortField } from '@waha/structures/contacts.dto';
import { PaginationParams } from '@waha/structures/pagination.dto';
export interface RequestOptions {
signal?: AbortSignal;
}
export class WAHASelf {
public client: AxiosInstance;
constructor() {
// Set 'X-Api-Key'
const key = Auth.keyplain.value;
const port =
parseInt(process.env.PORT) ||
parseInt(process.env.WHATSAPP_API_PORT) ||
3000;
const url = `http://localhost:${port}`;
this.client = axios.create({
baseURL: url,
headers: {
'X-Api-Key': key,
'Content-Type': 'application/json',
},
});
}
async fetch(url: string, opts?: RequestOptions): Promise<Buffer> {
const response = await this.client.get(url, {
responseType: 'arraybuffer',
signal: opts?.signal,
});
return Buffer.from(response.data);
}
async qr(session: string, opts?: RequestOptions): Promise<Buffer> {
const url = `/api/${session}/auth/qr`;
return await this.fetch(url, opts);
}
async screenshot(session: string, opts?: RequestOptions): Promise<Buffer> {
const url = `/api/screenshot?session=${session}`;
return await this.fetch(url, opts);
}
async restart(session: string, opts?: RequestOptions): Promise<any> {
const url = `/api/sessions/${session}/restart`;
return await this.client.post(url, undefined, { signal: opts?.signal });
}
async logout(session: string, opts?: RequestOptions): Promise<any> {
const url = `/api/sessions/${session}/logout`;
return await this.client.post(url, undefined, { signal: opts?.signal });
}
async stop(session: string, opts?: RequestOptions): Promise<any> {
const url = `/api/sessions/${session}/stop`;
return await this.client.post(url, undefined, { signal: opts?.signal });
}
async get(session: string, opts?: RequestOptions): Promise<SessionInfo> {
const url = `/api/sessions/${session}/`;
return await this.client
.get(url, { signal: opts?.signal })
.then((response) => response.data);
}
async getChats(
session: string,
page: PaginationParams,
opts?: RequestOptions,
) {
const url = `/api/${session}/chats`;
const params = {
...page,
};
return await this.client
.get(url, { params: params, signal: opts?.signal })
.then((response) => response.data);
}
async getContacts(
session: string,
page: PaginationParams,
opts?: RequestOptions,
) {
const url = `/api/contacts/all`;
const params = {
session: session,
...page,
sortBy: ContactSortField.ID,
sortOrder: ContactSortField.ID,
};
return await this.client
.get(url, { params: params, signal: opts?.signal })
.then((response) => response.data);
}
async getContact(session: string, contactId: string, opts?: RequestOptions) {
const url = `/api/contacts`;
const params = {
session: session,
contactId: contactId,
};
return await this.client
.get(url, { params: params, signal: opts?.signal })
.then((response) => response.data);
}
async contactCheckExists(
session: string,
phone: string,
opts?: RequestOptions,
): Promise<WANumberExistResult> {
const url = `/api/contacts/check-exists`;
const params = {
phone: phone,
session: session,
};
return await this.client
.get(url, { params: params, signal: opts?.signal })
.then((response) => response.data);
}
async getGroup(session: string, groupId: string, opts?: RequestOptions) {
const url = `/api/${session}/groups/${groupId}`;
return await this.client
.get(url, { signal: opts?.signal })
.then((response) => response.data);
}
async getGroupParticipants(
session: string,
groupId: string,
opts?: RequestOptions,
) {
const url = `/api/${session}/groups/${groupId}/participants/v2`;
return await this.client
.get(url, { signal: opts?.signal })
.then((response) => response.data);
}
async getChannel(
session: string,
channelId: string,
opts?: RequestOptions,
): Promise<Channel> {
const url = `/api/${session}/channels/${channelId}`;
return await this.client
.get(url, { signal: opts?.signal })
.then((response) => response.data);
}
async getChatPicture(
session: string,
chatId: string,
opts?: RequestOptions,
): Promise<string | null> {
const url = `/api/${session}/chats/${chatId}/picture`;
return await this.client
.get(url, { signal: opts?.signal })
.then((response) => response.data?.url);
}
async sendText(
body: MessageTextRequest,
opts?: RequestOptions,
): Promise<any> {
const url = `/api/sendText`;
return await this.client
.post(url, body, { signal: opts?.signal })
.then((response) => response.data);
}
async sendImage(
body: MessageImageRequest,
opts?: RequestOptions,
): Promise<any> {
const url = `/api/sendImage`;
return await this.client
.post(url, body, { signal: opts?.signal })
.then((response) => response.data);
}
async sendVideo(
body: MessageVideoRequest,
opts?: RequestOptions,
): Promise<any> {
const url = `/api/sendVideo`;
return await this.client
.post(url, body, { signal: opts?.signal })
.then((response) => response.data);
}
async sendVoice(
body: MessageVoiceRequest,
opts?: RequestOptions,
): Promise<any> {
const url = `/api/sendVoice`;
return await this.client
.post(url, body, { signal: opts?.signal })
.then((response) => response.data);
}
async sendFile(
body: MessageFileRequest,
opts?: RequestOptions,
): Promise<any> {
const url = `/api/sendFile`;
return await this.client
.post(url, body, { signal: opts?.signal })
.then((response) => response.data);
}
async deleteMessage(
session: string,
chatId: string,
messageId: string,
opts?: RequestOptions,
) {
const url = `/api/${session}/chats/${chatId}/messages/${messageId}`;
return await this.client
.delete(url, { signal: opts?.signal })
.then((response) => response.data);
}
async startTyping(body: ChatRequest, opts?: RequestOptions) {
const url = `/api/startTyping`;
return await this.client
.post(url, body, { signal: opts?.signal })
.then((response) => response.data);
}
async stopTyping(body: ChatRequest, opts?: RequestOptions) {
const url = `/api/stopTyping`;
return await this.client.post(url, body, { signal: opts?.signal });
}
async readMessages(session: string, chatId: string, opts?: RequestOptions) {
const url = `/api/${session}/chats/${chatId}/messages/read`;
return await this.client
.post(url, undefined, { signal: opts?.signal })
.then((response) => response.data);
}
async getMessages(
session: string,
chatId: string,
query: GetChatMessagesQuery,
filter: GetChatMessagesFilter,
opts?: RequestOptions,
) {
const url = `/api/${session}/chats/${chatId}/messages`;
const params = {
...query,
...filter,
};
return await this.client
.get(url, { params: params, signal: opts?.signal })
.then((response) => response.data);
}
async getMessageById(
session: string,
chatId: string,
messageId: string,
media: boolean,
opts?: RequestOptions,
) {
const url = `/api/${session}/chats/${chatId}/messages/${messageId}`;
const params = {
downloadMedia: media,
};
return await this.client
.get(url, { params: params, signal: opts?.signal })
.then((response) => response.data);
}
async findPNByLid(
session: string,
lid: string,
opts?: RequestOptions,
): Promise<string | null> {
const url = `/api/${session}/lids/${lid}`;
return await this.client
.get(url, { signal: opts?.signal })
.then((response) => response.data.pn);
}
async findLIDByPN(
session: string,
pn: string,
opts?: RequestOptions,
): Promise<string | null> {
const url = `/api/${session}/lids/pn/${pn}`;
return await this.client
.get(url, { signal: opts?.signal })
.then((response) => response.data.lid);
}
/**
* Get the server version
* @returns Server version information
*/
async serverVersion(opts?: RequestOptions): Promise<any> {
const url = `/api/server/version`;
return await this.client
.get(url, { signal: opts?.signal })
.then((response) => response.data);
}
/**
* Get the server status
* @returns Server status information
*/
async serverStatus(opts?: RequestOptions): Promise<any> {
const url = `/api/server/status`;
return await this.client
.get(url, { signal: opts?.signal })
.then((response) => response.data);
}
/**
* Reboot the server
* @param force Whether to force reboot (true) or gracefully reboot (false)
* @returns Server stop response
*/
async serverReboot(
force: boolean = false,
opts?: RequestOptions,
): Promise<any> {
const url = `/api/server/stop`;
return await this.client
.post(url, { force }, { signal: opts?.signal })
.then((response) => response.data);
}
}
export class WAHASessionAPI {
constructor(
private session: string,
private api: WAHASelf,
) {}
getChats(page: PaginationParams, opts?: RequestOptions) {
return this.api.getChats(this.session, page, opts);
}
getContacts(page: PaginationParams, opts?: RequestOptions): Promise<any> {
return this.api.getContacts(this.session, page, opts);
}
getContact(contactId: string, opts?: RequestOptions): Promise<any> {
return this.api.getContact(this.session, contactId, opts);
}
contactCheckExists(
phone: string,
opts?: RequestOptions,
): Promise<WANumberExistResult> {
return this.api.contactCheckExists(this.session, phone, opts);
}
getGroup(groupId: string, opts?: RequestOptions): Promise<any> {
return this.api.getGroup(this.session, groupId, opts);
}
getGroupParticipants(groupId: string, opts?: RequestOptions): Promise<any> {
return this.api.getGroupParticipants(this.session, groupId, opts);
}
getChannel(channelId: string, opts?: RequestOptions): Promise<Channel> {
return this.api.getChannel(this.session, channelId, opts);
}
getChatPicture(
chatId: string,
opts?: RequestOptions,
): Promise<string | null> {
return this.api.getChatPicture(this.session, chatId, opts);
}
sendText(body: MessageTextRequest, opts?: RequestOptions): Promise<any> {
body.session = this.session;
return this.api.sendText(body, opts);
}
sendImage(body: MessageImageRequest, opts?: RequestOptions): Promise<any> {
body.session = this.session;
return this.api.sendImage(body, opts);
}
sendVideo(body: MessageVideoRequest, opts?: RequestOptions): Promise<any> {
body.session = this.session;
return this.api.sendVideo(body, opts);
}
sendVoice(body: MessageVoiceRequest, opts?: RequestOptions): Promise<any> {
body.session = this.session;
return this.api.sendVoice(body, opts);
}
sendFile(body: MessageFileRequest, opts?: RequestOptions): Promise<any> {
body.session = this.session;
return this.api.sendFile(body, opts);
}
deleteMessage(chatId: string, messageId: string, opts?: RequestOptions) {
return this.api.deleteMessage(this.session, chatId, messageId, opts);
}
startTyping(body: ChatRequest, opts?: RequestOptions) {
body.session = this.session;
return this.api.startTyping(body, opts);
}
stopTyping(body: ChatRequest, opts?: RequestOptions) {
body.session = this.session;
return this.api.stopTyping(body, opts);
}
readMessages(chatId: string, opts?: RequestOptions) {
return this.api.readMessages(this.session, chatId, opts);
}
async getMessages(
chatId: string,
query: GetChatMessagesQuery,
filter: GetChatMessagesFilter,
opts?: RequestOptions,
) {
return this.api.getMessages(this.session, chatId, query, filter, opts);
}
async getMessageById(
chatId: string,
messageId: string,
media: boolean,
opts?: RequestOptions,
) {
return this.api.getMessageById(
this.session,
chatId,
messageId,
media,
opts,
);
}
//
// Lids
//
findPNByLid(lid: string, opts?: RequestOptions) {
return this.api.findPNByLid(this.session, lid, opts);
}
findLIDByPN(pn: string, opts?: RequestOptions) {
return this.api.findLIDByPN(this.session, pn, opts);
}
getSessionInfo(opts?: RequestOptions) {
return this.api.get(this.session, opts);
}
}
+15
View File
@@ -0,0 +1,15 @@
import { AppsService } from '@waha/apps/app_sdk/services/IAppsService';
import { AppsDisabledService } from '@waha/apps/app_sdk/services/AppsDisabledService';
import { AppsController } from '@waha/apps/app_sdk/api/apps.controller';
import { ChatwootLocalesController } from '@waha/apps/chatwoot/api/chatwoot.locales.controller';
export const AppsDisabled = {
providers: [
{
provide: AppsService,
useClass: AppsDisabledService,
},
],
imports: [],
controllers: [AppsController, ChatwootLocalesController],
};
+127
View File
@@ -0,0 +1,127 @@
import { BullModule } from '@nestjs/bullmq';
import { RedisModule } from '@liaoliaots/nestjs-redis';
import { RMutexModule } from '@waha/modules/rmutex';
import { BullBoardModule } from '@bull-board/nestjs';
import { ExpressAdapter } from '@bull-board/express';
import { BullAuthMiddleware } from '@waha/apps/app_sdk/auth';
import { ChatWootExports } from '@waha/apps/chatwoot/chatwoot.module';
import { AppsController } from '@waha/apps/app_sdk/api/apps.controller';
import { CallsAppExports } from '@waha/apps/calls/calls.module';
import { AppsService } from '@waha/apps/app_sdk/services/IAppsService';
import { AppsEnabledService } from '@waha/apps/app_sdk/services/AppsEnabledService';
import { Auth } from '@waha/core/auth/config';
import { AppRuntimeConfig } from '@waha/apps/app_sdk/apps/AppRuntime';
import { AppName } from '@waha/apps/app_sdk/apps/name';
const QUEUES_IMPORTS_REQUIRED = [
BullModule.forRoot({
connection: {
url: process.env.REDIS_URL || 'redis://:redis@localhost:6379',
maxRetriesPerRequest: null,
},
prefix: `waha-${process.env.WAHA_WORKER_ID}`,
}),
RedisModule.forRoot({
closeClient: true,
config: {
url: process.env.REDIS_URL || 'redis://:redis@localhost:6379',
onClientCreated: async (client) => {
try {
await client.ping();
} catch (err) {
console.error('[Redis] Connection failed:', err);
process.exit(1); // Stop the app if Redis is unavailable
}
},
},
}),
RMutexModule,
BullBoardModule.forRoot({
route: '/jobs',
adapter: ExpressAdapter,
middleware: BullAuthMiddleware(),
boardOptions: {
uiConfig: {
boardTitle: 'Jobs | WAHA',
boardLogo: {
path: '/dashboard/layout/images/logo-white.svg',
width: 35,
height: 35,
},
favIcon: {
default: '/dashboard/favicon.ico',
alternative: '/dashboard/favicon.ico',
},
miscLinks: [
{
text: '📊 Dashboard',
url: '/dashboard',
},
{
text: '📚 Swagger (OpenAPI)',
url: '/',
},
],
},
},
}),
];
const QUEUES_IMPORTS = AppRuntimeConfig.HasAppsRequiringQueue()
? QUEUES_IMPORTS_REQUIRED
: [];
function getAppModule(name: AppName) {
if (!AppRuntimeConfig.HasApp(name)) {
return {
imports: [],
controllers: [],
providers: [],
};
}
switch (name) {
case AppName.calls:
return CallsAppExports;
case AppName.chatwoot:
return ChatWootExports;
default:
throw Error(`App module not found for ${name}`);
}
}
export const AppsEnabled = {
imports: [
...QUEUES_IMPORTS,
...getAppModule(AppName.chatwoot).imports,
...getAppModule(AppName.calls).imports,
],
controllers: [
AppsController,
...getAppModule(AppName.chatwoot).controllers,
...getAppModule(AppName.calls).controllers,
],
providers: [
{
provide: AppsService,
useClass: AppsEnabledService,
},
...getAppModule(AppName.calls).providers,
...getAppModule(AppName.chatwoot).providers,
],
};
function checkApiKey() {
const key = Auth.key.value;
if (!key) {
return;
}
const plain = Auth.keyplain.value;
if (!plain) {
throw Error(
'WAHA_API_KEY set, please provide WAHA_API_KEY_PLAIN when WAHA_APPS_ENABLED',
);
}
}
if (AppRuntimeConfig.HasAppsRequiringPlainKey()) {
checkApiKey();
}
+7
View File
@@ -0,0 +1,7 @@
import { AppRuntimeConfig } from '@waha/apps/app_sdk/apps/AppRuntime';
import { AppsDisabled } from '@waha/apps/apps.module.disabled';
import { AppsEnabled } from '@waha/apps/apps.module.enabled';
export const AppsModuleExports = AppRuntimeConfig.Enabled()
? AppsEnabled
: AppsDisabled;
+7
View File
@@ -0,0 +1,7 @@
import { CallsAppService } from '@waha/apps/calls/services/CallsAppService';
export const CallsAppExports = {
providers: [CallsAppService],
imports: [],
controllers: [],
};
+44
View File
@@ -0,0 +1,44 @@
import { ApiProperty } from '@nestjs/swagger';
import { Type } from 'class-transformer';
import {
IsBoolean,
IsOptional,
IsString,
ValidateNested,
} from 'class-validator';
export class CallsAppChannelConfig {
@ApiProperty({
description: 'Reject incoming calls for this chat type',
default: true,
})
@IsBoolean()
reject: boolean = true;
@ApiProperty({
description:
'Optional auto-reply message sent after the call is rejected. If empty, no message is sent.',
required: false,
})
@IsOptional()
@IsString()
message?: string;
}
export class CallsAppConfig {
@ApiProperty({
description: 'Rules applied to direct messages (non-group calls)',
type: CallsAppChannelConfig,
})
@ValidateNested()
@Type(() => CallsAppChannelConfig)
dm: CallsAppChannelConfig = new CallsAppChannelConfig();
@ApiProperty({
description: 'Rules applied to group calls',
type: CallsAppChannelConfig,
})
@ValidateNested()
@Type(() => CallsAppChannelConfig)
group: CallsAppChannelConfig = new CallsAppChannelConfig();
}
Loaded 100 of 489 files, more files were not shown because too many files have changed in this diff. Show more