From 8468ce19cbc3ad8dd35c8792cc117be800e0cc42 Mon Sep 17 00:00:00 2001 From: IAmTomahawkx Date: Thu, 11 Jul 2024 19:44:52 -0700 Subject: [PATCH] fix apple push notifications Signed-off-by: IAmTomahawkx --- Cargo.lock | 4 +- .../database/src/models/messages/model.rs | 6 +- .../database/src/tasks/apple_notifications.rs | 113 ++++++++++++++---- crates/core/database/src/tasks/web_push.rs | 2 +- crates/core/models/src/v0/embeds.rs | 20 ++-- crates/core/models/src/v0/messages.rs | 26 +++- 6 files changed, 126 insertions(+), 45 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index be82b60a..6402cac7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3788,9 +3788,9 @@ dependencies = [ [[package]] name = "revolt_a2" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "466eb5262fcbb26e6e10b8a0acf56eb7cc095008132ff4ebddfc44d8672f8066" +checksum = "edbe1f79cb41271d3cd8f932d75dddeba963c19dc93d1ee6cbe0391b495ab2f5" dependencies = [ "base64 0.21.3", "erased-serde", diff --git a/crates/core/database/src/models/messages/model.rs b/crates/core/database/src/models/messages/model.rs index dd507a30..56b289a3 100644 --- a/crates/core/database/src/models/messages/model.rs +++ b/crates/core/database/src/models/messages/model.rs @@ -444,8 +444,8 @@ impl Message { ) -> Result<()> { self.send_without_notifications( db, - user, - member, + user.clone(), + member.clone(), matches!(channel, Channel::DirectMessage { .. }), generate_embeds, ) @@ -463,7 +463,7 @@ impl Message { } }, PushNotification::from( - self.clone().into_model(None, None), + self.clone().into_model(user, member), Some(author), &channel.id(), ) diff --git a/crates/core/database/src/tasks/apple_notifications.rs b/crates/core/database/src/tasks/apple_notifications.rs index 4271afe5..bde459bb 100644 --- a/crates/core/database/src/tasks/apple_notifications.rs +++ b/crates/core/database/src/tasks/apple_notifications.rs @@ -6,23 +6,53 @@ use base64::{ }; use deadqueue::limited::Queue; use once_cell::sync::Lazy; -use revolt_a2::{Client, ClientConfig, DefaultNotificationBuilder, Endpoint}; -use revolt_a2::{Error, ErrorBody, ErrorReason, NotificationBuilder, Response}; +use revolt_a2::{ + request::{ + notification::{DefaultAlert, NotificationOptions}, + payload::{APSAlert, PayloadLike, APS}, + }, + Client, ClientConfig, DefaultNotificationBuilder, Endpoint, Error, ErrorBody, ErrorReason, + NotificationBuilder, Priority, PushType, Response, +}; use revolt_config::config; use revolt_models::v0::{Message, PushNotification}; use crate::Database; -/// Payload information +/// Payload information, before assembly #[derive(Debug)] -pub struct ApnCustomPayload { +pub struct ApnPayload { message: Message, - serverId: String, + url: String, authorAvatar: String, authorDisplayName: String, channelName: String, } +#[derive(Serialize, Debug)] +struct Payload<'a> { + aps: APS<'a>, + #[serde(skip_serializing)] + options: NotificationOptions<'a>, + #[serde(skip_serializing)] + device_token: &'a str, + + message: Message, + url: String, + authorAvatar: String, + authorDisplayName: String, + channelName: String, +} + +impl<'a> PayloadLike for Payload<'a> { + fn get_device_token(&self) -> &'a str { + self.device_token + } + fn get_options(&self) -> &NotificationOptions { + &self.options + } +} + /// Task information #[derive(Debug)] pub struct ApnTask { @@ -45,7 +75,7 @@ pub struct ApnTask { category: String, /// Payload used by the iOS client to modify the notification - payload: ApnCustomPayload, + custom_payload: ApnPayload, } impl ApnTask { @@ -55,7 +85,10 @@ impl ApnTask { // in a group, it would look like "Sendername in groupname" // in a dm it should just be "Sendername". // not sure how feasible all those are given the PushNotification object as it currently stands. - todo!(); + format!( + "{} in {}", + notification.author, notification.message.channel + ) // TODO: this absolutely needs a channel name } pub fn from_notification( @@ -70,12 +103,12 @@ impl ApnTask { body: notification.body.to_string(), thread_id: notification.tag.to_string(), category: "ALERT_MESSAGE".to_string(), - payload: ApnCustomPayload { - message: (), - serverId: (), - authorAvatar: notification.icon.to_string(), - authorDisplayName: notification.author.to_string(), - channelName: (), + custom_payload: ApnPayload { + message: notification.message.clone(), + url: notification.url.clone(), + authorAvatar: notification.icon.clone(), + authorDisplayName: notification.author.clone(), + channelName: "#fetchchannelnamehere".to_string(), // TODO: get actual channel name }, } } @@ -110,8 +143,7 @@ pub async fn worker(db: Database) { .decode(config.api.apn.pkcs8) .expect("valid `pcks8`"); - let client_config = ClientConfig::default(); - client_config.endpoint = endpoint; + let client_config = ClientConfig::new(endpoint); let client = Client::token( &mut Cursor::new(pkcs8), @@ -121,21 +153,52 @@ pub async fn worker(db: Database) { ) .expect("could not create APN client"); + let payload_options = NotificationOptions { + apns_id: None, + apns_push_type: Some(PushType::Alert), + apns_expiration: None, + apns_priority: Some(Priority::High), + apns_topic: Some("chat.revolt.app"), + apns_collapse_id: None, + }; + loop { let task = Q.pop().await; - let payload = DefaultNotificationBuilder::new() - .set_title(&task.title) - .set_body(&task.body) - .set_thread_id(&task.thread_id) - .set_category(&task.category) - .set_mutable_content() // allows the service extension to execute - .build(&task.device_token, Default::default()); - payload.data = &task.payload; // this looks like a job for someone more rust-inclined than me. -tom + let payload: Payload = Payload { + aps: APS { + alert: Some(APSAlert::Default(DefaultAlert { + title: Some(&task.title), + subtitle: None, + body: Some(&task.body), + title_loc_key: None, + title_loc_args: None, + action_loc_key: None, + loc_key: None, + loc_args: None, + launch_image: None, + })), + badge: Some(1), + sound: None, + thread_id: Some(&task.thread_id), + content_available: Some(1), + category: Some(&task.category), + mutable_content: Some(1), + url_args: None, + }, + device_token: &task.device_token, + options: payload_options.clone(), + message: task.custom_payload.message, + url: task.custom_payload.url, + authorAvatar: task.custom_payload.authorAvatar, + authorDisplayName: task.custom_payload.authorDisplayName, + channelName: task.custom_payload.channelName, + }; - println!("sending APNS payload: {:?}", payload.data); + let resp = client.send(payload).await; + //println!("response from APNS: {:?}", resp); - if let Err(err) = client.send(payload).await { + if let Err(err) = resp { match err { Error::ResponseError(Response { error: diff --git a/crates/core/database/src/tasks/web_push.rs b/crates/core/database/src/tasks/web_push.rs index efe5fd7e..3508b64d 100644 --- a/crates/core/database/src/tasks/web_push.rs +++ b/crates/core/database/src/tasks/web_push.rs @@ -6,7 +6,6 @@ use base64::{ Engine as _, }; use deadqueue::limited::Queue; -use fcm::FcmError; use once_cell::sync::Lazy; use revolt_config::config; use revolt_models::v0::PushNotification; @@ -82,6 +81,7 @@ pub async fn worker(db: Database) { tag, timestamp: _, url: _, + message: _, } = &task.payload; let mut notification = fcm::NotificationBuilder::new(); diff --git a/crates/core/models/src/v0/embeds.rs b/crates/core/models/src/v0/embeds.rs index faeeeb3d..1db81bed 100644 --- a/crates/core/models/src/v0/embeds.rs +++ b/crates/core/models/src/v0/embeds.rs @@ -92,38 +92,38 @@ auto_derived!( pub struct WebsiteMetadata { /// Direct URL to web page #[serde(skip_serializing_if = "Option::is_none")] - url: Option, + pub url: Option, /// Original direct URL #[serde(skip_serializing_if = "Option::is_none")] - original_url: Option, + pub original_url: Option, /// Remote content #[serde(skip_serializing_if = "Option::is_none")] - special: Option, + pub special: Option, /// Title of website #[serde(skip_serializing_if = "Option::is_none")] - title: Option, + pub title: Option, /// Description of website #[serde(skip_serializing_if = "Option::is_none")] - description: Option, + pub description: Option, /// Embedded image #[serde(skip_serializing_if = "Option::is_none")] - image: Option, + pub image: Option, /// Embedded video #[serde(skip_serializing_if = "Option::is_none")] - video: Option