[core] Resolve Brazilian 9th-digit ambiguity on send (opt-in)#2180
Open
bergpinheiro wants to merge 1 commit into
Open
[core] Resolve Brazilian 9th-digit ambiguity on send (opt-in)#2180bergpinheiro wants to merge 1 commit into
bergpinheiro wants to merge 1 commit into
Conversation
Brazilian mobiles went through a 9th-digit migration. For DDD 11-30 every
mobile carries the extra 9, so a static rule is enough. For DDD 31-99 the
form is ambiguous: some accounts are registered with the 9, some without,
and the number alone does not tell which.
Adds resolveOutboundChatId() to WhatsappSession, applied on the send paths
of all four engines. Tiered, cheapest first: in-memory cache -> static rule
for DDD < 31 -> local contact/LID map -> WhatsApp lookup, single-flight so
concurrent sends to the same new number share one query. Read and presence
operations resolve locally only and never reach the network.
Brazilian toll-free numbers (0800) are non-geographic: dialed as '0800'
plus 7 subscriber digits, but stored on WhatsApp under country code 55 with
the leading 0 dropped ('08000464636' -> '558000464636'). Callers send the
dialed form, which the send path cannot resolve to a LID and rejects. This
rewrites it to the stored form with a deterministic rule - no lookup, since
unlike the 9th digit there is no ambiguity.
Opt-in via WAHA_BR_PHONE_NORMALIZE, off by default. Non-Brazilian numbers
exit on the first check, with no cache and no lookup. A number confirmed
not to exist is soft by default (warn and send the best guess);
WAHA_BR_PHONE_STRICT=true rejects it with 422 instead.
GOWS checkNumberStatus recovers the phone number when the usync answer
carries a LID (addressing_mode=lid), falling back to the LID when no
mapping exists, and WANumberExistResult gains an optional lid so no
identifier is lost. isBrazilMobile now accepts any digit after the leading
9 on a 9-digit local, since Brazil has no 9-digit landline.
Contributor
Author
|
@devlikepro If you have time to review this PR before the next version, and if it makes sense for Waha, everyone here was asking me for this. |
|
I love you |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Brazilian mobile numbers went through a 9th-digit migration. For DDD 11–30
every mobile carries the extra 9, so a static rule is enough. For DDD 31–99
the form is ambiguous: some accounts are registered with the 9, some
without, and the number alone does not tell which.
Integrations send whatever their CRM holds — usually the carrier form, with
the 9 — and the message fails or goes nowhere depending on how the account is
actually registered.
What this adds
resolveOutboundChatId()onWhatsappSession, applied to the send paths ofall four engines. It resolves the number to the chat id the account is
actually registered under, tiered cheapest-first:
number share one query
Read and presence operations resolve locally only (
{ validate: false }):they never reach the network and never throw.
A number confirmed not to exist is soft by default — warn and send the best
guess — so a usync false negative never blocks a valid send.
WAHA_BR_PHONE_STRICT=truerejects it with 422 instead.Toll-free (0800) numbers
Brazilian toll-free numbers are non-geographic: dialed as
0800+ 7subscriber digits, but stored on WhatsApp under country code 55 with the
leading
0dropped —08000464636is stored as558000464636. A callersending the dialed form (
08000464636or5508000464636) hits a send thatcannot resolve it to a LID and fails.
This rewrites the dialed form to the stored one with a deterministic rule —
no lookup, since unlike the 9th digit there is no ambiguity. The already-stored
form routes through untouched. Only
0800is handled (0300/0500/0900sharethe shape but are not covered).
Opt-in
Off unless
WAHA_BR_PHONE_NORMALIZE=true. Non-Brazilian numbers exit on thefirst check (
isBrazilCountryCode): no cache, no lookup, no cost.WAHA_BR_PHONE_NORMALIZEfalseWAHA_BR_PHONE_STRICTfalseRelationship to
PhoneJidNormalizerPhoneJidNormalizerconverts a JID into an E.164 string for the Chatwootcontact record — one direction, static rule. This resolves the opposite
direction: an inbound phone number into the chat id to address, with a server
lookup for the range where a static rule cannot decide.
They do not overlap, and its rule stays correct for DDD 11–30 — that is
exactly what tier 2 does here.
checkNumberStatusanswering with a LIDGOWS
checkNumberStatusreturned whateverjidthe usync answer carried.Since whatsmeow queries contacts with
addressing_mode=lid, that can be aLID — a contract break for an endpoint documented as returning the "Chat id
for the phone number", and it also breaks tier 4 of this feature.
Fixed by recovering the phone number from the LID map (
findPNByLid), with afallback to the LID when no mapping exists.
WANumberExistResultgains anoptional
lidso no identifier is lost. Happy to split this out if you wouldrather handle it separately.
Also
isBrazilMobilenow accepts any digit after the leading 9 on a 9-digit local.Brazil has no 9-digit landline, so
9+ 8 digits is unambiguously mobile;requiring 6–9 right after the 9 rejected real numbers.
Validation
Against a live GOWS session:
5585991203123(extra 9) and558591203123(correct form) resolve to thesame chat id, with a single usync shared between them through the cache
0800sent in either dialed form addresses the stored numberUnit tests in
brPhone.test.tsandsession.abc.brPhone.test.ts.Please excuse my explanation of our complex Brazilian numbering system;