Compare commits

..
200 Commits
Author SHA1 Message Date
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
213 changed files with 14807 additions and 4876 deletions

No files matched your search

+17 -13
View File
@@ -3,31 +3,35 @@
# 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
# 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
# ====================
# ===== SECURITY =====
# ====================
# "sha512:{SHA}" format - below is "admin" api key
WAHA_API_KEY=sha512:c7ad44cbad762a5da0a452f9e854fdc1e0e7a52a38015f23f3eab1d80b931dd472634dfac71cd34ebc35d16ab7fb8a90c81f975113d6c7538dc69dd8de9077ec
WAHA_DASHBOARD_ENABLED=True
WAHA_DASHBOARD_USERNAME=admin
WAHA_DASHBOARD_PASSWORD=admin
WHATSAPP_SWAGGER_ENABLED=True
WHATSAPP_SWAGGER_USERNAME=admin
WHATSAPP_SWAGGER_PASSWORD=admin
# WhatsApp engine (WEBJS is default, GOWS or NOWEB for better performance)
WHATSAPP_DEFAULT_ENGINE=WEBJS
# ===================
# ===== LOGGING =====
+1
View File
@@ -1,2 +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 },
],
},
};
+1
View File
@@ -129,6 +129,7 @@ jobs:
docker run -d \
--name smoke \
--rm -p3000:3000 \
-e WAHA_API_KEY=test \
${{ vars.DOCKER_IMAGE }}:${{ matrix.tag }}
sleep 3
+126
View File
@@ -0,0 +1,126 @@
name: Dev
on:
schedule:
- cron: '0 0 * * *'
workflow_dispatch:
jobs:
docker-build:
if: github.repository == 'devlikeapro/waha-plus'
runs-on: ${{ matrix.runner }}
name:
${{ matrix.engine }} - ${{ matrix.browser }} - ${{ matrix.platform }} -
${{ matrix.tag }}
strategy:
matrix:
include:
- runner: 'buildjet-4vcpu-ubuntu-2204'
tag: 'dev'
platform: 'amd64'
browser: 'chromium'
engine: 'WEBJS'
goss: 'goss-linux-amd64'
- 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
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: 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'
+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"
}
}
+7 -11
View File
@@ -17,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'
@@ -73,9 +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|
)$
+124
View File
@@ -0,0 +1,124 @@
# 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.
## 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 `../../../`).
- 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.
Following this playbook keeps contributions aligned with WAHA’s structure,
automation hooks, and release process.
+6 -1
View File
@@ -162,7 +162,7 @@ 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="137.0.7151.103-1"
ARG CHROME_VERSION="140.0.7339.80-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 \
@@ -195,6 +195,11 @@ 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
+3 -3
View File
@@ -35,13 +35,13 @@ for-swagger:
WHATSAPP_SWAGGER_CONFIG_ADVANCED=true . ${NVM_DIR}/nvm.sh && nvm exec yarn start
up-noweb:
. ${NVM_DIR}/nvm.sh && nvm exec yarn up @adiwajshing/baileys@github:devlikeapro/Baileys#fork-master-2025-07-04
yarn up @adiwajshing/baileys@github:devlikeapro/Baileys#fork-master-2025-10-19
up-noweb-libsignal:
. ${NVM_DIR}/nvm.sh && nvm exec yarn up libsignal@github:devlikeapro/libsignal-node#fork-master
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-main-2025-06-09
yarn up whatsapp-web.js@github:devlikeapro/whatsapp-web.js#fork-main-2025-09-10
start-proxy:
docker run --rm -d --name squid-container -e TZ=UTC -p 3128:3128 ubuntu/squid:5.2-22.04_beta
+1 -1
View File
@@ -36,7 +36,7 @@ services:
# NOTE: Only if you're using PostgreSQL to save sessions
# https://waha.devlike.pro/docs/how-to/storages/#media---postgresql
# postgres:
# image: postgres:17
# image: postgres:17.6
# restart: always
# environment:
# POSTGRES_USER: postgres
+4
View File
@@ -1,6 +1,10 @@
# 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
+15 -14
View File
@@ -3,31 +3,32 @@
# 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
# ====================
# ===== SECURITY =====
# ====================
# WAHA Apps requires both sha and plain version
WAHA_API_KEY_PLAIN=00000000000000000000000000000000
WAHA_API_KEY=sha512:98b6d128682e280b74b324ca82a6bae6e8a3f7174e0605bfd52eb9948fad8984854ec08f7652f32055c4a9f12b69add4850481d9503a7f2225501671d6124648
WAHA_DASHBOARD_USERNAME=admin
WAHA_DASHBOARD_PASSWORD=11111111111111111111111111111111
WHATSAPP_SWAGGER_USERNAME=admin
WHATSAPP_SWAGGER_PASSWORD=11111111111111111111111111111111
WAHA_DASHBOARD_ENABLED=True
WHATSAPP_SWAGGER_ENABLED=True
# WhatsApp engine (WEBJS is default, GOWS or NOWEB for better performance)
WHATSAPP_DEFAULT_ENGINE=GOWS
+3 -1
View File
@@ -24,10 +24,12 @@ services:
- 8.8.8.8
base: &base
image: chatwoot/chatwoot:v4.3.0
image: chatwoot/chatwoot:v4.6.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:
-5
View File
@@ -41,11 +41,6 @@ if [ -n "$key" ]; then
# If already hashed, use it as is
export WAHA_API_KEY="$key"
else
# Display warning about using plain text API key
echo "⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️"
echo "WARNING: Plain text API key detected. Converting to hashed format for security."
echo "For better security, use WAHA_API_KEY=sha512:{SHA512_HASH_FOR_YOUR_API_KEY}"
echo "⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️"
# Hash the key using sha512sum
HASHED_KEY=$(echo -n "$key" | sha512sum | awk '{print $1}')
export WAHA_API_KEY="sha512:$HASHED_KEY"
Binary file not shown.
Binary file not shown.
Binary file not shown.
+2 -1
View File
@@ -7,7 +7,8 @@
"dashboard/**",
"core/engines/webjs/*",
"plus/engines/webjs/*",
"apps/chatwoot/i18n/*.yaml"
"apps/chatwoot/i18n/locales/*.yaml",
"apps/chatwoot/i18n/locales/*.yml"
],
"watchAssets": true
}
+18 -16
View File
@@ -14,8 +14,8 @@
"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",
"lint": "oxlint --deny-warnings",
"lint-fix": "oxlint --fix --deny-warnings",
"test": "jest",
"test:watch": "jest --watch",
"test:cov": "jest --coverage",
@@ -26,7 +26,7 @@
"gows:proto": "yarn gows:proto:fetch && yarn gows:proto:build"
},
"dependencies": {
"@adiwajshing/baileys": "github:devlikeapro/Baileys#fork-master-2025-07-04",
"@adiwajshing/baileys": "github:devlikeapro/Baileys#fork-master-2025-10-19",
"@adiwajshing/keyed-db": "^0.2.4",
"@aws-sdk/client-s3": "^3.633.0",
"@aws-sdk/s3-request-presigner": "^3.633.0",
@@ -71,13 +71,15 @@
"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:devlikeapro/libsignal-node#fork-master",
"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.9.0",
"mustache": "^4.2.0",
@@ -90,7 +92,7 @@
"pino-pretty": "^11.2.1",
"pretty-bytes": "5.6.0",
"promise-retry": "^2.0.1",
"puppeteer": "^24.10.0",
"puppeteer": "^24.19.0",
"qrcode": "^1.5.1",
"qrcode-terminal": "^0.12.0",
"reflect-metadata": "^0.1.13",
@@ -100,7 +102,8 @@
"sqlite3": "^5.1.7",
"swagger-ui-express": "^4.1.4",
"ulid": "^2.3.0",
"whatsapp-web.js": "github:devlikeapro/whatsapp-web.js#fork-main-2025-06-09",
"undici": "^7.16.0",
"whatsapp-web.js": "github:devlikeapro/whatsapp-web.js#fork-main-2025-09-10",
"write-file-atomic": "^6.0.0",
"yaml": "^2.7.1"
},
@@ -110,9 +113,8 @@
"resolutions": {
"bufferutil": "^4.0.8",
"ws": "^8.18.0",
"puppeteer": "^24.10.0",
"puppeteer": "^24.19.0",
"whatwg-url": "13.0.0",
"libsignal": "github:devlikeapro/libsignal-node#fork-master",
"axios": "^1.9.0"
},
"devDependencies": {
@@ -124,23 +126,20 @@
"@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",
"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",
"yargs": "^17.7.2"
},
"jest": {
@@ -150,6 +149,9 @@
"ts"
],
"rootDir": "src",
"moduleNameMapper": {
"^@waha/(.*)$": "<rootDir>/$1"
},
"testRegex": ".test.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
+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'));
-1
View File
@@ -1,4 +1,3 @@
import { isJidGroup } from '@adiwajshing/baileys';
import {
Body,
Controller,
+26 -1
View File
@@ -16,6 +16,7 @@ import {
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 {
@@ -31,6 +32,7 @@ import {
MessageLinkPreviewRequest,
MessageLocationRequest,
MessagePollRequest,
MessagePollVoteRequest,
MessageReactionRequest,
MessageReplyRequest,
MessageStarRequest,
@@ -121,8 +123,9 @@ export class ChattingController {
@Post('/sendButtons')
@ApiOperation({
summary: 'Send buttons (interactive message)',
summary: 'Send buttons message (interactive)',
description: 'Send Buttons',
deprecated: true,
})
@UsePipes(new WAHAValidationPipe())
async sendButtons(@Body() request: SendButtonsRequest) {
@@ -130,6 +133,17 @@ export class ChattingController {
return whatsapp.sendButtons(request);
}
@Post('/sendList')
@ApiOperation({
summary: 'Send a list message (interactive)',
description: 'Send a List message with sections and rows',
})
@UsePipes(new WAHAValidationPipe())
async sendList(@Body() request: SendListRequest) {
const whatsapp = await this.manager.getWorkingSession(request.session);
return whatsapp.sendList(request);
}
@Post('/forwardMessage')
async forwardMessage(
@Body() request: MessageForwardRequest,
@@ -193,6 +207,17 @@ export class ChattingController {
return whatsapp.sendPoll(request);
}
@Post('/sendPollVote')
@ApiOperation({
summary: 'Vote on a poll',
description: 'Cast vote(s) on an existing poll message',
})
@UsePipes(new WAHAValidationPipe())
async sendPollVote(@Body() request: MessagePollVoteRequest) {
const whatsapp = await this.manager.getWorkingSession(request.session);
return whatsapp.sendPollVote(request);
}
@Post('/sendLocation')
async sendLocation(@Body() request: MessageLocationRequest) {
const whatsapp = await this.manager.getWorkingSession(request.session);
+1 -1
View File
@@ -1,4 +1,3 @@
import { isLidUser } from '@adiwajshing/baileys/lib/WABinary/jid-utils';
import {
Controller,
Get,
@@ -22,6 +21,7 @@ import {
import { PaginationParams, SortOrder } from '@waha/structures/pagination.dto';
import { SessionManager } from '../core/abc/manager.abc';
import { isLidUser } from '@waha/core/utils/jids';
@ApiSecurity('api_key')
@Controller('api/:session/lids')
+15
View File
@@ -50,3 +50,18 @@ export function HasBeenRetried(job: Job): boolean {
const attemptsMade = job.attemptsMade + 1;
return attemptsMade > 1;
}
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 } {
const text = `${job.queueName} => ${job.id}`;
const url = `${base}/jobs/queue/${encodeURIComponent(job.queueName)}/${
job.id
}`;
return { text: text, url: url };
}
+9 -2
View File
@@ -48,10 +48,17 @@ export class AppsController {
return await this.appsService.create(this.manager, app);
}
@Get('/:id')
@ApiOperation({ summary: 'Get app by ID' })
@UsePipes(new WAHAValidationPipe())
async get(@Param('id') id: string): Promise<App> {
return await this.appsService.get(this.manager, id);
}
@Put('/:id')
@ApiOperation({ summary: 'Update an existing app' })
@UsePipes(new WAHAValidationPipe())
async update(@Param('id') id: string, @Body() app: App): Promise<void> {
async update(@Param('id') id: string, @Body() app: App): Promise<App> {
if (!app.id) {
app.id = id;
} else if (app.id !== id) {
@@ -60,7 +67,7 @@ export class AppsController {
);
}
await this.appsService.update(this.manager, app);
return await this.appsService.update(this.manager, app);
}
@Delete('/:id')
+11 -8
View File
@@ -2,21 +2,24 @@ 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 = process.env.WAHA_DASHBOARD_USERNAME;
let password = process.env.WAHA_DASHBOARD_PASSWORD;
const enabled = parseBool(process.env.WAHA_DASHBOARD_ENABLED);
if (!enabled) {
// Generate a random uuid4 username /password to prevent access
username = 'admin';
password = crypto.randomUUID();
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: {
[username]: password,
[String(username)]: String(password),
},
});
}
+19 -1
View File
@@ -1,6 +1,13 @@
import { ChatWootAppConfig } from '@waha/apps/chatwoot/dto/config.dto';
import { Type } from 'class-transformer';
import { IsEnum, IsString, ValidateNested } from 'class-validator';
import {
IsBoolean,
IsEnum,
IsOptional,
IsString,
ValidateNested,
} from 'class-validator';
import { ApiExtraModels, ApiProperty } from '@nestjs/swagger';
export type AllowedAppConfig = ChatWootAppConfig;
@@ -8,6 +15,7 @@ export enum AppName {
chatwoot = 'chatwoot',
}
@ApiExtraModels(ChatWootAppConfig)
export class App<T extends AllowedAppConfig = any> {
@IsString()
id: string;
@@ -19,6 +27,16 @@ export class App<T extends AllowedAppConfig = any> {
@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) {
+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(),
};
}
@@ -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');
});
}
};
@@ -21,11 +21,15 @@ export class AppsDisabledService implements IAppsService {
throw new AppsIsDisabledError();
}
async get(manager: SessionManager, appId: string): Promise<App> {
throw new AppsIsDisabledError();
}
async create(manager: SessionManager, app: App): Promise<App> {
throw new AppsIsDisabledError();
}
async update(manager: SessionManager, app: App) {
async update(manager: SessionManager, app: App): Promise<App> {
throw new AppsIsDisabledError();
}
@@ -40,4 +44,8 @@ export class AppsDisabledService implements IAppsService {
async beforeSessionStart(session: WhatsappSession, store: DataStore) {
return;
}
async afterSessionStart(session: WhatsappSession, store: DataStore) {
return;
}
}
@@ -58,15 +58,32 @@ export class AppsEnabledService implements IAppsService {
}
const service = this.getAppService(app);
await service.beforeCreated(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);
await this.restartIfRunning(manager, app.session);
if (app.enabled) {
await this.restartIfRunning(manager, app.session);
}
delete result.pk;
return result;
}
async update(manager: SessionManager, app: App) {
async get(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`);
}
delete (app as any).pk;
return app;
}
async update(manager: SessionManager, app: App): Promise<App> {
await this.checkSessionExists(manager, app.session);
const knex = manager.store.getWAHADatabase();
const repo = new AppRepository(knex);
@@ -86,11 +103,26 @@ export class AppsEnabledService implements IAppsService {
}
const service = this.getAppService(app);
await service.beforeUpdated(savedApp, 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);
await this.restartIfRunning(manager, app.session);
return;
const updated = await repo.getById(app.id);
delete (updated as any)?.pk;
return updated!;
}
async delete(manager: SessionManager, appId: string) {
@@ -110,10 +142,20 @@ export class AppsEnabledService implements IAppsService {
async beforeSessionStart(session: WhatsappSession, store: DataStore) {
const knex = store.getWAHADatabase();
const repo = new AppRepository(knex);
const apps = await repo.getAllBySession(session.name);
const apps = await repo.getEnabledBySession(session.name);
for (const app of apps) {
const service = this.getAppService(app);
await service.beforeSessionStart(app, session);
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);
service.afterSessionStart(app, session);
}
}
+15 -1
View File
@@ -5,11 +5,25 @@ 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): Promise<void>;
beforeSessionStart(app: App, session: WhatsappSession): void;
afterSessionStart(app: App, session: WhatsappSession): void;
}
+5 -1
View File
@@ -7,14 +7,18 @@ import { Knex } from 'knex';
export interface IAppsService {
list(manager: SessionManager, session: string): Promise<App[]>;
get(manager: SessionManager, appId: string): Promise<App>;
create(manager: SessionManager, app: App): Promise<App>;
update(manager: SessionManager, app: App): Promise<void>;
update(manager: SessionManager, app: App): Promise<App>;
delete(manager: SessionManager, appId: string): Promise<void>;
beforeSessionStart(session: WhatsappSession, store: DataStore): Promise<void>;
afterSessionStart(session: WhatsappSession, store: DataStore): Promise<void>;
migrate(knex: Knex): Promise<void>;
}
+50 -3
View File
@@ -16,7 +16,7 @@ export class AppRepository {
* Saves an app to the database
*/
async save(app: Omit<App, 'pk'>): Promise<AppDB> {
const appToSave = { ...app };
const appToSave: any = { ...app };
if (appToSave.config && typeof appToSave.config === 'object') {
appToSave.config = JSON.stringify(appToSave.config);
}
@@ -29,7 +29,25 @@ export class AppRepository {
private deserialize(app: AppDB): AppDB {
const parsedConfig =
typeof app.config === 'string' ? JSON.parse(app.config) : app.config;
return { ...app, config: parsedConfig };
// 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 {
@@ -51,6 +69,20 @@ export class AppRepository {
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
*/
@@ -62,11 +94,26 @@ export class AppRepository {
.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 = this.serialize(app);
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);
}
+3 -2
View File
@@ -13,6 +13,7 @@ import { ChatwootLocalesController } from '@waha/apps/chatwoot/api/chatwoot.loca
import { ChatWootExports } from '@waha/apps/chatwoot/chatwoot.module';
import { parseBool } from '@waha/helpers';
import { RMutexModule } from '@waha/modules/rmutex';
import { Auth } from '@waha/core/auth/config';
const IMPORTS = [
BullModule.forRoot({
@@ -93,11 +94,11 @@ const AppsDisabled = {
};
function checkApiKey() {
const key = process.env.WHATSAPP_API_KEY || process.env.WAHA_API_KEY;
const key = Auth.key.value;
if (!key) {
return;
}
const plain = process.env.WAHA_API_KEY_PLAIN;
const plain = Auth.keyplain.value;
if (!plain) {
throw Error(
'WAHA_API_KEY set, please provide WAHA_API_KEY_PLAIN when WAHA_APPS_ENABLED',
@@ -1,6 +1,7 @@
import { Controller, Get } from '@nestjs/common';
import { sortBy } from 'lodash';
import { ApiOperation, ApiSecurity, ApiTags } from '@nestjs/swagger';
import { LOCALE_NAMES } from '@waha/apps/chatwoot/locale';
import { i18n } from '@waha/apps/chatwoot/i18n';
interface LanguageResponse {
name: string;
@@ -17,9 +18,14 @@ export class ChatwootLocalesController {
description: 'Get available languages for Chatwoot app',
})
getLanguages(): LanguageResponse[] {
return Array.from(LOCALE_NAMES.entries()).map(([locale, name]) => ({
name,
locale,
}));
const locales = i18n.available();
const priority = ['en-US', 'pt-BR', 'es-ES'];
return sortBy(locales, [
(x) => {
const idx = priority.indexOf(x.locale);
return idx === -1 ? Number.MAX_SAFE_INTEGER : idx;
},
'locale',
]);
}
}
@@ -1,15 +1,26 @@
import { Body, Controller, Param, Post } from '@nestjs/common';
import {
Body,
Controller,
NotFoundException,
Param,
Post,
} from '@nestjs/common';
import { ApiOperation, ApiTags } from '@nestjs/swagger';
import { FindChatID } from '@waha/apps/chatwoot/client/ids';
import { EventName, MessageType } from '@waha/apps/chatwoot/client/types';
import { INBOX_CONTACT_CHAT_ID } from '@waha/apps/chatwoot/const';
import { InboxData } from '@waha/apps/chatwoot/consumers/types';
import { ChatWootQueueService } from '@waha/apps/chatwoot/services/ChatWootQueueService';
import { SessionManager } from '@waha/core/abc/manager.abc';
import { AppRepository } from '@waha/apps/app_sdk/storage/AppRepository';
@Controller('webhooks/chatwoot/')
@ApiTags('🧩 Apps')
export class ChatwootWebhookController {
constructor(private readonly chatWootQueueService: ChatWootQueueService) {}
constructor(
private readonly chatWootQueueService: ChatWootQueueService,
private readonly manager: SessionManager,
) {}
@Post(':session/:id')
@ApiOperation({
@@ -41,6 +52,14 @@ export class ChatwootWebhookController {
body: body,
};
// Skip if app is disabled or does not exist
const knex = this.manager.store.getWAHADatabase();
const repo = new AppRepository(knex);
const app = await repo.findEnabledAppById(id);
if (!app || app.session !== session) {
throw new NotFoundException(`App '${id}' not found`);
}
// Check if it's a command message (sent to the special inbox contact)
const sender = body?.conversation?.meta?.sender;
const chatId = FindChatID(sender);
@@ -62,6 +81,19 @@ export class ChatwootWebhookController {
}
return { success: true };
case EventName.MESSAGE_UPDATED:
// We handle only "retries" on message_update
// This is the attribute ChatWoot send
// There's NO other way to identify "status: read" updates right now
// There's no "body.status" in message_updated webhook :(
const isRetryNull = body.content_attributes?.external_error === null;
const isRetrySomething = Boolean(
body.content_attributes?.external_error,
);
const isRetry = isRetryNull || isRetrySomething;
if (!isRetry) {
return { success: true };
}
if (!isCommandsChat) {
await this.chatWootQueueService.addMessageUpdatedJob(data);
} else {
@@ -70,6 +102,7 @@ export class ChatwootWebhookController {
return { success: true };
default:
// Ignore other events
await this.chatWootQueueService.addJobToQueue(body.event, data);
return { success: true };
}
}
+4 -4
View File
@@ -1,8 +1,8 @@
import { public_conversation } from '@figuro/chatwoot-sdk';
import { ChatWootAPIConfig } from '@waha/apps/chatwoot/client/interfaces';
import * as NodeCache from 'node-cache';
import { IConversationCache } from './IConversationCache';
import { ContactIds } from '@waha/apps/chatwoot/client/ConversationService';
const cache: NodeCache = new NodeCache({
stdTTL: 24 * 60 * 60, // 1 day
@@ -16,7 +16,7 @@ export function CacheForConfig(config: ChatWootAPIConfig): ConversationCache {
class ConversationCache implements IConversationCache {
constructor(private prefix: string) {}
fullKey(key: string) {
private fullKey(key: string) {
return `${this.prefix}.${key}`;
}
@@ -25,7 +25,7 @@ class ConversationCache implements IConversationCache {
cache.del(fullKey);
}
get(key: string): public_conversation | null {
get(key: string): ContactIds | null {
const fullKey = this.fullKey(key);
return cache.get(fullKey) || null;
}
@@ -35,7 +35,7 @@ class ConversationCache implements IConversationCache {
return cache.has(fullKey);
}
set(key: string, value: public_conversation): void {
set(key: string, value: ContactIds): void {
const fullKey = this.fullKey(key);
cache.set(fullKey, value);
}
+3 -3
View File
@@ -1,8 +1,8 @@
import { public_conversation } from '@figuro/chatwoot-sdk';
import { ContactIds } from '@waha/apps/chatwoot/client/ConversationService';
export interface IConversationCache {
get(key: string): public_conversation | null;
set(key: string, value: public_conversation): void;
get(key: string): ContactIds | null;
set(key: string, value: ContactIds): void;
has(key: string): boolean;
delete(key: string): void;
clean(): void;
+19 -1
View File
@@ -1,4 +1,3 @@
import { Module } from '@nestjs/common';
import { RegisterAppQueue } from '@waha/apps/app_sdk/BullUtils';
import {
ExponentialRetriesJobOptions,
@@ -22,10 +21,13 @@ import { WAHAMessageAnyConsumer } from './consumers/waha/message.any';
import { WAHAMessageEditedConsumer } from './consumers/waha/message.edited';
import { WAHAMessageReactionConsumer } from './consumers/waha/message.reaction';
import { WAHAMessageRevokedConsumer } from './consumers/waha/message.revoked';
import { WAHAMessageAckConsumer } from './consumers/waha/message.ack';
import { WAHASessionStatusConsumer } from './consumers/waha/session.status';
import { ChatWootQueueService } from './services/ChatWootQueueService';
import { ChatWootScheduleService } from './services/ChatWootScheduleService';
import { ChatWootWAHAQueueService } from './services/ChatWootWAHAQueueService';
import { ChatWootConversationCreatedConsumer } from './consumers/inbox/conversation_created';
import { ChatWootConversationStatusChangedConsumer } from '@waha/apps/chatwoot/consumers/inbox/conversation_status_changed';
const CONTROLLERS = [ChatwootWebhookController, ChatwootLocalesController];
@@ -54,6 +56,10 @@ const IMPORTS = lodash.flatten([
name: QueueName.WAHA_MESSAGE_REVOKED,
defaultJobOptions: merge(ExponentialRetriesJobOptions, JobRemoveOptions),
}),
RegisterAppQueue({
name: QueueName.WAHA_MESSAGE_ACK,
defaultJobOptions: merge(ExponentialRetriesJobOptions, JobRemoveOptions),
}),
RegisterAppQueue({
name: QueueName.WAHA_SESSION_STATUS,
defaultJobOptions: merge(ExponentialRetriesJobOptions, JobRemoveOptions),
@@ -66,6 +72,14 @@ const IMPORTS = lodash.flatten([
name: QueueName.INBOX_MESSAGE_UPDATED,
defaultJobOptions: merge(ExponentialRetriesJobOptions, JobRemoveOptions),
}),
RegisterAppQueue({
name: QueueName.INBOX_CONVERSATION_CREATED,
defaultJobOptions: merge(NoRetriesJobOptions, JobRemoveOptions),
}),
RegisterAppQueue({
name: QueueName.INBOX_CONVERSATION_STATUS_CHANGED,
defaultJobOptions: merge(NoRetriesJobOptions, JobRemoveOptions),
}),
RegisterAppQueue({
name: QueueName.INBOX_MESSAGE_DELETED,
defaultJobOptions: merge(ExponentialRetriesJobOptions, JobRemoveOptions),
@@ -80,12 +94,16 @@ const PROVIDERS = [
ChatWootInboxMessageCreatedConsumer,
ChatWootInboxMessageUpdatedConsumer,
ChatWootInboxMessageDeletedConsumer,
// Conversation events
ChatWootConversationCreatedConsumer,
ChatWootConversationStatusChangedConsumer,
ChatWootInboxCommandsConsumer,
WAHASessionStatusConsumer,
WAHAMessageAnyConsumer,
WAHAMessageReactionConsumer,
WAHAMessageEditedConsumer,
WAHAMessageRevokedConsumer,
WAHAMessageAckConsumer,
MessageCleanupConsumer,
CheckVersionConsumer,
ChatWootWAHAQueueService,
@@ -0,0 +1,26 @@
import axios from 'axios';
export class ChatWootInboxNewAPI {
constructor(
private base: string,
private inboxIdentifier: string,
) {}
async updateLastSeen(sourceId: string, conversationId: number) {
// TODO: Move to the lib (fork?)
// Call the update_last_seen endpoint using a direct HTTP request
// since the SDK doesn't have this method
const path = `/public/api/v1/inboxes/${this.inboxIdentifier}/contacts/${sourceId}/conversations/${conversationId}/update_last_seen`;
const url = new URL(path, this.base).toString();
await axios.post(
url,
{},
{
headers: {
api_access_token: this.inboxIdentifier,
'Content-Type': 'application/json',
},
},
);
}
}
@@ -1,15 +1,17 @@
import ChatwootClient, {
ApiError,
ApiError as ChatWootAPIError,
public_contact_create_update_payload,
public_conversation,
} from '@figuro/chatwoot-sdk';
import { ILogger } from '@waha/apps/app_sdk/ILogger';
import { ContactAPI } from '@waha/apps/chatwoot/client/ContactAPI';
import { ContactService } from '@waha/apps/chatwoot/client/ContactService';
import { Conversation } from '@waha/apps/chatwoot/client/Conversation';
import { ConversationAPI } from '@waha/apps/chatwoot/client/ConversationAPI';
import {
ContactIds,
ConversationService,
} from '@waha/apps/chatwoot/client/ConversationService';
import { ChatWootAPIConfig } from '@waha/apps/chatwoot/client/interfaces';
import { InboxContactInfo } from '@waha/apps/chatwoot/contacts/InboxContactInfo';
import { Locale } from '@waha/apps/chatwoot/locale';
import { Locale } from '@waha/apps/chatwoot/i18n/locale';
import { CacheForConfig } from '../cache/ConversationCache';
import { IConversationCache } from '../cache/IConversationCache';
@@ -29,8 +31,8 @@ export class ContactConversationService {
constructor(
private config: ChatWootAPIConfig,
private contactAPI: ContactAPI,
private conversationAPI: ConversationAPI,
private contactService: ContactService,
private conversationService: ConversationService,
private accountAPI: ChatwootClient,
private logger: ILogger,
private l: Locale,
@@ -40,7 +42,7 @@ export class ContactConversationService {
private async upsertByContactInfo(
contactInfo: ContactInfo,
): Promise<public_conversation> {
): Promise<ContactIds> {
const chatId = contactInfo.ChatId();
// Check cache for chat id
@@ -51,61 +53,75 @@ export class ContactConversationService {
//
// Find or create contact
//
let contact = await this.contactAPI.searchByAnyID(chatId);
let contact = await this.contactService.searchByAnyID(chatId);
if (!contact) {
const request = await contactInfo.PublicContactCreate();
contact = await this.contactAPI.create(chatId, request);
contact = await this.contactService.create(chatId, request);
}
// Update custom attributes - always
const attributes = await contactInfo.Attributes();
this.logger.info(
`Updating contact custom attributes for chat.id: ${chatId}, contact.id: ${contact.data.id}`,
`Updating if required contact custom attributes for chat.id: ${chatId}, contact.id: ${contact.data.id}`,
);
await this.contactAPI.updateCustomAttributes(contact.data, attributes);
await this.contactService.upsertCustomAttributes(contact.data, attributes);
// Update Avatar if nothing, but keep the original one if any
if (!contact.data.thumbnail) {
const avatarUrl = await contactInfo.AvatarUrl();
const avatarUrl = await contactInfo.AvatarUrl().catch((err) => {
this.logger.warn(
`Error getting avatar for chat.id from WhatsApp: ${chatId}`,
);
this.logger.warn(err);
return null;
});
if (avatarUrl) {
this.contactAPI.updateAvatarUrlSafe(contact.data.id, avatarUrl);
this.contactService.updateAvatarUrlSafe(contact.data.id, avatarUrl);
}
}
this.logger.info(
`Using contact for chat.id: ${chatId}, contact.id: ${contact.sourceId}`,
`Using contact for chat.id: ${chatId}, contact.id: ${contact.data.id}, contact.sourceId: ${contact.sourceId}`,
);
//
// Get or create a conversation for this inbox
//
const conversation = await this.conversationAPI.upsert(contact.sourceId);
const conversation = await this.conversationService.upsert({
id: contact.data.id,
sourceId: contact.sourceId,
});
this.logger.info(
`Using conversation for chat.id: ${chatId}, conversation.id: ${conversation.id}, contact.id: ${contact.sourceId}`,
);
// Save to cache
this.cache.set(chatId, conversation);
return conversation;
const ids = {
id: conversation.id,
sourceId: contact.sourceId,
};
this.cache.set(chatId, ids);
return ids;
}
public async ConversationByContact(
contactInfo: ContactInfo,
): Promise<Conversation> {
const chatId = contactInfo.ChatId();
const publicConversation = await this.upsertByContactInfo(contactInfo);
const ids = await this.upsertByContactInfo(contactInfo);
const conversation = new Conversation(
this.accountAPI,
this.config.accountId,
publicConversation.id,
ids.id,
ids.sourceId,
);
conversation.onError = (err) => {
if (err instanceof ApiError) {
if (err instanceof ChatWootAPIError) {
// invalidate cache
this.cache.delete(chatId);
this.logger.error(`ApiError: ${err.message}`);
this.logger.error(
`ApiError occurred, invalidating cache for chat.id: ${chatId}, conversation.id: ${publicConversation.id}`,
`ApiError occurred, invalidating cache for chat.id: ${chatId}, conversation.id: ${ids.id}, source.id: ${ids.sourceId}`,
);
}
};
@@ -127,4 +143,33 @@ export class ContactConversationService {
public async InboxNotifications() {
return this.ConversationByContact(new InboxContactInfo(this.l));
}
public ResetCache(chatIds: Array<string>) {
this.logger.info(`Resetting cache chat ids: ${chatIds.join(', ')}`);
for (const chatId of chatIds) {
this.cache.delete(chatId);
}
}
public ResetMismatchedCache(chatIds: Array<string>, contactId: number) {
for (const chatId of chatIds) {
if (!this.cache.has(chatId)) {
continue;
}
const current = this.cache.get(chatId);
if (current.id !== contactId) {
this.logger.info(
`Resetting cache for chat id: ${chatId}, value changed from ${current} to ${contactId}`,
);
this.cache.delete(chatId);
}
}
}
public async markConversationAsRead(
conversationId: number,
sourceId: string,
): Promise<void> {
await this.conversationService.markAsRead(conversationId, sourceId);
}
}
@@ -13,13 +13,14 @@ import { isJidCusFormat } from '@waha/utils/wa';
import * as lodash from 'lodash';
import { AttributeKey } from '../const';
import { E164Parser } from '@waha/core/utils/PhoneJidNormalizer';
export interface ContactResponse {
data: generic_id & contact;
sourceId: string;
}
export class ContactAPI {
export class ContactService {
constructor(
private config: ChatWootAPIConfig,
private accountAPI: ChatwootClient,
@@ -64,8 +65,8 @@ export class ContactAPI {
if (isJidCusFormat(chatId)) {
// Search by phone
let phone_number = chatId.split('@')[0];
phone_number = phone_number.replace('+', '');
const phoneNumberE164 = E164Parser.fromJid(chatId);
const phone_number = phoneNumberE164.replace('+', '');
payload[payload.length - 1].query_operator = 'OR';
payload.push({
attribute_key: 'phone_number',
@@ -96,18 +97,22 @@ export class ContactAPI {
};
}
public updateCustomAttributes(
public async upsertCustomAttributes(
contact: generic_id & contact,
attributes: any,
) {
): Promise<boolean> {
if (lodash.isEqual(attributes, contact.custom_attributes)) {
return false;
}
const update: contact_update = {
custom_attributes: { ...contact.custom_attributes, ...attributes },
};
return this.accountAPI.contacts.update({
await this.accountAPI.contacts.update({
id: contact.id,
accountId: this.config.accountId,
data: update,
});
return true;
}
public async create(
@@ -142,7 +147,7 @@ export class ContactAPI {
})
.catch((e) => {
this.logger.warn(
'Error updating avatar_url for contact.id: ' + contactId,
`Error updating avatar_url for contact.id: ${contactId}`,
);
this.logger.warn(e);
});
+1
View File
@@ -9,6 +9,7 @@ export class Conversation {
private accountAPI: ChatwootClient,
private accountId: number,
public conversationId: number,
public sourceId: string | null = null,
) {}
public async send(data: conversation_message_create) {
@@ -1,52 +0,0 @@
import { public_conversation } from '@figuro/chatwoot-sdk';
import { ILogger } from '@waha/apps/app_sdk/ILogger';
import {
ChatWootAPIConfig,
ChatWootInboxAPI,
} from '@waha/apps/chatwoot/client/interfaces';
export class ConversationAPI {
constructor(
private config: ChatWootAPIConfig,
private inboxAPI: ChatWootInboxAPI,
private logger: ILogger,
) {}
private async find(
contactIdentifier: string,
): Promise<public_conversation | null> {
const request = {
inboxIdentifier: this.config.inboxIdentifier,
contactIdentifier: contactIdentifier,
};
const conversations = await this.inboxAPI.conversations.list(request);
if (conversations.length != 0) {
return conversations[0];
}
return null;
}
private async create(
contactIdentifier: string,
): Promise<public_conversation> {
const conversation = await this.inboxAPI.conversations.create({
inboxIdentifier: this.config.inboxIdentifier,
contactIdentifier: contactIdentifier,
});
this.logger.info(
`Created conversation.id: ${conversation.id} for contact.id: ${contactIdentifier}`,
);
return conversation;
}
async upsert(contactIdentifier: string): Promise<public_conversation> {
let conversation = await this.find(contactIdentifier);
if (!conversation) {
conversation = await this.create(contactIdentifier);
}
this.logger.info(
`Using conversation.id: ${conversation.id} for contact.id: ${contactIdentifier}`,
);
return conversation;
}
}
@@ -0,0 +1,78 @@
import ChatwootClient, { contact_conversations } from '@figuro/chatwoot-sdk';
import { ILogger } from '@waha/apps/app_sdk/ILogger';
import {
ChatWootAPIConfig,
ChatWootInboxAPI,
} from '@waha/apps/chatwoot/client/interfaces';
import type { conversation } from '@figuro/chatwoot-sdk/dist/models/conversation';
import { ConversationSelector } from '@waha/apps/chatwoot/services/ConversationSelector';
import axios from 'axios';
import { ChatWootInboxNewAPI } from '@waha/apps/chatwoot/client/ChatWootInboxNewAPI';
export type ConversationResult = Pick<conversation, 'id' | 'account_id'>;
export interface ContactIds {
id: number;
sourceId: string;
}
export class ConversationService {
constructor(
private config: ChatWootAPIConfig,
private accountAPI: ChatwootClient,
private inboxAPI: ChatWootInboxAPI,
private selector: ConversationSelector,
private logger: ILogger,
) {}
private async find(contact: ContactIds): Promise<ConversationResult | null> {
const result: { payload: contact_conversations } =
(await this.accountAPI.contacts.listConversations({
accountId: this.config.accountId,
id: contact.id,
})) as any;
const conversations = result.payload;
return this.selector.select(conversations);
}
private async create(contact: ContactIds): Promise<ConversationResult> {
const conversation = await this.inboxAPI.conversations.create({
inboxIdentifier: this.config.inboxIdentifier,
contactIdentifier: contact.sourceId,
});
this.logger.info(
`Created conversation.id: ${conversation.id} for contact.id: ${contact.id}, contact.sourceId: ${contact.sourceId}`,
);
return conversation;
}
async upsert(contact: ContactIds): Promise<ConversationResult> {
let conversation = await this.find(contact);
if (!conversation) {
conversation = await this.create(contact);
}
this.logger.info(
`Using conversation.id: ${conversation.id} for contact.id: ${contact.id}, contact.sourceId: ${contact.sourceId}`,
);
return conversation;
}
async markAsRead(conversationId: number, sourceId: string): Promise<void> {
try {
const inboxNewAPI = new ChatWootInboxNewAPI(
this.config.url,
this.config.inboxIdentifier,
);
await inboxNewAPI.updateLastSeen(sourceId, conversationId);
this.logger.info(
`Marked conversation.id: ${conversationId} as read in inbox: ${this.config.inboxIdentifier} for contact: ${sourceId}`,
);
} catch (error) {
const reason = error instanceof Error ? error.message : String(error);
this.logger.error(
`Error marking conversation.id: ${conversationId} as read: ${reason}`,
);
throw error;
}
}
}
@@ -1,53 +0,0 @@
import ChatwootClient from '@figuro/chatwoot-sdk';
import { ChatWootAccountAPIConfig } from '@waha/apps/chatwoot/client/interfaces';
import {
CustomAttributeModel,
CustomAttributeType,
} from '@waha/apps/chatwoot/client/types';
export interface CustomAttribute {
key: string;
name: string;
type: CustomAttributeType;
description: string;
model: CustomAttributeModel;
}
export class CustomAttributesAPI {
constructor(
private config: ChatWootAccountAPIConfig,
private accountAPI: ChatwootClient,
) {}
async upsert(attribute: CustomAttribute): Promise<void> {
const attributes = await this.accountAPI.customAttributes.list({
accountId: this.config.accountId,
attributeModel: String(attribute.model) as '0' | '1',
});
const existing = attributes.find((a) => a.attribute_key === attribute.key);
if (existing) {
await this.accountAPI.customAttributes.update({
accountId: this.config.accountId,
id: existing.id,
data: {
attribute_key: attribute.key,
attribute_display_name: attribute.name,
attribute_display_type: attribute.type,
attribute_description: attribute.description,
},
});
} else {
await this.accountAPI.customAttributes.create({
accountId: this.config.accountId,
data: {
attribute_key: attribute.key,
attribute_display_name: attribute.name,
attribute_display_type: attribute.type,
attribute_description: attribute.description,
attribute_model: attribute.model,
},
});
}
}
}
@@ -0,0 +1,68 @@
import ChatwootClient from '@figuro/chatwoot-sdk';
import { ChatWootAccountAPIConfig } from '@waha/apps/chatwoot/client/interfaces';
import {
CustomAttributeModel,
CustomAttributeType,
} from '@waha/apps/chatwoot/client/types';
import type { custom_attribute } from '@figuro/chatwoot-sdk/dist/models/custom_attribute';
export interface CustomAttribute {
key: string;
name: string;
type: CustomAttributeType;
description: string;
model: CustomAttributeModel;
}
export class CustomAttributesService {
constructor(
private config: ChatWootAccountAPIConfig,
private accountAPI: ChatwootClient,
) {}
async upsert(attributes: Array<CustomAttribute>): Promise<void> {
const current: Record<CustomAttributeModel, Array<custom_attribute>> = {
[CustomAttributeModel.CONVERSATION]: [],
[CustomAttributeModel.CONTACT]: [],
};
const models = [
CustomAttributeModel.CONVERSATION,
CustomAttributeModel.CONTACT,
];
for (const model of models) {
current[model] = await this.accountAPI.customAttributes.list({
accountId: this.config.accountId,
attributeModel: String(model) as '0' | '1',
});
}
for (const attribute of attributes) {
const existing = current[attribute.model].find(
(a) => a.attribute_key === attribute.key,
);
if (existing) {
await this.accountAPI.customAttributes.update({
accountId: this.config.accountId,
id: existing.id,
data: {
attribute_key: attribute.key,
attribute_display_name: attribute.name,
attribute_display_type: attribute.type,
attribute_description: attribute.description,
},
});
} else {
await this.accountAPI.customAttributes.create({
accountId: this.config.accountId,
data: {
attribute_key: attribute.key,
attribute_display_name: attribute.name,
attribute_display_type: attribute.type,
attribute_description: attribute.description,
attribute_model: attribute.model,
},
});
}
}
}
}
+11 -1
View File
@@ -1,7 +1,7 @@
import { isLidUser } from '@adiwajshing/baileys/lib/WABinary/jid-utils';
import { AttributeKey } from '@waha/apps/chatwoot/const';
import { WhatsAppMessage } from '@waha/apps/chatwoot/storage';
import { buildMessageId } from '@waha/core/engines/noweb/session.noweb.core';
import { isLidUser } from '@waha/core/utils/jids';
export function GetJID(contact: any): string | null {
return contact?.custom_attributes?.[AttributeKey.WA_JID];
@@ -15,6 +15,16 @@ export function GetChatID(contact: any): string | null {
return contact?.custom_attributes?.[AttributeKey.WA_CHAT_ID];
}
export function GetAllChatIDs(contact: any): Array<string> {
const attrs = contact?.custom_attributes || {};
const ids = [
attrs[AttributeKey.WA_CHAT_ID],
attrs[AttributeKey.WA_JID],
attrs[AttributeKey.WA_LID],
];
return ids.filter(Boolean);
}
export function FindChatID(contact: any): string | null {
if (GetJID(contact)) {
return GetJID(contact);
+7
View File
@@ -51,3 +51,10 @@ export enum CustomAttributeModel {
CONVERSATION = 0,
CONTACT = 1,
}
export enum ConversationStatus {
OPEN = 'open',
PENDING = 'pending',
SNOOZED = 'snoozed',
RESOLVED = 'resolved',
}
+1 -1
View File
@@ -1,4 +1,4 @@
import { CustomAttribute } from '@waha/apps/chatwoot/client/CustomAttributesAPI';
import { CustomAttribute } from '@waha/apps/chatwoot/client/CustomAttributesService';
import { CustomAttributeModel, CustomAttributeType } from './client/types';
+3
View File
@@ -13,11 +13,14 @@ export enum QueueName {
WAHA_MESSAGE_REACTION = 'chatwoot.waha | message.reaction',
WAHA_MESSAGE_EDITED = 'chatwoot.waha | message.edited',
WAHA_MESSAGE_REVOKED = 'chatwoot.waha | message.revoked',
WAHA_MESSAGE_ACK = 'chatwoot.waha | message.ack',
//
// ChatWoot Events - Real
//
INBOX_MESSAGE_CREATED = 'chatwoot.inbox | message_created',
INBOX_MESSAGE_UPDATED = 'chatwoot.inbox | message_updated',
INBOX_CONVERSATION_CREATED = 'chatwoot.inbox | conversation_created',
INBOX_CONVERSATION_STATUS_CHANGED = 'chatwoot.inbox | conversation_status_changed',
//
// ChatWoot Events - Artificial
//
+9 -2
View File
@@ -10,7 +10,6 @@ import {
ChatIDNotFoundForContactError,
PhoneNumberNotFoundInWhatsAppError,
} from '@waha/apps/chatwoot/errors';
import { TKey } from '@waha/apps/chatwoot/locale';
import { WAHASessionAPI } from '@waha/apps/chatwoot/session/WAHASelf';
import { SessionManager } from '@waha/core/abc/manager.abc';
import { RMutexService } from '@waha/modules/rmutex/rmutex.service';
@@ -18,6 +17,7 @@ import { Job } from 'bullmq';
import { PinoLogger } from 'nestjs-pino';
import { AppRepository } from '../../storage';
import { TKey } from '@waha/apps/chatwoot/i18n/templates';
/**
* Base class for ChatWoot inbox consumers
@@ -58,13 +58,20 @@ export abstract class ChatWootInboxMessageConsumer extends AppConsumer {
job: Job,
): Promise<any>;
protected GetConversationID(body) {
return body.conversation.id;
}
/**
* Process the job
* This method is called by the queue processor
*/
async processJob(job: Job<InboxData, any, EventName>): Promise<any> {
const body = job.data.body;
const key = ChatWootConversationKey(job.data.app, body.conversation.id);
const key = ChatWootConversationKey(
job.data.app,
this.GetConversationID(body),
);
return await this.withMutex(job, key, () =>
this.ProcessAndReportStatus(job),
);
+28 -10
View File
@@ -7,13 +7,15 @@ import { MessageType } from '@waha/apps/chatwoot/client/types';
import { ChatWootInboxMessageConsumer } from '@waha/apps/chatwoot/consumers/inbox/base';
import { QueueName } from '@waha/apps/chatwoot/consumers/QueueName';
import { DIContainer } from '@waha/apps/chatwoot/di/DIContainer';
import { Locale, TKey } from '@waha/apps/chatwoot/locale';
import { Locale } from '@waha/apps/chatwoot/i18n/locale';
import { WAHASelf } from '@waha/apps/chatwoot/session/WAHASelf';
import { SessionStatusEmoji } from '@waha/apps/chatwoot/SessionStatusEmoji';
import { SessionManager } from '@waha/core/abc/manager.abc';
import { RMutexService } from '@waha/modules/rmutex/rmutex.service';
import { Job } from 'bullmq';
import { PinoLogger } from 'nestjs-pino';
import { TKey } from '@waha/apps/chatwoot/i18n/templates';
import { ChatWootCommandsConfig } from '@waha/apps/chatwoot/dto/config.dto';
@Processor(QueueName.INBOX_COMMANDS, { concurrency: JOB_CONCURRENCY })
export class ChatWootInboxCommandsConsumer extends ChatWootInboxMessageConsumer {
@@ -36,11 +38,14 @@ export class ChatWootInboxCommandsConsumer extends ChatWootInboxMessageConsumer
job.data.session,
container.Locale(),
container.WAHASelf(),
container.ChatWootConfig().commands,
);
return await handler.handle(body);
}
}
const CommandServerPrefix = 'server ';
enum Command {
STATUS = 'status',
RESTART = 'restart',
@@ -99,6 +104,7 @@ class SessionCommandHandler {
private session: string,
private l: Locale,
private waha: WAHASelf,
private commands: ChatWootCommandsConfig,
) {}
async handle(message: any) {
@@ -107,6 +113,13 @@ class SessionCommandHandler {
this.logger.info(
`Executing command ${command} for session ${this.session}`,
);
const warning = this.commandDisabledWarning(command);
const conversation = await this.repo.InboxNotifications();
if (warning) {
const text = this.l.key(warning).render();
await conversation.incoming(text);
return;
}
switch (command) {
case Command.RESTART:
await this.waha.restart(this.session);
@@ -117,7 +130,6 @@ class SessionCommandHandler {
case Command.LOGOUT:
this.logger.info(`Logging out session ${this.session}`);
await this.waha.logout(this.session);
const conversation = await this.repo.InboxNotifications();
const text = this.l.key(TKey.APP_LOGOUT_SUCCESS).render();
await conversation.incoming(text);
break;
@@ -128,7 +140,6 @@ class SessionCommandHandler {
case Command.STATUS: {
this.logger.info(`Getting status for session ${this.session}`);
const session = await this.waha.get(this.session);
const conversation = await this.repo.InboxNotifications();
const emoji = SessionStatusEmoji(session.status);
const text = this.l.key(TKey.APP_SESSION_CURRENT_STATUS).render({
emoji: emoji,
@@ -144,7 +155,6 @@ class SessionCommandHandler {
this.logger.info('Getting server version and status');
const version = await this.waha.serverVersion();
const status = await this.waha.serverStatus();
const conversation = await this.repo.InboxNotifications();
const text = this.l.key(TKey.APP_SERVER_VERSION_AND_STATUS).render({
version: JSON.stringify(version, null, 2),
status: JSON.stringify(status, null, 2),
@@ -154,7 +164,6 @@ class SessionCommandHandler {
}
case Command.SERVER_REBOOT: {
this.logger.info('Rebooting server (graceful)');
const conversation = await this.repo.InboxNotifications();
const text = this.l.key(TKey.APP_SERVER_REBOOT).render();
await conversation.incoming(text);
await this.waha.serverReboot(false);
@@ -162,14 +171,12 @@ class SessionCommandHandler {
}
case Command.SERVER_REBOOT_FORCE: {
this.logger.info('Rebooting server (force)');
const conversation = await this.repo.InboxNotifications();
const text = this.l.key(TKey.APP_SERVER_REBOOT_FORCE).render();
await conversation.incoming(text);
await this.waha.serverReboot(true);
break;
}
case Command.QR: {
const conversation = await this.repo.InboxNotifications();
const content = await this.waha.qr(this.session);
const message = AttachmentFromBuffer(content, 'qr.jpg');
message.message_type = MessageType.INCOMING;
@@ -178,7 +185,6 @@ class SessionCommandHandler {
}
case Command.SCREENSHOT: {
this.logger.info(`Getting screenshot for session ${this.session}`);
const conversation = await this.repo.InboxNotifications();
const content = await this.waha.screenshot(this.session);
const message = AttachmentFromBuffer(content, 'screenshot.jpg');
message.message_type = MessageType.INCOMING;
@@ -186,8 +192,9 @@ class SessionCommandHandler {
break;
}
case Command.HELP: {
const conversation = await this.repo.InboxNotifications();
const text = this.l.key(TKey.APP_COMMANDS_LIST).render();
const text = this.l
.key(TKey.APP_COMMANDS_LIST)
.render({ commands: this.commands });
await conversation.incoming(text);
break;
}
@@ -196,6 +203,17 @@ class SessionCommandHandler {
}
}
/**
* Check if the command allowed for this session
* Returns `null` if allowed or the message key to show as an error
*/
commandDisabledWarning(command: Command): TKey | null {
if (!this.commands.server && command.startsWith(CommandServerPrefix)) {
return TKey.APP_COMMANDS_SERVER_DISABLED;
}
return null;
}
findCommand(text: string): Command {
// Remove empty from end
text = text.trim();
@@ -0,0 +1,57 @@
import { Processor } from '@nestjs/bullmq';
import { JOB_CONCURRENCY } from '@waha/apps/app_sdk/constants';
import { QueueName } from '@waha/apps/chatwoot/consumers/QueueName';
import { ChatWootConversationKey } from '@waha/apps/chatwoot/consumers/mutex';
import { ChatWootInboxMessageConsumer } from '@waha/apps/chatwoot/consumers/inbox/base';
import { InboxData } from '@waha/apps/chatwoot/consumers/types';
import { Job } from 'bullmq';
import { GetAllChatIDs } from '@waha/apps/chatwoot/client/ids';
import { DIContainer } from '@waha/apps/chatwoot/di/DIContainer';
import { TKey } from '@waha/apps/chatwoot/i18n/templates';
import { ContactConversationService } from '@waha/apps/chatwoot/client/ContactConversationService';
import { SessionManager } from '@waha/core/abc/manager.abc';
import { PinoLogger } from 'nestjs-pino';
import { RMutexService } from '@waha/modules/rmutex';
@Processor(QueueName.INBOX_CONVERSATION_CREATED, {
concurrency: JOB_CONCURRENCY,
})
export class ChatWootConversationCreatedConsumer extends ChatWootInboxMessageConsumer {
constructor(
protected readonly manager: SessionManager,
log: PinoLogger,
rmutex: RMutexService,
) {
super(manager, log, rmutex, 'ChatWootConversationCreatedConsumer');
}
protected ErrorHeaderKey(): TKey | null {
return null;
}
protected GetConversationID(body) {
return body.id;
}
protected async Process(
container: DIContainer,
body: any,
job: Job,
): Promise<any> {
const handler = new ConversationCreatedHandler(
container.ContactConversationService(),
);
return handler.handle(body);
}
}
class ConversationCreatedHandler {
constructor(private service: ContactConversationService) {}
async handle(body: any) {
const ids = GetAllChatIDs(body?.meta?.sender);
if (!ids || ids.length === 0) {
return;
}
this.service.ResetMismatchedCache(ids, body.id);
}
}
@@ -0,0 +1,62 @@
import { Processor } from '@nestjs/bullmq';
import { JOB_CONCURRENCY } from '@waha/apps/app_sdk/constants';
import { QueueName } from '@waha/apps/chatwoot/consumers/QueueName';
import { ChatWootInboxMessageConsumer } from '@waha/apps/chatwoot/consumers/inbox/base';
import { Job } from 'bullmq';
import { DIContainer } from '../../di/DIContainer';
import { TKey } from '../../i18n/templates';
import { ContactConversationService } from '@waha/apps/chatwoot/client/ContactConversationService';
import { AttributeKey } from '@waha/apps/chatwoot/const';
import { ConversationSelector } from '@waha/apps/chatwoot/services/ConversationSelector';
import { GetAllChatIDs } from '@waha/apps/chatwoot/client/ids';
import { SessionManager } from '@waha/core/abc/manager.abc';
import { PinoLogger } from 'nestjs-pino';
import { RMutexService } from '@waha/modules/rmutex';
@Processor(QueueName.INBOX_CONVERSATION_STATUS_CHANGED, {
concurrency: JOB_CONCURRENCY,
})
export class ChatWootConversationStatusChangedConsumer extends ChatWootInboxMessageConsumer {
constructor(
protected readonly manager: SessionManager,
log: PinoLogger,
rmutex: RMutexService,
) {
super(manager, log, rmutex, 'ChatWootConversationStatusChangedConsumer');
}
protected ErrorHeaderKey(): TKey | null {
return null;
}
protected GetConversationID(body) {
return body.id;
}
protected async Process(
container: DIContainer,
body: any,
job: Job,
): Promise<any> {
const handler = new ConversationStatusChangedHandler(
container.ContactConversationService(),
container.ConversationSelector(),
);
return await handler.handle(body);
}
}
class ConversationStatusChangedHandler {
constructor(
private service: ContactConversationService,
private selector: ConversationSelector,
) {}
async handle(body) {
if (!this.selector.hasStatusFilter()) {
return;
}
const ids = GetAllChatIDs(body.meta?.sender);
this.service.ResetCache(ids);
}
}
@@ -8,14 +8,14 @@ import {
} from '@waha/apps/chatwoot/consumers/inbox/base';
import { QueueName } from '@waha/apps/chatwoot/consumers/QueueName';
import { DIContainer } from '@waha/apps/chatwoot/di/DIContainer';
import { TKey } from '@waha/apps/chatwoot/locale';
import { EngineHelper } from '@waha/apps/chatwoot/session';
import { WAHASessionAPI } from '@waha/apps/chatwoot/session/WAHASelf';
import {
ChatwootMessage,
MessageMapping,
MessageMappingService,
} from '@waha/apps/chatwoot/storage';
import { MarkdownToWhatsApp } from '@waha/apps/chatwoot/text';
import { MarkdownToWhatsApp } from '@waha/apps/chatwoot/messages/to/whatsapp/markdown';
import { SessionManager } from '@waha/core/abc/manager.abc';
import { RMutexService } from '@waha/modules/rmutex/rmutex.service';
import {
@@ -30,6 +30,12 @@ import { Job } from 'bullmq';
import { PinoLogger } from 'nestjs-pino';
import { SerializeWhatsAppKey } from '../../client/ids';
import { TKey } from '@waha/apps/chatwoot/i18n/templates';
import {
ChatWootConfig,
LinkPreview,
} from '@waha/apps/chatwoot/dto/config.dto';
import { Locale } from '@waha/apps/chatwoot/i18n/locale';
// eslint-disable-next-line @typescript-eslint/no-var-requires
const mime = require('mime-types');
@@ -45,7 +51,7 @@ export class ChatWootInboxMessageCreatedConsumer extends ChatWootInboxMessageCon
}
ErrorHeaderKey(): TKey | null {
return TKey.WHATSAPP_ERROR_SENDING_MESSAGE;
return TKey.WHATSAPP_MESSAGE_SENDING_ERROR;
}
protected async Process(container: DIContainer, body, job: Job) {
@@ -55,6 +61,8 @@ export class ChatWootInboxMessageCreatedConsumer extends ChatWootInboxMessageCon
container.MessageMappingService(),
container.Logger(),
session,
container.ChatWootConfig(),
container.Locale(),
);
return await handler.handle(body);
}
@@ -65,12 +73,17 @@ export class MessageHandler {
private mappingService: MessageMappingService,
private logger: ILogger,
private session: WAHASessionAPI,
private config: ChatWootConfig,
private l: Locale,
) {}
async handle(body: any) {
const chatId = await LookupAndCheckChatId(this.session, body);
const message = body;
if (message.content_type != 'text') {
if (
message.content_type != 'text' &&
message.content_type != 'input_csat'
) {
this.logger.info(
`Message content type not supported. Content type: ${message.content_type}`,
);
@@ -78,37 +91,60 @@ export class MessageHandler {
}
const content = MarkdownToWhatsApp(message.content);
const attachments = message.attachments || [];
const results = [];
const multipleAttachments = attachments.length > 1;
let part = 1;
let part = 0; // Start from 0 and increment before each send/check
const replyTo = await this.getReplyTo(message).catch((err) => {
this.logger.error(`Error getting reply to message ID: ${err}`);
this.logger.error(
`ChatWoot => WhatsApp: error getting reply to message ID: ${err}`,
);
return undefined;
});
// Send text
if (attachments.length == 0 || multipleAttachments) {
const msg = await this.sendTextMessage(chatId, content, replyTo);
results.push(msg);
part = await this.saveMapping(message, msg, part);
this.logger.info(`Text message sent: ${msg.id}`);
// Send text (Part 1 if present)
const attachments = message.attachments || [];
const sendText = content && attachments.length !== 1;
if (sendText) {
part += 1; // Text is the first possible part
const exists = await this.getMapping(message, part);
if (exists) {
this.logger.warn(
`Skip part ${part}: mapping exists for Chatwoot message ${message.id}`,
);
} else {
const textTemplate = this.l.key(TKey.CW_TO_WA_MESSAGE_TEXT);
const text = textTemplate.render({
content: content,
chatwoot: body,
});
const msg = await this.sendTextMessage(chatId, text, replyTo);
results.push(msg);
await this.saveMapping(message, msg, part);
this.logger.info(`Text message sent: ${msg.id}`);
}
}
// Send files
const fileMessageContent = multipleAttachments ? '' : content;
const captionTemplate = this.l.key(TKey.CW_TO_WA_MESSAGE_MEDIA_CAPTION);
for (const file of attachments) {
const msg = await this.sendFile(
chatId,
fileMessageContent,
file,
replyTo,
);
part += 1; // Increment before each attachment send/check
const exists = await this.getMapping(message, part);
if (exists) {
this.logger.warn(
`Skip part ${part}: mapping exists for Chatwoot message ${message.id}`,
);
continue;
}
const caption = captionTemplate.render({
content: content,
chatwoot: body,
singleAttachment: attachments.length == 1,
});
const msg = await this.sendFile(chatId, caption, file, replyTo);
this.logger.info(
`File message sent: ${msg.id} - ${file.data_url} - ${file.file_type}`,
);
results.push(msg);
part = await this.saveMapping(message, msg, part);
await this.saveMapping(message, msg, part);
}
return results;
}
@@ -117,15 +153,27 @@ export class MessageHandler {
chatwootMessage: any,
whatsappMessage: any,
part: number,
): Promise<number> {
): Promise<void> {
const chatwoot: Omit<ChatwootMessage, 'id'> = {
timestamp: new Date(chatwootMessage.created_at),
conversation_id: chatwootMessage.conversation.id,
message_id: chatwootMessage.id,
};
const whatsapp = EngineHelper.WhatsAppMessageKeys(whatsappMessage);
await this.mappingService.map(chatwoot, whatsapp, 1);
return part + 1;
await this.mappingService.map(chatwoot, whatsapp, part);
}
private async getMapping(
message: any,
part: number,
): Promise<MessageMapping | null> {
return this.mappingService.getMappingByChatwootCombinedKeyAndPart(
{
conversation_id: message.conversation.id,
message_id: message.id,
},
part,
);
}
async getReplyTo(message): Promise<string | undefined> {
@@ -154,6 +202,10 @@ export class MessageHandler {
chatId: chatId,
text: content,
reply_to: replyTo,
linkPreview: [LinkPreview.LQ, LinkPreview.HQ].includes(
this.config.linkPreview,
),
linkPreviewHighQuality: this.config.linkPreview == LinkPreview.HQ,
};
const session = this.session;
await session.readMessages(chatId);
@@ -8,13 +8,13 @@ import {
} from '@waha/apps/chatwoot/consumers/inbox/base';
import { QueueName } from '@waha/apps/chatwoot/consumers/QueueName';
import { DIContainer } from '@waha/apps/chatwoot/di/DIContainer';
import { TKey } from '@waha/apps/chatwoot/locale';
import { WAHASessionAPI } from '@waha/apps/chatwoot/session/WAHASelf';
import { MessageMappingService } from '@waha/apps/chatwoot/storage';
import { SessionManager } from '@waha/core/abc/manager.abc';
import { RMutexService } from '@waha/modules/rmutex/rmutex.service';
import { Job } from 'bullmq';
import { PinoLogger } from 'nestjs-pino';
import { TKey } from '@waha/apps/chatwoot/i18n/templates';
@Processor(QueueName.INBOX_MESSAGE_DELETED, { concurrency: JOB_CONCURRENCY })
export class ChatWootInboxMessageDeletedConsumer extends ChatWootInboxMessageConsumer {
@@ -27,7 +27,7 @@ export class ChatWootInboxMessageDeletedConsumer extends ChatWootInboxMessageCon
}
ErrorHeaderKey(): TKey | null {
return TKey.WHATSAPP_ERROR_REMOVING_MESSAGE;
return TKey.WHATSAPP_MESSAGE_REMOVING_ERROR;
}
protected async Process(container: DIContainer, body, job: Job) {
@@ -4,13 +4,13 @@ import { ChatWootInboxMessageConsumer } from '@waha/apps/chatwoot/consumers/inbo
import { MessageHandler } from '@waha/apps/chatwoot/consumers/inbox/message_created';
import { QueueName } from '@waha/apps/chatwoot/consumers/QueueName';
import { DIContainer } from '@waha/apps/chatwoot/di/DIContainer';
import { TKey } from '@waha/apps/chatwoot/locale';
import { SessionManager } from '@waha/core/abc/manager.abc';
import { RMutexService } from '@waha/modules/rmutex/rmutex.service';
import { Job } from 'bullmq';
import { PinoLogger } from 'nestjs-pino';
import { WAHASessionAPI } from '../../session/WAHASelf';
import { TKey } from '@waha/apps/chatwoot/i18n/templates';
@Processor(QueueName.INBOX_MESSAGE_UPDATED, { concurrency: JOB_CONCURRENCY })
export class ChatWootInboxMessageUpdatedConsumer extends ChatWootInboxMessageConsumer {
@@ -23,7 +23,7 @@ export class ChatWootInboxMessageUpdatedConsumer extends ChatWootInboxMessageCon
}
ErrorHeaderKey(): TKey | null {
return TKey.WHATSAPP_ERROR_SENDING_MESSAGE;
return TKey.WHATSAPP_MESSAGE_SENDING_ERROR;
}
protected async Process(container: DIContainer, body, job: Job) {
@@ -33,6 +33,8 @@ export class ChatWootInboxMessageUpdatedConsumer extends ChatWootInboxMessageCon
container.MessageMappingService(),
container.Logger(),
session,
container.ChatWootConfig(),
container.Locale(),
);
return await handler.handle(body);
}
@@ -5,13 +5,13 @@ import { MessageType } from '@waha/apps/chatwoot/client/types';
import { ScheduledData } from '@waha/apps/chatwoot/consumers/types';
import { DIContainer } from '@waha/apps/chatwoot/di/DIContainer';
import { ChatWootAppConfig } from '@waha/apps/chatwoot/dto/config.dto';
import { TKey } from '@waha/apps/chatwoot/locale';
import { SessionManager } from '@waha/core/abc/manager.abc';
import { RMutexService } from '@waha/modules/rmutex/rmutex.service';
import { Job } from 'bullmq';
import { PinoLogger } from 'nestjs-pino';
import { AppRepository } from '../../storage';
import { TKey } from '@waha/apps/chatwoot/i18n/templates';
/**
* Base class for ChatWoot scheduled consumers
@@ -4,7 +4,6 @@ import { JOB_CONCURRENCY } from '@waha/apps/app_sdk/constants';
import { ILogger } from '@waha/apps/app_sdk/ILogger';
import { QueueName } from '@waha/apps/chatwoot/consumers/QueueName';
import { DIContainer } from '@waha/apps/chatwoot/di/DIContainer';
import { TKey } from '@waha/apps/chatwoot/locale';
import { SessionManager } from '@waha/core/abc/manager.abc';
import { CHANGELOG_URL, SUPPORT_US_URL } from '@waha/core/constants';
import { RMutexService } from '@waha/modules/rmutex/rmutex.service';
@@ -14,6 +13,7 @@ import { Job } from 'bullmq';
import { PinoLogger } from 'nestjs-pino';
import { ChatWootScheduledConsumer } from './base';
import { TKey } from '@waha/apps/chatwoot/i18n/templates';
/**
* Scheduled consumer for version checking
@@ -26,7 +26,7 @@ export class CheckVersionConsumer extends ChatWootScheduledConsumer {
}
protected ErrorHeaderKey(): TKey {
return TKey.APP_SCHEDULED_JOB_ERROR;
return TKey.JOB_SCHEDULED_ERROR_HEADER;
}
/**
@@ -50,7 +50,7 @@ export class CheckVersionConsumer extends ChatWootScheduledConsumer {
return;
}
logger.info('WAHA is using the CORE version');
const supportMessage = locale.key(TKey.APP_CORE_VERSION_USED).render({
const supportMessage = locale.key(TKey.WAHA_CORE_VERSION_USED).render({
supportUrl: SUPPORT_US_URL,
});
await conversation.incoming(supportMessage);
@@ -74,7 +74,7 @@ export class CheckVersionConsumer extends ChatWootScheduledConsumer {
// Send a message to Chatwoot conversation about a new version
const locale = container.Locale();
const message = locale.key(TKey.APP_NEW_VERSION_AVAILABLE).render({
const message = locale.key(TKey.WAHA_NEW_VERSION_AVAILABLE).render({
currentVersion: currentVersion,
newVersion: latestVersion,
changelogUrl: CHANGELOG_URL,
@@ -3,13 +3,13 @@ import { Injectable } from '@nestjs/common';
import { JOB_CONCURRENCY } from '@waha/apps/app_sdk/constants';
import { QueueName } from '@waha/apps/chatwoot/consumers/QueueName';
import { DIContainer } from '@waha/apps/chatwoot/di/DIContainer';
import { TKey } from '@waha/apps/chatwoot/locale';
import { SessionManager } from '@waha/core/abc/manager.abc';
import { RMutexService } from '@waha/modules/rmutex/rmutex.service';
import { Job } from 'bullmq';
import { PinoLogger } from 'nestjs-pino';
import { ChatWootScheduledConsumer } from './base';
import { TKey } from '@waha/apps/chatwoot/i18n/templates';
/**
* Scheduled consumer for message cleanup tasks
@@ -26,7 +26,7 @@ export class MessageCleanupConsumer extends ChatWootScheduledConsumer {
}
protected ErrorHeaderKey(): TKey {
return TKey.APP_SCHEDULED_JOB_ERROR;
return TKey.JOB_SCHEDULED_ERROR_HEADER;
}
/**
+33 -21
View File
@@ -1,4 +1,3 @@
import { isJidGroup, isJidStatusBroadcast } from '@adiwajshing/baileys';
import { conversation_message_create } from '@figuro/chatwoot-sdk';
import type { generic_id } from '@figuro/chatwoot-sdk/dist/models/generic_id';
import type { message } from '@figuro/chatwoot-sdk/dist/models/message';
@@ -13,7 +12,7 @@ import { WhatsAppChatIdKey } from '@waha/apps/chatwoot/consumers/mutex';
import { EventData } from '@waha/apps/chatwoot/consumers/types';
import { WhatsAppContactInfo } from '@waha/apps/chatwoot/contacts/WhatsAppContactInfo';
import { DIContainer } from '@waha/apps/chatwoot/di/DIContainer';
import { Locale, TKey } from '@waha/apps/chatwoot/locale';
import { Locale } from '@waha/apps/chatwoot/i18n/locale';
import { WAHASelf, WAHASessionAPI } from '@waha/apps/chatwoot/session/WAHASelf';
import {
AppRepository,
@@ -22,7 +21,6 @@ import {
WhatsAppMessage,
} from '@waha/apps/chatwoot/storage';
import { SessionManager } from '@waha/core/abc/manager.abc';
import { toCusFormat } from '@waha/core/engines/noweb/session.noweb.core';
import { parseMessageIdSerialized } from '@waha/core/utils/ids';
import { RMutexService } from '@waha/modules/rmutex/rmutex.service';
import { WAHAEvents } from '@waha/structures/enums.dto';
@@ -35,7 +33,8 @@ import { sleep } from '@waha/utils/promiseTimeout';
import { Job } from 'bullmq';
import { PinoLogger } from 'nestjs-pino';
import { ChatWootAppConfig } from '../../dto/config.dto';
import { TKey } from '@waha/apps/chatwoot/i18n/templates';
import { isJidBroadcast, isJidGroup, toCusFormat } from '@waha/core/utils/jids';
export function ListenEventsForChatWoot() {
return [
@@ -43,6 +42,7 @@ export function ListenEventsForChatWoot() {
WAHAEvents.MESSAGE_REACTION,
WAHAEvents.MESSAGE_EDITED,
WAHAEvents.MESSAGE_REVOKED,
WAHAEvents.MESSAGE_ACK,
WAHAEvents.SESSION_STATUS,
];
}
@@ -64,7 +64,7 @@ export abstract class ChatWootWAHABaseConsumer extends AppConsumer {
}
protected ErrorHeaderKey(): TKey | null {
return TKey.WHATSAPP_ERROR_RECEIVING_MESSAGE;
return TKey.WHATSAPP_MESSAGE_RECEIVING_ERROR;
}
private async ProcessAndReportErrors(job) {
@@ -97,8 +97,15 @@ export abstract class ChatWootWAHABaseConsumer extends AppConsumer {
messageInfo: IMessageInfo,
): Promise<any>;
ShouldProcess(event: any): boolean {
return true;
}
async processJob(job: Job<EventData, any, WAHAEvents>): Promise<any> {
const event: WAHAWebhookMessageRevoked = job.data.event as any;
const event = job.data.event as any;
if (!this.ShouldProcess(event)) {
return;
}
const key = WhatsAppChatIdKey(job.data.app, this.GetChatId(event));
return await this.withMutex(job, key, () =>
this.ProcessAndReportErrors(job),
@@ -151,7 +158,7 @@ export abstract class ChatWootWAHABaseConsumer extends AppConsumer {
.ContactConversationService()
.ConversationById(info.conversationId);
} else {
const conversation = await container
conversation = await container
.ContactConversationService()
.InboxNotifications();
}
@@ -183,8 +190,15 @@ class MessageReportInfo implements IMessageInfo {
}
}
export interface ChatWootMessagePartial {
content: string;
attachments: SendAttachment[];
private?: boolean;
}
export abstract class MessageBaseHandler<Payload extends WAMessageBase> {
constructor(
protected job: Job,
protected mappingService: MessageMappingService,
protected repo: ContactConversationService,
protected logger: ILogger,
@@ -194,12 +208,12 @@ export abstract class MessageBaseHandler<Payload extends WAMessageBase> {
protected waha: WAHASelf,
) {}
abstract getContent(payload: Payload): string;
protected abstract getMessage(
payload: Payload,
): Promise<ChatWootMessagePartial>;
abstract getReplyToWhatsAppID(payload: Payload): string | undefined;
abstract getAttachments(payload: Payload): Promise<SendAttachment[]>;
async handle(data: WAHAWebhook<Payload>) {
const payload = data.payload;
// Find the type as soon as possible for error reporting
@@ -262,7 +276,8 @@ export abstract class MessageBaseHandler<Payload extends WAMessageBase> {
private async buildChatWootMessage(
payload: Payload,
): Promise<conversation_message_create> {
let content: string = this.getContent(payload);
const message = await this.getMessage(payload);
let content = message.content;
// Format the content if the message from me
if (payload.fromMe && payload.source === MessageSource.APP) {
@@ -276,7 +291,7 @@ export abstract class MessageBaseHandler<Payload extends WAMessageBase> {
const chatId = payload.from;
// Add participant name to group messages
const manyParticipants = isJidGroup(chatId) || isJidStatusBroadcast(chatId);
const manyParticipants = isJidGroup(chatId) || isJidBroadcast(chatId);
if (!payload.fromMe && manyParticipants) {
const key = parseMessageIdSerialized(payload.id, true);
let participant = toCusFormat(key.participant);
@@ -290,14 +305,11 @@ export abstract class MessageBaseHandler<Payload extends WAMessageBase> {
participant: participant,
});
}
const attachments: SendAttachment[] = await this.getAttachments(payload);
if (!content && attachments.length > 0) {
// With no content ChatWoot render the message as an ugly big attachment
content = ' ';
}
const replyTo = await this.getReplyToChatWootMessageID(payload).catch(
(err) => {
this.logger.error(`Error getting reply to message ID: ${err}`);
this.logger.error(
`WhatsApp => ChatWoot - error getting reply to message ID: ${err}`,
);
return undefined;
},
);
@@ -306,8 +318,8 @@ export abstract class MessageBaseHandler<Payload extends WAMessageBase> {
return {
content: content,
message_type: type,
private: payload.fromMe,
attachments: attachments as any,
private: message.private ?? payload.fromMe,
attachments: message.attachments as any,
content_attributes: {
in_reply_to: replyTo,
},
@@ -325,6 +337,6 @@ export abstract class MessageBaseHandler<Payload extends WAMessageBase> {
chat_id: payload.from,
message_id: replyToWhatsAppID,
});
return chatwoot.message_id;
return chatwoot?.message_id;
}
}
@@ -0,0 +1,108 @@
import { Processor } from '@nestjs/bullmq';
import { ILogger } from '@waha/apps/app_sdk/ILogger';
import { JOB_CONCURRENCY } from '@waha/apps/app_sdk/constants';
import { ContactConversationService } from '@waha/apps/chatwoot/client/ContactConversationService';
import { QueueName } from '@waha/apps/chatwoot/consumers/QueueName';
import { EventData } from '@waha/apps/chatwoot/consumers/types';
import {
ChatWootWAHABaseConsumer,
IMessageInfo,
} from '@waha/apps/chatwoot/consumers/waha/base';
import { WhatsAppContactInfo } from '@waha/apps/chatwoot/contacts/WhatsAppContactInfo';
import { Locale } from '@waha/apps/chatwoot/i18n/locale';
import { WAHASessionAPI } from '@waha/apps/chatwoot/session/WAHASelf';
import { SessionManager } from '@waha/core/abc/manager.abc';
import { RMutexService } from '@waha/modules/rmutex/rmutex.service';
import { WAHAEvents } from '@waha/structures/enums.dto';
import { WAHAWebhookMessageAck } from '@waha/structures/webhooks.dto';
import { Job } from 'bullmq';
import { PinoLogger } from 'nestjs-pino';
import { ShouldMarkAsReadInChatWoot } from '@waha/apps/chatwoot/consumers/waha/message.ack.utils';
@Processor(QueueName.WAHA_MESSAGE_ACK, { concurrency: JOB_CONCURRENCY })
export class WAHAMessageAckConsumer extends ChatWootWAHABaseConsumer {
constructor(
protected readonly manager: SessionManager,
log: PinoLogger,
rmutex: RMutexService,
) {
super(manager, log, rmutex, 'WAHAMessageAckConsumer');
}
ShouldProcess(event: any): boolean {
return ShouldMarkAsReadInChatWoot(event);
}
GetChatId(event: WAHAWebhookMessageAck): string {
return event.payload.from;
}
async Process(
job: Job<EventData, any, WAHAEvents>,
info: IMessageInfo,
): Promise<void> {
const container = await this.DIContainer(job, job.data.app);
const event = job.data.event as WAHAWebhookMessageAck;
const session = new WAHASessionAPI(event.session, container.WAHASelf());
const handler = new MessageAckHandler(
container.ContactConversationService(),
container.Logger(),
info,
session,
container.Locale(),
);
try {
await handler.handle(event);
} catch (e) {
// TODO: Investigate errors
// https://github.com/devlikeapro/waha/issues/1492
this.logger.error(e);
}
}
}
class MessageAckHandler {
constructor(
private readonly contactConversationService: ContactConversationService,
private readonly logger: ILogger,
private readonly info: IMessageInfo,
private readonly session: WAHASessionAPI,
private readonly locale: Locale,
) {}
async handle(event: WAHAWebhookMessageAck): Promise<void> {
const payload = event.payload;
const contactInfo = WhatsAppContactInfo(
this.session,
payload.from,
this.locale,
);
const conversation =
await this.contactConversationService.ConversationByContact(contactInfo);
this.info.onConversationId(conversation.conversationId);
const sourceId = conversation.sourceId;
if (!sourceId) {
this.logger.warn(
`Contact not found for chat.id: ${payload.from}. Skipping read update for message ${payload.id}`,
);
return;
}
try {
await this.contactConversationService.markConversationAsRead(
conversation.conversationId,
sourceId,
);
this.logger.info(
`Marked conversation ${conversation.conversationId} as read for chat.id: ${payload.from} (message: ${payload.id}, sourceId: ${sourceId})`,
);
} catch (error) {
const reason = error instanceof Error ? error.message : String(error);
this.logger.error(
`Error marking conversation ${conversation.conversationId} as read for chat.id: ${payload.from}. Reason: ${reason}`,
);
throw error;
}
}
}
@@ -0,0 +1,466 @@
import type { WAHAEngine, WAHAEvents } from '@waha/structures/enums.dto';
import { ShouldMarkAsReadInChatWoot } from '@waha/apps/chatwoot/consumers/waha/message.ack.utils';
interface TestParam {
name: string;
expected: boolean;
GOWS: any;
NOWEB: any;
WEBJS: any;
}
/**
* Me - 444444444444 (lid - 144444444444444)
* Participant - 11111111111 (lid - 011111111111111)
* Group - 222222222222222222
*/
const ShouldMarkAsReadCases: TestParam[] = [
{
name: 'Participant read My message (READ ack)',
expected: true,
GOWS: {
id: 'evt_00000000000000000000000000',
session: 'default',
event: 'message.ack' as WAHAEvents,
payload: {
id: 'true_11111111111@c.us_3333333333333333333333',
from: '11111111111@c.us',
to: null,
participant: null,
fromMe: true,
ack: 3,
ackName: 'READ',
_data: {
Chat: '11111111111@s.whatsapp.net',
Sender: '11111111111@s.whatsapp.net',
IsFromMe: false,
IsGroup: false,
AddressingMode: '',
SenderAlt: '',
RecipientAlt: '',
BroadcastListOwner: '',
BroadcastRecipients: null,
MessageIDs: ['3333333333333333333333'],
Type: 'read',
MessageSender: '',
},
},
metadata: {},
me: {
id: '444444444444@c.us',
lid: '144444444444444@lid',
jid: '444444444444:42@s.whatsapp.net',
pushName: '',
},
environment: {
version: '2025.10.4',
engine: 'GOWS',
tier: 'PLUS',
browser: null,
},
engine: 'GOWS' as WAHAEngine,
},
NOWEB: {
id: 'evt_00000000000000000000000000',
session: 'default',
event: 'message.ack',
payload: {
id: 'true_11111111111@c.us_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
from: '11111111111@c.us',
fromMe: true,
ack: 3,
ackName: 'READ',
},
metadata: {},
me: {
id: '444444444444@c.us',
pushName: '',
lid: '144444444444444@lid',
},
engine: 'NOWEB',
environment: {
version: '2025.10.4',
engine: 'NOWEB',
tier: 'PLUS',
browser: null,
},
},
WEBJS: {
id: 'evt_00000000000000000000000000',
session: 'default',
event: 'message.ack',
payload: {
id: 'true_11111111111@c.us_AAA',
from: '444444444444@c.us',
fromMe: true,
source: 'app',
to: '11111111111@c.us',
body: 'Nn',
hasMedia: false,
media: null,
ack: 3,
ackName: 'READ',
vCards: [],
_data: {
id: {
fromMe: true,
remote: '11111111111@c.us',
id: 'AAA',
_serialized: 'true_11111111111@c.us_AAA',
},
viewed: false,
from: '444444444444@c.us',
to: '11111111111@c.us',
author: '444444444444@c.us',
ack: 3,
},
},
metadata: {},
me: {
id: '444444444444@c.us',
pushName: '',
},
engine: 'WEBJS',
environment: {
version: '2025.10.4',
engine: 'WEBJS',
tier: 'PLUS',
browser: '/usr/bin/google-chrome-stable',
},
},
},
{
name: 'Participant received My message (DEVICE ack)',
expected: false,
GOWS: {
id: 'evt_00000000000000000000000000',
session: 'default',
event: 'message.ack' as WAHAEvents,
payload: {
id: 'true_11111111111@c.us_3333333333333333333333',
from: '11111111111@c.us',
to: null,
participant: null,
fromMe: true,
ack: 2,
ackName: 'DEVICE',
_data: {
Chat: '11111111111@s.whatsapp.net',
Sender: '11111111111:3@s.whatsapp.net',
IsFromMe: false,
IsGroup: false,
AddressingMode: '',
SenderAlt: '',
RecipientAlt: '',
BroadcastListOwner: '',
BroadcastRecipients: null,
MessageIDs: ['3333333333333333333333'],
Type: '',
MessageSender: '',
},
},
me: {
id: '444444444444@c.us',
lid: '144444444444444@lid',
jid: '444444444444:42@s.whatsapp.net',
pushName: '',
},
environment: {
version: '2025.10.4',
engine: 'GOWS',
tier: 'PLUS',
browser: null,
},
engine: 'GOWS' as WAHAEngine,
},
NOWEB: {
id: 'evt_00000000000000000000000000',
session: 'default',
event: 'message.ack',
payload: {
id: 'true_11111111111@c.us_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
from: '11111111111@c.us',
fromMe: true,
ack: 2,
ackName: 'DEVICE',
},
metadata: {},
me: {
id: '444444444444@c.us',
pushName: '',
lid: '1144444444444444@lid',
},
engine: 'NOWEB',
environment: {
version: '2025.10.4',
engine: 'NOWEB',
tier: 'PLUS',
browser: null,
},
},
WEBJS: {
id: 'evt_00000000000000000000000000',
session: 'default',
event: 'message.ack',
payload: {
id: 'true_11111111111@c.us_AAA',
from: '444444444444@c.us',
fromMe: true,
source: 'app',
to: '11111111111@c.us',
body: 'Nn',
hasMedia: false,
media: null,
ack: 2,
ackName: 'DEVICE',
vCards: [],
_data: {
id: {
fromMe: true,
remote: '11111111111@c.us',
id: 'AAA',
_serialized: 'true_11111111111@c.us_AAA',
},
from: '444444444444@c.us',
to: '11111111111@c.us',
author: '444444444444@c.us',
ack: 2,
},
},
metadata: {},
me: {
id: '444444444444@c.us',
pushName: '',
},
engine: 'WEBJS',
environment: {
version: '2025.10.4',
engine: 'WEBJS',
tier: 'PLUS',
browser: '/usr/bin/google-chrome-stable',
},
},
},
{
name: 'Me read Participant message on another device (READ ack)',
expected: false,
GOWS: {
id: 'evt_00000000000000000000000000',
session: 'default',
event: 'message.ack' as WAHAEvents,
payload: {
id: 'false_11111111111@c.us_3333333333333333333333',
from: '11111111111@c.us',
to: null,
participant: null,
fromMe: false,
ack: 3,
ackName: 'READ',
_data: {
Chat: '11111111111@s.whatsapp.net',
Sender: '444444444444@s.whatsapp.net',
IsFromMe: true,
IsGroup: false,
AddressingMode: '',
SenderAlt: '',
RecipientAlt: '',
BroadcastListOwner: '',
BroadcastRecipients: null,
MessageIDs: ['3333333333333333333333'],
Type: 'read',
MessageSender: '11111111111@s.whatsapp.net',
},
},
metadata: {},
me: {
id: '444444444444@c.us',
lid: '144444444444444@lid',
jid: '444444444444:42@s.whatsapp.net',
pushName: '',
},
environment: {
version: '2025.10.4',
engine: 'GOWS',
tier: 'PLUS',
browser: null,
},
engine: 'GOWS' as WAHAEngine,
},
NOWEB: {
id: 'evt_00000000000000000000000000',
session: 'default',
event: 'message.ack',
payload: {
id: '11111111111@c.us_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
from: '11111111111@c.us',
fromMe: false,
ack: 3,
ackName: 'READ',
},
metadata: {},
me: {
id: '444444444444@c.us',
pushName: '',
lid: '1144444444444444@lid',
},
engine: 'NOWEB',
environment: {
version: '2025.10.4',
engine: 'NOWEB',
tier: 'PLUS',
browser: null,
},
},
WEBJS: null,
},
{
name: 'Participant read My Group Message (READ ack)',
expected: false,
GOWS: {
id: 'evt_00000000000000000000000000',
session: 'default',
payload: {
id: 'true_222222222222222222@g.us_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA_444444444444@c.us',
from: '222222222222222222@g.us',
to: '011111111111111@lid',
participant: '011111111111111@lid',
fromMe: true,
ack: 3,
ackName: 'READ',
_data: {
Chat: '222222222222222222@g.us',
Sender: '011111111111111@lid',
IsFromMe: false,
IsGroup: true,
AddressingMode: '',
SenderAlt: '',
RecipientAlt: '',
BroadcastListOwner: '',
BroadcastRecipients: null,
MessageIDs: ['AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'],
Type: 'read',
MessageSender: '',
},
},
metadata: {},
me: {
id: '444444444444@c.us',
lid: '144444444444444@lid',
jid: '444444444444:42@s.whatsapp.net',
pushName: '',
},
environment: {
version: '2025.10.4',
engine: 'GOWS',
tier: 'PLUS',
browser: null,
},
},
NOWEB: {
id: 'evt_00000000000000000000000000',
session: 'default',
event: 'message.ack',
payload: {
id: 'true_222222222222222222@g.us_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA_444444444444@c.us',
from: '222222222222222222@g.us',
to: '222222222222222222@g.us',
participant: '011111111111111@lid',
fromMe: true,
ack: 3,
ackName: 'READ',
_data: {
key: {
remoteJid: '222222222222222222@g.us',
id: 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
fromMe: true,
participant: '011111111111111@lid',
},
receipt: {
userJid: '011111111111111@lid',
},
},
},
metadata: {},
me: {
id: '444444444444@c.us',
pushName: '',
lid: '1144444444444444@lid',
},
engine: 'NOWEB',
environment: {
version: '2025.10.4',
engine: 'NOWEB',
tier: 'PLUS',
browser: null,
},
},
WEBJS: {
id: 'evt_00000000000000000000000000',
session: 'default',
event: 'message.ack',
payload: {
id: 'true_222222222222222222@g.us_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA_444444444444@c.us',
from: '222222222222222222@g.us',
to: '222222222222222222@g.us',
participant: '011111111111111@lid',
fromMe: false,
ack: 3,
ackName: 'READ',
_data: {
tag: 'receipt',
attrs: {
from: {
$1: {
type: 0,
user: '222222222222222222',
server: 'g.us',
},
},
type: 'read',
id: 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
participant: {
$1: {
type: 1,
user: '011111111111111',
device: 0,
domainType: 1,
},
},
},
content: null,
},
},
metadata: {},
me: {
id: '444444444444@c.us',
pushName: '',
},
engine: 'WEBJS',
environment: {
version: '2025.10.4',
engine: 'WEBJS',
tier: 'PLUS',
browser: '/usr/bin/google-chrome-stable',
},
},
},
];
describe('ShouldMarkAsReadInChatWoot', () => {
const engines = ['GOWS', 'NOWEB', 'WEBJS'];
for (const engine of engines) {
describe(engine, () => {
for (const param of ShouldMarkAsReadCases) {
const name = `[${param.expected}] ${param.name}`;
const event = param[engine];
const testfn = event ? test : test.skip;
const expected = param.expected;
testfn(name, () => {
// Test
const result = ShouldMarkAsReadInChatWoot(event);
expect(result).toBe(expected);
});
}
});
}
});
@@ -0,0 +1,31 @@
import type { WAHAWebhookMessageAck } from '@waha/structures/webhooks.dto';
import { isJidCusFormat } from '@waha/utils/wa';
import { WAMessageAck } from '@waha/structures/enums.dto';
import { isLidUser, toCusFormat } from '@waha/core/utils/jids';
export function ShouldMarkAsReadInChatWoot(
event: WAHAWebhookMessageAck,
): boolean {
// Mark as seen only if it's DM
// Ignore groups and other multiple participants chats
const chatId = event.payload.from;
if (!isJidCusFormat(chatId) && !isLidUser(chatId)) {
return false;
}
const payload = event.payload;
// Only READ and PLAYED
const read = payload.ack === WAMessageAck.READ;
const played = payload.ack == WAMessageAck.PLAYED;
if (!read && !played) {
return false;
}
// Only process when OUR message (fromMe: true) was read by recipient
if (!payload.fromMe) {
return false;
}
// Mark ChatWoot conversation as read when recipient reads our message
return true;
}
+75 -36
View File
@@ -1,15 +1,14 @@
import { Processor } from '@nestjs/bullmq';
import { JOB_CONCURRENCY } from '@waha/apps/app_sdk/constants';
import { SendAttachment } from '@waha/apps/chatwoot/client/types';
import { QueueName } from '@waha/apps/chatwoot/consumers/QueueName';
import { EventData } from '@waha/apps/chatwoot/consumers/types';
import {
ChatWootMessagePartial,
ChatWootWAHABaseConsumer,
IMessageInfo,
MessageBaseHandler,
} from '@waha/apps/chatwoot/consumers/waha/base';
import { MessageBaseHandler } from '@waha/apps/chatwoot/consumers/waha/base';
import { WAHASessionAPI } from '@waha/apps/chatwoot/session/WAHASelf';
import { WhatsappToMarkdown } from '@waha/apps/chatwoot/text';
import { SessionManager } from '@waha/core/abc/manager.abc';
import { parseMessageIdSerialized } from '@waha/core/utils/ids';
import { RMutexService } from '@waha/modules/rmutex/rmutex.service';
@@ -18,9 +17,19 @@ import { WAMessage } from '@waha/structures/responses.dto';
import { WAHAWebhookMessageAny } from '@waha/structures/webhooks.dto';
import { Job } from 'bullmq';
import { PinoLogger } from 'nestjs-pino';
// eslint-disable-next-line @typescript-eslint/no-var-requires
const mime = require('mime-types');
import {
FacebookAdMessage,
EventMessage,
PixMessage,
LocationMessage,
MessageToChatWootConverter,
PollMessage,
ListMessage,
ShareContactMessage,
TextMessage,
UnsupportedMessage,
resolveProtoMessage,
} from '@waha/apps/chatwoot/messages/to/chatwoot';
@Processor(QueueName.WAHA_MESSAGE_ANY, { concurrency: JOB_CONCURRENCY })
export class WAHAMessageAnyConsumer extends ChatWootWAHABaseConsumer {
@@ -44,6 +53,7 @@ export class WAHAMessageAnyConsumer extends ChatWootWAHABaseConsumer {
const event: WAHAWebhookMessageAny = job.data.event as any;
const session = new WAHASessionAPI(event.session, container.WAHASelf());
const handler = new MessageAnyHandler(
job,
container.MessageMappingService(),
container.ContactConversationService(),
container.Logger(),
@@ -57,9 +67,65 @@ export class WAHAMessageAnyConsumer extends ChatWootWAHABaseConsumer {
}
class MessageAnyHandler extends MessageBaseHandler<WAMessage> {
getContent(payload: WAMessage): string {
const body = payload.body;
return WhatsappToMarkdown(body);
protected async getMessage(
payload: WAMessage,
): Promise<ChatWootMessagePartial> {
let converter: MessageToChatWootConverter;
let msg: ChatWootMessagePartial;
const protoMessage = resolveProtoMessage(payload);
// Check for Facebook Ad first - but let it use the normal flow later
converter = new FacebookAdMessage(this.l, this.logger);
msg = await converter.convert(payload, protoMessage);
if (msg) {
return msg;
}
converter = new TextMessage(this.l, this.logger, this.waha);
msg = await converter.convert(payload, null);
if (msg) {
return msg;
}
converter = new LocationMessage(this.l);
msg = await converter.convert(payload, protoMessage);
if (msg) {
return msg;
}
converter = new ShareContactMessage(this.l, this.logger);
msg = await converter.convert(payload, protoMessage);
if (msg) {
return msg;
}
converter = new PollMessage(this.l);
msg = await converter.convert(payload, protoMessage);
if (msg) {
return msg;
}
converter = new ListMessage(this.l);
msg = await converter.convert(payload, protoMessage);
if (msg) {
return msg;
}
converter = new EventMessage(this.l);
msg = await converter.convert(payload, protoMessage);
if (msg) {
return msg;
}
converter = new PixMessage(this.l, this.logger);
msg = await converter.convert(payload, protoMessage);
if (msg) {
return msg;
}
converter = new UnsupportedMessage(this.l, this.job);
msg = await converter.convert(payload, protoMessage);
return msg;
}
getReplyToWhatsAppID(payload: WAMessage): string {
@@ -70,31 +136,4 @@ class MessageAnyHandler extends MessageBaseHandler<WAMessage> {
const key = parseMessageIdSerialized(replyTo.id, true);
return key.id;
}
async getAttachments(payload: WAMessage): Promise<SendAttachment[]> {
const hasMedia = payload.media?.url;
if (!hasMedia) {
return [];
}
const attachments: SendAttachment[] = [];
const media = payload.media;
this.logger.info(`Downloading media from '${media.url}'...`);
const buffer = await this.waha.fetch(media.url);
const fileContent = buffer.toString('base64');
let filename = media.filename;
if (!filename) {
const extension = mime.extension(media.mimetype);
filename = `no-filename.${extension}`;
}
const attachment: SendAttachment = {
content: fileContent,
filename: filename,
encoding: 'base64',
};
attachments.push(attachment);
this.logger.info(`Downloaded media from '${media.url}' as '${filename}'`);
return attachments;
}
}
@@ -3,12 +3,11 @@ import { JOB_CONCURRENCY } from '@waha/apps/app_sdk/constants';
import { QueueName } from '@waha/apps/chatwoot/consumers/QueueName';
import { EventData } from '@waha/apps/chatwoot/consumers/types';
import {
ChatWootMessagePartial,
ChatWootWAHABaseConsumer,
IMessageInfo,
MessageBaseHandler,
} from '@waha/apps/chatwoot/consumers/waha/base';
import { MessageBaseHandler } from '@waha/apps/chatwoot/consumers/waha/base';
import { TKey } from '@waha/apps/chatwoot/locale';
import { WhatsappToMarkdown } from '@waha/apps/chatwoot/text';
import { SessionManager } from '@waha/core/abc/manager.abc';
import { RMutexService } from '@waha/modules/rmutex/rmutex.service';
import { WAHAEvents } from '@waha/structures/enums.dto';
@@ -19,8 +18,12 @@ import {
import { Job } from 'bullmq';
import { PinoLogger } from 'nestjs-pino';
import { SendAttachment } from '../../client/types';
import { WAHASessionAPI } from '../../session/WAHASelf';
import {
MessageEdited,
MessageToChatWootConverter,
resolveProtoMessage,
} from '@waha/apps/chatwoot/messages/to/chatwoot';
@Processor(QueueName.WAHA_MESSAGE_EDITED, { concurrency: JOB_CONCURRENCY })
export class WAHAMessageEditedConsumer extends ChatWootWAHABaseConsumer {
@@ -44,6 +47,7 @@ export class WAHAMessageEditedConsumer extends ChatWootWAHABaseConsumer {
const event: WAHAWebhookMessageEdited = job.data.event as any;
const session = new WAHASessionAPI(event.session, container.WAHASelf());
const handler = new MessageEditedHandler(
job,
container.MessageMappingService(),
container.ContactConversationService(),
container.Logger(),
@@ -57,21 +61,15 @@ export class WAHAMessageEditedConsumer extends ChatWootWAHABaseConsumer {
}
class MessageEditedHandler extends MessageBaseHandler<WAMessageEditedBody> {
getContent(payload: WAMessageEditedBody): string {
const body = payload.body;
const formatted = WhatsappToMarkdown(body);
return this.l
.key(TKey.MESSAGE_EDITED_IN_WHATSAPP)
.render({ text: formatted });
protected async getMessage(
payload: WAMessageEditedBody,
): Promise<ChatWootMessagePartial> {
const protoMessage = resolveProtoMessage(payload);
const converter: MessageToChatWootConverter = new MessageEdited(this.l);
return converter.convert(payload, protoMessage);
}
getReplyToWhatsAppID(payload: WAMessageEditedBody): string {
return payload.editedMessageId;
}
async getAttachments(
payload: WAMessageEditedBody,
): Promise<SendAttachment[]> {
return [];
}
}
@@ -4,11 +4,11 @@ import { SendAttachment } from '@waha/apps/chatwoot/client/types';
import { QueueName } from '@waha/apps/chatwoot/consumers/QueueName';
import { EventData } from '@waha/apps/chatwoot/consumers/types';
import {
ChatWootMessagePartial,
ChatWootWAHABaseConsumer,
IMessageInfo,
} from '@waha/apps/chatwoot/consumers/waha/base';
import { MessageBaseHandler } from '@waha/apps/chatwoot/consumers/waha/base';
import { TKey } from '@waha/apps/chatwoot/locale';
import { WAHASessionAPI } from '@waha/apps/chatwoot/session/WAHASelf';
import { SessionManager } from '@waha/core/abc/manager.abc';
import { parseMessageIdSerialized } from '@waha/core/utils/ids';
@@ -18,6 +18,7 @@ import { WAMessageReaction, WAReaction } from '@waha/structures/responses.dto';
import { WAHAWebhookMessageReaction } from '@waha/structures/webhooks.dto';
import { Job } from 'bullmq';
import { PinoLogger } from 'nestjs-pino';
import { TKey } from '@waha/apps/chatwoot/i18n/templates';
@Processor(QueueName.WAHA_MESSAGE_REACTION, { concurrency: JOB_CONCURRENCY })
export class WAHAMessageReactionConsumer extends ChatWootWAHABaseConsumer {
@@ -41,6 +42,7 @@ export class WAHAMessageReactionConsumer extends ChatWootWAHABaseConsumer {
const event: WAHAWebhookMessageReaction = job.data.event as any;
const session = new WAHASessionAPI(event.session, container.WAHASelf());
const handler = new MessageReactionHandler(
job,
container.MessageMappingService(),
container.ContactConversationService(),
container.Logger(),
@@ -54,15 +56,24 @@ export class WAHAMessageReactionConsumer extends ChatWootWAHABaseConsumer {
}
export class MessageReactionHandler extends MessageBaseHandler<WAMessageReaction> {
getContent(payload: WAMessageReaction) {
protected async getMessage(
payload: WAMessageReaction,
): Promise<ChatWootMessagePartial> {
const reaction = payload.reaction as WAReaction;
const emoji = reaction.text;
if (!emoji) {
return this.l.key(TKey.WHATSAPP_REACTION_REMOVED).render();
let content: string;
if (emoji) {
content = this.l.key(TKey.WHATSAPP_REACTION_ADDED).render({
emoji: emoji,
});
} else {
content = this.l.key(TKey.WHATSAPP_REACTION_REMOVED).render();
}
return this.l.key(TKey.WHATSAPP_REACTION_ADDED).render({
emoji: emoji,
});
return {
content: content,
attachments: [],
private: undefined,
};
}
getReplyToWhatsAppID(payload: WAMessageReaction) {
@@ -71,8 +82,4 @@ export class MessageReactionHandler extends MessageBaseHandler<WAMessageReaction
const key = parseMessageIdSerialized(messageId, false);
return key.id;
}
async getAttachments(payload: WAMessageReaction): Promise<SendAttachment[]> {
return [];
}
}
@@ -10,18 +10,19 @@ import {
ChatWootWAHABaseConsumer,
IMessageInfo,
} from '@waha/apps/chatwoot/consumers/waha/base';
import { Locale, TKey } from '@waha/apps/chatwoot/locale';
import { Locale } from '@waha/apps/chatwoot/i18n/locale';
import {
ChatwootMessage,
MessageMappingService,
} from '@waha/apps/chatwoot/storage';
import { SessionManager } from '@waha/core/abc/manager.abc';
import { toCusFormat } from '@waha/core/engines/noweb/session.noweb.core';
import { RMutexService } from '@waha/modules/rmutex/rmutex.service';
import { WAHAEvents } from '@waha/structures/enums.dto';
import { WAHAWebhookMessageRevoked } from '@waha/structures/webhooks.dto';
import { Job } from 'bullmq';
import { PinoLogger } from 'nestjs-pino';
import { TKey } from '@waha/apps/chatwoot/i18n/templates';
import { toCusFormat } from '@waha/core/utils/jids';
@Processor(QueueName.WAHA_MESSAGE_REVOKED, { concurrency: JOB_CONCURRENCY })
export class WAHAMessageRevokedConsumer extends ChatWootWAHABaseConsumer {
@@ -10,7 +10,7 @@ import {
ChatWootWAHABaseConsumer,
IMessageInfo,
} from '@waha/apps/chatwoot/consumers/waha/base';
import { Locale, TKey } from '@waha/apps/chatwoot/locale';
import { Locale } from '@waha/apps/chatwoot/i18n/locale';
import { WAHASelf } from '@waha/apps/chatwoot/session/WAHASelf';
import { SessionStatusEmoji } from '@waha/apps/chatwoot/SessionStatusEmoji';
import { SessionManager } from '@waha/core/abc/manager.abc';
@@ -19,6 +19,8 @@ import { WAHAEvents, WAHASessionStatus } from '@waha/structures/enums.dto';
import { WAHAWebhookSessionStatus } from '@waha/structures/webhooks.dto';
import { Job } from 'bullmq';
import { PinoLogger } from 'nestjs-pino';
import { TKey } from '@waha/apps/chatwoot/i18n/templates';
import { waitUntil } from '@waha/utils/promiseTimeout';
@Processor(QueueName.WAHA_SESSION_STATUS, { concurrency: JOB_CONCURRENCY })
export class WAHASessionStatusConsumer extends ChatWootWAHABaseConsumer {
@@ -48,6 +50,9 @@ export class WAHASessionStatusConsumer extends ChatWootWAHABaseConsumer {
}
}
const RECOVER_TIME_MS = 10_000;
const CHECK_STATUS_INTERVAL_MS = 2_000;
export class SessionStatusHandler {
constructor(
private repo: ContactConversationService,
@@ -56,28 +61,61 @@ export class SessionStatusHandler {
) {}
async handle(data: WAHAWebhookSessionStatus) {
const conversation = await this.repo.InboxNotifications();
// Report session status
const emoji = SessionStatusEmoji(data.payload.status);
const activity = this.l.key(TKey.APP_SESSION_STATUS_CHANGE).r({
emoji: emoji,
session: data.session,
status: data.payload.status,
});
await conversation.activity(activity);
const payload = data.payload;
let text = '';
const current = payload.statuses?.at(-1);
const previous = payload.statuses?.at(-2);
const older = payload.statuses?.at(-3);
switch (payload.status) {
case WAHASessionStatus.STARTING:
// WORKING => STARTING [=> WORKING]
if (previous?.status == WAHASessionStatus.WORKING) {
// if previous is WORKING - wait, give time to recover
const recovered = await waitUntil(
async () => {
const response = await this.waha.get(data.session);
return response.status == WAHASessionStatus.WORKING;
},
CHECK_STATUS_INTERVAL_MS,
RECOVER_TIME_MS,
);
if (recovered) {
// Current status is WORKING, no needs for an alert
return;
}
}
break;
case WAHASessionStatus.WORKING:
// WORKING => STARTING => WORKING
if (
previous?.status == WAHASessionStatus.STARTING &&
older?.status == WAHASessionStatus.WORKING
) {
if (current?.timestamp - previous?.timestamp < RECOVER_TIME_MS) {
// Session got recovered fast, no needs to notify about that
return;
}
}
text = this.l.key(TKey.APP_SESSION_STATUS_WORKING).r({
name: data.me?.pushName || 'Unknown',
id: data.me?.id || 'No phone number',
});
break;
case WAHASessionStatus.STOPPED:
try {
// Check the session is yet in STOPPED status.
// In case of server restarts,
// the consumer can process STOPPED after STARTING/WORKING
const response = await this.waha.get(data.session);
if (response.status != WAHASessionStatus.STOPPED) {
return;
}
} catch (_) {}
text = this.l.key(TKey.APP_SESSION_STATUS_ERROR).r();
text += '\n\n';
text += this.l.key(TKey.APP_HELP_REMINDER).r();
text += '\n\n';
break;
case WAHASessionStatus.FAILED:
text = this.l.key(TKey.APP_SESSION_STATUS_ERROR).r();
text += '\n\n';
@@ -87,6 +125,18 @@ export class SessionStatusHandler {
case WAHASessionStatus.SCAN_QR_CODE:
text = this.l.key(TKey.APP_SESSION_SCAN_QR_CODE).r();
}
//
// Report Status Change if required
//
const conversation = await this.repo.InboxNotifications();
const emoji = SessionStatusEmoji(data.payload.status);
const activity = this.l.key(TKey.APP_SESSION_STATUS_CHANGE).r({
emoji: emoji,
session: data.session,
status: data.payload.status,
});
await conversation.activity(activity);
if (text) {
const message: conversation_message_create = {
content: text,
@@ -1,7 +1,8 @@
import { public_contact_create_update_payload } from '@figuro/chatwoot-sdk';
import { ContactInfo } from '@waha/apps/chatwoot/client/ContactConversationService';
import { AttributeKey, INBOX_CONTACT_CHAT_ID } from '@waha/apps/chatwoot/const';
import { Locale, TKey } from '@waha/apps/chatwoot/locale';
import { Locale } from '@waha/apps/chatwoot/i18n/locale';
import { TKey } from '@waha/apps/chatwoot/i18n/templates';
/**
* Inbox Notifications Contact
@@ -1,16 +1,21 @@
import {
isJidGroup,
isJidNewsletter,
isJidStatusBroadcast,
} from '@adiwajshing/baileys';
import { isLidUser } from '@adiwajshing/baileys/lib/WABinary/jid-utils';
import { public_contact_create_update_payload as Contact } from '@figuro/chatwoot-sdk';
import { ContactInfo } from '@waha/apps/chatwoot/client/ContactConversationService';
import { AttributeKey } from '@waha/apps/chatwoot/const';
import { Locale, TKey } from '@waha/apps/chatwoot/locale';
import { Locale } from '@waha/apps/chatwoot/i18n/locale';
import { WAHASessionAPI } from '@waha/apps/chatwoot/session/WAHASelf';
import { Channel } from '@waha/structures/channels.dto';
import { CacheAsync } from '@waha/utils/Cache';
import { TKey } from '@waha/apps/chatwoot/i18n/templates';
import {
isJidBroadcast,
isJidGroup,
isJidNewsletter,
isJidStatusBroadcast,
isLidUser,
isPnUser,
} from '@waha/core/utils/jids';
import { UnknownJIDFormat } from '@waha/apps/chatwoot/errors';
import { E164Parser } from '@waha/core/utils/PhoneJidNormalizer';
/**
* Base WhatsApp contact info class
@@ -64,8 +69,7 @@ class JidContactInfo extends ChatContactInfo {
const contact: any = await this.session.getContact(this.chatId);
const name =
contact?.name || contact?.pushName || contact?.pushname || this.chatId;
const phoneNumber = this.chatId.split('@')[0];
const phoneNumberE164 = '+' + phoneNumber;
const phoneNumberE164 = E164Parser.fromJid(this.chatId);
const result: Contact = {
name: name,
@@ -147,6 +151,7 @@ class GroupContactInfo extends ChatContactInfo {
const group: any = await this.session?.getGroup(this.chatId);
if (group) {
name = group.subject || group.name || group.topic || name;
name = group.Name || name;
const suffix = this.locale
.key(TKey.WHATSAPP_CONTACT_GROUP_SUFFIX)
.render();
@@ -220,6 +225,31 @@ class StatusContactInfo extends ChatContactInfo {
}
}
class BroadcastContactInfo extends ChatContactInfo {
async Attributes() {
return {
[AttributeKey.WA_CHAT_ID]: this.chatId,
};
}
@CacheAsync()
async AvatarUrl(): Promise<string | null> {
return null;
}
async PublicContactCreate(): Promise<Contact> {
const name = this.locale
.key(TKey.WHATSAPP_CONTACT_BROADCAST_SUFFIX)
.render();
return {
identifier: this.chatId,
name: `${name} (${this.chatId})`,
custom_attributes: await this.Attributes(),
};
}
}
/**
* Factory function to get the appropriate ContactInfo implementation
* based on the chat ID type
@@ -235,8 +265,13 @@ export function WhatsAppContactInfo(
return new ChannelContactInfo(session, chatId, locale);
} else if (isJidStatusBroadcast(chatId)) {
return new StatusContactInfo(session, chatId, locale);
} else if (isJidBroadcast(chatId)) {
return new BroadcastContactInfo(session, chatId, locale);
} else if (isLidUser(chatId)) {
return new LidContactInfo(session, chatId, locale);
} else if (isPnUser(chatId)) {
return new JidContactInfo(session, chatId, locale);
} else {
throw new UnknownJIDFormat(chatId);
}
return new JidContactInfo(session, chatId, locale);
}
+114 -92
View File
@@ -1,14 +1,20 @@
import ChatwootClient from '@figuro/chatwoot-sdk';
import * as lodash from 'lodash';
import { AxiosLogging } from '@waha/apps/app_sdk/AxiosLogging';
import { ILogger } from '@waha/apps/app_sdk/ILogger';
import { ContactAPI } from '@waha/apps/chatwoot/client/ContactAPI';
import { ContactService } from '@waha/apps/chatwoot/client/ContactService';
import { ContactConversationService } from '@waha/apps/chatwoot/client/ContactConversationService';
import { ConversationAPI } from '@waha/apps/chatwoot/client/ConversationAPI';
import { CustomAttributesAPI } from '@waha/apps/chatwoot/client/CustomAttributesAPI';
import { ConversationService } from '@waha/apps/chatwoot/client/ConversationService';
import { CustomAttributesService } from '@waha/apps/chatwoot/client/CustomAttributesService';
import { ChatWootInboxAPI } from '@waha/apps/chatwoot/client/interfaces';
import { ChatWootAppConfig } from '@waha/apps/chatwoot/dto/config.dto';
import {
ChatWootAppConfig,
ChatWootConfig,
DEFAULT_LOCALE,
LinkPreview,
} from '@waha/apps/chatwoot/dto/config.dto';
import { ChatWootErrorReporter } from '@waha/apps/chatwoot/error/ChatWootErrorReporter';
import { Locale } from '@waha/apps/chatwoot/locale';
import { Locale } from '@waha/apps/chatwoot/i18n/locale';
import { WAHASelf } from '@waha/apps/chatwoot/session/WAHASelf';
import {
ChatwootMessageRepository,
@@ -18,21 +24,18 @@ import {
} from '@waha/apps/chatwoot/storage';
import { Job } from 'bullmq';
import { Knex } from 'knex';
import { i18n } from '@waha/apps/chatwoot/i18n';
import { CacheSync } from '@waha/utils/Cache';
import {
ConversationSelector,
ConversationSort,
} from '@waha/apps/chatwoot/services/ConversationSelector';
/**
* Dependency Injection Container for ChatWoot
* Manages the creation and caching of various clients and repositories
*/
export class DIContainer {
private accountAPI: ChatwootClient;
private inboxAPI: ChatWootInboxAPI;
private contactAPI: ContactAPI;
private conversationAPI: ConversationAPI;
private contactConversationService: ContactConversationService;
private locale: Locale;
private messageMappingService: MessageMappingService;
private wahaSelf: WAHASelf;
/**
* Creates a new DIContainer with the given configuration
*/
@@ -61,121 +64,124 @@ export class DIContainer {
return this.knex;
}
@CacheSync()
public Locale(): Locale {
if (!this.locale) {
this.locale = new Locale(this.config.locale);
}
return this.locale;
let locale = i18n.locale(this.config.locale || DEFAULT_LOCALE);
locale = locale.override(this.ChatWootConfig().templates);
return locale;
}
/**
* Gets the AccountAPI client
* @returns ChatwootClient instance
*/
@CacheSync()
public AccountAPI(): ChatwootClient {
if (!this.accountAPI) {
this.accountAPI = new ChatwootClient({
config: {
basePath: this.config.url,
with_credentials: true,
credentials: 'include',
token: this.config.accountToken,
},
});
}
return this.accountAPI;
return new ChatwootClient({
config: {
basePath: this.config.url,
with_credentials: true,
credentials: 'include',
token: this.config.accountToken,
},
});
}
/**
* Gets the InboxAPI client
* @returns ChatWootInboxAPI instance
*/
@CacheSync()
public InboxAPI(): ChatWootInboxAPI {
if (!this.inboxAPI) {
const chatwootClientAPI = new ChatwootClient({
config: {
basePath: this.config.url,
with_credentials: true,
credentials: 'include',
token: this.config.inboxIdentifier,
},
});
this.inboxAPI = chatwootClientAPI.client as ChatWootInboxAPI;
}
return this.inboxAPI;
const chatwootClientAPI = new ChatwootClient({
config: {
basePath: this.config.url,
with_credentials: true,
credentials: 'include',
token: this.config.inboxIdentifier,
},
});
return chatwootClientAPI.client as ChatWootInboxAPI;
}
/**
* Gets the ContactAPI
* @returns ContactAPI instance
* Gets the ContactService
* @returns ContactService instance
*/
private ContactAPI(): ContactAPI {
if (!this.contactAPI) {
this.contactAPI = new ContactAPI(
this.config,
this.AccountAPI(),
this.InboxAPI(),
this.logger,
);
}
return this.contactAPI;
@CacheSync()
private ContactService(): ContactService {
return new ContactService(
this.config,
this.AccountAPI(),
this.InboxAPI(),
this.logger,
);
}
@CacheSync()
public ConversationSelector() {
const config = this.ChatWootConfig();
return new ConversationSelector({
sort: config.conversations.sort,
status: config.conversations.status,
inboxId: this.config.inboxId,
});
}
/**
* Gets the ConversationAPI
* @returns ConversationAPI instance
* Gets the ConversationService
* @returns ConversationService instance
*/
private ConversationAPI(): ConversationAPI {
if (!this.conversationAPI) {
this.conversationAPI = new ConversationAPI(
this.config,
this.InboxAPI(),
this.logger,
);
}
return this.conversationAPI;
@CacheSync()
private ConversationService(): ConversationService {
return new ConversationService(
this.config,
this.AccountAPI(),
this.InboxAPI(),
this.ConversationSelector(),
this.logger,
);
}
/**
* Gets the ContactConversationService
* @returns ContactConversationService instance
*/
@CacheSync()
public ContactConversationService(): ContactConversationService {
if (!this.contactConversationService) {
this.contactConversationService = new ContactConversationService(
this.config,
this.ContactAPI(),
this.ConversationAPI(),
this.AccountAPI(),
this.logger,
this.Locale(),
);
}
return this.contactConversationService;
return new ContactConversationService(
this.config,
this.ContactService(),
this.ConversationService(),
this.AccountAPI(),
this.logger,
this.Locale(),
);
}
@CacheSync()
private ChatwootMessageRepository(): ChatwootMessageRepository {
return new ChatwootMessageRepository(this.Knex(), this.AppPk());
}
@CacheSync()
private WhatsAppMessageRepository(): WhatsAppMessageRepository {
return new WhatsAppMessageRepository(this.Knex(), this.AppPk());
}
@CacheSync()
private MessageMappingRepository(): MessageMappingRepository {
return new MessageMappingRepository(this.Knex(), this.AppPk());
}
@CacheSync()
public MessageMappingService(): MessageMappingService {
if (!this.messageMappingService) {
this.messageMappingService = new MessageMappingService(
this.Knex(),
this.WhatsAppMessageRepository(),
this.ChatwootMessageRepository(),
this.MessageMappingRepository(),
);
}
return this.messageMappingService;
return new MessageMappingService(
this.Knex(),
this.WhatsAppMessageRepository(),
this.ChatwootMessageRepository(),
this.MessageMappingRepository(),
);
}
public ChatWootErrorReporter(job: Job): ChatWootErrorReporter {
@@ -186,16 +192,32 @@ export class DIContainer {
* Gets the WAHASelf instance
* @returns WAHASelf instance
*/
@CacheSync()
public WAHASelf(): WAHASelf {
if (!this.wahaSelf) {
this.wahaSelf = new WAHASelf();
const logging = new AxiosLogging(this.Logger());
logging.applyTo(this.wahaSelf.client);
}
return this.wahaSelf;
const self = new WAHASelf();
const logging = new AxiosLogging(this.Logger());
logging.applyTo(self.client);
return self;
}
public CustomAttributesAPI() {
return new CustomAttributesAPI(this.config, this.AccountAPI());
@CacheSync()
public CustomAttributesService() {
return new CustomAttributesService(this.config, this.AccountAPI());
}
@CacheSync()
public ChatWootConfig(): ChatWootConfig {
const defaults: ChatWootConfig = {
templates: {},
linkPreview: LinkPreview.OFF,
commands: {
server: true,
},
conversations: {
sort: ConversationSort.created_newest,
status: null,
},
};
return lodash.defaults({}, this.config, defaults);
}
}
+62 -3
View File
@@ -1,6 +1,48 @@
import { ChatWootAPIConfig } from '@waha/apps/chatwoot/client/interfaces';
import { DEFAULT_LOCALE, LOCALES } from '@waha/apps/chatwoot/locale';
import { IsIn, IsNumber, IsString } from 'class-validator';
import {
IsBoolean,
IsEnum,
IsNumber,
IsOptional,
IsString,
ValidateNested,
} from 'class-validator';
import { Type } from 'class-transformer';
import { IsDynamicObject } from '@waha/nestjs/validation/IsDynamicObject';
import {
ConversationSelectorConfig,
ConversationSort,
} from '@waha/apps/chatwoot/services/ConversationSelector';
import { ConversationStatus } from '@waha/apps/chatwoot/client/types';
export const DEFAULT_LOCALE = 'en-US';
export class ChatWootCommandsConfig {
@IsBoolean()
server: boolean = true;
}
export enum LinkPreview {
OFF = 'OFF',
LQ = 'LG',
HQ = 'HG',
}
export class ChatWootConversationsConfig {
@IsEnum(ConversationSort)
sort: ConversationSort;
@IsOptional()
@IsEnum(ConversationStatus, { each: true })
status: Array<ConversationStatus> | null;
}
export interface ChatWootConfig {
templates: Record<string, string>;
linkPreview: LinkPreview;
commands: ChatWootCommandsConfig;
conversations: ChatWootConversationsConfig;
}
export class ChatWootAppConfig implements ChatWootAPIConfig {
@IsString()
@@ -18,7 +60,24 @@ export class ChatWootAppConfig implements ChatWootAPIConfig {
@IsString()
inboxIdentifier: string;
@IsEnum(LinkPreview)
@IsOptional()
linkPreview?: LinkPreview = LinkPreview.OFF;
@IsString()
@IsIn(LOCALES)
locale: string = DEFAULT_LOCALE;
@IsOptional()
@IsDynamicObject()
templates?: Record<string, string>;
@IsOptional()
@ValidateNested()
@Type(() => ChatWootCommandsConfig)
commands?: ChatWootCommandsConfig;
@IsOptional()
@ValidateNested()
@Type(() => ChatWootConversationsConfig)
conversations?: ChatWootConversationsConfig;
}
@@ -1,14 +1,20 @@
import type { conversation_message_create } from '@figuro/chatwoot-sdk/dist/models/conversation_message_create';
import { ILogger } from '@waha/apps/app_sdk/ILogger';
import { NextAttemptDelayInWholeSeconds } from '@waha/apps/app_sdk/JobUtils';
import {
JobLink,
NextAttemptDelayInWholeSeconds,
} from '@waha/apps/app_sdk/JobUtils';
import { Conversation } from '@waha/apps/chatwoot/client/Conversation';
import { MessageType } from '@waha/apps/chatwoot/client/types';
import { ErrorRenderer } from '@waha/apps/chatwoot/error/ErrorRenderer';
import { Locale, TKey } from '@waha/apps/chatwoot/locale';
import { Locale } from '@waha/apps/chatwoot/i18n/locale';
import { Job } from 'bullmq';
import { TKey } from '@waha/apps/chatwoot/i18n/templates';
const renderer: ErrorRenderer = new ErrorRenderer();
export class ChatWootErrorReporter {
private errorRenderer: ErrorRenderer = new ErrorRenderer();
private errorRenderer: ErrorRenderer = renderer;
constructor(
private logger: ILogger,
@@ -23,13 +29,22 @@ export class ChatWootErrorReporter {
error: any,
replyTo?: number,
) {
const errorText = this.errorRenderer.renderError(error);
const errorText = this.errorRenderer.text(error);
this.logger.error(errorText);
const errorUrl = `http://localhost:3000/jobs/queue/${encodeURIComponent(
this.job.queueName,
)}/${this.job.id}`;
const template = this.l.key(TKey.JOB_ERROR_REPORT);
try {
const data = this.errorRenderer.data(error);
this.logger.error(JSON.stringify(data, null, 2));
} catch (err) {
this.logger.error(`Error occurred while login details for error: ${err}`);
}
const template = this.l.key(TKey.JOB_REPORT_ERROR);
const nextDelay = NextAttemptDelayInWholeSeconds(this.job);
if (nextDelay) {
// TODO: Add a way to enable it back in config
// https://github.com/devlikeapro/waha/issues/1395
// There's retries more left - ignore it for now
return;
}
const attempts = {
current: this.job.attemptsMade + 1,
max: this.job.opts?.attempts || 1,
@@ -38,10 +53,7 @@ export class ChatWootErrorReporter {
const content = template.render({
header: header,
error: nextDelay != null ? null : errorText,
details: {
text: `${this.job.queueName} => ${this.job.id}`,
url: errorUrl,
},
details: JobLink(this.job),
attempts: attempts,
});
const request: conversation_message_create = {
@@ -71,20 +83,18 @@ export class ChatWootErrorReporter {
type: MessageType,
replyTo?: number,
): Promise<void> {
const jobUrl = `http://localhost:3000/jobs/queue/${encodeURIComponent(
this.job.queueName,
)}/${this.job.id}`;
const template = this.l.key(TKey.JOB_SUCCEEDED_REPORT);
// TODO: Add a way to enable it back in config
// https://github.com/devlikeapro/waha/issues/1395
return null;
const template = this.l.key(TKey.JOB_REPORT_SUCCEEDED);
const attempts = {
current: this.job.attemptsMade + 1,
max: this.job.opts?.attempts || 1,
};
const content = template.render({
details: {
text: `${this.job.queueName} => ${this.job.id}`,
url: jobUrl,
},
details: JobLink(this.job),
attempts: attempts,
});
+106 -41
View File
@@ -1,45 +1,28 @@
import { ApiError } from '@figuro/chatwoot-sdk/dist/core/ApiError';
import { AxiosError } from 'axios';
import { ApiError as ChatWootAPIError } from '@figuro/chatwoot-sdk/dist/core/ApiError';
import type { AxiosError } from 'axios';
/**
* Class responsible for rendering error information based on error type
*/
export class ErrorRenderer {
interface IErrorRender<T> {
/**
* Renders error information based on error type
* @param error The error to render
* @returns Formatted error text
* Get an error text to show to the user
*/
public renderError(error: any): string {
// Handle Axios errors
if (error.isAxiosError) {
return this.renderAxiosError(error as AxiosError);
}
// Handle Chatwoot SDK ApiError
if (error instanceof ApiError) {
return this.renderApiError(error);
}
// For all other errors
return this.renderGenericError(error);
}
text(error: T): string;
/**
* Renders Axios error information
* @param error The Axios error to render
* @returns Formatted error text
* Structured error data for logging/inspection
*/
private renderAxiosError(error: AxiosError): string {
let errorText = `Axios Error: ${error.message}`;
data(error: T): Record<string, any>;
}
class AxiosErrorRenderer implements IErrorRender<AxiosError> {
text(error: AxiosError): string {
let errorText = `API Error: ${error.message}`;
if (!error.response?.data) {
return errorText;
}
// Add response data if it exists and is JSON
try {
const data = error.response.data as any;
let json: undefined;
let json: any;
if (Buffer.isBuffer(data)) {
json = JSON.parse(data.toString());
} else if (typeof data === 'object') {
@@ -55,16 +38,35 @@ export class ErrorRenderer {
return errorText;
}
/**
* Renders Chatwoot SDK ApiError information
* @param error The ApiError to render
* @returns Formatted error text
*/
private renderApiError(error: ApiError): string {
let errorText = `API Error: ${error.message}`;
data(error: AxiosError): Record<string, any> {
const { config, response, request } = error as any;
return {
class: 'APIError',
request: {
method: request?.method || config?.method,
url: request?.path || config?.url,
params: config?.params,
body: request?.body || config?.data,
},
response: {
status: response?.status,
statusText: response?.statusText,
headers: response?.headers,
body: response?.data,
},
message: error.message,
code: (error as any)?.code,
stack: error.stack,
};
}
}
class ChatWootAPIErrorRenderer implements IErrorRender<ChatWootAPIError> {
text(error: ChatWootAPIError): string {
let errorText = `ChatWoot API Error: ${error.message}`;
errorText += `\nStatus: ${error.status}`;
// Add body if it exists
if (error.body) {
try {
const body =
@@ -78,12 +80,75 @@ export class ErrorRenderer {
return errorText;
}
data(error: ChatWootAPIError): Record<string, any> {
return {
class: 'ChatWootAPIError',
request: {
method: error.request?.method,
url: error.request?.url,
query: error.request?.query,
path: error.request?.path,
body: error.request?.body,
},
response: {
status: error.status,
statusText: error.statusText,
body: error.body,
},
message: error.message,
stack: error.stack,
};
}
}
class GenericErrorRenderer implements IErrorRender<any> {
text(error: any): string {
return error?.toString?.() ?? String(error);
}
data(error: any): Record<string, any> {
return {
class: error?.name ?? error?.constructor?.name ?? typeof error,
message: error?.message ?? String(error),
stack: error.stack,
};
}
}
/**
* Class responsible for rendering error information based on error type
*/
export class ErrorRenderer implements IErrorRender<any> {
private readonly axiosRenderer = new AxiosErrorRenderer();
private readonly chatwootRenderer = new ChatWootAPIErrorRenderer();
private readonly genericRenderer = new GenericErrorRenderer();
/**
* Renders generic error information
* Renders error information based on error type
* @param error The error to render
* @returns Formatted error text
*/
private renderGenericError(error: any): string {
return error.toString();
public text(error: any): string {
if (error?.isAxiosError) {
return this.axiosRenderer.text(error as AxiosError);
}
if (error instanceof ChatWootAPIError) {
return this.chatwootRenderer.text(error);
}
return this.genericRenderer.text(error);
}
public data(error: any): Record<string, any> {
if (error?.isAxiosError) {
return this.axiosRenderer.data(error as AxiosError);
}
if (error instanceof ChatWootAPIError) {
return this.chatwootRenderer.data(error);
}
return this.genericRenderer.data(error);
}
}
+6
View File
@@ -9,3 +9,9 @@ export class PhoneNumberNotFoundInWhatsAppError extends Error {
super(`Phone number not found in WhatsApp: ${phone}`);
}
}
export class UnknownJIDFormat extends Error {
constructor(public jid: string) {
super(`WhatsApp Chat ID is not recognized: ${jid}`);
}
}
-181
View File
@@ -1,181 +0,0 @@
# Arabic translations for ChatWoot integration
LOCALE_NAME: '🇦🇪 العربية (Arabic)'
APP_INBOX_CONTACT_NAME: 'تكامل واتساب (WAHA)'
JOB_ERROR_REPORT: |
⚠️ **{{{header}}}**
{{#attempts.nextDelay}}
🔄 إعادة المحاولة في {{attempts.nextDelay}} ثانية...
{{/attempts.nextDelay}}
{{^attempts.nextDelay}}
❌ لم تبق محاولات أخرى. يرجى المحاولة لاحقًا.
{{/attempts.nextDelay}}
المحاولة: {{{attempts.current}}} / {{{attempts.max}}}
التفاصيل: [{{{details.text}}}]({{{details.url}}})
{{#error}}
```
{{{error}}}
```
{{/error}}
JOB_SUCCEEDED_REPORT: |
✅ **نجاح**
المحاولة: {{{attempts.current}}} / {{{attempts.max}}}
التفاصيل: [{{{details.text}}}]({{{details.url}}})
WHATSAPP_ERROR_SENDING_MESSAGE: تعذر إرسال رسالة على واتساب.
WHATSAPP_ERROR_RECEIVING_MESSAGE: تعذر استلام رسالة من واتساب
WHATSAPP_ERROR_REMOVING_MESSAGE: تعذر إزالة رسالة من واتساب
APP_SCHEDULED_JOB_ERROR: فشل تنفيذ المهمة المجدولة
MESSAGE_FROM_WHATSAPP: |
{{{text}}}
📱 *أرسلت من واتساب*
MESSAGE_FROM_API: |
{{{text}}}
📱 *أرسلت عبر API*
MESSAGE_REMOVED_IN_WHATSAPP: |
❌ *تم حذف هذه الرسالة في واتساب*
MESSAGE_EDITED_IN_WHATSAPP: |
{{{text}}}
✏️ *تم تعديلها في واتساب*
WHATSAPP_GROUP_MESSAGE: |
{{{text}}}
👥 *{{{participant}}}*
WHATSAPP_REACTION_ADDED: |
*تفاعل* {{{emoji}}}
WHATSAPP_REACTION_REMOVED: |
*تمت إزالة التفاعل*
WHATSAPP_CONTACT_GROUP_SUFFIX: 'مجموعة'
WHATSAPP_CONTACT_CHANNEL_SUFFIX: 'قناة'
# Aka Stories
WHATSAPP_CONTACT_STATUS_NAME: 'الحالة'
APP_CONNECTED_MESSAGE: |
🔗 **متصل: جلسة واتساب إلى صندوق وارد ChatWoot**
✅ تم توصيل جلسة واتساب **{{{name}}}** بنجاح بصندوق وارد ChatWoot هذا.
صندوق الوارد هذا مرتبط الآن بـ **حساب واتساب** الخاص بك. يمكنك إرسال واستقبال الرسائل مباشرة من هنا — تمامًا مثل استخدام واتساب، ولكن داخل ChatWoot.
يمكنك أيضًا إدارة جلسة واتساب المرتبطة بصندوق الوارد هذا. لعرض الإجراءات المتاحة، أرسل **help** إلى هذه المحادثة.
📋 الإجراءات الشائعة التي يمكنك القيام بها:
- التحقق مما إذا كانت جلسة واتساب نشطة
- إعادة تشغيل أو إيقاف جلسة واتساب
- تسجيل الخروج وإعادة الاتصال عن طريق مسح رمز QR جديد
إذا كان كل شيء يعمل، ستتمكن من الدردشة بحرية. إذا رأيت تحذيرات، فقد تحتاج إلى إعادة توصيل حساب واتساب الخاص بك.
لنبدأ! 🚀
APP_DISCONNECTED_MESSAGE: |
🔌 **غير متصل: جلسة واتساب من صندوق وارد ChatWoot**
❌ تم قطع اتصال جلسة واتساب **{{{name}}}** من صندوق وارد ChatWoot هذا.
لن تتمكن بعد الآن من إرسال أو استقبال رسائل من واتساب.
لإعادة الاتصال، يرجى الاتصال بالمسؤول الخاص بك.
APP_UPDATED_MESSAGE: |
🔄 **تم التحديث: إعدادات جلسة واتساب**
✅ تم تحديث إعدادات جلسة واتساب **{{{name}}}**.
APP_COMMANDS_LIST: |
يمكنك إدارة **تكامل واتساب** عن طريق إرسال أحد الأوامر التالية:
🖥️ الجلسات:
1. **status** - الحصول على حالة الجلسة الحالية.
2. **restart** - إعادة تشغيل الجلسة - سيحاول هذا إعادة توصيل حساب واتساب الخاص بك.
3. **start** - بدء الجلسة.
4. **stop** - إيقاف الجلسة.
5. **logout** - تسجيل الخروج من الجلسة، حتى تتمكن من مسح رمز QR وإصلاح الجلسة.
6. **qr** - الحصول على رمز QR للجلسة.
7. **screenshot** - الحصول على لقطة شاشة للجلسة (متاح فقط في محرك **WEBJS**).
8. **help** - الأوامر المتاحة.
⚙️ الخادم:
- **server status** - الحصول على معلومات إصدار الخادم وحالته.
- **server reboot** - إعادة تشغيل الخادم بسلاسة.
- **server reboot force** - إعادة تشغيل الخادم بالقوة.
أرسل **أمرًا** (غير حساس لحالة الأحرف) أو **رقم أمر** إلى هذه المحادثة.
APP_HELP_REMINDER: |
أرسل **help** للحصول على قائمة الأوامر المتاحة.
APP_SESSION_STATUS_CHANGE: |
{{{emoji}}} تم تغيير حالة جلسة "{{{session}}}" إلى {{{status}}}
APP_SESSION_CURRENT_STATUS: |
{{{emoji}}} حالة جلسة **{{{session}}}** هي **{{{status}}}**
**حساب واتساب:**
📱 **{{{name}}} ({{{id}}})**
APP_DASHBOARD_LINK: |
قم بزيارة [لوحة تحكم WAHA]({{{url}}}) لمزيد من التفاصيل.
APP_SESSION_STATUS_WORKING: |
✅ صندوق وارد ChatWoot هذا مرتبط بـ **حساب واتساب** وجاهز لإرسال واستقبال الرسائل!
📱 **{{{name}}} ({{{id}}})**
APP_SESSION_STATUS_ERROR: |
⚠️ **صندوق وارد ChatWoot هذا مرتبط بحساب واتساب ولكنه غير جاهز لإرسال واستقبال الرسائل!**
يرجى التحقق من حالة الجلسة ومحاولة إعادة تشغيلها أو تسجيل الخروج ومسح رمز QR مرة أخرى.
APP_SESSION_SCAN_QR_CODE: |
⚠️ **امسح رمز QR على هاتفك لربط حساب واتساب بصندوق وارد ChatWoot هذا!**
APP_SERVER_VERSION_AND_STATUS: |
**الإصدار:**
```
{{{version}}}
```
**الحالة:**
```
{{{status}}}
```
APP_SERVER_REBOOT: |
يتم إعادة تشغيل الخادم بسلاسة...
APP_SERVER_REBOOT_FORCE: |
يتم إعادة تشغيل الخادم بالقوة...
APP_LOGOUT_SUCCESS: |
✅ تم إلغاء تفويض الحساب بنجاح!
APP_NEW_VERSION_AVAILABLE: |
🔄 **إصدار جديد متاح!**
الإصدار الحالي: **{{{currentVersion}}}**
الإصدار الجديد: **{{{newVersion}}}**
👉 تحقق مما هو جديد في [**🆕 سجل التغييرات**]({{{changelogUrl}}})
APP_CORE_VERSION_USED: |
❤️ **ادعم WAHA!**
أنت تستخدم إصدار **WAHA Core** المجاني. إذا وجدت هذا المشروع مفيدًا، فيرجى التفكير في دعم تطويره.
دعمك هو الطريقة الوحيدة التي يمكننا من خلالها الاستمرار في العمل على التحديثات والإصلاحات والميزات الجديدة.
[**🎁 ادعمنا**]({{{supportUrl}}})
-184
View File
@@ -1,184 +0,0 @@
# German translations for ChatWoot integration
LOCALE_NAME: '🇩🇪 Deutsch (German)'
APP_INBOX_CONTACT_NAME: 'WhatsApp Integration (WAHA)'
JOB_ERROR_REPORT: |
⚠️ **{{{header}}}**
{{#attempts.nextDelay}}
🔄 Wiederholung in {{attempts.nextDelay}} Sekunden...
{{/attempts.nextDelay}}
{{^attempts.nextDelay}}
❌ Keine weiteren Versuche übrig. Bitte versuchen Sie es später erneut.
{{/attempts.nextDelay}}
Versuch: {{{attempts.current}}} / {{{attempts.max}}}
Details: [{{{details.text}}}]({{{details.url}}})
{{#error}}
```
{{{error}}}
```
{{/error}}
JOB_SUCCEEDED_REPORT: |
✅ **Erfolgreich**
Versuch: {{{attempts.current}}} / {{{attempts.max}}}
Details: [{{{details.text}}}]({{{details.url}}})
WHATSAPP_ERROR_SENDING_MESSAGE:
Nachricht konnte nicht über WhatsApp gesendet werden.
WHATSAPP_ERROR_RECEIVING_MESSAGE:
Nachricht konnte nicht von WhatsApp empfangen werden
WHATSAPP_ERROR_REMOVING_MESSAGE:
Nachricht konnte nicht von WhatsApp entfernt werden
APP_SCHEDULED_JOB_ERROR: Geplante Aufgabe konnte nicht ausgeführt werden
MESSAGE_FROM_WHATSAPP: |
{{{text}}}
📱 *Von WhatsApp gesendet*
MESSAGE_FROM_API: |
{{{text}}}
📱 *Über API gesendet*
MESSAGE_REMOVED_IN_WHATSAPP: |
❌ *Diese Nachricht wurde in WhatsApp gelöscht*
MESSAGE_EDITED_IN_WHATSAPP: |
{{{text}}}
✏️ *In WhatsApp bearbeitet*
WHATSAPP_GROUP_MESSAGE: |
{{{text}}}
👥 *{{{participant}}}*
WHATSAPP_REACTION_ADDED: |
*Reagiert mit* {{{emoji}}}
WHATSAPP_REACTION_REMOVED: |
*Reaktion entfernt*
WHATSAPP_CONTACT_GROUP_SUFFIX: 'Gruppe'
WHATSAPP_CONTACT_CHANNEL_SUFFIX: 'Kanal'
# Aka Stories
WHATSAPP_CONTACT_STATUS_NAME: 'Status'
APP_CONNECTED_MESSAGE: |
🔗 **VERBUNDEN: WhatsApp-Sitzung mit ChatWoot Inbox**
✅ WhatsApp-Sitzung **{{{name}}}** wurde erfolgreich mit dieser ChatWoot Inbox verbunden.
Diese Inbox ist jetzt mit Ihrem **WhatsApp-Konto** verknüpft. Sie können Nachrichten direkt von hier senden und empfangen — genau wie bei WhatsApp, aber innerhalb von ChatWoot.
Sie können auch die mit dieser Inbox verknüpfte WhatsApp-Sitzung verwalten. Um verfügbare Aktionen anzuzeigen, senden Sie **help** an diesen Chat.
📋 Häufige Aktionen, die Sie durchführen können:
- Überprüfen, ob die WhatsApp-Sitzung aktiv ist
- Die WhatsApp-Sitzung neu starten oder stoppen
- Abmelden und durch Scannen eines neuen QR-Codes wieder verbinden
Wenn alles funktioniert, können Sie frei chatten. Wenn Sie Warnungen sehen, müssen Sie möglicherweise Ihr WhatsApp-Konto neu verbinden.
Lassen Sie uns beginnen! 🚀
APP_DISCONNECTED_MESSAGE: |
🔌 **GETRENNT: WhatsApp-Sitzung von ChatWoot Inbox**
❌ WhatsApp-Sitzung **{{{name}}}** wurde von dieser ChatWoot Inbox getrennt.
Sie können keine Nachrichten mehr von WhatsApp senden oder empfangen.
Um eine erneute Verbindung herzustellen, wenden Sie sich bitte an Ihren Administrator.
APP_UPDATED_MESSAGE: |
🔄 **AKTUALISIERT: WhatsApp-Sitzungseinstellungen**
✅ Einstellungen für WhatsApp-Sitzung **{{{name}}}** wurden aktualisiert.
APP_COMMANDS_LIST: |
Sie können die **WhatsApp Integration** verwalten, indem Sie einen der folgenden Befehle senden:
🖥️ Sitzungen:
1. **status** - den aktuellen Sitzungsstatus abrufen.
2. **restart** - die Sitzung neu starten - dies versucht, Ihr WhatsApp-Konto neu zu verbinden.
3. **start** - die Sitzung starten.
4. **stop** - die Sitzung stoppen.
5. **logout** - von der Sitzung abmelden, damit Sie den QR-Code scannen und die Sitzung reparieren können.
6. **qr** - den QR-Code für die Sitzung abrufen.
7. **screenshot** - einen Screenshot der Sitzung erhalten (nur in der **WEBJS**-Engine verfügbar).
8. **help** - verfügbare Befehle.
⚙️ Server:
- **server status** - Serverversion und Statusinformationen abrufen.
- **server reboot** - Server ordnungsgemäß neu starten.
- **server reboot force** - Server erzwungen neu starten.
Senden Sie einen **Befehl** (Groß-/Kleinschreibung wird nicht beachtet) oder eine **Befehlsnummer** an diese Konversation.
APP_HELP_REMINDER: |
Senden Sie **help**, um die Liste der verfügbaren Befehle zu erhalten.
APP_SESSION_STATUS_CHANGE: |
{{{emoji}}} Sitzung "{{{session}}}" hat den Status zu {{{status}}} gewechselt
APP_SESSION_CURRENT_STATUS: |
{{{emoji}}} **{{{session}}}** Sitzungsstatus ist **{{{status}}}**
**WhatsApp-Konto:**
📱 **{{{name}}} ({{{id}}})**
APP_DASHBOARD_LINK: |
Besuchen Sie das [WAHA Dashboard]({{{url}}}) für weitere Details.
APP_SESSION_STATUS_WORKING: |
✅ Diese ChatWoot Inbox ist mit dem **WhatsApp-Konto** verknüpft und bereit zum Senden und Empfangen von Nachrichten!
📱 **{{{name}}} ({{{id}}})**
APP_SESSION_STATUS_ERROR: |
⚠️ **Diese ChatWoot Inbox ist mit dem WhatsApp-Konto verknüpft, aber NICHT bereit zum Senden und Empfangen von Nachrichten!**
Bitte überprüfen Sie den Sitzungsstatus und versuchen Sie, sie neu zu starten oder melden Sie sich ab und scannen Sie den QR-Code erneut.
APP_SESSION_SCAN_QR_CODE: |
⚠️ **Scannen Sie den QR-Code auf Ihrem Telefon, um das WhatsApp-Konto mit dieser ChatWoot Inbox zu verknüpfen!**
APP_SERVER_VERSION_AND_STATUS: |
**Version:**
```
{{{version}}}
```
**Status:**
```
{{{status}}}
```
APP_SERVER_REBOOT: |
Server wird ordnungsgemäß neu gestartet...
APP_SERVER_REBOOT_FORCE: |
Server wird erzwungen neu gestartet...
APP_LOGOUT_SUCCESS: |
✅ Konto erfolgreich abgemeldet!
APP_NEW_VERSION_AVAILABLE: |
🔄 **Neue Version verfügbar!**
Aktuelle Version: **{{{currentVersion}}}**
Neue Version: **{{{newVersion}}}**
👉 Sehen Sie, was neu ist im [**🆕 Änderungsprotokoll**]({{{changelogUrl}}})
APP_CORE_VERSION_USED: |
❤️ **Unterstützen Sie WAHA!**
Sie verwenden die kostenlose **WAHA Core** Version. Wenn Sie dieses Projekt nützlich finden, erwägen Sie bitte, seine Entwicklung zu unterstützen.
Ihre Unterstützung ist der einzige Weg, wie wir weiterhin an Updates, Fehlerbehebungen und neuen Funktionen arbeiten können.
[**🎁 Unterstützen Sie uns**]({{{supportUrl}}})
-182
View File
@@ -1,182 +0,0 @@
# English translations for ChatWoot integration
LOCALE_NAME: '🇺🇸 English (English)'
APP_INBOX_CONTACT_NAME: 'WhatsApp Integration (WAHA)'
APP_INBOX_CONTACT_AVATAR_URL: 'https://raw.githubusercontent.com/devlikeapro/waha/core/logo.png'
JOB_ERROR_REPORT: |
⚠️ **{{{header}}}**
{{#attempts.nextDelay}}
🔄 Retrying in {{attempts.nextDelay}} seconds...
{{/attempts.nextDelay}}
{{^attempts.nextDelay}}
❌ No more retries left. Please try again later.
{{/attempts.nextDelay}}
Attempt: {{{attempts.current}}} / {{{attempts.max}}}
Details: [{{{details.text}}}]({{{details.url}}})
{{#error}}
```
{{{error}}}
```
{{/error}}
JOB_SUCCEEDED_REPORT: |
✅ **Succeeded**
Attempt: {{{attempts.current}}} / {{{attempts.max}}}
Details: [{{{details.text}}}]({{{details.url}}})
WHATSAPP_ERROR_SENDING_MESSAGE: Couldn't send a message on WhatsApp.
WHATSAPP_ERROR_RECEIVING_MESSAGE: Couldn't receive a message from WhatsApp
WHATSAPP_ERROR_REMOVING_MESSAGE: Couldn't remove a message from WhatsApp
APP_SCHEDULED_JOB_ERROR: Scheduled job failed to execute
MESSAGE_FROM_WHATSAPP: |
{{{text}}}
📱 *Sent from WhatsApp*
MESSAGE_FROM_API: |
{{{text}}}
📱 *Sent via API*
MESSAGE_REMOVED_IN_WHATSAPP: |
❌ *This message was deleted in WhatsApp*
MESSAGE_EDITED_IN_WHATSAPP: |
{{{text}}}
✏️ *Edited in WhatsApp*
WHATSAPP_GROUP_MESSAGE: |
{{{text}}}
👥 *{{{participant}}}*
WHATSAPP_REACTION_ADDED: |
*Reacted* {{{emoji}}}
WHATSAPP_REACTION_REMOVED: |
*Reaction removed*
WHATSAPP_CONTACT_GROUP_SUFFIX: 'Group'
WHATSAPP_CONTACT_CHANNEL_SUFFIX: 'Channel'
# Aka Stories
WHATSAPP_CONTACT_STATUS_NAME: 'Status'
APP_CONNECTED_MESSAGE: |
🔗 **CONNECTED: WhatsApp Session to ChatWoot Inbox**
✅ WhatsApp session **{{{name}}}** has been successfully connected to this ChatWoot Inbox.
This inbox is now linked to your **WhatsApp Account**. You can send and receive messages directly from here — just like using WhatsApp, but inside ChatWoot.
You can also manage the WhatsApp session linked to this inbox. To view available actions, send **help** to this chat.
📋 Common actions you can do:
- Check if the WhatsApp session is active
- Restart or Stop the WhatsApp session
- Log out and reconnect by scanning a new QR code
If everything is working, you'll be able to chat freely. If you see warnings, you might need to reconnect your WhatsApp account.
Let's get started! 🚀
APP_DISCONNECTED_MESSAGE: |
🔌 **DISCONNECTED: WhatsApp Session from ChatWoot Inbox**
❌ WhatsApp session **{{{name}}}** has been disconnected from this ChatWoot Inbox.
You will no longer be able to send or receive messages from WhatsApp.
To reconnect, please contact your administrator.
APP_UPDATED_MESSAGE: |
🔄 **UPDATED: WhatsApp Session Settings**
✅ Settings for WhatsApp session **{{{name}}}** have been updated.
APP_COMMANDS_LIST: |
You can manage **WhatsApp Integration** by sending one of the following commands:
🖥️ Sessions:
1. **status** - get the current session status.
2. **restart** - restart the session - this will try to reconnect your WhatsApp account.
3. **start** - start the session.
4. **stop** - stop the session.
5. **logout** - logout from the session, so you can scan QR code and repair the session.
6. **qr** - get the QR code for the session.
7. **screenshot** - get a screenshot of the session (only available in **WEBJS** engine).
8. **help** - available commands.
⚙️ Server:
- **server status** - get the server version and status information.
- **server reboot** - reboot the server gracefully.
- **server reboot force** - force reboot the server.
Send a **command** (case insensitive) or a **command number** to this conversation.
APP_HELP_REMINDER: |
Send **help** to get the list of available commands.
APP_SESSION_STATUS_CHANGE: |
{{{emoji}}} "{{{session}}}" session switched to {{{status}}} status
APP_SESSION_CURRENT_STATUS: |
{{{emoji}}} **{{{session}}}** session status is in **{{{status}}}**
**WhatsApp Account:**
📱 **{{{name}}} ({{{id}}})**
APP_DASHBOARD_LINK: |
Visit [WAHA Dashboard]({{{url}}}) for more details.
APP_SESSION_STATUS_WORKING: |
✅ This ChatWoot Inbox is linked to **WhatsApp Account** and ready to send and receive messages!
📱 **{{{name}}} ({{{id}}})**
APP_SESSION_STATUS_ERROR: |
⚠️ **This ChatWoot Inbox is linked to WhatsApp Account but NOT ready to send and receive messages!**
Please check the session status and try restarting it or logout and scan the QR code again.
APP_SESSION_SCAN_QR_CODE: |
⚠️ **Scan the QR code on your phone to link WhatsApp Account with this ChatWoot Inbox!**
APP_SERVER_VERSION_AND_STATUS: |
**Version:**
```
{{{version}}}
```
**Status:**
```
{{{status}}}
```
APP_SERVER_REBOOT: |
Server is rebooting gracefully...
APP_SERVER_REBOOT_FORCE: |
Server is force rebooting...
APP_LOGOUT_SUCCESS: |
✅ Account unauthorized successfully!
APP_NEW_VERSION_AVAILABLE: |
🔄 **New Version Available!**
Current version: **{{{currentVersion}}}**
New version: **{{{newVersion}}}**
👉 Check out what's new in the [**🆕 Changelog**]({{{changelogUrl}}})
APP_CORE_VERSION_USED: |
❤️ **Support WAHA!**
You're using the free **WAHA Core** version. If you find this project useful, please consider supporting its development.
Your support is the only way we can continue working on updates, fixes, and new features.
[**🎁 Support Us**]({{{supportUrl}}})
-181
View File
@@ -1,181 +0,0 @@
# Spanish translations for ChatWoot integration
LOCALE_NAME: '🇪🇸 Español (Spanish)'
APP_INBOX_CONTACT_NAME: 'Integración de WhatsApp (WAHA)'
JOB_ERROR_REPORT: |
⚠️ **{{{header}}}**
{{#attempts.nextDelay}}
🔄 Reintentando en {{attempts.nextDelay}} segundos...
{{/attempts.nextDelay}}
{{^attempts.nextDelay}}
❌ No quedan más intentos. Por favor, inténtelo de nuevo más tarde.
{{/attempts.nextDelay}}
Intento: {{{attempts.current}}} / {{{attempts.max}}}
Detalles: [{{{details.text}}}]({{{details.url}}})
{{#error}}
```
{{{error}}}
```
{{/error}}
JOB_SUCCEEDED_REPORT: |
✅ **Éxito**
Intento: {{{attempts.current}}} / {{{attempts.max}}}
Detalles: [{{{details.text}}}]({{{details.url}}})
WHATSAPP_ERROR_SENDING_MESSAGE: No se pudo enviar un mensaje en WhatsApp.
WHATSAPP_ERROR_RECEIVING_MESSAGE: No se pudo recibir un mensaje de WhatsApp
WHATSAPP_ERROR_REMOVING_MESSAGE: No se pudo eliminar un mensaje de WhatsApp
APP_SCHEDULED_JOB_ERROR: La tarea programada no se pudo ejecutar
MESSAGE_FROM_WHATSAPP: |
{{{text}}}
📱 *Enviado desde WhatsApp*
MESSAGE_FROM_API: |
{{{text}}}
📱 *Enviado a través de API*
MESSAGE_REMOVED_IN_WHATSAPP: |
❌ *Este mensaje fue eliminado en WhatsApp*
MESSAGE_EDITED_IN_WHATSAPP: |
{{{text}}}
✏️ *Editado en WhatsApp*
WHATSAPP_GROUP_MESSAGE: |
{{{text}}}
👥 *{{{participant}}}*
WHATSAPP_REACTION_ADDED: |
*Reaccionó con* {{{emoji}}}
WHATSAPP_REACTION_REMOVED: |
*Reacción eliminada*
WHATSAPP_CONTACT_GROUP_SUFFIX: 'Grupo'
WHATSAPP_CONTACT_CHANNEL_SUFFIX: 'Canal'
# Aka Stories
WHATSAPP_CONTACT_STATUS_NAME: 'Estado'
APP_CONNECTED_MESSAGE: |
🔗 **CONECTADO: Sesión de WhatsApp a Bandeja de Entrada de ChatWoot**
✅ La sesión de WhatsApp **{{{name}}}** ha sido conectada exitosamente a esta Bandeja de Entrada de ChatWoot.
Esta bandeja de entrada ahora está vinculada a tu **Cuenta de WhatsApp**. Puedes enviar y recibir mensajes directamente desde aquí — justo como usar WhatsApp, pero dentro de ChatWoot.
También puedes gestionar la sesión de WhatsApp vinculada a esta bandeja de entrada. Para ver las acciones disponibles, envía **help** a este chat.
📋 Acciones comunes que puedes realizar:
- Comprobar si la sesión de WhatsApp está activa
- Reiniciar o Detener la sesión de WhatsApp
- Cerrar sesión y reconectar escaneando un nuevo código QR
Si todo funciona, podrás chatear libremente. Si ves advertencias, es posible que necesites reconectar tu cuenta de WhatsApp.
¡Comencemos! 🚀
APP_DISCONNECTED_MESSAGE: |
🔌 **DESCONECTADO: Sesión de WhatsApp de Bandeja de Entrada de ChatWoot**
❌ La sesión de WhatsApp **{{{name}}}** ha sido desconectada de esta Bandeja de Entrada de ChatWoot.
Ya no podrás enviar o recibir mensajes desde WhatsApp.
Para reconectar, por favor contacta a tu administrador.
APP_UPDATED_MESSAGE: |
🔄 **ACTUALIZADO: Configuración de Sesión de WhatsApp**
✅ La configuración para la sesión de WhatsApp **{{{name}}}** ha sido actualizada.
APP_COMMANDS_LIST: |
Puedes gestionar la **Integración de WhatsApp** enviando uno de los siguientes comandos:
🖥️ Sesiones:
1. **status** - obtener el estado actual de la sesión.
2. **restart** - reiniciar la sesión - esto intentará reconectar tu cuenta de WhatsApp.
3. **start** - iniciar la sesión.
4. **stop** - detener la sesión.
5. **logout** - cerrar sesión, para que puedas escanear el código QR y reparar la sesión.
6. **qr** - obtener el código QR para la sesión.
7. **screenshot** - obtener una captura de pantalla de la sesión (solo disponible en el motor **WEBJS**).
8. **help** - comandos disponibles.
⚙️ Servidor:
- **server status** - obtener información de versión y estado del servidor.
- **server reboot** - reiniciar el servidor de forma segura.
- **server reboot force** - forzar el reinicio del servidor.
Envía un **comando** (no sensible a mayúsculas/minúsculas) o un **número de comando** a esta conversación.
APP_HELP_REMINDER: |
Envía **help** para obtener la lista de comandos disponibles.
APP_SESSION_STATUS_CHANGE: |
{{{emoji}}} La sesión "{{{session}}}" cambió al estado {{{status}}}
APP_SESSION_CURRENT_STATUS: |
{{{emoji}}} El estado de la sesión **{{{session}}}** es **{{{status}}}**
**Cuenta de WhatsApp:**
📱 **{{{name}}} ({{{id}}})**
APP_DASHBOARD_LINK: |
Visita el [Panel de WAHA]({{{url}}}) para más detalles.
APP_SESSION_STATUS_WORKING: |
✅ ¡Esta Bandeja de Entrada de ChatWoot está vinculada a la **Cuenta de WhatsApp** y lista para enviar y recibir mensajes!
📱 **{{{name}}} ({{{id}}})**
APP_SESSION_STATUS_ERROR: |
⚠️ **¡Esta Bandeja de Entrada de ChatWoot está vinculada a la Cuenta de WhatsApp pero NO está lista para enviar y recibir mensajes!**
Por favor, comprueba el estado de la sesión e intenta reiniciarla o cierra sesión y escanea el código QR de nuevo.
APP_SESSION_SCAN_QR_CODE: |
⚠️ **¡Escanea el código QR en tu teléfono para vincular la Cuenta de WhatsApp con esta Bandeja de Entrada de ChatWoot!**
APP_SERVER_VERSION_AND_STATUS: |
**Versión:**
```
{{{version}}}
```
**Estado:**
```
{{{status}}}
```
APP_SERVER_REBOOT: |
El servidor se está reiniciando de forma segura...
APP_SERVER_REBOOT_FORCE: |
El servidor se está reiniciando forzadamente...
APP_LOGOUT_SUCCESS: |
✅ ¡Cuenta desautorizada con éxito!
APP_NEW_VERSION_AVAILABLE: |
🔄 **¡Nueva Versión Disponible!**
Versión actual: **{{{currentVersion}}}**
Nueva versión: **{{{newVersion}}}**
👉 Mira qué hay de nuevo en el [**🆕 Registro de Cambios**]({{{changelogUrl}}})
APP_CORE_VERSION_USED: |
❤️ **¡Apoya a WAHA!**
Estás usando la versión gratuita **WAHA Core**. Si encuentras útil este proyecto, por favor considera apoyar su desarrollo.
Tu apoyo es la única forma en que podemos seguir trabajando en actualizaciones, correcciones y nuevas funciones.
[**🎁 Apóyanos**]({{{supportUrl}}})
-181
View File
@@ -1,181 +0,0 @@
# French translations for ChatWoot integration
LOCALE_NAME: '🇫🇷 Français (French)'
APP_INBOX_CONTACT_NAME: 'Intégration WhatsApp (WAHA)'
JOB_ERROR_REPORT: |
⚠️ **{{{header}}}**
{{#attempts.nextDelay}}
🔄 Nouvelle tentative dans {{attempts.nextDelay}} secondes...
{{/attempts.nextDelay}}
{{^attempts.nextDelay}}
❌ Plus de tentatives restantes. Veuillez réessayer plus tard.
{{/attempts.nextDelay}}
Tentative : {{{attempts.current}}} / {{{attempts.max}}}
Détails : [{{{details.text}}}]({{{details.url}}})
{{#error}}
```
{{{error}}}
```
{{/error}}
JOB_SUCCEEDED_REPORT: |
✅ **Réussi**
Tentative : {{{attempts.current}}} / {{{attempts.max}}}
Détails : [{{{details.text}}}]({{{details.url}}})
WHATSAPP_ERROR_SENDING_MESSAGE: Impossible d'envoyer un message sur WhatsApp.
WHATSAPP_ERROR_RECEIVING_MESSAGE: Impossible de recevoir un message de WhatsApp
WHATSAPP_ERROR_REMOVING_MESSAGE: Impossible de supprimer un message de WhatsApp
APP_SCHEDULED_JOB_ERROR: La tâche planifiée n'a pas pu être exécutée
MESSAGE_FROM_WHATSAPP: |
{{{text}}}
📱 *Envoyé depuis WhatsApp*
MESSAGE_FROM_API: |
{{{text}}}
📱 *Envoyé via API*
MESSAGE_REMOVED_IN_WHATSAPP: |
❌ *Ce message a été supprimé dans WhatsApp*
MESSAGE_EDITED_IN_WHATSAPP: |
{{{text}}}
✏️ *Modifié dans WhatsApp*
WHATSAPP_GROUP_MESSAGE: |
{{{text}}}
👥 *{{{participant}}}*
WHATSAPP_REACTION_ADDED: |
*A réagi avec* {{{emoji}}}
WHATSAPP_REACTION_REMOVED: |
*Réaction supprimée*
WHATSAPP_CONTACT_GROUP_SUFFIX: 'Groupe'
WHATSAPP_CONTACT_CHANNEL_SUFFIX: 'Canal'
# Aka Stories
WHATSAPP_CONTACT_STATUS_NAME: 'Statut'
APP_CONNECTED_MESSAGE: |
🔗 **CONNECTÉ : Session WhatsApp à la Boîte de Réception ChatWoot**
✅ La session WhatsApp **{{{name}}}** a été connectée avec succès à cette Boîte de Réception ChatWoot.
Cette boîte de réception est maintenant liée à votre **Compte WhatsApp**. Vous pouvez envoyer et recevoir des messages directement d'ici — tout comme utiliser WhatsApp, mais à l'intérieur de ChatWoot.
Vous pouvez également gérer la session WhatsApp liée à cette boîte de réception. Pour voir les actions disponibles, envoyez **help** à ce chat.
📋 Actions courantes que vous pouvez effectuer :
- Vérifier si la session WhatsApp est active
- Redémarrer ou Arrêter la session WhatsApp
- Se déconnecter et se reconnecter en scannant un nouveau code QR
Si tout fonctionne, vous pourrez discuter librement. Si vous voyez des avertissements, vous devrez peut-être reconnecter votre compte WhatsApp.
Commençons ! 🚀
APP_DISCONNECTED_MESSAGE: |
🔌 **DÉCONNECTÉ : Session WhatsApp de la Boîte de Réception ChatWoot**
❌ La session WhatsApp **{{{name}}}** a été déconnectée de cette Boîte de Réception ChatWoot.
Vous ne pourrez plus envoyer ou recevoir de messages depuis WhatsApp.
Pour vous reconnecter, veuillez contacter votre administrateur.
APP_UPDATED_MESSAGE: |
🔄 **MIS À JOUR : Paramètres de Session WhatsApp**
✅ Les paramètres de la session WhatsApp **{{{name}}}** ont été mis à jour.
APP_COMMANDS_LIST: |
Vous pouvez gérer l'**Intégration WhatsApp** en envoyant l'une des commandes suivantes :
🖥️ Sessions :
1. **status** - obtenir l'état actuel de la session.
2. **restart** - redémarrer la session - cela tentera de reconnecter votre compte WhatsApp.
3. **start** - démarrer la session.
4. **stop** - arrêter la session.
5. **logout** - se déconnecter de la session, pour pouvoir scanner le code QR et réparer la session.
6. **qr** - obtenir le code QR pour la session.
7. **screenshot** - obtenir une capture d'écran de la session (disponible uniquement dans le moteur **WEBJS**).
8. **help** - commandes disponibles.
⚙️ Serveur :
- **server status** - obtenir les informations de version et d'état du serveur.
- **server reboot** - redémarrer le serveur en douceur.
- **server reboot force** - forcer le redémarrage du serveur.
Envoyez une **commande** (insensible à la casse) ou un **numéro de commande** à cette conversation.
APP_HELP_REMINDER: |
Envoyez **help** pour obtenir la liste des commandes disponibles.
APP_SESSION_STATUS_CHANGE: |
{{{emoji}}} La session "{{{session}}}" est passée au statut {{{status}}}
APP_SESSION_CURRENT_STATUS: |
{{{emoji}}} Le statut de la session **{{{session}}}** est **{{{status}}}**
**Compte WhatsApp :**
📱 **{{{name}}} ({{{id}}})**
APP_DASHBOARD_LINK: |
Visitez le [Tableau de bord WAHA]({{{url}}}) pour plus de détails.
APP_SESSION_STATUS_WORKING: |
✅ Cette Boîte de Réception ChatWoot est liée au **Compte WhatsApp** et prête à envoyer et recevoir des messages !
📱 **{{{name}}} ({{{id}}})**
APP_SESSION_STATUS_ERROR: |
⚠️ **Cette Boîte de Réception ChatWoot est liée au Compte WhatsApp mais N'EST PAS prête à envoyer et recevoir des messages !**
Veuillez vérifier l'état de la session et essayer de la redémarrer ou déconnectez-vous et scannez à nouveau le code QR.
APP_SESSION_SCAN_QR_CODE: |
⚠️ **Scannez le code QR sur votre téléphone pour lier le Compte WhatsApp à cette Boîte de Réception ChatWoot !**
APP_SERVER_VERSION_AND_STATUS: |
**Version :**
```
{{{version}}}
```
**Statut :**
```
{{{status}}}
```
APP_SERVER_REBOOT: |
Le serveur redémarre en douceur...
APP_SERVER_REBOOT_FORCE: |
Le serveur redémarre de force...
APP_LOGOUT_SUCCESS: |
✅ Compte déconnecté avec succès !
APP_NEW_VERSION_AVAILABLE: |
🔄 **Nouvelle Version Disponible !**
Version actuelle : **{{{currentVersion}}}**
Nouvelle version : **{{{newVersion}}}**
👉 Découvrez les nouveautés dans le [**🆕 Journal des modifications**]({{{changelogUrl}}})
APP_CORE_VERSION_USED: |
❤️ **Soutenez WAHA !**
Vous utilisez la version gratuite **WAHA Core**. Si vous trouvez ce projet utile, veuillez envisager de soutenir son développement.
Votre soutien est le seul moyen pour nous de continuer à travailler sur les mises à jour, les corrections et les nouvelles fonctionnalités.
[**🎁 Soutenez-nous**]({{{supportUrl}}})
-181
View File
@@ -1,181 +0,0 @@
# Hebrew translations for ChatWoot integration
LOCALE_NAME: '🇮🇱 עברית (Hebrew)'
APP_INBOX_CONTACT_NAME: 'אינטגרציית וואטסאפ (WAHA)'
JOB_ERROR_REPORT: |
⚠️ **{{{header}}}**
{{#attempts.nextDelay}}
🔄 מנסה שוב בעוד {{attempts.nextDelay}} שניות...
{{/attempts.nextDelay}}
{{^attempts.nextDelay}}
❌ לא נותרו ניסיונות נוספים. אנא נסה שוב מאוחר יותר.
{{/attempts.nextDelay}}
ניסיון: {{{attempts.current}}} / {{{attempts.max}}}
פרטים: [{{{details.text}}}]({{{details.url}}})
{{#error}}
```
{{{error}}}
```
{{/error}}
JOB_SUCCEEDED_REPORT: |
✅ **הצליח**
ניסיון: {{{attempts.current}}} / {{{attempts.max}}}
פרטים: [{{{details.text}}}]({{{details.url}}})
WHATSAPP_ERROR_SENDING_MESSAGE: לא ניתן לשלוח הודעה בוואטסאפ.
WHATSAPP_ERROR_RECEIVING_MESSAGE: לא ניתן לקבל הודעה מוואטסאפ
WHATSAPP_ERROR_REMOVING_MESSAGE: לא ניתן להסיר הודעה מוואטסאפ
APP_SCHEDULED_JOB_ERROR: המשימה המתוזמנת נכשלה בביצוע
MESSAGE_FROM_WHATSAPP: |
{{{text}}}
📱 *נשלח מוואטסאפ*
MESSAGE_FROM_API: |
{{{text}}}
📱 *נשלח דרך API*
MESSAGE_REMOVED_IN_WHATSAPP: |
❌ *הודעה זו נמחקה בוואטסאפ*
MESSAGE_EDITED_IN_WHATSAPP: |
{{{text}}}
✏️ *נערך בוואטסאפ*
WHATSAPP_GROUP_MESSAGE: |
{{{text}}}
👥 *{{{participant}}}*
WHATSAPP_REACTION_ADDED: |
*הגיב* {{{emoji}}}
WHATSAPP_REACTION_REMOVED: |
*התגובה הוסרה*
WHATSAPP_CONTACT_GROUP_SUFFIX: 'קבוצה'
WHATSAPP_CONTACT_CHANNEL_SUFFIX: 'ערוץ'
# Aka Stories
WHATSAPP_CONTACT_STATUS_NAME: 'סטטוס'
APP_CONNECTED_MESSAGE: |
🔗 **מחובר: הפעלת וואטסאפ לתיבת הדואר הנכנס של ChatWoot**
✅ הפעלת וואטסאפ **{{{name}}}** חוברה בהצלחה לתיבת הדואר הנכנס של ChatWoot.
תיבת דואר נכנס זו מקושרת כעת ל**חשבון הוואטסאפ** שלך. אתה יכול לשלוח ולקבל הודעות ישירות מכאן — בדיוק כמו שימוש בוואטסאפ, אבל בתוך ChatWoot.
אתה יכול גם לנהל את הפעלת הוואטסאפ המקושרת לתיבת דואר נכנס זו. כדי לראות פעולות זמינות, שלח **help** לצ'אט זה.
📋 פעולות נפוצות שאתה יכול לעשות:
- בדוק אם הפעלת הוואטסאפ פעילה
- הפעל מחדש או עצור את הפעלת הוואטסאפ
- התנתק והתחבר מחדש על ידי סריקת קוד QR חדש
אם הכל עובד, תוכל לשוחח בחופשיות. אם אתה רואה אזהרות, ייתכן שתצטרך לחבר מחדש את חשבון הוואטסאפ שלך.
בוא נתחיל! 🚀
APP_DISCONNECTED_MESSAGE: |
🔌 **מנותק: הפעלת וואטסאפ מתיבת הדואר הנכנס של ChatWoot**
❌ הפעלת וואטסאפ **{{{name}}}** נותקה מתיבת הדואר הנכנס של ChatWoot.
לא תוכל יותר לשלוח או לקבל הודעות מוואטסאפ.
כדי להתחבר מחדש, אנא פנה למנהל המערכת שלך.
APP_UPDATED_MESSAGE: |
🔄 **עודכן: הגדרות הפעלת וואטסאפ**
✅ ההגדרות עבור הפעלת וואטסאפ **{{{name}}}** עודכנו.
APP_COMMANDS_LIST: |
אתה יכול לנהל **אינטגרציית וואטסאפ** על ידי שליחת אחת מהפקודות הבאות:
🖥️ הפעלות:
1. **status** - קבל את סטטוס ההפעלה הנוכחי.
2. **restart** - הפעל מחדש את ההפעלה - זה ינסה לחבר מחדש את חשבון הוואטסאפ שלך.
3. **start** - התחל את ההפעלה.
4. **stop** - עצור את ההפעלה.
5. **logout** - התנתק מההפעלה, כך שתוכל לסרוק קוד QR ולתקן את ההפעלה.
6. **qr** - קבל את קוד ה-QR עבור ההפעלה.
7. **screenshot** - קבל צילום מסך של ההפעלה (זמין רק במנוע **WEBJS**).
8. **help** - פקודות זמינות.
⚙️ שרת:
- **server status** - קבל מידע על גרסת השרת והסטטוס.
- **server reboot** - הפעל מחדש את השרת בצורה חלקה.
- **server reboot force** - אלץ הפעלה מחדש של השרת.
שלח **פקודה** (לא תלוי רישיות) או **מספר פקודה** לשיחה זו.
APP_HELP_REMINDER: |
שלח **help** כדי לקבל את רשימת הפקודות הזמינות.
APP_SESSION_STATUS_CHANGE: |
{{{emoji}}} הפעלה "{{{session}}}" עברה לסטטוס {{{status}}}
APP_SESSION_CURRENT_STATUS: |
{{{emoji}}} סטטוס ההפעלה **{{{session}}}** הוא **{{{status}}}**
**חשבון וואטסאפ:**
📱 **{{{name}}} ({{{id}}})**
APP_DASHBOARD_LINK: |
בקר ב[לוח הבקרה של WAHA]({{{url}}}) לפרטים נוספים.
APP_SESSION_STATUS_WORKING: |
✅ תיבת הדואר הנכנס של ChatWoot מקושרת ל**חשבון וואטסאפ** ומוכנה לשלוח ולקבל הודעות!
📱 **{{{name}}} ({{{id}}})**
APP_SESSION_STATUS_ERROR: |
⚠️ **תיבת הדואר הנכנס של ChatWoot מקושרת לחשבון וואטסאפ אך אינה מוכנה לשלוח ולקבל הודעות!**
אנא בדוק את סטטוס ההפעלה ונסה להפעיל אותה מחדש או התנתק וסרוק את קוד ה-QR שוב.
APP_SESSION_SCAN_QR_CODE: |
⚠️ **סרוק את קוד ה-QR בטלפון שלך כדי לקשר את חשבון הוואטסאפ לתיבת הדואר הנכנס של ChatWoot!**
APP_SERVER_VERSION_AND_STATUS: |
**גרסה:**
```
{{{version}}}
```
**סטטוס:**
```
{{{status}}}
```
APP_SERVER_REBOOT: |
השרת מופעל מחדש בצורה חלקה...
APP_SERVER_REBOOT_FORCE: |
השרת מופעל מחדש בכוח...
APP_LOGOUT_SUCCESS: |
✅ החשבון בוטל בהצלחה!
APP_NEW_VERSION_AVAILABLE: |
🔄 **גרסה חדשה זמינה!**
גרסה נוכחית: **{{{currentVersion}}}**
גרסה חדשה: **{{{newVersion}}}**
👉 בדוק מה חדש ב[**🆕 יומן שינויים**]({{{changelogUrl}}})
APP_CORE_VERSION_USED: |
❤️ **תמוך ב-WAHA!**
אתה משתמש בגרסת **WAHA Core** החינמית. אם אתה מוצא פרויקט זה שימושי, אנא שקול לתמוך בפיתוח שלו.
התמיכה שלך היא הדרך היחידה שבה אנחנו יכולים להמשיך לעבוד על עדכונים, תיקונים ותכונות חדשות.
[**🎁 תמוך בנו**]({{{supportUrl}}})
+43
View File
@@ -0,0 +1,43 @@
import { Locale } from '@waha/apps/chatwoot/i18n/locale';
import { TKey } from '@waha/apps/chatwoot/i18n/templates';
export class I18N {
private readonly locales: Record<string, Locale>;
constructor() {
this.locales = {};
}
available(): Array<{ name: string; locale: string }> {
const result = [];
for (const [key, template] of Object.entries(this.locales)) {
result.push({ locale: key, name: template.key(TKey.LOCALE_NAME).r() });
}
return result;
}
load(locales: Record<string, Record<string, string>>) {
for (const [locale, strings] of Object.entries(locales)) {
this.add(locale, strings);
}
}
add(locale: string, strings: Record<string, string>) {
if (!this.locales[locale]) {
this.locales[locale] = new Locale(strings);
} else {
this.locales[locale] = this.locales[locale].override(strings);
}
}
locale(locale: string) {
if (!this.has(locale)) {
throw new Error(`Locale ${locale} not found`);
}
return this.locales[locale];
}
has(locale: string) {
return !!this.locales[locale];
}
}
-181
View File
@@ -1,181 +0,0 @@
# Indonesian translations for ChatWoot integration
LOCALE_NAME: '🇮🇩 Bahasa Indonesia (Indonesian)'
APP_INBOX_CONTACT_NAME: 'Integrasi WhatsApp (WAHA)'
JOB_ERROR_REPORT: |
⚠️ **{{{header}}}**
{{#attempts.nextDelay}}
🔄 Mencoba lagi dalam {{attempts.nextDelay}} detik...
{{/attempts.nextDelay}}
{{^attempts.nextDelay}}
❌ Tidak ada percobaan lagi. Silakan coba lagi nanti.
{{/attempts.nextDelay}}
Percobaan: {{{attempts.current}}} / {{{attempts.max}}}
Detail: [{{{details.text}}}]({{{details.url}}})
{{#error}}
```
{{{error}}}
```
{{/error}}
JOB_SUCCEEDED_REPORT: |
✅ **Berhasil**
Percobaan: {{{attempts.current}}} / {{{attempts.max}}}
Detail: [{{{details.text}}}]({{{details.url}}})
WHATSAPP_ERROR_SENDING_MESSAGE: Tidak dapat mengirim pesan di WhatsApp.
WHATSAPP_ERROR_RECEIVING_MESSAGE: Tidak dapat menerima pesan dari WhatsApp
WHATSAPP_ERROR_REMOVING_MESSAGE: Tidak dapat menghapus pesan dari WhatsApp
APP_SCHEDULED_JOB_ERROR: Tugas terjadwal gagal dijalankan
MESSAGE_FROM_WHATSAPP: |
{{{text}}}
📱 *Dikirim dari WhatsApp*
MESSAGE_FROM_API: |
{{{text}}}
📱 *Dikirim melalui API*
MESSAGE_REMOVED_IN_WHATSAPP: |
❌ *Pesan ini telah dihapus di WhatsApp*
MESSAGE_EDITED_IN_WHATSAPP: |
{{{text}}}
✏️ *Diedit di WhatsApp*
WHATSAPP_GROUP_MESSAGE: |
{{{text}}}
👥 *{{{participant}}}*
WHATSAPP_REACTION_ADDED: |
*Bereaksi* {{{emoji}}}
WHATSAPP_REACTION_REMOVED: |
*Reaksi dihapus*
WHATSAPP_CONTACT_GROUP_SUFFIX: 'Grup'
WHATSAPP_CONTACT_CHANNEL_SUFFIX: 'Saluran'
# Aka Stories
WHATSAPP_CONTACT_STATUS_NAME: 'Status'
APP_CONNECTED_MESSAGE: |
🔗 **TERHUBUNG: Sesi WhatsApp ke Kotak Masuk ChatWoot**
✅ Sesi WhatsApp **{{{name}}}** telah berhasil terhubung ke Kotak Masuk ChatWoot ini.
Kotak masuk ini sekarang terhubung ke **Akun WhatsApp** Anda. Anda dapat mengirim dan menerima pesan langsung dari sini — seperti menggunakan WhatsApp, tetapi di dalam ChatWoot.
Anda juga dapat mengelola sesi WhatsApp yang terhubung ke kotak masuk ini. Untuk melihat tindakan yang tersedia, kirim **help** ke obrolan ini.
📋 Tindakan umum yang dapat Anda lakukan:
- Periksa apakah sesi WhatsApp aktif
- Mulai ulang atau Hentikan sesi WhatsApp
- Keluar dan hubungkan kembali dengan memindai kode QR baru
Jika semuanya berfungsi, Anda akan dapat mengobrol dengan bebas. Jika Anda melihat peringatan, Anda mungkin perlu menghubungkan kembali akun WhatsApp Anda.
Mari kita mulai! 🚀
APP_DISCONNECTED_MESSAGE: |
🔌 **TERPUTUS: Sesi WhatsApp dari Kotak Masuk ChatWoot**
❌ Sesi WhatsApp **{{{name}}}** telah terputus dari Kotak Masuk ChatWoot ini.
Anda tidak akan dapat lagi mengirim atau menerima pesan dari WhatsApp.
Untuk menghubungkan kembali, silakan hubungi administrator Anda.
APP_UPDATED_MESSAGE: |
🔄 **DIPERBARUI: Pengaturan Sesi WhatsApp**
✅ Pengaturan untuk sesi WhatsApp **{{{name}}}** telah diperbarui.
APP_COMMANDS_LIST: |
Anda dapat mengelola **Integrasi WhatsApp** dengan mengirimkan salah satu perintah berikut:
🖥️ Sesi:
1. **status** - dapatkan status sesi saat ini.
2. **restart** - mulai ulang sesi - ini akan mencoba menghubungkan kembali akun WhatsApp Anda.
3. **start** - mulai sesi.
4. **stop** - hentikan sesi.
5. **logout** - keluar dari sesi, sehingga Anda dapat memindai kode QR dan memperbaiki sesi.
6. **qr** - dapatkan kode QR untuk sesi.
7. **screenshot** - dapatkan tangkapan layar sesi (hanya tersedia di mesin **WEBJS**).
8. **help** - perintah yang tersedia.
⚙️ Server:
- **server status** - dapatkan informasi versi dan status server.
- **server reboot** - mulai ulang server dengan anggun.
- **server reboot force** - paksa mulai ulang server.
Kirim **perintah** (tidak peka huruf besar/kecil) atau **nomor perintah** ke percakapan ini.
APP_HELP_REMINDER: |
Kirim **help** untuk mendapatkan daftar perintah yang tersedia.
APP_SESSION_STATUS_CHANGE: |
{{{emoji}}} Sesi "{{{session}}}" beralih ke status {{{status}}}
APP_SESSION_CURRENT_STATUS: |
{{{emoji}}} Status sesi **{{{session}}}** adalah **{{{status}}}**
**Akun WhatsApp:**
📱 **{{{name}}} ({{{id}}})**
APP_DASHBOARD_LINK: |
Kunjungi [Dasbor WAHA]({{{url}}}) untuk detail lebih lanjut.
APP_SESSION_STATUS_WORKING: |
✅ Kotak Masuk ChatWoot ini terhubung ke **Akun WhatsApp** dan siap untuk mengirim dan menerima pesan!
📱 **{{{name}}} ({{{id}}})**
APP_SESSION_STATUS_ERROR: |
⚠️ **Kotak Masuk ChatWoot ini terhubung ke Akun WhatsApp tetapi TIDAK siap untuk mengirim dan menerima pesan!**
Silakan periksa status sesi dan coba mulai ulang atau keluar dan pindai kode QR lagi.
APP_SESSION_SCAN_QR_CODE: |
⚠️ **Pindai kode QR di ponsel Anda untuk menghubungkan Akun WhatsApp dengan Kotak Masuk ChatWoot ini!**
APP_SERVER_VERSION_AND_STATUS: |
**Versi:**
```
{{{version}}}
```
**Status:**
```
{{{status}}}
```
APP_SERVER_REBOOT: |
Server sedang dimulai ulang dengan anggun...
APP_SERVER_REBOOT_FORCE: |
Server sedang dipaksa untuk dimulai ulang...
APP_LOGOUT_SUCCESS: |
✅ Akun berhasil tidak diotorisasi!
APP_NEW_VERSION_AVAILABLE: |
🔄 **Versi Baru Tersedia!**
Versi saat ini: **{{{currentVersion}}}**
Versi baru: **{{{newVersion}}}**
👉 Lihat apa yang baru di [**🆕 Changelog**]({{{changelogUrl}}})
APP_CORE_VERSION_USED: |
❤️ **Dukung WAHA!**
Anda menggunakan versi **WAHA Core** gratis. Jika Anda merasa proyek ini bermanfaat, harap pertimbangkan untuk mendukung pengembangannya.
Dukungan Anda adalah satu-satunya cara kami dapat terus bekerja pada pembaruan, perbaikan, dan fitur baru.
[**🎁 Dukung Kami**]({{{supportUrl}}})
+19
View File
@@ -0,0 +1,19 @@
import * as path from 'path';
import { YamlLocaleLoader } from '@waha/apps/chatwoot/i18n/loader';
import { I18N } from '@waha/apps/chatwoot/i18n/i18n';
const i18n = new I18N();
const localesDir = path.join(__dirname, 'locales');
// Load both .yml and .yaml files from locales directory (non-recursive)
i18n.load(new YamlLocaleLoader(localesDir, 'yml').load());
i18n.load(new YamlLocaleLoader(localesDir, 'yaml').load());
// Resolve env var (supports '.' and '~'); fallback to default
const additional = process.env.WAHA_APPS_CHATWOOT_LANGUAGES_FOLDER?.trim();
if (additional) {
const resolved = path.resolve(additional);
i18n.load(new YamlLocaleLoader(resolved, 'yml').load());
i18n.load(new YamlLocaleLoader(resolved, 'yaml').load());
}
export { i18n };
+33
View File
@@ -0,0 +1,33 @@
import * as fs from 'fs-extra';
import * as path from 'path';
import * as yaml from 'yaml';
export class YamlLocaleLoader {
constructor(
private readonly dir: string,
private readonly ext: string,
) {}
load(): Record<string, Record<string, string>> {
const result: Record<string, Record<string, string>> = {};
const files = fs.readdirSync(this.dir, { withFileTypes: true });
for (const entry of files) {
if (!entry.isFile()) continue;
const filename = entry.name;
if (!filename.endsWith(`.${this.ext}`)) continue;
const locale = path.basename(filename, `.${this.ext}`);
const filePath = path.join(this.dir, filename);
const content = fs.readFileSync(filePath, 'utf-8');
const parsed = yaml.parse(content) as Record<string, string> | undefined;
if (parsed && typeof parsed === 'object') {
result[locale] = parsed as Record<string, string>;
}
}
return result;
}
}
+91
View File
@@ -0,0 +1,91 @@
import * as Mustache from 'mustache';
import { TemplatePayloads, TKey } from '@waha/apps/chatwoot/i18n/templates';
import Long from 'long';
import { ensureNumber } from '@waha/core/engines/noweb/utils';
export class Locale {
constructor(private readonly strings: Record<string, string>) {}
/**
* Return Base Locale
* For number, datetime, currency formats
*/
get locale(): string {
return this.strings['locale.base'] || 'en';
}
key<K extends TKey>(key: K): Template<K> {
return new Template(this.strings[key] || key);
}
/**
* Overrides the existing strings with the provided strings for the locale.
* Merges the new strings with the current strings.
*/
override(strings: Record<string, string>): Locale {
return new Locale({ ...this.strings, ...strings });
}
FormatCurrency(
currency: string,
value: number | null,
offset: number = 1,
): string | null {
if (value == null) {
return null;
}
if (!currency) {
return null;
}
offset = offset || 1;
try {
const fmt = new Intl.NumberFormat(this.locale, {
style: 'currency',
currency: currency,
});
return fmt.format(value / offset);
} catch (err) {
return `${currency} ${value}`;
}
}
FormatDatetime(date: Date | null): string | null {
if (!date) {
return null;
}
const options: any = this.strings['datetime'] || {};
options.timeZone = options.timeZone || options.timezone || process.env.TZ;
return date.toLocaleString(this.locale, options);
}
FormatTimestamp(timestamp: Long | string | number | null): string | null {
const value = ensureNumber(timestamp);
if (!value) {
return undefined;
}
if (!Number.isFinite(value)) {
return undefined;
}
const milliseconds = value >= 1e12 ? value : value * 1000;
const date = new Date(milliseconds);
if (Number.isNaN(date.getTime())) {
return undefined;
}
return this.FormatDatetime(date);
}
}
export class Template<K extends TKey> {
constructor(private readonly template: string) {}
render(data: TemplatePayloads[K]): string {
const text = Mustache.render(this.template, data);
// Remove /n at the end if any
return text.replace(/\n$/, '');
}
r(data: TemplatePayloads[K]): string {
return this.render(data);
}
}
+382
View File
@@ -0,0 +1,382 @@
# Arabic translations for ChatWoot integration
locale.name: '🇦🇪 العربية (Arabic)'
# رمز اللغة الأساسي - يجب أن يكون محليًا صالحًا وفق BCP 47
# https://en.wikipedia.org/wiki/IETF_language_tag
# تستخدمه WAHA لعرض الأرقام والعملات والتاريخ/الوقت
locale.base: 'ar-AE'
app.inbox.contact.name: 'تكامل واتساب'
app.inbox.contact.avatar.url: 'https://raw.githubusercontent.com/devlikeapro/waha/core/logo.png'
# "options" من date.toLocaleString
# https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat#options
datetime:
locales: ar-AE
day: 2-digit
month: 2-digit
year: numeric
hour: 2-digit
minute: 2-digit
# https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat#timezonename
timeZoneName: shortGeneric
# https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
# timeZone: 'Asia/Dubai'
job.report.error: |-
⚠️ **{{{header}}}**
{{#attempts.nextDelay}}
🔄 إعادة المحاولة في {{attempts.nextDelay}} ثانية...
{{/attempts.nextDelay}}
{{^attempts.nextDelay}}
❌ لم تبق محاولات أخرى. يرجى المحاولة لاحقًا.
{{/attempts.nextDelay}}
المحاولة: {{{attempts.current}}} / {{{attempts.max}}}
التفاصيل: [{{{details.text}}}]({{{details.url}}})
{{#error}}
```
{{{error}}}
```
{{/error}}
job.report.succeeded: |-
✅ **نجاح**
المحاولة: {{{attempts.current}}} / {{{attempts.max}}}
التفاصيل: [{{{details.text}}}]({{{details.url}}})
whatsapp.message.sending.error: تعذر إرسال رسالة على واتساب.
whatsapp.message.receiving.error: تعذر استلام رسالة من واتساب
whatsapp.message.removing.error: تعذر إزالة رسالة من واتساب
job.scheduled.error.header: فشل تنفيذ المهمة المجدولة
message.from.whatsapp: |-
{{{text}}}
📱 *أرسلت من واتساب*
message.from.api: |-
{{{text}}}
📱 *أرسلت عبر API*
message.removed.in.whatsapp: |-
❌ *تم حذف هذه الرسالة في واتساب*
message.edited.in.whatsapp: |-
{{{text}}}
✏️ *تم تعديلها في واتساب*
whatsapp.group.message: |-
👥 *{{{participant}}}*
{{{text}}}
whatsapp.reaction.added: |-
*تفاعل* {{{emoji}}}
whatsapp.reaction.removed: |-
*تمت إزالة التفاعل*
whatsapp.contact.group.suffix: 'مجموعة'
whatsapp.contact.channel.suffix: 'قناة'
whatsapp.contact.broadcast.suffix: 'قائمة البث'
# Aka Stories
whatsapp.contact.status.name: 'الحالة'
app.connected.message: |-
🔗 **متصل: جلسة واتساب إلى صندوق وارد ChatWoot**
✅ تم توصيل جلسة واتساب **{{{name}}}** بنجاح بصندوق وارد ChatWoot هذا.
صندوق الوارد هذا مرتبط الآن بـ **حساب واتساب** الخاص بك. يمكنك إرسال واستقبال الرسائل مباشرة من هنا — تمامًا مثل استخدام واتساب، ولكن داخل ChatWoot.
يمكنك أيضًا إدارة جلسة واتساب المرتبطة بصندوق الوارد هذا. لعرض الإجراءات المتاحة، أرسل **help** إلى هذه المحادثة.
📋 الإجراءات الشائعة التي يمكنك القيام بها:
- التحقق مما إذا كانت جلسة واتساب نشطة
- إعادة تشغيل أو إيقاف جلسة واتساب
- تسجيل الخروج وإعادة الاتصال عن طريق مسح رمز QR جديد
إذا كان كل شيء يعمل، ستتمكن من الدردشة بحرية. إذا رأيت تحذيرات، فقد تحتاج إلى إعادة توصيل حساب واتساب الخاص بك.
لنبدأ! 🚀
app.disconnected.message: |-
🔌 **غير متصل: جلسة واتساب من صندوق وارد ChatWoot**
❌ تم قطع اتصال جلسة واتساب **{{{name}}}** من صندوق وارد ChatWoot هذا.
لن تتمكن بعد الآن من إرسال أو استقبال رسائل من واتساب.
لإعادة الاتصال، يرجى الاتصال بالمسؤول الخاص بك.
app.updated.message: |-
🔄 **تم التحديث: إعدادات جلسة واتساب**
✅ تم تحديث إعدادات جلسة واتساب **{{{name}}}**.
app.commands.list: |-
يمكنك إدارة **تكامل واتساب** عن طريق إرسال أحد الأوامر التالية:
🖥️ **الجلسة**:
1. **status** - الحصول على حالة الجلسة الحالية.
2. **restart** - إعادة تشغيل الجلسة - سيحاول هذا إعادة توصيل حساب واتساب الخاص بك.
3. **start** - بدء الجلسة.
4. **stop** - إيقاف الجلسة.
5. **logout** - تسجيل الخروج من الجلسة، حتى تتمكن من مسح رمز QR وإصلاح الجلسة.
6. **qr** - الحصول على رمز QR للجلسة.
7. **screenshot** - الحصول على لقطة شاشة للجلسة (متاح فقط في محرك **WEBJS**).
8. **help** - الأوامر المتاحة.
{{#commands.server}}
⚙️ الخادم:
- **server status** - الحصول على معلومات إصدار الخادم وحالته.
- **server reboot** - إعادة تشغيل الخادم بسلاسة.
- **server reboot force** - إعادة تشغيل الخادم بالقوة.
{{/commands.server}}
أرسل **أمرًا** (غير حساس لحالة الأحرف) أو **رقم أمر** إلى هذه المحادثة.
app.commands.server.disabled: ⛔ **تم تعطيل أوامر الخادم**.
يرجى التواصل مع مسؤول النظام لديك.
أرسل **help** للحصول على قائمة الأوامر المتاحة.
app.help.reminder: |-
أرسل **help** للحصول على قائمة الأوامر المتاحة.
app.session.status.change: |-
{{{emoji}}} تم تغيير حالة جلسة "{{{session}}}" إلى {{{status}}}
app.session.current.status: |-
{{{emoji}}} حالة جلسة **{{{session}}}** هي **{{{status}}}**
**حساب واتساب:**
📱 **{{{name}}} ({{{id}}})**
app.session.status.working: |-
✅ صندوق وارد ChatWoot هذا مرتبط بـ **حساب واتساب** وجاهز لإرسال واستقبال الرسائل!
📱 **{{{name}}} ({{{id}}})**
app.session.status.error: |-
⚠️ **صندوق وارد ChatWoot هذا مرتبط بحساب واتساب ولكنه غير جاهز لإرسال واستقبال الرسائل!**
يرجى التحقق من حالة الجلسة ومحاولة إعادة تشغيلها أو تسجيل الخروج ومسح رمز QR مرة أخرى.
app.session.scan.qr.code: |-
⚠️ **امسح رمز QR على هاتفك لربط حساب واتساب بصندوق وارد ChatWoot هذا!**
app.server.version.and.status: |-
**الإصدار:**
```
{{{version}}}
```
**الحالة:**
```
{{{status}}}
```
app.server.reboot: |-
يتم إعادة تشغيل الخادم بسلاسة...
app.server.reboot.force: |-
يتم إعادة تشغيل الخادم بالقوة...
app.logout.success: |-
✅ تم إلغاء تفويض الحساب بنجاح!
waha.new.version.available: |-
🔄 **إصدار جديد متاح!**
الإصدار الحالي: **{{{currentVersion}}}**
الإصدار الجديد: **{{{newVersion}}}**
👉 تحقق مما هو جديد في [**🆕 سجل التغييرات**]({{{changelogUrl}}})
waha.core.version.used: |-
❤️ **ادعم WAHA!**
أنت تستخدم إصدار **WAHA Core** المجاني. إذا وجدت هذا المشروع مفيدًا، فيرجى التفكير في دعم تطويره.
دعمك هو الطريقة الوحيدة التي يمكننا من خلالها الاستمرار في العمل على التحديثات والإصلاحات والميزات الجديدة.
[**🎁 ادعمنا**]({{{supportUrl}}})
chatwoot.to.whatsapp.message.text: |-
{{{ content }}}
chatwoot.to.whatsapp.message.media.caption: |-
{{#singleAttachment}}
{{#content}}
{{{ content }}}
{{/content}}
{{/singleAttachment}}
whatsapp.to.chatwoot.message: |-
{{{payload.body}}}
whatsapp.to.chatwoot.message.contacts: |-
👤 **جهات الاتصال المشتركة**
{{#contacts}}
🪪 الاسم: **{{fullName}}**
{{#phoneNumbers}}
- 📞 الهاتف: **{{.}}**
{{/phoneNumbers}}
{{/contacts}}
whatsapp.to.chatwoot.message.location: |-
{{#message}}
{{#locationMessage}}
📍 **الموقع المشترك**
{{#name}}
**{{name}}**
{{/name}}
{{#address}}
📍 **العنوان:** {{address}}
{{/address}}
{{#url}}
🌐 **الموقع الإلكتروني:** {{url}}
{{/url}}
🔗 **عرض على الخريطة:** https://www.google.com/maps?q={{degreesLatitude}},{{degreesLongitude}}
{{#caption}}{{caption}}{{/caption}}
{{/locationMessage}}
{{#liveLocationMessage}}
📍📡 **مشاركة الموقع المباشر**
افتح **WhatsApp** لعرض التحديثات اللحظية.
{{#name}}
**{{name}}**
{{/name}}
{{#address}}
📍 **العنوان:** {{address}}
{{/address}}
{{#url}}
🌐 **الموقع الإلكتروني:** {{url}}
{{/url}}
🔗 **عرض على الخريطة:** https://www.google.com/maps?q={{degreesLatitude}},{{degreesLongitude}}
{{#caption}}{{caption}}{{/caption}}
{{/liveLocationMessage}}
{{/message}}
whatsapp.to.chatwoot.message.poll: |-
{{#poll}}
📊 **استطلاع**
**{{name}}**
{{#options}}
- {{optionName}}
{{/options}}
{{/poll}}
whatsapp.to.chatwoot.message.list: |-
{{#message.listMessage}}
{{#title}}
*{{{title}}}*
{{/title}}
{{#description}}
{{{description}}}
{{/description}}
{{#footerText}}
{{{footerText}}}
{{/footerText}}
{{#buttonText}}
{{{buttonText}}}
{{/buttonText}}
{{#sections}}
{{#title}}
{{{title}}}:
{{/title}}
{{#rows}}
{{#title}}
- {{{title}}}
{{/title}}
{{/rows}}
{{/sections}}
{{/message.listMessage}}
whatsapp.to.chatwoot.message.event: |-
{{#message}}
{{#eventMessage}}
📅 **حدث**
**{{name}}**
{{#description}}
{{description}}
{{/description}}
{{#location.name}}
📍 **الموقع:** {{location.name}}
{{/location.name}}
{{#startTime}}
🕐 **البدء:** {{startTime}}
{{/startTime}}
{{#endTime}}
🕐 **الانتهاء:** {{endTime}}
{{/endTime}}
{{#joinLink}}
🔗 **رابط الانضمام:** [{{joinLink}}]({{joinLink}})
{{/joinLink}}
{{/eventMessage}}
{{/message}}
whatsapp.to.chatwoot.message.pix: |-
💳 **PIX - نسخ ولصق**
{{#pixData}}
{{#merchantName}}
**{{merchantName}}**
{{/merchantName}}
{{#key}}
**مفتاح PIX:** {{key}}
{{/key}}
{{#keyType}}
**النوع:** {{keyType}}
{{/keyType}}
{{#totalAmount}}
**المبلغ:** {{totalAmountFormatted}}
{{/totalAmount}}
{{#referenceId}}
**المرجع:** {{referenceId}}
{{/referenceId}}
💳 **تم إرسال PIX نسخ ولصق**
{{/pixData}}
whatsapp.to.chatwoot.message.unsupported: |-
⚠️ **هذا النوع من الرسائل غير مدعوم في ChatWoot.**
📱 يرجى فتح **WhatsApp** لعرضه.
التفاصيل: [{{{details.text}}}]({{{details.url}}})
whatsapp.to.chatwoot.message.facebook.ad: |-
📢 **إعلان فيسبوك**
**العنوان:** {{{adData.title}}}
**الوصف:**
{{{adData.body}}}
**رابط الإعلان:** [عرض على فيسبوك]({{{adData.sourceUrl}}})
**معرّف الإعلان:** {{{adData.sourceId}}}
---
💬 **رسالة العميل:**
{{{payload.body}}}
@@ -1,8 +1,29 @@
# Bengali translations for ChatWoot integration
LOCALE_NAME: '🇧🇩 বাংলা (Bengali)'
APP_INBOX_CONTACT_NAME: 'হোয়াটসঅ্যাপ ইন্টিগ্রেশন (WAHA)'
locale.name: '🇧🇩 বাংলা (Bengali)'
JOB_ERROR_REPORT: |
# বেস লোকেল কোড - এটি একটি বৈধ BCP 47 লোকেল হতে হবে
# https://en.wikipedia.org/wiki/IETF_language_tag
# WAHA এটি সংখ্যা, মুদ্রা এবং তারিখ/সময়ের ফরম্যাটিংয়ে ব্যবহার করে।
locale.base: 'bn-BD'
app.inbox.contact.name: 'হোয়াটসঅ্যাপ ইন্টিগ্রেশন'
app.inbox.contact.avatar.url: 'https://raw.githubusercontent.com/devlikeapro/waha/core/logo.png'
# date.toLocaleString এর "options"
# https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat#options
datetime:
locales: bn-BD
day: 2-digit
month: 2-digit
year: numeric
hour: 2-digit
minute: 2-digit
# https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat#timezonename
timeZoneName: shortGeneric
# https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
# timeZone: 'Asia/Dhaka'
job.report.error: |-
⚠️ **{{{header}}}**
{{#attempts.nextDelay}}
🔄 {{attempts.nextDelay}} সেকেন্ডের মধ্যে পুনরায় চেষ্টা করা হচ্ছে...
@@ -19,51 +40,52 @@ JOB_ERROR_REPORT: |
```
{{/error}}
JOB_SUCCEEDED_REPORT: |
job.report.succeeded: |-
✅ **সফল হয়েছে**
প্রচেষ্টা: {{{attempts.current}}} / {{{attempts.max}}}
বিবরণ: [{{{details.text}}}]({{{details.url}}})
WHATSAPP_ERROR_SENDING_MESSAGE: হোয়াটসঅ্যাপে বার্তা পাঠাতে পারেনি।
WHATSAPP_ERROR_RECEIVING_MESSAGE: হোয়াটসঅ্যাপ থেকে বার্তা পেতে পারেনি
WHATSAPP_ERROR_REMOVING_MESSAGE: হোয়াটসঅ্যাপ থেকে বার্তা সরাতে পারেনি
APP_SCHEDULED_JOB_ERROR: নির্ধারিত কাজ সম্পাদন করতে ব্যর্থ হয়েছে
whatsapp.message.sending.error: হোয়াটসঅ্যাপে বার্তা পাঠাতে পারেনি।
whatsapp.message.receiving.error: হোয়াটসঅ্যাপ থেকে বার্তা পেতে পারেনি
whatsapp.message.removing.error: হোয়াটসঅ্যাপ থেকে বার্তা সরাতে পারেনি
job.scheduled.error.header: নির্ধারিত কাজ সম্পাদন করতে ব্যর্থ হয়েছে
MESSAGE_FROM_WHATSAPP: |
message.from.whatsapp: |-
{{{text}}}
📱 *হোয়াটসঅ্যাপ থেকে পাঠানো হয়েছে*
MESSAGE_FROM_API: |
message.from.api: |-
{{{text}}}
📱 *API এর মাধ্যমে পাঠানো হয়েছে*
MESSAGE_REMOVED_IN_WHATSAPP: |
message.removed.in.whatsapp: |-
❌ *এই বার্তাটি হোয়াটসঅ্যাপে মুছে ফেলা হয়েছে*
MESSAGE_EDITED_IN_WHATSAPP: |
message.edited.in.whatsapp: |-
{{{text}}}
✏️ *হোয়াটসঅ্যাপে সম্পাদিত*
WHATSAPP_GROUP_MESSAGE: |
{{{text}}}
whatsapp.group.message: |-
👥 *{{{participant}}}*
WHATSAPP_REACTION_ADDED: |
{{{text}}}
whatsapp.reaction.added: |-
*প্রতিক্রিয়া দিয়েছেন* {{{emoji}}}
WHATSAPP_REACTION_REMOVED: |
whatsapp.reaction.removed: |-
*প্রতিক্রিয়া সরানো হয়েছে*
WHATSAPP_CONTACT_GROUP_SUFFIX: 'গ্রুপ'
WHATSAPP_CONTACT_CHANNEL_SUFFIX: 'চ্যানেল'
whatsapp.contact.group.suffix: 'গ্রুপ'
whatsapp.contact.channel.suffix: 'চ্যানেল'
whatsapp.contact.broadcast.suffix: 'প্রচার তালিকা'
# Aka Stories
WHATSAPP_CONTACT_STATUS_NAME: 'স্ট্যাটাস'
whatsapp.contact.status.name: 'স্ট্যাটাস'
APP_CONNECTED_MESSAGE: |
app.connected.message: |-
🔗 **সংযুক্ত: হোয়াটসঅ্যাপ সেশন থেকে ChatWoot ইনবক্স**
✅ হোয়াটসঅ্যাপ সেশন **{{{name}}}** সফলভাবে এই ChatWoot ইনবক্সের সাথে সংযুক্ত করা হয়েছে।
@@ -81,7 +103,7 @@ APP_CONNECTED_MESSAGE: |
চলুন শুরু করা যাক! 🚀
APP_DISCONNECTED_MESSAGE: |
app.disconnected.message: |-
🔌 **সংযোগ বিচ্ছিন্ন: হোয়াটসঅ্যাপ সেশন থেকে ChatWoot ইনবক্স**
❌ হোয়াটসঅ্যাপ সেশন **{{{name}}}** এই ChatWoot ইনবক্স থেকে সংযোগ বিচ্ছিন্ন করা হয়েছে।
@@ -90,15 +112,15 @@ APP_DISCONNECTED_MESSAGE: |
পুনরায় সংযোগ করতে, অনুগ্রহ করে আপনার প্রশাসকের সাথে যোগাযোগ করুন।
APP_UPDATED_MESSAGE: |
app.updated.message: |-
🔄 **আপডেট করা হয়েছে: হোয়াটসঅ্যাপ সেশন সেটিংস**
✅ হোয়াটসঅ্যাপ সেশন **{{{name}}}** এর সেটিংস আপডেট করা হয়েছে।
APP_COMMANDS_LIST: |
app.commands.list: |-
আপনি নিম্নলিখিত কমান্ডগুলির একটি পাঠিয়ে **হোয়াটসঅ্যাপ ইন্টিগ্রেশন** পরিচালনা করতে পারেন:
🖥️ সেশন:
🖥️ **সেশন**:
1. **status** - বর্তমান সেশনের স্ট্যাটাস পান।
2. **restart** - সেশন পুনরায় চালু করুন - এটি আপনার হোয়াটসঅ্যাপ অ্যাকাউন্ট পুনরায় সংযোগ করার চেষ্টা করবে।
3. **start** - সেশন শুরু করুন।
@@ -108,42 +130,47 @@ APP_COMMANDS_LIST: |
7. **screenshot** - সেশনের স্ক্রিনশট পান (শুধুমাত্র **WEBJS** ইঞ্জিনে উপলব্ধ)।
8. **help** - উপলব্ধ কমান্ডগুলি।
{{#commands.server}}
⚙️ সার্ভার:
- **server status** - সার্ভারের সংস্করণ এবং স্ট্যাটাস তথ্য পান।
- **server reboot** - সার্ভার সুন্দরভাবে পুনরায় চালু করুন।
- **server reboot force** - সার্ভার জোর করে পুনরায় চালু করুন।
{{/commands.server}}
এই কথোপকথনে একটি **কমান্ড** (কেস সংবেদনশীল নয়) বা **কমান্ড নম্বর** পাঠান।
APP_HELP_REMINDER: |
app.commands.server.disabled: ⛔ **সার্ভার কমান্ড নিষ্ক্রিয় করা হয়েছে**।
দয়া করে আপনার প্রশাসকের সাথে যোগাযোগ করুন।
উপলব্ধ কমান্ডের তালিকা পেতে **help** পাঠান।
app.help.reminder: |-
উপলব্ধ কমান্ডগুলির তালিকা পেতে **help** পাঠান।
APP_SESSION_STATUS_CHANGE: |
app.session.status.change: |-
{{{emoji}}} "{{{session}}}" সেশন {{{status}}} স্ট্যাটাসে পরিবর্তিত হয়েছে
APP_SESSION_CURRENT_STATUS: |
app.session.current.status: |-
{{{emoji}}} **{{{session}}}** সেশনের স্ট্যাটাস **{{{status}}}**
**হোয়াটসঅ্যাপ অ্যাকাউন্ট:**
📱 **{{{name}}} ({{{id}}})**
APP_DASHBOARD_LINK: |
আরও বিবরণের জন্য [WAHA ড্যাশবোর্ড]({{{url}}}) দেখুন।
APP_SESSION_STATUS_WORKING: |
app.session.status.working: |-
✅ এই ChatWoot ইনবক্স **হোয়াটসঅ্যাপ অ্যাকাউন্টের** সাথে সংযুক্ত এবং বার্তা পাঠাতে ও গ্রহণ করতে প্রস্তুত!
📱 **{{{name}}} ({{{id}}})**
APP_SESSION_STATUS_ERROR: |
app.session.status.error: |-
⚠️ **এই ChatWoot ইনবক্স হোয়াটসঅ্যাপ অ্যাকাউন্টের সাথে সংযুক্ত কিন্তু বার্তা পাঠাতে ও গ্রহণ করতে প্রস্তুত নয়!**
অনুগ্রহ করে সেশনের স্ট্যাটাস পরীক্ষা করুন এবং এটি পুনরায় চালু করার চেষ্টা করুন অথবা লগআউট করুন এবং QR কোড আবার স্ক্যান করুন।
APP_SESSION_SCAN_QR_CODE: |
app.session.scan.qr.code: |-
⚠️ **এই ChatWoot ইনবক্সের সাথে হোয়াটসঅ্যাপ অ্যাকাউন্ট লিঙ্ক করতে আপনার ফোনে QR কোড স্ক্যান করুন!**
APP_SERVER_VERSION_AND_STATUS: |
app.server.version.and.status: |-
**সংস্করণ:**
```
{{{version}}}
@@ -154,16 +181,16 @@ APP_SERVER_VERSION_AND_STATUS: |
{{{status}}}
```
APP_SERVER_REBOOT: |
app.server.reboot: |-
সার্ভার সুন্দরভাবে পুনরায় চালু হচ্ছে...
APP_SERVER_REBOOT_FORCE: |
app.server.reboot.force: |-
সার্ভার জোর করে পুনরায় চালু হচ্ছে...
APP_LOGOUT_SUCCESS: |
app.logout.success: |-
✅ অ্যাকাউন্ট সফলভাবে অননুমোদিত করা হয়েছে!
APP_NEW_VERSION_AVAILABLE: |
waha.new.version.available: |-
🔄 **নতুন সংস্করণ উপলব্ধ!**
বর্তমান সংস্করণ: **{{{currentVersion}}}**
@@ -171,7 +198,7 @@ APP_NEW_VERSION_AVAILABLE: |
👉 [**🆕 পরিবর্তন লগ**]({{{changelogUrl}}}) এ নতুন কি আছে তা দেখুন
APP_CORE_VERSION_USED: |
waha.core.version.used: |-
❤️ **WAHA সমর্থন করুন!**
আপনি বিনামূল্যের **WAHA Core** সংস্করণ ব্যবহার করছেন। যদি আপনি এই প্রকল্পটি উপযোগী মনে করেন, তবে এর বিকাশকে সমর্থন করার কথা বিবেচনা করুন।
@@ -179,3 +206,177 @@ APP_CORE_VERSION_USED: |
আপনার সমর্থন হল একমাত্র উপায় যা দিয়ে আমরা আপডেট, ফিক্স এবং নতুন বৈশিষ্ট্যগুলিতে কাজ চালিয়ে যেতে পারি।
[**🎁 আমাদের সমর্থন করুন**]({{{supportUrl}}})
chatwoot.to.whatsapp.message.text: |-
{{{ content }}}
chatwoot.to.whatsapp.message.media.caption: |-
{{#singleAttachment}}
{{#content}}
{{{ content }}}
{{/content}}
{{/singleAttachment}}
whatsapp.to.chatwoot.message: |-
{{{payload.body}}}
whatsapp.to.chatwoot.message.contacts: |-
👤 **শেয়ার করা যোগাযোগ**
{{#contacts}}
🪪 নাম: **{{fullName}}**
{{#phoneNumbers}}
- 📞 ফোন: **{{.}}**
{{/phoneNumbers}}
{{/contacts}}
whatsapp.to.chatwoot.message.location: |-
{{#message}}
{{#locationMessage}}
📍 **শেয়ার করা লোকেশন**
{{#name}}
**{{name}}**
{{/name}}
{{#address}}
📍 **ঠিকানা:** {{address}}
{{/address}}
{{#url}}
🌐 **ওয়েবসাইট:** {{url}}
{{/url}}
🔗 **মানচিত্রে দেখুন:** https://www.google.com/maps?q={{degreesLatitude}},{{degreesLongitude}}
{{#caption}}{{caption}}{{/caption}}
{{/locationMessage}}
{{#liveLocationMessage}}
📍📡 **লাইভ লোকেশন শেয়ার করা হচ্ছে**
রিয়েল-টাইম আপডেট দেখতে **WhatsApp** খুলুন।
{{#name}}
**{{name}}**
{{/name}}
{{#address}}
📍 **ঠিকানা:** {{address}}
{{/address}}
{{#url}}
🌐 **ওয়েবসাইট:** {{url}}
{{/url}}
🔗 **মানচিত্রে দেখুন:** https://www.google.com/maps?q={{degreesLatitude}},{{degreesLongitude}}
{{#caption}}{{caption}}{{/caption}}
{{/liveLocationMessage}}
{{/message}}
whatsapp.to.chatwoot.message.poll: |-
{{#poll}}
📊 **জরিপ**
**{{name}}**
{{#options}}
- {{optionName}}
{{/options}}
{{/poll}}
whatsapp.to.chatwoot.message.list: |-
{{#message.listMessage}}
{{#title}}
*{{{title}}}*
{{/title}}
{{#description}}
{{{description}}}
{{/description}}
{{#footerText}}
{{{footerText}}}
{{/footerText}}
{{#buttonText}}
{{{buttonText}}}
{{/buttonText}}
{{#sections}}
{{#title}}
{{{title}}}:
{{/title}}
{{#rows}}
{{#title}}
- {{{title}}}
{{/title}}
{{/rows}}
{{/sections}}
{{/message.listMessage}}
whatsapp.to.chatwoot.message.event: |-
{{#message}}
{{#eventMessage}}
📅 **ইভেন্ট**
**{{name}}**
{{#description}}
{{description}}
{{/description}}
{{#location.name}}
📍 **স্থান:** {{location.name}}
{{/location.name}}
{{#startTime}}
🕐 **শুরুর সময়:** {{startTime}}
{{/startTime}}
{{#endTime}}
🕐 **শেষের সময়:** {{endTime}}
{{/endTime}}
{{#joinLink}}
🔗 **যোগদানের লিঙ্ক:** [{{joinLink}}]({{joinLink}})
{{/joinLink}}
{{/eventMessage}}
{{/message}}
whatsapp.to.chatwoot.message.pix: |-
💳 **PIX - কপি এবং পেস্ট**
{{#pixData}}
{{#merchantName}}
**{{merchantName}}**
{{/merchantName}}
{{#key}}
**PIX কী:** {{key}}
{{/key}}
{{#keyType}}
**ধরন:** {{keyType}}
{{/keyType}}
{{#totalAmount}}
**পরিমাণ:** {{totalAmountFormatted}}
{{/totalAmount}}
{{#referenceId}}
**রেফারেন্স:** {{referenceId}}
{{/referenceId}}
💳 **PIX কপি এবং পেস্ট পাঠানো হয়েছে**
{{/pixData}}
whatsapp.to.chatwoot.message.unsupported: |-
⚠️ **এই ধরনের বার্তা ChatWoot-এ সমর্থিত নয়।**
📱 এটি দেখতে অনুগ্রহ করে **WhatsApp** খুলুন।
বিস্তারিত: [{{{details.text}}}]({{{details.url}}})
whatsapp.to.chatwoot.message.facebook.ad: |-
📢 **ফেসবুক বিজ্ঞাপন**
**শিরোনাম:** {{{adData.title}}}
**বর্ণনা:**
{{{adData.body}}}
**বিজ্ঞাপনের লিংক:** [ফেসবুকে দেখুন]({{{adData.sourceUrl}}})
**বিজ্ঞাপনের আইডি:** {{{adData.sourceId}}}
---
💬 **গ্রাহকের বার্তা:**
{{{payload.body}}}
+385
View File
@@ -0,0 +1,385 @@
# German translations for ChatWoot integration
locale.name: '🇩🇪 Deutsch (German)'
# Basis-Gebietsschema-Code - muss eine gültige BCP 47 Locale sein
# https://en.wikipedia.org/wiki/IETF_language_tag
# WAHA nutzt ihn zur Darstellung von Zahlen, Währungen sowie Datum und Uhrzeit
locale.base: 'de-DE'
app.inbox.contact.name: 'WhatsApp Integration'
app.inbox.contact.avatar.url: 'https://raw.githubusercontent.com/devlikeapro/waha/core/logo.png'
# "Optionen" von date.toLocaleString
# https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat#options
datetime:
locales: de-DE
day: 2-digit
month: 2-digit
year: numeric
hour: 2-digit
minute: 2-digit
# https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat#timezonename
timeZoneName: shortGeneric
# https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
# timeZone: 'Europe/Berlin'
job.report.error: |-
⚠️ **{{{header}}}**
{{#attempts.nextDelay}}
🔄 Wiederholung in {{attempts.nextDelay}} Sekunden...
{{/attempts.nextDelay}}
{{^attempts.nextDelay}}
❌ Keine weiteren Versuche übrig. Bitte versuchen Sie es später erneut.
{{/attempts.nextDelay}}
Versuch: {{{attempts.current}}} / {{{attempts.max}}}
Details: [{{{details.text}}}]({{{details.url}}})
{{#error}}
```
{{{error}}}
```
{{/error}}
job.report.succeeded: |-
✅ **Erfolgreich**
Versuch: {{{attempts.current}}} / {{{attempts.max}}}
Details: [{{{details.text}}}]({{{details.url}}})
whatsapp.message.sending.error:
Nachricht konnte nicht über WhatsApp gesendet werden.
whatsapp.message.receiving.error:
Nachricht konnte nicht von WhatsApp empfangen werden
whatsapp.message.removing.error:
Nachricht konnte nicht von WhatsApp entfernt werden
job.scheduled.error.header: Geplante Aufgabe konnte nicht ausgeführt werden
message.from.whatsapp: |-
{{{text}}}
📱 *Von WhatsApp gesendet*
message.from.api: |-
{{{text}}}
📱 *Über API gesendet*
message.removed.in.whatsapp: |-
❌ *Diese Nachricht wurde in WhatsApp gelöscht*
message.edited.in.whatsapp: |-
{{{text}}}
✏️ *In WhatsApp bearbeitet*
whatsapp.group.message: |-
👥 *{{{participant}}}*
{{{text}}}
whatsapp.reaction.added: |-
*Reagiert mit* {{{emoji}}}
whatsapp.reaction.removed: |-
*Reaktion entfernt*
whatsapp.contact.group.suffix: 'Gruppe'
whatsapp.contact.channel.suffix: 'Kanal'
whatsapp.contact.broadcast.suffix: 'Broadcast-Liste'
# Aka Stories
whatsapp.contact.status.name: 'Status'
app.connected.message: |-
🔗 **VERBUNDEN: WhatsApp-Sitzung mit ChatWoot Inbox**
✅ WhatsApp-Sitzung **{{{name}}}** wurde erfolgreich mit dieser ChatWoot Inbox verbunden.
Diese Inbox ist jetzt mit Ihrem **WhatsApp-Konto** verknüpft. Sie können Nachrichten direkt von hier senden und empfangen — genau wie bei WhatsApp, aber innerhalb von ChatWoot.
Sie können auch die mit dieser Inbox verknüpfte WhatsApp-Sitzung verwalten. Um verfügbare Aktionen anzuzeigen, senden Sie **help** an diesen Chat.
📋 Häufige Aktionen, die Sie durchführen können:
- Überprüfen, ob die WhatsApp-Sitzung aktiv ist
- Die WhatsApp-Sitzung neu starten oder stoppen
- Abmelden und durch Scannen eines neuen QR-Codes wieder verbinden
Wenn alles funktioniert, können Sie frei chatten. Wenn Sie Warnungen sehen, müssen Sie möglicherweise Ihr WhatsApp-Konto neu verbinden.
Lassen Sie uns beginnen! 🚀
app.disconnected.message: |-
🔌 **GETRENNT: WhatsApp-Sitzung von ChatWoot Inbox**
❌ WhatsApp-Sitzung **{{{name}}}** wurde von dieser ChatWoot Inbox getrennt.
Sie können keine Nachrichten mehr von WhatsApp senden oder empfangen.
Um eine erneute Verbindung herzustellen, wenden Sie sich bitte an Ihren Administrator.
app.updated.message: |-
🔄 **AKTUALISIERT: WhatsApp-Sitzungseinstellungen**
✅ Einstellungen für WhatsApp-Sitzung **{{{name}}}** wurden aktualisiert.
app.commands.list: |-
Sie können die **WhatsApp Integration** verwalten, indem Sie einen der folgenden Befehle senden:
🖥️ **Sitzung**:
1. **status** - den aktuellen Sitzungsstatus abrufen.
2. **restart** - die Sitzung neu starten - dies versucht, Ihr WhatsApp-Konto neu zu verbinden.
3. **start** - die Sitzung starten.
4. **stop** - die Sitzung stoppen.
5. **logout** - von der Sitzung abmelden, damit Sie den QR-Code scannen und die Sitzung reparieren können.
6. **qr** - den QR-Code für die Sitzung abrufen.
7. **screenshot** - einen Screenshot der Sitzung erhalten (nur in der **WEBJS**-Engine verfügbar).
8. **help** - verfügbare Befehle.
{{#commands.server}}
⚙️ Server:
- **server status** - Serverversion und Statusinformationen abrufen.
- **server reboot** - Server ordnungsgemäß neu starten.
- **server reboot force** - Server erzwungen neu starten.
{{/commands.server}}
Senden Sie einen **Befehl** (Groß-/Kleinschreibung wird nicht beachtet) oder eine **Befehlsnummer** an diese Konversation.
app.commands.server.disabled: ⛔ **Serverbefehle sind deaktiviert**.
Bitte wenden Sie sich an Ihren Administrator.
Senden Sie **help**, um die Liste der verfügbaren Befehle zu erhalten.
app.help.reminder: |-
Senden Sie **help**, um die Liste der verfügbaren Befehle zu erhalten.
app.session.status.change: |-
{{{emoji}}} Sitzung "{{{session}}}" hat den Status zu {{{status}}} gewechselt
app.session.current.status: |-
{{{emoji}}} **{{{session}}}** Sitzungsstatus ist **{{{status}}}**
**WhatsApp-Konto:**
📱 **{{{name}}} ({{{id}}})**
app.session.status.working: |-
✅ Diese ChatWoot Inbox ist mit dem **WhatsApp-Konto** verknüpft und bereit zum Senden und Empfangen von Nachrichten!
📱 **{{{name}}} ({{{id}}})**
app.session.status.error: |-
⚠️ **Diese ChatWoot Inbox ist mit dem WhatsApp-Konto verknüpft, aber NICHT bereit zum Senden und Empfangen von Nachrichten!**
Bitte überprüfen Sie den Sitzungsstatus und versuchen Sie, sie neu zu starten oder melden Sie sich ab und scannen Sie den QR-Code erneut.
app.session.scan.qr.code: |-
⚠️ **Scannen Sie den QR-Code auf Ihrem Telefon, um das WhatsApp-Konto mit dieser ChatWoot Inbox zu verknüpfen!**
app.server.version.and.status: |-
**Version:**
```
{{{version}}}
```
**Status:**
```
{{{status}}}
```
app.server.reboot: |-
Server wird ordnungsgemäß neu gestartet...
app.server.reboot.force: |-
Server wird erzwungen neu gestartet...
app.logout.success: |-
✅ Konto erfolgreich abgemeldet!
waha.new.version.available: |-
🔄 **Neue Version verfügbar!**
Aktuelle Version: **{{{currentVersion}}}**
Neue Version: **{{{newVersion}}}**
👉 Sehen Sie, was neu ist im [**🆕 Änderungsprotokoll**]({{{changelogUrl}}})
waha.core.version.used: |-
❤️ **Unterstützen Sie WAHA!**
Sie verwenden die kostenlose **WAHA Core** Version. Wenn Sie dieses Projekt nützlich finden, erwägen Sie bitte, seine Entwicklung zu unterstützen.
Ihre Unterstützung ist der einzige Weg, wie wir weiterhin an Updates, Fehlerbehebungen und neuen Funktionen arbeiten können.
[**🎁 Unterstützen Sie uns**]({{{supportUrl}}})
chatwoot.to.whatsapp.message.text: |-
{{{ content }}}
chatwoot.to.whatsapp.message.media.caption: |-
{{#singleAttachment}}
{{#content}}
{{{ content }}}
{{/content}}
{{/singleAttachment}}
whatsapp.to.chatwoot.message: |-
{{{payload.body}}}
whatsapp.to.chatwoot.message.contacts: |-
👤 **Geteilte Kontakte**
{{#contacts}}
🪪 Name: **{{fullName}}**
{{#phoneNumbers}}
- 📞 Telefon: **{{.}}**
{{/phoneNumbers}}
{{/contacts}}
whatsapp.to.chatwoot.message.location: |-
{{#message}}
{{#locationMessage}}
📍 **Geteilter Standort**
{{#name}}
**{{name}}**
{{/name}}
{{#address}}
📍 **Adresse:** {{address}}
{{/address}}
{{#url}}
🌐 **Webseite:** {{url}}
{{/url}}
🔗 **Auf Karte ansehen:** https://www.google.com/maps?q={{degreesLatitude}},{{degreesLongitude}}
{{#caption}}{{caption}}{{/caption}}
{{/locationMessage}}
{{#liveLocationMessage}}
📍📡 **Live-Standort wird geteilt**
Öffne **WhatsApp**, um Aktualisierungen in Echtzeit zu sehen.
{{#name}}
**{{name}}**
{{/name}}
{{#address}}
📍 **Adresse:** {{address}}
{{/address}}
{{#url}}
🌐 **Webseite:** {{url}}
{{/url}}
🔗 **Auf Karte ansehen:** https://www.google.com/maps?q={{degreesLatitude}},{{degreesLongitude}}
{{#caption}}{{caption}}{{/caption}}
{{/liveLocationMessage}}
{{/message}}
whatsapp.to.chatwoot.message.poll: |-
{{#poll}}
📊 **Umfrage**
**{{name}}**
{{#options}}
- {{optionName}}
{{/options}}
{{/poll}}
whatsapp.to.chatwoot.message.list: |-
{{#message.listMessage}}
{{#title}}
*{{{title}}}*
{{/title}}
{{#description}}
{{{description}}}
{{/description}}
{{#footerText}}
{{{footerText}}}
{{/footerText}}
{{#buttonText}}
{{{buttonText}}}
{{/buttonText}}
{{#sections}}
{{#title}}
{{{title}}}:
{{/title}}
{{#rows}}
{{#title}}
- {{{title}}}
{{/title}}
{{/rows}}
{{/sections}}
{{/message.listMessage}}
whatsapp.to.chatwoot.message.event: |-
{{#message}}
{{#eventMessage}}
📅 **Veranstaltung**
**{{name}}**
{{#description}}
{{description}}
{{/description}}
{{#location.name}}
📍 **Ort:** {{location.name}}
{{/location.name}}
{{#startTime}}
🕐 **Beginn:** {{startTime}}
{{/startTime}}
{{#endTime}}
🕐 **Ende:** {{endTime}}
{{/endTime}}
{{#joinLink}}
🔗 **Teilnahmelink:** [{{joinLink}}]({{joinLink}})
{{/joinLink}}
{{/eventMessage}}
{{/message}}
whatsapp.to.chatwoot.message.pix: |-
💳 **PIX - Kopieren und Einfügen**
{{#pixData}}
{{#merchantName}}
**{{merchantName}}**
{{/merchantName}}
{{#key}}
**PIX-Schlüssel:** {{key}}
{{/key}}
{{#keyType}}
**Typ:** {{keyType}}
{{/keyType}}
{{#totalAmount}}
**Betrag:** {{totalAmountFormatted}}
{{/totalAmount}}
{{#referenceId}}
**Referenz:** {{referenceId}}
{{/referenceId}}
💳 **PIX Kopieren und Einfügen gesendet**
{{/pixData}}
whatsapp.to.chatwoot.message.unsupported: |-
⚠️ **Dieser Nachrichtentyp wird in ChatWoot nicht unterstützt.**
📱 Bitte öffne **WhatsApp**, um ihn anzusehen.
Details: [{{{details.text}}}]({{{details.url}}})
whatsapp.to.chatwoot.message.facebook.ad: |-
📢 **FACEBOOK-ANZEIGE**
**Titel:** {{{adData.title}}}
**Beschreibung:**
{{{adData.body}}}
**Anzeigenlink:** [Auf Facebook ansehen]({{{adData.sourceUrl}}})
**Anzeigen-ID:** {{{adData.sourceId}}}
---
💬 **Kundennachricht:**
{{{payload.body}}}
+381
View File
@@ -0,0 +1,381 @@
# English translations for ChatWoot integration
locale.name: '🇺🇸 English (English)'
# Base locale code - must be a valid BCP 47 locale
# https://en.wikipedia.org/wiki/IETF_language_tag
# WAHA uses it for number, currency and datetime representation
locale.base: 'en-US'
app.inbox.contact.name: 'WhatsApp Integration'
app.inbox.contact.avatar.url: 'https://raw.githubusercontent.com/devlikeapro/waha/core/logo.png'
# "options" from date.toLocaleString
# https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat#options
datetime:
day: 2-digit
month: 2-digit
year: numeric
hour: 2-digit
minute: 2-digit
# https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat#timezonename
timeZoneName: shortGeneric
# https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
# timeZone: 'America/New_York'
job.report.error: |-
⚠️ **{{{header}}}**
{{#attempts.nextDelay}}
🔄 Retrying in {{attempts.nextDelay}} seconds...
{{/attempts.nextDelay}}
{{^attempts.nextDelay}}
❌ No more retries left. Please try again later.
{{/attempts.nextDelay}}
Attempt: {{{attempts.current}}} / {{{attempts.max}}}
Details: [{{{details.text}}}]({{{details.url}}})
{{#error}}
```
{{{error}}}
```
{{/error}}
job.report.succeeded: |-
✅ **Succeeded**
Attempt: {{{attempts.current}}} / {{{attempts.max}}}
Details: [{{{details.text}}}]({{{details.url}}})
whatsapp.message.sending.error: Couldn't send a message on WhatsApp.
whatsapp.message.receiving.error: Couldn't receive a message from WhatsApp
whatsapp.message.removing.error: Couldn't remove a message from WhatsApp
job.scheduled.error.header: Scheduled job failed to execute
message.from.whatsapp: |-
{{{text}}}
📱 *Sent from WhatsApp*
message.from.api: |-
{{{text}}}
📱 *Sent via API*
message.removed.in.whatsapp: |-
❌ *This message was deleted in WhatsApp*
message.edited.in.whatsapp: |-
{{{text}}}
✏️ *Edited in WhatsApp*
whatsapp.group.message: |-
👥 *{{{participant}}}*
{{{text}}}
whatsapp.reaction.added: |-
*Reacted* {{{emoji}}}
whatsapp.reaction.removed: |-
*Reaction removed*
whatsapp.contact.group.suffix: 'Group'
whatsapp.contact.channel.suffix: 'Channel'
whatsapp.contact.broadcast.suffix: 'Broadcast List'
# Aka Stories
whatsapp.contact.status.name: 'Status'
chatwoot.to.whatsapp.message.text: |-
{{{ content }}}
chatwoot.to.whatsapp.message.media.caption: |-
{{#singleAttachment}}
{{#content}}
{{{ content }}}
{{/content}}
{{/singleAttachment}}
whatsapp.to.chatwoot.message: |-
{{{payload.body}}}
whatsapp.to.chatwoot.message.contacts: |-
👤 **Shared Contact(s)**
{{#contacts}}
🪪 Name: **{{fullName}}**
{{#phoneNumbers}}
- 📞 Phone: **{{.}}**
{{/phoneNumbers}}
{{/contacts}}
whatsapp.to.chatwoot.message.location: |-
{{#message}}
{{#locationMessage}}
📍 **Shared Location**
{{#name}}
**{{name}}**
{{/name}}
{{#address}}
📍 **Address:** {{address}}
{{/address}}
{{#url}}
🌐 **Website:** {{url}}
{{/url}}
🔗 **View on map:** https://www.google.com/maps?q={{degreesLatitude}},{{degreesLongitude}}
{{#caption}}{{caption}}{{/caption}}
{{/locationMessage}}
{{#liveLocationMessage}}
📍📡 **Sharing Live Location**
Open **WhatsApp** to see real-time updates.
{{#name}}
**{{name}}**
{{/name}}
{{#address}}
📍 **Address:** {{address}}
{{/address}}
{{#url}}
🌐 **Website:** {{url}}
{{/url}}
🔗 **View on map:** https://www.google.com/maps?q={{degreesLatitude}},{{degreesLongitude}}
{{#caption}}{{caption}}{{/caption}}
{{/liveLocationMessage}}
{{/message}}
whatsapp.to.chatwoot.message.poll: |-
{{#poll}}
📊 **Poll**
**{{name}}**
{{#options}}
- {{optionName}}
{{/options}}
{{/poll}}
whatsapp.to.chatwoot.message.list: |-
{{#message.listMessage}}
{{#title}}
*{{{title}}}*
{{/title}}
{{#description}}
{{{description}}}
{{/description}}
{{#footerText}}
{{{footerText}}}
{{/footerText}}
{{#buttonText}}
{{{buttonText}}}
{{/buttonText}}
{{#sections}}
{{#title}}
{{{title}}}:
{{/title}}
{{#rows}}
{{#title}}
- {{{title}}}
{{/title}}
{{/rows}}
{{/sections}}
{{/message.listMessage}}
whatsapp.to.chatwoot.message.event: |-
{{#message}}
{{#eventMessage}}
📅 **Event**
**{{name}}**
{{#description}}
{{description}}
{{/description}}
{{#location.name}}
📍 **Location:** {{location.name}}
{{/location.name}}
{{#startTime}}
🕐 **Start:** {{startTime}}
{{/startTime}}
{{#endTime}}
🕐 **End:** {{endTime}}
{{/endTime}}
{{#joinLink}}
🔗 **Join Link:** [{{joinLink}}]({{joinLink}})
{{/joinLink}}
{{/eventMessage}}
{{/message}}
whatsapp.to.chatwoot.message.pix: |-
💳 **PIX - Copy and Paste**
{{#pixData}}
{{#merchantName}}
**{{merchantName}}**
{{/merchantName}}
{{#key}}
**PIX Key:** {{key}}
{{/key}}
{{#keyType}}
**Type:** {{keyType}}
{{/keyType}}
{{#totalAmount}}
**Amount:** {{totalAmountFormatted}}
{{/totalAmount}}
{{#referenceId}}
**Reference:** {{referenceId}}
{{/referenceId}}
💳 **PIX Copy and Paste sent**
{{/pixData}}
whatsapp.to.chatwoot.message.unsupported: |-
⚠️ **This message type is not supported in ChatWoot.**
📱 Please open **WhatsApp** to view it.
Details: [{{{details.text}}}]({{{details.url}}})
whatsapp.to.chatwoot.message.facebook.ad: |-
📢 **FACEBOOK AD**
**Title:** {{{adData.title}}}
**Description:**
{{{adData.body}}}
**Ad Link:** [View on Facebook]({{{adData.sourceUrl}}})
**Ad ID:** {{{adData.sourceId}}}
---
💬 **CUSTOMER MESSAGE:**
{{{payload.body}}}
app.connected.message: |-
🔗 **CONNECTED: WhatsApp Session to ChatWoot Inbox**
✅ WhatsApp session **{{{name}}}** has been successfully connected to this ChatWoot Inbox.
This inbox is now linked to your **WhatsApp Account**. You can send and receive messages directly from here — just like using WhatsApp, but inside ChatWoot.
You can also manage the WhatsApp session linked to this inbox. To view available actions, send **help** to this chat.
📋 Common actions you can do:
- Check if the WhatsApp session is active
- Restart or Stop the WhatsApp session
- Log out and reconnect by scanning a new QR code
If everything is working, you'll be able to chat freely. If you see warnings, you might need to reconnect your WhatsApp account.
Let's get started! 🚀
app.disconnected.message: |-
🔌 **DISCONNECTED: WhatsApp Session from ChatWoot Inbox**
❌ WhatsApp session **{{{name}}}** has been disconnected from this ChatWoot Inbox.
You will no longer be able to send or receive messages from WhatsApp.
To reconnect, please contact your administrator.
app.updated.message: |-
🔄 **UPDATED: WhatsApp Session Settings**
✅ Settings for WhatsApp session **{{{name}}}** have been updated.
app.commands.list: |-
You can manage **WhatsApp Integration** by sending one of the following commands:
🖥️ **Session**:
1. **status** - get the current session status.
2. **restart** - restart the session - this will try to reconnect your WhatsApp account.
3. **start** - start the session.
4. **stop** - stop the session.
5. **logout** - logout from the session, so you can scan QR code and repair the session.
6. **qr** - get the QR code for the session.
7. **screenshot** - get a screenshot of the session (only available in **WEBJS** engine).
8. **help** - available commands.
{{#commands.server}}
🛠️ **Server**:
- **server status** - get the server version and status information.
- **server reboot** - reboot the server gracefully.
- **server reboot force** - force reboot the server.
{{/commands.server}}
Send a **command** (case insensitive) or a **command number** to this conversation.
app.commands.server.disabled: ⛔ **Server commands are disabled**.
Please contact your administrator.
Send **help** to get the list of available commands.
app.help.reminder: |-
Send **help** to get the list of available commands.
app.session.status.change: |-
{{{emoji}}} "{{{session}}}" session switched to {{{status}}} status
app.session.current.status: |-
{{{emoji}}} **{{{session}}}** session status is in **{{{status}}}**
**WhatsApp Account:**
📱 **{{{name}}} ({{{id}}})**
app.session.status.working: |-
✅ This ChatWoot Inbox is linked to **WhatsApp Account** and ready to send and receive messages!
📱 **{{{name}}} ({{{id}}})**
app.session.status.error: |-
⚠️ **This ChatWoot Inbox is linked to WhatsApp Account but NOT ready to send and receive messages!**
Please check the session status and try restarting it or logout and scan the QR code again.
app.session.scan.qr.code: |-
⚠️ **Scan the QR code on your phone to link WhatsApp Account with this ChatWoot Inbox!**
app.server.version.and.status: |-
**Version:**
```
{{{version}}}
```
**Status:**
```
{{{status}}}
```
app.server.reboot: |-
Server is rebooting gracefully...
app.server.reboot.force: |-
Server is force rebooting...
app.logout.success: |-
✅ Account unauthorized successfully!
waha.new.version.available: |-
🔄 **New Version Available!**
Current version: **{{{currentVersion}}}**
New version: **{{{newVersion}}}**
👉 Check out what's new in the [**🆕 Changelog**]({{{changelogUrl}}})
waha.core.version.used: |-
❤️ **Support WAHA!**
You're using the free **WAHA Core** version. If you find this project useful, please consider supporting its development.
Your support is the only way we can continue working on updates, fixes, and new features.
[**🎁 Support Us**]({{{supportUrl}}})
+385
View File
@@ -0,0 +1,385 @@
# Spanish translations for ChatWoot integration
locale.name: '🇪🇸 Español (Spanish)'
# Código de locale base - debe ser un locale BCP 47 válido
# https://en.wikipedia.org/wiki/IETF_language_tag
# WAHA lo utiliza para formatear números, moneda y fecha/hora
locale.base: 'es-ES'
app.inbox.contact.name: 'Integración de WhatsApp'
app.inbox.contact.avatar.url: 'https://raw.githubusercontent.com/devlikeapro/waha/core/logo.png'
# "opciones" de date.toLocaleString
# https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat#options
datetime:
locales: es-ES
day: 2-digit
month: 2-digit
year: numeric
hour: 2-digit
minute: 2-digit
# https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat#timezonename
timeZoneName: shortGeneric
# https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
# timeZone: 'Europe/Madrid'
job.report.error: |-
⚠️ **{{{header}}}**
{{#attempts.nextDelay}}
🔄 Reintentando en {{attempts.nextDelay}} segundos...
{{/attempts.nextDelay}}
{{^attempts.nextDelay}}
❌ No quedan más intentos. Por favor, inténtelo de nuevo más tarde.
{{/attempts.nextDelay}}
Intento: {{{attempts.current}}} / {{{attempts.max}}}
Detalles: [{{{details.text}}}]({{{details.url}}})
{{#error}}
```
{{{error}}}
```
{{/error}}
job.report.succeeded: |-
✅ **Éxito**
Intento: {{{attempts.current}}} / {{{attempts.max}}}
Detalles: [{{{details.text}}}]({{{details.url}}})
whatsapp.message.sending.error: No se pudo enviar un mensaje en WhatsApp.
whatsapp.message.receiving.error: No se pudo recibir un mensaje de WhatsApp
whatsapp.message.removing.error: No se pudo eliminar un mensaje de WhatsApp
job.scheduled.error.header: La tarea programada no se pudo ejecutar
message.from.whatsapp: |-
{{{text}}}
📱 *Enviado desde WhatsApp*
message.from.api: |-
{{{text}}}
📱 *Enviado a través de API*
message.removed.in.whatsapp: |-
❌ *Este mensaje fue eliminado en WhatsApp*
message.edited.in.whatsapp: |-
{{{text}}}
✏️ *Editado en WhatsApp*
whatsapp.group.message: |-
👥 *{{{participant}}}*
{{{text}}}
whatsapp.reaction.added: |-
*Reaccionó con* {{{emoji}}}
whatsapp.reaction.removed: |-
*Reacción eliminada*
whatsapp.contact.group.suffix: 'Grupo'
whatsapp.contact.channel.suffix: 'Canal'
whatsapp.contact.broadcast.suffix: 'Lista de difusión'
# Aka Stories
whatsapp.contact.status.name: 'Estado'
app.connected.message: |-
🔗 **CONECTADO: Sesión de WhatsApp a Bandeja de Entrada de ChatWoot**
✅ La sesión de WhatsApp **{{{name}}}** ha sido conectada exitosamente a esta Bandeja de Entrada de ChatWoot.
Esta bandeja de entrada ahora está vinculada a tu **Cuenta de WhatsApp**. Puedes enviar y recibir mensajes directamente desde aquí — justo como usar WhatsApp, pero dentro de ChatWoot.
También puedes gestionar la sesión de WhatsApp vinculada a esta bandeja de entrada. Para ver las acciones disponibles, envía **help** a este chat.
📋 Acciones comunes que puedes realizar:
- Comprobar si la sesión de WhatsApp está activa
- Reiniciar o Detener la sesión de WhatsApp
- Cerrar sesión y reconectar escaneando un nuevo código QR
Si todo funciona, podrás chatear libremente. Si ves advertencias, es posible que necesites reconectar tu cuenta de WhatsApp.
¡Comencemos! 🚀
app.disconnected.message: |-
🔌 **DESCONECTADO: Sesión de WhatsApp de Bandeja de Entrada de ChatWoot**
❌ La sesión de WhatsApp **{{{name}}}** ha sido desconectada de esta Bandeja de Entrada de ChatWoot.
Ya no podrás enviar o recibir mensajes desde WhatsApp.
Para reconectar, por favor contacta a tu administrador.
app.updated.message: |-
🔄 **ACTUALIZADO: Configuración de Sesión de WhatsApp**
✅ La configuración para la sesión de WhatsApp **{{{name}}}** ha sido actualizada.
app.commands.list: |-
Puedes gestionar la **Integración de WhatsApp** enviando uno de los siguientes comandos:
🖥️ **Sesión**:
1. **status** - obtener el estado actual de la sesión.
2. **restart** - reiniciar la sesión - esto intentará reconectar tu cuenta de WhatsApp.
3. **start** - iniciar la sesión.
4. **stop** - detener la sesión.
5. **logout** - cerrar sesión, para que puedas escanear el código QR y reparar la sesión.
6. **qr** - obtener el código QR para la sesión.
7. **screenshot** - obtener una captura de pantalla de la sesión (solo disponible en el motor **WEBJS**).
8. **help** - comandos disponibles.
{{#commands.server}}
{{#commands.server}}
⚙️ Servidor:
- **server status** - obtener información de versión y estado del servidor.
- **server reboot** - reiniciar el servidor de forma segura.
- **server reboot force** - forzar el reinicio del servidor.
{{/commands.server}}
{{/commands.server}}
Envía un **comando** (no sensible a mayúsculas/minúsculas) o un **número de comando** a esta conversación.
app.commands.server.disabled: ⛔ **Los comandos del servidor están
deshabilitados**.
Por favor, contacta a tu administrador.
Envía **help** para obtener la lista de comandos disponibles.
app.help.reminder: |-
Envía **help** para obtener la lista de comandos disponibles.
app.session.status.change: |-
{{{emoji}}} La sesión "{{{session}}}" cambió al estado {{{status}}}
app.session.current.status: |-
{{{emoji}}} El estado de la sesión **{{{session}}}** es **{{{status}}}**
**Cuenta de WhatsApp:**
📱 **{{{name}}} ({{{id}}})**
app.session.status.working: |-
✅ ¡Esta Bandeja de Entrada de ChatWoot está vinculada a la **Cuenta de WhatsApp** y lista para enviar y recibir mensajes!
📱 **{{{name}}} ({{{id}}})**
app.session.status.error: |-
⚠️ **¡Esta Bandeja de Entrada de ChatWoot está vinculada a la Cuenta de WhatsApp pero NO está lista para enviar y recibir mensajes!**
Por favor, comprueba el estado de la sesión e intenta reiniciarla o cierra sesión y escanea el código QR de nuevo.
app.session.scan.qr.code: |-
⚠️ **¡Escanea el código QR en tu teléfono para vincular la Cuenta de WhatsApp con esta Bandeja de Entrada de ChatWoot!**
app.server.version.and.status: |-
**Versión:**
```
{{{version}}}
```
**Estado:**
```
{{{status}}}
```
app.server.reboot: |-
El servidor se está reiniciando de forma segura...
app.server.reboot.force: |-
El servidor se está reiniciando forzadamente...
app.logout.success: |-
✅ ¡Cuenta desautorizada con éxito!
waha.new.version.available: |-
🔄 **¡Nueva Versión Disponible!**
Versión actual: **{{{currentVersion}}}**
Nueva versión: **{{{newVersion}}}**
👉 Mira qué hay de nuevo en el [**🆕 Registro de Cambios**]({{{changelogUrl}}})
waha.core.version.used: |-
❤️ **¡Apoya a WAHA!**
Estás usando la versión gratuita **WAHA Core**. Si encuentras útil este proyecto, por favor considera apoyar su desarrollo.
Tu apoyo es la única forma en que podemos seguir trabajando en actualizaciones, correcciones y nuevas funciones.
[**🎁 Apóyanos**]({{{supportUrl}}})
chatwoot.to.whatsapp.message.text: |-
{{{ content }}}
chatwoot.to.whatsapp.message.media.caption: |-
{{#singleAttachment}}
{{#content}}
{{{ content }}}
{{/content}}
{{/singleAttachment}}
whatsapp.to.chatwoot.message: |-
{{{payload.body}}}
whatsapp.to.chatwoot.message.contacts: |-
👤 **Contacto(s) compartido(s)**
{{#contacts}}
🪪 Nombre: **{{fullName}}**
{{#phoneNumbers}}
- 📞 Teléfono: **{{.}}**
{{/phoneNumbers}}
{{/contacts}}
whatsapp.to.chatwoot.message.location: |-
{{#message}}
{{#locationMessage}}
📍 **Ubicación compartida**
{{#name}}
**{{name}}**
{{/name}}
{{#address}}
📍 **Dirección:** {{address}}
{{/address}}
{{#url}}
🌐 **Sitio web:** {{url}}
{{/url}}
🔗 **Ver en el mapa:** https://www.google.com/maps?q={{degreesLatitude}},{{degreesLongitude}}
{{#caption}}{{caption}}{{/caption}}
{{/locationMessage}}
{{#liveLocationMessage}}
📍📡 **Compartiendo ubicación en tiempo real**
Abre **WhatsApp** para ver las actualizaciones en tiempo real.
{{#name}}
**{{name}}**
{{/name}}
{{#address}}
📍 **Dirección:** {{address}}
{{/address}}
{{#url}}
🌐 **Sitio web:** {{url}}
{{/url}}
🔗 **Ver en el mapa:** https://www.google.com/maps?q={{degreesLatitude}},{{degreesLongitude}}
{{#caption}}{{caption}}{{/caption}}
{{/liveLocationMessage}}
{{/message}}
whatsapp.to.chatwoot.message.poll: |-
{{#poll}}
📊 **Encuesta**
**{{name}}**
{{#options}}
- {{optionName}}
{{/options}}
{{/poll}}
whatsapp.to.chatwoot.message.list: |-
{{#message.listMessage}}
{{#title}}
*{{{title}}}*
{{/title}}
{{#description}}
{{{description}}}
{{/description}}
{{#footerText}}
{{{footerText}}}
{{/footerText}}
{{#buttonText}}
{{{buttonText}}}
{{/buttonText}}
{{#sections}}
{{#title}}
{{{title}}}:
{{/title}}
{{#rows}}
{{#title}}
- {{{title}}}
{{/title}}
{{/rows}}
{{/sections}}
{{/message.listMessage}}
whatsapp.to.chatwoot.message.event: |-
{{#message}}
{{#eventMessage}}
📅 **Evento**
**{{name}}**
{{#description}}
{{description}}
{{/description}}
{{#location.name}}
📍 **Ubicación:** {{location.name}}
{{/location.name}}
{{#startTime}}
🕐 **Inicio:** {{startTime}}
{{/startTime}}
{{#endTime}}
🕐 **Fin:** {{endTime}}
{{/endTime}}
{{#joinLink}}
🔗 **Enlace para unirse:** [{{joinLink}}]({{joinLink}})
{{/joinLink}}
{{/eventMessage}}
{{/message}}
whatsapp.to.chatwoot.message.pix: |-
💳 **PIX - Copiar y Pegar**
{{#pixData}}
{{#merchantName}}
**{{merchantName}}**
{{/merchantName}}
{{#key}}
**Clave PIX:** {{key}}
{{/key}}
{{#keyType}}
**Tipo:** {{keyType}}
{{/keyType}}
{{#totalAmount}}
**Importe:** {{totalAmountFormatted}}
{{/totalAmount}}
{{#referenceId}}
**Referencia:** {{referenceId}}
{{/referenceId}}
💳 **PIX Copiar y Pegar enviado**
{{/pixData}}
whatsapp.to.chatwoot.message.unsupported: |-
⚠️ **Este tipo de mensaje no es compatible con ChatWoot.**
📱 Por favor, abre **WhatsApp** para verlo.
Detalles: [{{{details.text}}}]({{{details.url}}})
whatsapp.to.chatwoot.message.facebook.ad: |-
📢 **ANUNCIO DE FACEBOOK**
**Título:** {{{adData.title}}}
**Descripción:**
{{{adData.body}}}
**Enlace del anuncio:** [Ver en Facebook]({{{adData.sourceUrl}}})
**ID del anuncio:** {{{adData.sourceId}}}
---
💬 **Mensaje del cliente:**
{{{payload.body}}}
@@ -1,8 +1,29 @@
# Persian translations for ChatWoot integration
LOCALE_NAME: '🇮🇷 فارسی (Persian)'
APP_INBOX_CONTACT_NAME: 'ادغام واتساپ (WAHA)'
locale.name: '🇮🇷 فارسی (Persian)'
JOB_ERROR_REPORT: |
# کد محلی پایه - باید یک محلی معتبر BCP 47 باشد
# https://en.wikipedia.org/wiki/IETF_language_tag
# WAHA از آن برای نمایش اعداد، ارز و تاریخ/زمان استفاده می‌کند
locale.base: 'fa-IR'
app.inbox.contact.name: 'ادغام واتساپ'
app.inbox.contact.avatar.url: 'https://raw.githubusercontent.com/devlikeapro/waha/core/logo.png'
# گزینه‌های "options" از date.toLocaleString
# https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat#options
datetime:
locales: fa-IR
day: 2-digit
month: 2-digit
year: numeric
hour: 2-digit
minute: 2-digit
# https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat#timezonename
timeZoneName: shortGeneric
# https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
# timeZone: 'Asia/Tehran'
job.report.error: |-
⚠️ **{{{header}}}**
{{#attempts.nextDelay}}
🔄 تلاش مجدد در {{attempts.nextDelay}} ثانیه...
@@ -19,51 +40,52 @@ JOB_ERROR_REPORT: |
```
{{/error}}
JOB_SUCCEEDED_REPORT: |
job.report.succeeded: |-
✅ **موفقیت‌آمیز**
تلاش: {{{attempts.current}}} / {{{attempts.max}}}
جزئیات: [{{{details.text}}}]({{{details.url}}})
WHATSAPP_ERROR_SENDING_MESSAGE: ارسال پیام در واتساپ امکان‌پذیر نبود.
WHATSAPP_ERROR_RECEIVING_MESSAGE: دریافت پیام از واتساپ امکان‌پذیر نبود
WHATSAPP_ERROR_REMOVING_MESSAGE: حذف پیام از واتساپ امکان‌پذیر نبود
APP_SCHEDULED_JOB_ERROR: اجرای کار زمان‌بندی شده ناموفق بود
whatsapp.message.sending.error: ارسال پیام در واتساپ امکان‌پذیر نبود.
whatsapp.message.receiving.error: دریافت پیام از واتساپ امکان‌پذیر نبود
whatsapp.message.removing.error: حذف پیام از واتساپ امکان‌پذیر نبود
job.scheduled.error.header: اجرای کار زمان‌بندی شده ناموفق بود
MESSAGE_FROM_WHATSAPP: |
message.from.whatsapp: |-
{{{text}}}
📱 *ارسال شده از واتساپ*
MESSAGE_FROM_API: |
message.from.api: |-
{{{text}}}
📱 *ارسال شده از طریق API*
MESSAGE_REMOVED_IN_WHATSAPP: |
message.removed.in.whatsapp: |-
❌ *این پیام در واتساپ حذف شده است*
MESSAGE_EDITED_IN_WHATSAPP: |
message.edited.in.whatsapp: |-
{{{text}}}
✏️ *ویرایش شده در واتساپ*
WHATSAPP_GROUP_MESSAGE: |
{{{text}}}
whatsapp.group.message: |-
👥 *{{{participant}}}*
WHATSAPP_REACTION_ADDED: |
{{{text}}}
whatsapp.reaction.added: |-
*واکنش* {{{emoji}}}
WHATSAPP_REACTION_REMOVED: |
whatsapp.reaction.removed: |-
*واکنش حذف شد*
WHATSAPP_CONTACT_GROUP_SUFFIX: 'گروه'
WHATSAPP_CONTACT_CHANNEL_SUFFIX: 'کانال'
whatsapp.contact.group.suffix: 'گروه'
whatsapp.contact.channel.suffix: 'کانال'
whatsapp.contact.broadcast.suffix: 'فهرست انتشار'
# Aka Stories
WHATSAPP_CONTACT_STATUS_NAME: 'وضعیت'
whatsapp.contact.status.name: 'وضعیت'
APP_CONNECTED_MESSAGE: |
app.connected.message: |-
🔗 **متصل: جلسه واتساپ به صندوق ورودی ChatWoot**
✅ جلسه واتساپ **{{{name}}}** با موفقیت به این صندوق ورودی ChatWoot متصل شده است.
@@ -81,7 +103,7 @@ APP_CONNECTED_MESSAGE: |
بیایید شروع کنیم! 🚀
APP_DISCONNECTED_MESSAGE: |
app.disconnected.message: |-
🔌 **قطع اتصال: جلسه واتساپ از صندوق ورودی ChatWoot**
❌ جلسه واتساپ **{{{name}}}** از این صندوق ورودی ChatWoot قطع شده است.
@@ -90,15 +112,15 @@ APP_DISCONNECTED_MESSAGE: |
برای اتصال مجدد، لطفاً با مدیر خود تماس بگیرید.
APP_UPDATED_MESSAGE: |
app.updated.message: |-
🔄 **به‌روزرسانی: تنظیمات جلسه واتساپ**
✅ تنظیمات جلسه واتساپ **{{{name}}}** به‌روزرسانی شده است.
APP_COMMANDS_LIST: |
app.commands.list: |-
شما می‌توانید **ادغام واتساپ** را با ارسال یکی از دستورات زیر مدیریت کنید:
🖥️ جلسات:
🖥️ **جلسه**:
1. **status** - دریافت وضعیت فعلی جلسه.
2. **restart** - راه‌اندازی مجدد جلسه - این تلاش خواهد کرد حساب واتساپ شما را مجدداً متصل کند.
3. **start** - شروع جلسه.
@@ -108,42 +130,47 @@ APP_COMMANDS_LIST: |
7. **screenshot** - دریافت اسکرین‌شات از جلسه (فقط در موتور **WEBJS** در دسترس است).
8. **help** - دستورات موجود.
{{#commands.server}}
⚙️ سرور:
- **server status** - دریافت اطلاعات نسخه و وضعیت سرور.
- **server reboot** - راه‌اندازی مجدد سرور به صورت نرم.
- **server reboot force** - راه‌اندازی مجدد اجباری سرور.
{{/commands.server}}
یک **دستور** (بدون حساسیت به بزرگی و کوچکی حروف) یا **شماره دستور** را به این گفتگو ارسال کنید.
APP_HELP_REMINDER: |
app.commands.server.disabled: ⛔ **دستورات سرور غیرفعال است**.
لطفاً با مدیر سیستم خود تماس بگیرید.
برای دریافت فهرست دستورات، **help** را ارسال کنید.
app.help.reminder: |-
برای دریافت لیست دستورات موجود، **help** را ارسال کنید.
APP_SESSION_STATUS_CHANGE: |
app.session.status.change: |-
{{{emoji}}} جلسه "{{{session}}}" به وضعیت {{{status}}} تغییر کرد
APP_SESSION_CURRENT_STATUS: |
app.session.current.status: |-
{{{emoji}}} وضعیت جلسه **{{{session}}}** در **{{{status}}}** است
**حساب واتساپ:**
📱 **{{{name}}} ({{{id}}})**
APP_DASHBOARD_LINK: |
برای جزئیات بیشتر به [داشبورد WAHA]({{{url}}}) مراجعه کنید.
APP_SESSION_STATUS_WORKING: |
app.session.status.working: |-
✅ این صندوق ورودی ChatWoot به **حساب واتساپ** متصل است و آماده ارسال و دریافت پیام است!
📱 **{{{name}}} ({{{id}}})**
APP_SESSION_STATUS_ERROR: |
app.session.status.error: |-
⚠️ **این صندوق ورودی ChatWoot به حساب واتساپ متصل است اما آماده ارسال و دریافت پیام نیست!**
لطفاً وضعیت جلسه را بررسی کنید و سعی کنید آن را مجدداً راه‌اندازی کنید یا خارج شوید و کد QR را دوباره اسکن کنید.
APP_SESSION_SCAN_QR_CODE: |
app.session.scan.qr.code: |-
⚠️ **کد QR را در تلفن خود اسکن کنید تا حساب واتساپ را با این صندوق ورودی ChatWoot پیوند دهید!**
APP_SERVER_VERSION_AND_STATUS: |
app.server.version.and.status: |-
**نسخه:**
```
{{{version}}}
@@ -154,16 +181,16 @@ APP_SERVER_VERSION_AND_STATUS: |
{{{status}}}
```
APP_SERVER_REBOOT: |
app.server.reboot: |-
سرور در حال راه‌اندازی مجدد نرم است...
APP_SERVER_REBOOT_FORCE: |
app.server.reboot.force: |-
سرور در حال راه‌اندازی مجدد اجباری است...
APP_LOGOUT_SUCCESS: |
app.logout.success: |-
✅ حساب با موفقیت غیرمجاز شد!
APP_NEW_VERSION_AVAILABLE: |
waha.new.version.available: |-
🔄 **نسخه جدید در دسترس است!**
نسخه فعلی: **{{{currentVersion}}}**
@@ -171,7 +198,7 @@ APP_NEW_VERSION_AVAILABLE: |
👉 موارد جدید را در [**🆕 تغییرات**]({{{changelogUrl}}}) مشاهده کنید
APP_CORE_VERSION_USED: |
waha.core.version.used: |-
❤️ **از WAHA پشتیبانی کنید!**
شما از نسخه رایگان **WAHA Core** استفاده می‌کنید. اگر این پروژه را مفید می‌دانید، لطفاً حمایت از توسعه آن را در نظر بگیرید.
@@ -179,3 +206,177 @@ APP_CORE_VERSION_USED: |
حمایت شما تنها راهی است که ما می‌توانیم به کار بر روی به‌روزرسانی‌ها، رفع اشکالات و ویژگی‌های جدید ادامه دهیم.
[**🎁 از ما حمایت کنید**]({{{supportUrl}}})
chatwoot.to.whatsapp.message.text: |-
{{{ content }}}
chatwoot.to.whatsapp.message.media.caption: |-
{{#singleAttachment}}
{{#content}}
{{{ content }}}
{{/content}}
{{/singleAttachment}}
whatsapp.to.chatwoot.message: |-
{{{payload.body}}}
whatsapp.to.chatwoot.message.contacts: |-
👤 **مخاطب(ها)ی به‌اشتراک‌گذاشته‌شده**
{{#contacts}}
🪪 نام: **{{fullName}}**
{{#phoneNumbers}}
- 📞 تلفن: **{{.}}**
{{/phoneNumbers}}
{{/contacts}}
whatsapp.to.chatwoot.message.location: |-
{{#message}}
{{#locationMessage}}
📍 **موقعیت مکانی به‌اشتراک‌گذاشته‌شده**
{{#name}}
**{{name}}**
{{/name}}
{{#address}}
📍 **آدرس:** {{address}}
{{/address}}
{{#url}}
🌐 **وب‌سایت:** {{url}}
{{/url}}
🔗 **مشاهده روی نقشه:** https://www.google.com/maps?q={{degreesLatitude}},{{degreesLongitude}}
{{#caption}}{{caption}}{{/caption}}
{{/locationMessage}}
{{#liveLocationMessage}}
📍📡 **اشتراک‌گذاری موقعیت مکانی زنده**
برای دیدن به‌روزرسانی‌های لحظه‌ای **WhatsApp** را باز کنید.
{{#name}}
**{{name}}**
{{/name}}
{{#address}}
📍 **آدرس:** {{address}}
{{/address}}
{{#url}}
🌐 **وب‌سایت:** {{url}}
{{/url}}
🔗 **مشاهده روی نقشه:** https://www.google.com/maps?q={{degreesLatitude}},{{degreesLongitude}}
{{#caption}}{{caption}}{{/caption}}
{{/liveLocationMessage}}
{{/message}}
whatsapp.to.chatwoot.message.poll: |-
{{#poll}}
📊 **نظرسنجی**
**{{name}}**
{{#options}}
- {{optionName}}
{{/options}}
{{/poll}}
whatsapp.to.chatwoot.message.list: |-
{{#message.listMessage}}
{{#title}}
*{{{title}}}*
{{/title}}
{{#description}}
{{{description}}}
{{/description}}
{{#footerText}}
{{{footerText}}}
{{/footerText}}
{{#buttonText}}
{{{buttonText}}}
{{/buttonText}}
{{#sections}}
{{#title}}
{{{title}}}:
{{/title}}
{{#rows}}
{{#title}}
- {{{title}}}
{{/title}}
{{/rows}}
{{/sections}}
{{/message.listMessage}}
whatsapp.to.chatwoot.message.event: |-
{{#message}}
{{#eventMessage}}
📅 **رویداد**
**{{name}}**
{{#description}}
{{description}}
{{/description}}
{{#location.name}}
📍 **مکان:** {{location.name}}
{{/location.name}}
{{#startTime}}
🕐 **شروع:** {{startTime}}
{{/startTime}}
{{#endTime}}
🕐 **پایان:** {{endTime}}
{{/endTime}}
{{#joinLink}}
🔗 **لینک پیوستن:** [{{joinLink}}]({{joinLink}})
{{/joinLink}}
{{/eventMessage}}
{{/message}}
whatsapp.to.chatwoot.message.pix: |-
💳 **PIX - کپی و چسباندن**
{{#pixData}}
{{#merchantName}}
**{{merchantName}}**
{{/merchantName}}
{{#key}}
**کلید PIX:** {{key}}
{{/key}}
{{#keyType}}
**نوع:** {{keyType}}
{{/keyType}}
{{#totalAmount}}
**مبلغ:** {{totalAmountFormatted}}
{{/totalAmount}}
{{#referenceId}}
**شناسه مرجع:** {{referenceId}}
{{/referenceId}}
💳 **PIX کپی و چسباندن ارسال شد**
{{/pixData}}
whatsapp.to.chatwoot.message.unsupported: |-
⚠️ **این نوع پیام در ChatWoot پشتیبانی نمی‌شود.**
📱 لطفاً برای مشاهده آن **WhatsApp** را باز کنید.
جزئیات: [{{{details.text}}}]({{{details.url}}})
whatsapp.to.chatwoot.message.facebook.ad: |-
📢 **آگهی فیس‌بوک**
**عنوان:** {{{adData.title}}}
**توضیحات:**
{{{adData.body}}}
**لینک آگهی:** [مشاهده در فیس‌بوک]({{{adData.sourceUrl}}})
**شناسه آگهی:** {{{adData.sourceId}}}
---
💬 **پیام مشتری:**
{{{payload.body}}}
+382
View File
@@ -0,0 +1,382 @@
# French translations for ChatWoot integration
locale.name: '🇫🇷 Français (French)'
# Code de locale de base - doit être une locale BCP 47 valide
# https://en.wikipedia.org/wiki/IETF_language_tag
# WAHA l'utilise pour le formatage des nombres, devises et dates/heures
locale.base: 'fr-FR'
app.inbox.contact.name: 'Intégration WhatsApp'
app.inbox.contact.avatar.url: 'https://raw.githubusercontent.com/devlikeapro/waha/core/logo.png'
# "options" de date.toLocaleString
# https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat#options
datetime:
locales: fr-FR
day: 2-digit
month: 2-digit
year: numeric
hour: 2-digit
minute: 2-digit
# https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat#timezonename
timeZoneName: shortGeneric
# https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
# timeZone: 'Europe/Paris'
job.report.error: |-
⚠️ **{{{header}}}**
{{#attempts.nextDelay}}
🔄 Nouvelle tentative dans {{attempts.nextDelay}} secondes...
{{/attempts.nextDelay}}
{{^attempts.nextDelay}}
❌ Plus de tentatives restantes. Veuillez réessayer plus tard.
{{/attempts.nextDelay}}
Tentative : {{{attempts.current}}} / {{{attempts.max}}}
Détails : [{{{details.text}}}]({{{details.url}}})
{{#error}}
```
{{{error}}}
```
{{/error}}
job.report.succeeded: |-
✅ **Réussi**
Tentative : {{{attempts.current}}} / {{{attempts.max}}}
Détails : [{{{details.text}}}]({{{details.url}}})
whatsapp.message.sending.error: Impossible d'envoyer un message sur WhatsApp.
whatsapp.message.receiving.error: Impossible de recevoir un message de WhatsApp
whatsapp.message.removing.error: Impossible de supprimer un message de WhatsApp
job.scheduled.error.header: La tâche planifiée n'a pas pu être exécutée
message.from.whatsapp: |-
{{{text}}}
📱 *Envoyé depuis WhatsApp*
message.from.api: |-
{{{text}}}
📱 *Envoyé via API*
message.removed.in.whatsapp: |-
❌ *Ce message a été supprimé dans WhatsApp*
message.edited.in.whatsapp: |-
{{{text}}}
✏️ *Modifié dans WhatsApp*
whatsapp.group.message: |-
👥 *{{{participant}}}*
{{{text}}}
whatsapp.reaction.added: |-
*A réagi avec* {{{emoji}}}
whatsapp.reaction.removed: |-
*Réaction supprimée*
whatsapp.contact.group.suffix: 'Groupe'
whatsapp.contact.channel.suffix: 'Canal'
whatsapp.contact.broadcast.suffix: 'Liste de diffusion'
# Aka Stories
whatsapp.contact.status.name: 'Statut'
app.connected.message: |-
🔗 **CONNECTÉ : Session WhatsApp à la Boîte de Réception ChatWoot**
✅ La session WhatsApp **{{{name}}}** a été connectée avec succès à cette Boîte de Réception ChatWoot.
Cette boîte de réception est maintenant liée à votre **Compte WhatsApp**. Vous pouvez envoyer et recevoir des messages directement d'ici — tout comme utiliser WhatsApp, mais à l'intérieur de ChatWoot.
Vous pouvez également gérer la session WhatsApp liée à cette boîte de réception. Pour voir les actions disponibles, envoyez **help** à ce chat.
📋 Actions courantes que vous pouvez effectuer :
- Vérifier si la session WhatsApp est active
- Redémarrer ou Arrêter la session WhatsApp
- Se déconnecter et se reconnecter en scannant un nouveau code QR
Si tout fonctionne, vous pourrez discuter librement. Si vous voyez des avertissements, vous devrez peut-être reconnecter votre compte WhatsApp.
Commençons ! 🚀
app.disconnected.message: |-
🔌 **DÉCONNECTÉ : Session WhatsApp de la Boîte de Réception ChatWoot**
❌ La session WhatsApp **{{{name}}}** a été déconnectée de cette Boîte de Réception ChatWoot.
Vous ne pourrez plus envoyer ou recevoir de messages depuis WhatsApp.
Pour vous reconnecter, veuillez contacter votre administrateur.
app.updated.message: |-
🔄 **MIS À JOUR : Paramètres de Session WhatsApp**
✅ Les paramètres de la session WhatsApp **{{{name}}}** ont été mis à jour.
app.commands.list: |-
Vous pouvez gérer l'**Intégration WhatsApp** en envoyant l'une des commandes suivantes :
🖥️ **Session** :
1. **status** - obtenir l'état actuel de la session.
2. **restart** - redémarrer la session - cela tentera de reconnecter votre compte WhatsApp.
3. **start** - démarrer la session.
4. **stop** - arrêter la session.
5. **logout** - se déconnecter de la session, pour pouvoir scanner le code QR et réparer la session.
6. **qr** - obtenir le code QR pour la session.
7. **screenshot** - obtenir une capture d'écran de la session (disponible uniquement dans le moteur **WEBJS**).
8. **help** - commandes disponibles.
{{#commands.server}}
⚙️ Serveur :
- **server status** - obtenir les informations de version et d'état du serveur.
- **server reboot** - redémarrer le serveur en douceur.
- **server reboot force** - forcer le redémarrage du serveur.
{{/commands.server}}
Envoyez une **commande** (insensible à la casse) ou un **numéro de commande** à cette conversation.
app.commands.server.disabled: ⛔ **Les commandes serveur sont désactivées**.
Veuillez contacter votre administrateur.
Envoyez **help** pour obtenir la liste des commandes disponibles.
app.help.reminder: |-
Envoyez **help** pour obtenir la liste des commandes disponibles.
app.session.status.change: |-
{{{emoji}}} La session "{{{session}}}" est passée au statut {{{status}}}
app.session.current.status: |-
{{{emoji}}} Le statut de la session **{{{session}}}** est **{{{status}}}**
**Compte WhatsApp :**
📱 **{{{name}}} ({{{id}}})**
app.session.status.working: |-
✅ Cette Boîte de Réception ChatWoot est liée au **Compte WhatsApp** et prête à envoyer et recevoir des messages !
📱 **{{{name}}} ({{{id}}})**
app.session.status.error: |-
⚠️ **Cette Boîte de Réception ChatWoot est liée au Compte WhatsApp mais N'EST PAS prête à envoyer et recevoir des messages !**
Veuillez vérifier l'état de la session et essayer de la redémarrer ou déconnectez-vous et scannez à nouveau le code QR.
app.session.scan.qr.code: |-
⚠️ **Scannez le code QR sur votre téléphone pour lier le Compte WhatsApp à cette Boîte de Réception ChatWoot !**
app.server.version.and.status: |-
**Version :**
```
{{{version}}}
```
**Statut :**
```
{{{status}}}
```
app.server.reboot: |-
Le serveur redémarre en douceur...
app.server.reboot.force: |-
Le serveur redémarre de force...
app.logout.success: |-
✅ Compte déconnecté avec succès !
waha.new.version.available: |-
🔄 **Nouvelle Version Disponible !**
Version actuelle : **{{{currentVersion}}}**
Nouvelle version : **{{{newVersion}}}**
👉 Découvrez les nouveautés dans le [**🆕 Journal des modifications**]({{{changelogUrl}}})
waha.core.version.used: |-
❤️ **Soutenez WAHA !**
Vous utilisez la version gratuite **WAHA Core**. Si vous trouvez ce projet utile, veuillez envisager de soutenir son développement.
Votre soutien est le seul moyen pour nous de continuer à travailler sur les mises à jour, les corrections et les nouvelles fonctionnalités.
[**🎁 Soutenez-nous**]({{{supportUrl}}})
chatwoot.to.whatsapp.message.text: |-
{{{ content }}}
chatwoot.to.whatsapp.message.media.caption: |-
{{#singleAttachment}}
{{#content}}
{{{ content }}}
{{/content}}
{{/singleAttachment}}
whatsapp.to.chatwoot.message: |-
{{{payload.body}}}
whatsapp.to.chatwoot.message.contacts: |-
👤 **Contact(s) partagé(s)**
{{#contacts}}
🪪 Nom: **{{fullName}}**
{{#phoneNumbers}}
- 📞 Téléphone: **{{.}}**
{{/phoneNumbers}}
{{/contacts}}
whatsapp.to.chatwoot.message.location: |-
{{#message}}
{{#locationMessage}}
📍 **Position partagée**
{{#name}}
**{{name}}**
{{/name}}
{{#address}}
📍 **Adresse :** {{address}}
{{/address}}
{{#url}}
🌐 **Site web :** {{url}}
{{/url}}
🔗 **Voir sur la carte :** https://www.google.com/maps?q={{degreesLatitude}},{{degreesLongitude}}
{{#caption}}{{caption}}{{/caption}}
{{/locationMessage}}
{{#liveLocationMessage}}
📍📡 **Partage de position en direct**
Ouvrez **WhatsApp** pour voir les mises à jour en temps réel.
{{#name}}
**{{name}}**
{{/name}}
{{#address}}
📍 **Adresse :** {{address}}
{{/address}}
{{#url}}
🌐 **Site web :** {{url}}
{{/url}}
🔗 **Voir sur la carte :** https://www.google.com/maps?q={{degreesLatitude}},{{degreesLongitude}}
{{#caption}}{{caption}}{{/caption}}
{{/liveLocationMessage}}
{{/message}}
whatsapp.to.chatwoot.message.poll: |-
{{#poll}}
📊 **Sondage**
**{{name}}**
{{#options}}
- {{optionName}}
{{/options}}
{{/poll}}
whatsapp.to.chatwoot.message.list: |-
{{#message.listMessage}}
{{#title}}
*{{{title}}}*
{{/title}}
{{#description}}
{{{description}}}
{{/description}}
{{#footerText}}
{{{footerText}}}
{{/footerText}}
{{#buttonText}}
{{{buttonText}}}
{{/buttonText}}
{{#sections}}
{{#title}}
{{{title}}}:
{{/title}}
{{#rows}}
{{#title}}
- {{{title}}}
{{/title}}
{{/rows}}
{{/sections}}
{{/message.listMessage}}
whatsapp.to.chatwoot.message.event: |-
{{#message}}
{{#eventMessage}}
📅 **Événement**
**{{name}}**
{{#description}}
{{description}}
{{/description}}
{{#location.name}}
📍 **Lieu :** {{location.name}}
{{/location.name}}
{{#startTime}}
🕐 **Début :** {{startTime}}
{{/startTime}}
{{#endTime}}
🕐 **Fin :** {{endTime}}
{{/endTime}}
{{#joinLink}}
🔗 **Lien de participation :** [{{joinLink}}]({{joinLink}})
{{/joinLink}}
{{/eventMessage}}
{{/message}}
whatsapp.to.chatwoot.message.pix: |-
💳 **PIX - Copier et Coller**
{{#pixData}}
{{#merchantName}}
**{{merchantName}}**
{{/merchantName}}
{{#key}}
**Clé PIX :** {{key}}
{{/key}}
{{#keyType}}
**Type :** {{keyType}}
{{/keyType}}
{{#totalAmount}}
**Montant :** {{totalAmountFormatted}}
{{/totalAmount}}
{{#referenceId}}
**Référence :** {{referenceId}}
{{/referenceId}}
💳 **PIX Copier et Coller envoyé**
{{/pixData}}
whatsapp.to.chatwoot.message.unsupported: |-
⚠️ **Ce type de message n’est pas pris en charge dans ChatWoot.**
📱 Veuillez ouvrir **WhatsApp** pour l’afficher.
Détails: [{{{details.text}}}]({{{details.url}}})
whatsapp.to.chatwoot.message.facebook.ad: |-
📢 **PUBLICITÉ FACEBOOK**
**Titre:** {{{adData.title}}}
**Description:**
{{{adData.body}}}
**Lien de l'annonce :** [Voir sur Facebook]({{{adData.sourceUrl}}})
**ID de l'annonce :** {{{adData.sourceId}}}
---
💬 **Message du client :**
{{{payload.body}}}
+382
View File
@@ -0,0 +1,382 @@
# Hebrew translations for ChatWoot integration
locale.name: '🇮🇱 עברית (Hebrew)'
# קוד הלוקאל הבסיסי - חייב להיות לוקאל תקין לפי BCP 47
# https://en.wikipedia.org/wiki/IETF_language_tag
# WAHA משתמש בכך להצגת מספרים, מטבע ותאריך/שעה
locale.base: 'he-IL'
app.inbox.contact.name: 'אינטגרציית וואטסאפ'
app.inbox.contact.avatar.url: 'https://raw.githubusercontent.com/devlikeapro/waha/core/logo.png'
# "אפשרויות" של date.toLocaleString
# https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat#options
datetime:
locales: he-IL
day: 2-digit
month: 2-digit
year: numeric
hour: 2-digit
minute: 2-digit
# https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat#timezonename
timeZoneName: shortGeneric
# https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
# timeZone: 'Asia/Jerusalem'
job.report.error: |-
⚠️ **{{{header}}}**
{{#attempts.nextDelay}}
🔄 מנסה שוב בעוד {{attempts.nextDelay}} שניות...
{{/attempts.nextDelay}}
{{^attempts.nextDelay}}
❌ לא נותרו ניסיונות נוספים. אנא נסה שוב מאוחר יותר.
{{/attempts.nextDelay}}
ניסיון: {{{attempts.current}}} / {{{attempts.max}}}
פרטים: [{{{details.text}}}]({{{details.url}}})
{{#error}}
```
{{{error}}}
```
{{/error}}
job.report.succeeded: |-
✅ **הצליח**
ניסיון: {{{attempts.current}}} / {{{attempts.max}}}
פרטים: [{{{details.text}}}]({{{details.url}}})
whatsapp.message.sending.error: לא ניתן לשלוח הודעה בוואטסאפ.
whatsapp.message.receiving.error: לא ניתן לקבל הודעה מוואטסאפ
whatsapp.message.removing.error: לא ניתן להסיר הודעה מוואטסאפ
job.scheduled.error.header: המשימה המתוזמנת נכשלה בביצוע
message.from.whatsapp: |-
{{{text}}}
📱 *נשלח מוואטסאפ*
message.from.api: |-
{{{text}}}
📱 *נשלח דרך API*
message.removed.in.whatsapp: |-
❌ *הודעה זו נמחקה בוואטסאפ*
message.edited.in.whatsapp: |-
{{{text}}}
✏️ *נערך בוואטסאפ*
whatsapp.group.message: |-
👥 *{{{participant}}}*
{{{text}}}
whatsapp.reaction.added: |-
*הגיב* {{{emoji}}}
whatsapp.reaction.removed: |-
*התגובה הוסרה*
whatsapp.contact.group.suffix: 'קבוצה'
whatsapp.contact.channel.suffix: 'ערוץ'
whatsapp.contact.broadcast.suffix: 'רשימת שידור'
# Aka Stories
whatsapp.contact.status.name: 'סטטוס'
app.connected.message: |-
🔗 **מחובר: הפעלת וואטסאפ לתיבת הדואר הנכנס של ChatWoot**
✅ הפעלת וואטסאפ **{{{name}}}** חוברה בהצלחה לתיבת הדואר הנכנס של ChatWoot.
תיבת דואר נכנס זו מקושרת כעת ל**חשבון הוואטסאפ** שלך. אתה יכול לשלוח ולקבל הודעות ישירות מכאן — בדיוק כמו שימוש בוואטסאפ, אבל בתוך ChatWoot.
אתה יכול גם לנהל את הפעלת הוואטסאפ המקושרת לתיבת דואר נכנס זו. כדי לראות פעולות זמינות, שלח **help** לצ'אט זה.
📋 פעולות נפוצות שאתה יכול לעשות:
- בדוק אם הפעלת הוואטסאפ פעילה
- הפעל מחדש או עצור את הפעלת הוואטסאפ
- התנתק והתחבר מחדש על ידי סריקת קוד QR חדש
אם הכל עובד, תוכל לשוחח בחופשיות. אם אתה רואה אזהרות, ייתכן שתצטרך לחבר מחדש את חשבון הוואטסאפ שלך.
בוא נתחיל! 🚀
app.disconnected.message: |-
🔌 **מנותק: הפעלת וואטסאפ מתיבת הדואר הנכנס של ChatWoot**
❌ הפעלת וואטסאפ **{{{name}}}** נותקה מתיבת הדואר הנכנס של ChatWoot.
לא תוכל יותר לשלוח או לקבל הודעות מוואטסאפ.
כדי להתחבר מחדש, אנא פנה למנהל המערכת שלך.
app.updated.message: |-
🔄 **עודכן: הגדרות הפעלת וואטסאפ**
✅ ההגדרות עבור הפעלת וואטסאפ **{{{name}}}** עודכנו.
app.commands.list: |-
אתה יכול לנהל **אינטגרציית וואטסאפ** על ידי שליחת אחת מהפקודות הבאות:
🖥️ **הפעלה**:
1. **status** - קבל את סטטוס ההפעלה הנוכחי.
2. **restart** - הפעל מחדש את ההפעלה - זה ינסה לחבר מחדש את חשבון הוואטסאפ שלך.
3. **start** - התחל את ההפעלה.
4. **stop** - עצור את ההפעלה.
5. **logout** - התנתק מההפעלה, כך שתוכל לסרוק קוד QR ולתקן את ההפעלה.
6. **qr** - קבל את קוד ה-QR עבור ההפעלה.
7. **screenshot** - קבל צילום מסך של ההפעלה (זמין רק במנוע **WEBJS**).
8. **help** - פקודות זמינות.
{{#commands.server}}
⚙️ שרת:
- **server status** - קבל מידע על גרסת השרת והסטטוס.
- **server reboot** - הפעל מחדש את השרת בצורה חלקה.
- **server reboot force** - אלץ הפעלה מחדש של השרת.
{{/commands.server}}
שלח **פקודה** (לא תלוי רישיות) או **מספר פקודה** לשיחה זו.
app.commands.server.disabled: ⛔ **פקודות השרת מושבתות**.
אנא פנו למנהל המערכת שלכם.
שלחו **help** כדי לקבל את רשימת הפקודות הזמינות.
app.help.reminder: |-
שלח **help** כדי לקבל את רשימת הפקודות הזמינות.
app.session.status.change: |-
{{{emoji}}} הפעלה "{{{session}}}" עברה לסטטוס {{{status}}}
app.session.current.status: |-
{{{emoji}}} סטטוס ההפעלה **{{{session}}}** הוא **{{{status}}}**
**חשבון וואטסאפ:**
📱 **{{{name}}} ({{{id}}})**
app.session.status.working: |-
✅ תיבת הדואר הנכנס של ChatWoot מקושרת ל**חשבון וואטסאפ** ומוכנה לשלוח ולקבל הודעות!
📱 **{{{name}}} ({{{id}}})**
app.session.status.error: |-
⚠️ **תיבת הדואר הנכנס של ChatWoot מקושרת לחשבון וואטסאפ אך אינה מוכנה לשלוח ולקבל הודעות!**
אנא בדוק את סטטוס ההפעלה ונסה להפעיל אותה מחדש או התנתק וסרוק את קוד ה-QR שוב.
app.session.scan.qr.code: |-
⚠️ **סרוק את קוד ה-QR בטלפון שלך כדי לקשר את חשבון הוואטסאפ לתיבת הדואר הנכנס של ChatWoot!**
app.server.version.and.status: |-
**גרסה:**
```
{{{version}}}
```
**סטטוס:**
```
{{{status}}}
```
app.server.reboot: |-
השרת מופעל מחדש בצורה חלקה...
app.server.reboot.force: |-
השרת מופעל מחדש בכוח...
app.logout.success: |-
✅ החשבון בוטל בהצלחה!
waha.new.version.available: |-
🔄 **גרסה חדשה זמינה!**
גרסה נוכחית: **{{{currentVersion}}}**
גרסה חדשה: **{{{newVersion}}}**
👉 בדוק מה חדש ב[**🆕 יומן שינויים**]({{{changelogUrl}}})
waha.core.version.used: |-
❤️ **תמוך ב-WAHA!**
אתה משתמש בגרסת **WAHA Core** החינמית. אם אתה מוצא פרויקט זה שימושי, אנא שקול לתמוך בפיתוח שלו.
התמיכה שלך היא הדרך היחידה שבה אנחנו יכולים להמשיך לעבוד על עדכונים, תיקונים ותכונות חדשות.
[**🎁 תמוך בנו**]({{{supportUrl}}})
chatwoot.to.whatsapp.message.text: |-
{{{ content }}}
chatwoot.to.whatsapp.message.media.caption: |-
{{#singleAttachment}}
{{#content}}
{{{ content }}}
{{/content}}
{{/singleAttachment}}
whatsapp.to.chatwoot.message: |-
{{{payload.body}}}
whatsapp.to.chatwoot.message.contacts: |-
👤 **אנשי קשר משותפים**
{{#contacts}}
🪪 שם: **{{fullName}}**
{{#phoneNumbers}}
- 📞 טלפון: **{{.}}**
{{/phoneNumbers}}
{{/contacts}}
whatsapp.to.chatwoot.message.location: |-
{{#message}}
{{#locationMessage}}
📍 **מיקום משותף**
{{#name}}
**{{name}}**
{{/name}}
{{#address}}
📍 **כתובת:** {{address}}
{{/address}}
{{#url}}
🌐 **אתר אינטרנט:** {{url}}
{{/url}}
🔗 **הצג במפה:** https://www.google.com/maps?q={{degreesLatitude}},{{degreesLongitude}}
{{#caption}}{{caption}}{{/caption}}
{{/locationMessage}}
{{#liveLocationMessage}}
📍📡 **משתף מיקום חי**
פתח/י את **WhatsApp** כדי לראות עדכונים בזמן אמת.
{{#name}}
**{{name}}**
{{/name}}
{{#address}}
📍 **כתובת:** {{address}}
{{/address}}
{{#url}}
🌐 **אתר אינטרנט:** {{url}}
{{/url}}
🔗 **הצג במפה:** https://www.google.com/maps?q={{degreesLatitude}},{{degreesLongitude}}
{{#caption}}{{caption}}{{/caption}}
{{/liveLocationMessage}}
{{/message}}
whatsapp.to.chatwoot.message.poll: |-
{{#poll}}
📊 **סקר**
**{{name}}**
{{#options}}
- {{optionName}}
{{/options}}
{{/poll}}
whatsapp.to.chatwoot.message.list: |-
{{#message.listMessage}}
{{#title}}
*{{{title}}}*
{{/title}}
{{#description}}
{{{description}}}
{{/description}}
{{#footerText}}
{{{footerText}}}
{{/footerText}}
{{#buttonText}}
{{{buttonText}}}
{{/buttonText}}
{{#sections}}
{{#title}}
{{{title}}}:
{{/title}}
{{#rows}}
{{#title}}
- {{{title}}}
{{/title}}
{{/rows}}
{{/sections}}
{{/message.listMessage}}
whatsapp.to.chatwoot.message.event: |-
{{#message}}
{{#eventMessage}}
📅 **אירוע**
**{{name}}**
{{#description}}
{{description}}
{{/description}}
{{#location.name}}
📍 **מיקום:** {{location.name}}
{{/location.name}}
{{#startTime}}
🕐 **התחלה:** {{startTime}}
{{/startTime}}
{{#endTime}}
🕐 **סיום:** {{endTime}}
{{/endTime}}
{{#joinLink}}
🔗 **קישור הצטרפות:** [{{joinLink}}]({{joinLink}})
{{/joinLink}}
{{/eventMessage}}
{{/message}}
whatsapp.to.chatwoot.message.pix: |-
💳 **PIX - העתק והדבק**
{{#pixData}}
{{#merchantName}}
**{{merchantName}}**
{{/merchantName}}
{{#key}}
**מפתח PIX:** {{key}}
{{/key}}
{{#keyType}}
**סוג:** {{keyType}}
{{/keyType}}
{{#totalAmount}}
**סכום:** {{totalAmountFormatted}}
{{/totalAmount}}
{{#referenceId}}
**אסמכתא:** {{referenceId}}
{{/referenceId}}
💳 **PIX העתק והדבק נשלח**
{{/pixData}}
whatsapp.to.chatwoot.message.unsupported: |-
⚠️ **סוג הודעה זה אינו נתמך ב-ChatWoot.**
📱 אנא פתח/י את **WhatsApp** כדי לצפות בזה.
פרטים: [{{{details.text}}}]({{{details.url}}})
whatsapp.to.chatwoot.message.facebook.ad: |-
📢 **מודעת פייסבוק**
**כותרת:** {{{adData.title}}}
**תיאור:**
{{{adData.body}}}
**קישור למודעה:** [לצפייה בפייסבוק]({{{adData.sourceUrl}}})
**מזהה המודעה:** {{{adData.sourceId}}}
---
💬 **הודעת הלקוח:**
{{{payload.body}}}
@@ -1,8 +1,29 @@
# Hindi translations for ChatWoot integration
LOCALE_NAME: '🇮🇳 हिंदी (Hindi)'
APP_INBOX_CONTACT_NAME: 'व्हाट्सएप इंटीग्रेशन (WAHA)'
locale.name: '🇮🇳 हिंदी (Hindi)'
JOB_ERROR_REPORT: |
# बेस लोकैल कोड - यह एक मान्य BCP 47 लोकैल होना चाहिए
# https://en.wikipedia.org/wiki/IETF_language_tag
# WAHA इसे संख्याएँ, मुद्रा और दिनांक/समय प्रदर्शित करने के लिए उपयोग करता है।
locale.base: 'hi-IN'
app.inbox.contact.name: 'व्हाट्सएप इंटीग्रेशन'
app.inbox.contact.avatar.url: 'https://raw.githubusercontent.com/devlikeapro/waha/core/logo.png'
# date.toLocaleString की "options"
# https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat#options
datetime:
locales: hi-IN
day: 2-digit
month: 2-digit
year: numeric
hour: 2-digit
minute: 2-digit
# https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat#timezonename
timeZoneName: shortGeneric
# https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
# timeZone: 'Asia/Kolkata'
job.report.error: |-
⚠️ **{{{header}}}**
{{#attempts.nextDelay}}
🔄 {{attempts.nextDelay}} सेकंड में पुनः प्रयास कर रहा है...
@@ -19,51 +40,52 @@ JOB_ERROR_REPORT: |
```
{{/error}}
JOB_SUCCEEDED_REPORT: |
job.report.succeeded: |-
✅ **सफल**
प्रयास: {{{attempts.current}}} / {{{attempts.max}}}
विवरण: [{{{details.text}}}]({{{details.url}}})
WHATSAPP_ERROR_SENDING_MESSAGE: व्हाट्सएप पर संदेश नहीं भेज सका।
WHATSAPP_ERROR_RECEIVING_MESSAGE: व्हाट्सएप से संदेश प्राप्त नहीं कर सका
WHATSAPP_ERROR_REMOVING_MESSAGE: व्हाट्सएप से संदेश हटा नहीं सका
APP_SCHEDULED_JOB_ERROR: निर्धारित कार्य निष्पादित करने में विफल रहा
whatsapp.message.sending.error: व्हाट्सएप पर संदेश नहीं भेज सका।
whatsapp.message.receiving.error: व्हाट्सएप से संदेश प्राप्त नहीं कर सका
whatsapp.message.removing.error: व्हाट्सएप से संदेश हटा नहीं सका
job.scheduled.error.header: निर्धारित कार्य निष्पादित करने में विफल रहा
MESSAGE_FROM_WHATSAPP: |
message.from.whatsapp: |-
{{{text}}}
📱 *व्हाट्सएप से भेजा गया*
MESSAGE_FROM_API: |
message.from.api: |-
{{{text}}}
📱 *API के माध्यम से भेजा गया*
MESSAGE_REMOVED_IN_WHATSAPP: |
message.removed.in.whatsapp: |-
❌ *यह संदेश व्हाट्सएप में हटा दिया गया था*
MESSAGE_EDITED_IN_WHATSAPP: |
message.edited.in.whatsapp: |-
{{{text}}}
✏️ *व्हाट्सएप में संपादित*
WHATSAPP_GROUP_MESSAGE: |
{{{text}}}
whatsapp.group.message: |-
👥 *{{{participant}}}*
WHATSAPP_REACTION_ADDED: |
{{{text}}}
whatsapp.reaction.added: |-
*प्रतिक्रिया दी* {{{emoji}}}
WHATSAPP_REACTION_REMOVED: |
whatsapp.reaction.removed: |-
*प्रतिक्रिया हटा दी गई*
WHATSAPP_CONTACT_GROUP_SUFFIX: 'समूह'
WHATSAPP_CONTACT_CHANNEL_SUFFIX: 'चैनल'
whatsapp.contact.group.suffix: 'समूह'
whatsapp.contact.channel.suffix: 'चैनल'
whatsapp.contact.broadcast.suffix: 'प्रसारण सूची'
# Aka Stories
WHATSAPP_CONTACT_STATUS_NAME: 'स्थिति'
whatsapp.contact.status.name: 'स्थिति'
APP_CONNECTED_MESSAGE: |
app.connected.message: |-
🔗 **कनेक्टेड: व्हाट्सएप सत्र से ChatWoot इनबॉक्स**
✅ व्हाट्सएप सत्र **{{{name}}}** सफलतापूर्वक इस ChatWoot इनबॉक्स से जुड़ गया है।
@@ -81,7 +103,7 @@ APP_CONNECTED_MESSAGE: |
चलिए शुरू करते हैं! 🚀
APP_DISCONNECTED_MESSAGE: |
app.disconnected.message: |-
🔌 **डिस्कनेक्टेड: व्हाट्सएप सत्र से ChatWoot इनबॉक्स**
❌ व्हाट्सएप सत्र **{{{name}}}** इस ChatWoot इनबॉक्स से डिस्कनेक्ट हो गया है।
@@ -90,15 +112,15 @@ APP_DISCONNECTED_MESSAGE: |
फिर से कनेक्ट करने के लिए, कृपया अपने प्रशासक से संपर्क करें।
APP_UPDATED_MESSAGE: |
app.updated.message: |-
🔄 **अपडेटेड: व्हाट्सएप सत्र सेटिंग्स**
✅ व्हाट्सएप सत्र **{{{name}}}** की सेटिंग्स अपडेट की गई हैं।
APP_COMMANDS_LIST: |
app.commands.list: |-
आप निम्न कमांड में से एक भेजकर **व्हाट्सएप इंटीग्रेशन** का प्रबंधन कर सकते हैं:
🖥️ सत्र:
🖥️ **सेशन**:
1. **status** - वर्तमान सत्र स्थिति प्राप्त करें।
2. **restart** - सत्र को पुनरारंभ करें - यह आपके व्हाट्सएप खाते को फिर से कनेक्ट करने का प्रयास करेगा।
3. **start** - सत्र शुरू करें।
@@ -108,42 +130,47 @@ APP_COMMANDS_LIST: |
7. **screenshot** - सत्र का स्क्रीनशॉट प्राप्त करें (केवल **WEBJS** इंजन में उपलब्ध)।
8. **help** - उपलब्ध कमांड।
{{#commands.server}}
⚙️ सर्वर:
- **server status** - सर्वर संस्करण और स्थिति जानकारी प्राप्त करें।
- **server reboot** - सर्वर को सुचारू रूप से पुनरारंभ करें।
- **server reboot force** - सर्वर को जबरदस्ती पुनरारंभ करें।
{{/commands.server}}
इस वार्तालाप में **कमांड** (केस इनसेंसिटिव) या **कमांड नंबर** भेजें।
APP_HELP_REMINDER: |
app.commands.server.disabled: ⛔ **सर्वर कमांड अक्षम हैं**।
कृपया अपने व्यवस्थापक से संपर्क करें।
उपलब्ध कमांड की सूची पाने के लिए **help** भेजें।
app.help.reminder: |-
उपलब्ध कमांड की सूची प्राप्त करने के लिए **help** भेजें।
APP_SESSION_STATUS_CHANGE: |
app.session.status.change: |-
{{{emoji}}} "{{{session}}}" सत्र {{{status}}} स्थिति में बदल गया
APP_SESSION_CURRENT_STATUS: |
app.session.current.status: |-
{{{emoji}}} **{{{session}}}** सत्र की स्थिति **{{{status}}}** में है
**व्हाट्सएप खाता:**
📱 **{{{name}}} ({{{id}}})**
APP_DASHBOARD_LINK: |
अधिक जानकारी के लिए [WAHA डैशबोर्ड]({{{url}}}) पर जाएं।
APP_SESSION_STATUS_WORKING: |
app.session.status.working: |-
✅ यह ChatWoot इनबॉक्स **व्हाट्सएप खाते** से जुड़ा हुआ है और संदेश भेजने और प्राप्त करने के लिए तैयार है!
📱 **{{{name}}} ({{{id}}})**
APP_SESSION_STATUS_ERROR: |
app.session.status.error: |-
⚠️ **यह ChatWoot इनबॉक्स व्हाट्सएप खाते से जुड़ा हुआ है लेकिन संदेश भेजने और प्राप्त करने के लिए तैयार नहीं है!**
कृपया सत्र की स्थिति जांचें और इसे पुनरारंभ करने का प्रयास करें या लॉग आउट करें और QR कोड फिर से स्कैन करें।
APP_SESSION_SCAN_QR_CODE: |
app.session.scan.qr.code: |-
⚠️ **इस ChatWoot इनबॉक्स के साथ व्हाट्सएप खाता जोड़ने के लिए अपने फोन पर QR कोड स्कैन करें!**
APP_SERVER_VERSION_AND_STATUS: |
app.server.version.and.status: |-
**संस्करण:**
```
{{{version}}}
@@ -154,16 +181,16 @@ APP_SERVER_VERSION_AND_STATUS: |
{{{status}}}
```
APP_SERVER_REBOOT: |
app.server.reboot: |-
सर्वर सुचारू रूप से पुनरारंभ हो रहा है...
APP_SERVER_REBOOT_FORCE: |
app.server.reboot.force: |-
सर्वर जबरदस्ती पुनरारंभ हो रहा है...
APP_LOGOUT_SUCCESS: |
app.logout.success: |-
✅ खाता सफलतापूर्वक अनधिकृत किया गया!
APP_NEW_VERSION_AVAILABLE: |
waha.new.version.available: |-
🔄 **नया संस्करण उपलब्ध!**
वर्तमान संस्करण: **{{{currentVersion}}}**
@@ -171,7 +198,7 @@ APP_NEW_VERSION_AVAILABLE: |
👉 [**🆕 चेंजलॉग**]({{{changelogUrl}}}) में क्या नया है देखें
APP_CORE_VERSION_USED: |
waha.core.version.used: |-
❤️ **WAHA का समर्थन करें!**
आप मुफ्त **WAHA Core** संस्करण का उपयोग कर रहे हैं। यदि आपको यह प्रोजेक्ट उपयोगी लगता है, तो कृपया इसके विकास का समर्थन करने पर विचार करें।
@@ -179,3 +206,177 @@ APP_CORE_VERSION_USED: |
आपका समर्थन ही एकमात्र तरीका है जिससे हम अपडेट, फिक्स और नई सुविधाओं पर काम करना जारी रख सकते हैं।
[**🎁 हमें समर्थन दें**]({{{supportUrl}}})
chatwoot.to.whatsapp.message.text: |-
{{{ content }}}
chatwoot.to.whatsapp.message.media.caption: |-
{{#singleAttachment}}
{{#content}}
{{{ content }}}
{{/content}}
{{/singleAttachment}}
whatsapp.to.chatwoot.message: |-
{{{payload.body}}}
whatsapp.to.chatwoot.message.contacts: |-
👤 **साझा संपर्क**
{{#contacts}}
🪪 नाम: **{{fullName}}**
{{#phoneNumbers}}
- 📞 फ़ोन: **{{.}}**
{{/phoneNumbers}}
{{/contacts}}
whatsapp.to.chatwoot.message.location: |-
{{#message}}
{{#locationMessage}}
📍 **साझा स्थान**
{{#name}}
**{{name}}**
{{/name}}
{{#address}}
📍 **पता:** {{address}}
{{/address}}
{{#url}}
🌐 **वेबसाइट:** {{url}}
{{/url}}
🔗 **मानचित्र पर देखें:** https://www.google.com/maps?q={{degreesLatitude}},{{degreesLongitude}}
{{#caption}}{{caption}}{{/caption}}
{{/locationMessage}}
{{#liveLocationMessage}}
📍📡 **लाइव लोकेशन साझा की जा रही है**
रीयल-टाइम अपडेट देखने के लिए **WhatsApp** खोलें।
{{#name}}
**{{name}}**
{{/name}}
{{#address}}
📍 **पता:** {{address}}
{{/address}}
{{#url}}
🌐 **वेबसाइट:** {{url}}
{{/url}}
🔗 **मानचित्र पर देखें:** https://www.google.com/maps?q={{degreesLatitude}},{{degreesLongitude}}
{{#caption}}{{caption}}{{/caption}}
{{/liveLocationMessage}}
{{/message}}
whatsapp.to.chatwoot.message.poll: |-
{{#poll}}
📊 **मतदान**
**{{name}}**
{{#options}}
- {{optionName}}
{{/options}}
{{/poll}}
whatsapp.to.chatwoot.message.list: |-
{{#message.listMessage}}
{{#title}}
*{{{title}}}*
{{/title}}
{{#description}}
{{{description}}}
{{/description}}
{{#footerText}}
{{{footerText}}}
{{/footerText}}
{{#buttonText}}
{{{buttonText}}}
{{/buttonText}}
{{#sections}}
{{#title}}
{{{title}}}:
{{/title}}
{{#rows}}
{{#title}}
- {{{title}}}
{{/title}}
{{/rows}}
{{/sections}}
{{/message.listMessage}}
whatsapp.to.chatwoot.message.event: |-
{{#message}}
{{#eventMessage}}
📅 **कार्यक्रम**
**{{name}}**
{{#description}}
{{description}}
{{/description}}
{{#location.name}}
📍 **स्थान:** {{location.name}}
{{/location.name}}
{{#startTime}}
🕐 **आरंभ:** {{startTime}}
{{/startTime}}
{{#endTime}}
🕐 **समाप्ति:** {{endTime}}
{{/endTime}}
{{#joinLink}}
🔗 **जुड़ने का लिंक:** [{{joinLink}}]({{joinLink}})
{{/joinLink}}
{{/eventMessage}}
{{/message}}
whatsapp.to.chatwoot.message.pix: |-
💳 **PIX - कॉपी और पेस्ट**
{{#pixData}}
{{#merchantName}}
**{{merchantName}}**
{{/merchantName}}
{{#key}}
**PIX कुंजी:** {{key}}
{{/key}}
{{#keyType}}
**प्रकार:** {{keyType}}
{{/keyType}}
{{#totalAmount}}
**राशि:** {{totalAmountFormatted}}
{{/totalAmount}}
{{#referenceId}}
**संदर्भ:** {{referenceId}}
{{/referenceId}}
💳 **PIX कॉपी और पेस्ट भेजा गया**
{{/pixData}}
whatsapp.to.chatwoot.message.unsupported: |-
⚠️ **इस प्रकार का संदेश ChatWoot में समर्थित नहीं है।**
📱 इसे देखने के लिए कृपया **WhatsApp** खोलें।
विवरण: [{{{details.text}}}]({{{details.url}}})
whatsapp.to.chatwoot.message.facebook.ad: |-
📢 **फेसबुक विज्ञापन**
**शीर्षक:** {{{adData.title}}}
**विवरण:**
{{{adData.body}}}
**विज्ञापन लिंक:** [फेसबुक पर देखें]({{{adData.sourceUrl}}})
**विज्ञापन आईडी:** {{{adData.sourceId}}}
---
💬 **ग्राहक संदेश:**
{{{payload.body}}}
Loaded 100 of 213 files, more files were not shown because too many files have changed in this diff. Show more