Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 69 additions & 1 deletion src/features/newsletter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use crate::features::mex::{MexError, mex_request};
use prost::Message as ProtoMessage;
use wacore::iq::mex_operations::{
create_newsletter, fetch_all_newsletters_metadata, fetch_newsletter, join_newsletter,
leave_newsletter, update_newsletter,
leave_newsletter, update_newsletter, update_newsletter_user_setting,
};
use wacore::iq::newsletter::NEWSLETTER_XMLNS;
use wacore::request::InfoQuery;
Expand Down Expand Up @@ -290,6 +290,46 @@ impl<'a> Newsletter<'a> {
parse_newsletter_metadata(newsletter)
}

/// Mute or unmute a newsletter's follower-activity notifications
/// (WA Web's `MUTE_FOLLOWER_ACTIVITY`). `muted = true` silences them.
pub async fn set_follower_mute(&self, jid: &Jid, muted: bool) -> Result<(), MexError> {
self.set_user_setting_mute(jid, "MUTE_FOLLOWER_ACTIVITY", muted)
.await
}

/// Mute or unmute a newsletter's admin-activity notifications
/// (WA Web's `MUTE_ADMIN_ACTIVITY`). Only meaningful for owners/admins.
pub async fn set_admin_mute(&self, jid: &Jid, muted: bool) -> Result<(), MexError> {
self.set_user_setting_mute(jid, "MUTE_ADMIN_ACTIVITY", muted)
.await
}

async fn set_user_setting_mute(
&self,
jid: &Jid,
mute_type: &str,
muted: bool,
) -> Result<(), MexError> {
let response = self
.client
.mex()
.mutate(mex_request!(
update_newsletter_user_setting,
mute_user_setting_variables(jid, mute_type, muted)
))
.await?;

let data = response
.data
.ok_or_else(|| MexError::PayloadParsing("missing data".into()))?;
if data["xwa2_newsletter_update_user_setting"].is_null() {
return Err(MexError::PayloadParsing(format!(
"failed to update newsletter user setting: {jid}"
)));
}
Ok(())
}

/// Fetch metadata for a newsletter by its invite code.
pub async fn get_metadata_by_invite(
&self,
Expand Down Expand Up @@ -643,11 +683,39 @@ fn parse_newsletter_messages_response(
Ok(result)
}

/// Build the MEX variables for `update_newsletter_user_setting`. WA Web
/// (WAWebNewsletterUpdateUserSettingJob) sends `{ input: { newsletter_id, type, value } }`
/// with value ON/OFF; the mute-expiration is local DB state, never on the wire. The
/// generated op's input type is opaque (a bare string), so the structured object is
/// passed directly as variables.
fn mute_user_setting_variables(jid: &Jid, mute_type: &str, muted: bool) -> serde_json::Value {
serde_json::json!({
"input": {
"newsletter_id": jid.to_string(),
"type": mute_type,
"value": if muted { "ON" } else { "OFF" },
}
})
}

#[cfg(test)]
mod tests {
use super::*;
use wacore_binary::builder::NodeBuilder;

#[test]
fn mute_variables_match_wa_web_shape() {
let jid: Jid = "111222333@newsletter".parse().unwrap();
let on = mute_user_setting_variables(&jid, "MUTE_FOLLOWER_ACTIVITY", true);
assert_eq!(on["input"]["newsletter_id"], "111222333@newsletter");
assert_eq!(on["input"]["type"], "MUTE_FOLLOWER_ACTIVITY");
assert_eq!(on["input"]["value"], "ON");

let off = mute_user_setting_variables(&jid, "MUTE_ADMIN_ACTIVITY", false);
assert_eq!(off["input"]["type"], "MUTE_ADMIN_ACTIVITY");
assert_eq!(off["input"]["value"], "OFF");
}

#[test]
fn test_missing_type_attribute_defaults_to_text() {
let response = NodeBuilder::new("iq")
Expand Down
Loading