pub(crate) struct Button {
pub id: String,
pub label: String,
}
impl Button {
pub fn new(id: impl Into<String>, label: impl Into<String>) -> Self {
Self {
id: id.into(),
label: label.into(),
}
}
}
pub(crate) const MAX_BUTTONS: usize = 3;
const NATIVE_FLOW_VERSION: i32 = 1;
pub(crate) fn build(
body: &str,
footer: Option<&str>,
buttons: &[Button],
) -> waproto::whatsapp::Message {
use waproto::whatsapp::message::interactive_message::native_flow_message::NativeFlowButton;
use waproto::whatsapp::message::interactive_message::{
Body, Footer, InteractiveMessage as InteractiveOneof, NativeFlowMessage,
};
let flow_buttons: Vec<NativeFlowButton> = buttons
.iter()
.take(MAX_BUTTONS)
.map(|b| NativeFlowButton {
name: Some("quick_reply".to_string()),
button_params_json: Some(button_params(&b.id, &b.label)),
})
.collect();
let interactive = waproto::whatsapp::message::InteractiveMessage {
body: Body {
text: Some(body.to_string()),
}
.into(),
footer: footer
.map(|f| Footer {
text: Some(f.to_string()),
..Default::default()
})
.into(),
interactive_message: Some(InteractiveOneof::NativeFlowMessage(Box::new(
NativeFlowMessage {
buttons: flow_buttons,
message_version: Some(NATIVE_FLOW_VERSION),
..Default::default()
},
))),
..Default::default()
};
let inner = waproto::whatsapp::Message {
message_context_info: waproto::whatsapp::MessageContextInfo {
device_list_metadata_version: Some(2),
..Default::default()
}
.into(),
interactive_message: interactive.into(),
..Default::default()
};
waproto::whatsapp::Message {
view_once_message: waproto::whatsapp::message::FutureProofMessage {
message: inner.into(),
}
.into(),
..Default::default()
}
}
fn button_params(id: &str, label: &str) -> String {
serde_json::json!({ "display_text": label, "id": id }).to_string()
}
pub(crate) fn parse_tap(msg: &waproto::whatsapp::Message) -> Option<String> {
use waproto::whatsapp::message::interactive_response_message::InteractiveResponseMessage as ResponseOneof;
if let Some(resp) = msg.interactive_response_message.as_option()
&& let Some(ResponseOneof::NativeFlowResponseMessage(flow)) =
resp.interactive_response_message.as_ref()
&& let Some(params) = flow.params_json.as_deref()
&& let Some(id) = id_from_params(params)
{
return Some(id);
}
msg.buttons_response_message
.as_option()
.and_then(|b| b.selected_button_id.as_deref())
.map(str::to_string)
}
fn id_from_params(params: &str) -> Option<String> {
serde_json::from_str::<serde_json::Value>(params)
.ok()?
.get("id")?
.as_str()
.map(str::to_string)
}
pub(crate) fn parse_suggestion_tap(id: &str) -> Option<usize> {
let n: usize = id.strip_prefix("wa_suggest_")?.parse().ok()?;
(n >= 1).then_some(n)
}
pub(crate) fn suggestion_card_fits(count: usize) -> bool {
(1..=MAX_BUTTONS).contains(&count)
}
pub(crate) const MAX_POLL_OPTIONS: usize = 12;
pub(crate) fn suggestion_poll_fits(count: usize) -> bool {
(2..=MAX_POLL_OPTIONS).contains(&count)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum SuggestionSurface {
Card,
Poll,
Text,
}
pub(crate) fn suggestion_surface(count: usize, interactive: bool) -> SuggestionSurface {
if !interactive {
return SuggestionSurface::Text;
}
if suggestion_card_fits(count) {
SuggestionSurface::Card
} else if suggestion_poll_fits(count) {
SuggestionSurface::Poll
} else {
SuggestionSurface::Text
}
}