Ctrl K
ComunidadGuías…

group-translate/CHANGELOG.md

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# Changelog

All notable changes to the **Group Auto-Translation** plugin are documented here.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this plugin adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
The version here always matches `manifest.json`'s `version`.

## [Unreleased]

## [1.3.17] - 2026-11-10

### Fixed

- Exclude host capability-concurrency rejections from the LibreTranslate circuit breaker failure count.

### Changed

- Align plugin metadata with OpenWA v0.30.0.

## [1.3.16] - 2026-11-08

### Changed

- Align plugin metadata with OpenWA v0.29.1.

### Fixed

- Translate forwarded group messages into the group's languages without updating the forwarder's learned language or running `/tr` commands.

## [1.3.15] - 2026-11-06

### Changed

- Align plugin metadata with OpenWA v0.29.0.

### Fixed

- Bound per-group storage keys to 184 UTF-8 bytes for long session and group identifiers.

## [1.3.14] - 2026-10-31

### Fixed

- Preserve group command ordering across live configuration changes.

## [1.3.13] - 2026-10-31

### Changed

- Align plugin metadata with OpenWA v0.27.0.

## [1.3.12] - 2026-10-28

### Changed

- Align plugin metadata with OpenWA v0.26.0.
- Document that a per-session override that points `libretranslateUrl` at another origin must also set `libretranslateApiKey`, which OpenWA 0.26.0 no longer inherits.
- Correct the multi-backend advice: OpenWA installs a plugin once per gateway, so there is no instance per session.

## [1.3.11] - 2026-10-15

### Changed

- Align plugin metadata with OpenWA v0.25.0 and complete dashboard translations for all supported locales.

## [1.3.10] - 2026-09-24

### Added

- Hindi (`hi`) dashboard text: the plugin name, description and config field titles.

## [1.3.9] - 2026-09-24

### Fixed

- A message that reaches the plugin more than 5 minutes after it was sent is no longer translated.
  From OpenWA 0.23.6 a Baileys session delivers what WhatsApp queued during a disconnect once it
  reconnects.
- A `/tr` command that late is still claimed but no longer run, since a replay can arrive out of order.
- A `/tr` command is claimed as soon as it is recognized, so another auto-reply plugin no longer
  answers it when the group admin lookup fails or outlasts the host's 5 s hook budget.
- A failed group admin lookup counts as an empty admin list: delegated controllers still work and
  `denyReply` decides whether the sender is told.
- A failed `@lid` identity lookup is no longer remembered, so one slow engine read cannot lock a group
  admin out until the plugin restarts.

## [1.3.8] - 2026-09-23

### Fixed

- Catalog orders and shared product cards are no longer translated. From OpenWA 0.23.5 they arrive as
  `order` and `product`, on Baileys with the order note or product title as the body, which was posted
  back into the group as a quote-reply and fed to language detection.

### Changed

- **Verified against OpenWA v0.23.6** (testedOpenWAVersion 0.23.4 -> 0.23.6).

## [1.3.7] - 2026-09-05

### Fixed

- **`setlang` and `auto` are admin-gated, as the README, the command table and the plugin description
  have always said.** Targeting yourself skipped authorization entirely, and an absent target defaults
  to yourself, so the plain `<prefix> setlang <code>` form was open to any group member. The
  confirmation is unconditional, so a member could make the bot post into the group on every attempt,
  the amplification `help` is explicitly bounded against, and could pin their own language. The
  in-chat help now marks both commands as admin, like its neighbours.
- **A backend URL carrying credentials no longer reaches the host log.** When the configured URL has
  embedded credentials the gateway refuses every fetch and names the full URL in the error, which was
  rethrown verbatim and logged once per message. Enable-time validation already refused to log that
  field's value for exactly this reason. Userinfo is now stripped before the error leaves the client.

### Changed

- **Verified against OpenWA v0.23.4** (testedOpenWAVersion 0.23.3 -> 0.23.4).

## [1.3.6] - 2026-08-25

### Changed

- **`minOpenWAVersion` raised to 0.8.0.** The plugin resolves the operator's backend host through
  `net.allowConfigHosts`, which first shipped in OpenWA 0.8.0. On a 0.7.x host it installed and enabled
  cleanly and then translated nothing, because only the shipped loopback entries were reachable.
- **`/tr status` is now admin-only.** It prints the participant roster (who is ignored, who holds
  delegated control, the language recorded for each member), which is the plugin's own access-control
  state, and it answered any group member on every attempt.
- **`/tr help` is answered at most once a minute per group.** It is the only reply an unauthorized user
  can draw, so repeating it was a way to make the bot post into the group indefinitely; the denial
  reply is opt-in for exactly that reason.

### Fixed

- A `libretranslateUrl` the host cannot use is now named at enable time. A value with no scheme,
  embedded credentials, or a query string is refused by the host at the capability boundary, which
  surfaced only as translation silently never happening. The URL itself is never logged.
- A failed language detection records why. An unreachable backend, a refused fetch and an SSRF-blocked
  address all returned without writing anything, so a group that had stopped being translated left no
  trace of the cause.

- A shared contact card is no longer translated. OpenWA 0.23.2 fills the message body with the card's
  vCard, which sent a third party's name and number to the translation backend, posted a
  machine-translated card back into the group, and let language detection pin the sender's language
  from vCard field names on their first shared card. Poll questions are still translated: a poll
  question is ordinary prose.

### Changed

- **Verified against OpenWA v0.23.3** (testedOpenWAVersion 0.23.0 → 0.23.3).

## [1.3.5] - 2026-08-20

### Changed

- **Verified against OpenWA v0.23.0** (testedOpenWAVersion 0.22.0 → 0.23.0).

## [1.3.4] - 2026-08-19

### Changed

- **Verified against OpenWA v0.22.0** (testedOpenWAVersion 0.20.0 → 0.22.0).

## [1.3.3] - 2026-08-16

### Changed

- **Verified against OpenWA v0.20.0** (testedOpenWAVersion 0.19.0 → 0.20.0); the catalog download URL now pins #sha256= for production installs.

## [1.3.2] — 2026-08-15

### Fixed

- **A group message can no longer stall the plugin worker.** The filter that skips messages with
  nothing to translate was a single regular expression whose emoji branch overlapped its URL branch —
  `\p{Emoji}` matches ASCII digits, `#` and `*`, because those are keycap-sequence components. Each
  additional link-shaped token multiplied the backtracking space, so a 145-character message took
  several seconds of uninterruptible CPU and `maxLength` allows 2000. The filter now scans token by
  token, which is linear regardless of input.
- **A message of only digits or punctuation is translated again.** The same emoji property classified
  `1` and `#5` as emoji, so those messages were skipped silently. The filter now uses
  `\p{Extended_Pictographic}`, which is the property that actually means "picture character".


### Changed

- **Compatibility re-verified against OpenWA v0.19.0** (testedOpenWAVersion 0.14.0 → 0.19.0). The
  manifest passes the v0.19.0 host's load-time validation (manifest, ingress and main-entry
  checks) and the built bundle loads under the loader contract. No capability surface this plugin
  uses changed between 0.14 and 0.19; the v0.19 breaking changes are host-side (API key length,
  removed REST endpoints, the plain-http install pin) and do not touch this plugin.

## [1.3.1] — 2026-08-12

### Changed

- **Declared the `storage:use` permission.** Each group's settings — whether translation is on, the
  language learned for every participant, and any delegated controllers — are kept in plugin storage.
  OpenWA is moving `ctx.storage` behind a permission the manifest has to declare, and a plugin that
  does not name it will have those reads and writes refused once the host enforces that. The failure
  would surface mid-conversation rather than at install: the plugin would keep running and quietly
  forget every group's configuration. Declaring it now costs nothing on the hosts you are running
  today — an unrecognized permission is ignored — so this release behaves exactly like 1.3.0
  everywhere 1.3.0 ran.

## [1.3.0] — 2026-08-01

### Fixed

- **Group admins were refused their own commands.** WhatsApp delivers the author of a group message
  under a privacy id (`…@lid`) while the group's participant list comes back under phone ids
  (`…@c.us`). Those two carry unrelated numbers, so an admin failed every comparison and `/tr on`,
  `/tr setlang`, `/tr grant` and the rest were silently denied. Only the group's creator was ever
  recognized, because WhatsApp reports the owner in the author's own dialect — and in a group created
  by the bot's own number, even that did not help, since the bot's own messages are never processed.
  The result was a group with no working administrator at all.

  When the direct comparison finds nothing, the plugin now asks the host to resolve the author to its
  phone identity and compares again. The same bridge fixes `/tr grant`: a controller delegated by
  phone number is now recognized when they speak. The lookup costs one call, is spent only on what
  would otherwise be a denial, and is remembered afterwards.

### Changed

- **The LibreTranslate backend is configurable without repackaging the plugin.** `libretranslateUrl`
  was checked against a fixed `localhost:7001` entry, so pointing the plugin at any other instance
  meant editing the manifest and rebuilding the `.zip` — and every other value silently failed. Any
  loopback address on any port is now accepted as shipped, and any other host is accepted over https
  through the manifest's `allowConfigHosts`. A loopback or private address still needs
  `SSRF_ALLOWED_HOSTS` on the OpenWA host; a plain-http backend on a non-loopback host still needs the
  manifest edit, because the host admits a config-supplied host only over https.

## [1.2.0] — 2026-08-01

### Changed

- **This plugin no longer introduces itself in your groups unless you ask it to.** Until now, enabling
  it made it post a "👋 Translation bot…" message into a group the first time it saw any message there.
  That fired **per group**, so a single enable announced the bot into every group the WhatsApp account
  belonged to — and the account running a plugin is usually somebody's own WhatsApp, not a dedicated
  bot number. The introduction is now controlled by a new **Announce this bot in new groups** setting,
  which is **off by default**, including for installs upgrading from an earlier version.

  Nothing else about the plugin was reachable without addressing it first: translation still requires
  an admin to run `/tr on`, and the same introduction text is still available to anyone on demand with
  `/tr help`. If you want the old behaviour, turn the new setting on.

## [1.1.1] — 2026-08-01

No behaviour change. 1.1.0 has now been smoke-tested against a newer host, so the tested-version field
is updated to match.

### Verified

- **Confirmed against a live OpenWA 0.12.1 host**, with a connected WhatsApp session, driven by real
  WhatsApp messages from a second connected number in a real WhatsApp group, against a real self-hosted
  LibreTranslate instance (English + Indonesian models) reachable at `http://localhost:7001` from inside
  the host container. `/tr status` reported `Translator: ok` — a live health probe of LibreTranslate
  through the host-proxied `ctx.net.fetch`. `/tr on` returned `✅ Translation activated.`, and both
  `/tr setlang en` (self) and `/tr setlang id <number>` applied and read back correctly in `/tr status`.
  The message `good morning, how are you today` was answered with
  `ID: selamat pagi, bagaimana kabarmu hari ini`.
- **The transformer-band fix from 1.1.0 holds**: the hook registered at priority 50, the Transformer
  band assigned in `PLUGIN-STANDARD.md`.
- **Not covered:** load or concurrency.

## [1.1.0] — 2026-07-31

### Fixed

- **A message could go untranslated entirely if another auto-reply plugin was also installed.** This
  plugin ran at the default hook priority — the same one an auto-reply plugin claiming its own messages
  runs at by default — so whether this plugin ever got a chance to translate a given message depended on
  which plugin happened to register first, which could change across a restart or re-enable. It now
  registers in the transformer band, ahead of every responder, so it translates an eligible message
  before any responder can answer it, instead of racing them for registration order. Note this only
  affects when the translation runs: a translated message is still passed on afterward, so a co-installed
  auto-reply plugin can still see and answer the original text. This plugin claims only its own `/tr`
  admin commands, never a translated conversational message.

## [1.0.7] — 2026-07-30

### Fixed

- **An empty translation is dropped instead of thrown away.** Whatever the backend returned was forwarded
  straight into the send, and a backend that answers 200 with a blank translation is a real case. OpenWA
  0.12 rejects an empty positional capability argument outright, so that stopped being a blank WhatsApp
  bubble and became a thrown capability error the coordinator swallowed — a translation that silently
  never arrives either way. It is now dropped deliberately, at the one place that can tell why.
- **An empty translation is dropped where it is produced, not at the send.** The guard first shipped in
  the gateway could never fire: the formatter prefixes each entry with a flag and language code, so a
  blank translation still rendered a non-empty `🇪🇸 ES:` bubble. It is now dropped at the point the
  translation is collected, which also keeps the decision log's count honest.
- **`timeoutMs` is clamped in code, not just hinted in the schema.** A `configSchema` `min` is a form
  hint the host never enforces, and the host clamps a fetch timeout of `<= 0` up to 1 ms — so a config of
  `0` made every translate abort instantly, with the coordinator swallowing the error and nothing logged.
  Values are now clamped to 500–30000 where it is actually enforceable.

## [1.0.6] — 2026-07-18

### Fixed

- **Per-session config overrides are now honored at message time without resetting the circuit breaker
  on every message.** The coordinator was built once at `onEnable` and the hook read that cached
  instance, so a per-session override (e.g. a different LibreTranslate instance or command prefix for one
  session) set via the dashboard after enable was ignored. The hook now recomputes a signature of the
  coordinator-affecting config fields per event and rebuilds the coordinator only when that signature
  changes — so an override takes effect, while the LibreTranslate client's circuit-breaker state is
  preserved across messages for an unchanged backend (a naive per-event rebuild would open/close the
  backend anew on each call and defeat the breaker's purpose).

## [1.0.5] — 2026-07-02

### Fixed

- **`denyReply` is now honored.** The denial reply for a restricted command was sent unconditionally,
  ignoring the `denyReply` config (which the manifest documents as default `false`). It now replies only
  when `denyReply` is enabled — so by default an unauthorized user cannot make the bot echo an "admins
  only" message back into the group on every attempt.

### Changed

- **README Security section corrected.** It previously claimed `SSRF_ALLOWED_HOSTS` "no longer applies to
  plugins" — the opposite of the truth. The host SSRF guard blocks loopback/private addresses at connect
  for every `ctx.net.fetch` regardless of `net.allow`, so a self-hosted LibreTranslate on
  `localhost`/`127.0.0.1`/a private host (including the default `http://localhost:7001`) requires
  `SSRF_ALLOWED_HOSTS=<hostname>` on the gateway. The Security section and config table now say so.

## [1.0.4] — 2026-06-25

### Fixed

- Translations now actually apply. The LibreTranslate client read the response with `res.json()`, but the
  sandboxed `ctx.net.fetch` returns the body as a string and provides no `.json()` method (functions can't
  cross the worker boundary) — so every call threw and failed open, a silent no-op. The client now parses
  `res.body` directly.

## [1.0.3] — 2026-06-23

### Fixed

- Participant lookups now reject prototype keys (`__proto__`, `constructor`, `prototype`) and test
  existence with `hasOwnProperty`, so a crafted participant/target id can no longer read or write
  `Object.prototype`.
- Concurrent messages for the same group are serialized through a per-(session, chat) lock, closing a
  load→mutate→save race that could duplicate the help announcement or drop a participant-language update.
  The lock map self-evicts when a chat's queue drains.
- A LibreTranslate `/translate` response without a string `translatedText` now fails the call (counted by
  the circuit breaker and excluded from the reply) instead of posting the literal text `undefined`.

## [1.0.2] — 2026-06-23

### Fixed

- Telugu (`te`) localization: the `libretranslateUrl` field title was left in English ("LibreTranslate URL");
  it is now localized to "LibreTranslate చిరునామా", matching the other locales.

## [1.0.1] — 2026-06-23

### Added

- Localized dashboard text (`name`, `description`, config field titles) for es, fr, it, ar, he, te, zh-CN,
  zh-HK via `manifest.i18n`. English remains the default + fallback. Translations are machine-generated;
  human review recommended for ar/he/te.

## [1.0.0] — 2026-06-23

First release. Built against the OpenWA v0.7 plugin contract.

### Added

- Auto-translation of group messages between participants' languages via a LibreTranslate backend, with
  in-chat `/tr` commands (help, status, on/off, setlang, auto, ignore/unignore, grant/revoke). Admin-gated
  via `ctx.engine.getGroupInfo`; disabled until enabled.
- All outbound calls go through the host's SSRF-guarded `ctx.net.fetch`; the LibreTranslate host must be in
  the manifest `net.allow` allowlist. Per-call timeout defaults to 4000ms (≤ the host hook budget), with a
  circuit breaker that backs off a flaky backend.