Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c44680c987 | ||
|
|
9394509a07 | ||
|
|
63890dc499 | ||
|
|
e2330901e4 | ||
|
|
58672fa1e1 | ||
|
|
3890071f77 | ||
|
|
d60dfba90a | ||
|
|
4e58713e7e | ||
|
|
6f0af3c7cc | ||
|
|
23bac2e7b1 | ||
|
|
e839203dbd | ||
|
|
304b0037ec | ||
|
|
482dbbeb0e | ||
|
|
8f4c79baf4 | ||
|
|
31038a9318 | ||
|
|
86fde1f670 | ||
|
|
984ef0c6a1 | ||
|
|
744ee1abb1 | ||
|
|
47f4f2670d | ||
|
|
f16eaed935 | ||
|
|
dfe9454a72 | ||
|
|
4aef38ff04 | ||
|
|
ab8de51022 | ||
|
|
c001d7fed6 | ||
|
|
cc056dca9c | ||
|
|
47f60a1626 | ||
|
|
98721051da | ||
|
|
5d7a6f4ff7 | ||
|
|
dcb7bf033f | ||
|
|
7d29be2d69 | ||
|
|
41b669a35c | ||
|
|
7b5ca3bc57 | ||
|
|
bb0c7fd22b | ||
|
|
c79958541e | ||
|
|
94ee8863d6 | ||
|
|
011477262d | ||
|
|
2e8e4cefb0 | ||
|
|
5e2802c098 | ||
|
|
9a6250a055 | ||
|
|
f8cb56525a | ||
|
|
b5b491cb7b | ||
|
|
7eb6be70f9 | ||
|
|
0de8a5fabc | ||
|
|
9fbc18fb70 | ||
|
|
e7ce3368b0 | ||
|
|
729600e9c6 | ||
|
|
54886f385b | ||
|
|
81854ad7ec | ||
|
|
5261d4f281 | ||
|
|
72d9d94342 | ||
|
|
b782ad27a2 | ||
|
|
d7dad5e6c2 | ||
|
|
8765bb4412 | ||
|
|
984be25d51 | ||
|
|
70620207dd | ||
|
|
a96200f981 | ||
|
|
4978abf3d8 | ||
|
|
a67d84f21e | ||
|
|
91f59b38fd | ||
|
|
d0ec351910 | ||
|
|
a7b12874fb | ||
|
|
cd2c7bdd86 | ||
|
|
045077143f | ||
|
|
bcd2199687 | ||
|
|
af108eede4 | ||
|
|
ee1cf838b8 | ||
|
|
0fc74895da | ||
|
|
394937c154 | ||
|
|
71f9adeff4 | ||
|
|
e13ccdab9c | ||
|
|
2074748a7a | ||
|
|
283d63394e | ||
|
|
5cfb53c367 | ||
|
|
9466407b8a | ||
|
|
4b65f0c462 | ||
|
|
23504b35c8 | ||
|
|
dd6ec4ca3a | ||
|
|
8235e5a111 | ||
|
|
55499a6144 | ||
|
|
9dbc4a6085 | ||
|
|
f7c6ce847c | ||
|
|
84eed00415 | ||
|
|
51aa0e3154 | ||
|
|
77893a0eb0 | ||
|
|
4a866d8a08 | ||
|
|
f912353a5c | ||
|
|
a6a1d8dc28 | ||
|
|
150e73aa74 | ||
|
|
a9300b3134 | ||
|
|
bbe3859085 | ||
|
|
b420548dd9 | ||
|
|
741483eec7 | ||
|
|
2acf7e4e17 | ||
|
|
9bca6d904d | ||
|
|
857c586df6 | ||
|
|
df8cb7cecb | ||
|
|
9d0050adb8 | ||
|
|
df927ab04a | ||
|
|
02e5934e8f | ||
|
|
825a4152db | ||
|
|
81b10099af | ||
|
|
3be5f328b0 | ||
|
|
5caa3b8b81 | ||
|
|
8231bf7d8e | ||
|
|
22d94808fb | ||
|
|
57daefa680 | ||
|
|
5cf8704973 | ||
|
|
28da9c34bb | ||
|
|
51ba5e3266 | ||
|
|
f696794ee1 | ||
|
|
c6eca9510b | ||
|
|
9a2fd2fccd | ||
|
|
c0a2d02f0d | ||
|
|
6493d16fd2 | ||
|
|
9f96c678dc | ||
|
|
a81f2ab553 | ||
|
|
8fbf7fa0e5 | ||
|
|
af0bba313a |
No files matched your search
+16
-13
@@ -3,9 +3,25 @@
|
||||
# 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
|
||||
@@ -15,20 +31,7 @@ WAHA_BASE_URL=http://localhost:3000
|
||||
# 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 =====
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 }}
|
||||
@@ -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'
|
||||
@@ -67,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|
|
||||
)$
|
||||
@@ -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.
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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-09-21
|
||||
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-09-10
|
||||
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
|
||||
|
||||
@@ -220,6 +220,7 @@ ANDROID_SHA256_CERT_FINGERPRINT=AC:73:8E:DE:EB:56:EA:CC:10:87:02:A7:65:37:7B:38:
|
||||
# ENABLE_RACK_ATTACK=true
|
||||
# RACK_ATTACK_LIMIT=300
|
||||
# ENABLE_RACK_ATTACK_WIDGET_API=true
|
||||
CONVERSATION_MESSAGE_PER_MINUTE_LIMIT=3000
|
||||
|
||||
## Running chatwoot as an API only server
|
||||
## setting this value to true will disable the frontend dashboard endpoints
|
||||
|
||||
@@ -3,6 +3,20 @@
|
||||
# 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 =====
|
||||
# ==================
|
||||
@@ -15,20 +29,6 @@ WAHA_PUBLIC_URL=http://localhost:3000
|
||||
# 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
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ services:
|
||||
- 8.8.8.8
|
||||
|
||||
base: &base
|
||||
image: chatwoot/chatwoot:v4.5.2
|
||||
image: chatwoot/chatwoot:v4.7.0
|
||||
env_file: .chatwoot.env ## Change this file for customized env variables
|
||||
volumes:
|
||||
- chatwoot_storage:/app/storage
|
||||
|
||||
@@ -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.
+11
-5
@@ -26,7 +26,7 @@
|
||||
"gows:proto": "yarn gows:proto:fetch && yarn gows:proto:build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@adiwajshing/baileys": "github:devlikeapro/Baileys#fork-master-2025-09-21",
|
||||
"@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",
|
||||
@@ -65,6 +65,7 @@
|
||||
"chokidar": "^3.6.0",
|
||||
"class-transformer": "^0.5.1",
|
||||
"class-validator": "0.14.0",
|
||||
"commander": "^14.0.1",
|
||||
"del": "^8.0.0",
|
||||
"express-basic-auth": "^1.2.1",
|
||||
"file-type": "16.5.4",
|
||||
@@ -76,11 +77,13 @@
|
||||
"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",
|
||||
"ms": "^2.1.3",
|
||||
"mustache": "^4.2.0",
|
||||
"nestjs-pino": "^4.1.0",
|
||||
"node-cache": "5.1.2",
|
||||
@@ -98,9 +101,11 @@
|
||||
"rimraf": "^4.3.0",
|
||||
"rxjs": "^7.8.1",
|
||||
"sharp": "^0.33.4",
|
||||
"shell-quote": "^1.8.3",
|
||||
"sqlite3": "^5.1.7",
|
||||
"swagger-ui-express": "^4.1.4",
|
||||
"ulid": "^2.3.0",
|
||||
"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"
|
||||
@@ -113,7 +118,6 @@
|
||||
"ws": "^8.18.0",
|
||||
"puppeteer": "^24.19.0",
|
||||
"whatwg-url": "13.0.0",
|
||||
"libsignal": "github:devlikeapro/libsignal-node#fork-master",
|
||||
"axios": "^1.9.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -138,8 +142,7 @@
|
||||
"ts-loader": "^6.2.1",
|
||||
"ts-node": "^10.9.2",
|
||||
"tsconfig-paths": "^4.1.0",
|
||||
"typescript": "5.9.2",
|
||||
"yargs": "^17.7.2"
|
||||
"typescript": "5.9.2"
|
||||
},
|
||||
"jest": {
|
||||
"moduleFileExtensions": [
|
||||
@@ -148,6 +151,9 @@
|
||||
"ts"
|
||||
],
|
||||
"rootDir": "src",
|
||||
"moduleNameMapper": {
|
||||
"^@waha/(.*)$": "<rootDir>/$1"
|
||||
},
|
||||
"testRegex": ".test.ts$",
|
||||
"transform": {
|
||||
"^.+\\.(t|j)s$": "ts-jest"
|
||||
|
||||
Executable
+72
@@ -0,0 +1,72 @@
|
||||
#!/usr/bin/env node
|
||||
const { readFileSync } = require('fs');
|
||||
const { spawnSync } = require('child_process');
|
||||
const path = require('path');
|
||||
const YAML = require('yaml');
|
||||
|
||||
const repoRoot = path.resolve(__dirname, '..');
|
||||
const enPath = path.join(repoRoot, 'src/apps/chatwoot/i18n/locales/en-US.yaml');
|
||||
const contents = readFileSync(enPath, 'utf8');
|
||||
const document = YAML.parse(contents);
|
||||
|
||||
const flatten = (node, prefix = '') => {
|
||||
if (node && typeof node === 'object' && !Array.isArray(node)) {
|
||||
return Object.entries(node).reduce((acc, [key, value]) => {
|
||||
const next = prefix ? `${prefix}.${key}` : key;
|
||||
return acc.concat(flatten(value, next));
|
||||
}, []);
|
||||
}
|
||||
|
||||
return [prefix];
|
||||
};
|
||||
|
||||
const allKeys = flatten(document).filter(Boolean);
|
||||
|
||||
const ignoredPrefixes = ['datetime.', 'locale.'];
|
||||
|
||||
const ignored = new Set();
|
||||
const unused = [];
|
||||
|
||||
const searchRoots = ['src', 'tests'];
|
||||
|
||||
for (const key of allKeys) {
|
||||
if (
|
||||
ignoredPrefixes.some((prefix) => key.startsWith(prefix)) ||
|
||||
!key.includes('.')
|
||||
) {
|
||||
ignored.add(key);
|
||||
continue;
|
||||
}
|
||||
|
||||
const result = spawnSync(
|
||||
'rg',
|
||||
[
|
||||
'--files-with-matches',
|
||||
'--fixed-strings',
|
||||
key,
|
||||
...searchRoots,
|
||||
'--glob',
|
||||
'!apps/chatwoot/i18n/locales/*.yaml',
|
||||
],
|
||||
{
|
||||
cwd: repoRoot,
|
||||
encoding: 'utf8',
|
||||
},
|
||||
);
|
||||
|
||||
if (result.status !== 0 || !result.stdout.trim()) {
|
||||
unused.push(key);
|
||||
}
|
||||
}
|
||||
|
||||
const message = [
|
||||
`Scanned ${allKeys.length} keys.`,
|
||||
`Ignored ${ignored.size} keys (datetime.*, locale.*, or without dot).`,
|
||||
unused.length
|
||||
? `Found ${unused.length} potentially unused keys:\n - ${unused.join(
|
||||
'\n - ',
|
||||
)}`
|
||||
: 'No unused keys detected.',
|
||||
];
|
||||
|
||||
console.log(message.join('\n'));
|
||||
Executable
+92
@@ -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'));
|
||||
@@ -29,6 +29,7 @@ import {
|
||||
GetChatMessageQuery,
|
||||
GetChatMessagesFilter,
|
||||
GetChatMessagesQuery,
|
||||
MessageSortField,
|
||||
OverviewBodyRequest,
|
||||
OverviewFilter,
|
||||
OverviewPaginationParams,
|
||||
@@ -38,6 +39,7 @@ import {
|
||||
transformAck,
|
||||
} from '../structures/chats.dto';
|
||||
import { EditMessageRequest } from '../structures/chatting.dto';
|
||||
import { SortOrder } from '@waha/structures/pagination.dto';
|
||||
|
||||
@ApiSecurity('api_key')
|
||||
@Controller('api/:session/chats')
|
||||
@@ -120,6 +122,8 @@ class ChatsController {
|
||||
@WorkingSessionParam session: WhatsappSession,
|
||||
@Param('chatId') chatId: string,
|
||||
) {
|
||||
query.sortBy = query.sortBy || MessageSortField.TIMESTAMP;
|
||||
query.sortOrder = query.sortOrder || SortOrder.DESC;
|
||||
filter = transformAck(filter);
|
||||
return session.getChatMessages(chatId, query, filter);
|
||||
}
|
||||
|
||||
@@ -7,6 +7,12 @@ import { DelayedError } from 'bullmq';
|
||||
import { PinoLogger } from 'nestjs-pino';
|
||||
import { Logger } from 'pino';
|
||||
|
||||
export interface JobDataTimeout {
|
||||
timeout?: {
|
||||
job?: number;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Base class for app consumers that provides common functionality
|
||||
* like mutex locking, logging setup, and error handling.
|
||||
@@ -27,6 +33,14 @@ export abstract class AppConsumer extends BaseWorkerHost {
|
||||
});
|
||||
}
|
||||
|
||||
protected signal(job) {
|
||||
if (job.data?.timeout?.job != null) {
|
||||
return AbortSignal.timeout(job.data?.timeout?.job);
|
||||
}
|
||||
// Aka never abort
|
||||
return new AbortController().signal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a job with mutex locking to ensure only one job processes a specific resource at a time.
|
||||
* @param job The job to process
|
||||
|
||||
@@ -21,7 +21,7 @@ export class AxiosLogging {
|
||||
: '';
|
||||
const body = config.data ? JSON.stringify(config.data) : '';
|
||||
|
||||
this.logger.info(`${method} ${url}${query} ${body}`);
|
||||
this.logger.debug(`${method} ${url}${query} ${body}`);
|
||||
return config;
|
||||
}
|
||||
|
||||
@@ -32,8 +32,8 @@ export class AxiosLogging {
|
||||
const responseData = JSON.stringify(response.data);
|
||||
|
||||
if (status >= 200 && status < 400) {
|
||||
this.logger.info(`${methodStr} ${status}:OK ${url}`);
|
||||
this.logger.debug(`${methodStr} ${status}:OK ${url} ${responseData}`);
|
||||
this.logger.debug(`${methodStr} ${status}:OK ${url}`);
|
||||
this.logger.trace(`${methodStr} ${status}:OK ${url} ${responseData}`);
|
||||
} else {
|
||||
this.logger.warn(`${methodStr} ${status} ${url} ${responseData}`);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Job } from 'bullmq';
|
||||
import { Backoffs } from 'bullmq';
|
||||
import { Backoffs, Job } from 'bullmq';
|
||||
import { FlowJob } from 'bullmq/dist/esm/interfaces';
|
||||
|
||||
/**
|
||||
* Calculates the delay until the next attempt for a job.
|
||||
@@ -60,8 +60,35 @@ 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
|
||||
}`;
|
||||
const queue = encodeURIComponent(job.queueName);
|
||||
const id = encodeURIComponent(job.id);
|
||||
const url = `${base}/jobs/queue/${queue}/${id}`;
|
||||
return { text: text, url: url };
|
||||
}
|
||||
|
||||
/**
|
||||
* Chain jobs so that they run one at a time
|
||||
* A | B | C
|
||||
* to
|
||||
* A -> B -> C
|
||||
*/
|
||||
export function ChainJobsOneAtATime(jobs: FlowJob[]): FlowJob {
|
||||
if (jobs.length == 0) {
|
||||
throw new Error('No jobs provided');
|
||||
}
|
||||
|
||||
for (const job of jobs) {
|
||||
if (job.children.length != 0) {
|
||||
throw new Error('Jobs with children are not supported');
|
||||
}
|
||||
}
|
||||
const left = [...jobs];
|
||||
const root = left.pop();
|
||||
let parent = root;
|
||||
while (left.length != 0) {
|
||||
const job = left.pop();
|
||||
parent.children = [job];
|
||||
parent = job;
|
||||
}
|
||||
return root;
|
||||
}
|
||||
@@ -2,10 +2,11 @@ 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 || '';
|
||||
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) {
|
||||
|
||||
@@ -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(),
|
||||
};
|
||||
}
|
||||
@@ -44,4 +44,8 @@ export class AppsDisabledService implements IAppsService {
|
||||
async beforeSessionStart(session: WhatsappSession, store: DataStore) {
|
||||
return;
|
||||
}
|
||||
|
||||
async afterSessionStart(session: WhatsappSession, store: DataStore) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -145,7 +145,17 @@ export class AppsEnabledService implements IAppsService {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,5 +23,7 @@ export interface IAppService {
|
||||
|
||||
beforeDeleted(app: App): Promise<void>;
|
||||
|
||||
beforeSessionStart(app: App, session: WhatsappSession): Promise<void>;
|
||||
beforeSessionStart(app: App, session: WhatsappSession): void;
|
||||
|
||||
afterSessionStart(app: App, session: WhatsappSession): void;
|
||||
}
|
||||
@@ -17,6 +17,8 @@ export interface IAppsService {
|
||||
|
||||
beforeSessionStart(session: WhatsappSession, store: DataStore): Promise<void>;
|
||||
|
||||
afterSessionStart(session: WhatsappSession, store: DataStore): Promise<void>;
|
||||
|
||||
migrate(knex: Knex): Promise<void>;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import * as lodash from 'lodash';
|
||||
|
||||
type Call<Record> = (processed: number) => Promise<Record[]>;
|
||||
|
||||
export interface PaginatorParams {
|
||||
processed?: number;
|
||||
max?: number;
|
||||
}
|
||||
|
||||
const DefaultParams: PaginatorParams = {
|
||||
processed: 0,
|
||||
max: Infinity,
|
||||
};
|
||||
|
||||
/**
|
||||
* Generic paginator for APIs that return arrays of records with limits and offsets.
|
||||
*/
|
||||
export class ArrayPaginator<Record> {
|
||||
private readonly params: PaginatorParams;
|
||||
|
||||
constructor(params: PaginatorParams = DefaultParams) {
|
||||
this.params = lodash.merge({}, DefaultParams, params) as PaginatorParams;
|
||||
}
|
||||
|
||||
async *iterate(call: Call<Record>): AsyncGenerator<Record> {
|
||||
let processed = this.params.processed;
|
||||
let records: Record[] = [];
|
||||
|
||||
while (true) {
|
||||
records = await call(processed);
|
||||
if (records.length === 0) {
|
||||
return;
|
||||
}
|
||||
for (const record of records) {
|
||||
yield record;
|
||||
processed += 1;
|
||||
if (processed >= this.params.max) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,462 @@
|
||||
import { Channel } from '@waha/structures/channels.dto';
|
||||
import {
|
||||
GetChatMessagesFilter,
|
||||
GetChatMessagesQuery,
|
||||
} from '@waha/structures/chats.dto';
|
||||
import {
|
||||
ChatRequest,
|
||||
MessageFileRequest,
|
||||
MessageImageRequest,
|
||||
MessageTextRequest,
|
||||
MessageVideoRequest,
|
||||
MessageVoiceRequest,
|
||||
WANumberExistResult,
|
||||
} from '@waha/structures/chatting.dto';
|
||||
import { SessionInfo } from '@waha/structures/sessions.dto';
|
||||
import axios, { AxiosInstance } from 'axios';
|
||||
import { Auth } from '@waha/core/auth/config';
|
||||
import { ContactSortField } from '@waha/structures/contacts.dto';
|
||||
import { PaginationParams } from '@waha/structures/pagination.dto';
|
||||
|
||||
export interface RequestOptions {
|
||||
signal?: AbortSignal;
|
||||
}
|
||||
|
||||
export class WAHASelf {
|
||||
public client: AxiosInstance;
|
||||
|
||||
constructor() {
|
||||
// Set 'X-Api-Key'
|
||||
const key = Auth.keyplain.value;
|
||||
const port =
|
||||
parseInt(process.env.PORT) ||
|
||||
parseInt(process.env.WHATSAPP_API_PORT) ||
|
||||
3000;
|
||||
const url = `http://localhost:${port}`;
|
||||
this.client = axios.create({
|
||||
baseURL: url,
|
||||
headers: {
|
||||
'X-Api-Key': key,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async fetch(url: string, opts?: RequestOptions): Promise<Buffer> {
|
||||
const response = await this.client.get(url, {
|
||||
responseType: 'arraybuffer',
|
||||
signal: opts?.signal,
|
||||
});
|
||||
return Buffer.from(response.data);
|
||||
}
|
||||
|
||||
async qr(session: string, opts?: RequestOptions): Promise<Buffer> {
|
||||
const url = `/api/${session}/auth/qr`;
|
||||
return await this.fetch(url, opts);
|
||||
}
|
||||
|
||||
async screenshot(session: string, opts?: RequestOptions): Promise<Buffer> {
|
||||
const url = `/api/screenshot?session=${session}`;
|
||||
return await this.fetch(url, opts);
|
||||
}
|
||||
|
||||
async restart(session: string, opts?: RequestOptions): Promise<any> {
|
||||
const url = `/api/sessions/${session}/restart`;
|
||||
return await this.client.post(url, undefined, { signal: opts?.signal });
|
||||
}
|
||||
|
||||
async logout(session: string, opts?: RequestOptions): Promise<any> {
|
||||
const url = `/api/sessions/${session}/logout`;
|
||||
return await this.client.post(url, undefined, { signal: opts?.signal });
|
||||
}
|
||||
|
||||
async stop(session: string, opts?: RequestOptions): Promise<any> {
|
||||
const url = `/api/sessions/${session}/stop`;
|
||||
return await this.client.post(url, undefined, { signal: opts?.signal });
|
||||
}
|
||||
|
||||
async get(session: string, opts?: RequestOptions): Promise<SessionInfo> {
|
||||
const url = `/api/sessions/${session}/`;
|
||||
return await this.client
|
||||
.get(url, { signal: opts?.signal })
|
||||
.then((response) => response.data);
|
||||
}
|
||||
|
||||
async getChats(
|
||||
session: string,
|
||||
page: PaginationParams,
|
||||
opts?: RequestOptions,
|
||||
) {
|
||||
const url = `/api/${session}/chats`;
|
||||
const params = {
|
||||
...page,
|
||||
};
|
||||
return await this.client
|
||||
.get(url, { params: params, signal: opts?.signal })
|
||||
.then((response) => response.data);
|
||||
}
|
||||
|
||||
async getContacts(
|
||||
session: string,
|
||||
page: PaginationParams,
|
||||
opts?: RequestOptions,
|
||||
) {
|
||||
const url = `/api/contacts/all`;
|
||||
const params = {
|
||||
session: session,
|
||||
...page,
|
||||
sortBy: ContactSortField.ID,
|
||||
sortOrder: ContactSortField.ID,
|
||||
};
|
||||
return await this.client
|
||||
.get(url, { params: params, signal: opts?.signal })
|
||||
.then((response) => response.data);
|
||||
}
|
||||
|
||||
async getContact(session: string, contactId: string, opts?: RequestOptions) {
|
||||
const url = `/api/contacts`;
|
||||
const params = {
|
||||
session: session,
|
||||
contactId: contactId,
|
||||
};
|
||||
return await this.client
|
||||
.get(url, { params: params, signal: opts?.signal })
|
||||
.then((response) => response.data);
|
||||
}
|
||||
|
||||
async contactCheckExists(
|
||||
session: string,
|
||||
phone: string,
|
||||
opts?: RequestOptions,
|
||||
): Promise<WANumberExistResult> {
|
||||
const url = `/api/contacts/check-exists`;
|
||||
const params = {
|
||||
phone: phone,
|
||||
session: session,
|
||||
};
|
||||
return await this.client
|
||||
.get(url, { params: params, signal: opts?.signal })
|
||||
.then((response) => response.data);
|
||||
}
|
||||
|
||||
async getGroup(session: string, groupId: string, opts?: RequestOptions) {
|
||||
const url = `/api/${session}/groups/${groupId}`;
|
||||
return await this.client
|
||||
.get(url, { signal: opts?.signal })
|
||||
.then((response) => response.data);
|
||||
}
|
||||
|
||||
async getChannel(
|
||||
session: string,
|
||||
channelId: string,
|
||||
opts?: RequestOptions,
|
||||
): Promise<Channel> {
|
||||
const url = `/api/${session}/channels/${channelId}`;
|
||||
return await this.client
|
||||
.get(url, { signal: opts?.signal })
|
||||
.then((response) => response.data);
|
||||
}
|
||||
|
||||
async getChatPicture(
|
||||
session: string,
|
||||
chatId: string,
|
||||
opts?: RequestOptions,
|
||||
): Promise<string | null> {
|
||||
const url = `/api/${session}/chats/${chatId}/picture`;
|
||||
return await this.client
|
||||
.get(url, { signal: opts?.signal })
|
||||
.then((response) => response.data?.url);
|
||||
}
|
||||
|
||||
async sendText(
|
||||
body: MessageTextRequest,
|
||||
opts?: RequestOptions,
|
||||
): Promise<any> {
|
||||
const url = `/api/sendText`;
|
||||
return await this.client
|
||||
.post(url, body, { signal: opts?.signal })
|
||||
.then((response) => response.data);
|
||||
}
|
||||
|
||||
async sendImage(
|
||||
body: MessageImageRequest,
|
||||
opts?: RequestOptions,
|
||||
): Promise<any> {
|
||||
const url = `/api/sendImage`;
|
||||
return await this.client
|
||||
.post(url, body, { signal: opts?.signal })
|
||||
.then((response) => response.data);
|
||||
}
|
||||
|
||||
async sendVideo(
|
||||
body: MessageVideoRequest,
|
||||
opts?: RequestOptions,
|
||||
): Promise<any> {
|
||||
const url = `/api/sendVideo`;
|
||||
return await this.client
|
||||
.post(url, body, { signal: opts?.signal })
|
||||
.then((response) => response.data);
|
||||
}
|
||||
|
||||
async sendVoice(
|
||||
body: MessageVoiceRequest,
|
||||
opts?: RequestOptions,
|
||||
): Promise<any> {
|
||||
const url = `/api/sendVoice`;
|
||||
return await this.client
|
||||
.post(url, body, { signal: opts?.signal })
|
||||
.then((response) => response.data);
|
||||
}
|
||||
|
||||
async sendFile(
|
||||
body: MessageFileRequest,
|
||||
opts?: RequestOptions,
|
||||
): Promise<any> {
|
||||
const url = `/api/sendFile`;
|
||||
return await this.client
|
||||
.post(url, body, { signal: opts?.signal })
|
||||
.then((response) => response.data);
|
||||
}
|
||||
|
||||
async deleteMessage(
|
||||
session: string,
|
||||
chatId: string,
|
||||
messageId: string,
|
||||
opts?: RequestOptions,
|
||||
) {
|
||||
const url = `/api/${session}/chats/${chatId}/messages/${messageId}`;
|
||||
return await this.client
|
||||
.delete(url, { signal: opts?.signal })
|
||||
.then((response) => response.data);
|
||||
}
|
||||
|
||||
async startTyping(body: ChatRequest, opts?: RequestOptions) {
|
||||
const url = `/api/startTyping`;
|
||||
return await this.client
|
||||
.post(url, body, { signal: opts?.signal })
|
||||
.then((response) => response.data);
|
||||
}
|
||||
|
||||
async stopTyping(body: ChatRequest, opts?: RequestOptions) {
|
||||
const url = `/api/stopTyping`;
|
||||
return await this.client.post(url, body, { signal: opts?.signal });
|
||||
}
|
||||
|
||||
async readMessages(session: string, chatId: string, opts?: RequestOptions) {
|
||||
const url = `/api/${session}/chats/${chatId}/messages/read`;
|
||||
return await this.client
|
||||
.post(url, undefined, { signal: opts?.signal })
|
||||
.then((response) => response.data);
|
||||
}
|
||||
|
||||
async getMessages(
|
||||
session: string,
|
||||
chatId: string,
|
||||
query: GetChatMessagesQuery,
|
||||
filter: GetChatMessagesFilter,
|
||||
opts?: RequestOptions,
|
||||
) {
|
||||
const url = `/api/${session}/chats/${chatId}/messages`;
|
||||
const params = {
|
||||
...query,
|
||||
...filter,
|
||||
};
|
||||
return await this.client
|
||||
.get(url, { params: params, signal: opts?.signal })
|
||||
.then((response) => response.data);
|
||||
}
|
||||
|
||||
async getMessageById(
|
||||
session: string,
|
||||
chatId: string,
|
||||
messageId: string,
|
||||
media: boolean,
|
||||
opts?: RequestOptions,
|
||||
) {
|
||||
const url = `/api/${session}/chats/${chatId}/messages/${messageId}`;
|
||||
const params = {
|
||||
downloadMedia: media,
|
||||
};
|
||||
return await this.client
|
||||
.get(url, { params: params, signal: opts?.signal })
|
||||
.then((response) => response.data);
|
||||
}
|
||||
|
||||
async findPNByLid(
|
||||
session: string,
|
||||
lid: string,
|
||||
opts?: RequestOptions,
|
||||
): Promise<string | null> {
|
||||
const url = `/api/${session}/lids/${lid}`;
|
||||
return await this.client
|
||||
.get(url, { signal: opts?.signal })
|
||||
.then((response) => response.data.pn);
|
||||
}
|
||||
|
||||
async findLIDByPN(
|
||||
session: string,
|
||||
pn: string,
|
||||
opts?: RequestOptions,
|
||||
): Promise<string | null> {
|
||||
const url = `/api/${session}/lids/pn/${pn}`;
|
||||
return await this.client
|
||||
.get(url, { signal: opts?.signal })
|
||||
.then((response) => response.data.lid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the server version
|
||||
* @returns Server version information
|
||||
*/
|
||||
async serverVersion(opts?: RequestOptions): Promise<any> {
|
||||
const url = `/api/server/version`;
|
||||
return await this.client
|
||||
.get(url, { signal: opts?.signal })
|
||||
.then((response) => response.data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the server status
|
||||
* @returns Server status information
|
||||
*/
|
||||
async serverStatus(opts?: RequestOptions): Promise<any> {
|
||||
const url = `/api/server/status`;
|
||||
return await this.client
|
||||
.get(url, { signal: opts?.signal })
|
||||
.then((response) => response.data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reboot the server
|
||||
* @param force Whether to force reboot (true) or gracefully reboot (false)
|
||||
* @returns Server stop response
|
||||
*/
|
||||
async serverReboot(
|
||||
force: boolean = false,
|
||||
opts?: RequestOptions,
|
||||
): Promise<any> {
|
||||
const url = `/api/server/stop`;
|
||||
return await this.client
|
||||
.post(url, { force }, { signal: opts?.signal })
|
||||
.then((response) => response.data);
|
||||
}
|
||||
}
|
||||
|
||||
export class WAHASessionAPI {
|
||||
constructor(
|
||||
private session: string,
|
||||
private api: WAHASelf,
|
||||
) {}
|
||||
|
||||
getChats(page: PaginationParams, opts?: RequestOptions) {
|
||||
return this.api.getChats(this.session, page, opts);
|
||||
}
|
||||
|
||||
getContacts(page: PaginationParams, opts?: RequestOptions): Promise<any> {
|
||||
return this.api.getContacts(this.session, page, opts);
|
||||
}
|
||||
|
||||
getContact(contactId: string, opts?: RequestOptions): Promise<any> {
|
||||
return this.api.getContact(this.session, contactId, opts);
|
||||
}
|
||||
|
||||
contactCheckExists(
|
||||
phone: string,
|
||||
opts?: RequestOptions,
|
||||
): Promise<WANumberExistResult> {
|
||||
return this.api.contactCheckExists(this.session, phone, opts);
|
||||
}
|
||||
|
||||
getGroup(groupId: string, opts?: RequestOptions): Promise<any> {
|
||||
return this.api.getGroup(this.session, groupId, opts);
|
||||
}
|
||||
|
||||
getChannel(channelId: string, opts?: RequestOptions): Promise<Channel> {
|
||||
return this.api.getChannel(this.session, channelId, opts);
|
||||
}
|
||||
|
||||
getChatPicture(
|
||||
chatId: string,
|
||||
opts?: RequestOptions,
|
||||
): Promise<string | null> {
|
||||
return this.api.getChatPicture(this.session, chatId, opts);
|
||||
}
|
||||
|
||||
sendText(body: MessageTextRequest, opts?: RequestOptions): Promise<any> {
|
||||
body.session = this.session;
|
||||
return this.api.sendText(body, opts);
|
||||
}
|
||||
|
||||
sendImage(body: MessageImageRequest, opts?: RequestOptions): Promise<any> {
|
||||
body.session = this.session;
|
||||
return this.api.sendImage(body, opts);
|
||||
}
|
||||
|
||||
sendVideo(body: MessageVideoRequest, opts?: RequestOptions): Promise<any> {
|
||||
body.session = this.session;
|
||||
return this.api.sendVideo(body, opts);
|
||||
}
|
||||
|
||||
sendVoice(body: MessageVoiceRequest, opts?: RequestOptions): Promise<any> {
|
||||
body.session = this.session;
|
||||
return this.api.sendVoice(body, opts);
|
||||
}
|
||||
|
||||
sendFile(body: MessageFileRequest, opts?: RequestOptions): Promise<any> {
|
||||
body.session = this.session;
|
||||
return this.api.sendFile(body, opts);
|
||||
}
|
||||
|
||||
deleteMessage(chatId: string, messageId: string, opts?: RequestOptions) {
|
||||
return this.api.deleteMessage(this.session, chatId, messageId, opts);
|
||||
}
|
||||
|
||||
startTyping(body: ChatRequest, opts?: RequestOptions) {
|
||||
body.session = this.session;
|
||||
return this.api.startTyping(body, opts);
|
||||
}
|
||||
|
||||
stopTyping(body: ChatRequest, opts?: RequestOptions) {
|
||||
body.session = this.session;
|
||||
return this.api.stopTyping(body, opts);
|
||||
}
|
||||
|
||||
readMessages(chatId: string, opts?: RequestOptions) {
|
||||
return this.api.readMessages(this.session, chatId, opts);
|
||||
}
|
||||
|
||||
async getMessages(
|
||||
chatId: string,
|
||||
query: GetChatMessagesQuery,
|
||||
filter: GetChatMessagesFilter,
|
||||
opts?: RequestOptions,
|
||||
) {
|
||||
return this.api.getMessages(this.session, chatId, query, filter, opts);
|
||||
}
|
||||
|
||||
async getMessageById(
|
||||
chatId: string,
|
||||
messageId: string,
|
||||
media: boolean,
|
||||
opts?: RequestOptions,
|
||||
) {
|
||||
return this.api.getMessageById(
|
||||
this.session,
|
||||
chatId,
|
||||
messageId,
|
||||
media,
|
||||
opts,
|
||||
);
|
||||
}
|
||||
|
||||
//
|
||||
// Lids
|
||||
//
|
||||
findPNByLid(lid: string, opts?: RequestOptions) {
|
||||
return this.api.findPNByLid(this.session, lid, opts);
|
||||
}
|
||||
|
||||
findLIDByPN(pn: string, opts?: RequestOptions) {
|
||||
return this.api.findLIDByPN(this.session, pn, opts);
|
||||
}
|
||||
}
|
||||
@@ -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,18 +0,0 @@
|
||||
import { WAHASessionStatus } from '@waha/structures/enums.dto';
|
||||
|
||||
export function SessionStatusEmoji(status: WAHASessionStatus): string {
|
||||
switch (status) {
|
||||
case WAHASessionStatus.STOPPED:
|
||||
return '⚠️';
|
||||
case WAHASessionStatus.STARTING:
|
||||
return '⏳';
|
||||
case WAHASessionStatus.SCAN_QR_CODE:
|
||||
return '⚠️';
|
||||
case WAHASessionStatus.WORKING:
|
||||
return '🟢';
|
||||
case WAHASessionStatus.FAILED:
|
||||
return '🛑';
|
||||
default:
|
||||
return '❓';
|
||||
}
|
||||
}
|
||||
@@ -6,13 +6,13 @@ import {
|
||||
Post,
|
||||
} from '@nestjs/common';
|
||||
import { ApiOperation, ApiTags } from '@nestjs/swagger';
|
||||
import { FindChatID } from '@waha/apps/chatwoot/client/ids';
|
||||
import { IsCommandsChat } 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';
|
||||
import { CommandPrefix } from '@waha/apps/chatwoot/cli';
|
||||
|
||||
@Controller('webhooks/chatwoot/')
|
||||
@ApiTags('🧩 Apps')
|
||||
@@ -36,16 +36,26 @@ export class ChatwootWebhookController {
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
// Ignore all private notes
|
||||
if (body.private) {
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
// Ignore all incoming messages
|
||||
if (body.message_type == MessageType.INCOMING) {
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
const isCommandsChat = IsCommandsChat(body);
|
||||
// Ignore private notes (most of them)
|
||||
const deleted = body?.content_attributes?.deleted;
|
||||
if (body.private) {
|
||||
// Ignore any private note in commands chats
|
||||
if (isCommandsChat) {
|
||||
return { success: true };
|
||||
}
|
||||
// keep "deleted" notes
|
||||
// So Agent can delete "Sent From API/WhatsApp" messages in ChatWoot
|
||||
if (!deleted) {
|
||||
return { success: true };
|
||||
}
|
||||
}
|
||||
|
||||
const data: InboxData = {
|
||||
session: session,
|
||||
app: id,
|
||||
@@ -61,12 +71,8 @@ export class ChatwootWebhookController {
|
||||
}
|
||||
|
||||
// Check if it's a command message (sent to the special inbox contact)
|
||||
const sender = body?.conversation?.meta?.sender;
|
||||
const chatId = FindChatID(sender);
|
||||
const isCommandsChat = chatId === INBOX_CONTACT_CHAT_ID;
|
||||
|
||||
// Check if it's a deleted message
|
||||
if (body.content_attributes?.deleted && !isCommandsChat) {
|
||||
if (deleted && !isCommandsChat) {
|
||||
await this.chatWootQueueService.addMessageDeletedJob(data);
|
||||
return { success: true };
|
||||
}
|
||||
@@ -74,17 +80,30 @@ export class ChatwootWebhookController {
|
||||
// Route to specific queues based on an event type
|
||||
switch (body.event) {
|
||||
case EventName.MESSAGE_CREATED:
|
||||
if (!isCommandsChat) {
|
||||
await this.chatWootQueueService.addMessageCreatedJob(data);
|
||||
} else {
|
||||
if (isCommandsChat || body.content?.startsWith(CommandPrefix)) {
|
||||
await this.chatWootQueueService.addCommandsJob(body.event, data);
|
||||
} else {
|
||||
await this.chatWootQueueService.addMessageCreatedJob(data);
|
||||
}
|
||||
return { success: true };
|
||||
case EventName.MESSAGE_UPDATED:
|
||||
if (!isCommandsChat) {
|
||||
await this.chatWootQueueService.addMessageUpdatedJob(data);
|
||||
} else {
|
||||
// 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 || body.content?.startsWith(CommandPrefix)) {
|
||||
await this.chatWootQueueService.addCommandsJob(body.event, data);
|
||||
} else {
|
||||
await this.chatWootQueueService.addMessageUpdatedJob(data);
|
||||
}
|
||||
return { success: true };
|
||||
default:
|
||||
|
||||
+4
-3
@@ -1,7 +1,8 @@
|
||||
import { ChatWootAPIConfig } from '@waha/apps/chatwoot/client/interfaces';
|
||||
import * as NodeCache from 'node-cache';
|
||||
|
||||
import { ConversationId, IConversationCache } from './IConversationCache';
|
||||
import { IConversationCache } from './IConversationCache';
|
||||
import { ContactIds } from '@waha/apps/chatwoot/client/ConversationService';
|
||||
|
||||
const cache: NodeCache = new NodeCache({
|
||||
stdTTL: 24 * 60 * 60, // 1 day
|
||||
@@ -24,7 +25,7 @@ class ConversationCache implements IConversationCache {
|
||||
cache.del(fullKey);
|
||||
}
|
||||
|
||||
get(key: string): ConversationId | null {
|
||||
get(key: string): ContactIds | null {
|
||||
const fullKey = this.fullKey(key);
|
||||
return cache.get(fullKey) || null;
|
||||
}
|
||||
@@ -34,7 +35,7 @@ class ConversationCache implements IConversationCache {
|
||||
return cache.has(fullKey);
|
||||
}
|
||||
|
||||
set(key: string, value: ConversationId): void {
|
||||
set(key: string, value: ContactIds): void {
|
||||
const fullKey = this.fullKey(key);
|
||||
cache.set(fullKey, value);
|
||||
}
|
||||
|
||||
+3
-3
@@ -1,8 +1,8 @@
|
||||
export type ConversationId = number;
|
||||
import { ContactIds } from '@waha/apps/chatwoot/client/ConversationService';
|
||||
|
||||
export interface IConversationCache {
|
||||
get(key: string): ConversationId | null;
|
||||
set(key: string, value: ConversationId): void;
|
||||
get(key: string): ContactIds | null;
|
||||
set(key: string, value: ContactIds): void;
|
||||
has(key: string): boolean;
|
||||
delete(key: string): void;
|
||||
clean(): void;
|
||||
|
||||
@@ -15,22 +15,31 @@ import { ChatWootInboxCommandsConsumer } from './consumers/inbox/commands';
|
||||
import { ChatWootInboxMessageCreatedConsumer } from './consumers/inbox/message_created';
|
||||
import { ChatWootInboxMessageDeletedConsumer } from './consumers/inbox/message_deleted';
|
||||
import { ChatWootInboxMessageUpdatedConsumer } from './consumers/inbox/message_updated';
|
||||
import { QueueName } from './consumers/QueueName';
|
||||
import { FlowProducerName, QueueName } from './consumers/QueueName';
|
||||
import { CheckVersionConsumer } from './consumers/scheduled/check.version';
|
||||
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 { QueueRegistry } from './services/QueueRegistry';
|
||||
import { ChatWootConversationCreatedConsumer } from './consumers/inbox/conversation_created';
|
||||
import { ChatWootConversationStatusChangedConsumer } from '@waha/apps/chatwoot/consumers/inbox/conversation_status_changed';
|
||||
import { TaskContactsPullConsumer } from '@waha/apps/chatwoot/consumers/task/contacts.pull';
|
||||
import { TaskMessagesPullConsumer } from '@waha/apps/chatwoot/consumers/task/messages.pull';
|
||||
import { BullModule } from '@nestjs/bullmq';
|
||||
import { QueueManager } from '@waha/apps/chatwoot/services/QueueManager';
|
||||
|
||||
const CONTROLLERS = [ChatwootWebhookController, ChatwootLocalesController];
|
||||
|
||||
const IMPORTS = lodash.flatten([
|
||||
BullModule.registerFlowProducer({
|
||||
name: FlowProducerName.MESSAGES_PULL_FLOW,
|
||||
}),
|
||||
RegisterAppQueue({
|
||||
name: QueueName.SCHEDULED_MESSAGE_CLEANUP,
|
||||
defaultJobOptions: merge(ExponentialRetriesJobOptions, JobRemoveOptions),
|
||||
@@ -39,6 +48,14 @@ const IMPORTS = lodash.flatten([
|
||||
name: QueueName.SCHEDULED_CHECK_VERSION,
|
||||
defaultJobOptions: merge(NoRetriesJobOptions, JobRemoveOptions),
|
||||
}),
|
||||
RegisterAppQueue({
|
||||
name: QueueName.TASK_CONTACTS_PULL,
|
||||
defaultJobOptions: merge(ExponentialRetriesJobOptions, JobRemoveOptions),
|
||||
}),
|
||||
RegisterAppQueue({
|
||||
name: QueueName.TASK_MESSAGES_PULL,
|
||||
defaultJobOptions: merge(ExponentialRetriesJobOptions, JobRemoveOptions),
|
||||
}),
|
||||
RegisterAppQueue({
|
||||
name: QueueName.WAHA_MESSAGE_ANY,
|
||||
defaultJobOptions: merge(ExponentialRetriesJobOptions, JobRemoveOptions),
|
||||
@@ -55,6 +72,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),
|
||||
@@ -93,17 +114,26 @@ const PROVIDERS = [
|
||||
ChatWootConversationCreatedConsumer,
|
||||
ChatWootConversationStatusChangedConsumer,
|
||||
ChatWootInboxCommandsConsumer,
|
||||
// Tasks
|
||||
TaskContactsPullConsumer,
|
||||
TaskMessagesPullConsumer,
|
||||
// WAHA
|
||||
WAHASessionStatusConsumer,
|
||||
WAHAMessageAnyConsumer,
|
||||
WAHAMessageReactionConsumer,
|
||||
WAHAMessageEditedConsumer,
|
||||
WAHAMessageRevokedConsumer,
|
||||
WAHAMessageAckConsumer,
|
||||
// Scheduled
|
||||
MessageCleanupConsumer,
|
||||
CheckVersionConsumer,
|
||||
// Services
|
||||
ChatWootWAHAQueueService,
|
||||
ChatWootQueueService,
|
||||
ChatWootScheduleService,
|
||||
ChatWootAppService,
|
||||
QueueRegistry,
|
||||
QueueManager,
|
||||
];
|
||||
|
||||
export const ChatWootExports = {
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
import { CommandContext } from '@waha/apps/chatwoot/cli/types';
|
||||
import { QueueName } from '@waha/apps/chatwoot/consumers/QueueName';
|
||||
import {
|
||||
ContactsPullOptions,
|
||||
ContactsPullStatusMessage,
|
||||
} from '@waha/apps/chatwoot/consumers/task/contacts.pull';
|
||||
import { Job, JobsOptions, Queue } from 'bullmq';
|
||||
import { ChatWootScheduleService } from '@waha/apps/chatwoot/services/ChatWootScheduleService';
|
||||
import { ILogger } from '@waha/apps/app_sdk/ILogger';
|
||||
import { JobDataTimeout } from '@waha/apps/app_sdk/AppConsumer';
|
||||
import {
|
||||
ExponentialRetriesJobOptions,
|
||||
JobRemoveOptions,
|
||||
merge,
|
||||
} from '@waha/apps/app_sdk/constants';
|
||||
|
||||
export async function ContactsPullStart(
|
||||
ctx: CommandContext,
|
||||
options: ContactsPullOptions,
|
||||
jobOptions: JobsOptions & JobDataTimeout,
|
||||
) {
|
||||
const jobId = ChatWootScheduleService.SingleJobId(ctx.data.app);
|
||||
const job: Job = await ctx.queues.contactsPull.getJob(jobId);
|
||||
if (job) {
|
||||
const state = await job.getState();
|
||||
const done = state === 'completed' || state === 'failed';
|
||||
if (!done) {
|
||||
const msg = ctx.l.r('cli.cmd.contacts.pull.already-running');
|
||||
await ctx.conversation.incoming(msg);
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove already done job
|
||||
await ContactsPullRemove(ctx.queues.contactsPull, ctx.data.app, ctx.logger);
|
||||
}
|
||||
|
||||
const opts: JobsOptions = merge(
|
||||
ExponentialRetriesJobOptions,
|
||||
JobRemoveOptions,
|
||||
jobOptions,
|
||||
);
|
||||
const data = {
|
||||
...ctx.data,
|
||||
timeout: {
|
||||
job: jobOptions.timeout.job,
|
||||
},
|
||||
options: options,
|
||||
};
|
||||
opts.jobId = jobId;
|
||||
await ctx.queues.contactsPull.add(QueueName.TASK_CONTACTS_PULL, data, opts);
|
||||
}
|
||||
|
||||
export async function ContactsPullStatus(ctx: CommandContext) {
|
||||
const jobId = ChatWootScheduleService.SingleJobId(ctx.data.app);
|
||||
const job: Job | null = await ctx.queues.contactsPull.getJob(jobId);
|
||||
|
||||
if (!job) {
|
||||
const msg = ctx.l.r('cli.cmd.contacts.status.not-found');
|
||||
await ctx.conversation.incoming(msg);
|
||||
return;
|
||||
}
|
||||
const state = await job.getState();
|
||||
const msg = ContactsPullStatusMessage(ctx.l, job, state);
|
||||
await ctx.conversation.incoming(msg);
|
||||
}
|
||||
|
||||
export async function ContactsPullRemove(
|
||||
queue: Queue,
|
||||
app: string,
|
||||
logger: ILogger,
|
||||
): Promise<boolean> {
|
||||
const jobId = ChatWootScheduleService.SingleJobId(app);
|
||||
const job: Job = await queue.getJob(jobId);
|
||||
if (!job) {
|
||||
logger.info('Pull Contacts job has already been removed');
|
||||
return false;
|
||||
}
|
||||
await job.remove();
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import { CommandContext } from '@waha/apps/chatwoot/cli/types';
|
||||
import { TKey } from '@waha/apps/chatwoot/i18n/templates';
|
||||
|
||||
export async function CommandDisabled(ctx: CommandContext, command: string) {
|
||||
const text = ctx.l.r('cli.cmd.disabled', { command: command });
|
||||
await ctx.conversation.incoming(text);
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
import * as lodash from 'lodash';
|
||||
import { CommandContext } from '@waha/apps/chatwoot/cli/types';
|
||||
import { QueueName } from '@waha/apps/chatwoot/consumers/QueueName';
|
||||
import {
|
||||
ChatID,
|
||||
MessagesPullOptions,
|
||||
MessagesPullStatusMessage,
|
||||
TaskActivity,
|
||||
} from '@waha/apps/chatwoot/consumers/task/messages.pull';
|
||||
import { ChatWootScheduleService } from '@waha/apps/chatwoot/services/ChatWootScheduleService';
|
||||
import { ILogger } from '@waha/apps/app_sdk/ILogger';
|
||||
import { JobDataTimeout } from '@waha/apps/app_sdk/AppConsumer';
|
||||
import { FlowProducer, Job, JobsOptions, Queue } from 'bullmq';
|
||||
import { FlowJob } from 'bullmq/dist/esm/interfaces';
|
||||
import { GetAllChatIDs, IsCommandsChat } from '@waha/apps/chatwoot/client/ids';
|
||||
import { ChainJobsOneAtATime } from '@waha/apps/app_sdk/JobUtils';
|
||||
import {
|
||||
ExponentialRetriesJobOptions,
|
||||
JobRemoveOptions,
|
||||
merge,
|
||||
} from '@waha/apps/app_sdk/constants';
|
||||
import { EngineHelper } from '@waha/apps/chatwoot/waha';
|
||||
import { WAHASelf, WAHASessionAPI } from '@waha/apps/app_sdk/waha/WAHASelf';
|
||||
import {
|
||||
IgnoreJidConfig,
|
||||
isNullJid,
|
||||
isPnUser,
|
||||
JidFilter,
|
||||
} from '@waha/core/utils/jids';
|
||||
|
||||
function oneJob(chat: string, data, opts: JobsOptions): FlowJob {
|
||||
data = lodash.cloneDeep(data);
|
||||
data.options.chat = chat;
|
||||
opts = lodash.cloneDeep(opts);
|
||||
return {
|
||||
name: chat,
|
||||
queueName: QueueName.TASK_MESSAGES_PULL,
|
||||
data: data,
|
||||
opts: merge(ExponentialRetriesJobOptions, JobRemoveOptions, opts),
|
||||
children: [],
|
||||
};
|
||||
}
|
||||
|
||||
export async function MessagesPullStart(
|
||||
ctx: CommandContext,
|
||||
options: MessagesPullOptions,
|
||||
jobOptions: JobsOptions & JobDataTimeout,
|
||||
) {
|
||||
const queue = ctx.queues.messagesPull;
|
||||
const jobId = ChatWootScheduleService.SingleJobId(ctx.data.app);
|
||||
const job: Job | null = await queue.getJob(jobId);
|
||||
|
||||
if (job) {
|
||||
const state = await job.getState();
|
||||
const done = state === 'completed' || state === 'failed';
|
||||
if (!done) {
|
||||
const msg = ctx.l.r('cli.cmd.messages.pull.already-running', {
|
||||
chat: job.data.options.chat,
|
||||
});
|
||||
await ctx.conversation.incoming(msg);
|
||||
return;
|
||||
}
|
||||
|
||||
await MessagesPullRemove(queue, ctx.data.app, ctx.logger);
|
||||
}
|
||||
const opts: JobsOptions = {
|
||||
...jobOptions,
|
||||
ignoreDependencyOnFailure: true,
|
||||
delay: 1_000,
|
||||
};
|
||||
const data = {
|
||||
...ctx.data,
|
||||
timeout: {
|
||||
job: jobOptions.timeout.job,
|
||||
},
|
||||
options: options,
|
||||
};
|
||||
const producer: FlowProducer = ctx.flows.messagesPull;
|
||||
let chats: string[] = [options.chat];
|
||||
|
||||
if (options.chat === ChatID.ALL) {
|
||||
if (IsCommandsChat(data.body)) {
|
||||
if (EngineHelper.SupportsAllChatForMessage()) {
|
||||
// keep "all" chats - that's fine
|
||||
} else {
|
||||
// "all" chats for engine that doesn't support it
|
||||
chats = await resolveAllToChats(ctx.waha, ctx.data.session, options);
|
||||
if (chats.length == 0) {
|
||||
throw new Error(ctx.l.r('cli.cmd.messages.pull.no-chats-found'));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// It's per-chat command, get the chat ids and run the pulling process
|
||||
chats = GetAllChatIDs(data.body?.conversation?.meta?.sender);
|
||||
chats = EngineHelper.FilterChatIdsForMessages(chats);
|
||||
}
|
||||
}
|
||||
const children = chats.map((chat) => oneJob(chat, data, opts));
|
||||
|
||||
let root: FlowJob;
|
||||
if (children.length == 1) {
|
||||
// Schedule one job, no "summary" job required
|
||||
root = children[0];
|
||||
} else {
|
||||
// Add "summary" parent job so we get the overall progress at the end
|
||||
root = oneJob(ChatID.SUMMARY, data, opts);
|
||||
root.children = [ChainJobsOneAtATime(children)];
|
||||
}
|
||||
root.opts.jobId = jobId;
|
||||
await producer.add(root);
|
||||
const activity = new TaskActivity(ctx.l, ctx.conversation);
|
||||
await activity.details(data);
|
||||
}
|
||||
|
||||
export async function MessagesPullStatus(ctx: CommandContext) {
|
||||
const queue = ctx.queues.messagesPull;
|
||||
const jobId = ChatWootScheduleService.SingleJobId(ctx.data.app);
|
||||
const job: Job | null = await queue.getJob(jobId);
|
||||
|
||||
if (!job) {
|
||||
const msg = ctx.l.r('cli.cmd.messages.status.not-found');
|
||||
await ctx.conversation.incoming(msg);
|
||||
return;
|
||||
}
|
||||
|
||||
const state = await job.getState();
|
||||
const msg = MessagesPullStatusMessage(ctx.l, job, state);
|
||||
await ctx.conversation.incoming(msg);
|
||||
}
|
||||
|
||||
export async function MessagesPullRemove(
|
||||
queue: Queue,
|
||||
app: string,
|
||||
logger: ILogger,
|
||||
): Promise<boolean> {
|
||||
const jobId = ChatWootScheduleService.SingleJobId(app);
|
||||
const job: Job | null = await queue.getJob(jobId);
|
||||
if (!job) {
|
||||
logger.info('Pull Messages job has already been removed');
|
||||
return false;
|
||||
}
|
||||
|
||||
await job.remove();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* WORKING ONLY FOR WEBJS
|
||||
*/
|
||||
async function resolveAllToChats(
|
||||
waha: WAHASelf,
|
||||
sessionName: string,
|
||||
options: MessagesPullOptions,
|
||||
): Promise<string[]> {
|
||||
const session = new WAHASessionAPI(sessionName, waha);
|
||||
let chats = await session.getChats({
|
||||
limit: undefined,
|
||||
offset: undefined,
|
||||
});
|
||||
// new chat last
|
||||
chats = lodash.sortBy(chats, (c) => c.timestamp || 0);
|
||||
|
||||
const gte = Date.now() - options.period.start;
|
||||
const result = [];
|
||||
const jids = new JidFilter(options.ignore);
|
||||
for (const chat of chats) {
|
||||
const id = chat.id._serialized;
|
||||
if (!jids.include(id)) {
|
||||
continue;
|
||||
}
|
||||
if (isNullJid(id)) {
|
||||
continue;
|
||||
}
|
||||
const timestamp = (chat.timestamp || Infinity) * 1000;
|
||||
if (timestamp < gte) {
|
||||
// Too old chat
|
||||
continue;
|
||||
}
|
||||
result.push(id);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
import * as lodash from 'lodash';
|
||||
import { QueueManager } from '@waha/apps/chatwoot/services/QueueManager';
|
||||
import { Locale } from '@waha/apps/chatwoot/i18n/locale';
|
||||
import { Conversation } from '@waha/apps/chatwoot/client/Conversation';
|
||||
import { QueueRegistry } from '@waha/apps/chatwoot/services/QueueRegistry';
|
||||
|
||||
function repr(name: string): string {
|
||||
// No chatwoot in the name always (it start with chatwoot)
|
||||
name = name.replace('chatwoot.', '');
|
||||
// No waha in the name
|
||||
name = name.replace('waha |', 'whatsapp |');
|
||||
return name;
|
||||
}
|
||||
|
||||
export interface QueueCommandContext {
|
||||
queues: {
|
||||
registry: QueueRegistry;
|
||||
};
|
||||
l: Locale;
|
||||
conversation: Conversation;
|
||||
}
|
||||
|
||||
export async function QueueStatus(ctx: QueueCommandContext, name: string) {
|
||||
const manager = new QueueManager(ctx.queues.registry);
|
||||
const names = manager.resolve(name);
|
||||
let result = await manager.status(names);
|
||||
for (const status of result) {
|
||||
status.name = repr(status.name);
|
||||
}
|
||||
// locked: true - last
|
||||
result = lodash.sortBy(result, [(x) => !!x.locked, 'name']);
|
||||
const msg = ctx.l.r('cli.cmd.queue.status.result', {
|
||||
queues: result,
|
||||
});
|
||||
await ctx.conversation.incoming(msg);
|
||||
}
|
||||
|
||||
export async function QueueStart(ctx: QueueCommandContext, name: string) {
|
||||
const manager = new QueueManager(ctx.queues.registry);
|
||||
const names = manager.resolve(name);
|
||||
await manager.resume(names);
|
||||
const msg = ctx.l.r('cli.cmd.queue.resumed');
|
||||
await ctx.conversation.activity(msg);
|
||||
}
|
||||
|
||||
export async function QueueStop(ctx: QueueCommandContext, name?: string) {
|
||||
const manager = new QueueManager(ctx.queues.registry);
|
||||
const names = manager.resolve(name);
|
||||
await manager.pause(names);
|
||||
const msg = ctx.l.r('cli.cmd.queue.paused');
|
||||
await ctx.conversation.activity(msg);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import { CommandContext } from '@waha/apps/chatwoot/cli/types';
|
||||
import { TKey } from '@waha/apps/chatwoot/i18n/templates';
|
||||
|
||||
export async function ServerStatus(ctx: CommandContext) {
|
||||
ctx.logger.info('Getting server version and status');
|
||||
const version = await ctx.waha.serverVersion();
|
||||
const status = await ctx.waha.serverStatus();
|
||||
const text = ctx.l.key(TKey.APP_SERVER_VERSION_AND_STATUS).render({
|
||||
version: JSON.stringify(version, null, 2),
|
||||
status: JSON.stringify(status, null, 2),
|
||||
});
|
||||
await ctx.conversation.incoming(text);
|
||||
}
|
||||
|
||||
export async function ServerReboot(ctx: CommandContext, force: boolean) {
|
||||
const type = force ? 'force' : 'graceful';
|
||||
const tkey = force ? TKey.APP_SERVER_REBOOT_FORCE : TKey.APP_SERVER_REBOOT;
|
||||
ctx.logger.info(`Rebooting server (${type})`);
|
||||
const text = ctx.l.key(tkey).render();
|
||||
await ctx.conversation.incoming(text);
|
||||
await ctx.waha.serverReboot(false);
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
import { CommandContext } from '@waha/apps/chatwoot/cli/types';
|
||||
import { TKey } from '@waha/apps/chatwoot/i18n/templates';
|
||||
import { SessionStatusEmoji } from '@waha/apps/chatwoot/emoji';
|
||||
import { AttachmentFromBuffer } from '@waha/apps/chatwoot/client/messages';
|
||||
import { MessageType } from '@waha/apps/chatwoot/client/types';
|
||||
|
||||
export async function SessionRestart(ctx: CommandContext) {
|
||||
await ctx.waha.restart(ctx.data.session);
|
||||
}
|
||||
|
||||
export async function SessionStart(ctx: CommandContext) {
|
||||
return SessionRestart(ctx);
|
||||
}
|
||||
|
||||
export async function SessionLogout(ctx: CommandContext) {
|
||||
ctx.logger.info(`Logging out session ${ctx.data.session}`);
|
||||
await ctx.waha.logout(ctx.data.session);
|
||||
const text = ctx.l.key(TKey.APP_LOGOUT_SUCCESS).render();
|
||||
await ctx.conversation.incoming(text);
|
||||
}
|
||||
|
||||
export async function SessionStop(ctx: CommandContext) {
|
||||
ctx.logger.info(`Stopping session ${ctx.data.session}`);
|
||||
await ctx.waha.stop(ctx.data.session);
|
||||
}
|
||||
|
||||
export async function SessionStatus(ctx: CommandContext) {
|
||||
ctx.logger.info(`Getting status for session ${ctx.data.session}`);
|
||||
const session = await ctx.waha.get(ctx.data.session);
|
||||
const emoji = SessionStatusEmoji(session.status);
|
||||
const text = ctx.l.key(TKey.APP_SESSION_CURRENT_STATUS).render({
|
||||
emoji: emoji,
|
||||
session: session.name,
|
||||
status: session.status,
|
||||
name: session.me?.pushName || 'Unknown',
|
||||
id: session.me?.id || 'No phone number',
|
||||
});
|
||||
await ctx.conversation.incoming(text);
|
||||
}
|
||||
|
||||
export async function SessionQR(ctx: CommandContext) {
|
||||
const content = await ctx.waha.qr(ctx.data.session);
|
||||
const message = AttachmentFromBuffer(content, 'qr.jpg');
|
||||
message.message_type = MessageType.INCOMING;
|
||||
await ctx.conversation.send(message);
|
||||
}
|
||||
|
||||
export async function SessionScreenshot(ctx: CommandContext) {
|
||||
ctx.logger.info(`Getting screenshot for session ${ctx.data.session}`);
|
||||
const content = await ctx.waha.screenshot(ctx.data.session);
|
||||
const message = AttachmentFromBuffer(content, 'screenshot.jpg');
|
||||
message.message_type = MessageType.INCOMING;
|
||||
await ctx.conversation.send(message);
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import { CommanderError } from 'commander';
|
||||
import { parse } from 'shell-quote';
|
||||
import { BufferedOutput } from '@waha/apps/chatwoot/cli/utils/BufferedOutput';
|
||||
import { CommandContext } from '@waha/apps/chatwoot/cli/types';
|
||||
import { ChatWootCommandsConfig } from '@waha/apps/chatwoot/dto/config.dto';
|
||||
import { BuildProgram } from '@waha/apps/chatwoot/cli/program.a';
|
||||
import { CHATWOOT_COMMAND_PREFIX } from '@waha/apps/chatwoot/env';
|
||||
|
||||
export async function runText(
|
||||
commands: ChatWootCommandsConfig,
|
||||
ctx: CommandContext,
|
||||
text: string,
|
||||
) {
|
||||
// Prepare text for cli like command
|
||||
text = text
|
||||
.split('\n') // split into lines
|
||||
.map((line) => line.trimStart()) // remove indentation
|
||||
.join(' ') // join with space
|
||||
.trim(); // final cleanup
|
||||
const output = new BufferedOutput();
|
||||
const program = BuildProgram(commands, ctx, output);
|
||||
const argv = parse(text);
|
||||
try {
|
||||
await program.parseAsync(argv, { from: 'user' });
|
||||
} catch (err) {
|
||||
if (err instanceof CommanderError) {
|
||||
return output;
|
||||
} else {
|
||||
throw err; // unexpected bug in your action code
|
||||
}
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
export const CommandPrefix = CHATWOOT_COMMAND_PREFIX;
|
||||
@@ -0,0 +1,70 @@
|
||||
import { ChatWootCommandsConfig } from '@waha/apps/chatwoot/dto/config.dto';
|
||||
import { CommandContext } from '@waha/apps/chatwoot/cli/types';
|
||||
import { Argument, Command, Option, OutputConfiguration } from 'commander';
|
||||
import {
|
||||
buildFormatHelp,
|
||||
fullCommandPath,
|
||||
} from '@waha/apps/chatwoot/cli/utils/help';
|
||||
import { AddSessionCommand } from '@waha/apps/chatwoot/cli/program.session';
|
||||
import { AddContactsCommand } from '@waha/apps/chatwoot/cli/program.contacts';
|
||||
import { AddMessagesCommand } from '@waha/apps/chatwoot/cli/program.messages';
|
||||
import { AddServerCommand } from '@waha/apps/chatwoot/cli/program.server';
|
||||
import { AddQueueCommand } from '@waha/apps/chatwoot/cli/program.queue';
|
||||
|
||||
function Program(ctx: CommandContext, output: OutputConfiguration) {
|
||||
const l = ctx.l;
|
||||
const program = new Command();
|
||||
program
|
||||
.name('')
|
||||
.description(l.r('cli.cmd.root.description'))
|
||||
.exitOverride()
|
||||
.configureOutput(output)
|
||||
.showSuggestionAfterError(true)
|
||||
.configureHelp({
|
||||
helpWidth: 200,
|
||||
styleOptionTerm(str: string) {
|
||||
return `- \`${str}\``;
|
||||
},
|
||||
styleArgumentTerm(str: string) {
|
||||
return `- **${str}**`;
|
||||
},
|
||||
styleUsage(str) {
|
||||
return `**${str}**`;
|
||||
},
|
||||
styleSubcommandTerm(str: string) {
|
||||
return `- **${str}**`;
|
||||
},
|
||||
styleSubcommandDescription(str: string) {
|
||||
return str ? `- ${str}` : str;
|
||||
},
|
||||
subcommandTerm: fullCommandPath,
|
||||
commandUsage(cmd) {
|
||||
// For the root program - no usage
|
||||
if (!cmd.parent)
|
||||
return `${l.r('cli.usage.command')} ${l.r('cli.usage.options')}`;
|
||||
// For subcommands, keep default behavior
|
||||
if (cmd.commands.length > 0) {
|
||||
return `${fullCommandPath(cmd)} ${l.r('cli.usage.command')} ${l.r(
|
||||
'cli.usage.options',
|
||||
)}`;
|
||||
}
|
||||
return `${fullCommandPath(cmd)} ${l.r('cli.usage.options')}`;
|
||||
},
|
||||
formatHelp: buildFormatHelp(l),
|
||||
});
|
||||
return program;
|
||||
}
|
||||
|
||||
export function BuildProgram(
|
||||
commands: ChatWootCommandsConfig,
|
||||
ctx: CommandContext,
|
||||
output: OutputConfiguration,
|
||||
) {
|
||||
const program = Program(ctx, output);
|
||||
AddSessionCommand(program, ctx);
|
||||
AddContactsCommand(program, ctx);
|
||||
AddMessagesCommand(program, ctx, commands.queue);
|
||||
AddQueueCommand(program, ctx, commands.queue);
|
||||
AddServerCommand(program, ctx, commands.server);
|
||||
return program;
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
import { Argument, Command, Option } from 'commander';
|
||||
import { CommandContext } from '@waha/apps/chatwoot/cli/types';
|
||||
import {
|
||||
JobAttemptsOption,
|
||||
JobTimeoutOption,
|
||||
ParseMS,
|
||||
NotNegativeNumber,
|
||||
ProgressOption,
|
||||
} from '@waha/apps/chatwoot/cli/utils/options';
|
||||
import {
|
||||
ContactsPullStart,
|
||||
ContactsPullStatus,
|
||||
} from '@waha/apps/chatwoot/cli/cmd.contacts';
|
||||
import { ContactsPullOptions } from '@waha/apps/chatwoot/consumers/task/contacts.pull';
|
||||
import { JobsOptions } from 'bullmq';
|
||||
import { JobDataTimeout } from '@waha/apps/app_sdk/AppConsumer';
|
||||
|
||||
export function AddContactsCommand(program: Command, ctx: CommandContext) {
|
||||
const l = ctx.l;
|
||||
const SyncGroup = l.r('cli.cmd.root.sub.sync');
|
||||
program.commandsGroup(SyncGroup);
|
||||
|
||||
program
|
||||
.command('contacts')
|
||||
.summary(l.r('cli.cmd.contacts.summary'))
|
||||
.description(l.r('cli.cmd.contacts.description'))
|
||||
.helpGroup(SyncGroup)
|
||||
.addArgument(
|
||||
new Argument(
|
||||
'[action]',
|
||||
l.r('cli.cmd.contacts.action.description'),
|
||||
).choices(['pull', 'status', 'help']),
|
||||
)
|
||||
.addOption(
|
||||
new Option(
|
||||
'-a, --avatar <mode>',
|
||||
l.r('cli.cmd.contacts.pull.option.avatar'),
|
||||
)
|
||||
.choices(['if-missing', 'update'])
|
||||
.preset('if-missing'),
|
||||
)
|
||||
.option('-g, --groups', l.r('cli.cmd.contacts.pull.option.groups'))
|
||||
.option('-l, --lids', l.r('cli.cmd.contacts.pull.option.lids'))
|
||||
.option(
|
||||
'--na, --no-attributes',
|
||||
l.r('cli.cmd.contacts.pull.option.no-attributes'),
|
||||
)
|
||||
.option(
|
||||
'-b, --batch <number>',
|
||||
l.r('cli.cmd.contacts.pull.option.batch'),
|
||||
NotNegativeNumber,
|
||||
100,
|
||||
)
|
||||
.addOption(
|
||||
ProgressOption(l.r('cli.cmd.contacts.pull.option.progress'), 100),
|
||||
)
|
||||
.addOption(new JobAttemptsOption(l, 6))
|
||||
.addOption(new JobTimeoutOption(l, '10m'))
|
||||
.addOption(
|
||||
new Option(
|
||||
'--dc, --delay-contact <duration>',
|
||||
l.r('cli.cmd.contacts.pull.option.delay-contact'),
|
||||
)
|
||||
.argParser(ParseMS)
|
||||
.default(ParseMS('0.1s'), '0.1s'),
|
||||
)
|
||||
.addOption(
|
||||
new Option(
|
||||
'--db, --delay-batch <duration>',
|
||||
l.r('cli.cmd.contacts.pull.option.delay-batch'),
|
||||
)
|
||||
.argParser(ParseMS)
|
||||
.default(ParseMS('1s'), '1s'),
|
||||
)
|
||||
.action(async (action, opts, cmd: Command) => {
|
||||
if (!action) {
|
||||
cmd.outputHelp();
|
||||
return;
|
||||
}
|
||||
|
||||
if (action === 'help') {
|
||||
cmd.outputHelp();
|
||||
return;
|
||||
}
|
||||
|
||||
if (action === 'status') {
|
||||
await ContactsPullStatus(ctx);
|
||||
return;
|
||||
}
|
||||
|
||||
const options: ContactsPullOptions = {
|
||||
batch: opts.batch,
|
||||
progress: opts.progress,
|
||||
avatar: opts.avatar,
|
||||
attributes: opts.attributes,
|
||||
contacts: {
|
||||
lids: opts.lids,
|
||||
groups: opts.groups,
|
||||
},
|
||||
delay: {
|
||||
contact: opts.delayContact,
|
||||
batch: opts.delayBatch,
|
||||
},
|
||||
};
|
||||
const jobOptions: JobsOptions & JobDataTimeout = {
|
||||
attempts: opts.attempts,
|
||||
timeout: {
|
||||
job: opts.timeout,
|
||||
},
|
||||
};
|
||||
await ContactsPullStart(ctx, options, jobOptions);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
import { Argument, Command, Option } from 'commander';
|
||||
import { CommandContext } from '@waha/apps/chatwoot/cli/types';
|
||||
import {
|
||||
JobAttemptsOption,
|
||||
JobTimeoutOption,
|
||||
ParseMS,
|
||||
NotNegativeNumber,
|
||||
ProgressOption,
|
||||
} from '@waha/apps/chatwoot/cli/utils/options';
|
||||
import {
|
||||
MessagesPullStart,
|
||||
MessagesPullStatus,
|
||||
} from '@waha/apps/chatwoot/cli/cmd.messages';
|
||||
import { MessagesPullOptions } from '@waha/apps/chatwoot/consumers/task/messages.pull';
|
||||
import { JobsOptions } from 'bullmq';
|
||||
import { JobDataTimeout } from '@waha/apps/app_sdk/AppConsumer';
|
||||
|
||||
export function AddMessagesCommand(
|
||||
program: Command,
|
||||
ctx: CommandContext,
|
||||
queue: boolean,
|
||||
) {
|
||||
const l = ctx.l;
|
||||
const SyncGroup = l.r('cli.cmd.root.sub.sync');
|
||||
program.commandsGroup(SyncGroup);
|
||||
|
||||
program
|
||||
.command('messages')
|
||||
.summary(l.r('cli.cmd.messages.summary'))
|
||||
.description(l.r('cli.cmd.messages.description'))
|
||||
.helpGroup(SyncGroup)
|
||||
.addArgument(
|
||||
new Argument(
|
||||
'[action]',
|
||||
l.r('cli.cmd.messages.action.description'),
|
||||
).choices(['pull', 'status', 'help']),
|
||||
)
|
||||
.addArgument(
|
||||
new Argument('[end]', l.r('cli.cmd.messages.pull.argument.end'))
|
||||
.argParser(ParseMS)
|
||||
.default(ParseMS('1d'), '1d'),
|
||||
)
|
||||
.addArgument(
|
||||
new Argument('[start]', l.r('cli.cmd.messages.pull.argument.start'))
|
||||
.argParser(ParseMS)
|
||||
.default(ParseMS('0d'), '0d'),
|
||||
)
|
||||
.option(
|
||||
'-c, --chat <chat>',
|
||||
l.r('cli.cmd.messages.pull.option.chat'),
|
||||
'all',
|
||||
)
|
||||
.option('-f, --force', l.r('cli.cmd.messages.pull.option.force'))
|
||||
.option('--nd, --no-dm', l.r('cli.cmd.messages.pull.option.no-dm'))
|
||||
.option('-g, --groups', l.r('cli.cmd.messages.pull.option.groups'))
|
||||
.option('--ch, --channels', l.r('cli.cmd.messages.pull.option.channels'))
|
||||
.option('-s, --status', l.r('cli.cmd.messages.pull.option.status'))
|
||||
.option('--bc, --broadcast', l.r('cli.cmd.messages.pull.option.broadcast'))
|
||||
.option('-m, --media', l.r('cli.cmd.messages.pull.option.media'))
|
||||
.option('--pause', l.r('cli.cmd.messages.pull.option.pause'))
|
||||
.option(
|
||||
'--rc, --resolve-conversations',
|
||||
l.r('cli.cmd.messages.pull.option.resolve-conversations'),
|
||||
)
|
||||
.option(
|
||||
'-b, --batch <number>',
|
||||
l.r('cli.cmd.messages.pull.option.batch'),
|
||||
NotNegativeNumber,
|
||||
100 as any,
|
||||
)
|
||||
.addOption(ProgressOption(l.r('cli.cmd.messages.pull.option.progress')))
|
||||
.addOption(new JobAttemptsOption(l, 6))
|
||||
.addOption(new JobTimeoutOption(l, '10m'))
|
||||
.addOption(
|
||||
new Option(
|
||||
'--tm, --timeout-media <duration>',
|
||||
l.r('cli.cmd.messages.pull.option.timeout-media'),
|
||||
)
|
||||
.argParser(ParseMS)
|
||||
.default(ParseMS('30s'), '30s'),
|
||||
)
|
||||
.action(async (action, end, start, opts, cmd: Command) => {
|
||||
if (!action) {
|
||||
cmd.outputHelp();
|
||||
return;
|
||||
}
|
||||
|
||||
if (action === 'help') {
|
||||
cmd.outputHelp();
|
||||
return;
|
||||
}
|
||||
|
||||
if (action === 'status') {
|
||||
await MessagesPullStatus(ctx);
|
||||
return;
|
||||
}
|
||||
if (end > start) {
|
||||
// Swap
|
||||
const tmp = end;
|
||||
end = start;
|
||||
start = tmp;
|
||||
}
|
||||
|
||||
if (opts.pause && !queue) {
|
||||
await ctx.conversation.incoming(
|
||||
l.r('cli.cmd.messages.pull.error.pause-no-queue'),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const options: MessagesPullOptions = {
|
||||
chat: opts.chat,
|
||||
progress: opts.progress,
|
||||
period: {
|
||||
end: end,
|
||||
start: start,
|
||||
},
|
||||
media: opts.media,
|
||||
force: opts.force,
|
||||
pause: opts.pause,
|
||||
timeout: {
|
||||
media: opts.timeoutMedia,
|
||||
},
|
||||
batch: opts.batch,
|
||||
ignore: {
|
||||
dm: !opts.dm,
|
||||
status: !opts.status,
|
||||
groups: !opts.groups,
|
||||
channels: !opts.channels,
|
||||
broadcast: !opts.broadcast,
|
||||
},
|
||||
resolveConversations: opts.resolveConversations,
|
||||
};
|
||||
const jobOptions: JobsOptions & JobDataTimeout = {
|
||||
attempts: opts.attempts,
|
||||
timeout: {
|
||||
job: opts.timeout,
|
||||
},
|
||||
};
|
||||
await MessagesPullStart(ctx, options, jobOptions);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
import { Argument, Command } from 'commander';
|
||||
import { CommandContext } from '@waha/apps/chatwoot/cli/types';
|
||||
import { CommandDisabled } from '@waha/apps/chatwoot/cli/cmd.disabled';
|
||||
import {
|
||||
QueueStart,
|
||||
QueueStatus,
|
||||
QueueStop,
|
||||
} from '@waha/apps/chatwoot/cli/cmd.queue';
|
||||
|
||||
export function AddQueueCommand(
|
||||
program: Command,
|
||||
ctx: CommandContext,
|
||||
enabled: boolean,
|
||||
) {
|
||||
const l = ctx.l;
|
||||
const QueueGroup = l.r('cli.cmd.root.sub.queue');
|
||||
program.commandsGroup(QueueGroup);
|
||||
|
||||
program
|
||||
.command('queue', { hidden: !enabled })
|
||||
.alias('q')
|
||||
.summary(l.r('cli.cmd.queue.summary'))
|
||||
.description(l.r('cli.cmd.queue.description'))
|
||||
.helpGroup(QueueGroup)
|
||||
.addArgument(
|
||||
new Argument('[action]', l.r('cli.cmd.queue.action.description'))
|
||||
.choices(['status', 'start', 'stop', 'help'])
|
||||
.default('help'),
|
||||
)
|
||||
.addArgument(
|
||||
new Argument('[name]', l.r('cli.cmd.queue.argument.name')).default(''),
|
||||
)
|
||||
.action(async function (this: Command, action: string, name: string) {
|
||||
if (!action) {
|
||||
this.outputHelp();
|
||||
return;
|
||||
}
|
||||
|
||||
if (action === 'help') {
|
||||
this.outputHelp();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!enabled) {
|
||||
await CommandDisabled(ctx, 'queue');
|
||||
return;
|
||||
}
|
||||
|
||||
if (action === 'status') {
|
||||
await QueueStatus(ctx, name);
|
||||
return;
|
||||
}
|
||||
|
||||
if (action === 'start') {
|
||||
await QueueStart(ctx, name);
|
||||
return;
|
||||
}
|
||||
|
||||
if (action === 'stop') {
|
||||
await QueueStop(ctx, name);
|
||||
return;
|
||||
}
|
||||
|
||||
this.outputHelp();
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import { Command } from 'commander';
|
||||
import { CommandContext } from '@waha/apps/chatwoot/cli/types';
|
||||
import { CommandDisabled } from '@waha/apps/chatwoot/cli/cmd.disabled';
|
||||
import { ServerReboot, ServerStatus } from '@waha/apps/chatwoot/cli/cmd.server';
|
||||
|
||||
export function AddServerCommand(
|
||||
program: Command,
|
||||
ctx: CommandContext,
|
||||
enabled: boolean,
|
||||
) {
|
||||
const l = ctx.l;
|
||||
const ServerGroup = l.r('cli.cmd.root.sub.server');
|
||||
program.commandsGroup(ServerGroup);
|
||||
|
||||
const server = program
|
||||
.command('server', { hidden: !enabled })
|
||||
.description(l.r('cli.cmd.server.description'))
|
||||
.helpGroup(ServerGroup);
|
||||
if (!enabled) {
|
||||
// Do not show help, show disabled ASAP
|
||||
server.action(() => CommandDisabled(ctx, 'server'));
|
||||
}
|
||||
server
|
||||
.command('status')
|
||||
.description(l.r('cli.cmd.server.status.description'))
|
||||
.action(() =>
|
||||
enabled ? ServerStatus(ctx) : CommandDisabled(ctx, 'server status'),
|
||||
);
|
||||
server
|
||||
.command('reboot')
|
||||
.description(l.r('cli.cmd.server.reboot.description'))
|
||||
.option('-f, --force', l.r('cli.cmd.server.reboot.option.force'), false)
|
||||
.action((opts) =>
|
||||
enabled
|
||||
? ServerReboot(ctx, opts.force)
|
||||
: CommandDisabled(ctx, 'server reboot'),
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
import { Command } from 'commander';
|
||||
import { CommandContext } from '@waha/apps/chatwoot/cli/types';
|
||||
import {
|
||||
SessionLogout,
|
||||
SessionQR,
|
||||
SessionRestart,
|
||||
SessionScreenshot,
|
||||
SessionStart,
|
||||
SessionStatus,
|
||||
SessionStop,
|
||||
} from '@waha/apps/chatwoot/cli/cms.session';
|
||||
|
||||
export function AddSessionCommand(program: Command, ctx: CommandContext) {
|
||||
const l = ctx.l;
|
||||
const SessionGroup = l.r('cli.cmd.root.sub.session');
|
||||
program.commandsGroup(SessionGroup);
|
||||
|
||||
program
|
||||
.command('status')
|
||||
.alias('1')
|
||||
.description(l.r('cli.cmd.session.status.description'))
|
||||
.helpGroup(SessionGroup)
|
||||
.action(() => SessionStatus(ctx));
|
||||
program
|
||||
.command('restart')
|
||||
.alias('2')
|
||||
.description(l.r('cli.cmd.session.restart.description'))
|
||||
.helpGroup(SessionGroup)
|
||||
.action(() => SessionRestart(ctx));
|
||||
program
|
||||
.command('start')
|
||||
.alias('3')
|
||||
.description(l.r('cli.cmd.session.start.description'))
|
||||
.helpGroup(SessionGroup)
|
||||
.action(() => SessionStart(ctx));
|
||||
program
|
||||
.command('stop')
|
||||
.alias('4')
|
||||
.description(l.r('cli.cmd.session.stop.description'))
|
||||
.helpGroup(SessionGroup)
|
||||
.action(() => SessionStop(ctx));
|
||||
program
|
||||
.command('logout')
|
||||
.alias('5')
|
||||
.description(l.r('cli.cmd.session.logout.description'))
|
||||
.helpGroup(SessionGroup)
|
||||
.action(() => SessionLogout(ctx));
|
||||
program
|
||||
.command('qr')
|
||||
.alias('6')
|
||||
.description(l.r('cli.cmd.session.qr.description'))
|
||||
.helpGroup(SessionGroup)
|
||||
.action(() => SessionQR(ctx));
|
||||
program
|
||||
.command('screenshot')
|
||||
.alias('7')
|
||||
.description(l.r('cli.cmd.session.screenshot.description'))
|
||||
.helpGroup(SessionGroup)
|
||||
.action(() => SessionScreenshot(ctx));
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { WAHASelf } from '@waha/apps/app_sdk/waha/WAHASelf';
|
||||
import { Locale } from '@waha/apps/chatwoot/i18n/locale';
|
||||
import { Conversation } from '../client/Conversation';
|
||||
import { ILogger } from '@waha/apps/app_sdk/ILogger';
|
||||
import { FlowProducer, Queue } from 'bullmq';
|
||||
import { InboxData } from '@waha/apps/chatwoot/consumers/types';
|
||||
import { QueueRegistry } from '@waha/apps/chatwoot/services/QueueRegistry';
|
||||
|
||||
export interface CommandContext {
|
||||
data: InboxData;
|
||||
logger: ILogger;
|
||||
l: Locale;
|
||||
waha: WAHASelf;
|
||||
conversation: Conversation;
|
||||
queues: {
|
||||
registry: QueueRegistry;
|
||||
contactsPull: Queue;
|
||||
messagesPull: Queue;
|
||||
};
|
||||
flows: {
|
||||
messagesPull: FlowProducer;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import { OutputConfiguration } from 'commander';
|
||||
|
||||
export class BufferedOutput implements OutputConfiguration {
|
||||
private outBuffer: string[] = [];
|
||||
private errBuffer: string[] = [];
|
||||
|
||||
writeOut = (str: string): void => {
|
||||
this.outBuffer.push(str);
|
||||
};
|
||||
|
||||
writeErr = (str: string): void => {
|
||||
this.errBuffer.push(str);
|
||||
};
|
||||
|
||||
outputError = (str: string, write: (s: string) => void) => {
|
||||
this.errBuffer.push(str);
|
||||
};
|
||||
|
||||
get out() {
|
||||
return this.outBuffer.join('');
|
||||
}
|
||||
|
||||
get err() {
|
||||
return this.errBuffer.join('');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
import { Command, Help, Option } from 'commander';
|
||||
import { Locale } from '@waha/apps/chatwoot/i18n/locale';
|
||||
|
||||
export function fullCommandPath(sub: Command): string {
|
||||
const parts: string[] = [];
|
||||
let cmd: Command | null = sub;
|
||||
// collect names up to (but not including) the root program
|
||||
const name = cmd.name();
|
||||
if (name) parts.push(name);
|
||||
while (cmd && cmd.parent) {
|
||||
cmd = cmd.parent!;
|
||||
const name = cmd.name();
|
||||
if (name && name != 'program') parts.push(name);
|
||||
}
|
||||
return parts.reverse().join(' ');
|
||||
}
|
||||
|
||||
export function buildFormatHelp(l: Locale) {
|
||||
/**
|
||||
* Commands first, then Arguments, then Options
|
||||
* @param cmd
|
||||
* @param helper
|
||||
*/
|
||||
|
||||
return function formatHelp(this: Help, cmd: Command, helper: Help) {
|
||||
const termWidth = helper.padWidth(cmd, helper);
|
||||
const helpWidth = helper.helpWidth ?? 80;
|
||||
|
||||
const callFormatItem = (term: string, description: string) =>
|
||||
helper.formatItem(term, termWidth, description, helper);
|
||||
let output: string[] = [];
|
||||
|
||||
const commandDescription = helper.commandDescription(cmd);
|
||||
if (commandDescription.length > 0) {
|
||||
output = output.concat([
|
||||
helper.boxWrap(
|
||||
helper.styleCommandDescription(commandDescription),
|
||||
helpWidth,
|
||||
),
|
||||
'',
|
||||
]);
|
||||
}
|
||||
|
||||
const usage = helper.commandUsage(cmd);
|
||||
if (usage) {
|
||||
output.push(
|
||||
`${
|
||||
helper.styleTitle(l.r('cli.help.usage.title')) + '\n'
|
||||
} ${helper.styleUsage(helper.commandUsage(cmd))}`,
|
||||
);
|
||||
output.push('');
|
||||
}
|
||||
|
||||
const commandGroups = this.groupItems(
|
||||
cmd.commands as Command[],
|
||||
helper.visibleCommands(cmd),
|
||||
(sub) => sub.helpGroup() || l.r('cli.help.commands.defaultGroup'),
|
||||
);
|
||||
commandGroups.forEach((commands, group) => {
|
||||
if (!commands.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const commandLines = commands.map((sub) => {
|
||||
const commandTerm = helper.styleSubcommandTerm(
|
||||
helper.subcommandTerm(sub),
|
||||
);
|
||||
const description = helper.subcommandDescription(sub);
|
||||
const styledDescription = description
|
||||
? helper.styleSubcommandDescription(description)
|
||||
: '';
|
||||
return `${commandTerm} ${styledDescription}`;
|
||||
});
|
||||
|
||||
output.push(helper.styleTitle(group));
|
||||
output.push(...commandLines);
|
||||
output.push('');
|
||||
});
|
||||
|
||||
const argumentList = helper
|
||||
.visibleArguments(cmd)
|
||||
.map((argument) =>
|
||||
callFormatItem(
|
||||
helper.styleArgumentTerm(helper.argumentTerm(argument)),
|
||||
helper.styleArgumentDescription(helper.argumentDescription(argument)),
|
||||
),
|
||||
);
|
||||
output = output.concat(
|
||||
this.formatItemList(
|
||||
l.r('cli.help.arguments.title'),
|
||||
argumentList,
|
||||
helper,
|
||||
),
|
||||
);
|
||||
|
||||
const optionGroups = this.groupItems(
|
||||
cmd.options as Option[],
|
||||
helper.visibleOptions(cmd),
|
||||
(option) =>
|
||||
option.helpGroupHeading ?? l.r('cli.help.options.defaultGroup'),
|
||||
);
|
||||
optionGroups.forEach((options, group) => {
|
||||
const optionList = options.map((option) =>
|
||||
callFormatItem(
|
||||
helper.styleOptionTerm(helper.optionTerm(option)),
|
||||
helper.styleOptionDescription(helper.optionDescription(option)),
|
||||
),
|
||||
);
|
||||
output = output.concat(this.formatItemList(group, optionList, helper));
|
||||
});
|
||||
|
||||
if (helper.showGlobalOptions) {
|
||||
const globalOptionList = helper
|
||||
.visibleGlobalOptions(cmd)
|
||||
.map((option) =>
|
||||
callFormatItem(
|
||||
helper.styleOptionTerm(helper.optionTerm(option)),
|
||||
helper.styleOptionDescription(helper.optionDescription(option)),
|
||||
),
|
||||
);
|
||||
output = output.concat(
|
||||
this.formatItemList(
|
||||
l.r('cli.help.globalOptions.title'),
|
||||
globalOptionList,
|
||||
helper,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return output.join('\n');
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
import * as ms from 'ms';
|
||||
import { Option } from 'commander';
|
||||
import { Locale } from '@waha/apps/chatwoot/i18n/locale';
|
||||
|
||||
/**
|
||||
* Parse human string into milliseconds
|
||||
*/
|
||||
export function ParseMS(value: string) {
|
||||
const duration = ms(value);
|
||||
if (duration == null) throw new Error(`Invalid duration: "${value}"`);
|
||||
if (duration < 0) throw new Error(`Duration cannot be negative: "${value}"`);
|
||||
return duration;
|
||||
}
|
||||
|
||||
export function ParseSeconds(value: string): number {
|
||||
const duration = ParseMS(value);
|
||||
return Math.floor(duration / 1000);
|
||||
}
|
||||
|
||||
export class JobAttemptsOption extends Option {
|
||||
constructor(l: Locale, def: number) {
|
||||
super('--at, --attempts <number>', l.r('cli.cmd.options.job.attempts'));
|
||||
this.argParser(NotNegativeNumber);
|
||||
this.default(def);
|
||||
}
|
||||
}
|
||||
|
||||
export class JobTimeoutOption extends Option {
|
||||
constructor(l: Locale, def: string) {
|
||||
super('-t, --timeout <duration>', l.r('cli.cmd.options.job.timeout'));
|
||||
this.argParser(ParseMS);
|
||||
this.default(ParseMS(def));
|
||||
}
|
||||
}
|
||||
|
||||
export function ProgressOption(
|
||||
description: string,
|
||||
def: number = 1000,
|
||||
): Option {
|
||||
return new Option('-p, --progress [number]', description)
|
||||
.argParser(NotNegativeNumber)
|
||||
.default(def);
|
||||
}
|
||||
|
||||
export function NotNegativeNumber(value: string): number {
|
||||
const n = parseInt(value, 10);
|
||||
if (isNaN(n)) {
|
||||
throw new Error(`Invalid number: "${value}"`);
|
||||
}
|
||||
if (n < 0) {
|
||||
throw new Error(`Number must be positive or 0: "${value}"`);
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
export function PositiveNumber(value: string): number {
|
||||
const n = parseInt(value, 10);
|
||||
if (isNaN(n)) {
|
||||
throw new Error(`Invalid number: "${value}"`);
|
||||
}
|
||||
if (n <= 0) {
|
||||
throw new Error(`Number must be positive: "${value}"`);
|
||||
}
|
||||
return n;
|
||||
}
|
||||
@@ -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',
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -3,18 +3,21 @@ import ChatwootClient, {
|
||||
public_contact_create_update_payload,
|
||||
} from '@figuro/chatwoot-sdk';
|
||||
import { ILogger } from '@waha/apps/app_sdk/ILogger';
|
||||
import { ContactService } from '@waha/apps/chatwoot/client/ContactService';
|
||||
import {
|
||||
AvatarUpdateMode,
|
||||
ContactService,
|
||||
} from '@waha/apps/chatwoot/client/ContactService';
|
||||
import { Conversation } from '@waha/apps/chatwoot/client/Conversation';
|
||||
import { ConversationService } from '@waha/apps/chatwoot/client/ConversationService';
|
||||
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/i18n/locale';
|
||||
|
||||
import { CacheForConfig } from '../cache/ConversationCache';
|
||||
import {
|
||||
ConversationId,
|
||||
IConversationCache,
|
||||
} from '../cache/IConversationCache';
|
||||
import { IConversationCache } from '../cache/IConversationCache';
|
||||
|
||||
export interface ContactInfo {
|
||||
ChatId(): string;
|
||||
@@ -40,9 +43,9 @@ export class ContactConversationService {
|
||||
this.cache = CacheForConfig(config);
|
||||
}
|
||||
|
||||
private async upsertByContactInfo(
|
||||
public async upsertByContactInfo(
|
||||
contactInfo: ContactInfo,
|
||||
): Promise<ConversationId> {
|
||||
): Promise<ContactIds> {
|
||||
const chatId = contactInfo.ChatId();
|
||||
|
||||
// Check cache for chat id
|
||||
@@ -50,65 +53,57 @@ export class ContactConversationService {
|
||||
return this.cache.get(chatId);
|
||||
}
|
||||
|
||||
//
|
||||
// Find or create contact
|
||||
//
|
||||
let contact = await this.contactService.searchByAnyID(chatId);
|
||||
if (!contact) {
|
||||
const request = await contactInfo.PublicContactCreate();
|
||||
contact = await this.contactService.create(chatId, request);
|
||||
}
|
||||
let [cwContact, created] =
|
||||
await this.contactService.findOrCreateContact(contactInfo);
|
||||
|
||||
// Update custom attributes - always
|
||||
const attributes = await contactInfo.Attributes();
|
||||
this.logger.info(
|
||||
`Updating if required contact custom attributes for chat.id: ${chatId}, contact.id: ${contact.data.id}`,
|
||||
this.logger.debug(
|
||||
`Updating if required contact custom attributes for chat.id: ${chatId}, contact.id: ${cwContact.data.id}`,
|
||||
);
|
||||
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().catch((err) => {
|
||||
this.logger.warn(
|
||||
`Error getting avatar for chat.id from WhatsApp: ${chatId}`,
|
||||
);
|
||||
this.logger.warn(err);
|
||||
return null;
|
||||
});
|
||||
if (avatarUrl) {
|
||||
this.contactService.updateAvatarUrlSafe(contact.data.id, avatarUrl);
|
||||
}
|
||||
}
|
||||
|
||||
this.logger.info(
|
||||
`Using contact for chat.id: ${chatId}, contact.id: ${contact.data.id}, contact.sourceId: ${contact.sourceId}`,
|
||||
const attributes = await contactInfo.Attributes();
|
||||
await this.contactService.upsertCustomAttributes(
|
||||
cwContact.data,
|
||||
attributes,
|
||||
);
|
||||
await this.contactService.updateAvatar(
|
||||
cwContact,
|
||||
contactInfo,
|
||||
AvatarUpdateMode.IF_MISSING,
|
||||
);
|
||||
this.logger.debug(
|
||||
`Using contact for chat.id: ${chatId}, contact.id: ${cwContact.data.id}, contact.sourceId: ${cwContact.sourceId}`,
|
||||
);
|
||||
|
||||
//
|
||||
// Get or create a conversation for this inbox
|
||||
//
|
||||
const conversation = await this.conversationService.upsert({
|
||||
id: contact.data.id,
|
||||
sourceId: contact.sourceId,
|
||||
id: cwContact.data.id,
|
||||
sourceId: cwContact.sourceId,
|
||||
});
|
||||
this.logger.info(
|
||||
`Using conversation for chat.id: ${chatId}, conversation.id: ${conversation.id}, contact.id: ${contact.sourceId}`,
|
||||
this.logger.debug(
|
||||
`Using conversation for chat.id: ${chatId}, conversation.id: ${conversation.id}, contact.id: ${cwContact.sourceId}`,
|
||||
);
|
||||
|
||||
// Save to cache
|
||||
this.cache.set(chatId, conversation.id);
|
||||
return conversation.id;
|
||||
const ids = {
|
||||
id: conversation.id,
|
||||
sourceId: cwContact.sourceId,
|
||||
};
|
||||
this.cache.set(chatId, ids);
|
||||
return ids;
|
||||
}
|
||||
|
||||
public async ConversationByContact(
|
||||
contactInfo: ContactInfo,
|
||||
): Promise<Conversation> {
|
||||
const chatId = contactInfo.ChatId();
|
||||
const conversationId = await this.upsertByContactInfo(contactInfo);
|
||||
const ids = await this.upsertByContactInfo(contactInfo);
|
||||
const conversation = new Conversation(
|
||||
this.accountAPI,
|
||||
this.config.accountId,
|
||||
conversationId,
|
||||
ids.id,
|
||||
ids.sourceId,
|
||||
);
|
||||
conversation.onError = (err) => {
|
||||
if (err instanceof ChatWootAPIError) {
|
||||
@@ -116,7 +111,7 @@ export class ContactConversationService {
|
||||
this.cache.delete(chatId);
|
||||
this.logger.error(`ApiError: ${err.message}`);
|
||||
this.logger.error(
|
||||
`ApiError occurred, invalidating cache for chat.id: ${chatId}, conversation.id: ${conversationId}`,
|
||||
`ApiError occurred, invalidating cache for chat.id: ${chatId}, conversation.id: ${ids.id}, source.id: ${ids.sourceId}`,
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -140,24 +135,31 @@ export class ContactConversationService {
|
||||
}
|
||||
|
||||
public ResetCache(chatIds: Array<string>) {
|
||||
this.logger.info(`Resetting cache chat ids: ${chatIds.join(', ')}`);
|
||||
this.logger.debug(`Resetting cache chat ids: ${chatIds.join(', ')}`);
|
||||
for (const chatId of chatIds) {
|
||||
this.cache.delete(chatId);
|
||||
}
|
||||
}
|
||||
|
||||
public ResetMismatchedCache(chatIds: Array<string>, value: ConversationId) {
|
||||
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 !== value) {
|
||||
this.logger.info(
|
||||
`Resetting cache for chat id: ${chatId}, value changed from ${current} to ${value}`,
|
||||
if (current.id !== contactId) {
|
||||
this.logger.debug(
|
||||
`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);
|
||||
}
|
||||
}
|
||||
@@ -14,12 +14,18 @@ import * as lodash from 'lodash';
|
||||
|
||||
import { AttributeKey } from '../const';
|
||||
import { E164Parser } from '@waha/core/utils/PhoneJidNormalizer';
|
||||
import { ContactInfo } from '@waha/apps/chatwoot/client/ContactConversationService';
|
||||
|
||||
export interface ContactResponse {
|
||||
data: generic_id & contact;
|
||||
sourceId: string;
|
||||
}
|
||||
|
||||
export enum AvatarUpdateMode {
|
||||
IF_MISSING,
|
||||
ALWAYS,
|
||||
}
|
||||
|
||||
export class ContactService {
|
||||
constructor(
|
||||
private config: ChatWootAPIConfig,
|
||||
@@ -28,6 +34,20 @@ export class ContactService {
|
||||
private logger: ILogger,
|
||||
) {}
|
||||
|
||||
async findOrCreateContact(
|
||||
contactInfo: ContactInfo,
|
||||
): Promise<[ContactResponse, boolean]> {
|
||||
const chatId = contactInfo.ChatId();
|
||||
let contact = await this.searchByAnyID(chatId);
|
||||
if (contact) {
|
||||
return [contact, false];
|
||||
}
|
||||
|
||||
const request = await contactInfo.PublicContactCreate();
|
||||
contact = await this.create(chatId, request);
|
||||
return [contact, true];
|
||||
}
|
||||
|
||||
async searchByAnyID(chatId: string): Promise<ContactResponse | null> {
|
||||
const payload: any[] = [
|
||||
{
|
||||
@@ -136,8 +156,32 @@ export class ContactService {
|
||||
};
|
||||
}
|
||||
|
||||
public updateAvatarUrlSafe(contactId, avatarUrl: string) {
|
||||
this.accountAPI.contacts
|
||||
public async updateAvatar(
|
||||
contact: ContactResponse,
|
||||
contactInfo: ContactInfo,
|
||||
mode: AvatarUpdateMode,
|
||||
): Promise<boolean> {
|
||||
// Update Avatar if nothing, but keep the original one if any
|
||||
if (contact.data.thumbnail && mode == AvatarUpdateMode.IF_MISSING) {
|
||||
return false;
|
||||
}
|
||||
const chatId = contactInfo.ChatId();
|
||||
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) {
|
||||
return false;
|
||||
}
|
||||
const success = await this.updateAvatarUrlSafe(contact.data.id, avatarUrl);
|
||||
return success;
|
||||
}
|
||||
|
||||
public updateAvatarUrlSafe(contactId, avatarUrl: string): Promise<boolean> {
|
||||
return this.accountAPI.contacts
|
||||
.update({
|
||||
accountId: this.config.accountId,
|
||||
id: contactId,
|
||||
@@ -145,11 +189,15 @@ export class ContactService {
|
||||
avatar_url: avatarUrl,
|
||||
},
|
||||
})
|
||||
.then(() => {
|
||||
return true;
|
||||
})
|
||||
.catch((e) => {
|
||||
this.logger.warn(
|
||||
`Error updating avatar_url for contact.id: ${contactId}`,
|
||||
);
|
||||
this.logger.warn(e);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -4,14 +4,19 @@ import { MessageType } from '@waha/apps/chatwoot/client/types';
|
||||
|
||||
export class Conversation {
|
||||
public onError: (e: any) => void;
|
||||
public overrideIncoming: any = null;
|
||||
|
||||
constructor(
|
||||
private accountAPI: ChatwootClient,
|
||||
private accountId: number,
|
||||
public conversationId: number,
|
||||
public sourceId: string | null = null,
|
||||
) {}
|
||||
|
||||
public async send(data: conversation_message_create) {
|
||||
if (data.message_type === MessageType.INCOMING && this.overrideIncoming) {
|
||||
data = { ...data, ...this.overrideIncoming };
|
||||
}
|
||||
try {
|
||||
const message = await this.accountAPI.messages.create({
|
||||
accountId: this.accountId,
|
||||
@@ -28,10 +33,13 @@ export class Conversation {
|
||||
}
|
||||
|
||||
public async incoming(text: string) {
|
||||
const data: conversation_message_create = {
|
||||
let data: conversation_message_create = {
|
||||
content: text,
|
||||
message_type: MessageType.INCOMING as any,
|
||||
};
|
||||
if (this.overrideIncoming) {
|
||||
data = { ...data, ...this.overrideIncoming };
|
||||
}
|
||||
return this.send(data);
|
||||
}
|
||||
|
||||
@@ -43,11 +51,22 @@ export class Conversation {
|
||||
return this.send(data);
|
||||
}
|
||||
|
||||
public async outgoing(text: string) {
|
||||
public async note(text: string) {
|
||||
const data: conversation_message_create = {
|
||||
content: text,
|
||||
private: true,
|
||||
message_type: MessageType.OUTGOING as any,
|
||||
};
|
||||
return this.send(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Force a note message (private outgoing) for all communications
|
||||
*/
|
||||
public forceNote() {
|
||||
this.overrideIncoming = {
|
||||
private: true,
|
||||
message_type: MessageType.OUTGOING as any,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,8 @@ import {
|
||||
} 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'>;
|
||||
|
||||
@@ -38,7 +40,7 @@ export class ConversationService {
|
||||
inboxIdentifier: this.config.inboxIdentifier,
|
||||
contactIdentifier: contact.sourceId,
|
||||
});
|
||||
this.logger.info(
|
||||
this.logger.debug(
|
||||
`Created conversation.id: ${conversation.id} for contact.id: ${contact.id}, contact.sourceId: ${contact.sourceId}`,
|
||||
);
|
||||
return conversation;
|
||||
@@ -49,9 +51,38 @@ export class ConversationService {
|
||||
if (!conversation) {
|
||||
conversation = await this.create(contact);
|
||||
}
|
||||
this.logger.info(
|
||||
this.logger.debug(
|
||||
`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;
|
||||
}
|
||||
}
|
||||
|
||||
async resolve(conversationId: number): Promise<any> {
|
||||
return this.accountAPI.conversations.toggleStatus({
|
||||
accountId: this.config.accountId,
|
||||
conversationId: conversationId,
|
||||
data: {
|
||||
status: 'resolved',
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import { AttributeKey } from '@waha/apps/chatwoot/const';
|
||||
import { AttributeKey, INBOX_CONTACT_CHAT_ID } from '@waha/apps/chatwoot/const';
|
||||
import * as lodash from 'lodash';
|
||||
import { WhatsAppMessage } from '@waha/apps/chatwoot/storage';
|
||||
import { buildMessageId } from '@waha/core/engines/noweb/session.noweb.core';
|
||||
import { isLidUser } from '@waha/core/utils/jids';
|
||||
@@ -22,7 +23,7 @@ export function GetAllChatIDs(contact: any): Array<string> {
|
||||
attrs[AttributeKey.WA_JID],
|
||||
attrs[AttributeKey.WA_LID],
|
||||
];
|
||||
return ids.filter(Boolean);
|
||||
return lodash.uniq(ids.filter(Boolean));
|
||||
}
|
||||
|
||||
export function FindChatID(contact: any): string | null {
|
||||
@@ -55,3 +56,9 @@ export function ContactAttr(chatId: string): AttributeKey {
|
||||
return AttributeKey.WA_JID;
|
||||
}
|
||||
}
|
||||
|
||||
export function IsCommandsChat(body): boolean {
|
||||
const sender = body?.conversation?.meta?.sender;
|
||||
const chatId = FindChatID(sender);
|
||||
return chatId === INBOX_CONTACT_CHAT_ID;
|
||||
}
|
||||
@@ -5,6 +5,12 @@ export enum QueueName {
|
||||
SCHEDULED_MESSAGE_CLEANUP = 'chatwoot.scheduled | message.cleanup',
|
||||
SCHEDULED_CHECK_VERSION = 'chatwoot.scheduled | check.version',
|
||||
|
||||
//
|
||||
// Task
|
||||
//
|
||||
TASK_CONTACTS_PULL = 'chatwoot.task | contacts.pull',
|
||||
TASK_MESSAGES_PULL = 'chatwoot.task | messages.pull',
|
||||
|
||||
//
|
||||
// WAHA Events
|
||||
//
|
||||
@@ -13,6 +19,7 @@ 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
|
||||
//
|
||||
@@ -26,3 +33,7 @@ export enum QueueName {
|
||||
INBOX_MESSAGE_DELETED = 'chatwoot.inbox | message_deleted',
|
||||
INBOX_COMMANDS = 'chatwoot.inbox | commands',
|
||||
}
|
||||
|
||||
export enum FlowProducerName {
|
||||
MESSAGES_PULL_FLOW = 'messages.pull.flow',
|
||||
}
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
ChatIDNotFoundForContactError,
|
||||
PhoneNumberNotFoundInWhatsAppError,
|
||||
} from '@waha/apps/chatwoot/errors';
|
||||
import { WAHASessionAPI } from '@waha/apps/chatwoot/session/WAHASelf';
|
||||
import { WAHASessionAPI } from '@waha/apps/app_sdk/waha/WAHASelf';
|
||||
import { SessionManager } from '@waha/core/abc/manager.abc';
|
||||
import { RMutexService } from '@waha/modules/rmutex/rmutex.service';
|
||||
import { Job } from 'bullmq';
|
||||
@@ -18,6 +18,7 @@ import { PinoLogger } from 'nestjs-pino';
|
||||
|
||||
import { AppRepository } from '../../storage';
|
||||
import { TKey } from '@waha/apps/chatwoot/i18n/templates';
|
||||
import { Conversation } from '@waha/apps/chatwoot/client/Conversation';
|
||||
|
||||
/**
|
||||
* Base class for ChatWoot inbox consumers
|
||||
@@ -90,6 +91,12 @@ export abstract class ChatWootInboxMessageConsumer extends AppConsumer {
|
||||
}
|
||||
}
|
||||
|
||||
protected conversationForReport(container, body): Conversation {
|
||||
return container
|
||||
.ContactConversationService()
|
||||
.ConversationById(body.conversation.id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Report an error for a message
|
||||
*/
|
||||
@@ -98,9 +105,7 @@ export abstract class ChatWootInboxMessageConsumer extends AppConsumer {
|
||||
const header: string = this.ErrorHeaderKey()
|
||||
? container.Locale().key(this.ErrorHeaderKey()).render()
|
||||
: err.message || `${err}`;
|
||||
const conversation = container
|
||||
.ContactConversationService()
|
||||
.ConversationById(body.conversation.id);
|
||||
const conversation = this.conversationForReport(container, body);
|
||||
const reporter = container.ChatWootErrorReporter(job);
|
||||
await reporter.ReportError(
|
||||
conversation,
|
||||
@@ -118,9 +123,7 @@ export abstract class ChatWootInboxMessageConsumer extends AppConsumer {
|
||||
}
|
||||
|
||||
const container = await this.DIContainer(job, job.data.app);
|
||||
const conversation = container
|
||||
.ContactConversationService()
|
||||
.ConversationById(body.conversation.id);
|
||||
const conversation = this.conversationForReport(container, body);
|
||||
const reporter = container.ChatWootErrorReporter(job);
|
||||
await reporter.ReportSucceeded(conversation, body.message_type, body.id);
|
||||
}
|
||||
|
||||
@@ -1,21 +1,20 @@
|
||||
import { Processor } from '@nestjs/bullmq';
|
||||
import { InjectFlowProducer, InjectQueue, Processor } from '@nestjs/bullmq';
|
||||
import { JOB_CONCURRENCY } from '@waha/apps/app_sdk/constants';
|
||||
import { ILogger } from '@waha/apps/app_sdk/ILogger';
|
||||
import { ContactConversationService } from '@waha/apps/chatwoot/client/ContactConversationService';
|
||||
import { AttachmentFromBuffer } from '@waha/apps/chatwoot/client/messages';
|
||||
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 {
|
||||
FlowProducerName,
|
||||
QueueName,
|
||||
} from '@waha/apps/chatwoot/consumers/QueueName';
|
||||
import { DIContainer } from '@waha/apps/chatwoot/di/DIContainer';
|
||||
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 { FlowProducer, Job, Queue } from 'bullmq';
|
||||
import { PinoLogger } from 'nestjs-pino';
|
||||
import { TKey } from '@waha/apps/chatwoot/i18n/templates';
|
||||
import { ChatWootCommandsConfig } from '@waha/apps/chatwoot/dto/config.dto';
|
||||
import { CommandPrefix, runText } from '@waha/apps/chatwoot/cli';
|
||||
import { CommandContext } from '@waha/apps/chatwoot/cli/types';
|
||||
import { IsCommandsChat } from '@waha/apps/chatwoot/client/ids';
|
||||
import { QueueRegistry } from '@waha/apps/chatwoot/services/QueueRegistry';
|
||||
|
||||
@Processor(QueueName.INBOX_COMMANDS, { concurrency: JOB_CONCURRENCY })
|
||||
export class ChatWootInboxCommandsConsumer extends ChatWootInboxMessageConsumer {
|
||||
@@ -23,6 +22,9 @@ export class ChatWootInboxCommandsConsumer extends ChatWootInboxMessageConsumer
|
||||
protected readonly manager: SessionManager,
|
||||
log: PinoLogger,
|
||||
rmutex: RMutexService,
|
||||
private readonly queueRegistry: QueueRegistry,
|
||||
@InjectFlowProducer(FlowProducerName.MESSAGES_PULL_FLOW)
|
||||
private readonly messagesPullFlow: FlowProducer,
|
||||
) {
|
||||
super(manager, log, rmutex, 'ChatWootInboxCommandsConsumer');
|
||||
}
|
||||
@@ -31,196 +33,47 @@ export class ChatWootInboxCommandsConsumer extends ChatWootInboxMessageConsumer
|
||||
return null;
|
||||
}
|
||||
|
||||
protected conversationForReport(container, body) {
|
||||
const conversation = super.conversationForReport(container, body);
|
||||
if (!IsCommandsChat(body)) {
|
||||
conversation.forceNote();
|
||||
}
|
||||
return conversation;
|
||||
}
|
||||
|
||||
protected async Process(container: DIContainer, body, job: Job) {
|
||||
const handler = new SessionCommandHandler(
|
||||
container.Logger(),
|
||||
container.ContactConversationService(),
|
||||
job.data.session,
|
||||
container.Locale(),
|
||||
container.WAHASelf(),
|
||||
container.ChatWootConfig().commands,
|
||||
);
|
||||
return await handler.handle(body);
|
||||
}
|
||||
}
|
||||
|
||||
const CommandServerPrefix = 'server ';
|
||||
|
||||
enum Command {
|
||||
STATUS = 'status',
|
||||
RESTART = 'restart',
|
||||
START = 'start',
|
||||
STOP = 'stop',
|
||||
LOGOUT = 'logout',
|
||||
QR = 'qr',
|
||||
SCREENSHOT = 'screenshot',
|
||||
HELP = 'help',
|
||||
SERVER_STATUS = 'server status',
|
||||
SERVER_REBOOT = 'server reboot',
|
||||
SERVER_REBOOT_FORCE = 'server reboot force',
|
||||
}
|
||||
|
||||
const CommandMap: Record<string, Command> = {
|
||||
// Status
|
||||
[Command.STATUS]: Command.STATUS,
|
||||
['1']: Command.STATUS,
|
||||
// Restart
|
||||
[Command.RESTART]: Command.RESTART,
|
||||
['2']: Command.RESTART,
|
||||
// Start
|
||||
[Command.START]: Command.START,
|
||||
['3']: Command.START,
|
||||
// Stop
|
||||
[Command.STOP]: Command.STOP,
|
||||
['4']: Command.STOP,
|
||||
// Logout
|
||||
[Command.LOGOUT]: Command.LOGOUT,
|
||||
['5']: Command.LOGOUT,
|
||||
// QR
|
||||
[Command.QR]: Command.QR,
|
||||
['6']: Command.QR,
|
||||
// Screenshot
|
||||
[Command.SCREENSHOT]: Command.SCREENSHOT,
|
||||
['7']: Command.SCREENSHOT,
|
||||
// Help
|
||||
[Command.HELP]: Command.HELP,
|
||||
['8']: Command.HELP,
|
||||
// Server commands
|
||||
[Command.SERVER_STATUS]: Command.SERVER_STATUS,
|
||||
[Command.SERVER_REBOOT]: Command.SERVER_REBOOT,
|
||||
[Command.SERVER_REBOOT_FORCE]: Command.SERVER_REBOOT_FORCE,
|
||||
};
|
||||
|
||||
class CommandIsNotImplementedError extends Error {
|
||||
constructor(private command: string) {
|
||||
super(`Command is not implemented`);
|
||||
}
|
||||
}
|
||||
|
||||
class SessionCommandHandler {
|
||||
constructor(
|
||||
private logger: ILogger,
|
||||
private repo: ContactConversationService,
|
||||
private session: string,
|
||||
private l: Locale,
|
||||
private waha: WAHASelf,
|
||||
private commands: ChatWootCommandsConfig,
|
||||
) {}
|
||||
|
||||
async handle(message: any) {
|
||||
const cmd = message.content?.toLowerCase();
|
||||
const command = this.findCommand(cmd) || Command.HELP;
|
||||
let cmd = body.content;
|
||||
cmd = cmd.startsWith(CommandPrefix) ? cmd.slice(CommandPrefix.length) : cmd;
|
||||
this.logger.info(
|
||||
`Executing command ${command} for session ${this.session}`,
|
||||
`Executing command '${cmd}' for session ${job.data.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;
|
||||
const repo = container.ContactConversationService();
|
||||
let conversation = repo.ConversationById(body.conversation.id);
|
||||
if (!IsCommandsChat(body)) {
|
||||
conversation.forceNote();
|
||||
}
|
||||
switch (command) {
|
||||
case Command.RESTART:
|
||||
await this.waha.restart(this.session);
|
||||
break;
|
||||
case Command.START:
|
||||
await this.waha.restart(this.session);
|
||||
break;
|
||||
case Command.LOGOUT:
|
||||
this.logger.info(`Logging out session ${this.session}`);
|
||||
await this.waha.logout(this.session);
|
||||
const text = this.l.key(TKey.APP_LOGOUT_SUCCESS).render();
|
||||
await conversation.incoming(text);
|
||||
break;
|
||||
case Command.STOP:
|
||||
this.logger.info(`Stopping session ${this.session}`);
|
||||
await this.waha.stop(this.session);
|
||||
break;
|
||||
case Command.STATUS: {
|
||||
this.logger.info(`Getting status for session ${this.session}`);
|
||||
const session = await this.waha.get(this.session);
|
||||
const emoji = SessionStatusEmoji(session.status);
|
||||
const text = this.l.key(TKey.APP_SESSION_CURRENT_STATUS).render({
|
||||
emoji: emoji,
|
||||
session: session.name,
|
||||
status: session.status,
|
||||
name: session.me?.pushName || 'Unknown',
|
||||
id: session.me?.id || 'No phone number',
|
||||
});
|
||||
await conversation.incoming(text);
|
||||
break;
|
||||
}
|
||||
case Command.SERVER_STATUS: {
|
||||
this.logger.info('Getting server version and status');
|
||||
const version = await this.waha.serverVersion();
|
||||
const status = await this.waha.serverStatus();
|
||||
const text = this.l.key(TKey.APP_SERVER_VERSION_AND_STATUS).render({
|
||||
version: JSON.stringify(version, null, 2),
|
||||
status: JSON.stringify(status, null, 2),
|
||||
});
|
||||
await conversation.incoming(text);
|
||||
break;
|
||||
}
|
||||
case Command.SERVER_REBOOT: {
|
||||
this.logger.info('Rebooting server (graceful)');
|
||||
const text = this.l.key(TKey.APP_SERVER_REBOOT).render();
|
||||
await conversation.incoming(text);
|
||||
await this.waha.serverReboot(false);
|
||||
break;
|
||||
}
|
||||
case Command.SERVER_REBOOT_FORCE: {
|
||||
this.logger.info('Rebooting server (force)');
|
||||
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 content = await this.waha.qr(this.session);
|
||||
const message = AttachmentFromBuffer(content, 'qr.jpg');
|
||||
message.message_type = MessageType.INCOMING;
|
||||
await conversation.send(message);
|
||||
break;
|
||||
}
|
||||
case Command.SCREENSHOT: {
|
||||
this.logger.info(`Getting screenshot for session ${this.session}`);
|
||||
const content = await this.waha.screenshot(this.session);
|
||||
const message = AttachmentFromBuffer(content, 'screenshot.jpg');
|
||||
message.message_type = MessageType.INCOMING;
|
||||
await conversation.send(message);
|
||||
break;
|
||||
}
|
||||
case Command.HELP: {
|
||||
const text = this.l
|
||||
.key(TKey.APP_COMMANDS_LIST)
|
||||
.render({ commands: this.commands });
|
||||
await conversation.incoming(text);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new CommandIsNotImplementedError(cmd);
|
||||
const ctx: CommandContext = {
|
||||
data: job.data,
|
||||
logger: this.logger,
|
||||
l: container.Locale(),
|
||||
waha: container.WAHASelf(),
|
||||
conversation: conversation,
|
||||
queues: {
|
||||
registry: this.queueRegistry,
|
||||
contactsPull: this.queueRegistry.queue(QueueName.TASK_CONTACTS_PULL),
|
||||
messagesPull: this.queueRegistry.queue(QueueName.TASK_MESSAGES_PULL),
|
||||
},
|
||||
flows: {
|
||||
messagesPull: this.messagesPullFlow,
|
||||
},
|
||||
};
|
||||
const commands = container.ChatWootConfig().commands;
|
||||
const output = await runText(commands, ctx, cmd);
|
||||
if (output.out) {
|
||||
await conversation.incoming(output.out);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
if (output.err) {
|
||||
await conversation.incoming(output.err);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
findCommand(text: string): Command {
|
||||
// Remove empty from end
|
||||
text = text.trim();
|
||||
// Remove empty from start
|
||||
text = text.trimStart();
|
||||
// Remove empty from end
|
||||
text = text.trimEnd();
|
||||
return CommandMap[text] || null;
|
||||
}
|
||||
}
|
||||
@@ -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 { EngineHelper } from '@waha/apps/chatwoot/session';
|
||||
import { WAHASessionAPI } from '@waha/apps/chatwoot/session/WAHASelf';
|
||||
import { EngineHelper } from '@waha/apps/chatwoot/waha';
|
||||
import { WAHASessionAPI } from '@waha/apps/app_sdk/waha/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 {
|
||||
@@ -80,7 +80,10 @@ export class MessageHandler {
|
||||
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}`,
|
||||
);
|
||||
|
||||
@@ -8,7 +8,7 @@ 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 { WAHASessionAPI } from '@waha/apps/chatwoot/session/WAHASelf';
|
||||
import { WAHASessionAPI } from '@waha/apps/app_sdk/waha/WAHASelf';
|
||||
import { MessageMappingService } from '@waha/apps/chatwoot/storage';
|
||||
import { SessionManager } from '@waha/core/abc/manager.abc';
|
||||
import { RMutexService } from '@waha/modules/rmutex/rmutex.service';
|
||||
@@ -56,6 +56,13 @@ class MessageDeletedHandler {
|
||||
message_id: body.id,
|
||||
});
|
||||
if (!messages || messages.length == 0) {
|
||||
if (body.private) {
|
||||
// Private notes can have no whatsapp messages
|
||||
this.logger.info(
|
||||
`No WhatsApp message to delete for private note '${body.id}'`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
throw Error(
|
||||
`No WhatsApp message found for Chatwoot message '${body.id}'`,
|
||||
);
|
||||
|
||||
@@ -9,7 +9,7 @@ import { RMutexService } from '@waha/modules/rmutex/rmutex.service';
|
||||
import { Job } from 'bullmq';
|
||||
import { PinoLogger } from 'nestjs-pino';
|
||||
|
||||
import { WAHASessionAPI } from '../../session/WAHASelf';
|
||||
import { WAHASessionAPI } from '../../../app_sdk/waha/WAHASelf';
|
||||
import { TKey } from '@waha/apps/chatwoot/i18n/templates';
|
||||
|
||||
@Processor(QueueName.INBOX_MESSAGE_UPDATED, { concurrency: JOB_CONCURRENCY })
|
||||
|
||||
@@ -4,7 +4,6 @@ import { HasBeenRetried } from '@waha/apps/app_sdk/JobUtils';
|
||||
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 { SessionManager } from '@waha/core/abc/manager.abc';
|
||||
import { RMutexService } from '@waha/modules/rmutex/rmutex.service';
|
||||
import { Job } from 'bullmq';
|
||||
|
||||
@@ -19,7 +19,7 @@ import { TKey } from '@waha/apps/chatwoot/i18n/templates';
|
||||
concurrency: JOB_CONCURRENCY,
|
||||
})
|
||||
export class MessageCleanupConsumer extends ChatWootScheduledConsumer {
|
||||
REMOVE_AFTER_DAYS = 14;
|
||||
REMOVE_AFTER_DAYS = 365;
|
||||
|
||||
constructor(manager: SessionManager, log: PinoLogger, rmutex: RMutexService) {
|
||||
super(manager, log, rmutex, MessageCleanupConsumer.name);
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
import { AppConsumer } from '@waha/apps/app_sdk/AppConsumer';
|
||||
import { JobLoggerWrapper } from '@waha/apps/app_sdk/JobLoggerWrapper';
|
||||
import { HasBeenRetried } from '@waha/apps/app_sdk/JobUtils';
|
||||
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 { 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';
|
||||
import { SignalRace } from '@waha/utils/abortable';
|
||||
import { IsCommandsChat } from '@waha/apps/chatwoot/client/ids';
|
||||
|
||||
/**
|
||||
* Base class for ChatWoot background task consumers
|
||||
*/
|
||||
export abstract class ChatWootTaskConsumer extends AppConsumer {
|
||||
protected appRepository: AppRepository;
|
||||
|
||||
constructor(
|
||||
protected readonly manager: SessionManager,
|
||||
log: PinoLogger,
|
||||
rmutex: RMutexService,
|
||||
protected readonly consumerName: string,
|
||||
) {
|
||||
super('ChatWoot', consumerName, log, rmutex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the DIContainer for the specified app.
|
||||
*/
|
||||
protected async DIContainer(job: Job, appId: string): Promise<DIContainer> {
|
||||
const knex = this.manager.store.getWAHADatabase();
|
||||
this.appRepository = new AppRepository(knex);
|
||||
const logger = new JobLoggerWrapper(job, this.logger);
|
||||
const app = await this.appRepository.getById(appId);
|
||||
return new DIContainer(app.pk, app.config, logger, knex);
|
||||
}
|
||||
|
||||
protected abstract ErrorHeaderKey(): TKey | null;
|
||||
|
||||
/**
|
||||
* Get the function that processes the scheduled job
|
||||
* This method must be implemented by subclasses
|
||||
*/
|
||||
protected abstract Process(
|
||||
container: DIContainer,
|
||||
job: Job,
|
||||
signal: AbortSignal,
|
||||
): Promise<any>;
|
||||
|
||||
/**
|
||||
* Process the job
|
||||
* This method is called by the queue processor
|
||||
*/
|
||||
async processJob(job: Job<ScheduledData, any, string>): Promise<any> {
|
||||
const signal = this.signal(job);
|
||||
return SignalRace(this.ProcessAndReportStatus(job, signal), signal);
|
||||
}
|
||||
|
||||
private async ProcessAndReportStatus(job: Job, signal: AbortSignal) {
|
||||
try {
|
||||
const container = await this.DIContainer(job, job.data.app);
|
||||
const result = await this.Process(container, job, signal);
|
||||
await this.ReportErrorRecovered(job);
|
||||
return result;
|
||||
} catch (err) {
|
||||
this.ReportErrorForJob(job, err).catch((exc) =>
|
||||
this.logger.error(`Failed to report error for job ${job.id}: ${exc}`),
|
||||
);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
protected conversationForReport(container, body) {
|
||||
const conversation = container
|
||||
.ContactConversationService()
|
||||
.ConversationById(body.conversation.id);
|
||||
if (!IsCommandsChat(body)) {
|
||||
conversation.forceNote();
|
||||
}
|
||||
return conversation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Report an error for a scheduled job
|
||||
*/
|
||||
protected async ReportErrorForJob(job: Job, err: any) {
|
||||
const container = await this.DIContainer(job, job.data.app);
|
||||
let header = this.ErrorHeaderKey()
|
||||
? container.Locale().key(this.ErrorHeaderKey()).render()
|
||||
: err.message || `${err}`;
|
||||
header = `${job.queueName}: ${header}`;
|
||||
|
||||
const conversation = this.conversationForReport(container, job.data.body);
|
||||
|
||||
const reporter = container.ChatWootErrorReporter(job);
|
||||
await reporter.ReportError(conversation, header, MessageType.INCOMING, err);
|
||||
throw err;
|
||||
}
|
||||
|
||||
protected async ReportErrorRecovered(job: Job) {
|
||||
if (!HasBeenRetried(job)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const container = await this.DIContainer(job, job.data.app);
|
||||
const conversation = this.conversationForReport(container, job.data.body);
|
||||
|
||||
const reporter = container.ChatWootErrorReporter(job);
|
||||
await reporter.ReportSucceeded(conversation, MessageType.INCOMING);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,306 @@
|
||||
import { Processor } from '@nestjs/bullmq';
|
||||
import * as lodash from 'lodash';
|
||||
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 { SessionManager } from '@waha/core/abc/manager.abc';
|
||||
import { RMutexService } from '@waha/modules/rmutex/rmutex.service';
|
||||
import { Job, JobState } from 'bullmq';
|
||||
import { PinoLogger } from 'nestjs-pino';
|
||||
|
||||
import { ChatWootTaskConsumer } from '@waha/apps/chatwoot/consumers/task/base';
|
||||
import { TKey } from '@waha/apps/chatwoot/i18n/templates';
|
||||
import { WAHASessionAPI } from '@waha/apps/app_sdk/waha/WAHASelf';
|
||||
import { WhatsAppContactInfo } from '@waha/apps/chatwoot/contacts/WhatsAppContactInfo';
|
||||
import { ContactSortField } from '@waha/structures/contacts.dto';
|
||||
import { SortOrder } from '@waha/structures/pagination.dto';
|
||||
import {
|
||||
AvatarUpdateMode,
|
||||
ContactService,
|
||||
} from '@waha/apps/chatwoot/client/ContactService';
|
||||
import { Conversation } from '@waha/apps/chatwoot/client/Conversation';
|
||||
import { Locale } from '@waha/apps/chatwoot/i18n/locale';
|
||||
import { ILogger } from '@waha/apps/app_sdk/ILogger';
|
||||
import { JobLink } from '@waha/apps/app_sdk/JobUtils';
|
||||
import { sleep } from '@waha/utils/promiseTimeout';
|
||||
import { isJidGroup, isLidUser, isPnUser } from '@waha/core/utils/jids';
|
||||
import {
|
||||
ArrayPaginator,
|
||||
PaginatorParams,
|
||||
} from '@waha/apps/app_sdk/waha/Paginator';
|
||||
import { EngineHelper } from '@waha/apps/chatwoot/waha';
|
||||
|
||||
export interface ContactsPullOptions {
|
||||
batch: number;
|
||||
progress: number | null;
|
||||
avatar: null | 'if-missing' | 'update';
|
||||
attributes: boolean;
|
||||
contacts: {
|
||||
groups: boolean;
|
||||
lids: boolean;
|
||||
};
|
||||
delay: {
|
||||
contact: number;
|
||||
batch: number;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Task consumer that pulls WhatsApp contacts into ChatWoot
|
||||
* Runs in batches to keep the job resumable and observable in ChatWoot
|
||||
*/
|
||||
@Processor(QueueName.TASK_CONTACTS_PULL, { concurrency: JOB_CONCURRENCY })
|
||||
export class TaskContactsPullConsumer extends ChatWootTaskConsumer {
|
||||
constructor(manager: SessionManager, log: PinoLogger, rmutex: RMutexService) {
|
||||
super(manager, log, rmutex, TaskContactsPullConsumer.name);
|
||||
}
|
||||
|
||||
protected ErrorHeaderKey(): TKey {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the contact pull job
|
||||
* Streams batches of contacts from WAHA into ChatWoot storage
|
||||
*/
|
||||
protected async Process(
|
||||
container: DIContainer,
|
||||
job: Job,
|
||||
signal?: AbortSignal,
|
||||
): Promise<any> {
|
||||
const options: ContactsPullOptions = job.data.options;
|
||||
const locale = container.Locale();
|
||||
const waha = container.WAHASelf();
|
||||
const session = new WAHASessionAPI(job.data.session, waha);
|
||||
const conversation = this.conversationForReport(container, job.data.body);
|
||||
const handler = new ContactsPullHandler(
|
||||
signal,
|
||||
container.Logger(),
|
||||
conversation,
|
||||
session,
|
||||
locale,
|
||||
container.ContactService(),
|
||||
);
|
||||
return await handler.handle(options, job);
|
||||
}
|
||||
}
|
||||
|
||||
interface Progress {
|
||||
created: number;
|
||||
updated: number;
|
||||
skipped: number;
|
||||
errors: number;
|
||||
avatar: {
|
||||
updated: number;
|
||||
};
|
||||
}
|
||||
|
||||
function total(progress: Progress) {
|
||||
return (
|
||||
progress.created + progress.updated + progress.skipped + progress.errors
|
||||
);
|
||||
}
|
||||
|
||||
const NullProgress: Progress = {
|
||||
created: 0,
|
||||
updated: 0,
|
||||
skipped: 0,
|
||||
errors: 0,
|
||||
avatar: {
|
||||
updated: 0,
|
||||
},
|
||||
};
|
||||
|
||||
class ContactsPullHandler {
|
||||
private activity: TaskActivity;
|
||||
|
||||
constructor(
|
||||
private signal: AbortSignal,
|
||||
private logger: ILogger,
|
||||
private conversation: Conversation,
|
||||
private session: WAHASessionAPI,
|
||||
private l: Locale,
|
||||
private contactService: ContactService,
|
||||
) {
|
||||
this.activity = new TaskActivity(this.l, this.conversation);
|
||||
}
|
||||
|
||||
async handle(options: ContactsPullOptions, job) {
|
||||
const batch = options.batch;
|
||||
let progress = lodash.merge({}, NullProgress, job.progress);
|
||||
this.logger.debug(
|
||||
`Pulling contacts for session ${job.data.session} with batch size ${batch}...`,
|
||||
);
|
||||
if (lodash.isEqual(progress, NullProgress)) {
|
||||
await this.activity.started(progress);
|
||||
}
|
||||
|
||||
const query = {
|
||||
sortBy: ContactSortField.ID,
|
||||
sortOrder: SortOrder.ASC,
|
||||
limit: batch,
|
||||
offset: 0,
|
||||
};
|
||||
const params: PaginatorParams = {
|
||||
processed: total(progress),
|
||||
};
|
||||
const paginator = new ArrayPaginator<any>(params);
|
||||
const contacts = paginator.iterate((processed: number) => {
|
||||
query.offset = processed;
|
||||
return this.session.getContacts(query, {
|
||||
signal: this.signal,
|
||||
});
|
||||
});
|
||||
|
||||
for await (const contact of contacts) {
|
||||
this.signal.throwIfAborted();
|
||||
|
||||
//
|
||||
// Show progress
|
||||
//
|
||||
const thetotal = total(progress);
|
||||
if (options.progress && thetotal) {
|
||||
if (thetotal % options.progress == 0) {
|
||||
await this.activity.progress(progress);
|
||||
}
|
||||
}
|
||||
// Delay on batch
|
||||
if (thetotal && options.delay.batch) {
|
||||
if (thetotal % batch == 0) {
|
||||
await sleep(options.delay.batch);
|
||||
}
|
||||
}
|
||||
|
||||
// Process
|
||||
try {
|
||||
if (!EngineHelper.ContactIsMy(contact)) {
|
||||
progress.skipped += 1;
|
||||
await job.updateProgress(progress);
|
||||
continue;
|
||||
}
|
||||
const chatId = contact.id;
|
||||
if (isJidGroup(chatId)) {
|
||||
if (!options.contacts.groups) {
|
||||
this.logger.debug(`Skipping group contact ${chatId}...`);
|
||||
progress.skipped += 1;
|
||||
await job.updateProgress(progress);
|
||||
continue;
|
||||
}
|
||||
} else if (isLidUser(chatId)) {
|
||||
if (!options.contacts.lids) {
|
||||
this.logger.debug(`Skipping LID contact ${chatId}...`);
|
||||
progress.skipped += 1;
|
||||
await job.updateProgress(progress);
|
||||
continue;
|
||||
}
|
||||
} else if (!isPnUser(chatId)) {
|
||||
this.logger.info(`Skipping non-phone-number contact ${chatId}...`);
|
||||
progress.skipped += 1;
|
||||
await job.updateProgress(progress);
|
||||
continue;
|
||||
}
|
||||
|
||||
this.logger.debug(`Pulling ${chatId}...`);
|
||||
const result = await this.pullOneContact(options, chatId);
|
||||
progress.created += result.created;
|
||||
progress.updated += result.updated;
|
||||
progress.avatar.updated += result.avatar.updated;
|
||||
this.logger.info(
|
||||
`Contact ${chatId}: created=${result.created}, updated=${result.updated}, avatar.updated=${result.avatar.updated}`,
|
||||
);
|
||||
} catch (e) {
|
||||
this.logger.error(`Error pulling contact ${contact.id}: ${e}`);
|
||||
progress.errors += 1;
|
||||
}
|
||||
// update progress before signal fails
|
||||
await job.updateProgress(progress);
|
||||
await sleep(options.delay.contact);
|
||||
}
|
||||
|
||||
await this.activity.completed(progress);
|
||||
}
|
||||
|
||||
private async pullOneContact(options: ContactsPullOptions, chatId: string) {
|
||||
// Contact
|
||||
const contactInfo = WhatsAppContactInfo(this.session, chatId, this.l);
|
||||
let [cwContact, created] =
|
||||
await this.contactService.findOrCreateContact(contactInfo);
|
||||
// Attributes
|
||||
if (options.attributes) {
|
||||
const attributes = await contactInfo.Attributes();
|
||||
await this.contactService.upsertCustomAttributes(
|
||||
cwContact.data,
|
||||
attributes,
|
||||
);
|
||||
}
|
||||
// Avatar
|
||||
let avatarUpdated = false;
|
||||
switch (options.avatar) {
|
||||
case 'if-missing':
|
||||
avatarUpdated = await this.contactService.updateAvatar(
|
||||
cwContact,
|
||||
contactInfo,
|
||||
AvatarUpdateMode.IF_MISSING,
|
||||
);
|
||||
break;
|
||||
case 'update':
|
||||
avatarUpdated = await this.contactService.updateAvatar(
|
||||
cwContact,
|
||||
contactInfo,
|
||||
AvatarUpdateMode.ALWAYS,
|
||||
);
|
||||
break;
|
||||
}
|
||||
return {
|
||||
created: created ? 1 : 0,
|
||||
updated: created ? 0 : 1,
|
||||
avatar: {
|
||||
updated: avatarUpdated ? 1 : 0,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class TaskActivity {
|
||||
constructor(
|
||||
private l: Locale,
|
||||
private conversation: Conversation,
|
||||
) {}
|
||||
|
||||
public async started(progress: Progress) {
|
||||
await this.conversation.activity(
|
||||
this.l.r('task.contacts.started', { progress: progress }),
|
||||
);
|
||||
}
|
||||
|
||||
public async progress(progress: Progress) {
|
||||
await this.conversation.activity(
|
||||
this.l.r('task.contacts.progress', { progress: progress }),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Final report
|
||||
*/
|
||||
public async completed(progress: Progress) {
|
||||
await this.conversation.activity(
|
||||
this.l.r('task.contacts.completed', { progress: progress }),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function ContactsPullStatusMessage(
|
||||
l: Locale,
|
||||
job: Job,
|
||||
state: JobState | 'unknown',
|
||||
) {
|
||||
const details = JobLink(job);
|
||||
const progress = lodash.merge({}, NullProgress, job.progress);
|
||||
const payload = {
|
||||
error: state === 'failed' || state === 'unknown' || progress.errors > 0,
|
||||
state: lodash.capitalize(state ?? 'unknown'),
|
||||
progress: progress,
|
||||
details: details,
|
||||
};
|
||||
return l.r('task.contacts.status', payload);
|
||||
}
|
||||
@@ -0,0 +1,523 @@
|
||||
import { Processor } from '@nestjs/bullmq';
|
||||
import * as ms from 'ms';
|
||||
import { JOB_CONCURRENCY } from '@waha/apps/app_sdk/constants';
|
||||
import { JobLink } from '@waha/apps/app_sdk/JobUtils';
|
||||
import { QueueName } from '@waha/apps/chatwoot/consumers/QueueName';
|
||||
import { ChatWootTaskConsumer } from '@waha/apps/chatwoot/consumers/task/base';
|
||||
import { DIContainer } from '@waha/apps/chatwoot/di/DIContainer';
|
||||
import { Locale } from '@waha/apps/chatwoot/i18n/locale';
|
||||
import { TKey } from '@waha/apps/chatwoot/i18n/templates';
|
||||
import { SessionManager } from '@waha/core/abc/manager.abc';
|
||||
import { RMutexService } from '@waha/modules/rmutex/rmutex.service';
|
||||
import { Job, JobState } from 'bullmq';
|
||||
import * as lodash from 'lodash';
|
||||
import { PinoLogger } from 'nestjs-pino';
|
||||
import { WAHASessionAPI } from '@waha/apps/app_sdk/waha/WAHASelf';
|
||||
import { Conversation } from '@waha/apps/chatwoot/client/Conversation';
|
||||
import { ILogger } from '@waha/apps/app_sdk/ILogger';
|
||||
import {
|
||||
GetChatMessagesFilter,
|
||||
GetChatMessagesQuery,
|
||||
MessageSortField,
|
||||
} from '@waha/structures/chats.dto';
|
||||
import { WAMessage } from '@waha/structures/responses.dto';
|
||||
import { EnsureSeconds } from '@waha/utils/timehelper';
|
||||
import { MessageAnyHandler } from '@waha/apps/chatwoot/consumers/waha/message.any';
|
||||
import { MessageReportInfo } from '@waha/apps/chatwoot/consumers/waha/base';
|
||||
import { MessageAckEmoji } from '@waha/apps/chatwoot/emoji';
|
||||
import { SortOrder } from '@waha/structures/pagination.dto';
|
||||
import {
|
||||
ArrayPaginator,
|
||||
PaginatorParams,
|
||||
} from '@waha/apps/app_sdk/waha/Paginator';
|
||||
import { EngineHelper } from '@waha/apps/chatwoot/waha/engines';
|
||||
import { IgnoreJidConfig, isNullJid, JidFilter } from '@waha/core/utils/jids';
|
||||
import { ErrorRenderer } from '@waha/apps/chatwoot/error/ErrorRenderer';
|
||||
import { IsCommandsChat } from '@waha/apps/chatwoot/client/ids';
|
||||
import { CommandPrefix } from '@waha/apps/chatwoot/cli';
|
||||
import { QueueManager } from '@waha/apps/chatwoot/services/QueueManager';
|
||||
import { addUnique } from '@waha/utils/list';
|
||||
|
||||
export enum ChatID {
|
||||
ALL = 'all',
|
||||
SUMMARY = 'summary',
|
||||
}
|
||||
|
||||
export type MessagesPullOptions = {
|
||||
chat: string;
|
||||
batch: number;
|
||||
progress: number | null;
|
||||
period: {
|
||||
start: number;
|
||||
end: number;
|
||||
};
|
||||
force: boolean;
|
||||
pause: boolean;
|
||||
timeout: {
|
||||
media: number;
|
||||
};
|
||||
media: boolean;
|
||||
ignore: IgnoreJidConfig;
|
||||
resolveConversations: boolean;
|
||||
};
|
||||
|
||||
interface Progress {
|
||||
ok: number;
|
||||
exists: number;
|
||||
ignored: number;
|
||||
errors: number;
|
||||
chats: string[];
|
||||
messages: {
|
||||
first?: number;
|
||||
last?: number;
|
||||
};
|
||||
// Conversations
|
||||
conversations: number[];
|
||||
conversationResolve: {
|
||||
success: number;
|
||||
error: number;
|
||||
};
|
||||
}
|
||||
|
||||
const NullProgress: Progress = {
|
||||
ok: 0,
|
||||
exists: 0,
|
||||
ignored: 0,
|
||||
errors: 0,
|
||||
chats: [],
|
||||
messages: {
|
||||
first: 0,
|
||||
last: 0,
|
||||
},
|
||||
conversations: [],
|
||||
conversationResolve: {
|
||||
success: 0,
|
||||
error: 0,
|
||||
},
|
||||
};
|
||||
|
||||
function total(progress: Progress) {
|
||||
return progress.ok + progress.exists + progress.ignored + progress.errors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Placeholder task consumer for pulling WhatsApp messages.
|
||||
* Throws until the implementation is completed.
|
||||
*/
|
||||
@Processor(QueueName.TASK_MESSAGES_PULL, { concurrency: JOB_CONCURRENCY })
|
||||
export class TaskMessagesPullConsumer extends ChatWootTaskConsumer {
|
||||
constructor(
|
||||
manager: SessionManager,
|
||||
log: PinoLogger,
|
||||
rmutex: RMutexService,
|
||||
protected queueManager: QueueManager,
|
||||
) {
|
||||
super(manager, log, rmutex, TaskMessagesPullConsumer.name);
|
||||
}
|
||||
|
||||
protected ErrorHeaderKey(): TKey | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
protected async Process(
|
||||
container: DIContainer,
|
||||
job: Job,
|
||||
signal: AbortSignal,
|
||||
) {
|
||||
const options: MessagesPullOptions = job.data.options;
|
||||
const locale = container.Locale();
|
||||
const conversation = this.conversationForReport(container, job.data.body);
|
||||
const waha = container.WAHASelf();
|
||||
const session = new WAHASessionAPI(job.data.session, waha);
|
||||
const handler = new MessagesPullHandler(
|
||||
this.queueManager,
|
||||
container,
|
||||
signal,
|
||||
container.Logger(),
|
||||
conversation,
|
||||
session,
|
||||
locale,
|
||||
);
|
||||
await handler.start(options, job);
|
||||
job = await handler.handle(options, job);
|
||||
return await handler.end(options, job);
|
||||
}
|
||||
}
|
||||
|
||||
class MessagesPullHandler {
|
||||
private activity: TaskActivity;
|
||||
|
||||
constructor(
|
||||
protected queueManager: QueueManager,
|
||||
protected container: DIContainer,
|
||||
protected signal: AbortSignal,
|
||||
protected logger: ILogger,
|
||||
conversation: Conversation,
|
||||
protected session: WAHASessionAPI,
|
||||
protected l: Locale,
|
||||
) {
|
||||
this.activity = new TaskActivity(l, conversation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate summary progress including current task and direct children
|
||||
*/
|
||||
protected async summaryProgress(job: Job): Promise<Progress> {
|
||||
const current = lodash.merge({}, NullProgress, job.progress);
|
||||
const childrenValues = await job.getChildrenValues();
|
||||
const values: Progress[] = [current, ...Object.values(childrenValues)];
|
||||
let progress = lodash.merge({}, NullProgress);
|
||||
|
||||
// Result
|
||||
for (const p of values) {
|
||||
progress.ok += p.ok;
|
||||
progress.exists += p.exists;
|
||||
progress.ignored += p.ignored;
|
||||
progress.errors += p.errors;
|
||||
progress.conversationResolve.success += p.conversationResolve.success;
|
||||
progress.conversationResolve.error += p.conversationResolve.error;
|
||||
}
|
||||
// Chats
|
||||
progress.chats = lodash.uniq(lodash.flatten(values.map((p) => p.chats)));
|
||||
|
||||
// Conversations
|
||||
progress.conversations = lodash.uniq(
|
||||
lodash.flatten(values.map((p) => p.conversations)),
|
||||
);
|
||||
|
||||
// Messages timestamps
|
||||
progress.messages.first = lodash.min(
|
||||
values.map((p) => p.messages.first || Infinity),
|
||||
);
|
||||
if (progress.messages.first === Infinity) {
|
||||
progress.messages.first = undefined;
|
||||
}
|
||||
progress.messages.last = lodash.max(
|
||||
values.map((p) => p.messages.last || 0),
|
||||
);
|
||||
if (progress.messages.last === 0) {
|
||||
progress.messages.last = undefined;
|
||||
}
|
||||
return progress;
|
||||
}
|
||||
|
||||
async start(options: MessagesPullOptions, job: Job) {
|
||||
const { ignored, processed, unprocessed, failed } =
|
||||
await job.getDependenciesCount();
|
||||
const total = ignored + processed + unprocessed + failed;
|
||||
const hasChildren = total > 0;
|
||||
if (hasChildren) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Perform some actions before any job started
|
||||
if (options.pause) {
|
||||
await this.queueManager.pause();
|
||||
await this.activity.queue(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform actions after all jobs are done
|
||||
*/
|
||||
async end(options: MessagesPullOptions, job: Job) {
|
||||
if (job.parentKey) {
|
||||
// Not end yet, have parent tasks to process
|
||||
return await this.summaryProgress(job);
|
||||
}
|
||||
|
||||
// Resume queues if paused
|
||||
if (options.pause) {
|
||||
await this.queueManager.resume();
|
||||
await this.activity.queue(false);
|
||||
}
|
||||
// Progress summary
|
||||
const progress = await this.summaryProgress(job);
|
||||
await job.updateProgress(progress);
|
||||
|
||||
// Resolve conversations
|
||||
if (options.resolveConversations) {
|
||||
await this.resolveConversations(progress);
|
||||
await job.updateProgress(progress);
|
||||
}
|
||||
|
||||
// Report completed
|
||||
await this.activity.completed(progress, options);
|
||||
return progress;
|
||||
}
|
||||
|
||||
protected async resolveConversations(progress: Progress) {
|
||||
const service = this.container.ConversationService();
|
||||
for (const conversationId of progress.conversations) {
|
||||
try {
|
||||
await service.resolve(conversationId);
|
||||
progress.conversationResolve.success += 1;
|
||||
} catch (err) {
|
||||
progress.conversationResolve.error += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async handle(options: MessagesPullOptions, job: Job): Promise<Job> {
|
||||
if (options.chat === ChatID.SUMMARY) {
|
||||
// Do nothing for "summary" chat
|
||||
return job;
|
||||
}
|
||||
const jids = new JidFilter(options.ignore);
|
||||
let progress = lodash.merge({}, NullProgress, job.progress);
|
||||
const batch = options.batch ?? 100;
|
||||
|
||||
this.logger.debug(
|
||||
`Pulling messages for session ${job.data.session} with batch size ${batch}...`,
|
||||
);
|
||||
if (lodash.isEqual(progress, NullProgress)) {
|
||||
await this.activity.started(job, options);
|
||||
}
|
||||
|
||||
// Handler
|
||||
const container = this.container;
|
||||
const info = new MessageReportInfo();
|
||||
const handler = new MessageAnyHistoryHandler(
|
||||
job,
|
||||
container.MessageMappingService(),
|
||||
container.ContactConversationService(),
|
||||
container.Logger(),
|
||||
info,
|
||||
this.session,
|
||||
container.Locale(),
|
||||
container.WAHASelf(),
|
||||
);
|
||||
handler.force = options.force;
|
||||
|
||||
// Messages
|
||||
const lte = job.timestamp - options.period.end;
|
||||
const gte = job.timestamp - options.period.start;
|
||||
const filters: GetChatMessagesFilter = {
|
||||
'filter.timestamp.lte': EnsureSeconds(lte),
|
||||
'filter.timestamp.gte': EnsureSeconds(gte),
|
||||
};
|
||||
|
||||
const query: GetChatMessagesQuery = {
|
||||
limit: batch,
|
||||
offset: 0,
|
||||
downloadMedia: false,
|
||||
sortBy: MessageSortField.TIMESTAMP,
|
||||
sortOrder: SortOrder.ASC,
|
||||
};
|
||||
const params: PaginatorParams = {
|
||||
processed: total(progress),
|
||||
};
|
||||
const paginator = new ArrayPaginator<WAMessage>(params);
|
||||
let messages = paginator.iterate((processed: number) => {
|
||||
query.offset = processed;
|
||||
return this.session.getMessages(options.chat, query, filters, {
|
||||
signal: this.signal,
|
||||
});
|
||||
});
|
||||
|
||||
messages = EngineHelper.IterateMessages(messages);
|
||||
const all = options.chat == ChatID.ALL;
|
||||
|
||||
for await (let message of messages) {
|
||||
this.signal.throwIfAborted();
|
||||
|
||||
//
|
||||
// Show progress
|
||||
//
|
||||
const thetotal = total(progress);
|
||||
if (options.progress && thetotal) {
|
||||
if (thetotal % options.progress == 0) {
|
||||
await this.activity.progress(progress, options);
|
||||
}
|
||||
}
|
||||
|
||||
// Process
|
||||
try {
|
||||
progress.messages.first = progress.messages.first || message.timestamp;
|
||||
progress.messages.last = message.timestamp;
|
||||
if (all && !jids.include(EngineHelper.ChatID(message))) {
|
||||
progress.ignored += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isNullJid(EngineHelper.ChatID(message))) {
|
||||
progress.ignored += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (
|
||||
options.media &&
|
||||
message.hasMedia &&
|
||||
(await handler.ShouldProcessMessage(message))
|
||||
) {
|
||||
// Fetch media for the message
|
||||
let signal = AbortSignal.timeout(options.timeout.media);
|
||||
signal = AbortSignal.any([signal, this.signal]);
|
||||
message = await this.session.getMessageById('all', message.id, true, {
|
||||
signal: signal,
|
||||
});
|
||||
}
|
||||
|
||||
const chatwoot = await handler.handle(message);
|
||||
if (chatwoot) {
|
||||
progress.ok += 1;
|
||||
addUnique(progress.chats, EngineHelper.ChatID(message));
|
||||
if (info.conversationId) {
|
||||
addUnique(progress.conversations, info.conversationId);
|
||||
}
|
||||
} else {
|
||||
progress.exists += 1;
|
||||
}
|
||||
} catch (error) {
|
||||
const renderer = new ErrorRenderer();
|
||||
const text = renderer.text(error);
|
||||
this.logger.error(`Error: ${text}`);
|
||||
this.logger.error(`Message:\n${JSON.stringify(message, null, 2)}`);
|
||||
try {
|
||||
const data = renderer.data(error);
|
||||
this.logger.error(JSON.stringify(data, null, 2));
|
||||
} catch (err) {
|
||||
this.logger.error(
|
||||
`Error occurred while login details for error: ${err}`,
|
||||
);
|
||||
}
|
||||
progress.errors += 1;
|
||||
}
|
||||
// update progress before signal fails
|
||||
await job.updateProgress(progress);
|
||||
}
|
||||
job.progress = progress;
|
||||
return job;
|
||||
}
|
||||
}
|
||||
|
||||
export class TaskActivity {
|
||||
constructor(
|
||||
private l: Locale,
|
||||
private conversation: Conversation,
|
||||
) {}
|
||||
|
||||
public async queue(paused: boolean) {
|
||||
let msg: string;
|
||||
if (paused) {
|
||||
msg = this.l.r('cli.cmd.queue.paused');
|
||||
} else {
|
||||
msg = this.l.r('cli.cmd.queue.resumed');
|
||||
}
|
||||
await this.conversation.activity(msg);
|
||||
}
|
||||
|
||||
public async details(data) {
|
||||
await this.conversation.activity(
|
||||
this.l.r('task.messages.details', {
|
||||
prefix: IsCommandsChat(data.body) ? '' : CommandPrefix,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
public async started(job: Job, options: MessagesPullOptions) {
|
||||
await this.conversation.activity(
|
||||
this.l.r('task.messages.started', {
|
||||
chat: options.chat,
|
||||
period: period(options),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
public async progress(progress: Progress, options: MessagesPullOptions) {
|
||||
const format: Intl.DateTimeFormatOptions = {
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
year: 'numeric',
|
||||
};
|
||||
await this.conversation.activity(
|
||||
this.l.r('task.messages.progress', {
|
||||
progress: progress,
|
||||
chat: options.chat,
|
||||
messages: {
|
||||
first: this.l.FormatTimestampOpts(
|
||||
progress.messages.first,
|
||||
format,
|
||||
false,
|
||||
),
|
||||
last: this.l.FormatTimestampOpts(
|
||||
progress.messages.last,
|
||||
format,
|
||||
false,
|
||||
),
|
||||
},
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Final report
|
||||
*/
|
||||
public async completed(progress, options) {
|
||||
const msg = this.l.r('task.messages.completed', {
|
||||
progress: progress,
|
||||
period: period(options),
|
||||
chat: options.chat,
|
||||
});
|
||||
await this.conversation.activity(msg);
|
||||
}
|
||||
}
|
||||
|
||||
class MessageAnyHistoryHandler extends MessageAnyHandler {
|
||||
public force: boolean = false;
|
||||
public shouldLogUnsupported = true;
|
||||
|
||||
protected get historyMessage() {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected get shouldAddFromTag() {
|
||||
return false;
|
||||
}
|
||||
|
||||
protected get delayFromMeAPI() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
async ShouldProcessMessage(payload: WAMessage) {
|
||||
if (this.force) {
|
||||
return true;
|
||||
}
|
||||
return await super.ShouldProcessMessage(payload);
|
||||
}
|
||||
}
|
||||
|
||||
function period(options: MessagesPullOptions) {
|
||||
const start = options.period.start;
|
||||
const end = options.period.end;
|
||||
if (end == 0) {
|
||||
return ms(start);
|
||||
}
|
||||
return `${ms(start)}-${ms(end)}`;
|
||||
}
|
||||
|
||||
export function MessagesPullStatusMessage(
|
||||
l: Locale,
|
||||
job: Job,
|
||||
state: JobState | 'unknown',
|
||||
) {
|
||||
const progress = lodash.merge({}, NullProgress, job.progress);
|
||||
const details = JobLink(job);
|
||||
const options = job.data.options as MessagesPullOptions;
|
||||
const payload = {
|
||||
options: options,
|
||||
chat: options.chat,
|
||||
chats: progress.chats.length,
|
||||
period: period(options),
|
||||
error: state === 'failed' || state === 'unknown' || progress.errors > 0,
|
||||
state: lodash.capitalize(state ?? 'unknown'),
|
||||
progress: progress,
|
||||
details: details,
|
||||
messages: {
|
||||
first: l.FormatTimestamp(progress.messages.first, false),
|
||||
last: l.FormatTimestamp(progress.messages.last, false),
|
||||
},
|
||||
};
|
||||
|
||||
return l.r('task.messages.status', payload);
|
||||
}
|
||||
@@ -13,7 +13,7 @@ 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 } from '@waha/apps/chatwoot/i18n/locale';
|
||||
import { WAHASelf, WAHASessionAPI } from '@waha/apps/chatwoot/session/WAHASelf';
|
||||
import { WAHASelf, WAHASessionAPI } from '@waha/apps/app_sdk/waha/WAHASelf';
|
||||
import {
|
||||
AppRepository,
|
||||
ChatwootMessage,
|
||||
@@ -25,16 +25,15 @@ import { parseMessageIdSerialized } from '@waha/core/utils/ids';
|
||||
import { RMutexService } from '@waha/modules/rmutex/rmutex.service';
|
||||
import { WAHAEvents } from '@waha/structures/enums.dto';
|
||||
import { MessageSource, WAMessageBase } from '@waha/structures/responses.dto';
|
||||
import {
|
||||
WAHAWebhook,
|
||||
WAHAWebhookMessageRevoked,
|
||||
} from '@waha/structures/webhooks.dto';
|
||||
import { sleep } from '@waha/utils/promiseTimeout';
|
||||
import { Job } from 'bullmq';
|
||||
import { PinoLogger } from 'nestjs-pino';
|
||||
|
||||
import { TKey } from '@waha/apps/chatwoot/i18n/templates';
|
||||
import { isJidBroadcast, isJidGroup, toCusFormat } from '@waha/core/utils/jids';
|
||||
import { EngineHelper } from '@waha/apps/chatwoot/waha';
|
||||
import { EnsureSeconds } from '@waha/utils/timehelper';
|
||||
import { CHATWOOT_MESSAGE_CALENDAR_THRESHOLD_SECONDS } from '@waha/apps/chatwoot/env';
|
||||
|
||||
export function ListenEventsForChatWoot() {
|
||||
return [
|
||||
@@ -42,6 +41,7 @@ export function ListenEventsForChatWoot() {
|
||||
WAHAEvents.MESSAGE_REACTION,
|
||||
WAHAEvents.MESSAGE_EDITED,
|
||||
WAHAEvents.MESSAGE_REVOKED,
|
||||
WAHAEvents.MESSAGE_ACK,
|
||||
WAHAEvents.SESSION_STATUS,
|
||||
];
|
||||
}
|
||||
@@ -96,8 +96,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),
|
||||
@@ -169,7 +176,7 @@ export interface IMessageInfo {
|
||||
onMessageType(type: MessageType): void;
|
||||
}
|
||||
|
||||
class MessageReportInfo implements IMessageInfo {
|
||||
export class MessageReportInfo implements IMessageInfo {
|
||||
public conversationId: number | null = null;
|
||||
public type: MessageType | null = null;
|
||||
|
||||
@@ -204,10 +211,65 @@ export abstract class MessageBaseHandler<Payload extends WAMessageBase> {
|
||||
payload: Payload,
|
||||
): Promise<ChatWootMessagePartial>;
|
||||
|
||||
protected get historyMessage() {
|
||||
return false;
|
||||
}
|
||||
|
||||
protected finalizeContent(content: string, payload: Payload): string {
|
||||
if (!content) {
|
||||
return content;
|
||||
}
|
||||
if (!payload.timestamp) {
|
||||
return content;
|
||||
}
|
||||
if (!this.historyMessage) {
|
||||
// Check if the message is recent enough to be considered current
|
||||
const now = EnsureSeconds(Date.now() / 1000);
|
||||
const messageAge = now - EnsureSeconds(payload.timestamp);
|
||||
const recent = messageAge <= CHATWOOT_MESSAGE_CALENDAR_THRESHOLD_SECONDS;
|
||||
if (recent) {
|
||||
return content;
|
||||
}
|
||||
}
|
||||
const date = this.l.ParseTimestamp(payload.timestamp);
|
||||
if (!date) {
|
||||
return content;
|
||||
}
|
||||
const timestamp = this.l.FormatHumanDate(date);
|
||||
return this.l.r('whatsapp.history.message.wrapper', {
|
||||
content: content,
|
||||
payload: payload,
|
||||
history: this.historyMessage,
|
||||
timestamp: timestamp,
|
||||
});
|
||||
}
|
||||
|
||||
abstract getReplyToWhatsAppID(payload: Payload): string | undefined;
|
||||
|
||||
async handle(data: WAHAWebhook<Payload>) {
|
||||
const payload = data.payload;
|
||||
protected get delayFromMeAPI() {
|
||||
return 3_000;
|
||||
}
|
||||
|
||||
/**
|
||||
* Weather to add a tag to messages sent from me like via API or WhatsApp
|
||||
*/
|
||||
protected get shouldAddFromTag() {
|
||||
return true;
|
||||
}
|
||||
|
||||
async ShouldProcessMessage(payload: Payload): Promise<boolean> {
|
||||
const key = parseMessageIdSerialized(payload.id);
|
||||
const chatwoot = await this.mappingService.getChatWootMessage({
|
||||
chat_id: toCusFormat(key.remoteJid),
|
||||
message_id: key.id,
|
||||
});
|
||||
if (chatwoot) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
async handle(payload: Payload): Promise<conversation_message_create | null> {
|
||||
// Find the type as soon as possible for error reporting
|
||||
const type = payload.fromMe ? MessageType.OUTGOING : MessageType.INCOMING;
|
||||
this.info.onMessageType(type);
|
||||
@@ -216,28 +278,25 @@ export abstract class MessageBaseHandler<Payload extends WAMessageBase> {
|
||||
// It also handles messages fromMe and ChatWoot (but does not if send via API)
|
||||
if (payload.fromMe && payload.source === MessageSource.API) {
|
||||
// Sleep a few seconds to save it to a database
|
||||
await sleep(3_000);
|
||||
await sleep(this.delayFromMeAPI);
|
||||
}
|
||||
|
||||
const key = parseMessageIdSerialized(payload.id);
|
||||
const chatwoot = await this.mappingService.getChatWootMessage({
|
||||
chat_id: toCusFormat(key.remoteJid),
|
||||
message_id: key.id,
|
||||
});
|
||||
if (chatwoot) {
|
||||
this.logger.info(
|
||||
`Message '${payload.id}' already in ChatWoot: conversation.id=${chatwoot.conversation_id}, message.id=${chatwoot.message_id}`,
|
||||
);
|
||||
return;
|
||||
if (!(await this.ShouldProcessMessage(payload))) {
|
||||
const log = `Skipping existing message '${payload.id}' from WhatsApp`;
|
||||
this.logger.debug(log);
|
||||
return null;
|
||||
}
|
||||
|
||||
const contactInfo = WhatsAppContactInfo(this.session, payload.from, this.l);
|
||||
const contactInfo = WhatsAppContactInfo(
|
||||
this.session,
|
||||
EngineHelper.ChatID(payload as any),
|
||||
this.l,
|
||||
);
|
||||
const conversation = await this.repo.ConversationByContact(contactInfo);
|
||||
this.info.onConversationId(conversation.conversationId);
|
||||
|
||||
const message = await this.buildChatWootMessage(payload);
|
||||
const response = await conversation.send(message);
|
||||
this.logger.info(
|
||||
this.logger.debug(
|
||||
`Created message as '${message.message_type}' from WhatsApp: ${response.id}`,
|
||||
);
|
||||
await this.saveMapping(response, payload);
|
||||
@@ -272,22 +331,26 @@ export abstract class MessageBaseHandler<Payload extends WAMessageBase> {
|
||||
let content = message.content;
|
||||
|
||||
// Format the content if the message from me
|
||||
if (payload.fromMe && payload.source === MessageSource.APP) {
|
||||
const key = TKey.MESSAGE_FROM_WHATSAPP;
|
||||
content = this.l.key(key).render({ text: content });
|
||||
} else if (payload.fromMe && payload.source === MessageSource.API) {
|
||||
const key = TKey.MESSAGE_FROM_API;
|
||||
content = this.l.key(key).render({ text: content });
|
||||
if (payload.fromMe && this.shouldAddFromTag) {
|
||||
if (payload.source === MessageSource.APP) {
|
||||
const key = TKey.MESSAGE_FROM_WHATSAPP;
|
||||
content = this.l.key(key).render({ text: content });
|
||||
} else if (payload.source === MessageSource.API) {
|
||||
const key = TKey.MESSAGE_FROM_API;
|
||||
content = this.l.key(key).render({ text: content });
|
||||
}
|
||||
}
|
||||
|
||||
const chatId = payload.from;
|
||||
const chatId = EngineHelper.ChatID(payload);
|
||||
|
||||
// Add participant name to group messages
|
||||
const manyParticipants = isJidGroup(chatId) || isJidBroadcast(chatId);
|
||||
if (!payload.fromMe && manyParticipants) {
|
||||
const key = parseMessageIdSerialized(payload.id, true);
|
||||
let participant = toCusFormat(key.participant);
|
||||
const contact: any = await this.session.getContact(key.participant);
|
||||
let participant = toCusFormat(
|
||||
key.participant || (payload as any)._data.participant,
|
||||
);
|
||||
const contact: any = await this.session.getContact(participant);
|
||||
const name = contact?.name || contact?.pushName || contact?.pushname;
|
||||
if (name) {
|
||||
participant = `${name} (${participant})`;
|
||||
@@ -307,6 +370,7 @@ export abstract class MessageBaseHandler<Payload extends WAMessageBase> {
|
||||
);
|
||||
|
||||
const type = payload.fromMe ? MessageType.OUTGOING : MessageType.INCOMING;
|
||||
content = this.finalizeContent(content, payload);
|
||||
return {
|
||||
content: content,
|
||||
message_type: type,
|
||||
@@ -326,7 +390,7 @@ export abstract class MessageBaseHandler<Payload extends WAMessageBase> {
|
||||
return;
|
||||
}
|
||||
const chatwoot = await this.mappingService.getChatWootMessage({
|
||||
chat_id: payload.from,
|
||||
chat_id: EngineHelper.ChatID(payload),
|
||||
message_id: replyToWhatsAppID,
|
||||
});
|
||||
return chatwoot?.message_id;
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
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/app_sdk/waha/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';
|
||||
import { toCusFormat } from '@waha/core/utils/jids';
|
||||
import { parseMessageIdSerialized } from '@waha/core/utils/ids';
|
||||
import { MessageMappingService } from '@waha/apps/chatwoot/storage';
|
||||
|
||||
@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.MessageMappingService(),
|
||||
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,
|
||||
protected mappingService: MessageMappingService,
|
||||
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 key = parseMessageIdSerialized(payload.id);
|
||||
const chatwoot = await this.mappingService.getChatWootMessage({
|
||||
chat_id: null,
|
||||
message_id: key.id,
|
||||
});
|
||||
// No chatwoot message found, so we don't mark it as read
|
||||
// Filters out old messages and some service ack messages
|
||||
if (!chatwoot) {
|
||||
return;
|
||||
}
|
||||
|
||||
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,32 @@
|
||||
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';
|
||||
import { EngineHelper } from '@waha/apps/chatwoot/waha';
|
||||
|
||||
export function ShouldMarkAsReadInChatWoot(
|
||||
event: WAHAWebhookMessageAck,
|
||||
): boolean {
|
||||
// Mark as seen only if it's DM
|
||||
// Ignore groups and other multiple participants chats
|
||||
const chatId = EngineHelper.ChatID(event.payload);
|
||||
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;
|
||||
}
|
||||
@@ -1,7 +1,5 @@
|
||||
import type { proto } from '@adiwajshing/baileys';
|
||||
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 {
|
||||
@@ -10,8 +8,7 @@ import {
|
||||
IMessageInfo,
|
||||
MessageBaseHandler,
|
||||
} from '@waha/apps/chatwoot/consumers/waha/base';
|
||||
import { WAHASessionAPI } from '@waha/apps/chatwoot/session/WAHASelf';
|
||||
import { WhatsappToMarkdown } from '@waha/apps/chatwoot/text';
|
||||
import { WAHASessionAPI } from '@waha/apps/app_sdk/waha/WAHASelf';
|
||||
import { SessionManager } from '@waha/core/abc/manager.abc';
|
||||
import { parseMessageIdSerialized } from '@waha/core/utils/ids';
|
||||
import { RMutexService } from '@waha/modules/rmutex/rmutex.service';
|
||||
@@ -20,13 +17,22 @@ import { WAMessage } from '@waha/structures/responses.dto';
|
||||
import { WAHAWebhookMessageAny } from '@waha/structures/webhooks.dto';
|
||||
import { Job } from 'bullmq';
|
||||
import { PinoLogger } from 'nestjs-pino';
|
||||
import { TKey } from '@waha/apps/chatwoot/i18n/templates';
|
||||
import { JobLink } from '@waha/apps/app_sdk/JobUtils';
|
||||
import * as lodash from 'lodash';
|
||||
import { parseVCardV3, SimpleVCardInfo } from '@waha/core/vcard';
|
||||
|
||||
// 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,
|
||||
AlbumMessage,
|
||||
resolveProtoMessage,
|
||||
} from '@waha/apps/chatwoot/messages/to/chatwoot';
|
||||
import { EngineHelper } from '@waha/apps/chatwoot/waha';
|
||||
import { HasMediaWithNoMediaMessage } from '@waha/apps/chatwoot/messages/to/chatwoot/HasMediaWithNoMediaMessage';
|
||||
|
||||
@Processor(QueueName.WAHA_MESSAGE_ANY, { concurrency: JOB_CONCURRENCY })
|
||||
export class WAHAMessageAnyConsumer extends ChatWootWAHABaseConsumer {
|
||||
@@ -39,7 +45,7 @@ export class WAHAMessageAnyConsumer extends ChatWootWAHABaseConsumer {
|
||||
}
|
||||
|
||||
GetChatId(event: WAHAWebhookMessageAny): string {
|
||||
return event.payload.from;
|
||||
return EngineHelper.ChatID(event.payload);
|
||||
}
|
||||
|
||||
async Process(
|
||||
@@ -59,142 +65,89 @@ export class WAHAMessageAnyConsumer extends ChatWootWAHABaseConsumer {
|
||||
container.Locale(),
|
||||
container.WAHASelf(),
|
||||
);
|
||||
return await handler.handle(event);
|
||||
return await handler.handle(event.payload);
|
||||
}
|
||||
}
|
||||
|
||||
function isEmptyString(content: string) {
|
||||
if (!content) {
|
||||
return true;
|
||||
}
|
||||
return content == '' || content == '\n';
|
||||
}
|
||||
export class MessageAnyHandler extends MessageBaseHandler<WAMessage> {
|
||||
public shouldLogUnsupported: boolean = false;
|
||||
|
||||
class MessageAnyHandler extends MessageBaseHandler<WAMessage> {
|
||||
protected async getMessage(
|
||||
payload: WAMessage,
|
||||
): Promise<ChatWootMessagePartial> {
|
||||
const protoMessage = this.getProtoMessage(payload);
|
||||
let msg = await this.getTextMessage(payload, protoMessage);
|
||||
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;
|
||||
}
|
||||
msg = this.getLocationMessage(payload, protoMessage);
|
||||
|
||||
converter = new TextMessage(this.l, this.logger, this.waha, this.job);
|
||||
msg = await converter.convert(payload, null);
|
||||
if (msg) {
|
||||
return msg;
|
||||
}
|
||||
msg = this.getShareContactMessage(payload, protoMessage);
|
||||
|
||||
converter = new LocationMessage(this.l);
|
||||
msg = await converter.convert(payload, protoMessage);
|
||||
if (msg) {
|
||||
return msg;
|
||||
}
|
||||
return this.getUnsupportedMessage();
|
||||
}
|
||||
|
||||
protected async getTextMessage(
|
||||
payload: WAMessage,
|
||||
_: proto.Message | null,
|
||||
): Promise<ChatWootMessagePartial | null> {
|
||||
const attachments = await this.getAttachments(payload);
|
||||
let content = this.l
|
||||
.key(TKey.WA_TO_CW_MESSAGE)
|
||||
.render({ payload: payload });
|
||||
if (isEmptyString(content) && attachments.length == 0) {
|
||||
return null;
|
||||
converter = new ShareContactMessage(this.l, this.logger);
|
||||
msg = await converter.convert(payload, protoMessage);
|
||||
if (msg) {
|
||||
return msg;
|
||||
}
|
||||
if (isEmptyString(content)) {
|
||||
content = null;
|
||||
}
|
||||
return {
|
||||
content: WhatsappToMarkdown(content),
|
||||
attachments: attachments,
|
||||
private: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
private getLocationMessage(
|
||||
payload: WAMessage,
|
||||
message: proto.Message | null,
|
||||
): ChatWootMessagePartial | null {
|
||||
const hasLocation = !lodash.isEmpty(message?.locationMessage);
|
||||
const hasLiveLocation = !lodash.isEmpty(message?.liveLocationMessage);
|
||||
if (!hasLocation && !hasLiveLocation) {
|
||||
return null;
|
||||
converter = new PollMessage(this.l);
|
||||
msg = await converter.convert(payload, protoMessage);
|
||||
if (msg) {
|
||||
return msg;
|
||||
}
|
||||
const location = this.l.key(TKey.WA_TO_CW_MESSAGE_LOCATION).r({
|
||||
payload,
|
||||
message,
|
||||
});
|
||||
if (isEmptyString(location)) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
content: location,
|
||||
attachments: [],
|
||||
private: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
private getShareContactMessage(
|
||||
_payload: WAMessage,
|
||||
message: proto.Message | null,
|
||||
): ChatWootMessagePartial | null {
|
||||
let vcards: string[] = [];
|
||||
converter = new ListMessage(this.l);
|
||||
msg = await converter.convert(payload, protoMessage);
|
||||
if (msg) {
|
||||
return msg;
|
||||
}
|
||||
|
||||
if (!lodash.isEmpty(message?.contactsArrayMessage?.contacts)) {
|
||||
vcards = message!.contactsArrayMessage!.contacts.map((c) => c.vcard);
|
||||
} else if (!lodash.isEmpty(message?.contactMessage?.vcard)) {
|
||||
vcards = [message!.contactMessage!.vcard];
|
||||
converter = new EventMessage(this.l);
|
||||
msg = await converter.convert(payload, protoMessage);
|
||||
if (msg) {
|
||||
return msg;
|
||||
}
|
||||
if (vcards.length === 0) {
|
||||
return null;
|
||||
|
||||
converter = new PixMessage(this.l, this.logger);
|
||||
msg = await converter.convert(payload, protoMessage);
|
||||
if (msg) {
|
||||
return msg;
|
||||
}
|
||||
const attachments: SendAttachment[] = vcards.map((v, i) => ({
|
||||
content: Buffer.from(v, 'utf8').toString('base64'),
|
||||
encoding: 'base64',
|
||||
filename: `vcard-${i + 1}.vcf`,
|
||||
}));
|
||||
let contacts: SimpleVCardInfo[] = [];
|
||||
try {
|
||||
contacts = vcards.map(parseVCardV3);
|
||||
} catch (err) {
|
||||
this.logger.error(
|
||||
`Error parsing some vcards: vcards=${vcards}, err=${err}`,
|
||||
|
||||
converter = new AlbumMessage(this.l);
|
||||
msg = await converter.convert(payload, protoMessage);
|
||||
if (msg) {
|
||||
return msg;
|
||||
}
|
||||
|
||||
converter = new HasMediaWithNoMediaMessage(this.l, this.job);
|
||||
msg = await converter.convert(payload, protoMessage);
|
||||
if (msg) {
|
||||
return msg;
|
||||
}
|
||||
|
||||
converter = new UnsupportedMessage(this.l, this.job);
|
||||
msg = await converter.convert(payload, protoMessage);
|
||||
if (this.shouldLogUnsupported) {
|
||||
this.logger.warn(
|
||||
`UnsupportedMessage:\n${JSON.stringify(payload, null, 2)}`,
|
||||
);
|
||||
}
|
||||
const msg = this.l.key(TKey.WA_TO_CW_MESSAGE_CONTACTS).r({ contacts });
|
||||
if (contacts.length === 0 && attachments.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
content: msg,
|
||||
attachments: attachments,
|
||||
private: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
private getUnsupportedMessage(): ChatWootMessagePartial {
|
||||
const unsupported = this.l
|
||||
.key(TKey.WA_TO_CW_MESSAGE_UNSUPPORTED)
|
||||
.render({ details: JobLink(this.job) });
|
||||
return {
|
||||
content: unsupported,
|
||||
attachments: [],
|
||||
private: true,
|
||||
};
|
||||
}
|
||||
|
||||
private getProtoMessage(payload: WAMessage): proto.Message | null {
|
||||
// GOWS
|
||||
if (payload._data.Message) {
|
||||
return payload._data.Message;
|
||||
}
|
||||
// NOWEB
|
||||
if (payload._data.message) {
|
||||
return payload._data.message;
|
||||
}
|
||||
// WEBJS - not available
|
||||
return null;
|
||||
return msg;
|
||||
}
|
||||
|
||||
getReplyToWhatsAppID(payload: WAMessage): string {
|
||||
@@ -202,37 +155,10 @@ class MessageAnyHandler extends MessageBaseHandler<WAMessage> {
|
||||
if (!replyTo) {
|
||||
return undefined;
|
||||
}
|
||||
if (!replyTo.id) {
|
||||
return undefined;
|
||||
}
|
||||
const key = parseMessageIdSerialized(replyTo.id, true);
|
||||
return key.id;
|
||||
}
|
||||
|
||||
async getAttachments(payload: WAMessage): Promise<SendAttachment[]> {
|
||||
//
|
||||
// WAHA
|
||||
//
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -6,9 +6,8 @@ import {
|
||||
ChatWootMessagePartial,
|
||||
ChatWootWAHABaseConsumer,
|
||||
IMessageInfo,
|
||||
MessageBaseHandler,
|
||||
} from '@waha/apps/chatwoot/consumers/waha/base';
|
||||
import { MessageBaseHandler } from '@waha/apps/chatwoot/consumers/waha/base';
|
||||
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,10 +18,13 @@ import {
|
||||
import { Job } from 'bullmq';
|
||||
import { PinoLogger } from 'nestjs-pino';
|
||||
|
||||
import { SendAttachment } from '../../client/types';
|
||||
import { WAHASessionAPI } from '../../session/WAHASelf';
|
||||
import { TKey } from '@waha/apps/chatwoot/i18n/templates';
|
||||
import { WAMessage } from '@waha/structures/responses.dto';
|
||||
import { WAHASessionAPI } from '../../../app_sdk/waha/WAHASelf';
|
||||
import {
|
||||
MessageEdited,
|
||||
MessageToChatWootConverter,
|
||||
resolveProtoMessage,
|
||||
} from '@waha/apps/chatwoot/messages/to/chatwoot';
|
||||
import { EngineHelper } from '@waha/apps/chatwoot/waha';
|
||||
|
||||
@Processor(QueueName.WAHA_MESSAGE_EDITED, { concurrency: JOB_CONCURRENCY })
|
||||
export class WAHAMessageEditedConsumer extends ChatWootWAHABaseConsumer {
|
||||
@@ -35,7 +37,7 @@ export class WAHAMessageEditedConsumer extends ChatWootWAHABaseConsumer {
|
||||
}
|
||||
|
||||
GetChatId(event: WAHAWebhookMessageEdited): string {
|
||||
return event.payload.from;
|
||||
return EngineHelper.ChatID(event.payload);
|
||||
}
|
||||
|
||||
async Process(
|
||||
@@ -55,7 +57,7 @@ export class WAHAMessageEditedConsumer extends ChatWootWAHABaseConsumer {
|
||||
container.Locale(),
|
||||
container.WAHASelf(),
|
||||
);
|
||||
return await handler.handle(event);
|
||||
return await handler.handle(event.payload);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,25 +65,12 @@ class MessageEditedHandler extends MessageBaseHandler<WAMessageEditedBody> {
|
||||
protected async getMessage(
|
||||
payload: WAMessageEditedBody,
|
||||
): Promise<ChatWootMessagePartial> {
|
||||
const body = payload.body;
|
||||
const formatted = WhatsappToMarkdown(body);
|
||||
const content = this.l
|
||||
.key(TKey.MESSAGE_EDITED_IN_WHATSAPP)
|
||||
.render({ text: formatted });
|
||||
return {
|
||||
content: content,
|
||||
attachments: [],
|
||||
private: undefined,
|
||||
};
|
||||
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 [];
|
||||
}
|
||||
}
|
||||
@@ -9,16 +9,17 @@ import {
|
||||
IMessageInfo,
|
||||
} 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 { WAHASessionAPI } from '@waha/apps/app_sdk/waha/WAHASelf';
|
||||
import { SessionManager } from '@waha/core/abc/manager.abc';
|
||||
import { parseMessageIdSerialized } from '@waha/core/utils/ids';
|
||||
import { RMutexService } from '@waha/modules/rmutex/rmutex.service';
|
||||
import { WAHAEvents } from '@waha/structures/enums.dto';
|
||||
import { WAHAEngine, WAHAEvents } from '@waha/structures/enums.dto';
|
||||
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';
|
||||
import { EngineHelper } from '@waha/apps/chatwoot/waha';
|
||||
|
||||
@Processor(QueueName.WAHA_MESSAGE_REACTION, { concurrency: JOB_CONCURRENCY })
|
||||
export class WAHAMessageReactionConsumer extends ChatWootWAHABaseConsumer {
|
||||
@@ -31,7 +32,13 @@ export class WAHAMessageReactionConsumer extends ChatWootWAHABaseConsumer {
|
||||
}
|
||||
|
||||
GetChatId(event: WAHAWebhookMessageReaction): string {
|
||||
return event.payload.from;
|
||||
if (event.environment?.engine == WAHAEngine.WEBJS) {
|
||||
// chat in "to" field for WEBJS for message.reaction
|
||||
// probably we need to set it to "from"
|
||||
// but for backward compatability we do this
|
||||
return event.payload.to;
|
||||
}
|
||||
return EngineHelper.ChatID(event.payload);
|
||||
}
|
||||
|
||||
async Process(
|
||||
@@ -51,7 +58,7 @@ export class WAHAMessageReactionConsumer extends ChatWootWAHABaseConsumer {
|
||||
container.Locale(),
|
||||
container.WAHASelf(),
|
||||
);
|
||||
return await handler.handle(event);
|
||||
return await handler.handle(event.payload);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,8 +89,4 @@ export class MessageReactionHandler extends MessageBaseHandler<WAMessageReaction
|
||||
const key = parseMessageIdSerialized(messageId, false);
|
||||
return key.id;
|
||||
}
|
||||
|
||||
async getAttachments(payload: WAMessageReaction): Promise<SendAttachment[]> {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -11,8 +11,8 @@ import {
|
||||
IMessageInfo,
|
||||
} from '@waha/apps/chatwoot/consumers/waha/base';
|
||||
import { Locale } from '@waha/apps/chatwoot/i18n/locale';
|
||||
import { WAHASelf } from '@waha/apps/chatwoot/session/WAHASelf';
|
||||
import { SessionStatusEmoji } from '@waha/apps/chatwoot/SessionStatusEmoji';
|
||||
import { WAHASelf } from '@waha/apps/app_sdk/waha/WAHASelf';
|
||||
import { SessionStatusEmoji } from '@waha/apps/chatwoot/emoji';
|
||||
import { SessionManager } from '@waha/core/abc/manager.abc';
|
||||
import { RMutexService } from '@waha/modules/rmutex/rmutex.service';
|
||||
import { WAHAEvents, WAHASessionStatus } from '@waha/structures/enums.dto';
|
||||
|
||||
@@ -2,7 +2,7 @@ import { public_contact_create_update_payload as Contact } from '@figuro/chatwoo
|
||||
import { ContactInfo } from '@waha/apps/chatwoot/client/ContactConversationService';
|
||||
import { AttributeKey } from '@waha/apps/chatwoot/const';
|
||||
import { Locale } from '@waha/apps/chatwoot/i18n/locale';
|
||||
import { WAHASessionAPI } from '@waha/apps/chatwoot/session/WAHASelf';
|
||||
import { WAHASessionAPI } from '@waha/apps/app_sdk/waha/WAHASelf';
|
||||
import { Channel } from '@waha/structures/channels.dto';
|
||||
import { CacheAsync } from '@waha/utils/Cache';
|
||||
import { TKey } from '@waha/apps/chatwoot/i18n/templates';
|
||||
@@ -91,6 +91,9 @@ class LidContactInfo extends ChatContactInfo {
|
||||
@CacheAsync()
|
||||
async jid() {
|
||||
const pn = await this.session.findPNByLid(this.chatId);
|
||||
if (!pn) {
|
||||
return null;
|
||||
}
|
||||
return new JidContactInfo(this.session, pn, this.locale);
|
||||
}
|
||||
|
||||
@@ -99,7 +102,7 @@ class LidContactInfo extends ChatContactInfo {
|
||||
if (jid) {
|
||||
return await jid.AvatarUrl();
|
||||
}
|
||||
return null;
|
||||
return await this.session.getChatPicture(this.chatId);
|
||||
}
|
||||
|
||||
@CacheAsync()
|
||||
@@ -109,7 +112,6 @@ class LidContactInfo extends ChatContactInfo {
|
||||
if (jid) {
|
||||
attributes = await jid.Attributes();
|
||||
}
|
||||
attributes[AttributeKey.WA_CHAT_ID] = this.chatId;
|
||||
attributes[AttributeKey.WA_LID] = this.chatId;
|
||||
return attributes;
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
} from '@waha/apps/chatwoot/dto/config.dto';
|
||||
import { ChatWootErrorReporter } from '@waha/apps/chatwoot/error/ChatWootErrorReporter';
|
||||
import { Locale } from '@waha/apps/chatwoot/i18n/locale';
|
||||
import { WAHASelf } from '@waha/apps/chatwoot/session/WAHASelf';
|
||||
import { WAHASelf } from '@waha/apps/app_sdk/waha/WAHASelf';
|
||||
import {
|
||||
ChatwootMessageRepository,
|
||||
MessageMappingRepository,
|
||||
@@ -109,7 +109,7 @@ export class DIContainer {
|
||||
* @returns ContactService instance
|
||||
*/
|
||||
@CacheSync()
|
||||
private ContactService(): ContactService {
|
||||
public ContactService(): ContactService {
|
||||
return new ContactService(
|
||||
this.config,
|
||||
this.AccountAPI(),
|
||||
@@ -133,7 +133,7 @@ export class DIContainer {
|
||||
* @returns ConversationService instance
|
||||
*/
|
||||
@CacheSync()
|
||||
private ConversationService(): ConversationService {
|
||||
public ConversationService(): ConversationService {
|
||||
return new ConversationService(
|
||||
this.config,
|
||||
this.AccountAPI(),
|
||||
@@ -212,6 +212,7 @@ export class DIContainer {
|
||||
linkPreview: LinkPreview.OFF,
|
||||
commands: {
|
||||
server: true,
|
||||
queue: false,
|
||||
},
|
||||
conversations: {
|
||||
sort: ConversationSort.created_newest,
|
||||
|
||||
@@ -20,6 +20,10 @@ export const DEFAULT_LOCALE = 'en-US';
|
||||
export class ChatWootCommandsConfig {
|
||||
@IsBoolean()
|
||||
server: boolean = true;
|
||||
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
queue?: boolean = false;
|
||||
}
|
||||
|
||||
export enum LinkPreview {
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
import { WAHASessionStatus, WAMessageAck } from '@waha/structures/enums.dto';
|
||||
|
||||
export function SessionStatusEmoji(status: WAHASessionStatus): string {
|
||||
switch (status) {
|
||||
case WAHASessionStatus.STOPPED:
|
||||
return '⚠️';
|
||||
case WAHASessionStatus.STARTING:
|
||||
return '⏳';
|
||||
case WAHASessionStatus.SCAN_QR_CODE:
|
||||
return '⚠️';
|
||||
case WAHASessionStatus.WORKING:
|
||||
return '🟢';
|
||||
case WAHASessionStatus.FAILED:
|
||||
return '🛑';
|
||||
default:
|
||||
return '❓';
|
||||
}
|
||||
}
|
||||
|
||||
export function MessageAckEmoji(ack: WAMessageAck) {
|
||||
switch (ack) {
|
||||
case WAMessageAck.ERROR:
|
||||
return '❌';
|
||||
case WAMessageAck.PENDING:
|
||||
return '⏳';
|
||||
case WAMessageAck.SERVER:
|
||||
return '✔️';
|
||||
case WAMessageAck.DEVICE:
|
||||
return '✔️';
|
||||
case WAMessageAck.READ:
|
||||
return '✅';
|
||||
case WAMessageAck.PLAYED:
|
||||
return '✅';
|
||||
default:
|
||||
return '❔';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
export const CHATWOOT_COMMAND_PREFIX =
|
||||
process.env.WAHA_CHATWOOT_COMMAND_PREFIX || 'wa/';
|
||||
|
||||
export const CHATWOOT_MESSAGE_CALENDAR_THRESHOLD_SECONDS =
|
||||
parseInt(process.env.WAHA_CHATWOOT_MESSAGE_CALENDAR_THRESHOLD_SECONDS) || 600;
|
||||
|
||||
export const APPS_CHATWOOT_LANGUAGES_FOLDER =
|
||||
process.env.WAHA_APPS_CHATWOOT_LANGUAGES_FOLDER?.trim();
|
||||
|
||||
export const TZ = process.env.TZ;
|
||||
@@ -39,6 +39,12 @@ export class ChatWootErrorReporter {
|
||||
}
|
||||
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,
|
||||
@@ -77,6 +83,10 @@ export class ChatWootErrorReporter {
|
||||
type: MessageType,
|
||||
replyTo?: number,
|
||||
): Promise<void> {
|
||||
// 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,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import * as path from 'path';
|
||||
import { YamlLocaleLoader } from '@waha/apps/chatwoot/i18n/loader';
|
||||
import { I18N } from '@waha/apps/chatwoot/i18n/i18n';
|
||||
import { APPS_CHATWOOT_LANGUAGES_FOLDER } from '@waha/apps/chatwoot/env';
|
||||
|
||||
const i18n = new I18N();
|
||||
const localesDir = path.join(__dirname, 'locales');
|
||||
@@ -9,7 +10,7 @@ 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();
|
||||
const additional = APPS_CHATWOOT_LANGUAGES_FOLDER;
|
||||
if (additional) {
|
||||
const resolved = path.resolve(additional);
|
||||
i18n.load(new YamlLocaleLoader(resolved, 'yml').load());
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
import { i18n } from '@waha/apps/chatwoot/i18n/index';
|
||||
|
||||
const enLocale = i18n.locale('en-US');
|
||||
// @ts-ignore
|
||||
enLocale.strings.datetime.timeZone = 'America/New_York';
|
||||
const ptBrLocale = i18n.locale('pt-BR');
|
||||
// @ts-ignore
|
||||
ptBrLocale.strings.datetime.timeZone = 'America/Sao_Paulo';
|
||||
|
||||
describe('Locale', () => {
|
||||
describe('FormatHumanDate', () => {
|
||||
beforeEach(() => {
|
||||
jest.useFakeTimers();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
it('formats dates occurring today with the Today prefix', () => {
|
||||
const now = new Date('2024-03-15T10:30:00Z');
|
||||
|
||||
jest.setSystemTime(now);
|
||||
|
||||
const result = enLocale.FormatHumanDate(new Date(now));
|
||||
|
||||
expect(result).toBe('Today, Mar 15 • 06:30 AM EDT');
|
||||
});
|
||||
|
||||
it('formats dates occurring yesterday with the Yesterday prefix', () => {
|
||||
const now = new Date('2024-03-15T10:30:00Z');
|
||||
const yesterday = new Date('2024-03-14T10:30:00Z');
|
||||
|
||||
jest.setSystemTime(now);
|
||||
|
||||
const result = enLocale.FormatHumanDate(yesterday);
|
||||
|
||||
expect(result).toBe('Yesterday, Mar 14 • 06:30 AM EDT');
|
||||
});
|
||||
|
||||
it('formats dates from the current year without the year', () => {
|
||||
const now = new Date('2024-05-01T09:00:00Z');
|
||||
const date = new Date('2024-02-10T14:45:00Z');
|
||||
|
||||
jest.setSystemTime(now);
|
||||
|
||||
const result = enLocale.FormatHumanDate(date);
|
||||
|
||||
expect(result).toBe('Sat, Feb 10 • 09:45 AM EST');
|
||||
});
|
||||
|
||||
it('formats dates from previous years including the year', () => {
|
||||
const now = new Date('2024-05-01T09:00:00Z');
|
||||
const date = new Date('2023-07-10T12:15:00Z');
|
||||
|
||||
jest.setSystemTime(now);
|
||||
|
||||
const result = enLocale.FormatHumanDate(date);
|
||||
|
||||
expect(result).toBe('Mon, Jul 10, 2023 • 08:15 AM EDT');
|
||||
});
|
||||
});
|
||||
|
||||
describe('FormatHumanDate (pt-BR)', () => {
|
||||
beforeEach(() => {
|
||||
jest.useFakeTimers();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
it('uses localized Today and Yesterday labels', () => {
|
||||
const now = new Date('2024-03-15T10:30:00Z');
|
||||
const yesterday = new Date('2024-03-14T10:30:00Z');
|
||||
|
||||
jest.setSystemTime(now);
|
||||
|
||||
const todayResult = ptBrLocale.FormatHumanDate(now);
|
||||
const yesterdayResult = ptBrLocale.FormatHumanDate(yesterday);
|
||||
|
||||
expect(todayResult).toBe('Hoje, 15 de mar. • 07:30 BRT');
|
||||
expect(yesterdayResult).toBe('Ontem, 14 de mar. • 07:30 BRT');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,13 +1,32 @@
|
||||
import * as Mustache from 'mustache';
|
||||
import * as lodash from 'lodash';
|
||||
import { TemplatePayloads, TKey } from '@waha/apps/chatwoot/i18n/templates';
|
||||
import Long from 'long';
|
||||
import { ensureNumber } from '@waha/core/engines/noweb/utils';
|
||||
import { EnsureMilliseconds } from '@waha/utils/timehelper';
|
||||
import { TZ } from '@waha/apps/chatwoot/env';
|
||||
import { isToday, isYesterday, isSameYear } from '@waha/utils/datehelper';
|
||||
|
||||
export class Locale {
|
||||
constructor(private readonly strings: Record<string, string>) {}
|
||||
constructor(public 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);
|
||||
}
|
||||
|
||||
r<K extends TKey | string>(key: K, data: TemplatePayloads[K] = null): string {
|
||||
const template = this.key(key as TKey);
|
||||
return template.render(data as any);
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides the existing strings with the provided strings for the locale.
|
||||
* Merges the new strings with the current strings.
|
||||
@@ -15,13 +34,141 @@ export class Locale {
|
||||
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, year): string | null {
|
||||
const options: any = lodash.clone(this.strings['datetime'] || {});
|
||||
return this.FormatDatetimeOpts(date, options, year);
|
||||
}
|
||||
|
||||
ParseTimestamp(timestamp: Long | string | number | null): Date | null {
|
||||
const value = ensureNumber(timestamp);
|
||||
if (!value) {
|
||||
return undefined;
|
||||
}
|
||||
if (!Number.isFinite(value)) {
|
||||
return undefined;
|
||||
}
|
||||
const milliseconds = EnsureMilliseconds(value);
|
||||
const date = new Date(milliseconds);
|
||||
if (Number.isNaN(date.getTime())) {
|
||||
return undefined;
|
||||
}
|
||||
return date;
|
||||
}
|
||||
|
||||
FormatTimestamp(
|
||||
timestamp: Long | string | number | null,
|
||||
year: boolean = true,
|
||||
): string | null {
|
||||
const date = this.ParseTimestamp(timestamp);
|
||||
return this.FormatDatetime(date, year);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format date using custom options
|
||||
*/
|
||||
|
||||
FormatDatetimeOpts(
|
||||
date: Date,
|
||||
options: Intl.DateTimeFormatOptions,
|
||||
year: boolean,
|
||||
) {
|
||||
options = lodash.cloneDeep(options);
|
||||
if (!date) {
|
||||
return null;
|
||||
}
|
||||
const opts: any = this.strings['datetime'] || {};
|
||||
// Copy timezone if any
|
||||
options.timeZone = opts.timeZone || opts.timezone || TZ;
|
||||
if (!year && date.getFullYear() === new Date().getFullYear()) {
|
||||
// Hide year if current year
|
||||
options.year = undefined;
|
||||
}
|
||||
return date.toLocaleDateString(this.locale, options);
|
||||
}
|
||||
|
||||
FormatTimestampOpts(
|
||||
timestamp: Long | string | number | null,
|
||||
options: Intl.DateTimeFormatOptions,
|
||||
year: boolean = true,
|
||||
): string | null {
|
||||
const date = this.ParseTimestamp(timestamp);
|
||||
return this.FormatDatetimeOpts(date, options, year);
|
||||
}
|
||||
|
||||
FormatHumanDate(date: Date): string {
|
||||
const options: any = lodash.clone(this.strings['datetime'] || {});
|
||||
const now = new Date();
|
||||
const today = isToday(date, now);
|
||||
const yesterday = isYesterday(date, now);
|
||||
const sameYear = isSameYear(date, now);
|
||||
const dateOptions: Intl.DateTimeFormatOptions = {
|
||||
...options,
|
||||
hour: undefined,
|
||||
minute: undefined,
|
||||
second: undefined,
|
||||
timeZoneName: undefined,
|
||||
year: sameYear ? undefined : options.year,
|
||||
weekday: today || yesterday ? undefined : options.weekday,
|
||||
};
|
||||
|
||||
const timeOptions: Intl.DateTimeFormatOptions = {
|
||||
...options,
|
||||
year: undefined,
|
||||
month: undefined,
|
||||
day: undefined,
|
||||
weekday: undefined,
|
||||
};
|
||||
|
||||
const dateStr = date.toLocaleDateString(this.locale, dateOptions);
|
||||
const timeStr = date.toLocaleTimeString(this.locale, timeOptions);
|
||||
|
||||
if (today) {
|
||||
const todayLabel = this.r('Today');
|
||||
return `${todayLabel}, ${dateStr} • ${timeStr}`;
|
||||
}
|
||||
|
||||
if (yesterday) {
|
||||
const yesterdayLabel = this.r('Yesterday');
|
||||
return `${yesterdayLabel}, ${dateStr} • ${timeStr}`;
|
||||
}
|
||||
|
||||
// weekday already included in dateStr
|
||||
return `${dateStr} • ${timeStr}`;
|
||||
}
|
||||
}
|
||||
|
||||
export class Template<K extends TKey> {
|
||||
constructor(private readonly template: string) {}
|
||||
|
||||
render(data: TemplatePayloads[K]): string {
|
||||
return Mustache.render(this.template, data);
|
||||
const text = Mustache.render(this.template, data);
|
||||
// Remove /n at the end if any
|
||||
return text.replace(/\n$/, '');
|
||||
}
|
||||
|
||||
r(data: TemplatePayloads[K]): string {
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
Here’s a complete `AGENTS.md` draft for your i18n workflow 👇
|
||||
|
||||
---
|
||||
|
||||
# 🌐 AGENTS.md — Locale Maintenance Guide
|
||||
|
||||
This document defines how the **i18n Agent** manages, updates, and synchronizes
|
||||
locale files (e.g. `en-US.yaml`, `pt-BR.yaml`, etc.) across all supported
|
||||
languages.
|
||||
|
||||
---
|
||||
|
||||
## 🧩 Overview
|
||||
|
||||
Each locale file (e.g. `en-US.yaml`) contains translation keys and values used
|
||||
throughout the system. The **English (en-US)** file is the **source of truth** —
|
||||
every other language must mirror its structure, keys, emojis, and formatting.
|
||||
|
||||
---
|
||||
|
||||
## 🪄 Core Commands
|
||||
|
||||
### 1. `update all i18n`
|
||||
|
||||
**Purpose:** Ensures all locales are synchronized with the current `en-US.yaml`.
|
||||
|
||||
**Steps:**
|
||||
|
||||
1. **Start with `en-US.yaml`.**
|
||||
|
||||
- Scan all code and templates to verify each key is used.
|
||||
- Ignore usage warnings for `datetime.*`, flat keys without a dot, and any
|
||||
`locale.*` entries — they are considered always in use.
|
||||
- Propagate every key change (add, edit, remove) from `en-US.yaml` to all
|
||||
other locale files with the corresponding translations so structures stay
|
||||
aligned.
|
||||
- Preserve `datetime.locales` in every non-English locale; it pins the
|
||||
language hint for formatting and must never be dropped.
|
||||
- If any keys appear **unused**, list them and **ask whether to remove or
|
||||
keep**.
|
||||
|
||||
2. **Check for “ChatWoot” mentions.**
|
||||
|
||||
- Any occurrence must be reviewed — the system should not reference
|
||||
_ChatWoot_ directly.
|
||||
- Ask:
|
||||
|
||||
> “Found ‘ChatWoot’ in key `X.Y.Z`. Should we reword this? If yes, how?”
|
||||
|
||||
3. **Validate formatting and structure.**
|
||||
|
||||
- Ensure all keys use consistent YAML indentation and ordering.
|
||||
- Emojis, punctuation, and tags (`{{variable}}`, `**bold**`, etc.) must stay
|
||||
exactly as in `en-US`.
|
||||
|
||||
4. **Synchronize all target languages.**
|
||||
|
||||
- For each locale (`es-ES.yaml`, `fr-FR.yaml`, etc.):
|
||||
|
||||
- Copy the structure and keys from `en-US.yaml`.
|
||||
- Translate the values from English → target language.
|
||||
- **Ignore any manual changes in target files** — always use the English
|
||||
source.
|
||||
- Preserve all markdown, emojis, punctuation, and placeholders.
|
||||
|
||||
5. **Report summary.**
|
||||
|
||||
- Added / updated / removed keys count per language.
|
||||
|
||||
---
|
||||
|
||||
### 2. `update key.* for i18n`
|
||||
|
||||
**Purpose:** Update or add translations for specific keys only.
|
||||
|
||||
**Steps:**
|
||||
|
||||
1. Match all keys provided in the command (supports wildcard like `key.auth.*`).
|
||||
2. Update those keys in `en-US.yaml` first.
|
||||
3. Propagate and translate the updated keys to all other locale files.
|
||||
4. Do **not** touch unrelated keys or structure.
|
||||
|
||||
---
|
||||
|
||||
## ⚙️ Translation Rules
|
||||
|
||||
| Aspect | Rule |
|
||||
| -------------- | --------------------------------------------------------------------------------- |
|
||||
| **Structure** | Must always follow `en-US.yaml`. |
|
||||
| **Formatting** | Keep emojis, punctuation, capitalization, and markdown exactly as in English. |
|
||||
| **Tags** | Preserve placeholders (`{{variable}}`) and code blocks. |
|
||||
| **Priority** | English (`en-US.yaml`) is always the canonical version. |
|
||||
| **Comments** | If a translation key needs explanation, add a YAML comment above it (not inline). |
|
||||
|
||||
---
|
||||
|
||||
## 🚫 ChatWoot Mentions
|
||||
|
||||
- The word **ChatWoot** must never appear in translations.
|
||||
- When found, the agent must pause and ask:
|
||||
|
||||
> “ChatWoot found in key `X.Y.Z`: `{{string}}`. Should we rephrase it (e.g.,
|
||||
> ‘Inbox’, ‘Messaging Platform’, or something else)?”
|
||||
|
||||
---
|
||||
|
||||
## 📘 Terms (reserved)
|
||||
|
||||
> This section will define key terminology and translation conventions (e.g.,
|
||||
> “Contact” vs “Customer”, “Session” vs “Chat”), once finalized.
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ Exceptions
|
||||
|
||||
- Treat `datetime.*` keys as standard infrastructure strings; do not flag them
|
||||
as unused.
|
||||
- Always keep `datetime.locales` aligned with the target locale code (e.g.,
|
||||
`es-ES`); if it is missing, restore it during the sync.
|
||||
- Keys without a dot (e.g., `UNKNOWN`) and any under `locale.*` are canonical
|
||||
metadata — skip unused checks for them.
|
||||
|
||||
---
|
||||
|
||||
## 🧾 Example Workflow
|
||||
|
||||
```bash
|
||||
# Full synchronization
|
||||
> update all i18n
|
||||
→ Checking unused keys...
|
||||
→ Found 3 unused keys: session.old, inbox.archive, legacy.test
|
||||
Keep or remove? [k/r]
|
||||
→ Found "ChatWoot" in en-US.yaml: "ChatWoot inbox connected"
|
||||
Replace? [yes/no]
|
||||
|
||||
→ Translating en-US → es-ES, fr-FR, pt-BR...
|
||||
✅ All i18n files updated and aligned with en-US.yaml
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Would you like me to add an example **YAML structure** and show how the agent
|
||||
should align keys (e.g., diff between en-US and pt-BR)? It helps clarify exactly
|
||||
how updates propagate.
|
||||
@@ -1,8 +1,33 @@
|
||||
# Arabic translations for ChatWoot integration
|
||||
# Arabic translations for WhatsApp inbox integration
|
||||
locale.name: '🇦🇪 العربية (Arabic)'
|
||||
app.inbox.contact.name: 'تكامل واتساب (WAHA)'
|
||||
|
||||
# رمز اللغة الأساسي - يجب أن يكون محليًا صالحًا وفق 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:
|
||||
weekday: short
|
||||
locales: ar-AE
|
||||
day: 2-digit
|
||||
month: short
|
||||
year: numeric
|
||||
hour: 2-digit
|
||||
minute: 2-digit
|
||||
hour12: true
|
||||
# https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat#timezonename
|
||||
timeZoneName: short
|
||||
# https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
|
||||
# timeZone: 'Asia/Dubai'
|
||||
|
||||
Today: 'اليوم'
|
||||
Yesterday: 'أمس'
|
||||
|
||||
job.report.error: |-
|
||||
⚠️ **{{{header}}}**
|
||||
{{#attempts.nextDelay}}
|
||||
@@ -31,14 +56,14 @@ whatsapp.message.removing.error: تعذر إزالة رسالة من واتسا
|
||||
job.scheduled.error.header: فشل تنفيذ المهمة المجدولة
|
||||
|
||||
message.from.whatsapp: |-
|
||||
{{{text}}}
|
||||
*📱 أرسلت من واتساب*
|
||||
|
||||
📱 *أرسلت من واتساب*
|
||||
{{{text}}}
|
||||
|
||||
message.from.api: |-
|
||||
{{{text}}}
|
||||
*📱 أرسلت عبر API*
|
||||
|
||||
📱 *أرسلت عبر API*
|
||||
{{{text}}}
|
||||
|
||||
message.removed.in.whatsapp: |-
|
||||
❌ *تم حذف هذه الرسالة في واتساب*
|
||||
@@ -66,11 +91,11 @@ whatsapp.contact.broadcast.suffix: 'قائمة البث'
|
||||
whatsapp.contact.status.name: 'الحالة'
|
||||
|
||||
app.connected.message: |-
|
||||
🔗 **متصل: جلسة واتساب إلى صندوق وارد ChatWoot**
|
||||
🔗 **متصل: جلسة واتساب إلى صندوق الوارد**
|
||||
|
||||
✅ تم توصيل جلسة واتساب **{{{name}}}** بنجاح بصندوق وارد ChatWoot هذا.
|
||||
✅ تم توصيل جلسة واتساب **{{{name}}}** بنجاح بهذا الصندوق الوارد.
|
||||
|
||||
صندوق الوارد هذا مرتبط الآن بـ **حساب واتساب** الخاص بك. يمكنك إرسال واستقبال الرسائل مباشرة من هنا — تمامًا مثل استخدام واتساب، ولكن داخل ChatWoot.
|
||||
صندوق الوارد هذا مرتبط الآن بـ **حساب واتساب** الخاص بك. يمكنك إرسال واستقبال الرسائل مباشرة من هنا — تمامًا مثل استخدام واتساب، ولكن داخل مساحة العمل.
|
||||
|
||||
يمكنك أيضًا إدارة جلسة واتساب المرتبطة بصندوق الوارد هذا. لعرض الإجراءات المتاحة، أرسل **help** إلى هذه المحادثة.
|
||||
|
||||
@@ -84,9 +109,9 @@ app.connected.message: |-
|
||||
لنبدأ! 🚀
|
||||
|
||||
app.disconnected.message: |-
|
||||
🔌 **غير متصل: جلسة واتساب من صندوق وارد ChatWoot**
|
||||
🔌 **غير متصل: جلسة واتساب من صندوق الوارد**
|
||||
|
||||
❌ تم قطع اتصال جلسة واتساب **{{{name}}}** من صندوق وارد ChatWoot هذا.
|
||||
❌ تم قطع اتصال جلسة واتساب **{{{name}}}** من هذا الصندوق الوارد.
|
||||
|
||||
لن تتمكن بعد الآن من إرسال أو استقبال رسائل من واتساب.
|
||||
|
||||
@@ -97,34 +122,156 @@ app.updated.message: |-
|
||||
|
||||
✅ تم تحديث إعدادات جلسة واتساب **{{{name}}}**.
|
||||
|
||||
app.commands.list: |-
|
||||
يمكنك إدارة **تكامل واتساب** عن طريق إرسال أحد الأوامر التالية:
|
||||
cli.usage.arguments: |-
|
||||
[الإجراء]
|
||||
|
||||
🖥️ **الجلسة**:
|
||||
1. **status** - الحصول على حالة الجلسة الحالية.
|
||||
2. **restart** - إعادة تشغيل الجلسة - سيحاول هذا إعادة توصيل حساب واتساب الخاص بك.
|
||||
3. **start** - بدء الجلسة.
|
||||
4. **stop** - إيقاف الجلسة.
|
||||
5. **logout** - تسجيل الخروج من الجلسة، حتى تتمكن من مسح رمز QR وإصلاح الجلسة.
|
||||
6. **qr** - الحصول على رمز QR للجلسة.
|
||||
7. **screenshot** - الحصول على لقطة شاشة للجلسة (متاح فقط في محرك **WEBJS**).
|
||||
8. **help** - الأوامر المتاحة.
|
||||
cli.usage.command: |-
|
||||
[الأمر]
|
||||
|
||||
{{#commands.server}}
|
||||
⚙️ الخادم:
|
||||
- **server status** - الحصول على معلومات إصدار الخادم وحالته.
|
||||
- **server reboot** - إعادة تشغيل الخادم بسلاسة.
|
||||
- **server reboot force** - إعادة تشغيل الخادم بالقوة.
|
||||
{{/commands.server}}
|
||||
cli.usage.options: |-
|
||||
[الخيارات]
|
||||
|
||||
أرسل **أمرًا** (غير حساس لحالة الأحرف) أو **رقم أمر** إلى هذه المحادثة.
|
||||
cli.cmd.disabled: ⛔ **تم تعطيل الأمر `{{command}}`**.
|
||||
|
||||
app.commands.server.disabled: ⛔ **تم تعطيل أوامر الخادم**.
|
||||
|
||||
يرجى التواصل مع مسؤول النظام لديك.
|
||||
يرجى الاتصال بالمسؤول الخاص بك.
|
||||
|
||||
أرسل **help** للحصول على قائمة الأوامر المتاحة.
|
||||
|
||||
cli.cmd.root.description: |-
|
||||
أدِر **تكامل واتساب** الخاص بك!
|
||||
|
||||
cli.cmd.root.sub.session: |-
|
||||
🖥️ **الجلسة**:
|
||||
|
||||
cli.cmd.root.sub.server: |-
|
||||
🖥️ **الخادم**:
|
||||
|
||||
cli.cmd.root.sub.sync: |-
|
||||
🔄️ **المزامنة**:
|
||||
|
||||
cli.cmd.session.status.description: |-
|
||||
اطّلع على حالة الجلسة الحالية.
|
||||
|
||||
cli.cmd.session.restart.description: |-
|
||||
أعد تشغيل الجلسة.
|
||||
|
||||
cli.cmd.session.start.description: |-
|
||||
ابدأ الجلسة.
|
||||
|
||||
cli.cmd.session.stop.description: |-
|
||||
أوقف الجلسة.
|
||||
|
||||
cli.cmd.session.logout.description: |-
|
||||
سجّل الخروج من الجلسة لمسح رمز QR جديد واستعادة الاتصال.
|
||||
|
||||
cli.cmd.session.qr.description: |-
|
||||
اعرض رمز QR الخاص بالجلسة.
|
||||
|
||||
cli.cmd.session.screenshot.description: |-
|
||||
التقط لقطة شاشة للجلسة (متاح فقط مع محرك **WEBJS**).
|
||||
|
||||
cli.cmd.contacts.summary: |-
|
||||
اسحب جهات اتصال واتساب.
|
||||
|
||||
cli.cmd.contacts.description: |-
|
||||
👤 شغِّل `contacts pull` لبدء المزامنة أو `contacts status` لمتابعة التقدم.
|
||||
|
||||
cli.cmd.contacts.action.description: |-
|
||||
استخدم `start` لبدء سحب جهات الاتصال أو `status` لمراجعة المهمة النشطة.
|
||||
|
||||
cli.cmd.contacts.pull.description: |-
|
||||
ابدأ بسحب جهات اتصال واتساب إلى الصندوق الوارد.
|
||||
|
||||
cli.cmd.contacts.pull.option.batch: |-
|
||||
عدد جهات الاتصال التي يتم سحبها في كل دفعة.
|
||||
|
||||
cli.cmd.contacts.pull.option.delay-contact: |-
|
||||
التأخير بين مزامنة كل جهة اتصال لتجنّب حدود معدل سحب الصور.
|
||||
|
||||
cli.cmd.contacts.pull.option.delay-batch: |-
|
||||
التأخير بين دفعات جهات الاتصال.
|
||||
|
||||
cli.cmd.contacts.pull.option.avatar: |-
|
||||
اختر كيفية سحب الصور الرمزية: `skip` أو `if-missing` أو `update`.
|
||||
|
||||
cli.cmd.contacts.pull.option.groups: |-
|
||||
قم بتضمين جهات اتصال مجموعات واتساب في المزامنة.
|
||||
|
||||
cli.cmd.contacts.pull.option.no-attributes: |-
|
||||
لا تسحب سمات جهة الاتصال إذا كانت موجودة بالفعل (هذا يسرّع العملية).
|
||||
|
||||
cli.cmd.contacts.pull.already-running: |-
|
||||
⏳ مهمة **سحب جهات الاتصال** تعمل بالفعل. انتظر حتى تنتهي أو أوقفها قبل بدء واحدة جديدة.
|
||||
|
||||
أرسل `contacts status` للاطلاع على التفاصيل.
|
||||
|
||||
cli.cmd.options.job.attempts: |-
|
||||
عدد مرات إعادة محاولة المهمة قبل اعتبارها فاشلة.
|
||||
|
||||
cli.cmd.options.job.timeout: |-
|
||||
الحد الأقصى لمدة تشغيل المهمة قبل إعادة المحاولة (يدعم قيمًا مثل `30s` أو `5m` أو `1h`).
|
||||
|
||||
cli.cmd.contacts.status.description: |-
|
||||
اعرض حالة مهمة السحب الحالية.
|
||||
|
||||
cli.cmd.contacts.status.not-found: |-
|
||||
ℹ️ لم يتم العثور على مهمة **سحب جهات الاتصال** نشطة. ابدأ واحدة باستخدام `contacts pull`.
|
||||
|
||||
cli.cmd.server.description: |-
|
||||
أدِر الخادم.
|
||||
|
||||
cli.cmd.server.status.description: |-
|
||||
اعرض إصدار الخادم وحالته.
|
||||
|
||||
cli.cmd.server.reboot.description: |-
|
||||
أعد تشغيل الخادم.
|
||||
|
||||
cli.cmd.server.reboot.option.force: |-
|
||||
فرض إعادة تشغيل فورية.
|
||||
|
||||
cli.help.usage.title: |-
|
||||
طريقة الاستخدام: أرسل أمراً إلى المحادثة بالشكل التالي:
|
||||
|
||||
cli.help.commands.defaultGroup: |-
|
||||
**الأوامر:**
|
||||
|
||||
cli.help.arguments.title: |-
|
||||
**المعاملات:**
|
||||
|
||||
cli.help.options.defaultGroup: |-
|
||||
**الخيارات:**
|
||||
|
||||
cli.help.globalOptions.title: |-
|
||||
**الخيارات العامة:**
|
||||
|
||||
task.contacts.status: |-
|
||||
👤 سحب جهات الاتصال: **{{state}}**
|
||||
- ✅ الناجحة: {{{progress.ok}}} جهة اتصال
|
||||
- ⚪ تم تخطيها: {{{progress.skipped}}} جهة اتصال
|
||||
- ⚠️ الأخطاء: {{{progress.errors}}} جهة اتصال
|
||||
|
||||
{{#job}}
|
||||
⌛ الجدول الزمني:
|
||||
- في قائمة الانتظار: {{{timestamp}}}
|
||||
{{#processedOn}}
|
||||
- وقت البدء: {{{processedOn}}}
|
||||
{{/processedOn}}
|
||||
{{#finishedOn}}
|
||||
- وقت الانتهاء: {{{finishedOn}}}
|
||||
{{/finishedOn}}
|
||||
{{/job}}
|
||||
|
||||
{{#error}}
|
||||
التفاصيل:
|
||||
[{{{details.text}}}]({{{details.url}}})
|
||||
{{/error}}
|
||||
|
||||
task.contacts.started: |-
|
||||
جارٍ سحب جهات الاتصال...
|
||||
|
||||
task.contacts.progress: |-
|
||||
سحب جهات الاتصال: ✅ {{progress.ok}}، ⚪ {{progress.skipped}}، ⚠️ {{progress.errors}}
|
||||
|
||||
app.help.reminder: |-
|
||||
أرسل **help** للحصول على قائمة الأوامر المتاحة.
|
||||
|
||||
@@ -138,17 +285,17 @@ app.session.current.status: |-
|
||||
📱 **{{{name}}} ({{{id}}})**
|
||||
|
||||
app.session.status.working: |-
|
||||
✅ صندوق وارد ChatWoot هذا مرتبط بـ **حساب واتساب** وجاهز لإرسال واستقبال الرسائل!
|
||||
✅ هذا الصندوق الوارد مرتبط بـ **حساب واتساب** وجاهز لإرسال واستقبال الرسائل!
|
||||
|
||||
📱 **{{{name}}} ({{{id}}})**
|
||||
|
||||
app.session.status.error: |-
|
||||
⚠️ **صندوق وارد ChatWoot هذا مرتبط بحساب واتساب ولكنه غير جاهز لإرسال واستقبال الرسائل!**
|
||||
⚠️ **هذا الصندوق الوارد مرتبط بحساب واتساب ولكنه غير جاهز لإرسال واستقبال الرسائل!**
|
||||
|
||||
يرجى التحقق من حالة الجلسة ومحاولة إعادة تشغيلها أو تسجيل الخروج ومسح رمز QR مرة أخرى.
|
||||
|
||||
app.session.scan.qr.code: |-
|
||||
⚠️ **امسح رمز QR على هاتفك لربط حساب واتساب بصندوق وارد ChatWoot هذا!**
|
||||
⚠️ **امسح رمز QR على هاتفك لربط حساب واتساب بهذا الصندوق الوارد!**
|
||||
|
||||
app.server.version.and.status: |-
|
||||
**الإصدار:**
|
||||
@@ -216,7 +363,19 @@ whatsapp.to.chatwoot.message.location: |-
|
||||
{{#locationMessage}}
|
||||
📍 **الموقع المشترك**
|
||||
|
||||
https://www.google.com/maps?q={{degreesLatitude}},{{degreesLongitude}}
|
||||
{{#name}}
|
||||
**{{name}}**
|
||||
{{/name}}
|
||||
|
||||
{{#address}}
|
||||
📍 **العنوان:** {{address}}
|
||||
{{/address}}
|
||||
|
||||
{{#url}}
|
||||
🌐 **الموقع الإلكتروني:** {{url}}
|
||||
{{/url}}
|
||||
|
||||
🔗 **عرض على الخريطة:** https://www.google.com/maps?q={{degreesLatitude}},{{degreesLongitude}}
|
||||
{{#caption}}{{caption}}{{/caption}}
|
||||
{{/locationMessage}}
|
||||
|
||||
@@ -224,13 +383,277 @@ whatsapp.to.chatwoot.message.location: |-
|
||||
📍📡 **مشاركة الموقع المباشر**
|
||||
افتح **WhatsApp** لعرض التحديثات اللحظية.
|
||||
|
||||
https://www.google.com/maps?q={{degreesLatitude}},{{degreesLongitude}}
|
||||
{{#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.album: |-
|
||||
📷 **تم استلام رسالة ألبوم**
|
||||
|
||||
{{#totalCount}}
|
||||
تم استلام ألبوم يحتوي على **{{totalCount}}** ملف وسائط.
|
||||
{{#expectedImageCount}}
|
||||
📸 صور: **{{expectedImageCount}}**
|
||||
{{/expectedImageCount}}
|
||||
{{#expectedVideoCount}}
|
||||
🎥 فيديوهات: **{{expectedVideoCount}}**
|
||||
{{/expectedVideoCount}}
|
||||
|
||||
سيتم تسليم ملفات الوسائط في الرسائل التالية.
|
||||
{{/totalCount}}
|
||||
|
||||
whatsapp.to.chatwoot.message.facebook.ad: |-
|
||||
📢 **إعلان فيسبوك**
|
||||
|
||||
**العنوان:** {{{adData.title}}}
|
||||
|
||||
**الوصف:**
|
||||
{{{adData.body}}}
|
||||
|
||||
**رابط الإعلان:** [عرض على فيسبوك]({{{adData.sourceUrl}}})
|
||||
**معرّف الإعلان:** {{{adData.sourceId}}}
|
||||
|
||||
---
|
||||
💬 **رسالة العميل:**
|
||||
{{{payload.body}}}
|
||||
whatsapp.history.message.wrapper: |-
|
||||
{{{content}}}
|
||||
`{{#history}}📥{{/history}}🗓️ {{{timestamp}}}`
|
||||
|
||||
UNKNOWN: غير معروف
|
||||
ERROR: خطأ
|
||||
PENDING: جارٍ الإرسال
|
||||
SERVER: الخادم
|
||||
DEVICE: الجهاز
|
||||
READ: مقروء
|
||||
PLAYED: مُشغَّل
|
||||
whatsapp.to.chatwoot.message.has.media.no.media: |-
|
||||
{{#content}}
|
||||
{{{content}}}
|
||||
|
||||
{{/content}}
|
||||
🖼️⚠️ **تحتوي الرسالة على وسائط ولكن تعذّر تنزيلها**
|
||||
📱 يرجى فتح **واتساب** للاطلاع عليها.
|
||||
|
||||
{{^content}}
|
||||
التفاصيل: [{{{details.text}}}]({{{details.url}}})
|
||||
{{/content}}
|
||||
cli.cmd.root.sub.queue: '🧩 **قائمة الانتظار**:'
|
||||
cli.cmd.messages.summary: سحب رسائل واتساب.
|
||||
cli.cmd.messages.description:
|
||||
💬 نفّذ `messages pull` لبدء مزامنة الرسائل أو `messages status` لمتابعة
|
||||
التقدم.
|
||||
cli.cmd.messages.action.description:
|
||||
'`pull` لبدء سحب الرسائل أو `status` لمراجعة المهمة النشطة.'
|
||||
cli.cmd.messages.pull.option.chat: حصر السحب على معرّف دردشة واتساب محدد.
|
||||
cli.cmd.messages.pull.option.force:
|
||||
إجبار صندوق الوارد على إنشاء الرسائل حتى لو كانت رسالة واتساب موجودة بالفعل في
|
||||
مخطط المحادثة.
|
||||
cli.cmd.messages.pull.option.batch: عدد الرسائل التي يتم سحبها في كل دفعة.
|
||||
cli.cmd.messages.pull.option.progress:
|
||||
الإبلاغ عن التقدم كل عدد N من الرسائل. القيمة 0 لتعطيل التقارير.
|
||||
cli.cmd.messages.pull.option.no-dm: تخطي الرسائل الخاصة أثناء سحب السجل.
|
||||
cli.cmd.messages.pull.option.groups: تضمين مجموعات واتساب أثناء سحب السجل.
|
||||
cli.cmd.messages.pull.option.channels: تضمين قنوات واتساب أثناء سحب السجل.
|
||||
cli.cmd.messages.pull.option.status: تضمين حالات واتساب أثناء سحب السجل.
|
||||
cli.cmd.messages.pull.option.broadcast: تضمين قوائم البث أثناء سحب السجل.
|
||||
cli.cmd.messages.pull.option.media:
|
||||
تنزيل المرفقات (صور، فيديوهات، ملفات) أثناء سحب السجل.
|
||||
cli.cmd.messages.pull.option.pause:
|
||||
وضع مهمة سحب الرسائل في قائمة الانتظار وهي متوقفة مؤقتًا وإيقاف معالجة رسائل
|
||||
واتساب الجديدة حتى تكتمل.
|
||||
cli.cmd.messages.pull.option.resolve-conversations:
|
||||
وضع علامة على المحادثات كمحلولة بعد معالجة كل رسائل السجل.
|
||||
cli.cmd.messages.pull.error.pause-no-queue:
|
||||
⛔ لا يمكن استخدام `--pause` لأن أمر **queue** معطَّل في هذا التكوين.
|
||||
cli.cmd.queue.description: إدارة قوائم الانتظار.
|
||||
cli.cmd.queue.summary: تنفيذ إجراءات على قوائم الانتظار.
|
||||
cli.cmd.queue.action.description: اختر إجراءً.
|
||||
cli.cmd.queue.status.description: عرض حالة قائمة الانتظار.
|
||||
cli.cmd.queue.argument.name:
|
||||
'اسم قائمة الانتظار: `inbox` أو `whatsapp` (اختياري).'
|
||||
cli.cmd.queue.start.description: بدء قائمة الانتظار بالاسم.
|
||||
cli.cmd.queue.stop.description: إيقاف قائمة الانتظار بالاسم.
|
||||
cli.cmd.queue.paused:
|
||||
⚪🧩 تم إيقاف قوائم الانتظار مؤقتًا. أرسل `queue status` للتحقق من الحالة.
|
||||
cli.cmd.queue.resumed:
|
||||
🟢🧩 تم استئناف قوائم الانتظار. أرسل `queue status` للتحقق من الحالة.
|
||||
cli.cmd.queue.status.result: >-
|
||||
📊 حالة قائمة الانتظار:
|
||||
|
||||
{{#queues}}
|
||||
|
||||
- **{{name}}**: {{#locked}}🔒{{/locked}}{{#paused}}⚪ متوقفة
|
||||
مؤقتًا{{/paused}}{{^paused}}🟢 تعمل{{/paused}}
|
||||
|
||||
{{/queues}}
|
||||
cli.cmd.messages.pull.option.timeout-media:
|
||||
تحديد مدة محاولة تنزيل الوسائط لكل رسالة.
|
||||
cli.cmd.messages.pull.argument.end:
|
||||
تحديد نقطة نهاية نافذة السحب؛ على سبيل المثال، `2d` يعني التوقف قبل يومين.
|
||||
cli.cmd.messages.pull.argument.start:
|
||||
تحديد نقطة بداية نافذة السحب؛ على سبيل المثال، `1d` يبدأ قبل يوم واحد (القيمة
|
||||
الافتراضية `0d` أي الآن).
|
||||
cli.cmd.messages.pull.already-running: >-
|
||||
⏳ مهمة **Pull Messages** تعمل بالفعل لـ **{{{chat}}}**. انتظر حتى تنتهي أو
|
||||
أوقفها قبل بدء مهمة جديدة.
|
||||
|
||||
|
||||
أرسل `messages status` للحصول على التفاصيل.
|
||||
cli.cmd.messages.status.not-found:
|
||||
ℹ️ لم يتم العثور على مهمة **Pull Messages** نشطة. ابدأ واحدة باستخدام
|
||||
`messages pull`.
|
||||
cli.cmd.messages.pull.no-chats-found:
|
||||
لم يتم العثور على دردشات واتساب للسحب خلال هذه الفترة.
|
||||
cli.cmd.contacts.pull.option.progress:
|
||||
الإبلاغ عن التقدم كل عدد N من جهات الاتصال. القيمة 0 لتعطيل التقارير.
|
||||
cli.cmd.contacts.pull.option.lids:
|
||||
تضمين جهات اتصال واتساب LID (مجهولة) في المزامنة.
|
||||
task.contacts.completed:
|
||||
👤✅ تم إنهاء جهات الاتصال → 🆕 {{progress.created}}، 🔄 {{progress.updated}}،
|
||||
⚪ {{progress.skipped}}، 🔴 {{progress.errors}}، 🖼️
|
||||
{{progress.avatar.updated}}
|
||||
task.messages.started: ⏳ جارٍ سحب الرسائل 💬 {{{chat}}} ⏱️ {{{period}}}...
|
||||
task.messages.details:
|
||||
أرسل "{{{prefix}}}messages status" للاطلاع على المزيد من التفاصيل.
|
||||
task.messages.progress:
|
||||
'⏳ رسائل {{{chat}}}: 🆕 {{progress.ok}}، 🟢 {{progress.exists}}، ⚪
|
||||
{{progress.ignored}}، 🔴 {{progress.errors}}{{#messages.last}} | 📅
|
||||
{{{messages.last}}}{{/messages.last}}'
|
||||
task.messages.completed:
|
||||
✅ رسائل {{{chat}}} خلال {{{period}}} → 🆕 {{progress.ok}}، 🟢
|
||||
{{progress.exists}}، ⚪ {{progress.ignored}}، 🔴 {{progress.errors}}
|
||||
task.messages.status: |-
|
||||
رسائل **{{{chat}}}** خلال الفترة ⏱️ **{{{period}}}**. ⚙️ حالة المهمة: **{{state}}**.
|
||||
|
||||
🗨️ الرسائل:
|
||||
- 🆕 تم الإنشاء: **{{{progress.ok}}}**
|
||||
- 🟢 موجودة مسبقًا: {{{progress.exists}}}
|
||||
- ⚪ تم تجاهلها: {{{progress.ignored}}}
|
||||
- 🔴 أخطاء: {{{progress.errors}}}
|
||||
{{#messages.first}}
|
||||
- 🗓️ الأقدم: **{{{messages.first}}}**
|
||||
{{/messages.first}}
|
||||
{{#messages.last}}
|
||||
- 🗓️ الأحدث: **{{{messages.last}}}**
|
||||
{{/messages.last}}
|
||||
|
||||
💬 دردشات واتساب:
|
||||
- 🆕 رسائل جديدة في **{{{chats}}}** دردشات
|
||||
{{#options.resolveConversations}}
|
||||
|
||||
🗣️ المحادثات:
|
||||
- 🟢 تم حلها: **{{{progress.conversationResolve.success}}}**
|
||||
{{#progress.conversationResolve.error}}
|
||||
- 🔴 أخطاء: **{{{progress.conversationResolve.error}}}**
|
||||
{{/progress.conversationResolve.error}}
|
||||
{{/options.resolveConversations}}
|
||||
|
||||
التفاصيل:
|
||||
[{{{details.text}}}]({{{details.url}}})
|
||||
@@ -1,8 +1,33 @@
|
||||
# Bengali translations for ChatWoot integration
|
||||
# Bengali translations for WhatsApp inbox integration
|
||||
locale.name: '🇧🇩 বাংলা (Bengali)'
|
||||
app.inbox.contact.name: 'হোয়াটসঅ্যাপ ইন্টিগ্রেশন (WAHA)'
|
||||
|
||||
# বেস লোকেল কোড - এটি একটি বৈধ 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:
|
||||
weekday: short
|
||||
locales: bn-BD
|
||||
day: 2-digit
|
||||
month: short
|
||||
year: numeric
|
||||
hour: 2-digit
|
||||
minute: 2-digit
|
||||
hour12: true
|
||||
# https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat#timezonename
|
||||
timeZoneName: short
|
||||
# https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
|
||||
# timeZone: 'Asia/Dhaka'
|
||||
|
||||
Today: 'আজ'
|
||||
Yesterday: 'গতকাল'
|
||||
|
||||
job.report.error: |-
|
||||
⚠️ **{{{header}}}**
|
||||
{{#attempts.nextDelay}}
|
||||
@@ -31,14 +56,14 @@ whatsapp.message.removing.error: হোয়াটসঅ্যাপ থেক
|
||||
job.scheduled.error.header: নির্ধারিত কাজ সম্পাদন করতে ব্যর্থ হয়েছে
|
||||
|
||||
message.from.whatsapp: |-
|
||||
{{{text}}}
|
||||
*📱 হোয়াটসঅ্যাপ থেকে পাঠানো হয়েছে*
|
||||
|
||||
📱 *হোয়াটসঅ্যাপ থেকে পাঠানো হয়েছে*
|
||||
{{{text}}}
|
||||
|
||||
message.from.api: |-
|
||||
{{{text}}}
|
||||
*📱 API এর মাধ্যমে পাঠানো হয়েছে*
|
||||
|
||||
📱 *API এর মাধ্যমে পাঠানো হয়েছে*
|
||||
{{{text}}}
|
||||
|
||||
message.removed.in.whatsapp: |-
|
||||
❌ *এই বার্তাটি হোয়াটসঅ্যাপে মুছে ফেলা হয়েছে*
|
||||
@@ -66,11 +91,11 @@ whatsapp.contact.broadcast.suffix: 'প্রচার তালিকা'
|
||||
whatsapp.contact.status.name: 'স্ট্যাটাস'
|
||||
|
||||
app.connected.message: |-
|
||||
🔗 **সংযুক্ত: হোয়াটসঅ্যাপ সেশন থেকে ChatWoot ইনবক্স**
|
||||
🔗 **সংযুক্ত: হোয়াটসঅ্যাপ সেশন থেকে ইনবক্স**
|
||||
|
||||
✅ হোয়াটসঅ্যাপ সেশন **{{{name}}}** সফলভাবে এই ChatWoot ইনবক্সের সাথে সংযুক্ত করা হয়েছে।
|
||||
✅ হোয়াটসঅ্যাপ সেশন **{{{name}}}** সফলভাবে এই ইনবক্সের সাথে সংযুক্ত করা হয়েছে।
|
||||
|
||||
এই ইনবক্সটি এখন আপনার **হোয়াটসঅ্যাপ অ্যাকাউন্টের** সাথে সংযুক্ত। আপনি এখান থেকে সরাসরি বার্তা পাঠাতে এবং গ্রহণ করতে পারেন — ঠিক হোয়াটসঅ্যাপ ব্যবহার করার মতো, কিন্তু ChatWoot এর ভিতরে।
|
||||
এই ইনবক্সটি এখন আপনার **হোয়াটসঅ্যাপ অ্যাকাউন্টের** সাথে সংযুক্ত। আপনি এখান থেকে সরাসরি বার্তা পাঠাতে এবং গ্রহণ করতে পারেন — ঠিক হোয়াটসঅ্যাপ ব্যবহার করার মতো, কিন্তু এই ওয়ার্কস্পেসের ভিতরে।
|
||||
|
||||
আপনি এই ইনবক্সের সাথে সংযুক্ত হোয়াটসঅ্যাপ সেশনও পরিচালনা করতে পারেন। উপলব্ধ কার্যগুলি দেখতে, এই চ্যাটে **help** পাঠান।
|
||||
|
||||
@@ -84,9 +109,9 @@ app.connected.message: |-
|
||||
চলুন শুরু করা যাক! 🚀
|
||||
|
||||
app.disconnected.message: |-
|
||||
🔌 **সংযোগ বিচ্ছিন্ন: হোয়াটসঅ্যাপ সেশন থেকে ChatWoot ইনবক্স**
|
||||
🔌 **সংযোগ বিচ্ছিন্ন: হোয়াটসঅ্যাপ সেশন থেকে ইনবক্স**
|
||||
|
||||
❌ হোয়াটসঅ্যাপ সেশন **{{{name}}}** এই ChatWoot ইনবক্স থেকে সংযোগ বিচ্ছিন্ন করা হয়েছে।
|
||||
❌ হোয়াটসঅ্যাপ সেশন **{{{name}}}** এই ইনবক্স থেকে সংযোগ বিচ্ছিন্ন করা হয়েছে।
|
||||
|
||||
আপনি আর হোয়াটসঅ্যাপ থেকে বার্তা পাঠাতে বা গ্রহণ করতে পারবেন না।
|
||||
|
||||
@@ -97,33 +122,155 @@ app.updated.message: |-
|
||||
|
||||
✅ হোয়াটসঅ্যাপ সেশন **{{{name}}}** এর সেটিংস আপডেট করা হয়েছে।
|
||||
|
||||
app.commands.list: |-
|
||||
আপনি নিম্নলিখিত কমান্ডগুলির একটি পাঠিয়ে **হোয়াটসঅ্যাপ ইন্টিগ্রেশন** পরিচালনা করতে পারেন:
|
||||
cli.usage.arguments: |-
|
||||
[অ্যাকশন]
|
||||
|
||||
cli.usage.command: |-
|
||||
[কমান্ড]
|
||||
|
||||
cli.usage.options: |-
|
||||
[বিকল্প]
|
||||
|
||||
cli.cmd.disabled: ⛔ **`{{command}}` কমান্ডটি নিষ্ক্রিয় করা হয়েছে**।
|
||||
|
||||
অনুগ্রহ করে আপনার প্রশাসকের সঙ্গে যোগাযোগ করুন।
|
||||
|
||||
উপলব্ধ কমান্ডগুলির তালিকা পেতে **help** পাঠান।
|
||||
|
||||
cli.cmd.root.description: |-
|
||||
আপনার **হোয়াটসঅ্যাপ ইন্টিগ্রেশন** পরিচালনা করুন!
|
||||
|
||||
cli.cmd.root.sub.session: |-
|
||||
🖥️ **সেশন**:
|
||||
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}}
|
||||
cli.cmd.root.sub.server: |-
|
||||
🖥️ **সার্ভার**:
|
||||
|
||||
এই কথোপকথনে একটি **কমান্ড** (কেস সংবেদনশীল নয়) বা **কমান্ড নম্বর** পাঠান।
|
||||
cli.cmd.root.sub.sync: |-
|
||||
🔄️ **সিঙ্ক**:
|
||||
|
||||
app.commands.server.disabled: ⛔ **সার্ভার কমান্ড নিষ্ক্রিয় করা হয়েছে**।
|
||||
cli.cmd.session.status.description: |-
|
||||
বর্তমান সেশনের স্ট্যাটাস দেখুন।
|
||||
|
||||
দয়া করে আপনার প্রশাসকের সাথে যোগাযোগ করুন।
|
||||
cli.cmd.session.restart.description: |-
|
||||
সেশন পুনরায় চালু করুন।
|
||||
|
||||
উপলব্ধ কমান্ডের তালিকা পেতে **help** পাঠান।
|
||||
cli.cmd.session.start.description: |-
|
||||
সেশন শুরু করুন।
|
||||
|
||||
cli.cmd.session.stop.description: |-
|
||||
সেশন বন্ধ করুন।
|
||||
|
||||
cli.cmd.session.logout.description: |-
|
||||
সংযোগ পুনরুদ্ধার করতে নতুন QR কোড স্ক্যান করার জন্য সেশন থেকে লগ আউট করুন।
|
||||
|
||||
cli.cmd.session.qr.description: |-
|
||||
সেশনের QR কোড দেখান।
|
||||
|
||||
cli.cmd.session.screenshot.description: |-
|
||||
সেশনের স্ক্রিনশট নিন (শুধুমাত্র **WEBJS** ইঞ্জিনে উপলব্ধ)।
|
||||
|
||||
cli.cmd.contacts.summary: |-
|
||||
হোয়াটসঅ্যাপ কনট্যাক্টস আনুন।
|
||||
|
||||
cli.cmd.contacts.description: |-
|
||||
👤 সিঙ্ক শুরু করতে `contacts pull` চালান বা অগ্রগতি দেখতে `contacts status` চালান।
|
||||
|
||||
cli.cmd.contacts.action.description: |-
|
||||
`start` দিয়ে কনট্যাক্ট টানা শুরু করুন বা `status` দিয়ে সক্রিয় কাজ পরীক্ষা করুন।
|
||||
|
||||
cli.cmd.contacts.pull.description: |-
|
||||
হোয়াটসঅ্যাপ কনট্যাক্টস ইনবক্সে আনা শুরু করুন।
|
||||
|
||||
cli.cmd.contacts.pull.option.batch: |-
|
||||
প্রতি ব্যাচে কত কনট্যাক্ট টানা হবে।
|
||||
|
||||
cli.cmd.contacts.pull.option.delay-contact: |-
|
||||
প্রতিটি কনট্যাক্ট সিঙ্কের মাঝে বিলম্ব, যাতে অ্যাভাটার টানার সময় রেট লিমিট এড়ানো যায়।
|
||||
|
||||
cli.cmd.contacts.pull.option.delay-batch: |-
|
||||
প্রতিটি ব্যাচের মাঝে বিলম্ব।
|
||||
|
||||
cli.cmd.contacts.pull.option.avatar: |-
|
||||
কনট্যাক্টের অ্যাভাটার কীভাবে টানবেন নির্ধারণ করুন: `skip`, `if-missing`, অথবা `update`।
|
||||
|
||||
cli.cmd.contacts.pull.option.groups: |-
|
||||
সিঙ্কে WhatsApp গ্রুপ কনট্যাক্ট অন্তর্ভুক্ত করুন।
|
||||
|
||||
cli.cmd.contacts.pull.option.no-attributes: |-
|
||||
কনট্যাক্ট আগেই থাকলে তার অ্যাট্রিবিউট টানবেন না (এতে প্রক্রিয়া দ্রুত হয়)।
|
||||
|
||||
cli.cmd.contacts.pull.already-running: |-
|
||||
⏳ **Pull Contacts** কাজটি ইতিমধ্যেই চলছে। এটি শেষ হওয়া পর্যন্ত অপেক্ষা করুন বা নতুন কাজ শুরু করার আগে থামান।
|
||||
|
||||
বিস্তারিত জানতে `contacts status` পাঠান।
|
||||
|
||||
cli.cmd.options.job.attempts: |-
|
||||
কোনো কাজ ব্যর্থ ঘোষণা করার আগে কয়বার পুনরায় চেষ্টা করা হবে।
|
||||
|
||||
cli.cmd.options.job.timeout: |-
|
||||
কাজ পুনরায় চেষ্টা করার আগে সর্বোচ্চ চালনার সময় (যেমন `30s`, `5m`, `1h`)।
|
||||
|
||||
cli.cmd.contacts.status.description: |-
|
||||
চলমান টানার কাজের অবস্থা দেখান।
|
||||
|
||||
cli.cmd.contacts.status.not-found: |-
|
||||
ℹ️ কোনো সক্রিয় **Pull Contacts** কাজ পাওয়া যায়নি। `contacts pull` দিয়ে একটি শুরু করুন।
|
||||
|
||||
cli.cmd.server.description: |-
|
||||
সার্ভার পরিচালনা করুন।
|
||||
|
||||
cli.cmd.server.status.description: |-
|
||||
সার্ভারের সংস্করণ এবং স্ট্যাটাস দেখান।
|
||||
|
||||
cli.cmd.server.reboot.description: |-
|
||||
সার্ভার পুনরায় চালু করুন।
|
||||
|
||||
cli.cmd.server.reboot.option.force: |-
|
||||
অবিলম্বে রিবুট করতে বাধ্য করুন।
|
||||
|
||||
cli.help.usage.title: |-
|
||||
ব্যবহার: কথোপকথনে নীচের ফরম্যাটে একটি কমান্ড পাঠান:
|
||||
|
||||
cli.help.commands.defaultGroup: |-
|
||||
**কমান্ডসমূহ:**
|
||||
|
||||
cli.help.arguments.title: |-
|
||||
**আর্গুমেন্টসমূহ:**
|
||||
|
||||
cli.help.options.defaultGroup: |-
|
||||
**বিকল্পসমূহ:**
|
||||
|
||||
cli.help.globalOptions.title: |-
|
||||
**গ্লোবাল বিকল্পসমূহ:**
|
||||
|
||||
task.contacts.status: |-
|
||||
👤 কনট্যাক্ট টানা: **{{state}}**
|
||||
- ✅ সফল: {{{progress.ok}}} কনট্যাক্ট
|
||||
- ⚪ এড়িয়ে গেছে: {{{progress.skipped}}} কনট্যাক্ট
|
||||
- ⚠️ ত্রুটি: {{{progress.errors}}} কনট্যাক্ট
|
||||
|
||||
{{#job}}
|
||||
⌛ সময়রেখা:
|
||||
- কিউ হয়েছে: {{{timestamp}}}
|
||||
{{#processedOn}}
|
||||
- শুরু হয়েছে: {{{processedOn}}}
|
||||
{{/processedOn}}
|
||||
{{#finishedOn}}
|
||||
- শেষ হয়েছে: {{{finishedOn}}}
|
||||
{{/finishedOn}}
|
||||
{{/job}}
|
||||
|
||||
{{#error}}
|
||||
বিস্তারিত:
|
||||
[{{{details.text}}}]({{{details.url}}})
|
||||
{{/error}}
|
||||
|
||||
task.contacts.started: |-
|
||||
কনট্যাক্ট টানা হচ্ছে...
|
||||
|
||||
task.contacts.progress: |-
|
||||
কনট্যাক্ট টানা: ✅ {{progress.ok}}, ⚪ {{progress.skipped}}, ⚠️ {{progress.errors}}
|
||||
|
||||
app.help.reminder: |-
|
||||
উপলব্ধ কমান্ডগুলির তালিকা পেতে **help** পাঠান।
|
||||
@@ -138,17 +285,17 @@ app.session.current.status: |-
|
||||
📱 **{{{name}}} ({{{id}}})**
|
||||
|
||||
app.session.status.working: |-
|
||||
✅ এই ChatWoot ইনবক্স **হোয়াটসঅ্যাপ অ্যাকাউন্টের** সাথে সংযুক্ত এবং বার্তা পাঠাতে ও গ্রহণ করতে প্রস্তুত!
|
||||
✅ এই ইনবক্সটি আপনার **হোয়াটসঅ্যাপ অ্যাকাউন্টের** সাথে সংযুক্ত এবং বার্তা পাঠাতে ও গ্রহণ করতে প্রস্তুত!
|
||||
|
||||
📱 **{{{name}}} ({{{id}}})**
|
||||
|
||||
app.session.status.error: |-
|
||||
⚠️ **এই ChatWoot ইনবক্স হোয়াটসঅ্যাপ অ্যাকাউন্টের সাথে সংযুক্ত কিন্তু বার্তা পাঠাতে ও গ্রহণ করতে প্রস্তুত নয়!**
|
||||
⚠️ **এই ইনবক্সটি হোয়াটসঅ্যাপ অ্যাকাউন্টের সাথে সংযুক্ত কিন্তু বার্তা পাঠাতে ও গ্রহণ করতে প্রস্তুত নয়!**
|
||||
|
||||
অনুগ্রহ করে সেশনের স্ট্যাটাস পরীক্ষা করুন এবং এটি পুনরায় চালু করার চেষ্টা করুন অথবা লগআউট করুন এবং QR কোড আবার স্ক্যান করুন।
|
||||
|
||||
app.session.scan.qr.code: |-
|
||||
⚠️ **এই ChatWoot ইনবক্সের সাথে হোয়াটসঅ্যাপ অ্যাকাউন্ট লিঙ্ক করতে আপনার ফোনে QR কোড স্ক্যান করুন!**
|
||||
⚠️ **এই ইনবক্সের সাথে হোয়াটসঅ্যাপ অ্যাকাউন্ট লিঙ্ক করতে আপনার ফোনে QR কোড স্ক্যান করুন!**
|
||||
|
||||
app.server.version.and.status: |-
|
||||
**সংস্করণ:**
|
||||
@@ -216,7 +363,19 @@ whatsapp.to.chatwoot.message.location: |-
|
||||
{{#locationMessage}}
|
||||
📍 **শেয়ার করা লোকেশন**
|
||||
|
||||
https://www.google.com/maps?q={{degreesLatitude}},{{degreesLongitude}}
|
||||
{{#name}}
|
||||
**{{name}}**
|
||||
{{/name}}
|
||||
|
||||
{{#address}}
|
||||
📍 **ঠিকানা:** {{address}}
|
||||
{{/address}}
|
||||
|
||||
{{#url}}
|
||||
🌐 **ওয়েবসাইট:** {{url}}
|
||||
{{/url}}
|
||||
|
||||
🔗 **মানচিত্রে দেখুন:** https://www.google.com/maps?q={{degreesLatitude}},{{degreesLongitude}}
|
||||
{{#caption}}{{caption}}{{/caption}}
|
||||
{{/locationMessage}}
|
||||
|
||||
@@ -224,13 +383,280 @@ whatsapp.to.chatwoot.message.location: |-
|
||||
📍📡 **লাইভ লোকেশন শেয়ার করা হচ্ছে**
|
||||
রিয়েল-টাইম আপডেট দেখতে **WhatsApp** খুলুন।
|
||||
|
||||
https://www.google.com/maps?q={{degreesLatitude}},{{degreesLongitude}}
|
||||
{{#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.album: |-
|
||||
📷 **অ্যালবাম বার্তা প্রাপ্ত**
|
||||
|
||||
{{#totalCount}}
|
||||
**{{totalCount}}** মিডিয়া ফাইল সহ একটি অ্যালবাম প্রাপ্ত হয়েছে।
|
||||
{{#expectedImageCount}}
|
||||
📸 ছবি: **{{expectedImageCount}}**
|
||||
{{/expectedImageCount}}
|
||||
{{#expectedVideoCount}}
|
||||
🎥 ভিডিও: **{{expectedVideoCount}}**
|
||||
{{/expectedVideoCount}}
|
||||
|
||||
মিডিয়া ফাইলগুলি পরবর্তী বার্তাগুলিতে সরবরাহ করা হবে।
|
||||
{{/totalCount}}
|
||||
|
||||
whatsapp.to.chatwoot.message.facebook.ad: |-
|
||||
📢 **ফেসবুক বিজ্ঞাপন**
|
||||
|
||||
**শিরোনাম:** {{{adData.title}}}
|
||||
|
||||
**বর্ণনা:**
|
||||
{{{adData.body}}}
|
||||
|
||||
**বিজ্ঞাপনের লিংক:** [ফেসবুকে দেখুন]({{{adData.sourceUrl}}})
|
||||
**বিজ্ঞাপনের আইডি:** {{{adData.sourceId}}}
|
||||
|
||||
---
|
||||
💬 **গ্রাহকের বার্তা:**
|
||||
{{{payload.body}}}
|
||||
whatsapp.history.message.wrapper: |-
|
||||
{{{content}}}
|
||||
`{{#history}}📥{{/history}}🗓️ {{{timestamp}}}`
|
||||
|
||||
UNKNOWN: অজানা
|
||||
ERROR: ত্রুটি
|
||||
PENDING: পাঠানো হচ্ছে
|
||||
SERVER: সার্ভার
|
||||
DEVICE: ডিভাইস
|
||||
READ: পড়া হয়েছে
|
||||
PLAYED: চালানো হয়েছে
|
||||
whatsapp.to.chatwoot.message.has.media.no.media: |-
|
||||
{{#content}}
|
||||
{{{content}}}
|
||||
|
||||
{{/content}}
|
||||
🖼️⚠️ **বার্তায় মিডিয়া আছে, কিন্তু আমরা এটি ডাউনলোড করতে পারিনি**
|
||||
📱 দেখার জন্য অনুগ্রহ করে **WhatsApp** খুলুন।
|
||||
|
||||
{{^content}}
|
||||
বিস্তারিত: [{{{details.text}}}]({{{details.url}}})
|
||||
{{/content}}
|
||||
cli.cmd.root.sub.queue: '🧩 **কিউ**:'
|
||||
cli.cmd.messages.summary: WhatsApp বার্তা টানুন।
|
||||
cli.cmd.messages.description:
|
||||
💬 বার্তা সিঙ্ক শুরু করতে `messages pull` চালান অথবা অগ্রগতি দেখতে `messages
|
||||
status` চালান।
|
||||
cli.cmd.messages.action.description:
|
||||
বার্তা টানতে `pull` ব্যবহার করুন অথবা সক্রিয় কাজ দেখার জন্য `status`।
|
||||
cli.cmd.messages.pull.option.chat:
|
||||
নির্দিষ্ট WhatsApp চ্যাট আইডিতে টান সীমাবদ্ধ করুন।
|
||||
cli.cmd.messages.pull.option.force:
|
||||
WhatsApp বার্তা কথোপকথনের ম্যাপিংয়ে থাকলেও ইনবক্সকে বার্তা তৈরি করতে বাধ্য
|
||||
করুন।
|
||||
cli.cmd.messages.pull.option.batch: প্রতিটি ব্যাচে কতটি বার্তা টানবেন।
|
||||
cli.cmd.messages.pull.option.progress:
|
||||
প্রতি N বার্তায় অগ্রগতি জানায়। রিপোর্ট বন্ধ করতে 0 সেট করুন।
|
||||
cli.cmd.messages.pull.option.no-dm: ইতিহাস টানার সময় সরাসরি বার্তা উপেক্ষা করুন।
|
||||
cli.cmd.messages.pull.option.groups:
|
||||
ইতিহাস টানার সময় WhatsApp গ্রুপ অন্তর্ভুক্ত করুন।
|
||||
cli.cmd.messages.pull.option.channels:
|
||||
ইতিহাস টানার সময় WhatsApp চ্যানেল অন্তর্ভুক্ত করুন।
|
||||
cli.cmd.messages.pull.option.status:
|
||||
ইতিহাস টানার সময় WhatsApp স্ট্যাটাস আপডেট অন্তর্ভুক্ত করুন।
|
||||
cli.cmd.messages.pull.option.broadcast:
|
||||
ইতিহাস টানার সময় ব্রডকাস্ট তালিকা অন্তর্ভুক্ত করুন।
|
||||
cli.cmd.messages.pull.option.media:
|
||||
ইতিহাস টানার সময় মিডিয়া (ছবি, ভিডিও, ফাইল) ডাউনলোড করুন।
|
||||
cli.cmd.messages.pull.option.pause:
|
||||
বার্তা টানার কাজটিকে বিরতি অবস্থায় কিউতে রাখে এবং শেষ না হওয়া পর্যন্ত নতুন
|
||||
WhatsApp বার্তার প্রসেসিং বন্ধ রাখে।
|
||||
cli.cmd.messages.pull.option.resolve-conversations:
|
||||
সমস্ত ইতিহাস বার্তা প্রক্রিয়ার পর কথোপকথনগুলোকে সমাধান হয়েছে হিসেবে চিহ্নিত
|
||||
করে।
|
||||
cli.cmd.messages.pull.error.pause-no-queue:
|
||||
⛔ `--pause` ব্যবহার করা যাবে না কারণ এই কনফিগারেশনে **queue** কমান্ড
|
||||
নিষ্ক্রিয়।
|
||||
cli.cmd.queue.description: কিউ পরিচালনা করুন।
|
||||
cli.cmd.queue.summary: কিউ-তে কর্ম সম্পাদন করুন।
|
||||
cli.cmd.queue.action.description: একটি কর্ম নির্বাচন করুন।
|
||||
cli.cmd.queue.status.description: কিউ-র অবস্থা দেখুন।
|
||||
cli.cmd.queue.argument.name: 'কিউ নাম: `inbox` বা `whatsapp` (ঐচ্ছিক)।'
|
||||
cli.cmd.queue.start.description: নামের ভিত্তিতে কিউ শুরু করুন।
|
||||
cli.cmd.queue.stop.description: নামের ভিত্তিতে কিউ বন্ধ করুন।
|
||||
cli.cmd.queue.paused: ⚪🧩 কিউ বিরতিতে আছে। অবস্থা দেখতে `queue status` পাঠান।
|
||||
cli.cmd.queue.resumed:
|
||||
🟢🧩 কিউ আবার চালু হয়েছে। অবস্থা দেখতে `queue status` পাঠান।
|
||||
cli.cmd.queue.status.result: >-
|
||||
📊 কিউ অবস্থা:
|
||||
|
||||
{{#queues}}
|
||||
|
||||
- **{{name}}**: {{#locked}}🔒{{/locked}}{{#paused}}⚪
|
||||
বিরতিতে{{/paused}}{{^paused}}🟢 চলমান{{/paused}}
|
||||
|
||||
{{/queues}}
|
||||
cli.cmd.messages.pull.option.timeout-media:
|
||||
প্রতিটি বার্তার জন্য মিডিয়া ডাউনলোডের সময়সীমা নির্ধারণ করুন।
|
||||
cli.cmd.messages.pull.argument.end:
|
||||
টানার উইন্ডো কোথায় শেষ হবে নির্ধারণ করুন; যেমন `2d` দুই দিন আগে থামে।
|
||||
cli.cmd.messages.pull.argument.start:
|
||||
টানার উইন্ডো কোথা থেকে শুরু হবে নির্ধারণ করুন; যেমন `1d` এক দিন আগে শুরু হয়
|
||||
(ডিফল্ট `0d`, অর্থাৎ এখন)।
|
||||
cli.cmd.messages.pull.already-running: >-
|
||||
⏳ **Pull Messages** কাজটি ইতিমধ্যে **{{{chat}}}**-এর জন্য চলছে। শেষ হওয়া
|
||||
পর্যন্ত অপেক্ষা করুন অথবা নতুন শুরু করার আগে এটি বন্ধ করুন।
|
||||
|
||||
|
||||
বিস্তারিত জানার জন্য `messages status` পাঠান।
|
||||
cli.cmd.messages.status.not-found:
|
||||
ℹ️ কোনো সক্রিয় **Pull Messages** কাজ পাওয়া যায়নি। `messages pull` দিয়ে একটি
|
||||
শুরু করুন।
|
||||
cli.cmd.messages.pull.no-chats-found:
|
||||
এই সময়সীমার জন্য কোনো WhatsApp চ্যাট পাওয়া যায়নি।
|
||||
cli.cmd.contacts.pull.option.progress:
|
||||
প্রতি N কন্টাক্টে অগ্রগতি জানান। রিপোর্ট বন্ধ করতে 0 সেট করুন।
|
||||
cli.cmd.contacts.pull.option.lids:
|
||||
সিঙ্কে WhatsApp LID (অনামা) কন্টাক্ট অন্তর্ভুক্ত করুন।
|
||||
task.contacts.completed:
|
||||
👤✅ কন্টাক্ট সম্পন্ন → 🆕 {{progress.created}}, 🔄 {{progress.updated}}, ⚪
|
||||
{{progress.skipped}}, 🔴 {{progress.errors}}, 🖼️ {{progress.avatar.updated}}
|
||||
task.messages.started: ⏳ বার্তা টানা হচ্ছে 💬 {{{chat}}} ⏱️ {{{period}}}...
|
||||
task.messages.details: আরও জানতে "{{{prefix}}}messages status" পাঠান।
|
||||
task.messages.progress:
|
||||
'⏳ বার্তা {{{chat}}}: 🆕 {{progress.ok}}, 🟢 {{progress.exists}}, ⚪
|
||||
{{progress.ignored}}, 🔴 {{progress.errors}}{{#messages.last}} | 📅
|
||||
{{{messages.last}}}{{/messages.last}}'
|
||||
task.messages.completed:
|
||||
✅ বার্তা {{{chat}}} {{{period}}}-এ → 🆕 {{progress.ok}}, 🟢
|
||||
{{progress.exists}}, ⚪ {{progress.ignored}}, 🔴 {{progress.errors}}
|
||||
task.messages.status: |-
|
||||
**{{{chat}}}**-এর বার্তা ⏱️ **{{{period}}}** সময়ে। ⚙️ কাজের অবস্থা: **{{state}}**.
|
||||
|
||||
🗨️ বার্তা:
|
||||
- 🆕 তৈরি: **{{{progress.ok}}}**
|
||||
- 🟢 আগেই ছিল: {{{progress.exists}}}
|
||||
- ⚪ উপেক্ষা: {{{progress.ignored}}}
|
||||
- 🔴 ত্রুটি: {{{progress.errors}}}
|
||||
{{#messages.first}}
|
||||
- 🗓️ প্রাচীনতম: **{{{messages.first}}}**
|
||||
{{/messages.first}}
|
||||
{{#messages.last}}
|
||||
- 🗓️ সর্বশেষ: **{{{messages.last}}}**
|
||||
{{/messages.last}}
|
||||
|
||||
💬 WhatsApp চ্যাট:
|
||||
- 🆕 **{{{chats}}}** চ্যাটে নতুন বার্তা
|
||||
{{#options.resolveConversations}}
|
||||
|
||||
🗣️ কথোপকথন:
|
||||
- 🟢 সমাধান হয়েছে: **{{{progress.conversationResolve.success}}}**
|
||||
{{#progress.conversationResolve.error}}
|
||||
- 🔴 ত্রুটি: **{{{progress.conversationResolve.error}}}**
|
||||
{{/progress.conversationResolve.error}}
|
||||
{{/options.resolveConversations}}
|
||||
|
||||
বিস্তারিত:
|
||||
[{{{details.text}}}]({{{details.url}}})
|
||||
@@ -1,8 +1,33 @@
|
||||
# German translations for ChatWoot integration
|
||||
# German translations for WhatsApp inbox integration
|
||||
locale.name: '🇩🇪 Deutsch (German)'
|
||||
app.inbox.contact.name: 'WhatsApp Integration (WAHA)'
|
||||
|
||||
# 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:
|
||||
weekday: short
|
||||
locales: de-DE
|
||||
day: 2-digit
|
||||
month: short
|
||||
year: numeric
|
||||
hour: 2-digit
|
||||
minute: 2-digit
|
||||
hour12: false
|
||||
# https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat#timezonename
|
||||
timeZoneName: short
|
||||
# https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
|
||||
# timeZone: 'Europe/Berlin'
|
||||
|
||||
Today: 'Heute'
|
||||
Yesterday: 'Gestern'
|
||||
|
||||
job.report.error: |-
|
||||
⚠️ **{{{header}}}**
|
||||
{{#attempts.nextDelay}}
|
||||
@@ -34,14 +59,14 @@ whatsapp.message.removing.error:
|
||||
job.scheduled.error.header: Geplante Aufgabe konnte nicht ausgeführt werden
|
||||
|
||||
message.from.whatsapp: |-
|
||||
{{{text}}}
|
||||
*📱 Von WhatsApp gesendet*
|
||||
|
||||
📱 *Von WhatsApp gesendet*
|
||||
{{{text}}}
|
||||
|
||||
message.from.api: |-
|
||||
{{{text}}}
|
||||
*📱 Über API gesendet*
|
||||
|
||||
📱 *Über API gesendet*
|
||||
{{{text}}}
|
||||
|
||||
message.removed.in.whatsapp: |-
|
||||
❌ *Diese Nachricht wurde in WhatsApp gelöscht*
|
||||
@@ -69,11 +94,11 @@ whatsapp.contact.broadcast.suffix: 'Broadcast-Liste'
|
||||
whatsapp.contact.status.name: 'Status'
|
||||
|
||||
app.connected.message: |-
|
||||
🔗 **VERBUNDEN: WhatsApp-Sitzung mit ChatWoot Inbox**
|
||||
🔗 **VERBUNDEN: WhatsApp-Sitzung mit der Inbox**
|
||||
|
||||
✅ WhatsApp-Sitzung **{{{name}}}** wurde erfolgreich mit dieser ChatWoot Inbox verbunden.
|
||||
✅ WhatsApp-Sitzung **{{{name}}}** wurde erfolgreich mit dieser 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.
|
||||
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 Ihrer Arbeitsumgebung.
|
||||
|
||||
Sie können auch die mit dieser Inbox verknüpfte WhatsApp-Sitzung verwalten. Um verfügbare Aktionen anzuzeigen, senden Sie **help** an diesen Chat.
|
||||
|
||||
@@ -87,9 +112,9 @@ app.connected.message: |-
|
||||
Lassen Sie uns beginnen! 🚀
|
||||
|
||||
app.disconnected.message: |-
|
||||
🔌 **GETRENNT: WhatsApp-Sitzung von ChatWoot Inbox**
|
||||
🔌 **GETRENNT: WhatsApp-Sitzung von der Inbox**
|
||||
|
||||
❌ WhatsApp-Sitzung **{{{name}}}** wurde von dieser ChatWoot Inbox getrennt.
|
||||
❌ WhatsApp-Sitzung **{{{name}}}** wurde von dieser Inbox getrennt.
|
||||
|
||||
Sie können keine Nachrichten mehr von WhatsApp senden oder empfangen.
|
||||
|
||||
@@ -100,34 +125,156 @@ app.updated.message: |-
|
||||
|
||||
✅ 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:
|
||||
cli.usage.arguments: |-
|
||||
[Aktion]
|
||||
|
||||
🖥️ **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.
|
||||
cli.usage.command: |-
|
||||
[Befehl]
|
||||
|
||||
{{#commands.server}}
|
||||
⚙️ Server:
|
||||
- **server status** - Serverversion und Statusinformationen abrufen.
|
||||
- **server reboot** - Server ordnungsgemäß neu starten.
|
||||
- **server reboot force** - Server erzwungen neu starten.
|
||||
{{/commands.server}}
|
||||
cli.usage.options: |-
|
||||
[Optionen]
|
||||
|
||||
Senden Sie einen **Befehl** (Groß-/Kleinschreibung wird nicht beachtet) oder eine **Befehlsnummer** an diese Konversation.
|
||||
|
||||
app.commands.server.disabled: ⛔ **Serverbefehle sind deaktiviert**.
|
||||
cli.cmd.disabled: ⛔ **Der Befehl `{{command}}` ist deaktiviert**.
|
||||
|
||||
Bitte wenden Sie sich an Ihren Administrator.
|
||||
|
||||
Senden Sie **help**, um die Liste der verfügbaren Befehle zu erhalten.
|
||||
|
||||
cli.cmd.root.description: |-
|
||||
Verwalten Sie Ihre **WhatsApp-Integration**!
|
||||
|
||||
cli.cmd.root.sub.session: |-
|
||||
🖥️ **Sitzung**:
|
||||
|
||||
cli.cmd.root.sub.server: |-
|
||||
🖥️ **Server**:
|
||||
|
||||
cli.cmd.root.sub.sync: |-
|
||||
🔄️ **Synchronisierung**:
|
||||
|
||||
cli.cmd.session.status.description: |-
|
||||
Aktuellen Sitzungsstatus abrufen.
|
||||
|
||||
cli.cmd.session.restart.description: |-
|
||||
Sitzung neu starten.
|
||||
|
||||
cli.cmd.session.start.description: |-
|
||||
Sitzung starten.
|
||||
|
||||
cli.cmd.session.stop.description: |-
|
||||
Sitzung stoppen.
|
||||
|
||||
cli.cmd.session.logout.description: |-
|
||||
Melden Sie die Sitzung ab, um einen neuen QR-Code zu scannen und die Verbindung wiederherzustellen.
|
||||
|
||||
cli.cmd.session.qr.description: |-
|
||||
QR-Code der Sitzung anzeigen.
|
||||
|
||||
cli.cmd.session.screenshot.description: |-
|
||||
Einen Screenshot der Sitzung aufnehmen (nur mit der **WEBJS**-Engine verfügbar).
|
||||
|
||||
cli.cmd.contacts.summary: |-
|
||||
WhatsApp-Kontakte abrufen.
|
||||
|
||||
cli.cmd.contacts.description: |-
|
||||
👤 Führen Sie `contacts pull` aus, um eine Synchronisierung zu starten, oder `contacts status`, um den Fortschritt zu prüfen.
|
||||
|
||||
cli.cmd.contacts.action.description: |-
|
||||
Mit `start` den Abruf beginnen oder mit `status` den aktiven Auftrag prüfen.
|
||||
|
||||
cli.cmd.contacts.pull.description: |-
|
||||
Beginnen Sie, WhatsApp-Kontakte in die Inbox zu importieren.
|
||||
|
||||
cli.cmd.contacts.pull.option.batch: |-
|
||||
Anzahl der Kontakte pro Batch.
|
||||
|
||||
cli.cmd.contacts.pull.option.delay-contact: |-
|
||||
Verzögerung zwischen einzelnen Kontakten, um beim Laden von Avataren Rate Limits zu vermeiden.
|
||||
|
||||
cli.cmd.contacts.pull.option.delay-batch: |-
|
||||
Verzögerung zwischen Kontakt-Batches.
|
||||
|
||||
cli.cmd.contacts.pull.option.avatar: |-
|
||||
Legen Sie fest, wie Avatare geladen werden: `skip`, `if-missing` oder `update`.
|
||||
|
||||
cli.cmd.contacts.pull.option.groups: |-
|
||||
WhatsApp-Gruppenkontakte in die Synchronisierung einbeziehen.
|
||||
|
||||
cli.cmd.contacts.pull.option.no-attributes: |-
|
||||
Keine Kontaktattribute laden, wenn der Kontakt bereits existiert (beschleunigt den Vorgang).
|
||||
|
||||
cli.cmd.contacts.pull.already-running: |-
|
||||
⏳ Der Auftrag **Pull Contacts** läuft bereits. Warten Sie, bis er abgeschlossen ist, oder stoppen Sie ihn, bevor Sie einen neuen starten.
|
||||
|
||||
Senden Sie `contacts status` für Details.
|
||||
|
||||
cli.cmd.options.job.attempts: |-
|
||||
Anzahl der erneuten Versuche, bevor der Auftrag als fehlgeschlagen markiert wird.
|
||||
|
||||
cli.cmd.options.job.timeout: |-
|
||||
Maximale Laufzeit, bevor der Auftrag erneut versucht wird (unterstützt Werte wie `30s`, `5m`, `1h`).
|
||||
|
||||
cli.cmd.contacts.status.description: |-
|
||||
Status des aktuellen Abrufauftrags anzeigen.
|
||||
|
||||
cli.cmd.contacts.status.not-found: |-
|
||||
ℹ️ Kein aktiver **Pull Contacts** Auftrag gefunden. Starten Sie einen mit `contacts pull`.
|
||||
|
||||
cli.cmd.server.description: |-
|
||||
Server verwalten.
|
||||
|
||||
cli.cmd.server.status.description: |-
|
||||
Serverversion und -status anzeigen.
|
||||
|
||||
cli.cmd.server.reboot.description: |-
|
||||
Server neu starten.
|
||||
|
||||
cli.cmd.server.reboot.option.force: |-
|
||||
Neustart sofort erzwingen.
|
||||
|
||||
cli.help.usage.title: |-
|
||||
Verwendung: Senden Sie einen Befehl im folgenden Format an die Konversation:
|
||||
|
||||
cli.help.commands.defaultGroup: |-
|
||||
**Befehle:**
|
||||
|
||||
cli.help.arguments.title: |-
|
||||
**Argumente:**
|
||||
|
||||
cli.help.options.defaultGroup: |-
|
||||
**Optionen:**
|
||||
|
||||
cli.help.globalOptions.title: |-
|
||||
**Globale Optionen:**
|
||||
|
||||
task.contacts.status: |-
|
||||
👤 Kontakte abrufen: **{{state}}**
|
||||
- ✅ Erfolgreich: {{{progress.ok}}} Kontakte
|
||||
- ⚪ Übersprungen: {{{progress.skipped}}} Kontakte
|
||||
- ⚠️ Fehler: {{{progress.errors}}} Kontakte
|
||||
|
||||
{{#job}}
|
||||
⌛ Zeitplan:
|
||||
- Eingereiht: {{{timestamp}}}
|
||||
{{#processedOn}}
|
||||
- Start: {{{processedOn}}}
|
||||
{{/processedOn}}
|
||||
{{#finishedOn}}
|
||||
- Ende: {{{finishedOn}}}
|
||||
{{/finishedOn}}
|
||||
{{/job}}
|
||||
|
||||
{{#error}}
|
||||
Details:
|
||||
[{{{details.text}}}]({{{details.url}}})
|
||||
{{/error}}
|
||||
|
||||
task.contacts.started: |-
|
||||
Kontakte werden abgerufen...
|
||||
|
||||
task.contacts.progress: |-
|
||||
Kontakte abrufen: ✅ {{progress.ok}}, ⚪ {{progress.skipped}}, ⚠️ {{progress.errors}}
|
||||
|
||||
app.help.reminder: |-
|
||||
Senden Sie **help**, um die Liste der verfügbaren Befehle zu erhalten.
|
||||
|
||||
@@ -141,17 +288,17 @@ app.session.current.status: |-
|
||||
📱 **{{{name}}} ({{{id}}})**
|
||||
|
||||
app.session.status.working: |-
|
||||
✅ Diese ChatWoot Inbox ist mit dem **WhatsApp-Konto** verknüpft und bereit zum Senden und Empfangen von Nachrichten!
|
||||
✅ Diese Inbox ist mit Ihrem **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!**
|
||||
⚠️ **Diese 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!**
|
||||
⚠️ **Scannen Sie den QR-Code auf Ihrem Telefon, um das WhatsApp-Konto mit dieser Inbox zu verknüpfen!**
|
||||
|
||||
app.server.version.and.status: |-
|
||||
**Version:**
|
||||
@@ -219,7 +366,19 @@ whatsapp.to.chatwoot.message.location: |-
|
||||
{{#locationMessage}}
|
||||
📍 **Geteilter Standort**
|
||||
|
||||
https://www.google.com/maps?q={{degreesLatitude}},{{degreesLongitude}}
|
||||
{{#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}}
|
||||
|
||||
@@ -227,13 +386,299 @@ whatsapp.to.chatwoot.message.location: |-
|
||||
📍📡 **Live-Standort wird geteilt**
|
||||
Öffne **WhatsApp**, um Aktualisierungen in Echtzeit zu sehen.
|
||||
|
||||
https://www.google.com/maps?q={{degreesLatitude}},{{degreesLongitude}}
|
||||
{{#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.**
|
||||
⚠️ **Dieser Nachrichtentyp wird in dieser Inbox nicht unterstützt.**
|
||||
📱 Bitte öffne **WhatsApp**, um ihn anzusehen.
|
||||
|
||||
Details: [{{{details.text}}}]({{{details.url}}})
|
||||
|
||||
whatsapp.to.chatwoot.message.album: |-
|
||||
📷 **Album-Nachricht Empfangen**
|
||||
|
||||
{{#totalCount}}
|
||||
Ein Album mit **{{totalCount}}** Mediendatei(en) erhalten.
|
||||
{{#expectedImageCount}}
|
||||
📸 Bilder: **{{expectedImageCount}}**
|
||||
{{/expectedImageCount}}
|
||||
{{#expectedVideoCount}}
|
||||
🎥 Videos: **{{expectedVideoCount}}**
|
||||
{{/expectedVideoCount}}
|
||||
|
||||
Die Mediendateien werden in den folgenden Nachrichten geliefert.
|
||||
{{/totalCount}}
|
||||
|
||||
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}}}
|
||||
whatsapp.history.message.wrapper: |-
|
||||
{{{content}}}
|
||||
`{{#history}}📥{{/history}}🗓️ {{{timestamp}}}`
|
||||
|
||||
UNKNOWN: Unbekannt
|
||||
ERROR: Fehler
|
||||
PENDING: Wird gesendet
|
||||
SERVER: Server
|
||||
DEVICE: Gerät
|
||||
READ: Gelesen
|
||||
PLAYED: Abgespielt
|
||||
whatsapp.to.chatwoot.message.has.media.no.media: >-
|
||||
{{#content}}
|
||||
|
||||
{{{content}}}
|
||||
|
||||
|
||||
{{/content}}
|
||||
|
||||
🖼️⚠️ **Diese Nachricht enthält Medien, konnte aber nicht heruntergeladen
|
||||
werden**
|
||||
|
||||
📱 Bitte öffnen Sie **WhatsApp**, um sie anzusehen.
|
||||
|
||||
|
||||
{{^content}}
|
||||
|
||||
Details: [{{{details.text}}}]({{{details.url}}})
|
||||
|
||||
{{/content}}
|
||||
cli.cmd.root.sub.queue: '🧩 **Warteschlange**:'
|
||||
cli.cmd.messages.summary: WhatsApp-Nachrichten abrufen.
|
||||
cli.cmd.messages.description:
|
||||
💬 Führen Sie `messages pull` aus, um eine Synchronisierung zu starten, oder
|
||||
`messages status`, um den Fortschritt zu prüfen.
|
||||
cli.cmd.messages.action.description:
|
||||
'`pull`, um das Abrufen zu starten, oder `status`, um den aktuellen Job zu
|
||||
prüfen.'
|
||||
cli.cmd.messages.pull.option.chat:
|
||||
Beschränkt das Abrufen auf eine bestimmte WhatsApp-Chat-ID.
|
||||
cli.cmd.messages.pull.option.force:
|
||||
Erzwingt, dass die Inbox Nachrichten erstellt, auch wenn die
|
||||
WhatsApp-Nachricht bereits im Gesprächs-Mapping vorhanden ist.
|
||||
cli.cmd.messages.pull.option.batch:
|
||||
Anzahl der Nachrichten, die pro Paket abgerufen werden.
|
||||
cli.cmd.messages.pull.option.progress:
|
||||
Meldet den Fortschritt nach N Nachrichten. 0 deaktiviert die
|
||||
Fortschrittsmeldungen.
|
||||
cli.cmd.messages.pull.option.no-dm:
|
||||
Ignoriert Direktnachrichten beim Abrufen des Verlaufs.
|
||||
cli.cmd.messages.pull.option.groups:
|
||||
Bezieht WhatsApp-Gruppen beim Abrufen mit ein.
|
||||
cli.cmd.messages.pull.option.channels:
|
||||
Bezieht WhatsApp-Kanäle beim Abrufen mit ein.
|
||||
cli.cmd.messages.pull.option.status:
|
||||
Bezieht WhatsApp-Statusmeldungen beim Abrufen mit ein.
|
||||
cli.cmd.messages.pull.option.broadcast:
|
||||
Bezieht Broadcast-Listen beim Abrufen mit ein.
|
||||
cli.cmd.messages.pull.option.media:
|
||||
Lädt Medienanhänge (Bilder, Videos, Dateien) beim Abrufen herunter.
|
||||
cli.cmd.messages.pull.option.pause:
|
||||
Stellt den Nachrichtenabruf in der Warteschlange auf Pause und stoppt neue
|
||||
WhatsApp-Verarbeitung bis zum Abschluss.
|
||||
cli.cmd.messages.pull.option.resolve-conversations:
|
||||
Markiert die Gespräche nach Abschluss aller historischen Nachrichten als
|
||||
erledigt.
|
||||
cli.cmd.messages.pull.error.pause-no-queue:
|
||||
⛔ `--pause` kann nicht verwendet werden, weil der **queue**-Befehl in dieser
|
||||
Konfiguration deaktiviert ist.
|
||||
cli.cmd.queue.description: Warteschlangen verwalten.
|
||||
cli.cmd.queue.summary: Aktionen für Warteschlangen durchführen.
|
||||
cli.cmd.queue.action.description: Wählen Sie eine Aktion.
|
||||
cli.cmd.queue.status.description: Zeigt den Status einer Warteschlange an.
|
||||
cli.cmd.queue.argument.name:
|
||||
'Name der Warteschlange: `inbox` oder `whatsapp` (optional).'
|
||||
cli.cmd.queue.start.description: Startet eine Warteschlange anhand des Namens.
|
||||
cli.cmd.queue.stop.description: Stoppt eine Warteschlange anhand des Namens.
|
||||
cli.cmd.queue.paused:
|
||||
⚪🧩 Die Warteschlangen wurden pausiert. Senden Sie `queue status`, um den
|
||||
Status zu prüfen.
|
||||
cli.cmd.queue.resumed:
|
||||
🟢🧩 Die Warteschlangen wurden fortgesetzt. Senden Sie `queue status`, um den
|
||||
Status zu prüfen.
|
||||
cli.cmd.queue.status.result: >-
|
||||
📊 Warteschlangenstatus:
|
||||
|
||||
{{#queues}}
|
||||
|
||||
- **{{name}}**: {{#locked}}🔒{{/locked}}{{#paused}}⚪
|
||||
Pausiert{{/paused}}{{^paused}}🟢 Aktiv{{/paused}}
|
||||
|
||||
{{/queues}}
|
||||
cli.cmd.messages.pull.option.timeout-media:
|
||||
Legt fest, wie lange versucht wird, Medien je Nachricht herunterzuladen.
|
||||
cli.cmd.messages.pull.argument.end:
|
||||
Legt fest, wo das Abruffenster endet; `2d` bedeutet vor zwei Tagen.
|
||||
cli.cmd.messages.pull.argument.start:
|
||||
Legt fest, wo das Abruffenster beginnt; `1d` bedeutet vor einem Tag (Standard
|
||||
`0d`, also jetzt).
|
||||
cli.cmd.messages.pull.already-running: >-
|
||||
⏳ Der Job **Pull Messages** läuft bereits für **{{{chat}}}**. Warten Sie bis
|
||||
zum Abschluss oder stoppen Sie ihn, bevor Sie einen neuen starten.
|
||||
|
||||
|
||||
Senden Sie `messages status` für Details.
|
||||
cli.cmd.messages.status.not-found:
|
||||
ℹ️ Kein aktiver **Pull Messages**-Job gefunden. Starten Sie einen mit
|
||||
`messages pull`.
|
||||
cli.cmd.messages.pull.no-chats-found:
|
||||
Keine WhatsApp-Chats für diesen Zeitraum gefunden.
|
||||
cli.cmd.contacts.pull.option.progress:
|
||||
Meldet den Fortschritt nach N Kontakten. 0 deaktiviert die Meldungen.
|
||||
cli.cmd.contacts.pull.option.lids:
|
||||
Bezieht WhatsApp-LID-Kontakte (anonyme Kontakte) in die Synchronisierung ein.
|
||||
task.contacts.completed:
|
||||
👤✅ Kontakte abgeschlossen → 🆕 {{progress.created}}, 🔄
|
||||
{{progress.updated}}, ⚪ {{progress.skipped}}, 🔴 {{progress.errors}}, 🖼️
|
||||
{{progress.avatar.updated}}
|
||||
task.messages.started:
|
||||
⏳ Nachrichten werden abgerufen 💬 {{{chat}}} ⏱️ {{{period}}}...
|
||||
task.messages.details:
|
||||
Senden Sie "{{{prefix}}}messages status", um weitere Details zu sehen.
|
||||
task.messages.progress:
|
||||
'⏳ Nachrichten {{{chat}}}: 🆕 {{progress.ok}}, 🟢 {{progress.exists}}, ⚪
|
||||
{{progress.ignored}}, 🔴 {{progress.errors}}{{#messages.last}} | 📅
|
||||
{{{messages.last}}}{{/messages.last}}'
|
||||
task.messages.completed:
|
||||
✅ Nachrichten {{{chat}}} in {{{period}}} → 🆕 {{progress.ok}}, 🟢
|
||||
{{progress.exists}}, ⚪ {{progress.ignored}}, 🔴 {{progress.errors}}
|
||||
task.messages.status: |-
|
||||
Nachrichten **{{{chat}}}** im Zeitraum ⏱️ **{{{period}}}**. ⚙️ Jobstatus: **{{state}}**.
|
||||
|
||||
🗨️ Nachrichten:
|
||||
- 🆕 Erstellt: **{{{progress.ok}}}**
|
||||
- 🟢 Bereits vorhanden: {{{progress.exists}}}
|
||||
- ⚪ Ignoriert: {{{progress.ignored}}}
|
||||
- 🔴 Fehler: {{{progress.errors}}}
|
||||
{{#messages.first}}
|
||||
- 🗓️ Älteste: **{{{messages.first}}}**
|
||||
{{/messages.first}}
|
||||
{{#messages.last}}
|
||||
- 🗓️ Neueste: **{{{messages.last}}}**
|
||||
{{/messages.last}}
|
||||
|
||||
💬 WhatsApp-Chats:
|
||||
- 🆕 Neue Nachrichten in **{{{chats}}}** Chats
|
||||
{{#options.resolveConversations}}
|
||||
|
||||
🗣️ Konversationen:
|
||||
- 🟢 Aufgelöst: **{{{progress.conversationResolve.success}}}**
|
||||
{{#progress.conversationResolve.error}}
|
||||
- 🔴 Fehler: **{{{progress.conversationResolve.error}}}**
|
||||
{{/progress.conversationResolve.error}}
|
||||
{{/options.resolveConversations}}
|
||||
|
||||
Details:
|
||||
[{{{details.text}}}]({{{details.url}}})
|
||||
@@ -1,9 +1,32 @@
|
||||
# English translations for ChatWoot integration
|
||||
# English translations for WhatsApp inbox integration
|
||||
locale.name: '🇺🇸 English (English)'
|
||||
|
||||
app.inbox.contact.name: 'WhatsApp Integration (WAHA)'
|
||||
# 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:
|
||||
weekday: short
|
||||
day: 2-digit
|
||||
month: short
|
||||
year: numeric
|
||||
hour: 2-digit
|
||||
minute: 2-digit
|
||||
hour12: true
|
||||
# https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat#timezonename
|
||||
timeZoneName: short
|
||||
# https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
|
||||
# timeZone: 'America/New_York'
|
||||
|
||||
Today: Today
|
||||
Yesterday: Yesterday
|
||||
|
||||
job.report.error: |-
|
||||
⚠️ **{{{header}}}**
|
||||
{{#attempts.nextDelay}}
|
||||
@@ -32,14 +55,14 @@ 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*
|
||||
|
||||
📱 *Sent from WhatsApp*
|
||||
{{{text}}}
|
||||
|
||||
message.from.api: |-
|
||||
{{{text}}}
|
||||
*📱 Sent via API*
|
||||
|
||||
📱 *Sent via API*
|
||||
{{{text}}}
|
||||
|
||||
message.removed.in.whatsapp: |-
|
||||
❌ *This message was deleted in WhatsApp*
|
||||
@@ -54,6 +77,32 @@ whatsapp.group.message: |-
|
||||
|
||||
{{{text}}}
|
||||
|
||||
whatsapp.history.message.wrapper: |-
|
||||
{{{content}}}
|
||||
`{{#history}}📥{{/history}}🗓️ {{{timestamp}}}`
|
||||
|
||||
# WhatsApp Messages Ack
|
||||
UNKNOWN: |-
|
||||
Unknown
|
||||
|
||||
ERROR: |-
|
||||
Error
|
||||
|
||||
PENDING: |-
|
||||
Sending
|
||||
|
||||
SERVER: |-
|
||||
Server
|
||||
|
||||
DEVICE: |-
|
||||
Device
|
||||
|
||||
READ: |-
|
||||
Read
|
||||
|
||||
PLAYED: |-
|
||||
Played
|
||||
|
||||
whatsapp.reaction.added: |-
|
||||
*Reacted* {{{emoji}}}
|
||||
|
||||
@@ -95,7 +144,19 @@ whatsapp.to.chatwoot.message.location: |-
|
||||
{{#locationMessage}}
|
||||
📍 **Shared Location**
|
||||
|
||||
https://www.google.com/maps?q={{degreesLatitude}},{{degreesLongitude}}
|
||||
{{#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}}
|
||||
|
||||
@@ -103,23 +164,164 @@ whatsapp.to.chatwoot.message.location: |-
|
||||
📍📡 **Sharing Live Location**
|
||||
Open **WhatsApp** to see real-time updates.
|
||||
|
||||
https://www.google.com/maps?q={{degreesLatitude}},{{degreesLongitude}}
|
||||
{{#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.has.media.no.media: |-
|
||||
{{#content}}
|
||||
{{{content}}}
|
||||
|
||||
{{/content}}
|
||||
🖼️⚠️ **The message has media, but we couldn't download it**
|
||||
📱 Please open **WhatsApp** to view it.
|
||||
|
||||
{{^content}}
|
||||
Details: [{{{details.text}}}]({{{details.url}}})
|
||||
{{/content}}
|
||||
|
||||
whatsapp.to.chatwoot.message.unsupported: |-
|
||||
⚠️ **This message type is not supported in ChatWoot.**
|
||||
⚠️ **This message type is not supported in this inbox.**
|
||||
📱 Please open **WhatsApp** to view it.
|
||||
|
||||
Details: [{{{details.text}}}]({{{details.url}}})
|
||||
|
||||
whatsapp.to.chatwoot.message.album: |-
|
||||
📷 **Album Message Received**
|
||||
|
||||
{{#totalCount}}
|
||||
Received an album with **{{totalCount}}** media file(s).
|
||||
{{#expectedImageCount}}
|
||||
📸 Images: **{{expectedImageCount}}**
|
||||
{{/expectedImageCount}}
|
||||
{{#expectedVideoCount}}
|
||||
🎥 Videos: **{{expectedVideoCount}}**
|
||||
{{/expectedVideoCount}}
|
||||
|
||||
The media files will be delivered in the following messages.
|
||||
{{/totalCount}}
|
||||
|
||||
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**
|
||||
🔗 **CONNECTED: WhatsApp Session to Inbox**
|
||||
|
||||
✅ WhatsApp session **{{{name}}}** has been successfully connected to this ChatWoot Inbox.
|
||||
✅ WhatsApp session **{{{name}}}** has been successfully connected to this 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.
|
||||
This inbox is now linked to your **WhatsApp Account**. You can send and receive messages directly from here — just like using WhatsApp, but inside your workspace.
|
||||
|
||||
You can also manage the WhatsApp session linked to this inbox. To view available actions, send **help** to this chat.
|
||||
|
||||
@@ -133,9 +335,9 @@ app.connected.message: |-
|
||||
Let's get started! 🚀
|
||||
|
||||
app.disconnected.message: |-
|
||||
🔌 **DISCONNECTED: WhatsApp Session from ChatWoot Inbox**
|
||||
🔌 **DISCONNECTED: WhatsApp Session from Inbox**
|
||||
|
||||
❌ WhatsApp session **{{{name}}}** has been disconnected from this ChatWoot Inbox.
|
||||
❌ WhatsApp session **{{{name}}}** has been disconnected from this inbox.
|
||||
|
||||
You will no longer be able to send or receive messages from WhatsApp.
|
||||
|
||||
@@ -146,34 +348,299 @@ app.updated.message: |-
|
||||
|
||||
✅ Settings for WhatsApp session **{{{name}}}** have been updated.
|
||||
|
||||
app.commands.list: |-
|
||||
You can manage **WhatsApp Integration** by sending one of the following commands:
|
||||
cli.usage.arguments: |-
|
||||
[action]
|
||||
|
||||
🖥️ **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.
|
||||
cli.usage.command: |-
|
||||
[command]
|
||||
|
||||
{{#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.
|
||||
cli.usage.options: |-
|
||||
[options]
|
||||
|
||||
{{/commands.server}}
|
||||
Send a **command** (case insensitive) or a **command number** to this conversation.
|
||||
|
||||
app.commands.server.disabled: ⛔ **Server commands are disabled**.
|
||||
cli.cmd.disabled: ⛔ **`{{command}}` command is disabled**.
|
||||
|
||||
Please contact your administrator.
|
||||
|
||||
Send **help** to get the list of available commands.
|
||||
|
||||
cli.cmd.root.description: |-
|
||||
Manage your **WhatsApp Integration**!
|
||||
|
||||
cli.cmd.root.sub.session: |-
|
||||
🖥️ **Session**:
|
||||
|
||||
cli.cmd.root.sub.queue: |-
|
||||
🧩 **Queue**:
|
||||
|
||||
cli.cmd.root.sub.server: |-
|
||||
🔧 **Server**:
|
||||
|
||||
cli.cmd.root.sub.sync: |-
|
||||
🔄️ **Sync**:
|
||||
|
||||
cli.cmd.session.status.description: |-
|
||||
Get current session status.
|
||||
|
||||
cli.cmd.session.restart.description: |-
|
||||
Restart the session.
|
||||
|
||||
cli.cmd.session.start.description: |-
|
||||
Start the session.
|
||||
|
||||
cli.cmd.session.stop.description: |-
|
||||
Stop the session.
|
||||
|
||||
cli.cmd.session.logout.description: |-
|
||||
Log out of the session to scan a new QR code and restore the connection.
|
||||
|
||||
cli.cmd.session.qr.description: |-
|
||||
Show the session QR code.
|
||||
|
||||
cli.cmd.session.screenshot.description: |-
|
||||
Capture a session screenshot (available only with the **WEBJS** engine).
|
||||
|
||||
cli.cmd.messages.summary: |-
|
||||
Pull WhatsApp messages.
|
||||
|
||||
cli.cmd.messages.description: |-
|
||||
💬 Run `messages pull` to request message sync or `messages status` to review progress.
|
||||
|
||||
cli.cmd.messages.action.description: |-
|
||||
`pull` to start a message pull or `status` to inspect the active job.
|
||||
|
||||
cli.cmd.messages.pull.option.chat: |-
|
||||
Limit the pull to a specific WhatsApp Chat ID.
|
||||
|
||||
cli.cmd.messages.pull.option.force: |-
|
||||
Force the inbox to create messages even when the WhatsApp message already exists in the conversation mapping.
|
||||
|
||||
cli.cmd.messages.pull.option.batch: |-
|
||||
Number of messages to pull in each batch.
|
||||
|
||||
cli.cmd.messages.pull.option.progress: |-
|
||||
Report progress every N messages. Set to 0 to disable progress reports.
|
||||
|
||||
cli.cmd.messages.pull.option.no-dm: |-
|
||||
Skip direct messages while pulling history.
|
||||
|
||||
cli.cmd.messages.pull.option.groups: |-
|
||||
Include WhatsApp group chats when pulling history.
|
||||
|
||||
cli.cmd.messages.pull.option.channels: |-
|
||||
Include WhatsApp Channels when pulling history.
|
||||
|
||||
cli.cmd.messages.pull.option.status: |-
|
||||
Include WhatsApp status updates when pulling history.
|
||||
|
||||
cli.cmd.messages.pull.option.broadcast: |-
|
||||
Include broadcast lists when pulling history.
|
||||
|
||||
cli.cmd.messages.pull.option.media: |-
|
||||
Fetch media attachments (images, videos, files) when pulling history.
|
||||
|
||||
cli.cmd.messages.pull.option.pause: |-
|
||||
Enqueue the messages pull job in a paused state, halting new WhatsApp message processing until the pull completes.
|
||||
|
||||
cli.cmd.messages.pull.option.resolve-conversations: |-
|
||||
Mark the conversations as resolved after all history messages are processed.
|
||||
|
||||
cli.cmd.messages.pull.error.pause-no-queue: |-
|
||||
⛔ Cannot use `--pause` because the **queue** command is disabled in this app configuration.
|
||||
|
||||
cli.cmd.queue.description: |-
|
||||
Manage queues.
|
||||
|
||||
cli.cmd.queue.summary: |-
|
||||
Perform queue actions.
|
||||
|
||||
cli.cmd.queue.action.description: |-
|
||||
Choose an action.
|
||||
|
||||
cli.cmd.queue.status.description: |-
|
||||
Show the status of a queue.
|
||||
|
||||
cli.cmd.queue.argument.name: |-
|
||||
Queue name: `inbox` or `whatsapp` (optional).
|
||||
|
||||
cli.cmd.queue.start.description: |-
|
||||
Start a queue by name.
|
||||
|
||||
cli.cmd.queue.stop.description: |-
|
||||
Stop a queue by name.
|
||||
|
||||
cli.cmd.queue.paused: |-
|
||||
⚪🧩 Queues has been paused. Send `queue status` to check the status.
|
||||
|
||||
cli.cmd.queue.resumed: |-
|
||||
🟢🧩 Queues has been resumed. Send `queue status` to check the status.
|
||||
|
||||
cli.cmd.queue.status.result: |-
|
||||
📊 Queue Status:
|
||||
{{#queues}}
|
||||
- **{{name}}**: {{#locked}}🔒{{/locked}}{{#paused}}⚪ Paused{{/paused}}{{^paused}}🟢 Running{{/paused}}
|
||||
{{/queues}}
|
||||
|
||||
cli.cmd.messages.pull.option.timeout-media: |-
|
||||
Set how long to try downloading media for each message.
|
||||
|
||||
cli.cmd.messages.pull.argument.end: |-
|
||||
Set how far back the pull window ends, e.g., `2d` stops at two days ago.
|
||||
|
||||
cli.cmd.messages.pull.argument.start: |-
|
||||
Set where the pull window starts, e.g., `1d` begins one day ago (defaults to `0d`, meaning now).
|
||||
|
||||
cli.cmd.messages.pull.already-running: |-
|
||||
⏳ **Pull Messages** job is already running for **{{{chat}}}**. Wait for it to finish or stop it before starting a new one.
|
||||
|
||||
Send `messages status` for details.
|
||||
|
||||
cli.cmd.messages.status.not-found: |-
|
||||
ℹ️ No active **Pull Messages** job found. Start one with `messages pull`.
|
||||
|
||||
cli.cmd.messages.pull.no-chats-found: |-
|
||||
No WhatsApp chats found to pull messages from for the period.
|
||||
|
||||
cli.cmd.contacts.summary: |-
|
||||
Pull WhatsApp contacts.
|
||||
|
||||
cli.cmd.contacts.description: |-
|
||||
👤 Run `contacts pull` to launch a sync or `contacts status` to inspect progress.
|
||||
|
||||
cli.cmd.contacts.action.description: |-
|
||||
`start` to launch a contact pull or `status` to inspect the active job.
|
||||
|
||||
cli.cmd.contacts.pull.description: |-
|
||||
Start pulling WhatsApp contacts into the inbox.
|
||||
|
||||
cli.cmd.contacts.pull.option.batch: |-
|
||||
Number of contacts to pull in each batch.
|
||||
|
||||
cli.cmd.contacts.pull.option.progress: |-
|
||||
Report progress every N contacts. Set to 0 to disable progress reports.
|
||||
|
||||
cli.cmd.contacts.pull.option.delay-contact: |-
|
||||
Delay between syncing each contact to avoid rate limiting in pulling avatars.
|
||||
|
||||
cli.cmd.contacts.pull.option.delay-batch: |-
|
||||
Delay between batches of contacts.
|
||||
|
||||
cli.cmd.contacts.pull.option.avatar: |-
|
||||
Choose how to pull avatars for contacts.
|
||||
|
||||
cli.cmd.contacts.pull.option.groups: |-
|
||||
Include WhatsApp group contacts in the sync.
|
||||
|
||||
cli.cmd.contacts.pull.option.lids: |-
|
||||
Include WhatsApp LID contacts (anonymous contacts) in the sync.
|
||||
|
||||
cli.cmd.contacts.pull.option.no-attributes: |-
|
||||
Do not pull contact attributes if contact already exists (it speeds up the process).
|
||||
|
||||
cli.cmd.contacts.pull.already-running: |-
|
||||
⏳ **Pull Contacts** job is already running. Wait for it to finish or stop it before starting a new one.
|
||||
|
||||
Send `contacts status` for details.
|
||||
|
||||
cli.cmd.options.job.attempts: |-
|
||||
Number of times to retry the job before marking it as failed.
|
||||
|
||||
cli.cmd.options.job.timeout: |-
|
||||
Maximum time the job is allowed to run before it is retried (supports values like `30s`, `5m`, `1h`).
|
||||
|
||||
cli.cmd.contacts.status.description: |-
|
||||
Show the status of the current pull job.
|
||||
|
||||
cli.cmd.contacts.status.not-found: |-
|
||||
ℹ️ No active **Pull Contacts** job found. Start one with `contacts pull`.
|
||||
|
||||
cli.cmd.server.description: |-
|
||||
Manage the server.
|
||||
|
||||
cli.cmd.server.status.description: |-
|
||||
Show the server version and status.
|
||||
|
||||
cli.cmd.server.reboot.description: |-
|
||||
Reboot the server.
|
||||
|
||||
cli.cmd.server.reboot.option.force: |-
|
||||
Force an immediate reboot.
|
||||
|
||||
cli.help.usage.title: |-
|
||||
Send a command to the conversation in the format:
|
||||
|
||||
cli.help.commands.defaultGroup: |-
|
||||
**Commands:**
|
||||
|
||||
cli.help.arguments.title: |-
|
||||
**Arguments:**
|
||||
|
||||
cli.help.options.defaultGroup: |-
|
||||
**Options:**
|
||||
|
||||
cli.help.globalOptions.title: |-
|
||||
**Global Options:**
|
||||
|
||||
task.contacts.started: |-
|
||||
👤⏳ Pulling contacts... Send "contacts status" for more details.
|
||||
|
||||
task.contacts.progress: |-
|
||||
👤⏳ Contacts: 🆕 {{progress.created}}, 🔄 {{progress.updated}}, ⚪ {{progress.skipped}}, 🔴 {{progress.errors}}, 🖼️ {{progress.avatar.updated}}
|
||||
|
||||
task.contacts.completed: |-
|
||||
👤✅ Contacts Completed → 🆕 {{progress.created}}, 🔄 {{progress.updated}}, ⚪ {{progress.skipped}}, 🔴 {{progress.errors}}, 🖼️ {{progress.avatar.updated}}
|
||||
|
||||
task.contacts.status: |-
|
||||
👤 Contacts: **{{state}}**
|
||||
- 🆕 Created: {{{progress.created}}} contacts
|
||||
- 🔄 Updated: {{{progress.updated}}} contacts
|
||||
- ⚪ Skipped: {{{progress.skipped}}} contacts
|
||||
- 🔴 Errors: {{{progress.errors}}} contacts
|
||||
- 🖼️ Avatar Updated: {{{progress.avatar.updated}}} contacts
|
||||
|
||||
Details:
|
||||
[{{{details.text}}}]({{{details.url}}})
|
||||
|
||||
task.messages.started: |-
|
||||
⏳ Pulling messages 💬 {{{chat}}} ⏱️ {{{period}}}...
|
||||
|
||||
task.messages.details: |-
|
||||
Send "{{{prefix}}}messages status" to see more details.
|
||||
|
||||
task.messages.progress: |-
|
||||
⏳ Messages {{{chat}}}: 🆕 {{progress.ok}}, 🟢 {{progress.exists}}, ⚪ {{progress.ignored}}, 🔴 {{progress.errors}}{{#messages.last}} | 📅 {{{messages.last}}}{{/messages.last}}
|
||||
|
||||
task.messages.completed: |-
|
||||
✅ Messages {{{chat}}} in {{{period}}} → 🆕 {{progress.ok}}, 🟢 {{progress.exists}}, ⚪ {{progress.ignored}}, 🔴 {{progress.errors}}
|
||||
|
||||
task.messages.status: |-
|
||||
Messages **{{{chat}}}** in ⏱️ **{{{period}}}** period. ⚙️ Job State: **{{state}}**.
|
||||
|
||||
🗨️ Messages:
|
||||
- 🆕 Created: **{{{progress.ok}}}**
|
||||
- 🟢 Already exists: {{{progress.exists}}}
|
||||
- ⚪ Ignored: {{{progress.ignored}}}
|
||||
- 🔴 Errors: {{{progress.errors}}}
|
||||
{{#messages.first}}
|
||||
- 🗓️ Earliest: **{{{messages.first}}}**
|
||||
{{/messages.first}}
|
||||
{{#messages.last}}
|
||||
- 🗓️ Latest: **{{{messages.last}}}**
|
||||
{{/messages.last}}
|
||||
|
||||
💬 WhatsApp Chats:
|
||||
- 🆕 New messages in **{{{chats}}}** chats
|
||||
{{#options.resolveConversations}}
|
||||
|
||||
🗣️ Conversations:
|
||||
- 🟢 Resolved: **{{{progress.conversationResolve.success}}}**
|
||||
{{#progress.conversationResolve.error}}
|
||||
- 🔴 Errors: **{{{progress.conversationResolve.error}}}**
|
||||
{{/progress.conversationResolve.error}}
|
||||
{{/options.resolveConversations}}
|
||||
|
||||
Details:
|
||||
[{{{details.text}}}]({{{details.url}}})
|
||||
|
||||
app.help.reminder: |-
|
||||
Send **help** to get the list of available commands.
|
||||
|
||||
@@ -187,17 +654,17 @@ app.session.current.status: |-
|
||||
📱 **{{{name}}} ({{{id}}})**
|
||||
|
||||
app.session.status.working: |-
|
||||
✅ This ChatWoot Inbox is linked to **WhatsApp Account** and ready to send and receive messages!
|
||||
✅ This inbox is linked to your **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!**
|
||||
⚠️ **This inbox is linked to your 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!**
|
||||
⚠️ **Scan the QR code on your phone to link your WhatsApp Account with this inbox!**
|
||||
|
||||
app.server.version.and.status: |-
|
||||
**Version:**
|
||||
|
||||
@@ -1,8 +1,33 @@
|
||||
# Spanish translations for ChatWoot integration
|
||||
# Spanish translations for WhatsApp inbox integration
|
||||
locale.name: '🇪🇸 Español (Spanish)'
|
||||
app.inbox.contact.name: 'Integración de WhatsApp (WAHA)'
|
||||
|
||||
# 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:
|
||||
weekday: short
|
||||
locales: es-ES
|
||||
day: 2-digit
|
||||
month: short
|
||||
year: numeric
|
||||
hour: 2-digit
|
||||
minute: 2-digit
|
||||
hour12: false
|
||||
# https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat#timezonename
|
||||
timeZoneName: short
|
||||
# https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
|
||||
# timeZone: 'Europe/Madrid'
|
||||
|
||||
Today: 'Hoy'
|
||||
Yesterday: 'Ayer'
|
||||
|
||||
job.report.error: |-
|
||||
⚠️ **{{{header}}}**
|
||||
{{#attempts.nextDelay}}
|
||||
@@ -31,14 +56,14 @@ 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*
|
||||
|
||||
📱 *Enviado desde WhatsApp*
|
||||
{{{text}}}
|
||||
|
||||
message.from.api: |-
|
||||
{{{text}}}
|
||||
*📱 Enviado a través de API*
|
||||
|
||||
📱 *Enviado a través de API*
|
||||
{{{text}}}
|
||||
|
||||
message.removed.in.whatsapp: |-
|
||||
❌ *Este mensaje fue eliminado en WhatsApp*
|
||||
@@ -66,11 +91,11 @@ whatsapp.contact.broadcast.suffix: 'Lista de difusión'
|
||||
whatsapp.contact.status.name: 'Estado'
|
||||
|
||||
app.connected.message: |-
|
||||
🔗 **CONECTADO: Sesión de WhatsApp a Bandeja de Entrada de ChatWoot**
|
||||
🔗 **CONECTADO: Sesión de WhatsApp a la bandeja de entrada**
|
||||
|
||||
✅ La sesión de WhatsApp **{{{name}}}** ha sido conectada exitosamente a esta Bandeja de Entrada de ChatWoot.
|
||||
✅ La sesión de WhatsApp **{{{name}}}** ha sido conectada exitosamente a esta bandeja de entrada.
|
||||
|
||||
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.
|
||||
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 tu espacio de trabajo.
|
||||
|
||||
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.
|
||||
|
||||
@@ -84,9 +109,9 @@ app.connected.message: |-
|
||||
¡Comencemos! 🚀
|
||||
|
||||
app.disconnected.message: |-
|
||||
🔌 **DESCONECTADO: Sesión de WhatsApp de Bandeja de Entrada de ChatWoot**
|
||||
🔌 **DESCONECTADO: Sesión de WhatsApp de la bandeja de entrada**
|
||||
|
||||
❌ La sesión de WhatsApp **{{{name}}}** ha sido desconectada de esta Bandeja de Entrada de ChatWoot.
|
||||
❌ La sesión de WhatsApp **{{{name}}}** ha sido desconectada de esta bandeja de entrada.
|
||||
|
||||
Ya no podrás enviar o recibir mensajes desde WhatsApp.
|
||||
|
||||
@@ -97,37 +122,156 @@ app.updated.message: |-
|
||||
|
||||
✅ 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:
|
||||
cli.usage.arguments: |-
|
||||
[acción]
|
||||
|
||||
🖥️ **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.
|
||||
cli.usage.command: |-
|
||||
[comando]
|
||||
|
||||
{{#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}}
|
||||
cli.usage.options: |-
|
||||
[opciones]
|
||||
|
||||
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**.
|
||||
cli.cmd.disabled: ⛔ **El comando `{{command}}` está deshabilitado**.
|
||||
|
||||
Por favor, contacta a tu administrador.
|
||||
|
||||
Envía **help** para obtener la lista de comandos disponibles.
|
||||
|
||||
cli.cmd.root.description: |-
|
||||
¡Administra tu **integración de WhatsApp**!
|
||||
|
||||
cli.cmd.root.sub.session: |-
|
||||
🖥️ **Sesión**:
|
||||
|
||||
cli.cmd.root.sub.server: |-
|
||||
🖥️ **Servidor**:
|
||||
|
||||
cli.cmd.root.sub.sync: |-
|
||||
🔄️ **Sincronización**:
|
||||
|
||||
cli.cmd.session.status.description: |-
|
||||
Obtén el estado actual de la sesión.
|
||||
|
||||
cli.cmd.session.restart.description: |-
|
||||
Reinicia la sesión.
|
||||
|
||||
cli.cmd.session.start.description: |-
|
||||
Inicia la sesión.
|
||||
|
||||
cli.cmd.session.stop.description: |-
|
||||
Detén la sesión.
|
||||
|
||||
cli.cmd.session.logout.description: |-
|
||||
Cierra la sesión para escanear un nuevo código QR y restaurar la conexión.
|
||||
|
||||
cli.cmd.session.qr.description: |-
|
||||
Muestra el código QR de la sesión.
|
||||
|
||||
cli.cmd.session.screenshot.description: |-
|
||||
Captura una imagen de la sesión (disponible solo con el motor **WEBJS**).
|
||||
|
||||
cli.cmd.contacts.summary: |-
|
||||
Obtener contactos de WhatsApp.
|
||||
|
||||
cli.cmd.contacts.description: |-
|
||||
👤 Ejecuta `contacts pull` para iniciar una sincronización o `contacts status` para revisar el progreso.
|
||||
|
||||
cli.cmd.contacts.action.description: |-
|
||||
Usa `start` para comenzar la extracción de contactos o `status` para revisar el trabajo activo.
|
||||
|
||||
cli.cmd.contacts.pull.description: |-
|
||||
Empieza a importar contactos de WhatsApp en la bandeja de entrada.
|
||||
|
||||
cli.cmd.contacts.pull.option.batch: |-
|
||||
Cantidad de contactos a extraer en cada lote.
|
||||
|
||||
cli.cmd.contacts.pull.option.delay-contact: |-
|
||||
Retraso entre cada contacto para evitar límites al obtener los avatares.
|
||||
|
||||
cli.cmd.contacts.pull.option.delay-batch: |-
|
||||
Retraso entre los lotes de contactos.
|
||||
|
||||
cli.cmd.contacts.pull.option.avatar: |-
|
||||
Elige cómo obtener los avatares de los contactos: `skip`, `if-missing` o `update`.
|
||||
|
||||
cli.cmd.contacts.pull.option.groups: |-
|
||||
Incluir contactos de grupos de WhatsApp en la sincronización.
|
||||
|
||||
cli.cmd.contacts.pull.option.no-attributes: |-
|
||||
No obtengas los atributos del contacto si ya existe (acelera el proceso).
|
||||
|
||||
cli.cmd.contacts.pull.already-running: |-
|
||||
⏳ El trabajo **Pull Contacts** ya se está ejecutando. Espera a que finalice o deténlo antes de iniciar uno nuevo.
|
||||
|
||||
Envía `contacts status` para más detalles.
|
||||
|
||||
cli.cmd.options.job.attempts: |-
|
||||
Número de intentos antes de marcar el trabajo como fallido.
|
||||
|
||||
cli.cmd.options.job.timeout: |-
|
||||
Tiempo máximo que puede ejecutarse el trabajo antes de reintentarlo (admite valores como `30s`, `5m`, `1h`).
|
||||
|
||||
cli.cmd.contacts.status.description: |-
|
||||
Mostrar el estado del trabajo de importación actual.
|
||||
|
||||
cli.cmd.contacts.status.not-found: |-
|
||||
ℹ️ No se encontró ningún trabajo activo de **Pull Contacts**. Inicia uno con `contacts pull`.
|
||||
|
||||
cli.cmd.server.description: |-
|
||||
Administra el servidor.
|
||||
|
||||
cli.cmd.server.status.description: |-
|
||||
Muestra la versión y el estado del servidor.
|
||||
|
||||
cli.cmd.server.reboot.description: |-
|
||||
Reinicia el servidor.
|
||||
|
||||
cli.cmd.server.reboot.option.force: |-
|
||||
Forzar un reinicio inmediato.
|
||||
|
||||
cli.help.usage.title: |-
|
||||
Uso: envía un comando a la conversación en el formato:
|
||||
|
||||
cli.help.commands.defaultGroup: |-
|
||||
**Comandos:**
|
||||
|
||||
cli.help.arguments.title: |-
|
||||
**Argumentos:**
|
||||
|
||||
cli.help.options.defaultGroup: |-
|
||||
**Opciones:**
|
||||
|
||||
cli.help.globalOptions.title: |-
|
||||
**Opciones globales:**
|
||||
|
||||
task.contacts.status: |-
|
||||
👤 Extracción de contactos: **{{state}}**
|
||||
- ✅ Correctos: {{{progress.ok}}} contactos
|
||||
- ⚪ Omitidos: {{{progress.skipped}}} contactos
|
||||
- ⚠️ Errores: {{{progress.errors}}} contactos
|
||||
|
||||
{{#job}}
|
||||
⌛ Cronología:
|
||||
- En cola: {{{timestamp}}}
|
||||
{{#processedOn}}
|
||||
- Inicio: {{{processedOn}}}
|
||||
{{/processedOn}}
|
||||
{{#finishedOn}}
|
||||
- Finalizó: {{{finishedOn}}}
|
||||
{{/finishedOn}}
|
||||
{{/job}}
|
||||
|
||||
{{#error}}
|
||||
Detalles:
|
||||
[{{{details.text}}}]({{{details.url}}})
|
||||
{{/error}}
|
||||
|
||||
task.contacts.started: |-
|
||||
Extrayendo contactos...
|
||||
|
||||
task.contacts.progress: |-
|
||||
Extracción de contactos: ✅ {{progress.ok}}, ⚪ {{progress.skipped}}, ⚠️ {{progress.errors}}
|
||||
|
||||
app.help.reminder: |-
|
||||
Envía **help** para obtener la lista de comandos disponibles.
|
||||
|
||||
@@ -141,17 +285,17 @@ app.session.current.status: |-
|
||||
📱 **{{{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!
|
||||
✅ ¡Esta bandeja de entrada está vinculada a tu **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!**
|
||||
⚠️ **¡Esta bandeja de entrada 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!**
|
||||
⚠️ **¡Escanea el código QR en tu teléfono para vincular la Cuenta de WhatsApp con esta bandeja de entrada!**
|
||||
|
||||
app.server.version.and.status: |-
|
||||
**Versión:**
|
||||
@@ -219,7 +363,19 @@ whatsapp.to.chatwoot.message.location: |-
|
||||
{{#locationMessage}}
|
||||
📍 **Ubicación compartida**
|
||||
|
||||
https://www.google.com/maps?q={{degreesLatitude}},{{degreesLongitude}}
|
||||
{{#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}}
|
||||
|
||||
@@ -227,13 +383,299 @@ whatsapp.to.chatwoot.message.location: |-
|
||||
📍📡 **Compartiendo ubicación en tiempo real**
|
||||
Abre **WhatsApp** para ver las actualizaciones en tiempo real.
|
||||
|
||||
https://www.google.com/maps?q={{degreesLatitude}},{{degreesLongitude}}
|
||||
{{#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.**
|
||||
⚠️ **Este tipo de mensaje no es compatible con esta bandeja de entrada.**
|
||||
📱 Por favor, abre **WhatsApp** para verlo.
|
||||
|
||||
Detalles: [{{{details.text}}}]({{{details.url}}})
|
||||
|
||||
whatsapp.to.chatwoot.message.album: |-
|
||||
📷 **Mensaje de Álbum Recibido**
|
||||
|
||||
{{#totalCount}}
|
||||
Recibido un álbum con **{{totalCount}}** archivo(s) de medios.
|
||||
{{#expectedImageCount}}
|
||||
📸 Imágenes: **{{expectedImageCount}}**
|
||||
{{/expectedImageCount}}
|
||||
{{#expectedVideoCount}}
|
||||
🎥 Videos: **{{expectedVideoCount}}**
|
||||
{{/expectedVideoCount}}
|
||||
|
||||
Los archivos de medios se entregarán en los siguientes mensajes.
|
||||
{{/totalCount}}
|
||||
|
||||
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}}}
|
||||
whatsapp.history.message.wrapper: |-
|
||||
{{{content}}}
|
||||
`{{#history}}📥{{/history}}🗓️ {{{timestamp}}}`
|
||||
|
||||
UNKNOWN: Desconocido
|
||||
ERROR: Error
|
||||
PENDING: Enviando
|
||||
SERVER: Servidor
|
||||
DEVICE: Dispositivo
|
||||
READ: Leído
|
||||
PLAYED: Reproducido
|
||||
whatsapp.to.chatwoot.message.has.media.no.media: >-
|
||||
{{#content}}
|
||||
|
||||
{{{content}}}
|
||||
|
||||
|
||||
{{/content}}
|
||||
|
||||
🖼️⚠️ **El mensaje contiene contenido multimedia, pero no pudimos descargarlo**
|
||||
|
||||
📱 Abre **WhatsApp** para verlo.
|
||||
|
||||
|
||||
{{^content}}
|
||||
|
||||
Detalles: [{{{details.text}}}]({{{details.url}}})
|
||||
|
||||
{{/content}}
|
||||
cli.cmd.root.sub.queue: '🧩 **Cola**:'
|
||||
cli.cmd.messages.summary: Extrae mensajes de WhatsApp.
|
||||
cli.cmd.messages.description:
|
||||
💬 Ejecuta `messages pull` para solicitar la sincronización de mensajes o
|
||||
`messages status` para revisar el progreso.
|
||||
cli.cmd.messages.action.description:
|
||||
'`pull` para iniciar la extracción de mensajes o `status` para revisar el
|
||||
trabajo activo.'
|
||||
cli.cmd.messages.pull.option.chat:
|
||||
Limita la extracción a un ID de chat de WhatsApp específico.
|
||||
cli.cmd.messages.pull.option.force:
|
||||
Obliga a la bandeja de entrada a crear mensajes incluso cuando el mensaje de
|
||||
WhatsApp ya existe en el mapeo de la conversación.
|
||||
cli.cmd.messages.pull.option.batch:
|
||||
Número de mensajes que se extraen en cada lote.
|
||||
cli.cmd.messages.pull.option.progress:
|
||||
Informa el progreso cada N mensajes. Establécelo en 0 para desactivar los
|
||||
informes de progreso.
|
||||
cli.cmd.messages.pull.option.no-dm:
|
||||
Omite los mensajes directos al extraer el historial.
|
||||
cli.cmd.messages.pull.option.groups:
|
||||
Incluye chats de grupo de WhatsApp al extraer el historial.
|
||||
cli.cmd.messages.pull.option.channels:
|
||||
Incluye canales de WhatsApp al extraer el historial.
|
||||
cli.cmd.messages.pull.option.status:
|
||||
Incluye actualizaciones de estado de WhatsApp al extraer el historial.
|
||||
cli.cmd.messages.pull.option.broadcast:
|
||||
Incluye listas de difusión al extraer el historial.
|
||||
cli.cmd.messages.pull.option.media:
|
||||
Descarga archivos multimedia (imágenes, videos, archivos) al extraer el
|
||||
historial.
|
||||
cli.cmd.messages.pull.option.pause:
|
||||
Encola el trabajo de extracción de mensajes en estado pausado, deteniendo el
|
||||
procesamiento de nuevos mensajes de WhatsApp hasta que finalice.
|
||||
cli.cmd.messages.pull.option.resolve-conversations:
|
||||
Marca las conversaciones como resueltas después de procesar todo el historial.
|
||||
cli.cmd.messages.pull.error.pause-no-queue:
|
||||
⛔ No se puede usar `--pause` porque el comando **queue** está deshabilitado
|
||||
en esta configuración de la aplicación.
|
||||
cli.cmd.queue.description: Gestiona las colas.
|
||||
cli.cmd.queue.summary: Realiza acciones sobre las colas.
|
||||
cli.cmd.queue.action.description: Elige una acción.
|
||||
cli.cmd.queue.status.description: Muestra el estado de una cola.
|
||||
cli.cmd.queue.argument.name:
|
||||
'Nombre de la cola: `inbox` o `whatsapp` (opcional).'
|
||||
cli.cmd.queue.start.description: Inicia una cola por nombre.
|
||||
cli.cmd.queue.stop.description: Detiene una cola por nombre.
|
||||
cli.cmd.queue.paused:
|
||||
⚪🧩 Las colas se han pausado. Envía `queue status` para comprobar el estado.
|
||||
cli.cmd.queue.resumed:
|
||||
🟢🧩 Las colas se han reanudado. Envía `queue status` para comprobar el
|
||||
estado.
|
||||
cli.cmd.queue.status.result: >-
|
||||
📊 Estado de la cola:
|
||||
|
||||
{{#queues}}
|
||||
|
||||
- **{{name}}**: {{#locked}}🔒{{/locked}}{{#paused}}⚪ En
|
||||
pausa{{/paused}}{{^paused}}🟢 En ejecución{{/paused}}
|
||||
|
||||
{{/queues}}
|
||||
cli.cmd.messages.pull.option.timeout-media:
|
||||
Define cuánto tiempo se intenta descargar los archivos multimedia de cada
|
||||
mensaje.
|
||||
cli.cmd.messages.pull.argument.end:
|
||||
Define dónde termina la ventana de extracción; por ejemplo, `2d` se detiene
|
||||
hace dos días.
|
||||
cli.cmd.messages.pull.argument.start:
|
||||
Define desde dónde comienza la ventana de extracción; por ejemplo, `1d`
|
||||
empieza hace un día (el valor predeterminado es `0d`, es decir, ahora).
|
||||
cli.cmd.messages.pull.already-running: >-
|
||||
⏳ El trabajo **Pull Messages** ya se está ejecutando para **{{{chat}}}**.
|
||||
Espera a que termine o deténlo antes de iniciar uno nuevo.
|
||||
|
||||
|
||||
Envía `messages status` para obtener detalles.
|
||||
cli.cmd.messages.status.not-found:
|
||||
ℹ️ No se encontró ningún trabajo activo de **Pull Messages**. Inicia uno con
|
||||
`messages pull`.
|
||||
cli.cmd.messages.pull.no-chats-found:
|
||||
No se encontraron chats de WhatsApp de los que extraer mensajes en el período.
|
||||
cli.cmd.contacts.pull.option.progress:
|
||||
Informa el progreso cada N contactos. Establécelo en 0 para desactivar los
|
||||
informes de progreso.
|
||||
cli.cmd.contacts.pull.option.lids:
|
||||
Incluye contactos LID de WhatsApp (contactos anónimos) en la sincronización.
|
||||
task.contacts.completed:
|
||||
👤✅ Contactos completados → 🆕 {{progress.created}}, 🔄 {{progress.updated}},
|
||||
⚪ {{progress.skipped}}, 🔴 {{progress.errors}}, 🖼️
|
||||
{{progress.avatar.updated}}
|
||||
task.messages.started: ⏳ Extrayendo mensajes 💬 {{{chat}}} ⏱️ {{{period}}}...
|
||||
task.messages.details:
|
||||
Envía "{{{prefix}}}messages status" para ver más detalles.
|
||||
task.messages.progress:
|
||||
'⏳ Mensajes {{{chat}}}: 🆕 {{progress.ok}}, 🟢 {{progress.exists}}, ⚪
|
||||
{{progress.ignored}}, 🔴 {{progress.errors}}{{#messages.last}} | 📅
|
||||
{{{messages.last}}}{{/messages.last}}'
|
||||
task.messages.completed:
|
||||
✅ Mensajes {{{chat}}} en {{{period}}} → 🆕 {{progress.ok}}, 🟢
|
||||
{{progress.exists}}, ⚪ {{progress.ignored}}, 🔴 {{progress.errors}}
|
||||
task.messages.status: |-
|
||||
Mensajes **{{{chat}}}** en el periodo ⏱️ **{{{period}}}**. ⚙️ Estado del trabajo: **{{state}}**.
|
||||
|
||||
🗨️ Mensajes:
|
||||
- 🆕 Creados: **{{{progress.ok}}}**
|
||||
- 🟢 Ya existen: {{{progress.exists}}}
|
||||
- ⚪ Ignorados: {{{progress.ignored}}}
|
||||
- 🔴 Errores: {{{progress.errors}}}
|
||||
{{#messages.first}}
|
||||
- 🗓️ Más antiguo: **{{{messages.first}}}**
|
||||
{{/messages.first}}
|
||||
{{#messages.last}}
|
||||
- 🗓️ Más reciente: **{{{messages.last}}}**
|
||||
{{/messages.last}}
|
||||
|
||||
💬 Chats de WhatsApp:
|
||||
- 🆕 Nuevos mensajes en **{{{chats}}}** chats
|
||||
{{#options.resolveConversations}}
|
||||
|
||||
🗣️ Conversaciones:
|
||||
- 🟢 Resueltas: **{{{progress.conversationResolve.success}}}**
|
||||
{{#progress.conversationResolve.error}}
|
||||
- 🔴 Errores: **{{{progress.conversationResolve.error}}}**
|
||||
{{/progress.conversationResolve.error}}
|
||||
{{/options.resolveConversations}}
|
||||
|
||||
Detalles:
|
||||
[{{{details.text}}}]({{{details.url}}})
|
||||
@@ -1,8 +1,33 @@
|
||||
# Persian translations for ChatWoot integration
|
||||
# Persian translations for WhatsApp inbox integration
|
||||
locale.name: '🇮🇷 فارسی (Persian)'
|
||||
app.inbox.contact.name: 'ادغام واتساپ (WAHA)'
|
||||
|
||||
# کد محلی پایه - باید یک محلی معتبر 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:
|
||||
weekday: short
|
||||
locales: fa-IR
|
||||
day: 2-digit
|
||||
month: short
|
||||
year: numeric
|
||||
hour: 2-digit
|
||||
minute: 2-digit
|
||||
hour12: false
|
||||
# https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat#timezonename
|
||||
timeZoneName: short
|
||||
# https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
|
||||
# timeZone: 'Asia/Tehran'
|
||||
|
||||
Today: 'امروز'
|
||||
Yesterday: 'دیروز'
|
||||
|
||||
job.report.error: |-
|
||||
⚠️ **{{{header}}}**
|
||||
{{#attempts.nextDelay}}
|
||||
@@ -31,14 +56,14 @@ whatsapp.message.removing.error: حذف پیام از واتساپ امکان
|
||||
job.scheduled.error.header: اجرای کار زمانبندی شده ناموفق بود
|
||||
|
||||
message.from.whatsapp: |-
|
||||
{{{text}}}
|
||||
*📱 ارسال شده از واتساپ*
|
||||
|
||||
📱 *ارسال شده از واتساپ*
|
||||
{{{text}}}
|
||||
|
||||
message.from.api: |-
|
||||
{{{text}}}
|
||||
*📱 ارسال شده از طریق API*
|
||||
|
||||
📱 *ارسال شده از طریق API*
|
||||
{{{text}}}
|
||||
|
||||
message.removed.in.whatsapp: |-
|
||||
❌ *این پیام در واتساپ حذف شده است*
|
||||
@@ -66,11 +91,11 @@ whatsapp.contact.broadcast.suffix: 'فهرست انتشار'
|
||||
whatsapp.contact.status.name: 'وضعیت'
|
||||
|
||||
app.connected.message: |-
|
||||
🔗 **متصل: جلسه واتساپ به صندوق ورودی ChatWoot**
|
||||
🔗 **متصل: جلسه واتساپ به صندوق ورودی**
|
||||
|
||||
✅ جلسه واتساپ **{{{name}}}** با موفقیت به این صندوق ورودی ChatWoot متصل شده است.
|
||||
✅ جلسه واتساپ **{{{name}}}** با موفقیت به این صندوق ورودی متصل شده است.
|
||||
|
||||
این صندوق ورودی اکنون به **حساب واتساپ** شما متصل است. شما میتوانید پیامها را مستقیماً از اینجا ارسال و دریافت کنید — درست مانند استفاده از واتساپ، اما در داخل ChatWoot.
|
||||
این صندوق ورودی اکنون به **حساب واتساپ** شما متصل است. شما میتوانید پیامها را مستقیماً از اینجا ارسال و دریافت کنید — درست مانند استفاده از واتساپ، اما در فضای کاری شما.
|
||||
|
||||
همچنین میتوانید جلسه واتساپ متصل به این صندوق ورودی را مدیریت کنید. برای مشاهده اقدامات موجود، **help** را به این گفتگو ارسال کنید.
|
||||
|
||||
@@ -84,9 +109,9 @@ app.connected.message: |-
|
||||
بیایید شروع کنیم! 🚀
|
||||
|
||||
app.disconnected.message: |-
|
||||
🔌 **قطع اتصال: جلسه واتساپ از صندوق ورودی ChatWoot**
|
||||
🔌 **قطع اتصال: جلسه واتساپ از صندوق ورودی**
|
||||
|
||||
❌ جلسه واتساپ **{{{name}}}** از این صندوق ورودی ChatWoot قطع شده است.
|
||||
❌ جلسه واتساپ **{{{name}}}** از این صندوق ورودی قطع شده است.
|
||||
|
||||
شما دیگر قادر به ارسال یا دریافت پیام از واتساپ نخواهید بود.
|
||||
|
||||
@@ -97,33 +122,155 @@ app.updated.message: |-
|
||||
|
||||
✅ تنظیمات جلسه واتساپ **{{{name}}}** بهروزرسانی شده است.
|
||||
|
||||
app.commands.list: |-
|
||||
شما میتوانید **ادغام واتساپ** را با ارسال یکی از دستورات زیر مدیریت کنید:
|
||||
cli.usage.arguments: |-
|
||||
[اقدام]
|
||||
|
||||
cli.usage.command: |-
|
||||
[دستور]
|
||||
|
||||
cli.usage.options: |-
|
||||
[گزینهها]
|
||||
|
||||
cli.cmd.disabled: ⛔ **دستور `{{command}}` غیرفعال شده است**.
|
||||
|
||||
لطفاً با مدیر خود تماس بگیرید.
|
||||
|
||||
برای دریافت فهرست دستورات موجود، **help** را ارسال کنید.
|
||||
|
||||
cli.cmd.root.description: |-
|
||||
یکپارچهسازی **واتساپ** خود را مدیریت کنید!
|
||||
|
||||
cli.cmd.root.sub.session: |-
|
||||
🖥️ **جلسه**:
|
||||
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}}
|
||||
cli.cmd.root.sub.server: |-
|
||||
🖥️ **سرور**:
|
||||
|
||||
یک **دستور** (بدون حساسیت به بزرگی و کوچکی حروف) یا **شماره دستور** را به این گفتگو ارسال کنید.
|
||||
cli.cmd.root.sub.sync: |-
|
||||
🔄️ **همگامسازی**:
|
||||
|
||||
app.commands.server.disabled: ⛔ **دستورات سرور غیرفعال است**.
|
||||
cli.cmd.session.status.description: |-
|
||||
وضعیت فعلی جلسه را دریافت کنید.
|
||||
|
||||
لطفاً با مدیر سیستم خود تماس بگیرید.
|
||||
cli.cmd.session.restart.description: |-
|
||||
جلسه را مجدداً راهاندازی کنید.
|
||||
|
||||
برای دریافت فهرست دستورات، **help** را ارسال کنید.
|
||||
cli.cmd.session.start.description: |-
|
||||
جلسه را شروع کنید.
|
||||
|
||||
cli.cmd.session.stop.description: |-
|
||||
جلسه را متوقف کنید.
|
||||
|
||||
cli.cmd.session.logout.description: |-
|
||||
برای اسکن کد QR جدید و بازیابی اتصال از جلسه خارج شوید.
|
||||
|
||||
cli.cmd.session.qr.description: |-
|
||||
کد QR جلسه را نمایش دهید.
|
||||
|
||||
cli.cmd.session.screenshot.description: |-
|
||||
از جلسه اسکرینشات بگیرید (فقط با موتور **WEBJS** در دسترس است).
|
||||
|
||||
cli.cmd.contacts.summary: |-
|
||||
دریافت مخاطبان واتساپ.
|
||||
|
||||
cli.cmd.contacts.description: |-
|
||||
👤 `contacts pull` را اجرا کنید تا همگامسازی آغاز شود یا `contacts status` را برای بررسی پیشرفت اجرا کنید.
|
||||
|
||||
cli.cmd.contacts.action.description: |-
|
||||
از `start` برای آغاز دریافت مخاطبان یا از `status` برای بررسی کار فعال استفاده کنید.
|
||||
|
||||
cli.cmd.contacts.pull.description: |-
|
||||
دریافت مخاطبان واتساپ را به این صندوق ورودی آغاز کنید.
|
||||
|
||||
cli.cmd.contacts.pull.option.batch: |-
|
||||
تعداد مخاطبانی که در هر دسته دریافت میشوند.
|
||||
|
||||
cli.cmd.contacts.pull.option.delay-contact: |-
|
||||
تأخیر بین همگامسازی هر مخاطب برای جلوگیری از محدودیت نرخ هنگام دریافت آواتار.
|
||||
|
||||
cli.cmd.contacts.pull.option.delay-batch: |-
|
||||
تأخیر بین دستههای مخاطبان.
|
||||
|
||||
cli.cmd.contacts.pull.option.avatar: |-
|
||||
نحوه دریافت آواتار مخاطبان را انتخاب کنید: `skip`، `if-missing` یا `update`.
|
||||
|
||||
cli.cmd.contacts.pull.option.groups: |-
|
||||
مخاطبان گروههای واتساپ را در همگامسازی قرار دهید.
|
||||
|
||||
cli.cmd.contacts.pull.option.no-attributes: |-
|
||||
اگر مخاطب از قبل وجود دارد ویژگیهای او را دریافت نکنید (فرآیند را سریعتر میکند).
|
||||
|
||||
cli.cmd.contacts.pull.already-running: |-
|
||||
⏳ کار **Pull Contacts** هماکنون در حال اجراست. صبر کنید تا تمام شود یا قبل از شروع کار جدید آن را متوقف کنید.
|
||||
|
||||
برای جزئیات `contacts status` را ارسال کنید.
|
||||
|
||||
cli.cmd.options.job.attempts: |-
|
||||
تعداد دفعات تلاش مجدد پیش از علامتگذاری کار بهعنوان ناموفق.
|
||||
|
||||
cli.cmd.options.job.timeout: |-
|
||||
حداکثر مدت اجرای کار پیش از تلاش دوباره (از مقادیری مانند `30s`، `5m`، `1h` پشتیبانی میکند).
|
||||
|
||||
cli.cmd.contacts.status.description: |-
|
||||
وضعیت کار دریافت فعلی را نمایش دهید.
|
||||
|
||||
cli.cmd.contacts.status.not-found: |-
|
||||
ℹ️ هیچ کار فعال **Pull Contacts** یافت نشد. با `contacts pull` یکی آغاز کنید.
|
||||
|
||||
cli.cmd.server.description: |-
|
||||
سرور را مدیریت کنید.
|
||||
|
||||
cli.cmd.server.status.description: |-
|
||||
نسخه و وضعیت سرور را نمایش دهید.
|
||||
|
||||
cli.cmd.server.reboot.description: |-
|
||||
سرور را راهاندازی مجدد کنید.
|
||||
|
||||
cli.cmd.server.reboot.option.force: |-
|
||||
راهاندازی مجدد فوری را اجباری کنید.
|
||||
|
||||
cli.help.usage.title: |-
|
||||
نحوه استفاده: دستور را با قالب زیر به گفتگو ارسال کنید:
|
||||
|
||||
cli.help.commands.defaultGroup: |-
|
||||
**دستورات:**
|
||||
|
||||
cli.help.arguments.title: |-
|
||||
**آرگومانها:**
|
||||
|
||||
cli.help.options.defaultGroup: |-
|
||||
**گزینهها:**
|
||||
|
||||
cli.help.globalOptions.title: |-
|
||||
**گزینههای عمومی:**
|
||||
|
||||
task.contacts.status: |-
|
||||
👤 دریافت مخاطبان: **{{state}}**
|
||||
- ✅ موفق: {{{progress.ok}}} مخاطب
|
||||
- ⚪ نادیدهگرفتهشده: {{{progress.skipped}}} مخاطب
|
||||
- ⚠️ خطا: {{{progress.errors}}} مخاطب
|
||||
|
||||
{{#job}}
|
||||
⌛ جدول زمانی:
|
||||
- در صف: {{{timestamp}}}
|
||||
{{#processedOn}}
|
||||
- زمان شروع: {{{processedOn}}}
|
||||
{{/processedOn}}
|
||||
{{#finishedOn}}
|
||||
- زمان پایان: {{{finishedOn}}}
|
||||
{{/finishedOn}}
|
||||
{{/job}}
|
||||
|
||||
{{#error}}
|
||||
جزئیات:
|
||||
[{{{details.text}}}]({{{details.url}}})
|
||||
{{/error}}
|
||||
|
||||
task.contacts.started: |-
|
||||
در حال دریافت مخاطبان...
|
||||
|
||||
task.contacts.progress: |-
|
||||
دریافت مخاطبان: ✅ {{progress.ok}}، ⚪ {{progress.skipped}}، ⚠️ {{progress.errors}}
|
||||
|
||||
app.help.reminder: |-
|
||||
برای دریافت لیست دستورات موجود، **help** را ارسال کنید.
|
||||
@@ -138,17 +285,17 @@ app.session.current.status: |-
|
||||
📱 **{{{name}}} ({{{id}}})**
|
||||
|
||||
app.session.status.working: |-
|
||||
✅ این صندوق ورودی ChatWoot به **حساب واتساپ** متصل است و آماده ارسال و دریافت پیام است!
|
||||
✅ این صندوق ورودی به **حساب واتساپ** شما متصل است و آماده ارسال و دریافت پیام است!
|
||||
|
||||
📱 **{{{name}}} ({{{id}}})**
|
||||
|
||||
app.session.status.error: |-
|
||||
⚠️ **این صندوق ورودی ChatWoot به حساب واتساپ متصل است اما آماده ارسال و دریافت پیام نیست!**
|
||||
⚠️ **این صندوق ورودی به حساب واتساپ متصل است اما آماده ارسال و دریافت پیام نیست!**
|
||||
|
||||
لطفاً وضعیت جلسه را بررسی کنید و سعی کنید آن را مجدداً راهاندازی کنید یا خارج شوید و کد QR را دوباره اسکن کنید.
|
||||
|
||||
app.session.scan.qr.code: |-
|
||||
⚠️ **کد QR را در تلفن خود اسکن کنید تا حساب واتساپ را با این صندوق ورودی ChatWoot پیوند دهید!**
|
||||
⚠️ **کد QR را در تلفن خود اسکن کنید تا حساب واتساپ را با این صندوق ورودی پیوند دهید!**
|
||||
|
||||
app.server.version.and.status: |-
|
||||
**نسخه:**
|
||||
@@ -216,7 +363,19 @@ whatsapp.to.chatwoot.message.location: |-
|
||||
{{#locationMessage}}
|
||||
📍 **موقعیت مکانی بهاشتراکگذاشتهشده**
|
||||
|
||||
https://www.google.com/maps?q={{degreesLatitude}},{{degreesLongitude}}
|
||||
{{#name}}
|
||||
**{{name}}**
|
||||
{{/name}}
|
||||
|
||||
{{#address}}
|
||||
📍 **آدرس:** {{address}}
|
||||
{{/address}}
|
||||
|
||||
{{#url}}
|
||||
🌐 **وبسایت:** {{url}}
|
||||
{{/url}}
|
||||
|
||||
🔗 **مشاهده روی نقشه:** https://www.google.com/maps?q={{degreesLatitude}},{{degreesLongitude}}
|
||||
{{#caption}}{{caption}}{{/caption}}
|
||||
{{/locationMessage}}
|
||||
|
||||
@@ -224,13 +383,286 @@ whatsapp.to.chatwoot.message.location: |-
|
||||
📍📡 **اشتراکگذاری موقعیت مکانی زنده**
|
||||
برای دیدن بهروزرسانیهای لحظهای **WhatsApp** را باز کنید.
|
||||
|
||||
https://www.google.com/maps?q={{degreesLatitude}},{{degreesLongitude}}
|
||||
{{#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.album: |-
|
||||
📷 **پیام آلبوم دریافت شد**
|
||||
|
||||
{{#totalCount}}
|
||||
یک آلبوم با **{{totalCount}}** فایل رسانهای دریافت شد.
|
||||
{{#expectedImageCount}}
|
||||
📸 تصاویر: **{{expectedImageCount}}**
|
||||
{{/expectedImageCount}}
|
||||
{{#expectedVideoCount}}
|
||||
🎥 ویدیوها: **{{expectedVideoCount}}**
|
||||
{{/expectedVideoCount}}
|
||||
|
||||
فایلهای رسانهای در پیامهای بعدی تحویل داده خواهند شد.
|
||||
{{/totalCount}}
|
||||
|
||||
whatsapp.to.chatwoot.message.facebook.ad: |-
|
||||
📢 **آگهی فیسبوک**
|
||||
|
||||
**عنوان:** {{{adData.title}}}
|
||||
|
||||
**توضیحات:**
|
||||
{{{adData.body}}}
|
||||
|
||||
**لینک آگهی:** [مشاهده در فیسبوک]({{{adData.sourceUrl}}})
|
||||
**شناسه آگهی:** {{{adData.sourceId}}}
|
||||
|
||||
---
|
||||
💬 **پیام مشتری:**
|
||||
{{{payload.body}}}
|
||||
whatsapp.history.message.wrapper: |-
|
||||
{{{content}}}
|
||||
`{{#history}}📥{{/history}}🗓️ {{{timestamp}}}`
|
||||
|
||||
UNKNOWN: ناشناخته
|
||||
ERROR: خطا
|
||||
PENDING: در حال ارسال
|
||||
SERVER: سرور
|
||||
DEVICE: دستگاه
|
||||
READ: خوانده شد
|
||||
PLAYED: پخش شد
|
||||
whatsapp.to.chatwoot.message.has.media.no.media: |-
|
||||
{{#content}}
|
||||
{{{content}}}
|
||||
|
||||
{{/content}}
|
||||
🖼️⚠️ **پیام حاوی رسانه است اما نتوانستیم آن را دانلود کنیم**
|
||||
📱 لطفاً برای مشاهده، **واتساپ** را باز کنید.
|
||||
|
||||
{{^content}}
|
||||
جزئیات: [{{{details.text}}}]({{{details.url}}})
|
||||
{{/content}}
|
||||
cli.cmd.root.sub.queue: '🧩 **صف**:'
|
||||
cli.cmd.messages.summary: دریافت پیامهای واتساپ.
|
||||
cli.cmd.messages.description:
|
||||
💬 برای آغاز همگامسازی پیامها `messages pull` را اجرا کنید یا برای مشاهده
|
||||
پیشرفت `messages status` را اجرا کنید.
|
||||
cli.cmd.messages.action.description:
|
||||
'`pull` برای شروع دریافت پیام یا `status` برای بررسی کار فعال.'
|
||||
cli.cmd.messages.pull.option.chat:
|
||||
دریافت را به یک شناسه گفتوگوی مشخص واتساپ محدود میکند.
|
||||
cli.cmd.messages.pull.option.force:
|
||||
صندوق ورودی را مجبور میکند پیام بسازد حتی اگر پیام واتساپ قبلاً در نگاشت
|
||||
مکالمه وجود داشته باشد.
|
||||
cli.cmd.messages.pull.option.batch:
|
||||
تعداد پیامهایی که در هر دسته دریافت میشوند.
|
||||
cli.cmd.messages.pull.option.progress:
|
||||
پیشرفت را هر N پیام اعلام میکند. مقدار 0 گزارشها را غیرفعال میکند.
|
||||
cli.cmd.messages.pull.option.no-dm:
|
||||
هنگام دریافت تاریخچه، پیامهای خصوصی را نادیده میگیرد.
|
||||
cli.cmd.messages.pull.option.groups:
|
||||
گروههای واتساپ را هنگام دریافت تاریخچه وارد میکند.
|
||||
cli.cmd.messages.pull.option.channels:
|
||||
کانالهای واتساپ را هنگام دریافت تاریخچه وارد میکند.
|
||||
cli.cmd.messages.pull.option.status:
|
||||
وضعیتهای واتساپ را هنگام دریافت تاریخچه وارد میکند.
|
||||
cli.cmd.messages.pull.option.broadcast:
|
||||
لیستهای پخش را هنگام دریافت تاریخچه وارد میکند.
|
||||
cli.cmd.messages.pull.option.media:
|
||||
هنگام دریافت تاریخچه، پیوستهای رسانهای (تصویر، ویدیو، فایل) را دانلود
|
||||
میکند.
|
||||
cli.cmd.messages.pull.option.pause:
|
||||
کار دریافت پیامها را در حالت توقف در صف قرار میدهد و پردازش پیامهای جدید
|
||||
واتساپ را تا پایان متوقف میکند.
|
||||
cli.cmd.messages.pull.option.resolve-conversations:
|
||||
پس از پردازش تمام پیامهای تاریخچه، مکالمهها را بهعنوان حلشده علامتگذاری
|
||||
میکند.
|
||||
cli.cmd.messages.pull.error.pause-no-queue:
|
||||
⛔ امکان استفاده از `--pause` نیست زیرا فرمان **queue** در این پیکربندی
|
||||
غیرفعال است.
|
||||
cli.cmd.queue.description: مدیریت صفها.
|
||||
cli.cmd.queue.summary: اعمال روی صفها را انجام دهید.
|
||||
cli.cmd.queue.action.description: یک عمل را انتخاب کنید.
|
||||
cli.cmd.queue.status.description: وضعیت یک صف را نشان میدهد.
|
||||
cli.cmd.queue.argument.name: 'نام صف: `inbox` یا `whatsapp` (اختیاری).'
|
||||
cli.cmd.queue.start.description: صف را بر اساس نام شروع کنید.
|
||||
cli.cmd.queue.stop.description: صف را بر اساس نام متوقف کنید.
|
||||
cli.cmd.queue.paused:
|
||||
⚪🧩 صفها متوقف شدهاند. برای بررسی وضعیت `queue status` را ارسال کنید.
|
||||
cli.cmd.queue.resumed:
|
||||
🟢🧩 صفها از سر گرفته شدند. برای بررسی وضعیت `queue status` را ارسال کنید.
|
||||
cli.cmd.queue.status.result: >-
|
||||
📊 وضعیت صف:
|
||||
|
||||
{{#queues}}
|
||||
|
||||
- **{{name}}**: {{#locked}}🔒{{/locked}}{{#paused}}⚪
|
||||
متوقف{{/paused}}{{^paused}}🟢 فعال{{/paused}}
|
||||
|
||||
{{/queues}}
|
||||
cli.cmd.messages.pull.option.timeout-media:
|
||||
مدت تلاش برای دانلود رسانه هر پیام را مشخص میکند.
|
||||
cli.cmd.messages.pull.argument.end:
|
||||
پایان بازه دریافت را تعیین میکند؛ برای مثال `2d` یعنی دو روز پیش متوقف
|
||||
میشود.
|
||||
cli.cmd.messages.pull.argument.start:
|
||||
آغاز بازه دریافت را تعیین میکند؛ برای مثال `1d` یعنی یک روز پیش (پیشفرض `0d`
|
||||
یعنی اکنون).
|
||||
cli.cmd.messages.pull.already-running: >-
|
||||
⏳ کار **Pull Messages** هماکنون برای **{{{chat}}}** در حال اجراست. منتظر
|
||||
بمانید تا تمام شود یا قبل از شروع کار جدید، آن را متوقف کنید.
|
||||
|
||||
|
||||
برای جزئیات `messages status` را ارسال کنید.
|
||||
cli.cmd.messages.status.not-found:
|
||||
ℹ️ هیچ کار فعال **Pull Messages** پیدا نشد. با `messages pull` یک کار را آغاز
|
||||
کنید.
|
||||
cli.cmd.messages.pull.no-chats-found:
|
||||
هیچ گفتوگوی واتساپی برای این بازه زمانی پیدا نشد.
|
||||
cli.cmd.contacts.pull.option.progress:
|
||||
پیشرفت را هر N مخاطب اعلام میکند. مقدار 0 گزارشها را غیرفعال میکند.
|
||||
cli.cmd.contacts.pull.option.lids:
|
||||
مخاطبین ناشناس واتساپ (LID) را در همگامسازی وارد میکند.
|
||||
task.contacts.completed:
|
||||
👤✅ مخاطبین تکمیل شدند → 🆕 {{progress.created}}، 🔄 {{progress.updated}}، ⚪
|
||||
{{progress.skipped}}، 🔴 {{progress.errors}}، 🖼️ {{progress.avatar.updated}}
|
||||
task.messages.started: ⏳ در حال دریافت پیامها 💬 {{{chat}}} ⏱️ {{{period}}}...
|
||||
task.messages.details:
|
||||
برای جزئیات بیشتر "{{{prefix}}}messages status" را ارسال کنید.
|
||||
task.messages.progress:
|
||||
'⏳ پیامهای {{{chat}}}: 🆕 {{progress.ok}}، 🟢 {{progress.exists}}، ⚪
|
||||
{{progress.ignored}}، 🔴 {{progress.errors}}{{#messages.last}} | 📅
|
||||
{{{messages.last}}}{{/messages.last}}'
|
||||
task.messages.completed:
|
||||
✅ پیامهای {{{chat}}} در {{{period}}} → 🆕 {{progress.ok}}، 🟢
|
||||
{{progress.exists}}، ⚪ {{progress.ignored}}، 🔴 {{progress.errors}}
|
||||
task.messages.status: |-
|
||||
پیامهای **{{{chat}}}** در بازه ⏱️ **{{{period}}}**. ⚙️ وضعیت کار: **{{state}}**.
|
||||
|
||||
🗨️ پیامها:
|
||||
- 🆕 ایجاد شده: **{{{progress.ok}}}**
|
||||
- 🟢 موجود: {{{progress.exists}}}
|
||||
- ⚪ نادیده گرفته شده: {{{progress.ignored}}}
|
||||
- 🔴 خطا: {{{progress.errors}}}
|
||||
{{#messages.first}}
|
||||
- 🗓️ قدیمیترین: **{{{messages.first}}}**
|
||||
{{/messages.first}}
|
||||
{{#messages.last}}
|
||||
- 🗓️ جدیدترین: **{{{messages.last}}}**
|
||||
{{/messages.last}}
|
||||
|
||||
💬 گفتوگوهای واتساپ:
|
||||
- 🆕 پیامهای جدید در **{{{chats}}}** گفتوگو
|
||||
{{#options.resolveConversations}}
|
||||
|
||||
🗣️ مکالمهها:
|
||||
- 🟢 حل شد: **{{{progress.conversationResolve.success}}}**
|
||||
{{#progress.conversationResolve.error}}
|
||||
- 🔴 خطا: **{{{progress.conversationResolve.error}}}**
|
||||
{{/progress.conversationResolve.error}}
|
||||
{{/options.resolveConversations}}
|
||||
|
||||
جزئیات:
|
||||
[{{{details.text}}}]({{{details.url}}})
|
||||
@@ -1,8 +1,33 @@
|
||||
# French translations for ChatWoot integration
|
||||
# French translations for WhatsApp inbox integration
|
||||
locale.name: '🇫🇷 Français (French)'
|
||||
app.inbox.contact.name: 'Intégration WhatsApp (WAHA)'
|
||||
|
||||
# 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:
|
||||
weekday: short
|
||||
locales: fr-FR
|
||||
day: 2-digit
|
||||
month: short
|
||||
year: numeric
|
||||
hour: 2-digit
|
||||
minute: 2-digit
|
||||
hour12: false
|
||||
# https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat#timezonename
|
||||
timeZoneName: short
|
||||
# https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
|
||||
# timeZone: 'Europe/Paris'
|
||||
|
||||
Today: "Aujourd'hui"
|
||||
Yesterday: 'Hier'
|
||||
|
||||
job.report.error: |-
|
||||
⚠️ **{{{header}}}**
|
||||
{{#attempts.nextDelay}}
|
||||
@@ -31,14 +56,14 @@ 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*
|
||||
|
||||
📱 *Envoyé depuis WhatsApp*
|
||||
{{{text}}}
|
||||
|
||||
message.from.api: |-
|
||||
{{{text}}}
|
||||
*📱 Envoyé via API*
|
||||
|
||||
📱 *Envoyé via API*
|
||||
{{{text}}}
|
||||
|
||||
message.removed.in.whatsapp: |-
|
||||
❌ *Ce message a été supprimé dans WhatsApp*
|
||||
@@ -66,11 +91,11 @@ whatsapp.contact.broadcast.suffix: 'Liste de diffusion'
|
||||
whatsapp.contact.status.name: 'Statut'
|
||||
|
||||
app.connected.message: |-
|
||||
🔗 **CONNECTÉ : Session WhatsApp à la Boîte de Réception ChatWoot**
|
||||
🔗 **CONNECTÉ : Session WhatsApp à la boîte de réception**
|
||||
|
||||
✅ La session WhatsApp **{{{name}}}** a été connectée avec succès à cette Boîte de Réception ChatWoot.
|
||||
✅ La session WhatsApp **{{{name}}}** a été connectée avec succès à cette boîte de réception.
|
||||
|
||||
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.
|
||||
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 dans votre espace de travail.
|
||||
|
||||
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.
|
||||
|
||||
@@ -84,9 +109,9 @@ app.connected.message: |-
|
||||
Commençons ! 🚀
|
||||
|
||||
app.disconnected.message: |-
|
||||
🔌 **DÉCONNECTÉ : Session WhatsApp de la Boîte de Réception ChatWoot**
|
||||
🔌 **DÉCONNECTÉ : Session WhatsApp de la boîte de réception**
|
||||
|
||||
❌ La session WhatsApp **{{{name}}}** a été déconnectée de cette Boîte de Réception ChatWoot.
|
||||
❌ La session WhatsApp **{{{name}}}** a été déconnectée de cette boîte de réception.
|
||||
|
||||
Vous ne pourrez plus envoyer ou recevoir de messages depuis WhatsApp.
|
||||
|
||||
@@ -97,34 +122,156 @@ app.updated.message: |-
|
||||
|
||||
✅ 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 :
|
||||
cli.usage.arguments: |-
|
||||
[action]
|
||||
|
||||
🖥️ **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.
|
||||
cli.usage.command: |-
|
||||
[commande]
|
||||
|
||||
{{#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}}
|
||||
cli.usage.options: |-
|
||||
[options]
|
||||
|
||||
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**.
|
||||
cli.cmd.disabled: ⛔ **La commande `{{command}}` est désactivée**.
|
||||
|
||||
Veuillez contacter votre administrateur.
|
||||
|
||||
Envoyez **help** pour obtenir la liste des commandes disponibles.
|
||||
|
||||
cli.cmd.root.description: |-
|
||||
Gérez votre **intégration WhatsApp** !
|
||||
|
||||
cli.cmd.root.sub.session: |-
|
||||
🖥️ **Session** :
|
||||
|
||||
cli.cmd.root.sub.server: |-
|
||||
🖥️ **Serveur** :
|
||||
|
||||
cli.cmd.root.sub.sync: |-
|
||||
🔄️ **Synchronisation** :
|
||||
|
||||
cli.cmd.session.status.description: |-
|
||||
Obtenez l'état actuel de la session.
|
||||
|
||||
cli.cmd.session.restart.description: |-
|
||||
Redémarrez la session.
|
||||
|
||||
cli.cmd.session.start.description: |-
|
||||
Démarrez la session.
|
||||
|
||||
cli.cmd.session.stop.description: |-
|
||||
Arrêtez la session.
|
||||
|
||||
cli.cmd.session.logout.description: |-
|
||||
Déconnectez la session pour scanner un nouveau code QR et rétablir la connexion.
|
||||
|
||||
cli.cmd.session.qr.description: |-
|
||||
Affichez le code QR de la session.
|
||||
|
||||
cli.cmd.session.screenshot.description: |-
|
||||
Prenez une capture d'écran de la session (disponible uniquement avec le moteur **WEBJS**).
|
||||
|
||||
cli.cmd.contacts.summary: |-
|
||||
Importer les contacts WhatsApp.
|
||||
|
||||
cli.cmd.contacts.description: |-
|
||||
👤 Exécutez `contacts pull` pour lancer une synchronisation ou `contacts status` pour suivre la progression.
|
||||
|
||||
cli.cmd.contacts.action.description: |-
|
||||
Utilisez `start` pour démarrer l'import de contacts ou `status` pour consulter la tâche active.
|
||||
|
||||
cli.cmd.contacts.pull.description: |-
|
||||
Commencez à importer les contacts WhatsApp dans la boîte de réception.
|
||||
|
||||
cli.cmd.contacts.pull.option.batch: |-
|
||||
Nombre de contacts importés par lot.
|
||||
|
||||
cli.cmd.contacts.pull.option.delay-contact: |-
|
||||
Délai entre chaque contact pour éviter les limites lors du chargement des avatars.
|
||||
|
||||
cli.cmd.contacts.pull.option.delay-batch: |-
|
||||
Délai entre les lots de contacts.
|
||||
|
||||
cli.cmd.contacts.pull.option.avatar: |-
|
||||
Choisissez comment charger les avatars des contacts : `skip`, `if-missing` ou `update`.
|
||||
|
||||
cli.cmd.contacts.pull.option.groups: |-
|
||||
Inclure les contacts de groupes WhatsApp dans la synchronisation.
|
||||
|
||||
cli.cmd.contacts.pull.option.no-attributes: |-
|
||||
Ne pas importer les attributs si le contact existe déjà (accélère le processus).
|
||||
|
||||
cli.cmd.contacts.pull.already-running: |-
|
||||
⏳ La tâche **Pull Contacts** est déjà en cours. Attendez qu'elle se termine ou arrêtez-la avant d'en démarrer une nouvelle.
|
||||
|
||||
Envoyez `contacts status` pour plus de détails.
|
||||
|
||||
cli.cmd.options.job.attempts: |-
|
||||
Nombre de tentatives avant de marquer la tâche comme échouée.
|
||||
|
||||
cli.cmd.options.job.timeout: |-
|
||||
Durée maximale autorisée avant une nouvelle tentative (accepte `30s`, `5m`, `1h`).
|
||||
|
||||
cli.cmd.contacts.status.description: |-
|
||||
Afficher l'état de la tâche d'import en cours.
|
||||
|
||||
cli.cmd.contacts.status.not-found: |-
|
||||
ℹ️ Aucune tâche **Pull Contacts** active trouvée. Démarrez-en une avec `contacts pull`.
|
||||
|
||||
cli.cmd.server.description: |-
|
||||
Gérez le serveur.
|
||||
|
||||
cli.cmd.server.status.description: |-
|
||||
Affichez la version et l'état du serveur.
|
||||
|
||||
cli.cmd.server.reboot.description: |-
|
||||
Redémarrez le serveur.
|
||||
|
||||
cli.cmd.server.reboot.option.force: |-
|
||||
Forcer un redémarrage immédiat.
|
||||
|
||||
cli.help.usage.title: |-
|
||||
Utilisation : envoyez une commande à la conversation au format suivant :
|
||||
|
||||
cli.help.commands.defaultGroup: |-
|
||||
**Commandes :**
|
||||
|
||||
cli.help.arguments.title: |-
|
||||
**Arguments :**
|
||||
|
||||
cli.help.options.defaultGroup: |-
|
||||
**Options :**
|
||||
|
||||
cli.help.globalOptions.title: |-
|
||||
**Options globales :**
|
||||
|
||||
task.contacts.status: |-
|
||||
👤 Import de contacts : **{{state}}**
|
||||
- ✅ Réussis : {{{progress.ok}}} contacts
|
||||
- ⚪ Ignorés : {{{progress.skipped}}} contacts
|
||||
- ⚠️ Erreurs : {{{progress.errors}}} contacts
|
||||
|
||||
{{#job}}
|
||||
⌛ Chronologie :
|
||||
- En file d'attente : {{{timestamp}}}
|
||||
{{#processedOn}}
|
||||
- Début : {{{processedOn}}}
|
||||
{{/processedOn}}
|
||||
{{#finishedOn}}
|
||||
- Fin : {{{finishedOn}}}
|
||||
{{/finishedOn}}
|
||||
{{/job}}
|
||||
|
||||
{{#error}}
|
||||
Détails :
|
||||
[{{{details.text}}}]({{{details.url}}})
|
||||
{{/error}}
|
||||
|
||||
task.contacts.started: |-
|
||||
Importation des contacts...
|
||||
|
||||
task.contacts.progress: |-
|
||||
Import de contacts : ✅ {{progress.ok}}, ⚪ {{progress.skipped}}, ⚠️ {{progress.errors}}
|
||||
|
||||
app.help.reminder: |-
|
||||
Envoyez **help** pour obtenir la liste des commandes disponibles.
|
||||
|
||||
@@ -138,17 +285,17 @@ app.session.current.status: |-
|
||||
📱 **{{{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 !
|
||||
✅ Cette boîte de réception est liée à votre **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 !**
|
||||
⚠️ **Cette boîte de réception 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 !**
|
||||
⚠️ **Scannez le code QR sur votre téléphone pour lier le Compte WhatsApp à cette boîte de réception !**
|
||||
|
||||
app.server.version.and.status: |-
|
||||
**Version :**
|
||||
@@ -216,7 +363,19 @@ whatsapp.to.chatwoot.message.location: |-
|
||||
{{#locationMessage}}
|
||||
📍 **Position partagée**
|
||||
|
||||
https://www.google.com/maps?q={{degreesLatitude}},{{degreesLongitude}}
|
||||
{{#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}}
|
||||
|
||||
@@ -224,13 +383,299 @@ whatsapp.to.chatwoot.message.location: |-
|
||||
📍📡 **Partage de position en direct**
|
||||
Ouvrez **WhatsApp** pour voir les mises à jour en temps réel.
|
||||
|
||||
https://www.google.com/maps?q={{degreesLatitude}},{{degreesLongitude}}
|
||||
{{#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.**
|
||||
⚠️ **Ce type de message n’est pas pris en charge dans cette boîte de réception.**
|
||||
📱 Veuillez ouvrir **WhatsApp** pour l’afficher.
|
||||
|
||||
Détails: [{{{details.text}}}]({{{details.url}}})
|
||||
|
||||
whatsapp.to.chatwoot.message.album: |-
|
||||
📷 **Message d'Album Reçu**
|
||||
|
||||
{{#totalCount}}
|
||||
Reçu un album avec **{{totalCount}}** fichier(s) multimédia.
|
||||
{{#expectedImageCount}}
|
||||
📸 Images: **{{expectedImageCount}}**
|
||||
{{/expectedImageCount}}
|
||||
{{#expectedVideoCount}}
|
||||
🎥 Vidéos: **{{expectedVideoCount}}**
|
||||
{{/expectedVideoCount}}
|
||||
|
||||
Les fichiers multimédias seront livrés dans les messages suivants.
|
||||
{{/totalCount}}
|
||||
|
||||
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}}}
|
||||
whatsapp.history.message.wrapper: |-
|
||||
{{{content}}}
|
||||
`{{#history}}📥{{/history}}🗓️ {{{timestamp}}}`
|
||||
|
||||
UNKNOWN: Inconnu
|
||||
ERROR: Erreur
|
||||
PENDING: Envoi en cours
|
||||
SERVER: Serveur
|
||||
DEVICE: Appareil
|
||||
READ: Lu
|
||||
PLAYED: Écouté
|
||||
whatsapp.to.chatwoot.message.has.media.no.media: >-
|
||||
{{#content}}
|
||||
|
||||
{{{content}}}
|
||||
|
||||
|
||||
{{/content}}
|
||||
|
||||
🖼️⚠️ **Le message contient un média, mais nous n’avons pas pu le télécharger**
|
||||
|
||||
📱 Ouvrez **WhatsApp** pour le consulter.
|
||||
|
||||
|
||||
{{^content}}
|
||||
|
||||
Détails : [{{{details.text}}}]({{{details.url}}})
|
||||
|
||||
{{/content}}
|
||||
cli.cmd.root.sub.queue: '🧩 **File** :'
|
||||
cli.cmd.messages.summary: Récupérer les messages WhatsApp.
|
||||
cli.cmd.messages.description:
|
||||
💬 Exécutez `messages pull` pour lancer la synchronisation ou `messages
|
||||
status` pour suivre l’avancement.
|
||||
cli.cmd.messages.action.description:
|
||||
'`pull` pour lancer la récupération ou `status` pour inspecter la tâche en
|
||||
cours.'
|
||||
cli.cmd.messages.pull.option.chat:
|
||||
Limite la récupération à un identifiant de conversation WhatsApp précis.
|
||||
cli.cmd.messages.pull.option.force:
|
||||
Force la boîte de réception à créer des messages même si le message WhatsApp
|
||||
existe déjà dans le mappage de la conversation.
|
||||
cli.cmd.messages.pull.option.batch: Nombre de messages récupérés à chaque lot.
|
||||
cli.cmd.messages.pull.option.progress:
|
||||
Annonce la progression toutes les N messages. Mettre 0 pour désactiver les
|
||||
rapports.
|
||||
cli.cmd.messages.pull.option.no-dm:
|
||||
Ignore les messages directs lors de la récupération de l’historique.
|
||||
cli.cmd.messages.pull.option.groups:
|
||||
Inclut les groupes WhatsApp lors de la récupération de l’historique.
|
||||
cli.cmd.messages.pull.option.channels:
|
||||
Inclut les canaux WhatsApp lors de la récupération de l’historique.
|
||||
cli.cmd.messages.pull.option.status:
|
||||
Inclut les statuts WhatsApp lors de la récupération de l’historique.
|
||||
cli.cmd.messages.pull.option.broadcast:
|
||||
Inclut les listes de diffusion lors de la récupération de l’historique.
|
||||
cli.cmd.messages.pull.option.media:
|
||||
Télécharge les pièces jointes (images, vidéos, fichiers) lors de la
|
||||
récupération.
|
||||
cli.cmd.messages.pull.option.pause:
|
||||
Met la tâche de récupération en file d’attente à l’arrêt, suspendant les
|
||||
nouveaux messages WhatsApp jusqu’à la fin.
|
||||
cli.cmd.messages.pull.option.resolve-conversations:
|
||||
Marque les conversations comme résolues après le traitement de tout
|
||||
l’historique.
|
||||
cli.cmd.messages.pull.error.pause-no-queue:
|
||||
⛔ Impossible d’utiliser `--pause` car la commande **queue** est désactivée
|
||||
dans cette configuration.
|
||||
cli.cmd.queue.description: Gérer les files.
|
||||
cli.cmd.queue.summary: Effectuer des actions sur les files.
|
||||
cli.cmd.queue.action.description: Choisissez une action.
|
||||
cli.cmd.queue.status.description: Affiche l’état d’une file.
|
||||
cli.cmd.queue.argument.name:
|
||||
'Nom de la file : `inbox` ou `whatsapp` (facultatif).'
|
||||
cli.cmd.queue.start.description: Démarre une file par son nom.
|
||||
cli.cmd.queue.stop.description: Arrête une file par son nom.
|
||||
cli.cmd.queue.paused:
|
||||
⚪🧩 Les files ont été mises en pause. Envoyez `queue status` pour vérifier
|
||||
l’état.
|
||||
cli.cmd.queue.resumed:
|
||||
🟢🧩 Les files ont repris. Envoyez `queue status` pour vérifier l’état.
|
||||
cli.cmd.queue.status.result: >-
|
||||
📊 État de la file :
|
||||
|
||||
{{#queues}}
|
||||
|
||||
- **{{name}}** : {{#locked}}🔒{{/locked}}{{#paused}}⚪ En
|
||||
pause{{/paused}}{{^paused}}🟢 En cours{{/paused}}
|
||||
|
||||
{{/queues}}
|
||||
cli.cmd.messages.pull.option.timeout-media:
|
||||
Définit la durée d’une tentative de téléchargement des médias de chaque
|
||||
message.
|
||||
cli.cmd.messages.pull.argument.end:
|
||||
Définit la fin de la fenêtre de récupération, par exemple `2d` s’arrête deux
|
||||
jours en arrière.
|
||||
cli.cmd.messages.pull.argument.start:
|
||||
Définit le début de la fenêtre, par exemple `1d` commence un jour en arrière
|
||||
(par défaut `0d`, maintenant).
|
||||
cli.cmd.messages.pull.already-running: >-
|
||||
⏳ La tâche **Pull Messages** est déjà en cours pour **{{{chat}}}**. Patientez
|
||||
jusqu’à la fin ou arrêtez-la avant d’en lancer une nouvelle.
|
||||
|
||||
|
||||
Envoyez `messages status` pour plus de détails.
|
||||
cli.cmd.messages.status.not-found:
|
||||
ℹ️ Aucune tâche **Pull Messages** active trouvée. Lancez-en une avec `messages
|
||||
pull`.
|
||||
cli.cmd.messages.pull.no-chats-found:
|
||||
Aucune conversation WhatsApp trouvée pour cette période.
|
||||
cli.cmd.contacts.pull.option.progress:
|
||||
Annonce la progression toutes les N contacts. Mettre 0 pour désactiver les
|
||||
rapports.
|
||||
cli.cmd.contacts.pull.option.lids:
|
||||
Inclut les contacts LID WhatsApp (contacts anonymes) dans la synchronisation.
|
||||
task.contacts.completed:
|
||||
👤✅ Contacts terminés → 🆕 {{progress.created}}, 🔄 {{progress.updated}}, ⚪
|
||||
{{progress.skipped}}, 🔴 {{progress.errors}}, 🖼️ {{progress.avatar.updated}}
|
||||
task.messages.started:
|
||||
⏳ Récupération des messages 💬 {{{chat}}} ⏱️ {{{period}}}...
|
||||
task.messages.details:
|
||||
Envoyez "{{{prefix}}}messages status" pour plus de détails.
|
||||
task.messages.progress:
|
||||
'⏳ Messages {{{chat}}} : 🆕 {{progress.ok}}, 🟢 {{progress.exists}}, ⚪
|
||||
{{progress.ignored}}, 🔴 {{progress.errors}}{{#messages.last}} | 📅
|
||||
{{{messages.last}}}{{/messages.last}}'
|
||||
task.messages.completed:
|
||||
✅ Messages {{{chat}}} pendant {{{period}}} → 🆕 {{progress.ok}}, 🟢
|
||||
{{progress.exists}}, ⚪ {{progress.ignored}}, 🔴 {{progress.errors}}
|
||||
task.messages.status: |-
|
||||
Messages **{{{chat}}}** sur la période ⏱️ **{{{period}}}**. ⚙️ État de la tâche : **{{state}}**.
|
||||
|
||||
🗨️ Messages :
|
||||
- 🆕 Créés : **{{{progress.ok}}}**
|
||||
- 🟢 Déjà existants : {{{progress.exists}}}
|
||||
- ⚪ Ignorés : {{{progress.ignored}}}
|
||||
- 🔴 Erreurs : {{{progress.errors}}}
|
||||
{{#messages.first}}
|
||||
- 🗓️ Plus ancien : **{{{messages.first}}}**
|
||||
{{/messages.first}}
|
||||
{{#messages.last}}
|
||||
- 🗓️ Plus récent : **{{{messages.last}}}**
|
||||
{{/messages.last}}
|
||||
|
||||
💬 Conversations WhatsApp :
|
||||
- 🆕 Nouveaux messages dans **{{{chats}}}** conversations
|
||||
{{#options.resolveConversations}}
|
||||
|
||||
🗣️ Conversations :
|
||||
- 🟢 Résolues : **{{{progress.conversationResolve.success}}}**
|
||||
{{#progress.conversationResolve.error}}
|
||||
- 🔴 Erreurs : **{{{progress.conversationResolve.error}}}**
|
||||
{{/progress.conversationResolve.error}}
|
||||
{{/options.resolveConversations}}
|
||||
|
||||
Détails :
|
||||
[{{{details.text}}}]({{{details.url}}})
|
||||
@@ -1,8 +1,33 @@
|
||||
# Hebrew translations for ChatWoot integration
|
||||
# Hebrew translations for WhatsApp inbox integration
|
||||
locale.name: '🇮🇱 עברית (Hebrew)'
|
||||
app.inbox.contact.name: 'אינטגרציית וואטסאפ (WAHA)'
|
||||
|
||||
# קוד הלוקאל הבסיסי - חייב להיות לוקאל תקין לפי 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:
|
||||
weekday: short
|
||||
locales: he-IL
|
||||
day: 2-digit
|
||||
month: short
|
||||
year: numeric
|
||||
hour: 2-digit
|
||||
minute: 2-digit
|
||||
hour12: false
|
||||
# https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat#timezonename
|
||||
timeZoneName: short
|
||||
# https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
|
||||
# timeZone: 'Asia/Jerusalem'
|
||||
|
||||
Today: 'היום'
|
||||
Yesterday: 'אתמול'
|
||||
|
||||
job.report.error: |-
|
||||
⚠️ **{{{header}}}**
|
||||
{{#attempts.nextDelay}}
|
||||
@@ -31,14 +56,14 @@ whatsapp.message.removing.error: לא ניתן להסיר הודעה מוואט
|
||||
job.scheduled.error.header: המשימה המתוזמנת נכשלה בביצוע
|
||||
|
||||
message.from.whatsapp: |-
|
||||
{{{text}}}
|
||||
*📱 נשלח מוואטסאפ*
|
||||
|
||||
📱 *נשלח מוואטסאפ*
|
||||
{{{text}}}
|
||||
|
||||
message.from.api: |-
|
||||
{{{text}}}
|
||||
*📱 נשלח דרך API*
|
||||
|
||||
📱 *נשלח דרך API*
|
||||
{{{text}}}
|
||||
|
||||
message.removed.in.whatsapp: |-
|
||||
❌ *הודעה זו נמחקה בוואטסאפ*
|
||||
@@ -66,11 +91,11 @@ whatsapp.contact.broadcast.suffix: 'רשימת שידור'
|
||||
whatsapp.contact.status.name: 'סטטוס'
|
||||
|
||||
app.connected.message: |-
|
||||
🔗 **מחובר: הפעלת וואטסאפ לתיבת הדואר הנכנס של ChatWoot**
|
||||
🔗 **מחובר: הפעלת וואטסאפ לתיבת הדואר הנכנס**
|
||||
|
||||
✅ הפעלת וואטסאפ **{{{name}}}** חוברה בהצלחה לתיבת הדואר הנכנס של ChatWoot.
|
||||
✅ הפעלת וואטסאפ **{{{name}}}** חוברה בהצלחה לתיבת דואר נכנס זו.
|
||||
|
||||
תיבת דואר נכנס זו מקושרת כעת ל**חשבון הוואטסאפ** שלך. אתה יכול לשלוח ולקבל הודעות ישירות מכאן — בדיוק כמו שימוש בוואטסאפ, אבל בתוך ChatWoot.
|
||||
תיבת דואר נכנס זו מקושרת כעת ל**חשבון הוואטסאפ** שלך. אתה יכול לשלוח ולקבל הודעות ישירות מכאן — בדיוק כמו שימוש בוואטסאפ, אבל בתוך מרחב העבודה שלך.
|
||||
|
||||
אתה יכול גם לנהל את הפעלת הוואטסאפ המקושרת לתיבת דואר נכנס זו. כדי לראות פעולות זמינות, שלח **help** לצ'אט זה.
|
||||
|
||||
@@ -84,9 +109,9 @@ app.connected.message: |-
|
||||
בוא נתחיל! 🚀
|
||||
|
||||
app.disconnected.message: |-
|
||||
🔌 **מנותק: הפעלת וואטסאפ מתיבת הדואר הנכנס של ChatWoot**
|
||||
🔌 **מנותק: הפעלת וואטסאפ מתיבת הדואר הנכנס**
|
||||
|
||||
❌ הפעלת וואטסאפ **{{{name}}}** נותקה מתיבת הדואר הנכנס של ChatWoot.
|
||||
❌ הפעלת וואטסאפ **{{{name}}}** נותקה מתיבת דואר נכנס זו.
|
||||
|
||||
לא תוכל יותר לשלוח או לקבל הודעות מוואטסאפ.
|
||||
|
||||
@@ -97,33 +122,155 @@ app.updated.message: |-
|
||||
|
||||
✅ ההגדרות עבור הפעלת וואטסאפ **{{{name}}}** עודכנו.
|
||||
|
||||
app.commands.list: |-
|
||||
אתה יכול לנהל **אינטגרציית וואטסאפ** על ידי שליחת אחת מהפקודות הבאות:
|
||||
cli.usage.arguments: |-
|
||||
[פעולה]
|
||||
|
||||
cli.usage.command: |-
|
||||
[פקודה]
|
||||
|
||||
cli.usage.options: |-
|
||||
[אפשרויות]
|
||||
|
||||
cli.cmd.disabled: ⛔ **הפקודה `{{command}}` מושבתת**.
|
||||
|
||||
אנא פנה למנהל המערכת שלך.
|
||||
|
||||
שלח **help** כדי לקבל את רשימת הפקודות הזמינות.
|
||||
|
||||
cli.cmd.root.description: |-
|
||||
נהל את **האינטגרציה של וואטסאפ** שלך!
|
||||
|
||||
cli.cmd.root.sub.session: |-
|
||||
🖥️ **הפעלה**:
|
||||
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}}
|
||||
cli.cmd.root.sub.server: |-
|
||||
🖥️ **שרת**:
|
||||
|
||||
שלח **פקודה** (לא תלוי רישיות) או **מספר פקודה** לשיחה זו.
|
||||
cli.cmd.root.sub.sync: |-
|
||||
🔄️ **סנכרון**:
|
||||
|
||||
app.commands.server.disabled: ⛔ **פקודות השרת מושבתות**.
|
||||
cli.cmd.session.status.description: |-
|
||||
קבל את סטטוס ההפעלה הנוכחי.
|
||||
|
||||
אנא פנו למנהל המערכת שלכם.
|
||||
cli.cmd.session.restart.description: |-
|
||||
אתחל את ההפעלה מחדש.
|
||||
|
||||
שלחו **help** כדי לקבל את רשימת הפקודות הזמינות.
|
||||
cli.cmd.session.start.description: |-
|
||||
התחל את ההפעלה.
|
||||
|
||||
cli.cmd.session.stop.description: |-
|
||||
עצור את ההפעלה.
|
||||
|
||||
cli.cmd.session.logout.description: |-
|
||||
התנתק מההפעלה כדי לסרוק קוד QR חדש ולהחזיר את החיבור.
|
||||
|
||||
cli.cmd.session.qr.description: |-
|
||||
הצג את קוד ה-QR של ההפעלה.
|
||||
|
||||
cli.cmd.session.screenshot.description: |-
|
||||
צלם צילום מסך של ההפעלה (זמין רק עם מנוע **WEBJS**).
|
||||
|
||||
cli.cmd.contacts.summary: |-
|
||||
משיכת אנשי קשר של וואטסאפ.
|
||||
|
||||
cli.cmd.contacts.description: |-
|
||||
👤 הרץ `contacts pull` כדי להתחיל סנכרון או `contacts status` כדי לבדוק את ההתקדמות.
|
||||
|
||||
cli.cmd.contacts.action.description: |-
|
||||
השתמש ב-`start` כדי להתחיל למשוך אנשי קשר או ב-`status` כדי לבדוק את המשימה הפעילה.
|
||||
|
||||
cli.cmd.contacts.pull.description: |-
|
||||
התחל למשוך אנשי קשר של וואטסאפ אל תיבת הדואר הנכנס.
|
||||
|
||||
cli.cmd.contacts.pull.option.batch: |-
|
||||
מספר אנשי הקשר שנמשכים בכל אצווה.
|
||||
|
||||
cli.cmd.contacts.pull.option.delay-contact: |-
|
||||
השהיה בין כל איש קשר כדי להימנע ממגבלות בעת משיכת תמונות הפרופיל.
|
||||
|
||||
cli.cmd.contacts.pull.option.delay-batch: |-
|
||||
השהיה בין אצוות של אנשי קשר.
|
||||
|
||||
cli.cmd.contacts.pull.option.avatar: |-
|
||||
בחר כיצד למשוך תמונות פרופיל: `skip`, `if-missing` או `update`.
|
||||
|
||||
cli.cmd.contacts.pull.option.groups: |-
|
||||
כלול בסנכרון אנשי קשר של קבוצות WhatsApp.
|
||||
|
||||
cli.cmd.contacts.pull.option.no-attributes: |-
|
||||
אל תמשוך מאפייני איש קשר אם הוא כבר קיים (מאיץ את התהליך).
|
||||
|
||||
cli.cmd.contacts.pull.already-running: |-
|
||||
⏳ המשימה **Pull Contacts** כבר פועלת. המתן שתסתיים או עצור אותה לפני שמתחילים חדשה.
|
||||
|
||||
שלח `contacts status` לקבלת פרטים.
|
||||
|
||||
cli.cmd.options.job.attempts: |-
|
||||
מספר ניסיונות החזרה לפני שסימנים את המשימה ככושלת.
|
||||
|
||||
cli.cmd.options.job.timeout: |-
|
||||
משך הזמן המרבי שבו המשימה יכולה לפעול לפני ניסיון נוסף (תומך בערכים כגון `30s`, `5m`, `1h`).
|
||||
|
||||
cli.cmd.contacts.status.description: |-
|
||||
הצגת הסטטוס של משימת המשיכה הנוכחית.
|
||||
|
||||
cli.cmd.contacts.status.not-found: |-
|
||||
ℹ️ לא נמצאה משימת **Pull Contacts** פעילה. התחל אחת עם `contacts pull`.
|
||||
|
||||
cli.cmd.server.description: |-
|
||||
נהל את השרת.
|
||||
|
||||
cli.cmd.server.status.description: |-
|
||||
הצג את גרסת השרת והסטטוס.
|
||||
|
||||
cli.cmd.server.reboot.description: |-
|
||||
אתחל את השרת מחדש.
|
||||
|
||||
cli.cmd.server.reboot.option.force: |-
|
||||
כפה אתחול מיידי.
|
||||
|
||||
cli.help.usage.title: |-
|
||||
שימוש: שלח פקודה לשיחה בפורמט הבא:
|
||||
|
||||
cli.help.commands.defaultGroup: |-
|
||||
**פקודות:**
|
||||
|
||||
cli.help.arguments.title: |-
|
||||
**ארגומנטים:**
|
||||
|
||||
cli.help.options.defaultGroup: |-
|
||||
**אפשרויות:**
|
||||
|
||||
cli.help.globalOptions.title: |-
|
||||
**אפשרויות גלובליות:**
|
||||
|
||||
task.contacts.status: |-
|
||||
👤 משיכת אנשי קשר: **{{state}}**
|
||||
- ✅ הצלחות: {{{progress.ok}}} אנשי קשר
|
||||
- ⚪ שדלגו עליהם: {{{progress.skipped}}} אנשי קשר
|
||||
- ⚠️ שגיאות: {{{progress.errors}}} אנשי קשר
|
||||
|
||||
{{#job}}
|
||||
⌛ ציר זמן:
|
||||
- נכנסה לתור: {{{timestamp}}}
|
||||
{{#processedOn}}
|
||||
- התחלה: {{{processedOn}}}
|
||||
{{/processedOn}}
|
||||
{{#finishedOn}}
|
||||
- סיום: {{{finishedOn}}}
|
||||
{{/finishedOn}}
|
||||
{{/job}}
|
||||
|
||||
{{#error}}
|
||||
פרטים:
|
||||
[{{{details.text}}}]({{{details.url}}})
|
||||
{{/error}}
|
||||
|
||||
task.contacts.started: |-
|
||||
אנשי קשר נמשכים...
|
||||
|
||||
task.contacts.progress: |-
|
||||
משיכת אנשי קשר: ✅ {{progress.ok}}, ⚪ {{progress.skipped}}, ⚠️ {{progress.errors}}
|
||||
|
||||
app.help.reminder: |-
|
||||
שלח **help** כדי לקבל את רשימת הפקודות הזמינות.
|
||||
@@ -138,17 +285,17 @@ app.session.current.status: |-
|
||||
📱 **{{{name}}} ({{{id}}})**
|
||||
|
||||
app.session.status.working: |-
|
||||
✅ תיבת הדואר הנכנס של ChatWoot מקושרת ל**חשבון וואטסאפ** ומוכנה לשלוח ולקבל הודעות!
|
||||
✅ תיבת דואר נכנס זו מקושרת ל**חשבון וואטסאפ** שלך ומוכנה לשלוח ולקבל הודעות!
|
||||
|
||||
📱 **{{{name}}} ({{{id}}})**
|
||||
|
||||
app.session.status.error: |-
|
||||
⚠️ **תיבת הדואר הנכנס של ChatWoot מקושרת לחשבון וואטסאפ אך אינה מוכנה לשלוח ולקבל הודעות!**
|
||||
⚠️ **תיבת דואר נכנס זו מקושרת לחשבון וואטסאפ אך אינה מוכנה לשלוח ולקבל הודעות!**
|
||||
|
||||
אנא בדוק את סטטוס ההפעלה ונסה להפעיל אותה מחדש או התנתק וסרוק את קוד ה-QR שוב.
|
||||
|
||||
app.session.scan.qr.code: |-
|
||||
⚠️ **סרוק את קוד ה-QR בטלפון שלך כדי לקשר את חשבון הוואטסאפ לתיבת הדואר הנכנס של ChatWoot!**
|
||||
⚠️ **סרוק את קוד ה-QR בטלפון שלך כדי לקשר את חשבון הוואטסאפ לתיבת דואר נכנס זו!**
|
||||
|
||||
app.server.version.and.status: |-
|
||||
**גרסה:**
|
||||
@@ -216,7 +363,19 @@ whatsapp.to.chatwoot.message.location: |-
|
||||
{{#locationMessage}}
|
||||
📍 **מיקום משותף**
|
||||
|
||||
https://www.google.com/maps?q={{degreesLatitude}},{{degreesLongitude}}
|
||||
{{#name}}
|
||||
**{{name}}**
|
||||
{{/name}}
|
||||
|
||||
{{#address}}
|
||||
📍 **כתובת:** {{address}}
|
||||
{{/address}}
|
||||
|
||||
{{#url}}
|
||||
🌐 **אתר אינטרנט:** {{url}}
|
||||
{{/url}}
|
||||
|
||||
🔗 **הצג במפה:** https://www.google.com/maps?q={{degreesLatitude}},{{degreesLongitude}}
|
||||
{{#caption}}{{caption}}{{/caption}}
|
||||
{{/locationMessage}}
|
||||
|
||||
@@ -224,13 +383,270 @@ whatsapp.to.chatwoot.message.location: |-
|
||||
📍📡 **משתף מיקום חי**
|
||||
פתח/י את **WhatsApp** כדי לראות עדכונים בזמן אמת.
|
||||
|
||||
https://www.google.com/maps?q={{degreesLatitude}},{{degreesLongitude}}
|
||||
{{#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.album: |-
|
||||
📷 **הודעת אלבום התקבלה**
|
||||
|
||||
{{#totalCount}}
|
||||
התקבל אלבום עם **{{totalCount}}** קובץ/קבצי מדיה.
|
||||
{{#expectedImageCount}}
|
||||
📸 תמונות: **{{expectedImageCount}}**
|
||||
{{/expectedImageCount}}
|
||||
{{#expectedVideoCount}}
|
||||
🎥 סרטונים: **{{expectedVideoCount}}**
|
||||
{{/expectedVideoCount}}
|
||||
|
||||
קבצי המדיה יועברו בהודעות הבאות.
|
||||
{{/totalCount}}
|
||||
|
||||
whatsapp.to.chatwoot.message.facebook.ad: |-
|
||||
📢 **מודעת פייסבוק**
|
||||
|
||||
**כותרת:** {{{adData.title}}}
|
||||
|
||||
**תיאור:**
|
||||
{{{adData.body}}}
|
||||
|
||||
**קישור למודעה:** [לצפייה בפייסבוק]({{{adData.sourceUrl}}})
|
||||
**מזהה המודעה:** {{{adData.sourceId}}}
|
||||
|
||||
---
|
||||
💬 **הודעת הלקוח:**
|
||||
{{{payload.body}}}
|
||||
whatsapp.history.message.wrapper: |-
|
||||
{{{content}}}
|
||||
`{{#history}}📥{{/history}}🗓️ {{{timestamp}}}`
|
||||
|
||||
UNKNOWN: לא ידוע
|
||||
ERROR: שגיאה
|
||||
PENDING: נשלח
|
||||
SERVER: שרת
|
||||
DEVICE: מכשיר
|
||||
READ: נקרא
|
||||
PLAYED: הושמע
|
||||
whatsapp.to.chatwoot.message.has.media.no.media: |-
|
||||
{{#content}}
|
||||
{{{content}}}
|
||||
|
||||
{{/content}}
|
||||
🖼️⚠️ **להודעה יש מדיה אך לא הצלחנו להוריד אותה**
|
||||
📱 פתחו את **WhatsApp** כדי לצפות.
|
||||
|
||||
{{^content}}
|
||||
פרטים: [{{{details.text}}}]({{{details.url}}})
|
||||
{{/content}}
|
||||
cli.cmd.root.sub.queue: '🧩 **תור**:'
|
||||
cli.cmd.messages.summary: משיכת הודעות WhatsApp.
|
||||
cli.cmd.messages.description:
|
||||
💬 הפעילו `messages pull` כדי להתחיל סנכרון הודעות או `messages status` למעקב
|
||||
אחר ההתקדמות.
|
||||
cli.cmd.messages.action.description:
|
||||
'`pull` להתחיל משיכה או `status` לבדיקת המשימה הפעילה.'
|
||||
cli.cmd.messages.pull.option.chat: הגבלת המשיכה למזהה שיחה מסוים ב-WhatsApp.
|
||||
cli.cmd.messages.pull.option.force:
|
||||
אילוץ תיבת הדואר הנכנס ליצור הודעות גם אם ההודעה כבר קיימת במיפוי השיחה.
|
||||
cli.cmd.messages.pull.option.batch: כמות ההודעות הנמשכות בכל אצווה.
|
||||
cli.cmd.messages.pull.option.progress:
|
||||
דיווח על ההתקדמות כל N הודעות. ערך 0 מבטל את הדוחות.
|
||||
cli.cmd.messages.pull.option.no-dm: דילוג על הודעות פרטיות בעת משיכת ההיסטוריה.
|
||||
cli.cmd.messages.pull.option.groups: הכללת קבוצות WhatsApp בעת משיכת ההיסטוריה.
|
||||
cli.cmd.messages.pull.option.channels: הכללת ערוצי WhatsApp בעת משיכת ההיסטוריה.
|
||||
cli.cmd.messages.pull.option.status:
|
||||
הכללת סטטוסים של WhatsApp בעת משיכת ההיסטוריה.
|
||||
cli.cmd.messages.pull.option.broadcast: הכללת רשימות שידור בעת משיכת ההיסטוריה.
|
||||
cli.cmd.messages.pull.option.media:
|
||||
הורדת קבצי מדיה (תמונות, סרטונים, קבצים) בעת משיכת ההיסטוריה.
|
||||
cli.cmd.messages.pull.option.pause:
|
||||
הכנסת משימת המשיכה לתור במצב מושהה ועצירת עיבוד הודעות WhatsApp חדשות עד
|
||||
לסיום.
|
||||
cli.cmd.messages.pull.option.resolve-conversations:
|
||||
מסמן את השיחות כפתורות לאחר שכל הודעות ההיסטוריה עובדו.
|
||||
cli.cmd.messages.pull.error.pause-no-queue:
|
||||
⛔ לא ניתן להשתמש ב-`--pause` כי הפקודה **queue** מושבתת בתצורה זו.
|
||||
cli.cmd.queue.description: ניהול תורים.
|
||||
cli.cmd.queue.summary: ביצוע פעולות על תורים.
|
||||
cli.cmd.queue.action.description: בחרו פעולה.
|
||||
cli.cmd.queue.status.description: הצגת מצב התור.
|
||||
cli.cmd.queue.argument.name: 'שם התור: `inbox` או `whatsapp` (רשות).'
|
||||
cli.cmd.queue.start.description: הפעלת תור לפי שם.
|
||||
cli.cmd.queue.stop.description: עצירת תור לפי שם.
|
||||
cli.cmd.queue.paused: ⚪🧩 התורים הושהו. שלחו `queue status` כדי לבדוק את המצב.
|
||||
cli.cmd.queue.resumed: 🟢🧩 התורים חודשו. שלחו `queue status` כדי לבדוק את המצב.
|
||||
cli.cmd.queue.status.result: >-
|
||||
📊 מצב התור:
|
||||
|
||||
{{#queues}}
|
||||
|
||||
- **{{name}}**: {{#locked}}🔒{{/locked}}{{#paused}}⚪
|
||||
מושהה{{/paused}}{{^paused}}🟢 פעיל{{/paused}}
|
||||
|
||||
{{/queues}}
|
||||
cli.cmd.messages.pull.option.timeout-media:
|
||||
הגדרת משך הניסיון להוריד מדיה לכל הודעה.
|
||||
cli.cmd.messages.pull.argument.end:
|
||||
הגדרת נקודת הסיום של חלון המשיכה; למשל `2d` נעצר יומיים אחורה.
|
||||
cli.cmd.messages.pull.argument.start:
|
||||
הגדרת נקודת ההתחלה של החלון; למשל `1d` מתחיל יום אחורה (ברירת המחדל `0d`,
|
||||
כלומר עכשיו).
|
||||
cli.cmd.messages.pull.already-running: >-
|
||||
⏳ המשימה **Pull Messages** כבר פועלת עבור **{{{chat}}}**. המתינו לסיום או
|
||||
עצרו לפני התחלת משימה חדשה.
|
||||
|
||||
|
||||
שלחו `messages status` לפרטים.
|
||||
cli.cmd.messages.status.not-found:
|
||||
ℹ️ לא נמצאה משימת **Pull Messages** פעילה. התחילו אחת עם `messages pull`.
|
||||
cli.cmd.messages.pull.no-chats-found: לא נמצאו שיחות WhatsApp למשיכה בתקופה זו.
|
||||
cli.cmd.contacts.pull.option.progress:
|
||||
דיווח על התקדמות כל N אנשי קשר. ערך 0 מבטל את הדוחות.
|
||||
cli.cmd.contacts.pull.option.lids:
|
||||
הכללת אנשי קשר אנונימיים של WhatsApp (LID) בסנכרון.
|
||||
task.contacts.completed:
|
||||
👤✅ השלמת אנשי קשר → 🆕 {{progress.created}}, 🔄 {{progress.updated}}, ⚪
|
||||
{{progress.skipped}}, 🔴 {{progress.errors}}, 🖼️ {{progress.avatar.updated}}
|
||||
task.messages.started: ⏳ מושכים הודעות 💬 {{{chat}}} ⏱️ {{{period}}}...
|
||||
task.messages.details: שלחו "{{{prefix}}}messages status" לעוד פרטים.
|
||||
task.messages.progress:
|
||||
'⏳ הודעות {{{chat}}}: 🆕 {{progress.ok}}, 🟢 {{progress.exists}}, ⚪
|
||||
{{progress.ignored}}, 🔴 {{progress.errors}}{{#messages.last}} | 📅
|
||||
{{{messages.last}}}{{/messages.last}}'
|
||||
task.messages.completed:
|
||||
✅ הודעות {{{chat}}} במהלך {{{period}}} → 🆕 {{progress.ok}}, 🟢
|
||||
{{progress.exists}}, ⚪ {{progress.ignored}}, 🔴 {{progress.errors}}
|
||||
task.messages.status: |-
|
||||
הודעות **{{{chat}}}** בתקופה ⏱️ **{{{period}}}**. ⚙️ מצב המשימה: **{{state}}**.
|
||||
|
||||
🗨️ הודעות:
|
||||
- 🆕 נוצרו: **{{{progress.ok}}}**
|
||||
- 🟢 קיימות כבר: {{{progress.exists}}}
|
||||
- ⚪ הוזנחו: {{{progress.ignored}}}
|
||||
- 🔴 שגיאות: {{{progress.errors}}}
|
||||
{{#messages.first}}
|
||||
- 🗓️ ישן ביותר: **{{{messages.first}}}**
|
||||
{{/messages.first}}
|
||||
{{#messages.last}}
|
||||
- 🗓️ חדש ביותר: **{{{messages.last}}}**
|
||||
{{/messages.last}}
|
||||
|
||||
💬 שיחות WhatsApp:
|
||||
- 🆕 הודעות חדשות ב-**{{{chats}}}** שיחות
|
||||
{{#options.resolveConversations}}
|
||||
|
||||
🗣️ שיחות:
|
||||
- 🟢 נפתרו: **{{{progress.conversationResolve.success}}}**
|
||||
{{#progress.conversationResolve.error}}
|
||||
- 🔴 שגיאות: **{{{progress.conversationResolve.error}}}**
|
||||
{{/progress.conversationResolve.error}}
|
||||
{{/options.resolveConversations}}
|
||||
|
||||
פרטים:
|
||||
[{{{details.text}}}]({{{details.url}}})
|
||||
@@ -1,8 +1,33 @@
|
||||
# Hindi translations for ChatWoot integration
|
||||
# Hindi translations for WhatsApp inbox integration
|
||||
locale.name: '🇮🇳 हिंदी (Hindi)'
|
||||
app.inbox.contact.name: 'व्हाट्सएप इंटीग्रेशन (WAHA)'
|
||||
|
||||
# बेस लोकैल कोड - यह एक मान्य 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:
|
||||
weekday: short
|
||||
locales: hi-IN
|
||||
day: 2-digit
|
||||
month: short
|
||||
year: numeric
|
||||
hour: 2-digit
|
||||
minute: 2-digit
|
||||
hour12: true
|
||||
# https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat#timezonename
|
||||
timeZoneName: short
|
||||
# https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
|
||||
# timeZone: 'Asia/Kolkata'
|
||||
|
||||
Today: 'आज'
|
||||
Yesterday: 'कल'
|
||||
|
||||
job.report.error: |-
|
||||
⚠️ **{{{header}}}**
|
||||
{{#attempts.nextDelay}}
|
||||
@@ -31,14 +56,14 @@ whatsapp.message.removing.error: व्हाट्सएप से संदे
|
||||
job.scheduled.error.header: निर्धारित कार्य निष्पादित करने में विफल रहा
|
||||
|
||||
message.from.whatsapp: |-
|
||||
{{{text}}}
|
||||
*📱 व्हाट्सएप से भेजा गया*
|
||||
|
||||
📱 *व्हाट्सएप से भेजा गया*
|
||||
{{{text}}}
|
||||
|
||||
message.from.api: |-
|
||||
{{{text}}}
|
||||
*📱 API के माध्यम से भेजा गया*
|
||||
|
||||
📱 *API के माध्यम से भेजा गया*
|
||||
{{{text}}}
|
||||
|
||||
message.removed.in.whatsapp: |-
|
||||
❌ *यह संदेश व्हाट्सएप में हटा दिया गया था*
|
||||
@@ -66,11 +91,11 @@ whatsapp.contact.broadcast.suffix: 'प्रसारण सूची'
|
||||
whatsapp.contact.status.name: 'स्थिति'
|
||||
|
||||
app.connected.message: |-
|
||||
🔗 **कनेक्टेड: व्हाट्सएप सत्र से ChatWoot इनबॉक्स**
|
||||
🔗 **कनेक्टेड: व्हाट्सएप सत्र से इनबॉक्स**
|
||||
|
||||
✅ व्हाट्सएप सत्र **{{{name}}}** सफलतापूर्वक इस ChatWoot इनबॉक्स से जुड़ गया है।
|
||||
✅ व्हाट्सएप सत्र **{{{name}}}** सफलतापूर्वक इस इनबॉक्स से जुड़ गया है।
|
||||
|
||||
यह इनबॉक्स अब आपके **व्हाट्सएप खाते** से जुड़ा हुआ है। आप यहां से सीधे संदेश भेज और प्राप्त कर सकते हैं — बिल्कुल व्हाट्सएप का उपयोग करने जैसा, लेकिन ChatWoot के अंदर।
|
||||
यह इनबॉक्स अब आपके **व्हाट्सएप खाते** से जुड़ा हुआ है। आप यहां से सीधे संदेश भेज और प्राप्त कर सकते हैं — बिल्कुल व्हाट्सएप का उपयोग करने जैसा, लेकिन आपके कार्यक्षेत्र के भीतर।
|
||||
|
||||
आप इस इनबॉक्स से जुड़े व्हाट्सएप सत्र को भी प्रबंधित कर सकते हैं। उपलब्ध कार्यों को देखने के लिए, इस चैट पर **help** भेजें।
|
||||
|
||||
@@ -84,9 +109,9 @@ app.connected.message: |-
|
||||
चलिए शुरू करते हैं! 🚀
|
||||
|
||||
app.disconnected.message: |-
|
||||
🔌 **डिस्कनेक्टेड: व्हाट्सएप सत्र से ChatWoot इनबॉक्स**
|
||||
🔌 **डिस्कनेक्टेड: व्हाट्सएप सत्र से इनबॉक्स**
|
||||
|
||||
❌ व्हाट्सएप सत्र **{{{name}}}** इस ChatWoot इनबॉक्स से डिस्कनेक्ट हो गया है।
|
||||
❌ व्हाट्सएप सत्र **{{{name}}}** इस इनबॉक्स से डिस्कनेक्ट हो गया है।
|
||||
|
||||
आप अब व्हाट्सएप से संदेश भेज या प्राप्त नहीं कर पाएंगे।
|
||||
|
||||
@@ -97,33 +122,155 @@ app.updated.message: |-
|
||||
|
||||
✅ व्हाट्सएप सत्र **{{{name}}}** की सेटिंग्स अपडेट की गई हैं।
|
||||
|
||||
app.commands.list: |-
|
||||
आप निम्न कमांड में से एक भेजकर **व्हाट्सएप इंटीग्रेशन** का प्रबंधन कर सकते हैं:
|
||||
cli.usage.arguments: |-
|
||||
[क्रिया]
|
||||
|
||||
🖥️ **सेशन**:
|
||||
1. **status** - वर्तमान सत्र स्थिति प्राप्त करें।
|
||||
2. **restart** - सत्र को पुनरारंभ करें - यह आपके व्हाट्सएप खाते को फिर से कनेक्ट करने का प्रयास करेगा।
|
||||
3. **start** - सत्र शुरू करें।
|
||||
4. **stop** - सत्र बंद करें।
|
||||
5. **logout** - सत्र से लॉग आउट करें, ताकि आप QR कोड स्कैन कर सकें और सत्र को मरम्मत कर सकें।
|
||||
6. **qr** - सत्र के लिए QR कोड प्राप्त करें।
|
||||
7. **screenshot** - सत्र का स्क्रीनशॉट प्राप्त करें (केवल **WEBJS** इंजन में उपलब्ध)।
|
||||
8. **help** - उपलब्ध कमांड।
|
||||
cli.usage.command: |-
|
||||
[कमांड]
|
||||
|
||||
{{#commands.server}}
|
||||
⚙️ सर्वर:
|
||||
- **server status** - सर्वर संस्करण और स्थिति जानकारी प्राप्त करें।
|
||||
- **server reboot** - सर्वर को सुचारू रूप से पुनरारंभ करें।
|
||||
- **server reboot force** - सर्वर को जबरदस्ती पुनरारंभ करें।
|
||||
{{/commands.server}}
|
||||
cli.usage.options: |-
|
||||
[विकल्प]
|
||||
|
||||
इस वार्तालाप में **कमांड** (केस इनसेंसिटिव) या **कमांड नंबर** भेजें।
|
||||
cli.cmd.disabled: ⛔ **`{{command}}` कमांड अक्षम है**.
|
||||
|
||||
app.commands.server.disabled: ⛔ **सर्वर कमांड अक्षम हैं**।
|
||||
कृपया अपने प्रशासक से संपर्क करें।
|
||||
|
||||
कृपया अपने व्यवस्थापक से संपर्क करें।
|
||||
उपलब्ध कमांड की सूची प्राप्त करने के लिए **help** भेजें।
|
||||
|
||||
उपलब्ध कमांड की सूची पाने के लिए **help** भेजें।
|
||||
cli.cmd.root.description: |-
|
||||
अपने **व्हाट्सएप इंटीग्रेशन** का प्रबंधन करें!
|
||||
|
||||
cli.cmd.root.sub.session: |-
|
||||
🖥️ **सत्र**:
|
||||
|
||||
cli.cmd.root.sub.server: |-
|
||||
🖥️ **सर्वर**:
|
||||
|
||||
cli.cmd.root.sub.sync: |-
|
||||
🔄️ **सिंक**:
|
||||
|
||||
cli.cmd.session.status.description: |-
|
||||
वर्तमान सत्र की स्थिति प्राप्त करें।
|
||||
|
||||
cli.cmd.session.restart.description: |-
|
||||
सत्र को पुनरारंभ करें।
|
||||
|
||||
cli.cmd.session.start.description: |-
|
||||
सत्र प्रारंभ करें।
|
||||
|
||||
cli.cmd.session.stop.description: |-
|
||||
सत्र रोकें।
|
||||
|
||||
cli.cmd.session.logout.description: |-
|
||||
नया QR कोड स्कैन करके कनेक्शन बहाल करने के लिए सत्र से लॉग आउट करें।
|
||||
|
||||
cli.cmd.session.qr.description: |-
|
||||
सत्र का QR कोड दिखाएं।
|
||||
|
||||
cli.cmd.session.screenshot.description: |-
|
||||
सत्र का स्क्रीनशॉट लें (केवल **WEBJS** इंजन के साथ उपलब्ध)।
|
||||
|
||||
cli.cmd.contacts.summary: |-
|
||||
व्हाट्सएप संपर्क प्राप्त करें।
|
||||
|
||||
cli.cmd.contacts.description: |-
|
||||
👤 सिंक शुरू करने के लिए `contacts pull` चलाएँ या प्रगति देखने के लिए `contacts status` चलाएँ।
|
||||
|
||||
cli.cmd.contacts.action.description: |-
|
||||
`start` से संपर्क खींचना शुरू करें या `status` से सक्रिय कार्य जांचें।
|
||||
|
||||
cli.cmd.contacts.pull.description: |-
|
||||
व्हाट्सएप संपर्कों को इस इनबॉक्स में लाना शुरू करें।
|
||||
|
||||
cli.cmd.contacts.pull.option.batch: |-
|
||||
प्रत्येक बैच में खींचे जाने वाले संपर्कों की संख्या।
|
||||
|
||||
cli.cmd.contacts.pull.option.delay-contact: |-
|
||||
हर संपर्क के बीच देरी ताकि अवतार खींचते समय रेट लिमिट से बचा जा सके।
|
||||
|
||||
cli.cmd.contacts.pull.option.delay-batch: |-
|
||||
संपर्क बैचों के बीच देरी।
|
||||
|
||||
cli.cmd.contacts.pull.option.avatar: |-
|
||||
संपर्क अवतार कैसे खींचें चुनें: `skip`, `if-missing`, या `update`।
|
||||
|
||||
cli.cmd.contacts.pull.option.groups: |-
|
||||
सिंक में WhatsApp समूह संपर्क शामिल करें।
|
||||
|
||||
cli.cmd.contacts.pull.option.no-attributes: |-
|
||||
यदि संपर्क पहले से मौजूद है तो उसके गुण न खींचें (इससे प्रक्रिया तेज होती है)।
|
||||
|
||||
cli.cmd.contacts.pull.already-running: |-
|
||||
⏳ **Pull Contacts** कार्य पहले से ही चल रहा है। इसे समाप्त होने दें या नया शुरू करने से पहले रोकें।
|
||||
|
||||
विवरण के लिए `contacts status` भेजें।
|
||||
|
||||
cli.cmd.options.job.attempts: |-
|
||||
कार्य को विफल मानने से पहले पुनः प्रयासों की संख्या।
|
||||
|
||||
cli.cmd.options.job.timeout: |-
|
||||
कार्य को पुनः कोशिश करने से पहले अधिकतम चलने का समय (जैसे `30s`, `5m`, `1h`)।
|
||||
|
||||
cli.cmd.contacts.status.description: |-
|
||||
वर्तमान खींचने वाले कार्य की स्थिति दिखाएँ।
|
||||
|
||||
cli.cmd.contacts.status.not-found: |-
|
||||
ℹ️ कोई सक्रिय **Pull Contacts** कार्य नहीं मिला। `contacts pull` से एक शुरू करें।
|
||||
|
||||
cli.cmd.server.description: |-
|
||||
सर्वर का प्रबंधन करें।
|
||||
|
||||
cli.cmd.server.status.description: |-
|
||||
सर्वर का संस्करण और स्थिति दिखाएं।
|
||||
|
||||
cli.cmd.server.reboot.description: |-
|
||||
सर्वर को पुनरारंभ करें।
|
||||
|
||||
cli.cmd.server.reboot.option.force: |-
|
||||
तुरंत पुनरारंभ को मजबूर करें।
|
||||
|
||||
cli.help.usage.title: |-
|
||||
उपयोग: नीचे दिए गए प्रारूप में बातचीत में कमांड भेजें:
|
||||
|
||||
cli.help.commands.defaultGroup: |-
|
||||
**कमांड:**
|
||||
|
||||
cli.help.arguments.title: |-
|
||||
**आर्ग्युमेंट्स:**
|
||||
|
||||
cli.help.options.defaultGroup: |-
|
||||
**विकल्प:**
|
||||
|
||||
cli.help.globalOptions.title: |-
|
||||
**ग्लोबल विकल्प:**
|
||||
|
||||
task.contacts.status: |-
|
||||
👤 संपर्क खींचना: **{{state}}**
|
||||
- ✅ सफल: {{{progress.ok}}} संपर्क
|
||||
- ⚪ छोड़े गए: {{{progress.skipped}}} संपर्क
|
||||
- ⚠️ त्रुटियाँ: {{{progress.errors}}} संपर्क
|
||||
|
||||
{{#job}}
|
||||
⌛ समयरेखा:
|
||||
- कतार में: {{{timestamp}}}
|
||||
{{#processedOn}}
|
||||
- शुरू हुआ: {{{processedOn}}}
|
||||
{{/processedOn}}
|
||||
{{#finishedOn}}
|
||||
- समाप्त: {{{finishedOn}}}
|
||||
{{/finishedOn}}
|
||||
{{/job}}
|
||||
|
||||
{{#error}}
|
||||
विवरण:
|
||||
[{{{details.text}}}]({{{details.url}}})
|
||||
{{/error}}
|
||||
|
||||
task.contacts.started: |-
|
||||
संपर्क खींचे जा रहे हैं...
|
||||
|
||||
task.contacts.progress: |-
|
||||
संपर्क खींचना: ✅ {{progress.ok}}, ⚪ {{progress.skipped}}, ⚠️ {{progress.errors}}
|
||||
|
||||
app.help.reminder: |-
|
||||
उपलब्ध कमांड की सूची प्राप्त करने के लिए **help** भेजें।
|
||||
@@ -138,17 +285,17 @@ app.session.current.status: |-
|
||||
📱 **{{{name}}} ({{{id}}})**
|
||||
|
||||
app.session.status.working: |-
|
||||
✅ यह ChatWoot इनबॉक्स **व्हाट्सएप खाते** से जुड़ा हुआ है और संदेश भेजने और प्राप्त करने के लिए तैयार है!
|
||||
✅ यह इनबॉक्स आपके **व्हाट्सएप खाते** से जुड़ा हुआ है और संदेश भेजने और प्राप्त करने के लिए तैयार है!
|
||||
|
||||
📱 **{{{name}}} ({{{id}}})**
|
||||
|
||||
app.session.status.error: |-
|
||||
⚠️ **यह ChatWoot इनबॉक्स व्हाट्सएप खाते से जुड़ा हुआ है लेकिन संदेश भेजने और प्राप्त करने के लिए तैयार नहीं है!**
|
||||
⚠️ **यह इनबॉक्स व्हाट्सएप खाते से जुड़ा हुआ है लेकिन संदेश भेजने और प्राप्त करने के लिए तैयार नहीं है!**
|
||||
|
||||
कृपया सत्र की स्थिति जांचें और इसे पुनरारंभ करने का प्रयास करें या लॉग आउट करें और QR कोड फिर से स्कैन करें।
|
||||
|
||||
app.session.scan.qr.code: |-
|
||||
⚠️ **इस ChatWoot इनबॉक्स के साथ व्हाट्सएप खाता जोड़ने के लिए अपने फोन पर QR कोड स्कैन करें!**
|
||||
⚠️ **इस इनबॉक्स के साथ व्हाट्सएप खाता जोड़ने के लिए अपने फोन पर QR कोड स्कैन करें!**
|
||||
|
||||
app.server.version.and.status: |-
|
||||
**संस्करण:**
|
||||
@@ -216,7 +363,19 @@ whatsapp.to.chatwoot.message.location: |-
|
||||
{{#locationMessage}}
|
||||
📍 **साझा स्थान**
|
||||
|
||||
https://www.google.com/maps?q={{degreesLatitude}},{{degreesLongitude}}
|
||||
{{#name}}
|
||||
**{{name}}**
|
||||
{{/name}}
|
||||
|
||||
{{#address}}
|
||||
📍 **पता:** {{address}}
|
||||
{{/address}}
|
||||
|
||||
{{#url}}
|
||||
🌐 **वेबसाइट:** {{url}}
|
||||
{{/url}}
|
||||
|
||||
🔗 **मानचित्र पर देखें:** https://www.google.com/maps?q={{degreesLatitude}},{{degreesLongitude}}
|
||||
{{#caption}}{{caption}}{{/caption}}
|
||||
{{/locationMessage}}
|
||||
|
||||
@@ -224,13 +383,283 @@ whatsapp.to.chatwoot.message.location: |-
|
||||
📍📡 **लाइव लोकेशन साझा की जा रही है**
|
||||
रीयल-टाइम अपडेट देखने के लिए **WhatsApp** खोलें।
|
||||
|
||||
https://www.google.com/maps?q={{degreesLatitude}},{{degreesLongitude}}
|
||||
{{#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.album: |-
|
||||
📷 **एल्बम संदेश प्राप्त हुआ**
|
||||
|
||||
{{#totalCount}}
|
||||
**{{totalCount}}** मीडिया फ़ाइल(ओं) वाला एक एल्बम प्राप्त हुआ।
|
||||
{{#expectedImageCount}}
|
||||
📸 छवियाँ: **{{expectedImageCount}}**
|
||||
{{/expectedImageCount}}
|
||||
{{#expectedVideoCount}}
|
||||
🎥 वीडियो: **{{expectedVideoCount}}**
|
||||
{{/expectedVideoCount}}
|
||||
|
||||
मीडिया फ़ाइलें अगले संदेशों में वितरित की जाएंगी।
|
||||
{{/totalCount}}
|
||||
|
||||
whatsapp.to.chatwoot.message.facebook.ad: |-
|
||||
📢 **फेसबुक विज्ञापन**
|
||||
|
||||
**शीर्षक:** {{{adData.title}}}
|
||||
|
||||
**विवरण:**
|
||||
{{{adData.body}}}
|
||||
|
||||
**विज्ञापन लिंक:** [फेसबुक पर देखें]({{{adData.sourceUrl}}})
|
||||
**विज्ञापन आईडी:** {{{adData.sourceId}}}
|
||||
|
||||
---
|
||||
💬 **ग्राहक संदेश:**
|
||||
{{{payload.body}}}
|
||||
whatsapp.history.message.wrapper: |-
|
||||
{{{content}}}
|
||||
`{{#history}}📥{{/history}}🗓️ {{{timestamp}}}`
|
||||
|
||||
UNKNOWN: अज्ञात
|
||||
ERROR: त्रुटि
|
||||
PENDING: भेजा जा रहा है
|
||||
SERVER: सर्वर
|
||||
DEVICE: डिवाइस
|
||||
READ: पढ़ा गया
|
||||
PLAYED: चलाया गया
|
||||
whatsapp.to.chatwoot.message.has.media.no.media: |-
|
||||
{{#content}}
|
||||
{{{content}}}
|
||||
|
||||
{{/content}}
|
||||
🖼️⚠️ **संदेश में मीडिया है लेकिन हम इसे डाउनलोड नहीं कर सके**
|
||||
📱 कृपया इसे देखने के लिए **WhatsApp** खोलें।
|
||||
|
||||
{{^content}}
|
||||
विवरण: [{{{details.text}}}]({{{details.url}}})
|
||||
{{/content}}
|
||||
cli.cmd.root.sub.queue: '🧩 **क्यू**:'
|
||||
cli.cmd.messages.summary: WhatsApp संदेश खींचें।
|
||||
cli.cmd.messages.description:
|
||||
💬 संदेश सिंक शुरू करने के लिए `messages pull` चलाएँ या प्रगति देखने के लिए
|
||||
`messages status` चलाएँ।
|
||||
cli.cmd.messages.action.description:
|
||||
संदेश खींचने के लिए `pull` या सक्रिय कार्य देखने के लिए `status` का उपयोग
|
||||
करें।
|
||||
cli.cmd.messages.pull.option.chat:
|
||||
खींचने को किसी विशिष्ट WhatsApp चैट आईडी तक सीमित करें।
|
||||
cli.cmd.messages.pull.option.force:
|
||||
अगर WhatsApp संदेश बातचीत मैपिंग में पहले से मौजूद है तब भी इनबॉक्स को संदेश
|
||||
बनाने के लिए बाध्य करें।
|
||||
cli.cmd.messages.pull.option.batch:
|
||||
प्रत्येक बैच में खींचे जाने वाले संदेशों की संख्या।
|
||||
cli.cmd.messages.pull.option.progress:
|
||||
हर N संदेश पर प्रगति रिपोर्ट करें। रिपोर्ट बंद करने के लिए 0 सेट करें।
|
||||
cli.cmd.messages.pull.option.no-dm: इतिहास खींचते समय डायरेक्ट संदेश छोड़ें।
|
||||
cli.cmd.messages.pull.option.groups: इतिहास खींचते समय WhatsApp समूह शामिल करें।
|
||||
cli.cmd.messages.pull.option.channels:
|
||||
इतिहास खींचते समय WhatsApp चैनल शामिल करें।
|
||||
cli.cmd.messages.pull.option.status:
|
||||
इतिहास खींचते समय WhatsApp स्टेटस अपडेट शामिल करें।
|
||||
cli.cmd.messages.pull.option.broadcast:
|
||||
इतिहास खींचते समय प्रसारण सूचियाँ शामिल करें।
|
||||
cli.cmd.messages.pull.option.media:
|
||||
इतिहास खींचते समय मीडिया (छवियाँ, वीडियो, फाइलें) डाउनलोड करें।
|
||||
cli.cmd.messages.pull.option.pause:
|
||||
संदेश खींचने के कार्य को रोके गए अवस्था में कतार में डालें और पूरा होने तक नए
|
||||
WhatsApp संदेशों की प्रोसेसिंग रोकें।
|
||||
cli.cmd.messages.pull.option.resolve-conversations:
|
||||
सभी इतिहास संदेश संसाधित होने के बाद बातचीत को हल किया हुआ चिह्नित करता है।
|
||||
cli.cmd.messages.pull.error.pause-no-queue:
|
||||
⛔ `--pause` का उपयोग नहीं किया जा सकता क्योंकि इस कॉन्फ़िगरेशन में **queue**
|
||||
कमांड अक्षम है।
|
||||
cli.cmd.queue.description: क्यू प्रबंधित करें।
|
||||
cli.cmd.queue.summary: क्यू पर क्रियाएँ चलाएँ।
|
||||
cli.cmd.queue.action.description: एक क्रिया चुनें।
|
||||
cli.cmd.queue.status.description: किसी क्यू की स्थिति दिखाएँ।
|
||||
cli.cmd.queue.argument.name: 'क्यू का नाम: `inbox` या `whatsapp` (वैकल्पिक)।'
|
||||
cli.cmd.queue.start.description: नाम से क्यू शुरू करें।
|
||||
cli.cmd.queue.stop.description: नाम से क्यू बंद करें।
|
||||
cli.cmd.queue.paused:
|
||||
⚪🧩 क्यू रोक दी गई हैं। स्थिति देखने के लिए `queue status` भेजें।
|
||||
cli.cmd.queue.resumed:
|
||||
🟢🧩 क्यू फिर से शुरू हो गई हैं। स्थिति देखने के लिए `queue status` भेजें।
|
||||
cli.cmd.queue.status.result: >-
|
||||
📊 क्यू की स्थिति:
|
||||
|
||||
{{#queues}}
|
||||
|
||||
- **{{name}}**: {{#locked}}🔒{{/locked}}{{#paused}}⚪ रुकी
|
||||
हुई{{/paused}}{{^paused}}🟢 सक्रिय{{/paused}}
|
||||
|
||||
{{/queues}}
|
||||
cli.cmd.messages.pull.option.timeout-media:
|
||||
प्रत्येक संदेश के लिए मीडिया डाउनलोड करने की अवधि निर्धारित करें।
|
||||
cli.cmd.messages.pull.argument.end:
|
||||
खींचने की विंडो कहाँ समाप्त होगी, तय करें; उदाहरण के लिए `2d` दो दिन पहले
|
||||
रुकता है।
|
||||
cli.cmd.messages.pull.argument.start:
|
||||
खींचने की विंडो कहाँ से शुरू होगी, तय करें; उदाहरण के लिए `1d` एक दिन पहले से
|
||||
शुरू होता है (डिफ़ॉल्ट `0d`, यानी अभी)।
|
||||
cli.cmd.messages.pull.already-running: >-
|
||||
⏳ **Pull Messages** कार्य पहले से ही **{{{chat}}}** के लिए चल रहा है। समाप्त
|
||||
होने तक प्रतीक्षा करें या नया शुरू करने से पहले इसे रोकें।
|
||||
|
||||
|
||||
विवरण के लिए `messages status` भेजें।
|
||||
cli.cmd.messages.status.not-found:
|
||||
ℹ️ कोई सक्रिय **Pull Messages** कार्य नहीं मिला। `messages pull` से एक शुरू
|
||||
करें।
|
||||
cli.cmd.messages.pull.no-chats-found:
|
||||
निर्धारित अवधि के लिए कोई WhatsApp चैट नहीं मिली।
|
||||
cli.cmd.contacts.pull.option.progress:
|
||||
हर N संपर्क पर प्रगति रिपोर्ट करें। रिपोर्ट बंद करने के लिए 0 सेट करें।
|
||||
cli.cmd.contacts.pull.option.lids:
|
||||
सिंक में WhatsApp LID (अनाम) संपर्क शामिल करें।
|
||||
task.contacts.completed:
|
||||
👤✅ संपर्क पूरे → 🆕 {{progress.created}}, 🔄 {{progress.updated}}, ⚪
|
||||
{{progress.skipped}}, 🔴 {{progress.errors}}, 🖼️ {{progress.avatar.updated}}
|
||||
task.messages.started:
|
||||
⏳ संदेश खींचे जा रहे हैं 💬 {{{chat}}} ⏱️ {{{period}}}...
|
||||
task.messages.details: और जानकारी के लिए "{{{prefix}}}messages status" भेजें।
|
||||
task.messages.progress:
|
||||
'⏳ संदेश {{{chat}}}: 🆕 {{progress.ok}}, 🟢 {{progress.exists}}, ⚪
|
||||
{{progress.ignored}}, 🔴 {{progress.errors}}{{#messages.last}} | 📅
|
||||
{{{messages.last}}}{{/messages.last}}'
|
||||
task.messages.completed:
|
||||
✅ संदेश {{{chat}}} में {{{period}}} → 🆕 {{progress.ok}}, 🟢
|
||||
{{progress.exists}}, ⚪ {{progress.ignored}}, 🔴 {{progress.errors}}
|
||||
task.messages.status: |-
|
||||
**{{{chat}}}** के संदेश ⏱️ **{{{period}}}** अवधि में। ⚙️ कार्य की स्थिति: **{{state}}**.
|
||||
|
||||
🗨️ संदेश:
|
||||
- 🆕 बनाए गए: **{{{progress.ok}}}**
|
||||
- 🟢 पहले से मौजूद: {{{progress.exists}}}
|
||||
- ⚪ अनदेखा: {{{progress.ignored}}}
|
||||
- 🔴 त्रुटियाँ: {{{progress.errors}}}
|
||||
{{#messages.first}}
|
||||
- 🗓️ सबसे पुराना: **{{{messages.first}}}**
|
||||
{{/messages.first}}
|
||||
{{#messages.last}}
|
||||
- 🗓️ नवीनतम: **{{{messages.last}}}**
|
||||
{{/messages.last}}
|
||||
|
||||
💬 WhatsApp चैट:
|
||||
- 🆕 **{{{chats}}}** चैट में नए संदेश
|
||||
{{#options.resolveConversations}}
|
||||
|
||||
🗣️ बातचीत:
|
||||
- 🟢 हल हुईं: **{{{progress.conversationResolve.success}}}**
|
||||
{{#progress.conversationResolve.error}}
|
||||
- 🔴 त्रुटियाँ: **{{{progress.conversationResolve.error}}}**
|
||||
{{/progress.conversationResolve.error}}
|
||||
{{/options.resolveConversations}}
|
||||
|
||||
विवरण:
|
||||
[{{{details.text}}}]({{{details.url}}})
|
||||
@@ -1,8 +1,33 @@
|
||||
# Indonesian translations for ChatWoot integration
|
||||
# Indonesian translations for WhatsApp inbox integration
|
||||
locale.name: '🇮🇩 Bahasa Indonesia (Indonesian)'
|
||||
app.inbox.contact.name: 'Integrasi WhatsApp (WAHA)'
|
||||
|
||||
# Kode locale dasar - harus berupa locale BCP 47 yang valid
|
||||
# https://en.wikipedia.org/wiki/IETF_language_tag
|
||||
# WAHA menggunakannya untuk format angka, mata uang, dan tanggal/waktu
|
||||
locale.base: 'id-ID'
|
||||
|
||||
app.inbox.contact.name: 'Integrasi WhatsApp'
|
||||
app.inbox.contact.avatar.url: 'https://raw.githubusercontent.com/devlikeapro/waha/core/logo.png'
|
||||
|
||||
# "opsi" dari date.toLocaleString
|
||||
# https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat#options
|
||||
datetime:
|
||||
weekday: short
|
||||
locales: id-ID
|
||||
day: 2-digit
|
||||
month: short
|
||||
year: numeric
|
||||
hour: 2-digit
|
||||
minute: 2-digit
|
||||
hour12: false
|
||||
# https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat#timezonename
|
||||
timeZoneName: short
|
||||
# https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
|
||||
# timeZone: 'Asia/Jakarta'
|
||||
|
||||
Today: 'Hari ini'
|
||||
Yesterday: 'Kemarin'
|
||||
|
||||
job.report.error: |-
|
||||
⚠️ **{{{header}}}**
|
||||
{{#attempts.nextDelay}}
|
||||
@@ -31,14 +56,14 @@ whatsapp.message.removing.error: Tidak dapat menghapus pesan dari WhatsApp
|
||||
job.scheduled.error.header: Tugas terjadwal gagal dijalankan
|
||||
|
||||
message.from.whatsapp: |-
|
||||
{{{text}}}
|
||||
*📱 Dikirim dari WhatsApp*
|
||||
|
||||
📱 *Dikirim dari WhatsApp*
|
||||
{{{text}}}
|
||||
|
||||
message.from.api: |-
|
||||
{{{text}}}
|
||||
*📱 Dikirim melalui API*
|
||||
|
||||
📱 *Dikirim melalui API*
|
||||
{{{text}}}
|
||||
|
||||
message.removed.in.whatsapp: |-
|
||||
❌ *Pesan ini telah dihapus di WhatsApp*
|
||||
@@ -66,11 +91,11 @@ whatsapp.contact.broadcast.suffix: 'Daftar Siaran'
|
||||
whatsapp.contact.status.name: 'Status'
|
||||
|
||||
app.connected.message: |-
|
||||
🔗 **TERHUBUNG: Sesi WhatsApp ke Kotak Masuk ChatWoot**
|
||||
🔗 **TERHUBUNG: Sesi WhatsApp ke kotak masuk**
|
||||
|
||||
✅ Sesi WhatsApp **{{{name}}}** telah berhasil terhubung ke Kotak Masuk ChatWoot ini.
|
||||
✅ Sesi WhatsApp **{{{name}}}** telah berhasil terhubung ke kotak masuk 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.
|
||||
Kotak masuk ini sekarang terhubung ke **Akun WhatsApp** Anda. Anda dapat mengirim dan menerima pesan langsung dari sini — seperti menggunakan WhatsApp, tetapi di dalam ruang kerja Anda.
|
||||
|
||||
Anda juga dapat mengelola sesi WhatsApp yang terhubung ke kotak masuk ini. Untuk melihat tindakan yang tersedia, kirim **help** ke obrolan ini.
|
||||
|
||||
@@ -84,9 +109,9 @@ app.connected.message: |-
|
||||
Mari kita mulai! 🚀
|
||||
|
||||
app.disconnected.message: |-
|
||||
🔌 **TERPUTUS: Sesi WhatsApp dari Kotak Masuk ChatWoot**
|
||||
🔌 **TERPUTUS: Sesi WhatsApp dari kotak masuk**
|
||||
|
||||
❌ Sesi WhatsApp **{{{name}}}** telah terputus dari Kotak Masuk ChatWoot ini.
|
||||
❌ Sesi WhatsApp **{{{name}}}** telah terputus dari kotak masuk ini.
|
||||
|
||||
Anda tidak akan dapat lagi mengirim atau menerima pesan dari WhatsApp.
|
||||
|
||||
@@ -97,33 +122,155 @@ app.updated.message: |-
|
||||
|
||||
✅ Pengaturan untuk sesi WhatsApp **{{{name}}}** telah diperbarui.
|
||||
|
||||
app.commands.list: |-
|
||||
Anda dapat mengelola **Integrasi WhatsApp** dengan mengirimkan salah satu perintah berikut:
|
||||
cli.usage.arguments: |-
|
||||
[aksi]
|
||||
|
||||
🖥️ **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.
|
||||
cli.usage.command: |-
|
||||
[perintah]
|
||||
|
||||
{{#commands.server}}
|
||||
⚙️ Server:
|
||||
- **server status** - dapatkan informasi versi dan status server.
|
||||
- **server reboot** - mulai ulang server dengan anggun.
|
||||
- **server reboot force** - paksa mulai ulang server.
|
||||
{{/commands.server}}
|
||||
cli.usage.options: |-
|
||||
[opsi]
|
||||
|
||||
Kirim **perintah** (tidak peka huruf besar/kecil) atau **nomor perintah** ke percakapan ini.
|
||||
|
||||
app.commands.server.disabled: ⛔ **Perintah server dinonaktifkan**.
|
||||
cli.cmd.disabled: ⛔ **Perintah `{{command}}` dinonaktifkan**.
|
||||
|
||||
Silakan hubungi administrator Anda.
|
||||
|
||||
Kirim **help** untuk melihat daftar perintah yang tersedia.
|
||||
Kirim **help** untuk mendapatkan daftar perintah yang tersedia.
|
||||
|
||||
cli.cmd.root.description: |-
|
||||
Kelola **Integrasi WhatsApp** Anda!
|
||||
|
||||
cli.cmd.root.sub.session: |-
|
||||
🖥️ **Sesi**:
|
||||
|
||||
cli.cmd.root.sub.server: |-
|
||||
🖥️ **Server**:
|
||||
|
||||
cli.cmd.root.sub.sync: |-
|
||||
🔄️ **Sinkronisasi**:
|
||||
|
||||
cli.cmd.session.status.description: |-
|
||||
Dapatkan status sesi saat ini.
|
||||
|
||||
cli.cmd.session.restart.description: |-
|
||||
Mulai ulang sesi.
|
||||
|
||||
cli.cmd.session.start.description: |-
|
||||
Mulai sesi.
|
||||
|
||||
cli.cmd.session.stop.description: |-
|
||||
Hentikan sesi.
|
||||
|
||||
cli.cmd.session.logout.description: |-
|
||||
Keluar dari sesi untuk memindai kode QR baru dan memulihkan koneksi.
|
||||
|
||||
cli.cmd.session.qr.description: |-
|
||||
Tampilkan kode QR sesi.
|
||||
|
||||
cli.cmd.session.screenshot.description: |-
|
||||
Ambil tangkapan layar sesi (hanya tersedia dengan mesin **WEBJS**).
|
||||
|
||||
cli.cmd.contacts.summary: |-
|
||||
Tarik kontak WhatsApp.
|
||||
|
||||
cli.cmd.contacts.description: |-
|
||||
👤 Jalankan `contacts pull` untuk memulai sinkronisasi atau `contacts status` untuk melihat progres.
|
||||
|
||||
cli.cmd.contacts.action.description: |-
|
||||
Gunakan `start` untuk mulai menarik kontak atau `status` untuk memeriksa pekerjaan aktif.
|
||||
|
||||
cli.cmd.contacts.pull.description: |-
|
||||
Mulai menarik kontak WhatsApp ke kotak masuk ini.
|
||||
|
||||
cli.cmd.contacts.pull.option.batch: |-
|
||||
Jumlah kontak yang ditarik di setiap batch.
|
||||
|
||||
cli.cmd.contacts.pull.option.delay-contact: |-
|
||||
Jeda antar tiap kontak untuk menghindari pembatasan saat mengambil avatar.
|
||||
|
||||
cli.cmd.contacts.pull.option.delay-batch: |-
|
||||
Jeda antar batch kontak.
|
||||
|
||||
cli.cmd.contacts.pull.option.avatar: |-
|
||||
Pilih cara mengambil avatar kontak: `skip`, `if-missing`, atau `update`.
|
||||
|
||||
cli.cmd.contacts.pull.option.groups: |-
|
||||
Sertakan kontak grup WhatsApp dalam sinkronisasi.
|
||||
|
||||
cli.cmd.contacts.pull.option.no-attributes: |-
|
||||
Jangan ambil atribut kontak jika kontak sudah ada (mempercepat proses).
|
||||
|
||||
cli.cmd.contacts.pull.already-running: |-
|
||||
⏳ Pekerjaan **Pull Contacts** sudah berjalan. Tunggu hingga selesai atau hentikan sebelum memulai yang baru.
|
||||
|
||||
Kirim `contacts status` untuk detail.
|
||||
|
||||
cli.cmd.options.job.attempts: |-
|
||||
Jumlah percobaan ulang sebelum pekerjaan dianggap gagal.
|
||||
|
||||
cli.cmd.options.job.timeout: |-
|
||||
Durasi maksimum pekerjaan sebelum dicoba ulang (mendukung nilai seperti `30s`, `5m`, `1h`).
|
||||
|
||||
cli.cmd.contacts.status.description: |-
|
||||
Tampilkan status pekerjaan penarikan saat ini.
|
||||
|
||||
cli.cmd.contacts.status.not-found: |-
|
||||
ℹ️ Tidak ada pekerjaan **Pull Contacts** aktif. Mulai satu dengan `contacts pull`.
|
||||
|
||||
cli.cmd.server.description: |-
|
||||
Kelola server.
|
||||
|
||||
cli.cmd.server.status.description: |-
|
||||
Tampilkan versi dan status server.
|
||||
|
||||
cli.cmd.server.reboot.description: |-
|
||||
Mulai ulang server.
|
||||
|
||||
cli.cmd.server.reboot.option.force: |-
|
||||
Paksa reboot segera.
|
||||
|
||||
cli.help.usage.title: |-
|
||||
Penggunaan: kirim perintah ke percakapan dalam format berikut:
|
||||
|
||||
cli.help.commands.defaultGroup: |-
|
||||
**Perintah:**
|
||||
|
||||
cli.help.arguments.title: |-
|
||||
**Argumen:**
|
||||
|
||||
cli.help.options.defaultGroup: |-
|
||||
**Opsi:**
|
||||
|
||||
cli.help.globalOptions.title: |-
|
||||
**Opsi global:**
|
||||
|
||||
task.contacts.status: |-
|
||||
👤 Penarikan kontak: **{{state}}**
|
||||
- ✅ Berhasil: {{{progress.ok}}} kontak
|
||||
- ⚪ Dilewati: {{{progress.skipped}}} kontak
|
||||
- ⚠️ Kesalahan: {{{progress.errors}}} kontak
|
||||
|
||||
{{#job}}
|
||||
⌛ Linimasa:
|
||||
- Masuk antrean: {{{timestamp}}}
|
||||
{{#processedOn}}
|
||||
- Mulai: {{{processedOn}}}
|
||||
{{/processedOn}}
|
||||
{{#finishedOn}}
|
||||
- Selesai: {{{finishedOn}}}
|
||||
{{/finishedOn}}
|
||||
{{/job}}
|
||||
|
||||
{{#error}}
|
||||
Detail:
|
||||
[{{{details.text}}}]({{{details.url}}})
|
||||
{{/error}}
|
||||
|
||||
task.contacts.started: |-
|
||||
Menarik kontak...
|
||||
|
||||
task.contacts.progress: |-
|
||||
Penarikan kontak: ✅ {{progress.ok}}, ⚪ {{progress.skipped}}, ⚠️ {{progress.errors}}
|
||||
|
||||
app.help.reminder: |-
|
||||
Kirim **help** untuk mendapatkan daftar perintah yang tersedia.
|
||||
@@ -138,17 +285,17 @@ app.session.current.status: |-
|
||||
📱 **{{{name}}} ({{{id}}})**
|
||||
|
||||
app.session.status.working: |-
|
||||
✅ Kotak Masuk ChatWoot ini terhubung ke **Akun WhatsApp** dan siap untuk mengirim dan menerima pesan!
|
||||
✅ Kotak masuk ini terhubung ke **Akun WhatsApp** Anda 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!**
|
||||
⚠️ **Kotak masuk 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!**
|
||||
⚠️ **Pindai kode QR di ponsel Anda untuk menghubungkan Akun WhatsApp dengan kotak masuk ini!**
|
||||
|
||||
app.server.version.and.status: |-
|
||||
**Versi:**
|
||||
@@ -216,7 +363,19 @@ whatsapp.to.chatwoot.message.location: |-
|
||||
{{#locationMessage}}
|
||||
📍 **Lokasi yang dibagikan**
|
||||
|
||||
https://www.google.com/maps?q={{degreesLatitude}},{{degreesLongitude}}
|
||||
{{#name}}
|
||||
**{{name}}**
|
||||
{{/name}}
|
||||
|
||||
{{#address}}
|
||||
📍 **Alamat:** {{address}}
|
||||
{{/address}}
|
||||
|
||||
{{#url}}
|
||||
🌐 **Situs web:** {{url}}
|
||||
{{/url}}
|
||||
|
||||
🔗 **Lihat di peta:** https://www.google.com/maps?q={{degreesLatitude}},{{degreesLongitude}}
|
||||
{{#caption}}{{caption}}{{/caption}}
|
||||
{{/locationMessage}}
|
||||
|
||||
@@ -224,13 +383,283 @@ whatsapp.to.chatwoot.message.location: |-
|
||||
📍📡 **Berbagi Lokasi Langsung**
|
||||
Buka **WhatsApp** untuk melihat pembaruan waktu nyata.
|
||||
|
||||
https://www.google.com/maps?q={{degreesLatitude}},{{degreesLongitude}}
|
||||
{{#name}}
|
||||
**{{name}}**
|
||||
{{/name}}
|
||||
|
||||
{{#address}}
|
||||
📍 **Alamat:** {{address}}
|
||||
{{/address}}
|
||||
|
||||
{{#url}}
|
||||
🌐 **Situs web:** {{url}}
|
||||
{{/url}}
|
||||
|
||||
🔗 **Lihat di peta:** https://www.google.com/maps?q={{degreesLatitude}},{{degreesLongitude}}
|
||||
{{#caption}}{{caption}}{{/caption}}
|
||||
{{/liveLocationMessage}}
|
||||
{{/message}}
|
||||
|
||||
whatsapp.to.chatwoot.message.poll: |-
|
||||
{{#poll}}
|
||||
📊 **Jajak Pendapat**
|
||||
|
||||
**{{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}}
|
||||
📅 **Acara**
|
||||
|
||||
**{{name}}**
|
||||
{{#description}}
|
||||
{{description}}
|
||||
{{/description}}
|
||||
|
||||
{{#location.name}}
|
||||
📍 **Lokasi:** {{location.name}}
|
||||
{{/location.name}}
|
||||
{{#startTime}}
|
||||
🕐 **Mulai:** {{startTime}}
|
||||
{{/startTime}}
|
||||
{{#endTime}}
|
||||
🕐 **Selesai:** {{endTime}}
|
||||
{{/endTime}}
|
||||
{{#joinLink}}
|
||||
🔗 **Tautan bergabung:** [{{joinLink}}]({{joinLink}})
|
||||
{{/joinLink}}
|
||||
{{/eventMessage}}
|
||||
{{/message}}
|
||||
|
||||
whatsapp.to.chatwoot.message.pix: |-
|
||||
💳 **PIX - Salin dan Tempel**
|
||||
|
||||
{{#pixData}}
|
||||
{{#merchantName}}
|
||||
**{{merchantName}}**
|
||||
{{/merchantName}}
|
||||
|
||||
{{#key}}
|
||||
**Kunci PIX:** {{key}}
|
||||
{{/key}}
|
||||
{{#keyType}}
|
||||
**Jenis:** {{keyType}}
|
||||
{{/keyType}}
|
||||
{{#totalAmount}}
|
||||
**Jumlah:** {{totalAmountFormatted}}
|
||||
{{/totalAmount}}
|
||||
{{#referenceId}}
|
||||
**Referensi:** {{referenceId}}
|
||||
{{/referenceId}}
|
||||
|
||||
💳 **PIX Salin dan Tempel terkirim**
|
||||
{{/pixData}}
|
||||
|
||||
whatsapp.to.chatwoot.message.unsupported: |-
|
||||
⚠️ **Jenis pesan ini tidak didukung di ChatWoot.**
|
||||
⚠️ **Jenis pesan ini tidak didukung di kotak masuk ini.**
|
||||
📱 Silakan buka **WhatsApp** untuk melihatnya.
|
||||
|
||||
Detail: [{{{details.text}}}]({{{details.url}}})
|
||||
|
||||
whatsapp.to.chatwoot.message.album: |-
|
||||
📷 **Pesan Album Diterima**
|
||||
|
||||
{{#totalCount}}
|
||||
Menerima album dengan **{{totalCount}}** file media.
|
||||
{{#expectedImageCount}}
|
||||
📸 Gambar: **{{expectedImageCount}}**
|
||||
{{/expectedImageCount}}
|
||||
{{#expectedVideoCount}}
|
||||
🎥 Video: **{{expectedVideoCount}}**
|
||||
{{/expectedVideoCount}}
|
||||
|
||||
File media akan dikirimkan dalam pesan berikutnya.
|
||||
{{/totalCount}}
|
||||
|
||||
whatsapp.to.chatwoot.message.facebook.ad: |-
|
||||
📢 **IKLAN FACEBOOK**
|
||||
|
||||
**Judul:** {{{adData.title}}}
|
||||
|
||||
**Deskripsi:**
|
||||
{{{adData.body}}}
|
||||
|
||||
**Tautan Iklan:** [Lihat di Facebook]({{{adData.sourceUrl}}})
|
||||
**ID Iklan:** {{{adData.sourceId}}}
|
||||
|
||||
---
|
||||
💬 **Pesan Pelanggan:**
|
||||
{{{payload.body}}}
|
||||
whatsapp.history.message.wrapper: |-
|
||||
{{{content}}}
|
||||
`{{#history}}📥{{/history}}🗓️ {{{timestamp}}}`
|
||||
|
||||
UNKNOWN: Tidak diketahui
|
||||
ERROR: Kesalahan
|
||||
PENDING: Mengirim
|
||||
SERVER: Server
|
||||
DEVICE: Perangkat
|
||||
READ: Dibaca
|
||||
PLAYED: Diputar
|
||||
whatsapp.to.chatwoot.message.has.media.no.media: |-
|
||||
{{#content}}
|
||||
{{{content}}}
|
||||
|
||||
{{/content}}
|
||||
🖼️⚠️ **Pesan memiliki media, tetapi kami tidak dapat mengunduhnya**
|
||||
📱 Buka **WhatsApp** untuk melihatnya.
|
||||
|
||||
{{^content}}
|
||||
Detail: [{{{details.text}}}]({{{details.url}}})
|
||||
{{/content}}
|
||||
cli.cmd.root.sub.queue: '🧩 **Antrian**:'
|
||||
cli.cmd.messages.summary: Tarik pesan WhatsApp.
|
||||
cli.cmd.messages.description:
|
||||
💬 Jalankan `messages pull` untuk memulai sinkronisasi pesan atau `messages
|
||||
status` untuk memantau progres.
|
||||
cli.cmd.messages.action.description:
|
||||
'`pull` untuk memulai penarikan pesan atau `status` untuk memeriksa pekerjaan
|
||||
yang berjalan.'
|
||||
cli.cmd.messages.pull.option.chat:
|
||||
Batasi penarikan pada ID chat WhatsApp tertentu.
|
||||
cli.cmd.messages.pull.option.force:
|
||||
Paksa kotak masuk membuat pesan meskipun pesan WhatsApp sudah ada di pemetaan
|
||||
percakapan.
|
||||
cli.cmd.messages.pull.option.batch: Jumlah pesan yang ditarik di setiap batch.
|
||||
cli.cmd.messages.pull.option.progress:
|
||||
Laporkan progres setiap N pesan. Setel ke 0 untuk menonaktifkan laporan.
|
||||
cli.cmd.messages.pull.option.no-dm: Lewati pesan langsung saat menarik riwayat.
|
||||
cli.cmd.messages.pull.option.groups:
|
||||
Sertakan grup WhatsApp saat menarik riwayat.
|
||||
cli.cmd.messages.pull.option.channels:
|
||||
Sertakan Channel WhatsApp saat menarik riwayat.
|
||||
cli.cmd.messages.pull.option.status:
|
||||
Sertakan pembaruan status WhatsApp saat menarik riwayat.
|
||||
cli.cmd.messages.pull.option.broadcast:
|
||||
Sertakan daftar siaran saat menarik riwayat.
|
||||
cli.cmd.messages.pull.option.media:
|
||||
Unduh lampiran media (gambar, video, file) saat menarik riwayat.
|
||||
cli.cmd.messages.pull.option.pause:
|
||||
Masukkan pekerjaan penarikan pesan ke antrian dalam keadaan jeda dan hentikan
|
||||
pemrosesan pesan WhatsApp baru sampai selesai.
|
||||
cli.cmd.messages.pull.option.resolve-conversations:
|
||||
Menandai percakapan sebagai selesai setelah semua pesan riwayat diproses.
|
||||
cli.cmd.messages.pull.error.pause-no-queue:
|
||||
⛔ Tidak dapat menggunakan `--pause` karena perintah **queue** dinonaktifkan
|
||||
pada konfigurasi ini.
|
||||
cli.cmd.queue.description: Kelola antrian.
|
||||
cli.cmd.queue.summary: Jalankan tindakan pada antrian.
|
||||
cli.cmd.queue.action.description: Pilih sebuah tindakan.
|
||||
cli.cmd.queue.status.description: Tampilkan status sebuah antrian.
|
||||
cli.cmd.queue.argument.name: 'Nama antrian: `inbox` atau `whatsapp` (opsional).'
|
||||
cli.cmd.queue.start.description: Mulai antrian berdasarkan nama.
|
||||
cli.cmd.queue.stop.description: Hentikan antrian berdasarkan nama.
|
||||
cli.cmd.queue.paused:
|
||||
⚪🧩 Antrian dijeda. Kirim `queue status` untuk memeriksa status.
|
||||
cli.cmd.queue.resumed:
|
||||
🟢🧩 Antrian dilanjutkan. Kirim `queue status` untuk memeriksa status.
|
||||
cli.cmd.queue.status.result: >-
|
||||
📊 Status antrian:
|
||||
|
||||
{{#queues}}
|
||||
|
||||
- **{{name}}**: {{#locked}}🔒{{/locked}}{{#paused}}⚪
|
||||
Dijeda{{/paused}}{{^paused}}🟢 Berjalan{{/paused}}
|
||||
|
||||
{{/queues}}
|
||||
cli.cmd.messages.pull.option.timeout-media:
|
||||
Tetapkan berapa lama mencoba mengunduh media untuk setiap pesan.
|
||||
cli.cmd.messages.pull.argument.end:
|
||||
Tetapkan titik akhir jendela penarikan; misalnya `2d` berhenti dua hari yang
|
||||
lalu.
|
||||
cli.cmd.messages.pull.argument.start:
|
||||
Tetapkan titik awal jendela penarikan; misalnya `1d` dimulai satu hari yang
|
||||
lalu (default `0d`, yaitu sekarang).
|
||||
cli.cmd.messages.pull.already-running: >-
|
||||
⏳ Pekerjaan **Pull Messages** sudah berjalan untuk **{{{chat}}}**. Tunggu
|
||||
hingga selesai atau hentikan sebelum memulai yang baru.
|
||||
|
||||
|
||||
Kirim `messages status` untuk detail.
|
||||
cli.cmd.messages.status.not-found:
|
||||
ℹ️ Tidak ada pekerjaan **Pull Messages** aktif yang ditemukan. Mulai dengan
|
||||
`messages pull`.
|
||||
cli.cmd.messages.pull.no-chats-found:
|
||||
Tidak ada chat WhatsApp yang ditemukan untuk periode tersebut.
|
||||
cli.cmd.contacts.pull.option.progress:
|
||||
Laporkan progres setiap N kontak. Setel ke 0 untuk menonaktifkan laporan.
|
||||
cli.cmd.contacts.pull.option.lids:
|
||||
Sertakan kontak WhatsApp LID (kontak anonim) dalam sinkronisasi.
|
||||
task.contacts.completed:
|
||||
👤✅ Kontak selesai → 🆕 {{progress.created}}, 🔄 {{progress.updated}}, ⚪
|
||||
{{progress.skipped}}, 🔴 {{progress.errors}}, 🖼️ {{progress.avatar.updated}}
|
||||
task.messages.started: ⏳ Menarik pesan 💬 {{{chat}}} ⏱️ {{{period}}}...
|
||||
task.messages.details:
|
||||
Kirim "{{{prefix}}}messages status" untuk melihat rincian.
|
||||
task.messages.progress:
|
||||
'⏳ Pesan {{{chat}}}: 🆕 {{progress.ok}}, 🟢 {{progress.exists}}, ⚪
|
||||
{{progress.ignored}}, 🔴 {{progress.errors}}{{#messages.last}} | 📅
|
||||
{{{messages.last}}}{{/messages.last}}'
|
||||
task.messages.completed:
|
||||
✅ Pesan {{{chat}}} selama {{{period}}} → 🆕 {{progress.ok}}, 🟢
|
||||
{{progress.exists}}, ⚪ {{progress.ignored}}, 🔴 {{progress.errors}}
|
||||
task.messages.status: |-
|
||||
Pesan **{{{chat}}}** dalam periode ⏱️ **{{{period}}}**. ⚙️ Status pekerjaan: **{{state}}**.
|
||||
|
||||
🗨️ Pesan:
|
||||
- 🆕 Dibuat: **{{{progress.ok}}}**
|
||||
- 🟢 Sudah ada: {{{progress.exists}}}
|
||||
- ⚪ Diabaikan: {{{progress.ignored}}}
|
||||
- 🔴 Kesalahan: {{{progress.errors}}}
|
||||
{{#messages.first}}
|
||||
- 🗓️ Paling awal: **{{{messages.first}}}**
|
||||
{{/messages.first}}
|
||||
{{#messages.last}}
|
||||
- 🗓️ Terbaru: **{{{messages.last}}}**
|
||||
{{/messages.last}}
|
||||
|
||||
💬 Chat WhatsApp:
|
||||
- 🆕 Pesan baru di **{{{chats}}}** chat
|
||||
{{#options.resolveConversations}}
|
||||
|
||||
🗣️ Percakapan:
|
||||
- 🟢 Selesai: **{{{progress.conversationResolve.success}}}**
|
||||
{{#progress.conversationResolve.error}}
|
||||
- 🔴 Kesalahan: **{{{progress.conversationResolve.error}}}**
|
||||
{{/progress.conversationResolve.error}}
|
||||
{{/options.resolveConversations}}
|
||||
|
||||
Detail:
|
||||
[{{{details.text}}}]({{{details.url}}})
|
||||
@@ -1,8 +1,33 @@
|
||||
# Punjabi translations for ChatWoot integration
|
||||
# Punjabi translations for WhatsApp inbox integration
|
||||
locale.name: '🇵🇰 ਪੰਜਾਬੀ (Punjabi)'
|
||||
app.inbox.contact.name: 'ਵਟਸਐਪ ਇੰਟੀਗ੍ਰੇਸ਼ਨ (WAHA)'
|
||||
|
||||
# ਅਧਾਰਭੂਤ ਲੋਕੇਲ ਕੋਡ - ਇਹ ਇੱਕ ਵੈਧ BCP 47 ਲੋਕੇਲ ਹੋਣਾ ਚਾਹੀਦਾ ਹੈ
|
||||
# https://en.wikipedia.org/wiki/IETF_language_tag
|
||||
# WAHA ਇਸਨੂੰ ਅੰਕ, ਕਰੰਸੀ ਅਤੇ ਤਾਰੀਖ/ਸਮੇਂ ਦੀ ਨਮਾਇਸ਼ ਲਈ ਵਰਤਦਾ ਹੈ
|
||||
locale.base: 'pa-PK'
|
||||
|
||||
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:
|
||||
weekday: short
|
||||
locales: pa-PK
|
||||
day: 2-digit
|
||||
month: short
|
||||
year: numeric
|
||||
hour: 2-digit
|
||||
minute: 2-digit
|
||||
hour12: true
|
||||
# https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat#timezonename
|
||||
timeZoneName: short
|
||||
# https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
|
||||
# timeZone: 'Asia/Karachi'
|
||||
|
||||
Today: 'ਅੱਜ'
|
||||
Yesterday: 'ਕੱਲ੍ਹ'
|
||||
|
||||
job.report.error: |-
|
||||
⚠️ **{{{header}}}**
|
||||
{{#attempts.nextDelay}}
|
||||
@@ -31,14 +56,14 @@ whatsapp.message.removing.error: ਵਟਸਐਪ ਤੋਂ ਸੁਨੇਹਾ
|
||||
job.scheduled.error.header: ਨਿਰਧਾਰਿਤ ਕੰਮ ਚਲਾਉਣ ਵਿੱਚ ਅਸਫਲ ਰਿਹਾ
|
||||
|
||||
message.from.whatsapp: |-
|
||||
{{{text}}}
|
||||
*📱 ਵਟਸਐਪ ਤੋਂ ਭੇਜਿਆ ਗਿਆ*
|
||||
|
||||
📱 *ਵਟਸਐਪ ਤੋਂ ਭੇਜਿਆ ਗਿਆ*
|
||||
{{{text}}}
|
||||
|
||||
message.from.api: |-
|
||||
{{{text}}}
|
||||
*📱 API ਰਾਹੀਂ ਭੇਜਿਆ ਗਿਆ*
|
||||
|
||||
📱 *API ਰਾਹੀਂ ਭੇਜਿਆ ਗਿਆ*
|
||||
{{{text}}}
|
||||
|
||||
message.removed.in.whatsapp: |-
|
||||
❌ *ਇਹ ਸੁਨੇਹਾ ਵਟਸਐਪ ਵਿੱਚ ਮਿਟਾ ਦਿੱਤਾ ਗਿਆ ਸੀ*
|
||||
@@ -66,11 +91,11 @@ whatsapp.contact.broadcast.suffix: 'ਪ੍ਰਸਾਰਣ ਸੂਚੀ'
|
||||
whatsapp.contact.status.name: 'ਸਥਿਤੀ'
|
||||
|
||||
app.connected.message: |-
|
||||
🔗 **ਕਨੈਕਟ ਕੀਤਾ: ਵਟਸਐਪ ਸੈਸ਼ਨ ਤੋਂ ChatWoot ਇਨਬਾਕਸ**
|
||||
🔗 **ਕਨੈਕਟ ਕੀਤਾ: ਵਟਸਐਪ ਸੈਸ਼ਨ ਤੋਂ ਇਨਬਾਕਸ**
|
||||
|
||||
✅ ਵਟਸਐਪ ਸੈਸ਼ਨ **{{{name}}}** ਨੂੰ ਸਫਲਤਾਪੂਰਵਕ ਇਸ ChatWoot ਇਨਬਾਕਸ ਨਾਲ ਜੋੜਿਆ ਗਿਆ ਹੈ।
|
||||
✅ ਵਟਸਐਪ ਸੈਸ਼ਨ **{{{name}}}** ਨੂੰ ਸਫਲਤਾਪੂਰਵਕ ਇਸ ਇਨਬਾਕਸ ਨਾਲ ਜੋੜਿਆ ਗਿਆ ਹੈ।
|
||||
|
||||
ਇਹ ਇਨਬਾਕਸ ਹੁਣ ਤੁਹਾਡੇ **ਵਟਸਐਪ ਖਾਤੇ** ਨਾਲ ਜੁੜਿਆ ਹੋਇਆ ਹੈ। ਤੁਸੀਂ ਇੱਥੋਂ ਸਿੱਧੇ ਸੁਨੇਹੇ ਭੇਜ ਅਤੇ ਪ੍ਰਾਪਤ ਕਰ ਸਕਦੇ ਹੋ — ਬਿਲਕੁਲ ਵਟਸਐਪ ਦੀ ਵਰਤੋਂ ਵਾਂਗ, ਪਰ ChatWoot ਦੇ ਅੰਦਰ।
|
||||
ਇਹ ਇਨਬਾਕਸ ਹੁਣ ਤੁਹਾਡੇ **ਵਟਸਐਪ ਖਾਤੇ** ਨਾਲ ਜੁੜਿਆ ਹੋਇਆ ਹੈ। ਤੁਸੀਂ ਇੱਥੋਂ ਸਿੱਧੇ ਸੁਨੇਹੇ ਭੇਜ ਅਤੇ ਪ੍ਰਾਪਤ ਕਰ ਸਕਦੇ ਹੋ — ਬਿਲਕੁਲ ਵਟਸਐਪ ਦੀ ਵਰਤੋਂ ਵਾਂਗ, ਪਰ ਇਸ ਵਰਕਸਪੇਸ ਦੇ ਅੰਦਰ।
|
||||
|
||||
ਤੁਸੀਂ ਇਸ ਇਨਬਾਕਸ ਨਾਲ ਜੁੜੇ ਵਟਸਐਪ ਸੈਸ਼ਨ ਦਾ ਪ੍ਰਬੰਧਨ ਵੀ ਕਰ ਸਕਦੇ ਹੋ। ਉਪਲਬਧ ਕਾਰਵਾਈਆਂ ਦੇਖਣ ਲਈ, ਇਸ ਚੈਟ ਨੂੰ **help** ਭੇਜੋ।
|
||||
|
||||
@@ -84,9 +109,9 @@ app.connected.message: |-
|
||||
ਆਓ ਸ਼ੁਰੂ ਕਰੀਏ! 🚀
|
||||
|
||||
app.disconnected.message: |-
|
||||
🔌 **ਡਿਸਕਨੈਕਟ ਕੀਤਾ: ਵਟਸਐਪ ਸੈਸ਼ਨ ਤੋਂ ChatWoot ਇਨਬਾਕਸ**
|
||||
🔌 **ਡਿਸਕਨੈਕਟ ਕੀਤਾ: ਵਟਸਐਪ ਸੈਸ਼ਨ ਤੋਂ ਇਨਬਾਕਸ**
|
||||
|
||||
❌ ਵਟਸਐਪ ਸੈਸ਼ਨ **{{{name}}}** ਇਸ ChatWoot ਇਨਬਾਕਸ ਤੋਂ ਡਿਸਕਨੈਕਟ ਕੀਤਾ ਗਿਆ ਹੈ।
|
||||
❌ ਵਟਸਐਪ ਸੈਸ਼ਨ **{{{name}}}** ਇਸ ਇਨਬਾਕਸ ਤੋਂ ਡਿਸਕਨੈਕਟ ਕੀਤਾ ਗਿਆ ਹੈ।
|
||||
|
||||
ਤੁਸੀਂ ਹੁਣ ਵਟਸਐਪ ਤੋਂ ਸੁਨੇਹੇ ਭੇਜ ਜਾਂ ਪ੍ਰਾਪਤ ਨਹੀਂ ਕਰ ਸਕੋਗੇ।
|
||||
|
||||
@@ -97,33 +122,155 @@ app.updated.message: |-
|
||||
|
||||
✅ ਵਟਸਐਪ ਸੈਸ਼ਨ **{{{name}}}** ਲਈ ਸੈਟਿੰਗਾਂ ਅਪਡੇਟ ਕੀਤੀਆਂ ਗਈਆਂ ਹਨ।
|
||||
|
||||
app.commands.list: |-
|
||||
ਤੁਸੀਂ ਹੇਠਾਂ ਦਿੱਤੇ ਕਮਾਂਡਾਂ ਵਿੱਚੋਂ ਇੱਕ ਭੇਜ ਕੇ **ਵਟਸਐਪ ਇੰਟੀਗ੍ਰੇਸ਼ਨ** ਦਾ ਪ੍ਰਬੰਧਨ ਕਰ ਸਕਦੇ ਹੋ:
|
||||
cli.usage.arguments: |-
|
||||
[ਕਾਰਵਾਈ]
|
||||
|
||||
🖥️ **ਸੈਸ਼ਨ**:
|
||||
1. **status** - ਮੌਜੂਦਾ ਸੈਸ਼ਨ ਸਥਿਤੀ ਪ੍ਰਾਪਤ ਕਰੋ।
|
||||
2. **restart** - ਸੈਸ਼ਨ ਮੁੜ ਚਾਲੂ ਕਰੋ - ਇਹ ਤੁਹਾਡੇ ਵਟਸਐਪ ਖਾਤੇ ਨੂੰ ਮੁੜ ਕਨੈਕਟ ਕਰਨ ਦੀ ਕੋਸ਼ਿਸ਼ ਕਰੇਗਾ।
|
||||
3. **start** - ਸੈਸ਼ਨ ਸ਼ੁਰੂ ਕਰੋ।
|
||||
4. **stop** - ਸੈਸ਼ਨ ਬੰਦ ਕਰੋ।
|
||||
5. **logout** - ਸੈਸ਼ਨ ਤੋਂ ਲੌਗਆਉਟ ਕਰੋ, ਤਾਂ ਜੋ ਤੁਸੀਂ QR ਕੋਡ ਸਕੈਨ ਕਰ ਸਕੋ ਅਤੇ ਸੈਸ਼ਨ ਨੂੰ ਮੁਰੰਮਤ ਕਰ ਸਕੋ।
|
||||
6. **qr** - ਸੈਸ਼ਨ ਲਈ QR ਕੋਡ ਪ੍ਰਾਪਤ ਕਰੋ।
|
||||
7. **screenshot** - ਸੈਸ਼ਨ ਦਾ ਸਕ੍ਰੀਨਸ਼ਾਟ ਪ੍ਰਾਪਤ ਕਰੋ (ਸਿਰਫ **WEBJS** ਇੰਜਣ ਵਿੱਚ ਉਪਲਬਧ)।
|
||||
8. **help** - ਉਪਲਬਧ ਕਮਾਂਡਾਂ।
|
||||
cli.usage.command: |-
|
||||
[ਕਮਾਂਡ]
|
||||
|
||||
{{#commands.server}}
|
||||
⚙️ ਸਰਵਰ:
|
||||
- **server status** - ਸਰਵਰ ਵਰਜਨ ਅਤੇ ਸਥਿਤੀ ਜਾਣਕਾਰੀ ਪ੍ਰਾਪਤ ਕਰੋ।
|
||||
- **server reboot** - ਸਰਵਰ ਨੂੰ ਸੁਚਾਰੂ ਢੰਗ ਨਾਲ ਮੁੜ ਚਾਲੂ ਕਰੋ।
|
||||
- **server reboot force** - ਸਰਵਰ ਨੂੰ ਜ਼ਬਰਦਸਤੀ ਮੁੜ ਚਾਲੂ ਕਰੋ।
|
||||
{{/commands.server}}
|
||||
cli.usage.options: |-
|
||||
[ਚੋਣਾਂ]
|
||||
|
||||
ਇਸ ਗੱਲਬਾਤ ਵਿੱਚ **ਕਮਾਂਡ** (ਕੇਸ ਇਨਸੈਂਸਿਟਿਵ) ਜਾਂ **ਕਮਾਂਡ ਨੰਬਰ** ਭੇਜੋ।
|
||||
|
||||
app.commands.server.disabled: ⛔ **ਸਰਵਰ ਕਮਾਂਡ ਅਯੋਗ ਹਨ**।
|
||||
cli.cmd.disabled: ⛔ **`{{command}}` ਕਮਾਂਡ ਨਿਸ਼ਕ੍ਰਿਆ ਕੀਤੀ ਗਈ ਹੈ**।
|
||||
|
||||
ਕਿਰਪਾ ਕਰਕੇ ਆਪਣੇ ਪ੍ਰਸ਼ਾਸਕ ਨਾਲ ਸੰਪਰਕ ਕਰੋ।
|
||||
|
||||
ਉਪਲਬਧ ਕਮਾਂਡਾਂ ਦੀ ਸੂਚੀ ਲਈ **help** ਭੇਜੋ।
|
||||
ਉਪਲਬਧ ਕਮਾਂਡਾਂ ਦੀ ਸੂਚੀ ਪ੍ਰਾਪਤ ਕਰਨ ਲਈ **help** ਭੇਜੋ।
|
||||
|
||||
cli.cmd.root.description: |-
|
||||
ਆਪਣੀ **ਵਟਸਐਪ ਇੰਟੀਗ੍ਰੇਸ਼ਨ** ਦਾ ਪ੍ਰਬੰਧ ਕਰੋ!
|
||||
|
||||
cli.cmd.root.sub.session: |-
|
||||
🖥️ **ਸੈਸ਼ਨ**:
|
||||
|
||||
cli.cmd.root.sub.server: |-
|
||||
🖥️ **ਸਰਵਰ**:
|
||||
|
||||
cli.cmd.root.sub.sync: |-
|
||||
🔄️ **ਸਿੰਕ**:
|
||||
|
||||
cli.cmd.session.status.description: |-
|
||||
ਮੌਜੂਦਾ ਸੈਸ਼ਨ ਦੀ ਸਥਿਤੀ ਪ੍ਰਾਪਤ ਕਰੋ।
|
||||
|
||||
cli.cmd.session.restart.description: |-
|
||||
ਸੈਸ਼ਨ ਨੂੰ ਮੁੜ ਚਾਲੂ ਕਰੋ।
|
||||
|
||||
cli.cmd.session.start.description: |-
|
||||
ਸੈਸ਼ਨ ਸ਼ੁਰੂ ਕਰੋ।
|
||||
|
||||
cli.cmd.session.stop.description: |-
|
||||
ਸੈਸ਼ਨ ਰੋਕੋ।
|
||||
|
||||
cli.cmd.session.logout.description: |-
|
||||
ਨਵਾਂ QR ਕੋਡ ਸਕੈਨ ਕਰਕੇ ਕਨੈਕਸ਼ਨ ਬਹਾਲ ਕਰਨ ਲਈ ਸੈਸ਼ਨ ਤੋਂ ਲੌਗਆਉਟ ਕਰੋ।
|
||||
|
||||
cli.cmd.session.qr.description: |-
|
||||
ਸੈਸ਼ਨ ਦਾ QR ਕੋਡ ਵੇਖਾਓ।
|
||||
|
||||
cli.cmd.session.screenshot.description: |-
|
||||
ਸੈਸ਼ਨ ਦਾ ਸਕ੍ਰੀਨਸ਼ਾਟ ਲਓ (ਸਿਰਫ਼ **WEBJS** ਇੰਜਨ ਨਾਲ ਉਪਲਬਧ)।
|
||||
|
||||
cli.cmd.contacts.summary: |-
|
||||
ਵਟਸਐਪ ਸੰਪਰਕ ਖਿੱਚੋ।
|
||||
|
||||
cli.cmd.contacts.description: |-
|
||||
👤 ਸਿੰਕ ਸ਼ੁਰੂ ਕਰਨ ਲਈ `contacts pull` ਚਲਾਓ ਜਾਂ ਤਰੱਕੀ ਦੇਖਣ ਲਈ `contacts status` ਚਲਾਓ।
|
||||
|
||||
cli.cmd.contacts.action.description: |-
|
||||
`start` ਨਾਲ ਸੰਪਰਕ ਖਿੱਚਣਾ ਸ਼ੁਰੂ ਕਰੋ ਜਾਂ `status` ਨਾਲ ਸਰਗਰਮ ਕੰਮ ਦੀ ਜਾਂਚ ਕਰੋ।
|
||||
|
||||
cli.cmd.contacts.pull.description: |-
|
||||
ਵਟਸਐਪ ਸੰਪਰਕਾਂ ਨੂੰ ਇਸ ਇਨਬਾਕਸ ਵਿੱਚ ਲਿਆਉਣਾ ਸ਼ੁਰੂ ਕਰੋ।
|
||||
|
||||
cli.cmd.contacts.pull.option.batch: |-
|
||||
ਹਰ ਬੈਚ ਵਿੱਚ ਖਿੱਚੇ ਜਾਣ ਵਾਲੇ ਸੰਪਰਕਾਂ ਦੀ ਗਿਣਤੀ।
|
||||
|
||||
cli.cmd.contacts.pull.option.delay-contact: |-
|
||||
ਹਰ ਸੰਪਰਕ ਦੇ ਵਿਚਕਾਰ ਦੇਰੀ, ਤਾਂ ਜੋ ਅਵਤਾਰ ਖਿੱਚਦੇ ਸਮੇਂ ਰੇਟ ਲਿਮਟ ਤੋਂ ਬਚਿਆ ਜਾ ਸਕੇ।
|
||||
|
||||
cli.cmd.contacts.pull.option.delay-batch: |-
|
||||
ਸੰਪਰਕ ਬੈਚਾਂ ਦੇ ਵਿਚਕਾਰ ਦੇਰੀ।
|
||||
|
||||
cli.cmd.contacts.pull.option.avatar: |-
|
||||
ਚੁਣੋ ਕਿ ਅਵਤਾਰ ਕਿਵੇਂ ਖਿੱਚਣੇ ਹਨ: `skip`, `if-missing`, ਜਾਂ `update`।
|
||||
|
||||
cli.cmd.contacts.pull.option.groups: |-
|
||||
ਸਿੰਕ ਵਿੱਚ WhatsApp ਗਰੁੱਪ ਸੰਪਰਕ ਸ਼ਾਮਲ ਕਰੋ।
|
||||
|
||||
cli.cmd.contacts.pull.option.no-attributes: |-
|
||||
ਜੇ ਸੰਪਰਕ ਪਹਿਲਾਂ ਹੀ ਮੌਜੂਦ ਹੈ ਤਾਂ ਉਸਦੇ ਗੁਣ ਨਾ ਖਿੱਚੋ (ਇਸ ਨਾਲ ਪ੍ਰਕਿਰਿਆ ਤੇਜ਼ ਹੁੰਦੀ ਹੈ)।
|
||||
|
||||
cli.cmd.contacts.pull.already-running: |-
|
||||
⏳ **Pull Contacts** ਕੰਮ ਪਹਿਲਾਂ ਹੀ ਚੱਲ ਰਿਹਾ ਹੈ। ਇਸਦੇ ਮੁਕੰਮਲ ਹੋਣ ਦੀ ਉਡੀਕ ਕਰੋ ਜਾਂ ਨਵਾਂ ਸ਼ੁਰੂ ਕਰਨ ਤੋਂ ਪਹਿਲਾਂ ਇਸਨੂੰ ਰੋਕੋ।
|
||||
|
||||
ਵੇਰਵਿਆਂ ਲਈ `contacts status` ਭੇਜੋ।
|
||||
|
||||
cli.cmd.options.job.attempts: |-
|
||||
ਕੰਮ ਨੂੰ ਅਸਫਲ ਘੋਸ਼ਿਤ ਕਰਨ ਤੋਂ ਪਹਿਲਾਂ ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ਾਂ ਦੀ ਗਿਣਤੀ।
|
||||
|
||||
cli.cmd.options.job.timeout: |-
|
||||
ਕੰਮ ਨੂੰ ਮੁੜ ਕੋਸ਼ਿਸ਼ ਕਰਨ ਤੋਂ ਪਹਿਲਾਂ ਚੱਲਣ ਦਾ ਵੱਧ ਤੋਂ ਵੱਧ ਸਮਾਂ (ਜਿਵੇਂ `30s`, `5m`, `1h`)।
|
||||
|
||||
cli.cmd.contacts.status.description: |-
|
||||
ਮੌਜੂਦਾ ਖਿੱਚਣ ਵਾਲੇ ਕੰਮ ਦੀ ਸਥਿਤੀ ਵੇਖਾਓ।
|
||||
|
||||
cli.cmd.contacts.status.not-found: |-
|
||||
ℹ️ ਕੋਈ ਸਰਗਰਮ **Pull Contacts** ਕੰਮ ਨਹੀਂ ਮਿਲਿਆ। `contacts pull` ਨਾਲ ਇੱਕ ਸ਼ੁਰੂ ਕਰੋ।
|
||||
|
||||
cli.cmd.server.description: |-
|
||||
ਸਰਵਰ ਦਾ ਪ੍ਰਬੰਧ ਕਰੋ।
|
||||
|
||||
cli.cmd.server.status.description: |-
|
||||
ਸਰਵਰ ਦਾ ਵਰਜਨ ਅਤੇ ਸਥਿਤੀ ਵੇਖਾਓ।
|
||||
|
||||
cli.cmd.server.reboot.description: |-
|
||||
ਸਰਵਰ ਨੂੰ ਮੁੜ ਚਾਲੂ ਕਰੋ।
|
||||
|
||||
cli.cmd.server.reboot.option.force: |-
|
||||
ਤੁਰੰਤ ਰੀਬੂਟ ਲਾਜ਼ਮੀ ਕਰੋ।
|
||||
|
||||
cli.help.usage.title: |-
|
||||
ਵਰਤੋਂ: ਹੇਠਾਂ ਦਿੱਤੇ ਫਾਰਮੈਟ ਵਿੱਚ ਗੱਲਬਾਤ ਵਿੱਚ ਕਮਾਂਡ ਭੇਜੋ:
|
||||
|
||||
cli.help.commands.defaultGroup: |-
|
||||
**ਕਮਾਂਡਾਂ:**
|
||||
|
||||
cli.help.arguments.title: |-
|
||||
**ਦਲੀਲਾਂ:**
|
||||
|
||||
cli.help.options.defaultGroup: |-
|
||||
**ਚੋਣਾਂ:**
|
||||
|
||||
cli.help.globalOptions.title: |-
|
||||
**ਗਲੋਬਲ ਚੋਣਾਂ:**
|
||||
|
||||
task.contacts.status: |-
|
||||
👤 ਸੰਪਰਕ ਖਿੱਚਣਾ: **{{state}}**
|
||||
- ✅ ਸਫਲ: {{{progress.ok}}} ਸੰਪਰਕ
|
||||
- ⚪ ਛੱਡੇ ਗਏ: {{{progress.skipped}}} ਸੰਪਰਕ
|
||||
- ⚠️ ਗਲਤੀਆਂ: {{{progress.errors}}} ਸੰਪਰਕ
|
||||
|
||||
{{#job}}
|
||||
⌛ ਸਮਾਂ-ਰੇਖਾ:
|
||||
- ਕਤਾਰ ਵਿੱਚ: {{{timestamp}}}
|
||||
{{#processedOn}}
|
||||
- ਸ਼ੁਰੂ: {{{processedOn}}}
|
||||
{{/processedOn}}
|
||||
{{#finishedOn}}
|
||||
- ਸਮਾਪਤ: {{{finishedOn}}}
|
||||
{{/finishedOn}}
|
||||
{{/job}}
|
||||
|
||||
{{#error}}
|
||||
ਵੇਰਵੇ:
|
||||
[{{{details.text}}}]({{{details.url}}})
|
||||
{{/error}}
|
||||
|
||||
task.contacts.started: |-
|
||||
ਸੰਪਰਕ ਖਿੱਚੇ ਜਾ ਰਹੇ ਹਨ...
|
||||
|
||||
task.contacts.progress: |-
|
||||
ਸੰਪਰਕ ਖਿੱਚਣਾ: ✅ {{progress.ok}}, ⚪ {{progress.skipped}}, ⚠️ {{progress.errors}}
|
||||
|
||||
app.help.reminder: |-
|
||||
ਉਪਲਬਧ ਕਮਾਂਡਾਂ ਦੀ ਸੂਚੀ ਪ੍ਰਾਪਤ ਕਰਨ ਲਈ **help** ਭੇਜੋ।
|
||||
@@ -138,17 +285,17 @@ app.session.current.status: |-
|
||||
📱 **{{{name}}} ({{{id}}})**
|
||||
|
||||
app.session.status.working: |-
|
||||
✅ ਇਹ ChatWoot ਇਨਬਾਕਸ **ਵਟਸਐਪ ਖਾਤੇ** ਨਾਲ ਜੁੜਿਆ ਹੋਇਆ ਹੈ ਅਤੇ ਸੁਨੇਹੇ ਭੇਜਣ ਅਤੇ ਪ੍ਰਾਪਤ ਕਰਨ ਲਈ ਤਿਆਰ ਹੈ!
|
||||
✅ ਇਹ ਇਨਬਾਕਸ ਤੁਹਾਡੇ **ਵਟਸਐਪ ਖਾਤੇ** ਨਾਲ ਜੁੜਿਆ ਹੋਇਆ ਹੈ ਅਤੇ ਸੁਨੇਹੇ ਭੇਜਣ ਅਤੇ ਪ੍ਰਾਪਤ ਕਰਨ ਲਈ ਤਿਆਰ ਹੈ!
|
||||
|
||||
📱 **{{{name}}} ({{{id}}})**
|
||||
|
||||
app.session.status.error: |-
|
||||
⚠️ **ਇਹ ChatWoot ਇਨਬਾਕਸ ਵਟਸਐਪ ਖਾਤੇ ਨਾਲ ਜੁੜਿਆ ਹੋਇਆ ਹੈ ਪਰ ਸੁਨੇਹੇ ਭੇਜਣ ਅਤੇ ਪ੍ਰਾਪਤ ਕਰਨ ਲਈ ਤਿਆਰ ਨਹੀਂ ਹੈ!**
|
||||
⚠️ **ਇਹ ਇਨਬਾਕਸ ਵਟਸਐਪ ਖਾਤੇ ਨਾਲ ਜੁੜਿਆ ਹੋਇਆ ਹੈ ਪਰ ਸੁਨੇਹੇ ਭੇਜਣ ਅਤੇ ਪ੍ਰਾਪਤ ਕਰਨ ਲਈ ਤਿਆਰ ਨਹੀਂ ਹੈ!**
|
||||
|
||||
ਕਿਰਪਾ ਕਰਕੇ ਸੈਸ਼ਨ ਸਥਿਤੀ ਦੀ ਜਾਂਚ ਕਰੋ ਅਤੇ ਇਸਨੂੰ ਮੁੜ ਚਾਲੂ ਕਰਨ ਦੀ ਕੋਸ਼ਿਸ਼ ਕਰੋ ਜਾਂ ਲੌਗਆਉਟ ਕਰੋ ਅਤੇ QR ਕੋਡ ਦੁਬਾਰਾ ਸਕੈਨ ਕਰੋ।
|
||||
|
||||
app.session.scan.qr.code: |-
|
||||
⚠️ **ਵਟਸਐਪ ਖਾਤੇ ਨੂੰ ਇਸ ChatWoot ਇਨਬਾਕਸ ਨਾਲ ਜੋੜਨ ਲਈ ਆਪਣੇ ਫੋਨ 'ਤੇ QR ਕੋਡ ਸਕੈਨ ਕਰੋ!**
|
||||
⚠️ **ਵਟਸਐਪ ਖਾਤੇ ਨੂੰ ਇਸ ਇਨਬਾਕਸ ਨਾਲ ਜੋੜਨ ਲਈ ਆਪਣੇ ਫੋਨ 'ਤੇ QR ਕੋਡ ਸਕੈਨ ਕਰੋ!**
|
||||
|
||||
app.server.version.and.status: |-
|
||||
**ਵਰਜਨ:**
|
||||
@@ -216,7 +363,19 @@ whatsapp.to.chatwoot.message.location: |-
|
||||
{{#locationMessage}}
|
||||
📍 **ਸਾਂਝੀ ਸਥਿਤੀ**
|
||||
|
||||
https://www.google.com/maps?q={{degreesLatitude}},{{degreesLongitude}}
|
||||
{{#name}}
|
||||
**{{name}}**
|
||||
{{/name}}
|
||||
|
||||
{{#address}}
|
||||
📍 **ਪਤਾ:** {{address}}
|
||||
{{/address}}
|
||||
|
||||
{{#url}}
|
||||
🌐 **ਵੈਬਸਾਈਟ:** {{url}}
|
||||
{{/url}}
|
||||
|
||||
🔗 **ਨਕਸ਼ੇ 'ਤੇ ਵੇਖੋ:** https://www.google.com/maps?q={{degreesLatitude}},{{degreesLongitude}}
|
||||
{{#caption}}{{caption}}{{/caption}}
|
||||
{{/locationMessage}}
|
||||
|
||||
@@ -224,13 +383,282 @@ whatsapp.to.chatwoot.message.location: |-
|
||||
📍📡 **ਲਾਈਵ ਸਥਿਤੀ ਸਾਂਝੀ ਕੀਤੀ ਜਾ ਰਹੀ ਹੈ**
|
||||
ਰੀਅਲ-ਟਾਈਮ ਅੱਪਡੇਟ ਦੇਖਣ ਲਈ **WhatsApp** ਖੋਲ੍ਹੋ।
|
||||
|
||||
https://www.google.com/maps?q={{degreesLatitude}},{{degreesLongitude}}
|
||||
{{#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.album: |-
|
||||
📷 **ਐਲਬਮ ਸੁਨੇਹਾ ਪ੍ਰਾਪਤ ਹੋਇਆ**
|
||||
|
||||
{{#totalCount}}
|
||||
**{{totalCount}}** ਮੀਡੀਆ ਫ਼ਾਈਲ(ਾਂ) ਵਾਲਾ ਇੱਕ ਐਲਬਮ ਪ੍ਰਾਪਤ ਹੋਇਆ।
|
||||
{{#expectedImageCount}}
|
||||
📸 ਤਸਵੀਰਾਂ: **{{expectedImageCount}}**
|
||||
{{/expectedImageCount}}
|
||||
{{#expectedVideoCount}}
|
||||
🎥 ਵੀਡੀਓ: **{{expectedVideoCount}}**
|
||||
{{/expectedVideoCount}}
|
||||
|
||||
ਮੀਡੀਆ ਫ਼ਾਈਲਾਂ ਅਗਲੇ ਸੁਨੇਹਿਆਂ ਵਿੱਚ ਵਿਤਰਿਤ ਕੀਤੀਆਂ ਜਾਣਗੀਆਂ।
|
||||
{{/totalCount}}
|
||||
|
||||
whatsapp.to.chatwoot.message.facebook.ad: |-
|
||||
📢 **ਫੇਸਬੁੱਕ ਵਿਗਿਆਪਨ**
|
||||
|
||||
**ਸਿਰਲੇਖ:** {{{adData.title}}}
|
||||
|
||||
**ਵੇਰਵਾ:**
|
||||
{{{adData.body}}}
|
||||
|
||||
**ਵਿਗਿਆਪਨ ਲਿੰਕ:** [ਫੇਸਬੁੱਕ 'ਤੇ ਵੇਖੋ]({{{adData.sourceUrl}}})
|
||||
**ਵਿਗਿਆਪਨ ਆਈਡੀ:** {{{adData.sourceId}}}
|
||||
|
||||
---
|
||||
💬 **ਗਾਹਕ ਦਾ ਸੁਨੇਹਾ:**
|
||||
{{{payload.body}}}
|
||||
whatsapp.history.message.wrapper: |-
|
||||
{{{content}}}
|
||||
`{{#history}}📥{{/history}}🗓️ {{{timestamp}}}`
|
||||
|
||||
UNKNOWN: ਅਣਜਾਣ
|
||||
ERROR: ਗਲਤੀ
|
||||
PENDING: ਭੇਜਿਆ ਜਾ ਰਿਹਾ ਹੈ
|
||||
SERVER: ਸਰਵਰ
|
||||
DEVICE: ਜੰਤਰ
|
||||
READ: ਪੜ੍ਹਿਆ ਗਿਆ
|
||||
PLAYED: ਚਲਾਇਆ ਗਿਆ
|
||||
whatsapp.to.chatwoot.message.has.media.no.media: |-
|
||||
{{#content}}
|
||||
{{{content}}}
|
||||
|
||||
{{/content}}
|
||||
🖼️⚠️ **ਸੁਨੇਹੇ ਵਿੱਚ ਮੀਡੀਆ ਹੈ ਪਰ ਅਸੀਂ ਇਸਨੂੰ ਡਾਊਨਲੋਡ ਨਹੀਂ ਕਰ ਸਕੇ**
|
||||
📱 ਕਿਰਪਾ ਕਰਕੇ ਵੇਖਣ ਲਈ **WhatsApp** ਖੋਲ੍ਹੋ।
|
||||
|
||||
{{^content}}
|
||||
ਵੇਰਵੇ: [{{{details.text}}}]({{{details.url}}})
|
||||
{{/content}}
|
||||
cli.cmd.root.sub.queue: '🧩 **ਕਤਾਰ**:'
|
||||
cli.cmd.messages.summary: WhatsApp ਸੁਨੇਹੇ ਖਿੱਚੋ।
|
||||
cli.cmd.messages.description:
|
||||
💬 ਸੁਨੇਹੇ ਸਿੰਕ ਕਰਨ ਲਈ `messages pull` ਚਲਾਓ ਜਾਂ ਪ੍ਰਗਤੀ ਦੇਖਣ ਲਈ `messages
|
||||
status` ਚਲਾਓ।
|
||||
cli.cmd.messages.action.description:
|
||||
ਸੁਨੇਹੇ ਖਿੱਚਣ ਲਈ `pull` ਜਾਂ ਸਕ੍ਰਿਆ ਕੰਮ ਦੇਖਣ ਲਈ `status` ਵਰਤੋਂ।
|
||||
cli.cmd.messages.pull.option.chat:
|
||||
ਖਿੱਚਣ ਨੂੰ ਕਿਸੇ ਖਾਸ WhatsApp ਚੈਟ ID ਤੱਕ ਸੀਮਿਤ ਕਰੋ।
|
||||
cli.cmd.messages.pull.option.force:
|
||||
ਜੇ WhatsApp ਸੁਨੇਹਾ ਪਹਿਲਾਂ ਹੀ ਗੱਲਬਾਤ ਦੇ ਮੈਪ ਵਿੱਚ ਹੈ ਤਾਂ ਵੀ ਇਨਬਾਕਸ ਨੂੰ ਸੁਨੇਹੇ
|
||||
ਬਣਾਉਣ ਲਈ ਮਜਬੂਰ ਕਰੋ।
|
||||
cli.cmd.messages.pull.option.batch:
|
||||
ਹਰ ਬੈਚ ਵਿੱਚ ਖਿੱਚੇ ਜਾਣ ਵਾਲੇ ਸੁਨੇਹਿਆਂ ਦੀ ਗਿਣਤੀ।
|
||||
cli.cmd.messages.pull.option.progress:
|
||||
ਹਰ N ਸੁਨੇਹਿਆਂ ’ਤੇ ਪ੍ਰਗਤੀ ਦੀ ਰਿਪੋਰਟ ਕਰੋ। ਰਿਪੋਰਟਾਂ ਬੰਦ ਕਰਨ ਲਈ 0 ਸੈੱਟ ਕਰੋ।
|
||||
cli.cmd.messages.pull.option.no-dm: ਇਤਿਹਾਸ ਖਿੱਚਦੇ ਸਮੇਂ ਡਾਇਰੈਕਟ ਸੁਨੇਹੇ ਛੱਡੋ।
|
||||
cli.cmd.messages.pull.option.groups:
|
||||
ਇਤਿਹਾਸ ਖਿੱਚਦੇ ਸਮੇਂ WhatsApp ਗਰੁੱਪ ਸ਼ਾਮਲ ਕਰੋ।
|
||||
cli.cmd.messages.pull.option.channels:
|
||||
ਇਤਿਹਾਸ ਖਿੱਚਦੇ ਸਮੇਂ WhatsApp ਚੈਨਲ ਸ਼ਾਮਲ ਕਰੋ।
|
||||
cli.cmd.messages.pull.option.status:
|
||||
ਇਤਿਹਾਸ ਖਿੱਚਦੇ ਸਮੇਂ WhatsApp ਸਟੇਟਸ ਅੱਪਡੇਟ ਸ਼ਾਮਲ ਕਰੋ।
|
||||
cli.cmd.messages.pull.option.broadcast:
|
||||
ਇਤਿਹਾਸ ਖਿੱਚਦੇ ਸਮੇਂ ਪ੍ਰਸਾਰਣ ਸੂਚੀਆਂ ਸ਼ਾਮਲ ਕਰੋ।
|
||||
cli.cmd.messages.pull.option.media:
|
||||
ਇਤਿਹਾਸ ਖਿੱਚਦੇ ਸਮੇਂ ਮੀਡੀਆ (ਤਸਵੀਰਾਂ, ਵੀਡੀਓ, ਫਾਈਲਾਂ) ਡਾਊਨਲੋਡ ਕਰੋ।
|
||||
cli.cmd.messages.pull.option.pause:
|
||||
ਸੁਨੇਹੇ ਖਿੱਚਣ ਦਾ ਕੰਮ ਠਹਿਰੇ ਹੋਏ ਹਾਲਤ ਵਿੱਚ ਕਤਾਰ ਵਿੱਚ ਪਾਓ ਅਤੇ ਮੁਕੰਮਲ ਹੋਣ ਤੱਕ ਨਵੇਂ
|
||||
WhatsApp ਸੁਨੇਹਿਆਂ ਦੀ ਪ੍ਰਕਿਰਿਆ ਰੋਕੋ।
|
||||
cli.cmd.messages.pull.option.resolve-conversations:
|
||||
ਸਾਰੇ ਇਤਿਹਾਸਿਕ ਸੁਨੇਹੇ ਪ੍ਰਕਿਰਿਆ ਕਰਨ ਤੋਂ ਬਾਅਦ ਗੱਲਬਾਤਾਂ ਨੂੰ ਹੱਲ ਹੋਇਆ ਚਿੰਨ੍ਹਤ ਕਰਦਾ
|
||||
ਹੈ।
|
||||
cli.cmd.messages.pull.error.pause-no-queue:
|
||||
⛔ `--pause` ਵਰਤ ਨਹੀਂ ਸਕਦੇ ਕਿਉਂਕਿ ਇਸ ਸੰਰਚਨਾ ਵਿੱਚ **queue** ਹੁਕਮ ਅਸਮਰੱਥ ਹੈ।
|
||||
cli.cmd.queue.description: ਕਤਾਰਾਂ ਦਾ ਪ੍ਰਬੰਧਨ ਕਰੋ।
|
||||
cli.cmd.queue.summary: ਕਤਾਰਾਂ ’ਤੇ ਕਾਰਵਾਈਆਂ ਕਰੋ।
|
||||
cli.cmd.queue.action.description: ਇੱਕ ਕਾਰਵਾਈ ਚੁਣੋ।
|
||||
cli.cmd.queue.status.description: ਕਤਾਰ ਦੀ ਸਥਿਤੀ ਦਿਖਾਓ।
|
||||
cli.cmd.queue.argument.name: 'ਕਤਾਰ ਦਾ ਨਾਮ: `inbox` ਜਾਂ `whatsapp` (ਵਿਕਲਪਿਕ)।'
|
||||
cli.cmd.queue.start.description: ਨਾਂ ਦੁਆਰਾ ਕਤਾਰ ਸ਼ੁਰੂ ਕਰੋ।
|
||||
cli.cmd.queue.stop.description: ਨਾਂ ਦੁਆਰਾ ਕਤਾਰ ਰੋਕੋ।
|
||||
cli.cmd.queue.paused:
|
||||
⚪🧩 ਕਤਾਰਾਂ ਨੂੰ ਰੋਕ ਦਿੱਤਾ ਗਿਆ ਹੈ। ਸਥਿਤੀ ਜਾਂਚਣ ਲਈ `queue status` ਭੇਜੋ।
|
||||
cli.cmd.queue.resumed:
|
||||
🟢🧩 ਕਤਾਰਾਂ ਮੁੜ ਚਾਲੂ ਹੋ ਚੁੱਕੀਆਂ ਹਨ। ਸਥਿਤੀ ਜਾਂਚਣ ਲਈ `queue status` ਭੇਜੋ।
|
||||
cli.cmd.queue.status.result: >-
|
||||
📊 ਕਤਾਰ ਦੀ ਸਥਿਤੀ:
|
||||
|
||||
{{#queues}}
|
||||
|
||||
- **{{name}}**: {{#locked}}🔒{{/locked}}{{#paused}}⚪ ਰੋਕੀ
|
||||
ਗਈ{{/paused}}{{^paused}}🟢 ਚੱਲ ਰਹੀ{{/paused}}
|
||||
|
||||
{{/queues}}
|
||||
cli.cmd.messages.pull.option.timeout-media:
|
||||
ਹਰ ਸੁਨੇਹੇ ਲਈ ਮੀਡੀਆ ਡਾਊਨਲੋਡ ਕਰਨ ਦਾ ਸਮਾਂ ਨਿਰਧਾਰਤ ਕਰੋ।
|
||||
cli.cmd.messages.pull.argument.end:
|
||||
ਖਿੱਚਣ ਦੀ ਵਿੰਡੋ ਕਿੱਥੇ ਖਤਮ ਹੁੰਦੀ ਹੈ ਨਿਰਧਾਰਤ ਕਰੋ; ਜਿਵੇਂ `2d` ਦੋ ਦਿਨ ਪਹਿਲਾਂ ਰੁਕਦਾ
|
||||
ਹੈ।
|
||||
cli.cmd.messages.pull.argument.start:
|
||||
ਖਿੱਚਣ ਦੀ ਵਿੰਡੋ ਕਿੱਥੋਂ ਸ਼ੁਰੂ ਹੁੰਦੀ ਹੈ ਨਿਰਧਾਰਤ ਕਰੋ; ਜਿਵੇਂ `1d` ਇੱਕ ਦਿਨ ਪਹਿਲਾਂ
|
||||
ਤੋਂ ਸ਼ੁਰੂ ਹੁੰਦਾ ਹੈ (ਡਿਫ਼ਾਲਟ `0d`, ਅਰਥਾਤ ਹੁਣ)।
|
||||
cli.cmd.messages.pull.already-running: >-
|
||||
⏳ **Pull Messages** ਕੰਮ ਪਹਿਲਾਂ ਹੀ **{{{chat}}}** ਲਈ ਚੱਲ ਰਿਹਾ ਹੈ। ਨਵਾਂ ਸ਼ੁਰੂ
|
||||
ਕਰਨ ਤੋਂ ਪਹਿਲਾਂ ਇਸਦੇ ਮੁਕੰਮਲ ਹੋਣ ਦੀ ਉਡੀਕ ਕਰੋ ਜਾਂ ਇਸਨੂੰ ਰੋਕੋ।
|
||||
|
||||
|
||||
ਵੇਰਵੇ ਲਈ `messages status` ਭੇਜੋ।
|
||||
cli.cmd.messages.status.not-found:
|
||||
ℹ️ ਕੋਈ ਸਰਗਰਮ **Pull Messages** ਕੰਮ ਨਹੀਂ ਮਿਲਿਆ। `messages pull` ਨਾਲ ਇੱਕ ਸ਼ੁਰੂ
|
||||
ਕਰੋ।
|
||||
cli.cmd.messages.pull.no-chats-found: ਇਸ ਅਵਧੀ ਲਈ ਕੋਈ WhatsApp ਚੈਟ ਨਹੀਂ ਮਿਲੀ।
|
||||
cli.cmd.contacts.pull.option.progress:
|
||||
ਹਰ N ਸੰਪਰਕ ’ਤੇ ਪ੍ਰਗਤੀ ਦੱਸੋ। ਰਿਪੋਰਟਾਂ ਬੰਦ ਕਰਨ ਲਈ 0 ਸੈੱਟ ਕਰੋ।
|
||||
cli.cmd.contacts.pull.option.lids:
|
||||
ਸਿੰਕ ਵਿੱਚ WhatsApp LID (ਅਣਪਛਾਤੇ) ਸੰਪਰਕ ਸ਼ਾਮਲ ਕਰੋ।
|
||||
task.contacts.completed:
|
||||
👤✅ ਸੰਪਰਕ ਮੁਕੰਮਲ → 🆕 {{progress.created}}, 🔄 {{progress.updated}}, ⚪
|
||||
{{progress.skipped}}, 🔴 {{progress.errors}}, 🖼️ {{progress.avatar.updated}}
|
||||
task.messages.started:
|
||||
⏳ ਸੁਨੇਹੇ ਖਿੱਚੇ ਜਾ ਰਹੇ ਹਨ 💬 {{{chat}}} ⏱️ {{{period}}}...
|
||||
task.messages.details: ਵਧੇਰੇ ਵੇਰਵਿਆਂ ਲਈ "{{{prefix}}}messages status" ਭੇਜੋ।
|
||||
task.messages.progress:
|
||||
'⏳ ਸੁਨੇਹੇ {{{chat}}}: 🆕 {{progress.ok}}, 🟢 {{progress.exists}}, ⚪
|
||||
{{progress.ignored}}, 🔴 {{progress.errors}}{{#messages.last}} | 📅
|
||||
{{{messages.last}}}{{/messages.last}}'
|
||||
task.messages.completed:
|
||||
✅ ਸੁਨੇਹੇ {{{chat}}} ਵਿਚ {{{period}}} → 🆕 {{progress.ok}}, 🟢
|
||||
{{progress.exists}}, ⚪ {{progress.ignored}}, 🔴 {{progress.errors}}
|
||||
task.messages.status: |-
|
||||
**{{{chat}}}** ਦੇ ਸੁਨੇਹੇ ⏱️ **{{{period}}}** ਸਮੇਂ ਵਿੱਚ। ⚙️ ਕੰਮ ਦੀ ਸਥਿਤੀ: **{{state}}**।
|
||||
|
||||
🗨️ ਸੁਨੇਹੇ:
|
||||
- 🆕 ਬਣਾਇਆ: **{{{progress.ok}}}**
|
||||
- 🟢 ਪਹਿਲਾਂ ਤੋਂ ਮੌਜੂਦ: {{{progress.exists}}}
|
||||
- ⚪ ਅਣਦੇਖੇ: {{{progress.ignored}}}
|
||||
- 🔴 ਗਲਤੀਆਂ: {{{progress.errors}}}
|
||||
{{#messages.first}}
|
||||
- 🗓️ ਸਭ ਤੋਂ ਪੁਰਾਣਾ: **{{{messages.first}}}**
|
||||
{{/messages.first}}
|
||||
{{#messages.last}}
|
||||
- 🗓️ ਸਭ ਤੋਂ ਨਵਾਂ: **{{{messages.last}}}**
|
||||
{{/messages.last}}
|
||||
|
||||
💬 WhatsApp ਚੈਟ:
|
||||
- 🆕 **{{{chats}}}** ਚੈਟਾਂ ਵਿੱਚ ਨਵੇਂ ਸੁਨੇਹੇ
|
||||
{{#options.resolveConversations}}
|
||||
|
||||
🗣️ ਗੱਲਬਾਤਾਂ:
|
||||
- 🟢 ਹੱਲ ਹੋਈਆਂ: **{{{progress.conversationResolve.success}}}**
|
||||
{{#progress.conversationResolve.error}}
|
||||
- 🔴 ਗਲਤੀਆਂ: **{{{progress.conversationResolve.error}}}**
|
||||
{{/progress.conversationResolve.error}}
|
||||
{{/options.resolveConversations}}
|
||||
|
||||
ਵੇਰਵੇ:
|
||||
[{{{details.text}}}]({{{details.url}}})
|
||||
@@ -1,8 +1,31 @@
|
||||
# Portuguese translations for ChatWoot integration
|
||||
# Portuguese translations for WhatsApp inbox integration
|
||||
locale.name: '🇧🇷🇵🇹 Português (Portuguese)'
|
||||
app.inbox.contact.name: 'Integração WhatsApp (WAHA)'
|
||||
|
||||
# Código de locale base - deve ser um locale BCP 47 válido
|
||||
# https://en.wikipedia.org/wiki/IETF_language_tag
|
||||
# A WAHA usa isso para formatar números, moeda e data/hora
|
||||
locale.base: 'pt-BR'
|
||||
|
||||
app.inbox.contact.name: 'Integração WhatsApp'
|
||||
app.inbox.contact.avatar.url: 'https://raw.githubusercontent.com/devlikeapro/waha/core/logo.png'
|
||||
|
||||
# "opções" de date.toLocaleString
|
||||
# https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat#options
|
||||
datetime:
|
||||
weekday: short
|
||||
locales: pt-BR
|
||||
day: 2-digit
|
||||
month: short
|
||||
year: numeric
|
||||
hour: 2-digit
|
||||
minute: 2-digit
|
||||
hour12: false
|
||||
# https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat#timezonename
|
||||
timeZoneName: short
|
||||
|
||||
Today: Hoje
|
||||
Yesterday: Ontem
|
||||
|
||||
job.report.error: |-
|
||||
⚠️ **{{{header}}}**
|
||||
{{#attempts.nextDelay}}
|
||||
@@ -34,14 +57,14 @@ whatsapp.message.removing.error:
|
||||
job.scheduled.error.header: Falha ao executar tarefa agendada
|
||||
|
||||
message.from.whatsapp: |-
|
||||
{{{text}}}
|
||||
*📱 Enviado do WhatsApp*
|
||||
|
||||
📱 *Enviado do WhatsApp*
|
||||
{{{text}}}
|
||||
|
||||
message.from.api: |-
|
||||
{{{text}}}
|
||||
*📱 Enviado via API*
|
||||
|
||||
📱 *Enviado via API*
|
||||
{{{text}}}
|
||||
|
||||
message.removed.in.whatsapp: |-
|
||||
❌ *Esta mensagem foi excluída no WhatsApp*
|
||||
@@ -69,11 +92,11 @@ whatsapp.contact.broadcast.suffix: 'Lista de transmissão'
|
||||
whatsapp.contact.status.name: 'Status'
|
||||
|
||||
app.connected.message: |-
|
||||
🔗 **CONECTADO: Sessão do WhatsApp para Caixa de Entrada do ChatWoot**
|
||||
🔗 **CONECTADO: Sessão do WhatsApp para a caixa de entrada**
|
||||
|
||||
✅ A sessão do WhatsApp **{{{name}}}** foi conectada com sucesso a esta Caixa de Entrada do ChatWoot.
|
||||
✅ A sessão do WhatsApp **{{{name}}}** foi conectada com sucesso a esta caixa de entrada.
|
||||
|
||||
Esta caixa de entrada agora está vinculada à sua **Conta do WhatsApp**. Você pode enviar e receber mensagens diretamente daqui — assim como usar o WhatsApp, mas dentro do ChatWoot.
|
||||
Esta caixa de entrada agora está vinculada à sua **Conta do WhatsApp**. Você pode enviar e receber mensagens diretamente daqui — assim como usar o WhatsApp, mas dentro do seu espaço de trabalho.
|
||||
|
||||
Você também pode gerenciar a sessão do WhatsApp vinculada a esta caixa de entrada. Para ver as ações disponíveis, envie **help** para este chat.
|
||||
|
||||
@@ -87,9 +110,9 @@ app.connected.message: |-
|
||||
Vamos começar! 🚀
|
||||
|
||||
app.disconnected.message: |-
|
||||
🔌 **DESCONECTADO: Sessão do WhatsApp da Caixa de Entrada do ChatWoot**
|
||||
🔌 **DESCONECTADO: Sessão do WhatsApp da caixa de entrada**
|
||||
|
||||
❌ A sessão do WhatsApp **{{{name}}}** foi desconectada desta Caixa de Entrada do ChatWoot.
|
||||
❌ A sessão do WhatsApp **{{{name}}}** foi desconectada desta caixa de entrada.
|
||||
|
||||
Você não poderá mais enviar ou receber mensagens do WhatsApp.
|
||||
|
||||
@@ -100,34 +123,156 @@ app.updated.message: |-
|
||||
|
||||
✅ As configurações para a sessão do WhatsApp **{{{name}}}** foram atualizadas.
|
||||
|
||||
app.commands.list: |-
|
||||
Você pode gerenciar a **Integração WhatsApp** enviando um dos seguintes comandos:
|
||||
cli.usage.arguments: |-
|
||||
[ação]
|
||||
|
||||
🖥️ **Sessão**:
|
||||
1. **status** - obter o status atual da sessão.
|
||||
2. **restart** - reiniciar a sessão - isso tentará reconectar sua conta do WhatsApp.
|
||||
3. **start** - iniciar a sessão.
|
||||
4. **stop** - parar a sessão.
|
||||
5. **logout** - sair da sessão, para que você possa escanear o código QR e reparar a sessão.
|
||||
6. **qr** - obter o código QR para a sessão.
|
||||
7. **screenshot** - obter uma captura de tela da sessão (disponível apenas no motor **WEBJS**).
|
||||
8. **help** - comandos disponíveis.
|
||||
cli.usage.command: |-
|
||||
[comando]
|
||||
|
||||
{{#commands.server}}
|
||||
⚙️ Servidor:
|
||||
- **server status** - obter informações sobre a versão e status do servidor.
|
||||
- **server reboot** - reiniciar o servidor suavemente.
|
||||
- **server reboot force** - forçar a reinicialização do servidor.
|
||||
{{/commands.server}}
|
||||
cli.usage.options: |-
|
||||
[opções]
|
||||
|
||||
Envie um **comando** (não sensível a maiúsculas/minúsculas) ou um **número de comando** para esta conversa.
|
||||
cli.cmd.disabled: ⛔ **O comando `{{command}}` está desativado**.
|
||||
|
||||
app.commands.server.disabled: ⛔ **Os comandos do servidor estão desativados**.
|
||||
|
||||
Por favor, contate o seu administrador.
|
||||
Entre em contato com seu administrador.
|
||||
|
||||
Envie **help** para obter a lista de comandos disponíveis.
|
||||
|
||||
cli.cmd.root.description: |-
|
||||
Gerencie sua **Integração com WhatsApp**!
|
||||
|
||||
cli.cmd.root.sub.session: |-
|
||||
🖥️ **Sessão**:
|
||||
|
||||
cli.cmd.root.sub.server: |-
|
||||
🖥️ **Servidor**:
|
||||
|
||||
cli.cmd.root.sub.sync: |-
|
||||
🔄️ **Sincronização**:
|
||||
|
||||
cli.cmd.session.status.description: |-
|
||||
Obtenha o status atual da sessão.
|
||||
|
||||
cli.cmd.session.restart.description: |-
|
||||
Reinicie a sessão.
|
||||
|
||||
cli.cmd.session.start.description: |-
|
||||
Inicie a sessão.
|
||||
|
||||
cli.cmd.session.stop.description: |-
|
||||
Pare a sessão.
|
||||
|
||||
cli.cmd.session.logout.description: |-
|
||||
Saia da sessão para escanear um novo código QR e restaurar a conexão.
|
||||
|
||||
cli.cmd.session.qr.description: |-
|
||||
Mostre o código QR da sessão.
|
||||
|
||||
cli.cmd.session.screenshot.description: |-
|
||||
Faça uma captura de tela da sessão (disponível apenas com o mecanismo **WEBJS**).
|
||||
|
||||
cli.cmd.contacts.summary: |-
|
||||
Buscar contatos do WhatsApp.
|
||||
|
||||
cli.cmd.contacts.description: |-
|
||||
👤 Execute `contacts pull` para iniciar uma sincronização ou `contacts status` para acompanhar o progresso.
|
||||
|
||||
cli.cmd.contacts.action.description: |-
|
||||
Use `start` para iniciar a busca de contatos ou `status` para conferir o trabalho ativo.
|
||||
|
||||
cli.cmd.contacts.pull.description: |-
|
||||
Comece a importar contatos do WhatsApp para esta caixa de entrada.
|
||||
|
||||
cli.cmd.contacts.pull.option.batch: |-
|
||||
Quantidade de contatos buscados em cada lote.
|
||||
|
||||
cli.cmd.contacts.pull.option.delay-contact: |-
|
||||
Atraso entre cada contato para evitar limites ao buscar avatares.
|
||||
|
||||
cli.cmd.contacts.pull.option.delay-batch: |-
|
||||
Atraso entre os lotes de contatos.
|
||||
|
||||
cli.cmd.contacts.pull.option.avatar: |-
|
||||
Escolha como buscar os avatares dos contatos: `skip`, `if-missing` ou `update`.
|
||||
|
||||
cli.cmd.contacts.pull.option.groups: |-
|
||||
Incluir contatos de grupo do WhatsApp na sincronização.
|
||||
|
||||
cli.cmd.contacts.pull.option.no-attributes: |-
|
||||
Não busque atributos do contato se ele já existir (acelera o processo).
|
||||
|
||||
cli.cmd.contacts.pull.already-running: |-
|
||||
⏳ O trabalho **Pull Contacts** já está em execução. Aguarde terminar ou interrompa-o antes de iniciar um novo.
|
||||
|
||||
Envie `contacts status` para obter detalhes.
|
||||
|
||||
cli.cmd.options.job.attempts: |-
|
||||
Número de tentativas antes de marcar o trabalho como falho.
|
||||
|
||||
cli.cmd.options.job.timeout: |-
|
||||
Tempo máximo permitido antes de tentar novamente (aceita valores como `30s`, `5m`, `1h`).
|
||||
|
||||
cli.cmd.contacts.status.description: |-
|
||||
Mostrar o status do trabalho de busca atual.
|
||||
|
||||
cli.cmd.contacts.status.not-found: |-
|
||||
ℹ️ Nenhum trabalho ativo **Pull Contacts** encontrado. Inicie um com `contacts pull`.
|
||||
|
||||
cli.cmd.server.description: |-
|
||||
Gerencie o servidor.
|
||||
|
||||
cli.cmd.server.status.description: |-
|
||||
Mostre a versão e o status do servidor.
|
||||
|
||||
cli.cmd.server.reboot.description: |-
|
||||
Reinicie o servidor.
|
||||
|
||||
cli.cmd.server.reboot.option.force: |-
|
||||
Forçar uma reinicialização imediata.
|
||||
|
||||
cli.help.usage.title: |-
|
||||
Uso: envie um comando para a conversa no formato:
|
||||
|
||||
cli.help.commands.defaultGroup: |-
|
||||
**Comandos:**
|
||||
|
||||
cli.help.arguments.title: |-
|
||||
**Argumentos:**
|
||||
|
||||
cli.help.options.defaultGroup: |-
|
||||
**Opções:**
|
||||
|
||||
cli.help.globalOptions.title: |-
|
||||
**Opções globais:**
|
||||
|
||||
task.contacts.status: |-
|
||||
👤 Busca de contatos: **{{state}}**
|
||||
- ✅ Sucesso: {{{progress.ok}}} contatos
|
||||
- ⚪ Ignorados: {{{progress.skipped}}} contatos
|
||||
- ⚠️ Erros: {{{progress.errors}}} contatos
|
||||
|
||||
{{#job}}
|
||||
⌛ Linha do tempo:
|
||||
- Na fila: {{{timestamp}}}
|
||||
{{#processedOn}}
|
||||
- Início: {{{processedOn}}}
|
||||
{{/processedOn}}
|
||||
{{#finishedOn}}
|
||||
- Fim: {{{finishedOn}}}
|
||||
{{/finishedOn}}
|
||||
{{/job}}
|
||||
|
||||
{{#error}}
|
||||
Detalhes:
|
||||
[{{{details.text}}}]({{{details.url}}})
|
||||
{{/error}}
|
||||
|
||||
task.contacts.started: |-
|
||||
Buscando contatos...
|
||||
|
||||
task.contacts.progress: |-
|
||||
Busca de contatos: ✅ {{progress.ok}}, ⚪ {{progress.skipped}}, ⚠️ {{progress.errors}}
|
||||
|
||||
app.help.reminder: |-
|
||||
Envie **help** para obter a lista de comandos disponíveis.
|
||||
|
||||
@@ -141,17 +286,17 @@ app.session.current.status: |-
|
||||
📱 **{{{name}}} ({{{id}}})**
|
||||
|
||||
app.session.status.working: |-
|
||||
✅ Esta Caixa de Entrada do ChatWoot está vinculada à **Conta do WhatsApp** e pronta para enviar e receber mensagens!
|
||||
✅ Esta caixa de entrada está vinculada à sua **Conta do WhatsApp** e pronta para enviar e receber mensagens!
|
||||
|
||||
📱 **{{{name}}} ({{{id}}})**
|
||||
|
||||
app.session.status.error: |-
|
||||
⚠️ **Esta Caixa de Entrada do ChatWoot está vinculada à Conta do WhatsApp, mas NÃO está pronta para enviar e receber mensagens!**
|
||||
⚠️ **Esta caixa de entrada está vinculada à Conta do WhatsApp, mas NÃO está pronta para enviar e receber mensagens!**
|
||||
|
||||
Por favor, verifique o status da sessão e tente reiniciá-la ou saia e escaneie o código QR novamente.
|
||||
|
||||
app.session.scan.qr.code: |-
|
||||
⚠️ **Escaneie o código QR no seu telefone para vincular a Conta do WhatsApp a esta Caixa de Entrada do ChatWoot!**
|
||||
⚠️ **Escaneie o código QR no seu telefone para vincular a Conta do WhatsApp a esta caixa de entrada!**
|
||||
|
||||
app.server.version.and.status: |-
|
||||
**Versão:**
|
||||
@@ -219,7 +364,19 @@ whatsapp.to.chatwoot.message.location: |-
|
||||
{{#locationMessage}}
|
||||
📍 **Localização compartilhada**
|
||||
|
||||
https://www.google.com/maps?q={{degreesLatitude}},{{degreesLongitude}}
|
||||
{{#name}}
|
||||
**{{name}}**
|
||||
{{/name}}
|
||||
|
||||
{{#address}}
|
||||
📍 **Endereço:** {{address}}
|
||||
{{/address}}
|
||||
|
||||
{{#url}}
|
||||
🌐 **Site:** {{url}}
|
||||
{{/url}}
|
||||
|
||||
🔗 **Ver no mapa:** https://www.google.com/maps?q={{degreesLatitude}},{{degreesLongitude}}
|
||||
{{#caption}}{{caption}}{{/caption}}
|
||||
{{/locationMessage}}
|
||||
|
||||
@@ -227,13 +384,293 @@ whatsapp.to.chatwoot.message.location: |-
|
||||
📍📡 **Compartilhando localização ao vivo**
|
||||
Abra o **WhatsApp** para ver as atualizações em tempo real.
|
||||
|
||||
https://www.google.com/maps?q={{degreesLatitude}},{{degreesLongitude}}
|
||||
{{#name}}
|
||||
**{{name}}**
|
||||
{{/name}}
|
||||
|
||||
{{#address}}
|
||||
📍 **Endereço:** {{address}}
|
||||
{{/address}}
|
||||
|
||||
{{#url}}
|
||||
🌐 **Site:** {{url}}
|
||||
{{/url}}
|
||||
|
||||
🔗 **Ver no mapa:** https://www.google.com/maps?q={{degreesLatitude}},{{degreesLongitude}}
|
||||
{{#caption}}{{caption}}{{/caption}}
|
||||
{{/liveLocationMessage}}
|
||||
{{/message}}
|
||||
|
||||
whatsapp.to.chatwoot.message.poll: |-
|
||||
{{#poll}}
|
||||
📊 **Enquete**
|
||||
|
||||
**{{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}}
|
||||
📍 **Local:** {{location.name}}
|
||||
{{/location.name}}
|
||||
|
||||
{{#startTime}}
|
||||
🕐 **Início:** {{startTime}}
|
||||
{{/startTime}}
|
||||
|
||||
{{#endTime}}
|
||||
🕐 **Fim:** {{endTime}}
|
||||
{{/endTime}}
|
||||
|
||||
{{#joinLink}}
|
||||
🔗 **Link de participação:** [{{joinLink}}]({{joinLink}})
|
||||
{{/joinLink}}
|
||||
{{/eventMessage}}
|
||||
{{/message}}
|
||||
|
||||
whatsapp.to.chatwoot.message.pix: |-
|
||||
💳 **PIX - Copia e Cola**
|
||||
|
||||
{{#pixData}}
|
||||
{{#merchantName}}
|
||||
**{{merchantName}}**
|
||||
{{/merchantName}}
|
||||
|
||||
{{#key}}
|
||||
**Chave PIX:** {{key}}
|
||||
{{/key}}
|
||||
{{#keyType}}
|
||||
**Tipo:** {{keyType}}
|
||||
{{/keyType}}
|
||||
{{#totalAmount}}
|
||||
**Valor:** {{totalAmountFormatted}}
|
||||
{{/totalAmount}}
|
||||
{{#referenceId}}
|
||||
**Referência:** {{referenceId}}
|
||||
{{/referenceId}}
|
||||
|
||||
💳 **PIX Copia e Cola enviado**
|
||||
{{/pixData}}
|
||||
|
||||
whatsapp.to.chatwoot.message.unsupported: |-
|
||||
⚠️ **Esse tipo de mensagem não é suportado no ChatWoot.**
|
||||
⚠️ **Esse tipo de mensagem não é suportado nesta caixa de entrada.**
|
||||
📱 Por favor, abra o **WhatsApp** para visualizá-la.
|
||||
|
||||
Detalhes: [{{{details.text}}}]({{{details.url}}})
|
||||
|
||||
whatsapp.to.chatwoot.message.album: |-
|
||||
📷 **Mensagem de Álbum Recebida**
|
||||
|
||||
{{#totalCount}}
|
||||
Recebido um álbum com **{{totalCount}}** arquivo(s) de mídia.
|
||||
{{#expectedImageCount}}
|
||||
📸 Imagens: **{{expectedImageCount}}**
|
||||
{{/expectedImageCount}}
|
||||
{{#expectedVideoCount}}
|
||||
🎥 Vídeos: **{{expectedVideoCount}}**
|
||||
{{/expectedVideoCount}}
|
||||
|
||||
Os arquivos de mídia serão entregues nas mensagens seguintes.
|
||||
{{/totalCount}}
|
||||
|
||||
whatsapp.to.chatwoot.message.facebook.ad: |-
|
||||
📢 **ANÚNCIO DO FACEBOOK**
|
||||
|
||||
**Título:** {{{adData.title}}}
|
||||
|
||||
**Descrição:**
|
||||
{{{adData.body}}}
|
||||
|
||||
**Link do Anúncio:** [Ver no Facebook]({{{adData.sourceUrl}}})
|
||||
**ID do Anúncio:** {{{adData.sourceId}}}
|
||||
|
||||
---
|
||||
💬 **MENSAGEM DO CLIENTE:**
|
||||
{{{payload.body}}}
|
||||
whatsapp.history.message.wrapper: |-
|
||||
{{{content}}}
|
||||
`{{#history}}📥{{/history}}🗓️ {{{timestamp}}}`
|
||||
|
||||
UNKNOWN: Desconhecido
|
||||
ERROR: Erro
|
||||
PENDING: Enviando
|
||||
SERVER: Servidor
|
||||
DEVICE: Dispositivo
|
||||
READ: Lido
|
||||
PLAYED: Reproduzido
|
||||
whatsapp.to.chatwoot.message.has.media.no.media: |-
|
||||
{{#content}}
|
||||
{{{content}}}
|
||||
|
||||
{{/content}}
|
||||
🖼️⚠️ **A mensagem tem mídia, mas não conseguimos fazer o download**
|
||||
📱 Abra o **WhatsApp** para visualizá-la.
|
||||
|
||||
{{^content}}
|
||||
Detalhes: [{{{details.text}}}]({{{details.url}}})
|
||||
{{/content}}
|
||||
cli.cmd.root.sub.queue: '🧩 **Fila**:'
|
||||
cli.cmd.messages.summary: Buscar mensagens do WhatsApp.
|
||||
cli.cmd.messages.description:
|
||||
💬 Execute `messages pull` para solicitar a sincronização de mensagens ou
|
||||
`messages status` para acompanhar o progresso.
|
||||
cli.cmd.messages.action.description:
|
||||
'`pull` para iniciar a busca de mensagens ou `status` para inspecionar o
|
||||
trabalho em andamento.'
|
||||
cli.cmd.messages.pull.option.chat:
|
||||
Limita a busca a um ID de conversa do WhatsApp específico.
|
||||
cli.cmd.messages.pull.option.force:
|
||||
Força a caixa de entrada a criar mensagens mesmo quando a mensagem do WhatsApp
|
||||
já existe no mapeamento da conversa.
|
||||
cli.cmd.messages.pull.option.batch:
|
||||
Quantidade de mensagens buscadas em cada lote.
|
||||
cli.cmd.messages.pull.option.progress:
|
||||
Relata o progresso a cada N mensagens. Defina 0 para desativar os relatórios
|
||||
de progresso.
|
||||
cli.cmd.messages.pull.option.no-dm:
|
||||
Ignora mensagens diretas ao buscar o histórico.
|
||||
cli.cmd.messages.pull.option.groups:
|
||||
Inclui conversas em grupo do WhatsApp ao buscar o histórico.
|
||||
cli.cmd.messages.pull.option.channels:
|
||||
Inclui canais do WhatsApp ao buscar o histórico.
|
||||
cli.cmd.messages.pull.option.status:
|
||||
Inclui atualizações de status do WhatsApp ao buscar o histórico.
|
||||
cli.cmd.messages.pull.option.broadcast:
|
||||
Inclui listas de transmissão ao buscar o histórico.
|
||||
cli.cmd.messages.pull.option.media:
|
||||
Baixa anexos de mídia (imagens, vídeos, arquivos) ao buscar o histórico.
|
||||
cli.cmd.messages.pull.option.pause:
|
||||
Enfileira o trabalho de busca de mensagens em estado pausado, interrompendo o
|
||||
processamento de novos WhatsApp até finalizar.
|
||||
cli.cmd.messages.pull.option.resolve-conversations:
|
||||
Marca as conversas como resolvidas depois que todas as mensagens do histórico
|
||||
forem processadas.
|
||||
cli.cmd.messages.pull.error.pause-no-queue:
|
||||
⛔ Não é possível usar `--pause` porque o comando **queue** está desabilitado
|
||||
nesta configuração.
|
||||
cli.cmd.queue.description: Gerencie filas.
|
||||
cli.cmd.queue.summary: Execute ações nas filas.
|
||||
cli.cmd.queue.action.description: Escolha uma ação.
|
||||
cli.cmd.queue.status.description: Mostra o status de uma fila.
|
||||
cli.cmd.queue.argument.name: 'Nome da fila: `inbox` ou `whatsapp` (opcional).'
|
||||
cli.cmd.queue.start.description: Inicia uma fila pelo nome.
|
||||
cli.cmd.queue.stop.description: Para uma fila pelo nome.
|
||||
cli.cmd.queue.paused:
|
||||
⚪🧩 As filas foram pausadas. Envie `queue status` para verificar o status.
|
||||
cli.cmd.queue.resumed:
|
||||
🟢🧩 As filas foram retomadas. Envie `queue status` para verificar o status.
|
||||
cli.cmd.queue.status.result: >-
|
||||
📊 Status da fila:
|
||||
|
||||
{{#queues}}
|
||||
|
||||
- **{{name}}**: {{#locked}}🔒{{/locked}}{{#paused}}⚪ Em
|
||||
pausa{{/paused}}{{^paused}}🟢 Em execução{{/paused}}
|
||||
|
||||
{{/queues}}
|
||||
cli.cmd.messages.pull.option.timeout-media:
|
||||
Define por quanto tempo tentar baixar a mídia de cada mensagem.
|
||||
cli.cmd.messages.pull.argument.end:
|
||||
Define até onde a janela de busca termina; por exemplo, `2d` encerra há dois
|
||||
dias.
|
||||
cli.cmd.messages.pull.argument.start:
|
||||
Define onde a janela de busca começa; por exemplo, `1d` inicia há um dia
|
||||
(padrão `0d`, ou seja, agora).
|
||||
cli.cmd.messages.pull.already-running: >-
|
||||
⏳ O trabalho **Pull Messages** já está em execução para **{{{chat}}}**.
|
||||
Aguarde terminar ou pare antes de iniciar outro.
|
||||
|
||||
|
||||
Envie `messages status` para mais detalhes.
|
||||
cli.cmd.messages.status.not-found:
|
||||
ℹ️ Nenhum trabalho ativo de **Pull Messages** foi encontrado. Inicie um com
|
||||
`messages pull`.
|
||||
cli.cmd.messages.pull.no-chats-found:
|
||||
Nenhuma conversa do WhatsApp encontrada para buscar mensagens nesse período.
|
||||
cli.cmd.contacts.pull.option.progress:
|
||||
Informa o progresso a cada N contatos. Defina 0 para desativar os relatórios
|
||||
de progresso.
|
||||
cli.cmd.contacts.pull.option.lids:
|
||||
Inclui contatos LID do WhatsApp (contatos anônimos) na sincronização.
|
||||
task.contacts.completed:
|
||||
👤✅ Contatos concluídos → 🆕 {{progress.created}}, 🔄 {{progress.updated}},
|
||||
⚪ {{progress.skipped}}, 🔴 {{progress.errors}}, 🖼️
|
||||
{{progress.avatar.updated}}
|
||||
task.messages.started: ⏳ Buscando mensagens 💬 {{{chat}}} ⏱️ {{{period}}}...
|
||||
task.messages.details:
|
||||
Envie "{{{prefix}}}messages status" para ver mais detalhes.
|
||||
task.messages.progress:
|
||||
'⏳ Mensagens {{{chat}}}: 🆕 {{progress.ok}}, 🟢 {{progress.exists}}, ⚪
|
||||
{{progress.ignored}}, 🔴 {{progress.errors}}{{#messages.last}} | 📅
|
||||
{{{messages.last}}}{{/messages.last}}'
|
||||
task.messages.completed:
|
||||
✅ Mensagens {{{chat}}} em {{{period}}} → 🆕 {{progress.ok}}, 🟢
|
||||
{{progress.exists}}, ⚪ {{progress.ignored}}, 🔴 {{progress.errors}}
|
||||
task.messages.status: |-
|
||||
Mensagens **{{{chat}}}** no período ⏱️ **{{{period}}}**. ⚙️ Estado do trabalho: **{{state}}**.
|
||||
|
||||
🗨️ Mensagens:
|
||||
- 🆕 Criadas: **{{{progress.ok}}}**
|
||||
- 🟢 Já existem: {{{progress.exists}}}
|
||||
- ⚪ Ignoradas: {{{progress.ignored}}}
|
||||
- 🔴 Erros: {{{progress.errors}}}
|
||||
{{#messages.first}}
|
||||
- 🗓️ Mais antiga: **{{{messages.first}}}**
|
||||
{{/messages.first}}
|
||||
{{#messages.last}}
|
||||
- 🗓️ Mais recente: **{{{messages.last}}}**
|
||||
{{/messages.last}}
|
||||
|
||||
💬 Conversas do WhatsApp:
|
||||
- 🆕 Novas mensagens em **{{{chats}}}** conversas
|
||||
{{#options.resolveConversations}}
|
||||
|
||||
🗣️ Conversas:
|
||||
- 🟢 Resolvidas: **{{{progress.conversationResolve.success}}}**
|
||||
{{#progress.conversationResolve.error}}
|
||||
- 🔴 Erros: **{{{progress.conversationResolve.error}}}**
|
||||
{{/progress.conversationResolve.error}}
|
||||
{{/options.resolveConversations}}
|
||||
|
||||
Detalhes:
|
||||
[{{{details.text}}}]({{{details.url}}})
|
||||
@@ -1,8 +1,33 @@
|
||||
# Russian translations for ChatWoot integration
|
||||
# Russian translations for WhatsApp inbox integration
|
||||
locale.name: '🇷🇺 Русский (Russian)'
|
||||
app.inbox.contact.name: 'WhatsApp Интеграция (WAHA)'
|
||||
|
||||
# Базовый код локали - должен быть корректной локалью BCP 47
|
||||
# https://en.wikipedia.org/wiki/IETF_language_tag
|
||||
# WAHA использует его для форматирования чисел, валют и даты/времени
|
||||
locale.base: 'ru-RU'
|
||||
|
||||
app.inbox.contact.name: 'WhatsApp Интеграция'
|
||||
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:
|
||||
weekday: short
|
||||
locales: ru-RU
|
||||
day: 2-digit
|
||||
month: short
|
||||
year: numeric
|
||||
hour: 2-digit
|
||||
minute: 2-digit
|
||||
hour12: false
|
||||
# https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat#timezonename
|
||||
timeZoneName: short
|
||||
# https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
|
||||
# timeZone: 'Europe/Moscow'
|
||||
|
||||
Today: 'Сегодня'
|
||||
Yesterday: 'Вчера'
|
||||
|
||||
job.report.error: |-
|
||||
⚠️ **{{{header}}}**
|
||||
{{#attempts.nextDelay}}
|
||||
@@ -31,14 +56,14 @@ whatsapp.message.removing.error: Не удалось удалить сообще
|
||||
job.scheduled.error.header: Запланированная задача не выполнена
|
||||
|
||||
message.from.whatsapp: |-
|
||||
{{{text}}}
|
||||
*📱 Отправлено из WhatsApp*
|
||||
|
||||
📱 *Отправлено из WhatsApp*
|
||||
{{{text}}}
|
||||
|
||||
message.from.api: |-
|
||||
{{{text}}}
|
||||
*📱 Отправлено через API*
|
||||
|
||||
📱 *Отправлено через API*
|
||||
{{{text}}}
|
||||
|
||||
message.removed.in.whatsapp: |-
|
||||
❌ *Это сообщение было удалено в WhatsApp*
|
||||
@@ -66,11 +91,11 @@ whatsapp.contact.broadcast.suffix: 'Список рассылки'
|
||||
whatsapp.contact.status.name: 'Статус'
|
||||
|
||||
app.connected.message: |-
|
||||
🔗 **ПОДКЛЮЧЕНО: Сессия WhatsApp к ChatWoot Inbox**
|
||||
🔗 **ПОДКЛЮЧЕНО: Сессия WhatsApp к Inbox**
|
||||
|
||||
✅ Сессия WhatsApp **{{{name}}}** была успешно подключена к этому ChatWoot Inbox.
|
||||
✅ Сессия WhatsApp **{{{name}}}** была успешно подключена к этому Inbox.
|
||||
|
||||
Этот inbox теперь связан с вашим **аккаунтом WhatsApp**. Вы можете отправлять и получать сообщения прямо отсюда — как в WhatsApp, но внутри ChatWoot.
|
||||
Этот inbox теперь связан с вашим **аккаунтом WhatsApp**. Вы можете отправлять и получать сообщения прямо отсюда — как в WhatsApp, но внутри вашего рабочего пространства.
|
||||
|
||||
Вы также можете управлять сессией WhatsApp, связанной с этим inbox. Чтобы увидеть доступные действия, отправьте **help** в этот чат.
|
||||
|
||||
@@ -84,9 +109,9 @@ app.connected.message: |-
|
||||
Давайте начнем! 🚀
|
||||
|
||||
app.disconnected.message: |-
|
||||
🔌 **ОТКЛЮЧЕНО: Сессия WhatsApp от ChatWoot Inbox**
|
||||
🔌 **ОТКЛЮЧЕНО: Сессия WhatsApp от Inbox**
|
||||
|
||||
❌ Сессия WhatsApp **{{{name}}}** была отключена от этого ChatWoot Inbox.
|
||||
❌ Сессия WhatsApp **{{{name}}}** была отключена от этого Inbox.
|
||||
|
||||
Вы больше не сможете отправлять или получать сообщения из WhatsApp.
|
||||
|
||||
@@ -97,34 +122,156 @@ app.updated.message: |-
|
||||
|
||||
✅ Настройки для сессии WhatsApp **{{{name}}}** были обновлены.
|
||||
|
||||
app.commands.list: |-
|
||||
Вы можете управлять **WhatsApp Интеграцией**, отправив одну из следующих команд:
|
||||
cli.usage.arguments: |-
|
||||
[действие]
|
||||
|
||||
🖥️ **Сессия**:
|
||||
1. **status** - получить текущий статус сессии.
|
||||
2. **restart** - перезапустить сессию - это попытается переподключить ваш аккаунт WhatsApp.
|
||||
3. **start** - запустить сессию.
|
||||
4. **stop** - остановить сессию.
|
||||
5. **logout** - выйти из сессии, чтобы вы могли отсканировать QR-код и восстановить сессию.
|
||||
6. **qr** - получить QR-код для сессии.
|
||||
7. **screenshot** - получить скриншот сессии (доступно только в движке **WEBJS**).
|
||||
8. **help** - доступные команды.
|
||||
cli.usage.command: |-
|
||||
[команда]
|
||||
|
||||
{{#commands.server}}
|
||||
⚙️ Сервер:
|
||||
- **server status** - получить информацию о версии и статусе сервера.
|
||||
- **server reboot** - корректно перезагрузить сервер.
|
||||
- **server reboot force** - принудительно перезагрузить сервер.
|
||||
{{/commands.server}}
|
||||
cli.usage.options: |-
|
||||
[опции]
|
||||
|
||||
Отправьте **команду** (без учета регистра) или **номер команды** в этот разговор.
|
||||
cli.cmd.disabled: ⛔ **Команда `{{command}}` отключена**.
|
||||
|
||||
app.commands.server.disabled: ⛔ **Команды сервера отключены**.
|
||||
|
||||
Пожалуйста, свяжитесь с вашим администратором.
|
||||
Пожалуйста, обратитесь к вашему администратору.
|
||||
|
||||
Отправьте **help**, чтобы получить список доступных команд.
|
||||
|
||||
cli.cmd.root.description: |-
|
||||
Управляйте своей **интеграцией WhatsApp**!
|
||||
|
||||
cli.cmd.root.sub.session: |-
|
||||
🖥️ **Сессия**:
|
||||
|
||||
cli.cmd.root.sub.server: |-
|
||||
🖥️ **Сервер**:
|
||||
|
||||
cli.cmd.root.sub.sync: |-
|
||||
🔄️ **Синхронизация**:
|
||||
|
||||
cli.cmd.session.status.description: |-
|
||||
Получите текущий статус сессии.
|
||||
|
||||
cli.cmd.session.restart.description: |-
|
||||
Перезапустите сессию.
|
||||
|
||||
cli.cmd.session.start.description: |-
|
||||
Запустите сессию.
|
||||
|
||||
cli.cmd.session.stop.description: |-
|
||||
Остановите сессию.
|
||||
|
||||
cli.cmd.session.logout.description: |-
|
||||
Выйдите из сессии, чтобы отсканировать новый QR-код и восстановить соединение.
|
||||
|
||||
cli.cmd.session.qr.description: |-
|
||||
Покажите QR-код сессии.
|
||||
|
||||
cli.cmd.session.screenshot.description: |-
|
||||
Сделайте снимок экрана сессии (доступно только с движком **WEBJS**).
|
||||
|
||||
cli.cmd.contacts.summary: |-
|
||||
Загрузить контакты WhatsApp.
|
||||
|
||||
cli.cmd.contacts.description: |-
|
||||
👤 Выполните `contacts pull`, чтобы запустить синхронизацию, или `contacts status`, чтобы проверить прогресс.
|
||||
|
||||
cli.cmd.contacts.action.description: |-
|
||||
Используйте `start`, чтобы начать загрузку контактов, или `status`, чтобы проверить активное задание.
|
||||
|
||||
cli.cmd.contacts.pull.description: |-
|
||||
Начните импорт контактов WhatsApp в inbox.
|
||||
|
||||
cli.cmd.contacts.pull.option.batch: |-
|
||||
Количество контактов в каждой партии.
|
||||
|
||||
cli.cmd.contacts.pull.option.delay-contact: |-
|
||||
Задержка между обработкой контактов, чтобы избежать ограничений при загрузке аватаров.
|
||||
|
||||
cli.cmd.contacts.pull.option.delay-batch: |-
|
||||
Задержка между партиями контактов.
|
||||
|
||||
cli.cmd.contacts.pull.option.avatar: |-
|
||||
Выберите, как загружать аватары контактов: `skip`, `if-missing` или `update`.
|
||||
|
||||
cli.cmd.contacts.pull.option.groups: |-
|
||||
Включить контакты групп WhatsApp в синхронизацию.
|
||||
|
||||
cli.cmd.contacts.pull.option.no-attributes: |-
|
||||
Не загружать атрибуты контакта, если он уже существует (ускоряет процесс).
|
||||
|
||||
cli.cmd.contacts.pull.already-running: |-
|
||||
⏳ Задание **Pull Contacts** уже выполняется. Подождите его завершения или остановите перед запуском нового.
|
||||
|
||||
Отправьте `contacts status` для подробностей.
|
||||
|
||||
cli.cmd.options.job.attempts: |-
|
||||
Число повторных попыток до пометки задания как неуспешного.
|
||||
|
||||
cli.cmd.options.job.timeout: |-
|
||||
Максимальное время выполнения перед повторной попыткой (поддерживает значения `30s`, `5m`, `1h`).
|
||||
|
||||
cli.cmd.contacts.status.description: |-
|
||||
Показать статус текущего задания загрузки.
|
||||
|
||||
cli.cmd.contacts.status.not-found: |-
|
||||
ℹ️ Активное задание **Pull Contacts** не найдено. Запустите его командой `contacts pull`.
|
||||
|
||||
cli.cmd.server.description: |-
|
||||
Управляйте сервером.
|
||||
|
||||
cli.cmd.server.status.description: |-
|
||||
Покажите версию и статус сервера.
|
||||
|
||||
cli.cmd.server.reboot.description: |-
|
||||
Перезагрузите сервер.
|
||||
|
||||
cli.cmd.server.reboot.option.force: |-
|
||||
Принудительно выполните немедленную перезагрузку.
|
||||
|
||||
cli.help.usage.title: |-
|
||||
Использование: отправьте команду в диалог в следующем формате:
|
||||
|
||||
cli.help.commands.defaultGroup: |-
|
||||
**Команды:**
|
||||
|
||||
cli.help.arguments.title: |-
|
||||
**Аргументы:**
|
||||
|
||||
cli.help.options.defaultGroup: |-
|
||||
**Опции:**
|
||||
|
||||
cli.help.globalOptions.title: |-
|
||||
**Глобальные опции:**
|
||||
|
||||
task.contacts.status: |-
|
||||
👤 Загрузка контактов: **{{state}}**
|
||||
- ✅ Успешно: {{{progress.ok}}} контактов
|
||||
- ⚪ Пропущено: {{{progress.skipped}}} контактов
|
||||
- ⚠️ Ошибки: {{{progress.errors}}} контактов
|
||||
|
||||
{{#job}}
|
||||
⌛ Хронология:
|
||||
- В очереди: {{{timestamp}}}
|
||||
{{#processedOn}}
|
||||
- Начато: {{{processedOn}}}
|
||||
{{/processedOn}}
|
||||
{{#finishedOn}}
|
||||
- Завершено: {{{finishedOn}}}
|
||||
{{/finishedOn}}
|
||||
{{/job}}
|
||||
|
||||
{{#error}}
|
||||
Подробности:
|
||||
[{{{details.text}}}]({{{details.url}}})
|
||||
{{/error}}
|
||||
|
||||
task.contacts.started: |-
|
||||
Идёт загрузка контактов...
|
||||
|
||||
task.contacts.progress: |-
|
||||
Загрузка контактов: ✅ {{progress.ok}}, ⚪ {{progress.skipped}}, ⚠️ {{progress.errors}}
|
||||
|
||||
app.help.reminder: |-
|
||||
Отправьте **help**, чтобы получить список доступных команд.
|
||||
|
||||
@@ -138,17 +285,17 @@ app.session.current.status: |-
|
||||
📱 **{{{name}}} ({{{id}}})**
|
||||
|
||||
app.session.status.working: |-
|
||||
✅ Этот ChatWoot Inbox связан с **аккаунтом WhatsApp** и готов отправлять и получать сообщения!
|
||||
✅ Этот inbox связан с вашим **аккаунтом WhatsApp** и готов отправлять и получать сообщения!
|
||||
|
||||
📱 **{{{name}}} ({{{id}}})**
|
||||
|
||||
app.session.status.error: |-
|
||||
⚠️ **Этот ChatWoot Inbox связан с аккаунтом WhatsApp, но НЕ готов отправлять и получать сообщения!**
|
||||
⚠️ **Этот inbox связан с аккаунтом WhatsApp, но НЕ готов отправлять и получать сообщения!**
|
||||
|
||||
Пожалуйста, проверьте статус сессии и попробуйте перезапустить её или выйдите и отсканируйте QR-код снова.
|
||||
|
||||
app.session.scan.qr.code: |-
|
||||
⚠️ **Отсканируйте QR-код на вашем телефоне, чтобы связать аккаунт WhatsApp с этим ChatWoot Inbox!**
|
||||
⚠️ **Отсканируйте QR-код на вашем телефоне, чтобы связать аккаунт WhatsApp с этим Inbox!**
|
||||
|
||||
app.server.version.and.status: |-
|
||||
**Версия:**
|
||||
@@ -216,7 +363,19 @@ whatsapp.to.chatwoot.message.location: |-
|
||||
{{#locationMessage}}
|
||||
📍 **Переданное местоположение**
|
||||
|
||||
https://www.google.com/maps?q={{degreesLatitude}},{{degreesLongitude}}
|
||||
{{#name}}
|
||||
**{{name}}**
|
||||
{{/name}}
|
||||
|
||||
{{#address}}
|
||||
📍 **Адрес:** {{address}}
|
||||
{{/address}}
|
||||
|
||||
{{#url}}
|
||||
🌐 **Сайт:** {{url}}
|
||||
{{/url}}
|
||||
|
||||
🔗 **Открыть на карте:** https://www.google.com/maps?q={{degreesLatitude}},{{degreesLongitude}}
|
||||
{{#caption}}{{caption}}{{/caption}}
|
||||
{{/locationMessage}}
|
||||
|
||||
@@ -224,13 +383,285 @@ whatsapp.to.chatwoot.message.location: |-
|
||||
📍📡 **Делится геопозицией в реальном времени**
|
||||
Откройте **WhatsApp**, чтобы увидеть обновления в реальном времени.
|
||||
|
||||
https://www.google.com/maps?q={{degreesLatitude}},{{degreesLongitude}}
|
||||
{{#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.**
|
||||
⚠️ **Этот тип сообщения не поддерживается в этом Inbox.**
|
||||
📱 Пожалуйста, откройте **WhatsApp**, чтобы просмотреть.
|
||||
|
||||
Подробности: [{{{details.text}}}]({{{details.url}}})
|
||||
|
||||
whatsapp.to.chatwoot.message.album: |-
|
||||
📷 **Получено сообщение с альбомом**
|
||||
|
||||
{{#totalCount}}
|
||||
Получен альбом с **{{totalCount}}** медиафайлом(ами).
|
||||
{{#expectedImageCount}}
|
||||
📸 Изображений: **{{expectedImageCount}}**
|
||||
{{/expectedImageCount}}
|
||||
{{#expectedVideoCount}}
|
||||
🎥 Видео: **{{expectedVideoCount}}**
|
||||
{{/expectedVideoCount}}
|
||||
|
||||
Медиафайлы будут доставлены в следующих сообщениях.
|
||||
{{/totalCount}}
|
||||
|
||||
whatsapp.to.chatwoot.message.facebook.ad: |-
|
||||
📢 **РЕКЛАМА FACEBOOK**
|
||||
|
||||
**Заголовок:** {{{adData.title}}}
|
||||
|
||||
**Описание:**
|
||||
{{{adData.body}}}
|
||||
|
||||
**Ссылка на объявление:** [Посмотреть в Facebook]({{{adData.sourceUrl}}})
|
||||
**Идентификатор объявления:** {{{adData.sourceId}}}
|
||||
|
||||
---
|
||||
💬 **Сообщение клиента:**
|
||||
{{{payload.body}}}
|
||||
whatsapp.history.message.wrapper: |-
|
||||
{{{content}}}
|
||||
`{{#history}}📥{{/history}}🗓️ {{{timestamp}}}`
|
||||
|
||||
UNKNOWN: Неизвестно
|
||||
ERROR: Ошибка
|
||||
PENDING: Отправляется
|
||||
SERVER: Сервер
|
||||
DEVICE: Устройство
|
||||
READ: Прочитано
|
||||
PLAYED: Прослушано
|
||||
whatsapp.to.chatwoot.message.has.media.no.media: |-
|
||||
{{#content}}
|
||||
{{{content}}}
|
||||
|
||||
{{/content}}
|
||||
🖼️⚠️ **В сообщении есть медиа, но нам не удалось его скачать**
|
||||
📱 Откройте **WhatsApp**, чтобы посмотреть.
|
||||
|
||||
{{^content}}
|
||||
Подробности: [{{{details.text}}}]({{{details.url}}})
|
||||
{{/content}}
|
||||
cli.cmd.root.sub.queue: '🧩 **Очередь**:'
|
||||
cli.cmd.messages.summary: Загрузить сообщения WhatsApp.
|
||||
cli.cmd.messages.description:
|
||||
💬 Выполните `messages pull`, чтобы запустить синхронизацию, или `messages
|
||||
status`, чтобы проверить прогресс.
|
||||
cli.cmd.messages.action.description:
|
||||
'`pull` — начать загрузку, `status` — посмотреть текущую задачу.'
|
||||
cli.cmd.messages.pull.option.chat:
|
||||
Ограничить загрузку конкретным идентификатором чата WhatsApp.
|
||||
cli.cmd.messages.pull.option.force:
|
||||
Принудительно создавать сообщения в ящике, даже если сообщение WhatsApp уже
|
||||
есть в сопоставлении беседы.
|
||||
cli.cmd.messages.pull.option.batch: Количество сообщений в каждом пакете.
|
||||
cli.cmd.messages.pull.option.progress:
|
||||
Сообщать о прогрессе каждые N сообщений. Значение 0 отключает отчёты.
|
||||
cli.cmd.messages.pull.option.no-dm:
|
||||
Пропускать личные сообщения при загрузке истории.
|
||||
cli.cmd.messages.pull.option.groups:
|
||||
Включать группы WhatsApp при загрузке истории.
|
||||
cli.cmd.messages.pull.option.channels:
|
||||
Включать каналы WhatsApp при загрузке истории.
|
||||
cli.cmd.messages.pull.option.status:
|
||||
Включать статусы WhatsApp при загрузке истории.
|
||||
cli.cmd.messages.pull.option.broadcast:
|
||||
Включать списки рассылок при загрузке истории.
|
||||
cli.cmd.messages.pull.option.media:
|
||||
Скачивать медиафайлы (изображения, видео, документы) при загрузке истории.
|
||||
cli.cmd.messages.pull.option.pause:
|
||||
Поставить задачу загрузки сообщений в очередь на паузу и остановить обработку
|
||||
новых сообщений WhatsApp до завершения.
|
||||
cli.cmd.messages.pull.option.resolve-conversations:
|
||||
Отмечает беседы как завершённые после обработки всей истории сообщений.
|
||||
cli.cmd.messages.pull.error.pause-no-queue:
|
||||
⛔ Невозможно использовать `--pause`, потому что команда **queue** отключена в
|
||||
этой конфигурации.
|
||||
cli.cmd.queue.description: Управление очередями.
|
||||
cli.cmd.queue.summary: Действия с очередями.
|
||||
cli.cmd.queue.action.description: Выберите действие.
|
||||
cli.cmd.queue.status.description: Показать состояние очереди.
|
||||
cli.cmd.queue.argument.name:
|
||||
'Название очереди: `inbox` или `whatsapp` (необязательно).'
|
||||
cli.cmd.queue.start.description: Запустить очередь по имени.
|
||||
cli.cmd.queue.stop.description: Остановить очередь по имени.
|
||||
cli.cmd.queue.paused:
|
||||
⚪🧩 Очереди поставлены на паузу. Отправьте `queue status`, чтобы проверить
|
||||
состояние.
|
||||
cli.cmd.queue.resumed:
|
||||
🟢🧩 Очереди возобновлены. Отправьте `queue status`, чтобы проверить
|
||||
состояние.
|
||||
cli.cmd.queue.status.result: >-
|
||||
📊 Состояние очередей:
|
||||
|
||||
{{#queues}}
|
||||
|
||||
- **{{name}}**: {{#locked}}🔒{{/locked}}{{#paused}}⚪ На
|
||||
паузе{{/paused}}{{^paused}}🟢 Активна{{/paused}}
|
||||
|
||||
{{/queues}}
|
||||
cli.cmd.messages.pull.option.timeout-media:
|
||||
Указывает, как долго пытаться скачать медиа для каждого сообщения.
|
||||
cli.cmd.messages.pull.argument.end:
|
||||
Задаёт конец окна загрузки, например `2d` — до двух дней назад.
|
||||
cli.cmd.messages.pull.argument.start:
|
||||
Задаёт начало окна загрузки, например `1d` — с одного дня назад (по умолчанию
|
||||
`0d`, то есть сейчас).
|
||||
cli.cmd.messages.pull.already-running: >-
|
||||
⏳ Задача **Pull Messages** уже выполняется для **{{{chat}}}**. Дождитесь
|
||||
завершения или остановите её перед запуском новой.
|
||||
|
||||
|
||||
Отправьте `messages status` для деталей.
|
||||
cli.cmd.messages.status.not-found:
|
||||
ℹ️ Активная задача **Pull Messages** не найдена. Запустите её командой
|
||||
`messages pull`.
|
||||
cli.cmd.messages.pull.no-chats-found:
|
||||
Чаты WhatsApp для выбранного периода не найдены.
|
||||
cli.cmd.contacts.pull.option.progress:
|
||||
Сообщать о прогрессе каждые N контактов. Значение 0 отключает отчёты.
|
||||
cli.cmd.contacts.pull.option.lids:
|
||||
Включать контакты WhatsApp LID (анонимные контакты) в синхронизацию.
|
||||
task.contacts.completed:
|
||||
👤✅ Контакты завершены → 🆕 {{progress.created}}, 🔄 {{progress.updated}}, ⚪
|
||||
{{progress.skipped}}, 🔴 {{progress.errors}}, 🖼️ {{progress.avatar.updated}}
|
||||
task.messages.started: ⏳ Загружаем сообщения 💬 {{{chat}}} ⏱️ {{{period}}}...
|
||||
task.messages.details:
|
||||
Отправьте "{{{prefix}}}messages status", чтобы увидеть подробности.
|
||||
task.messages.progress:
|
||||
'⏳ Сообщения {{{chat}}}: 🆕 {{progress.ok}}, 🟢 {{progress.exists}}, ⚪
|
||||
{{progress.ignored}}, 🔴 {{progress.errors}}{{#messages.last}} | 📅
|
||||
{{{messages.last}}}{{/messages.last}}'
|
||||
task.messages.completed:
|
||||
✅ Сообщения {{{chat}}} за {{{period}}} → 🆕 {{progress.ok}}, 🟢
|
||||
{{progress.exists}}, ⚪ {{progress.ignored}}, 🔴 {{progress.errors}}
|
||||
task.messages.status: |-
|
||||
Сообщения **{{{chat}}}** за период ⏱️ **{{{period}}}**. ⚙️ Статус задачи: **{{state}}**.
|
||||
|
||||
🗨️ Сообщения:
|
||||
- 🆕 Создано: **{{{progress.ok}}}**
|
||||
- 🟢 Уже есть: {{{progress.exists}}}
|
||||
- ⚪ Пропущено: {{{progress.ignored}}}
|
||||
- 🔴 Ошибки: {{{progress.errors}}}
|
||||
{{#messages.first}}
|
||||
- 🗓️ Самое раннее: **{{{messages.first}}}**
|
||||
{{/messages.first}}
|
||||
{{#messages.last}}
|
||||
- 🗓️ Самое позднее: **{{{messages.last}}}**
|
||||
{{/messages.last}}
|
||||
|
||||
💬 Чаты WhatsApp:
|
||||
- 🆕 Новые сообщения в **{{{chats}}}** чатах
|
||||
{{#options.resolveConversations}}
|
||||
|
||||
🗣️ Беседы:
|
||||
- 🟢 Закрыто: **{{{progress.conversationResolve.success}}}**
|
||||
{{#progress.conversationResolve.error}}
|
||||
- 🔴 Ошибки: **{{{progress.conversationResolve.error}}}**
|
||||
{{/progress.conversationResolve.error}}
|
||||
{{/options.resolveConversations}}
|
||||
|
||||
Подробности:
|
||||
[{{{details.text}}}]({{{details.url}}})
|
||||
Loaded 100 of 180 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user