Skip to content

Commit bb1c374

Browse files
authored
Merge pull request #473 from moltis-org/bloom-porcupine
fix(whatsapp): improve discoverability and debug logging
2 parents 919c443 + 4d8ec43 commit bb1c374

3 files changed

Lines changed: 120 additions & 0 deletions

File tree

crates/config/src/template.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -581,6 +581,20 @@ reset_on_exit = true # Reset serve/funnel when gateway shuts down
581581
# External messaging integrations.
582582
583583
[channels]
584+
# Which channel types appear in the web UI's "+ Add Channel" menu.
585+
# Default: ["telegram", "discord", "slack"]
586+
# Add "whatsapp" or "msteams" to enable them in the UI.
587+
# offered = ["telegram", "discord", "slack", "whatsapp"]
588+
589+
# WhatsApp linked-device accounts
590+
# [channels.whatsapp.my-bot]
591+
# dm_policy = "open" # "open", "allowlist", or "disabled"
592+
# group_policy = "disabled" # "open", "allowlist", or "disabled"
593+
# model = "anthropic/claude-sonnet-4-20250514"
594+
# model_provider = "anthropic"
595+
# otp_self_approval = true # OTP self-approval for non-allowlisted DM users
596+
# otp_cooldown_secs = 300 # Cooldown after 3 failed OTP attempts
597+
584598
# Telegram bots
585599
# [channels.telegram.my-bot]
586600
# token = "..." # Bot token from @BotFather

crates/whatsapp/src/handlers.rs

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -396,6 +396,13 @@ async fn handle_message(
396396
handle_location(&msg, account_id, reply_to, meta, chat_jid, state).await;
397397
},
398398
ChannelMessageKind::Other => {
399+
info!(
400+
account_id = %state.account_id,
401+
chat = %chat_jid,
402+
"unhandled WhatsApp message type — replying with error. \
403+
Message fields present: {}",
404+
describe_message_fields(&msg),
405+
);
399406
let reply_msg = wa::Message {
400407
conversation: Some(
401408
"Sorry, I can't understand that message type. Check logs for details.".into(),
@@ -717,6 +724,42 @@ fn is_owner_user(jid: &Jid, own_pn: Option<&Jid>, own_lid: Option<&Jid>) -> bool
717724
|| own_lid.is_some_and(|lid| lid.is_same_user_as(jid))
718725
}
719726

727+
/// List which `Option` fields on `wa::Message` are `Some`, giving operators a
728+
/// concrete clue about the unhandled message type (e.g. "sticker_message, reaction_message").
729+
///
730+
/// Only checks fields that are NOT already handled by `classify_message` to avoid
731+
/// misleading output — if a field appears here it genuinely was not dispatched.
732+
fn describe_message_fields(msg: &wa::Message) -> String {
733+
let mut present = Vec::new();
734+
macro_rules! check {
735+
($($field:ident),+ $(,)?) => {
736+
$(if msg.$field.is_some() { present.push(stringify!($field)); })+
737+
};
738+
}
739+
// Omit fields already handled by classify_message:
740+
// conversation, extended_text_message, image_message, audio_message,
741+
// video_message, document_message, location_message, live_location_message
742+
check!(
743+
sender_key_distribution_message,
744+
contact_message,
745+
call,
746+
protocol_message,
747+
contacts_array_message,
748+
sticker_message,
749+
reaction_message,
750+
poll_creation_message,
751+
poll_update_message,
752+
interactive_message,
753+
edited_message,
754+
event_message,
755+
);
756+
if present.is_empty() {
757+
"none".to_owned()
758+
} else {
759+
present.join(", ")
760+
}
761+
}
762+
720763
/// Classify the inbound message kind based on its content.
721764
///
722765
/// Media types take priority over text — an image with a caption is still `Photo`,
@@ -949,4 +992,39 @@ mod tests {
949992
assert!(is_self_chat);
950993
assert!(!sender_is_owner);
951994
}
995+
996+
#[test]
997+
fn describe_message_fields_reports_present_fields() {
998+
let msg = wa::Message {
999+
sticker_message: Some(Default::default()),
1000+
reaction_message: Some(Default::default()),
1001+
..Default::default()
1002+
};
1003+
let desc = describe_message_fields(&msg);
1004+
assert!(
1005+
desc.contains("sticker_message"),
1006+
"expected sticker_message in: {desc}"
1007+
);
1008+
assert!(
1009+
desc.contains("reaction_message"),
1010+
"expected reaction_message in: {desc}"
1011+
);
1012+
}
1013+
1014+
#[test]
1015+
fn describe_message_fields_empty_message_returns_none() {
1016+
let msg = wa::Message::default();
1017+
assert_eq!(describe_message_fields(&msg), "none");
1018+
}
1019+
1020+
#[test]
1021+
fn describe_message_fields_excludes_handled_types() {
1022+
// image_message is handled by classify_message — should NOT appear
1023+
let msg = wa::Message {
1024+
image_message: Some(Default::default()),
1025+
..Default::default()
1026+
};
1027+
let desc = describe_message_fields(&msg);
1028+
assert_eq!(desc, "none", "handled fields should not appear: {desc}");
1029+
}
9521030
}

docs/src/whatsapp.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,19 @@ whatsapp = ["moltis-gateway/whatsapp"]
5151
When disabled, all WhatsApp code is compiled out — no QR code library, no
5252
Signal Protocol store, no WhatsApp event handlers.
5353

54+
```admonish important title="Enable in Channel List"
55+
WhatsApp is not shown in the web UI by default. Add it to the offered
56+
channels list in `moltis.toml`:
57+
58+
\`\`\`toml
59+
[channels]
60+
offered = ["telegram", "discord", "slack", "whatsapp"]
61+
\`\`\`
62+
63+
Restart Moltis after changing this setting. The **+ Add Channel** menu
64+
will then include the WhatsApp option.
65+
```
66+
5467
## Quick Start (Web UI)
5568

5669
The fastest way to connect WhatsApp:
@@ -336,6 +349,21 @@ Switch to the **Senders** tab to see everyone who has messaged the bot:
336349

337350
## Troubleshooting
338351

352+
### WhatsApp Not in Add Channel Menu
353+
354+
- WhatsApp is not offered by default. Add `"whatsapp"` to the `offered` list in `moltis.toml`:
355+
```toml
356+
[channels]
357+
offered = ["telegram", "discord", "slack", "whatsapp"]
358+
```
359+
- Restart Moltis after changing this setting
360+
361+
### "Can't Understand That Message Type"
362+
363+
- This means the bot received a message type it doesn't handle (e.g. stickers, reactions, polls)
364+
- Check the server logs for an `info` entry that lists which message fields were present
365+
- Supported types: text, images, audio, voice notes, video, documents, and locations
366+
339367
### QR Code Not Appearing
340368

341369
- Ensure the `whatsapp` feature is enabled (it is by default)

0 commit comments

Comments
 (0)