Skip to content
Merged
Show file tree
Hide file tree
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
53 changes: 42 additions & 11 deletions src/features/groups.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,14 @@ pub use wacore::iq::contacts::SetProfilePictureResponse;
use wacore::iq::groups::{
AcceptGroupInviteIq, AcceptGroupInviteV4Iq, AcknowledgeGroupIq, AddParticipantsIq,
BatchGetGroupInfoIq, CancelMembershipRequestsIq, DemoteParticipantsIq, GetGroupInviteInfoIq,
GetGroupInviteLinkIq, GetGroupProfilePicturesIq, GetMembershipRequestsIq, GroupCreateIq,
GroupInfoOutcome, GroupInfoResponse, GroupParticipantResponse, GroupParticipatingIq,
GroupQueryIq, LeaveGroupIq, MembershipRequestActionIq, PromoteParticipantsIq,
RemoveParticipantsIncludingLinkedGroupsIq, RemoveParticipantsIq, RevokeRequestCodeIq,
SetAllowAdminReportsIq, SetGroupAnnouncementIq, SetGroupDescriptionIq, SetGroupEphemeralIq,
SetGroupHistoryIq, SetGroupLockedIq, SetGroupMembershipApprovalIq, SetGroupSubjectIq,
SetMemberAddModeIq, SetNoFrequentlyForwardedIq, normalize_participants,
GetGroupInviteLinkIq, GetGroupProfilePicturesIq, GetMembershipRequestsIq,
GetReportedGroupMessagesIq, GroupCreateIq, GroupInfoOutcome, GroupInfoResponse,
GroupParticipantResponse, GroupParticipatingIq, GroupQueryIq, LeaveGroupIq,
MembershipRequestActionIq, PromoteParticipantsIq, RemoveParticipantsIncludingLinkedGroupsIq,
RemoveParticipantsIq, ReportGroupMessagesIq, RevokeRequestCodeIq, SetAllowAdminReportsIq,
SetGroupAnnouncementIq, SetGroupDescriptionIq, SetGroupEphemeralIq, SetGroupHistoryIq,
SetGroupLockedIq, SetGroupMembershipApprovalIq, SetGroupSubjectIq, SetMemberAddModeIq,
SetNoFrequentlyForwardedIq, normalize_participants,
};
use wacore::iq::mex_operations::update_group_property;
use wacore::types::message::AddressingMode;
Expand All @@ -28,10 +29,11 @@ use wacore_binary::{Jid, JidExt as _};
use wacore::iq::groups::BatchGroupInfoResult as RawBatchResult;
pub use wacore::iq::groups::{
GroupAppealStatus, GroupCreateOptions, GroupDescription, GroupEphemeralSettings,
GroupJoinError, GroupParticipantDetails, GroupParticipantOptions, GroupProfilePicture,
GroupSubject, GrowthLockInfo, InviteInfoError, JoinGroupResult, MemberAddMode, MemberLinkMode,
MemberShareHistoryMode, MembershipApprovalMode, MembershipRequest, ParticipantChangeResponse,
ParticipantType, PictureType,
GroupJoinError, GroupMessageReporter, GroupParticipantDetails, GroupParticipantOptions,
GroupProfilePicture, GroupSubject, GrowthLockInfo, InviteInfoError, JoinGroupResult,
MemberAddMode, MemberLinkMode, MemberShareHistoryMode, MembershipApprovalMode,
MembershipRequest, ParticipantChangeResponse, ParticipantType, PictureType,
ReportedGroupMessage, ReportedGroupMessages,
};

/// Error returned by group operations (metadata queries, participant and
Expand Down Expand Up @@ -1086,6 +1088,35 @@ impl<'a> Groups<'a> {
.await?)
}

/// Report messages to the group's admins.
///
/// Reports to the group's own admins, not to WhatsApp; the group has to
/// allow admin reports for the server to accept it. The result is empty on
/// success and says nothing about which ids were accepted.
pub async fn report_messages_to_admins(
&self,
jid: impl Into<Jid>,
message_ids: &[String],
) -> Result<(), GroupError> {
let jid = &jid.into();
Ok(self
.client
.execute(ReportGroupMessagesIq::new(jid, message_ids))
.await?)
}

/// Fetch the messages reported to this group's admins.
pub async fn get_reported_messages(
&self,
jid: impl Into<Jid>,
) -> Result<ReportedGroupMessages, GroupError> {
let jid = &jid.into();
Ok(self
.client
.execute(GetReportedGroupMessagesIq::new(jid))
.await?)
}

/// Approve pending membership requests.
pub async fn approve_membership_requests(
&self,
Expand Down
11 changes: 6 additions & 5 deletions src/features/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,11 +69,12 @@ pub use events::{EventCreationParams, EventResponseType, Events};

pub use groups::{
BatchGroupResult, CreateGroupResult, GroupAppealStatus, GroupCreateOptions, GroupDescription,
GroupEphemeralSettings, GroupError, GroupJoinError, GroupMetadata, GroupParticipant,
GroupParticipantDetails, GroupParticipantOptions, GroupProfilePicture, GroupSubject, Groups,
GrowthLockInfo, InviteInfoError, JoinGroupResult, MemberAddMode, MemberLinkMode,
MemberShareHistoryMode, MembershipApprovalMode, MembershipRequest, ParticipantChangeResponse,
ParticipantType, PictureType, PreviousDescription,
GroupEphemeralSettings, GroupError, GroupJoinError, GroupMessageReporter, GroupMetadata,
GroupParticipant, GroupParticipantDetails, GroupParticipantOptions, GroupProfilePicture,
GroupSubject, Groups, GrowthLockInfo, InviteInfoError, JoinGroupResult, MemberAddMode,
MemberLinkMode, MemberShareHistoryMode, MembershipApprovalMode, MembershipRequest,
ParticipantChangeResponse, ParticipantType, PictureType, PreviousDescription,
ReportedGroupMessage, ReportedGroupMessages,
};

pub use labels::Labels;
Expand Down
20 changes: 20 additions & 0 deletions src/features/stanza.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ pub enum StanzaResponseError {
pub struct RetryRequestOptions {
reason: RetryReason,
force_include_keys: bool,
decrypt_fail_mode: wacore::types::events::DecryptFailMode,
}

impl RetryRequestOptions {
Expand All @@ -91,6 +92,7 @@ impl RetryRequestOptions {
Self {
reason: RetryReason::UnknownError,
force_include_keys: false,
decrypt_fail_mode: wacore::types::events::DecryptFailMode::Show,
}
}

Expand All @@ -115,6 +117,24 @@ impl RetryRequestOptions {
pub const fn force_include_keys(self) -> bool {
self.force_include_keys
}

/// Record that the stanza being retried asked for its decryption failures
/// to be hidden, which the receipt reports back in its `<meta mode>`.
///
/// Defaults to [`DecryptFailMode::Show`](wacore::types::events::DecryptFailMode::Show),
/// so a caller that does not set it sends no `<meta>` at all.
pub const fn with_decrypt_fail_mode(
mut self,
decrypt_fail_mode: wacore::types::events::DecryptFailMode,
) -> Self {
self.decrypt_fail_mode = decrypt_fail_mode;
self
}

/// How the stanza being retried asked its failures to be surfaced.
pub const fn decrypt_fail_mode(self) -> wacore::types::events::DecryptFailMode {
self.decrypt_fail_mode
}
}

impl Default for RetryRequestOptions {
Expand Down
29 changes: 15 additions & 14 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -219,26 +219,27 @@ pub use features::{
CommunitySubgroup, ContactError, Contacts, CoverPhotoUpload, CreateCommunityOptions,
CreateCommunityResult, CreateGroupResult, DayOfWeek, EncType, EncryptedEdit,
EventCreationParams, EventResponseType, Events, GroupAppealStatus, GroupCreateOptions,
GroupDescription, GroupEphemeralSettings, GroupError, GroupJoinError, GroupMetadata,
GroupParticipant, GroupParticipantDetails, GroupParticipantOptions, GroupProfilePicture,
GroupSubject, GroupType, Groups, GrowthLockInfo, ImporterAddress, InviteInfoError,
IsOnWhatsAppResult, JoinGroupResult, Labels, LinkSubgroupsResult, MediaRetryResult,
MediaReupload, MediaReuploadError, MediaReuploadRequest, MemberAddMode, MemberLinkMode,
MemberShareHistoryMode, MembershipApprovalMode, MembershipRequest, MessageEditError,
MessageRetransmission, Mex, MexError, MexErrorExtensions, MexGraphQLError, MexRequest,
MexResponse, NackReason, NewChatMessageCapping, Newsletter, NewsletterAdminInfo,
GroupDescription, GroupEphemeralSettings, GroupError, GroupJoinError, GroupMessageReporter,
GroupMetadata, GroupParticipant, GroupParticipantDetails, GroupParticipantOptions,
GroupProfilePicture, GroupSubject, GroupType, Groups, GrowthLockInfo, ImporterAddress,
InviteInfoError, IsOnWhatsAppResult, JoinGroupResult, Labels, LinkSubgroupsResult,
MediaRetryResult, MediaReupload, MediaReuploadError, MediaReuploadRequest, MemberAddMode,
MemberLinkMode, MemberShareHistoryMode, MembershipApprovalMode, MembershipRequest,
MessageEditError, MessageRetransmission, Mex, MexError, MexErrorExtensions, MexGraphQLError,
MexRequest, MexResponse, NackReason, NewChatMessageCapping, Newsletter, NewsletterAdminInfo,
NewsletterAdminProfile, NewsletterError, NewsletterFollower, NewsletterMessage,
NewsletterMessageType, NewsletterMetadata, NewsletterReactionCount, NewsletterRole,
NewsletterState, NewsletterVerification, Order, OrderPriceDetails, OrderProduct,
ParticipantChangeResponse, ParticipantType, PictureType, PollError, PollOptionResult,
PollVoteCiphertext, Polls, Presence, PresenceError, PresenceStatus, PreviousDescription, Price,
Product, ProductAvailability, ProductImage, ProductVideo, Profile, ProfileError,
ProfilePicture, QuickReplies, ReachoutTimelock, RetryReason, RetryRequestError,
RetryRequestOptions, RetryRequestOutcome, SalePrice, SecretEncKind, SecretEncrypted,
SetProfilePictureResponse, Signal, SignalError, SignalSessionInfo, SignalSessionMigration,
StanzaRejection, StanzaResponseError, Status, StatusPrivacySetting, StatusSendOptions,
SyncActionMessageRange, TcToken, TcTokenError, UnlinkSubgroupsResult, UserInfo,
UsyncSubprotocolError, VariantProperty, VerifiedName, group_type, message_key, message_range,
ProfilePicture, QuickReplies, ReachoutTimelock, ReportedGroupMessage, ReportedGroupMessages,
RetryReason, RetryRequestError, RetryRequestOptions, RetryRequestOutcome, SalePrice,
SecretEncKind, SecretEncrypted, SetProfilePictureResponse, Signal, SignalError,
SignalSessionInfo, SignalSessionMigration, StanzaRejection, StanzaResponseError, Status,
StatusPrivacySetting, StatusSendOptions, SyncActionMessageRange, TcToken, TcTokenError,
UnlinkSubgroupsResult, UserInfo, UsyncSubprotocolError, VariantProperty, VerifiedName,
group_type, message_key, message_range,
};

pub mod bot;
Expand Down
31 changes: 31 additions & 0 deletions src/message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,12 @@ pub(crate) struct EncPayload {
/// payload are skipped, so a position within a bucket is not a position in
/// the stanza.
pub enc_index: usize,
/// The node's `state` attribute, verbatim. Absent on ordinary traffic, so
/// the common case allocates nothing.
pub state: Option<String>,
/// The node's `session_type` attribute, verbatim. Absent on ordinary
/// traffic, so the common case allocates nothing.
pub session_type: Option<String>,
}

impl EncPayload {
Expand All @@ -91,11 +97,16 @@ impl EncPayload {
) -> Option<Self> {
let enc_type = EncType::from_wire(enc_node.attrs().optional_string("type")?.as_ref())?;
let padding_version = enc_node.attrs().optional_u64("v").unwrap_or(2) as u8;
let mut attrs = enc_node.attrs();
Some(Self {
ciphertext,
enc_type,
padding_version,
enc_index,
state: attrs.optional_string("state").map(|s| s.into_owned()),
session_type: attrs
.optional_string("session_type")
.map(|s| s.into_owned()),
})
}

Expand Down Expand Up @@ -123,6 +134,24 @@ impl EncPayload {
}
}

/// The `<enc>` attributes this build carries to the consumer without acting on
/// them. Borrowed from the payload they came from, so passing them costs
/// nothing on a node that declared neither.
#[derive(Clone, Copy, Default)]
pub(crate) struct EncNodeAnnotations<'a> {
pub state: Option<&'a str>,
pub session_type: Option<&'a str>,
}

impl EncPayload {
pub(crate) fn annotations(&self) -> EncNodeAnnotations<'_> {
EncNodeAnnotations {
state: self.state.as_deref(),
session_type: self.session_type.as_deref(),
}
}
}

/// Parsed and classified message ready for decryption. All data is owned --
/// the original node tree is no longer borrowed.
pub(crate) struct ClassifiedMessage {
Expand Down Expand Up @@ -233,6 +262,8 @@ struct DeferredPlaintext {
/// Which `<enc>` in the stanza produced this — [`EncPayload::enc_index`],
/// carried through because the buffer drains after the decrypt loop.
enc_index: usize,
state: Option<String>,
session_type: Option<String>,
}

fn should_process_skmsg_after_session(
Expand Down
4 changes: 4 additions & 0 deletions src/message/msg_secret.rs
Original file line number Diff line number Diff line change
Expand Up @@ -500,6 +500,8 @@ impl Client {
// Read off before the payload is consumed below.
let enc_index = payload.enc_index;
let enc_type = payload.enc_type.as_wire_str();
let enc_state = payload.state.clone();
let enc_session_type = payload.session_type.clone();

let ms_msg = match waproto::codec::message_secret_message_decode(&payload.ciphertext) {
Ok(m) => m,
Expand Down Expand Up @@ -801,6 +803,8 @@ impl Client {
.info(Arc::clone(info))
.enc_index(enc_index)
.enc_type(enc_type)
.maybe_state(enc_state.clone())
.maybe_session_type(enc_session_type.clone())
.payload(plaintext.clone())
.build(),
));
Expand Down
Loading
Loading