Add endpoints for push notifications

This commit is contained in:
Karlinde 2020-01-14 21:58:28 +01:00 committed by Jonas Platte
parent 0314384c60
commit 505721bdad
14 changed files with 765 additions and 0 deletions

View File

@ -37,6 +37,7 @@ Improvements:
* Add `r0::account::bind_3pid`
* Add `r0::account::delete_3pid`
* Add `r0::account::unbind_3pid`
* Add `r0::push` endpoints
# 0.5.0

View File

@ -1 +1,327 @@
//! Endpoints for push notifications.
use std::fmt::{Display, Formatter, Result as FmtResult};
use serde::{
de::{Error as SerdeError, MapAccess, Unexpected, Visitor},
ser::SerializeStruct,
Deserialize, Deserializer, Serialize, Serializer,
};
use serde_json::Value as JsonValue;
pub mod delete_pushrule;
pub mod get_notifications;
pub mod get_pushers;
pub mod get_pushrule;
pub mod get_pushrule_actions;
pub mod get_pushrule_enabled;
pub mod get_pushrules_all;
pub mod get_pushrules_global_scope;
pub mod set_pusher;
pub mod set_pushrule;
pub mod set_pushrule_actions;
pub mod set_pushrule_enabled;
/// The kinds of push rules that are available
#[derive(Clone, Copy, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
#[serde(rename_all = "snake_case")]
pub enum RuleKind {
/// User-configured rules that override all other kinds
Override,
/// Lowest priority user-defined rules
Underride,
/// Sender-specific rules
Sender,
/// Room-specific rules
Room,
/// Content-specific rules
Content,
}
impl Display for RuleKind {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
let s = match self {
RuleKind::Override => "override",
RuleKind::Underride => "underride",
RuleKind::Sender => "sender",
RuleKind::Room => "room",
RuleKind::Content => "content",
};
write!(f, "{}", s)
}
}
/// A push rule
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct PushRule {
/// The actions to perform when this rule is matched.
pub actions: Vec<Action>,
/// Whether this is a default rule, or has been set explicitly.
pub default: bool,
/// Whether the push rule is enabled or not.
pub enabled: bool,
/// The ID of this rule.
pub rule_id: String,
/// The conditions that must hold true for an event in order for a rule to be applied to an event. A rule with no conditions always matches.
/// Only applicable to underride and override rules.
#[serde(skip_serializing_if = "Option::is_none")]
pub conditions: Option<Vec<PushCondition>>,
/// The glob-style pattern to match against. Only applicable to content rules.
#[serde(skip_serializing_if = "Option::is_none")]
pub pattern: Option<String>,
}
/// A condition for a push rule
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case")] // Using internally tagged enum representation to match the spec
pub enum PushCondition {
/// This is a glob pattern match on a field of the event.
EventMatch {
/// The dot-separated field of the event to match, e.g. `content.body`
key: String,
/// The glob-style pattern to match against.
pattern: String,
},
/// This matches unencrypted messages where `content.body` contains
/// the owner's display name in that room.
ContainsDisplayName,
/// This matches the current number of members in the room.
RoomMemberCount {
/// A decimal integer optionally prefixed by one of, ==, <, >, >= or <=.
/// Default prefix is ==.
is: String,
},
/// This takes into account the current power levels in the room, ensuring the
/// sender of the event has high enough power to trigger the notification.
SenderNotificationPermission {
/// A string that determines the power level the sender must have to
/// trigger notifications of a given type, such as `room`.
key: String,
},
}
/// Defines a pusher
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Pusher {
/// This is a unique identifier for this pusher. Max length, 512 bytes.
pub pushkey: String,
/// The kind of the pusher. If set to None in a call to set_pusher, this
/// will delete the pusher
pub kind: Option<PusherKind>,
/// This is a reverse-DNS style identifier for the application. Max length, 64 chars.
pub app_id: String,
/// A string that will allow the user to identify what application owns this pusher.
pub app_display_name: String,
/// A string that will allow the user to identify what device owns this pusher.
pub device_display_name: String,
/// This string determines which set of device specific rules this pusher executes.
#[serde(skip_serializing_if = "Option::is_none")]
pub profile_tag: Option<String>,
/// The preferred language for receiving notifications (e.g. 'en' or 'en-US')
pub lang: String,
/// Information for the pusher implementation itself.
pub data: PusherData,
}
/// Which kind a pusher is
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum PusherKind {
/// A pusher that sends HTTP pokes.
Http,
/// A pusher that emails the user with unread notifications.
Email,
}
/// Information for the pusher implementation itself.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct PusherData {
/// Required if the pusher's kind is http. The URL to use to send notifications to.
#[serde(skip_serializing_if = "Option::is_none")]
pub url: Option<String>,
/// The format to use when sending notifications to the Push Gateway.
#[serde(skip_serializing_if = "Option::is_none")]
pub format: Option<PushFormat>,
}
/// A special format that the homeserver should use when sending notifications to a Push Gateway.
/// Currently, only "event_id_only" is supported as of [Push Gateway API r0.1.1](https://matrix.org/docs/spec/push_gateway/r0.1.1#homeserver-behaviour)
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum PushFormat {
/// Require the homeserver to only send a reduced set of fields in the push.
EventIdOnly,
}
/// This represents the different actions that should be taken when a rule is matched, and
/// controls how notifications are delivered to the client.
// See https://matrix.org/docs/spec/client_server/r0.6.0#actions for details.
#[derive(Clone, Debug)]
pub enum Action {
/// Causes matching events to generate a notification.
Notify,
/// Prevents matching events from generating a notification.
DontNotify,
/// Behaves like notify but homeservers may choose to coalesce multiple events
/// into a single notification.
Coalesce,
/// Sets an entry in the 'tweaks' dictionary sent to the push gateway.
SetTweak {
/// The kind of this tweak
kind: TweakKind,
/// The value of the tweak, if any
value: Option<JsonValue>,
},
}
/// The different kinds of tweaks available
#[derive(Clone, Debug)]
pub enum TweakKind {
/// The "sound" tweak.
Sound,
/// The "highlight" tweak.
Highlight,
/// A name for a custom client-defined tweak.
Custom(String),
}
impl Serialize for Action {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match self {
Action::Notify => serializer.serialize_unit_variant("Action", 0, "notify"),
Action::DontNotify => serializer.serialize_unit_variant("Action", 1, "dont_notify"),
Action::Coalesce => serializer.serialize_unit_variant("Action", 2, "coalesce"),
Action::SetTweak { kind, value } => {
let kind_name = match &kind {
TweakKind::Sound => "sound",
TweakKind::Highlight => "highlight",
TweakKind::Custom(name) => name,
};
let num_fields = match value {
Some(_) => 2,
None => 1,
};
let mut s = serializer.serialize_struct("Action", num_fields)?;
s.serialize_field("set_tweak", kind_name)?;
match &value {
Some(value) => {
s.serialize_field("value", value)?;
}
None => {}
};
s.end()
}
}
}
}
impl<'de> Deserialize<'de> for Action {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct ActionVisitor;
impl<'de> Visitor<'de> for ActionVisitor {
type Value = Action;
fn expecting(&self, formatter: &mut Formatter<'_>) -> FmtResult {
write!(formatter, "a valid action object")
}
/// Match a simple action type
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: SerdeError,
{
match v {
"notify" => Ok(Action::Notify),
"dont_notify" => Ok(Action::DontNotify),
"coalesce" => Ok(Action::Coalesce),
s => Err(E::unknown_variant(
&s,
&["notify", "dont_notify", "coalesce"],
)),
}
}
/// Match the more complex set_tweaks action object as a key-value map
fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
where
A: MapAccess<'de>,
{
let mut tweak_kind: Option<TweakKind> = None;
let mut tweak_value: Option<JsonValue> = None;
// We loop over all entries in the map to find one with a "set_tweak" key to find
// which type of tweak is being set.
// Then we also try to find one with the "value" key if it exists.
while let Some((key, value)) = map.next_entry::<&str, JsonValue>()? {
match key {
"set_tweak" => {
let kind = match value.as_str() {
Some("sound") => TweakKind::Sound,
Some("highlight") => TweakKind::Highlight,
Some(s) => TweakKind::Custom(s.to_string()),
None => {
return Err(A::Error::invalid_type(
Unexpected::Other("non-string object"),
&"string",
))
}
};
tweak_kind = Some(kind);
}
"value" => {
tweak_value = Some(value);
}
_ => {}
}
}
match tweak_kind {
Some(kind) => Ok(Action::SetTweak {
kind,
value: tweak_value,
}),
None => Err(A::Error::invalid_type(
Unexpected::Other("object without \"set_tweak\" key"),
&"valid \"set_tweak\" action object",
)),
}
}
}
deserializer.deserialize_any(ActionVisitor)
}
}

View File

@ -0,0 +1,32 @@
//! [DELETE /_matrix/client/r0/pushrules/{scope}/{kind}/{ruleId}](https://matrix.org/docs/spec/client_server/r0.6.0#delete-matrix-client-r0-pushrules-scope-kind-ruleid)
use ruma_api::ruma_api;
use super::RuleKind;
ruma_api! {
metadata {
description: "This endpoint removes the push rule defined in the path.",
method: DELETE,
name: "delete_pushrule",
path: "/_matrix/client/r0/pushrules/:scope/:kind/:rule_id",
rate_limited: false,
requires_authentication: true,
}
request {
/// The scope to delete from. 'global' to specify global rules.
#[ruma_api(path)]
pub scope: String,
/// The kind of rule
#[ruma_api(path)]
pub kind: RuleKind,
/// The identifier for the rule.
#[ruma_api(path)]
pub rule_id: String,
}
response {}
}

View File

@ -0,0 +1,74 @@
//! [GET /_matrix/client/r0/notifications](https://matrix.org/docs/spec/client_server/r0.6.0#get-matrix-client-r0-notifications)
use js_int::UInt;
use ruma_api::{ruma_api, Outgoing};
use ruma_events::{collections::all, EventResult};
use ruma_identifiers::RoomId;
use serde::Serialize;
use super::Action;
ruma_api! {
metadata {
description: "Paginate through the list of events that the user has been, or would have been notified about.",
method: GET,
name: "get_notifications",
path: "/_matrix/client/r0/notifications",
rate_limited: false,
requires_authentication: true,
}
request {
/// Pagination token given to retrieve the next set of events.
#[ruma_api(query)]
#[serde(skip_serializing_if = "Option::is_none")]
pub from: Option<String>,
/// Limit on the number of events to return in this request.
#[ruma_api(query)]
#[serde(skip_serializing_if = "Option::is_none")]
pub limit: Option<UInt>,
/// Allows basic filtering of events returned. Supply "highlight" to return only events where
/// the notification had the 'highlight' tweak set.
#[ruma_api(query)]
#[serde(skip_serializing_if = "Option::is_none")]
pub only: Option<String>
}
response {
/// The token to supply in the from param of the next /notifications request in order
/// to request more events. If this is absent, there are no more results.
#[serde(skip_serializing_if = "Option::is_none")]
pub next_token: Option<String>,
/// The list of events that triggered notifications.
#[wrap_incoming(Notification)]
pub notifications: Vec<Notification>,
}
}
/// Represents a notification
#[derive(Clone, Debug, Serialize, Outgoing)]
pub struct Notification {
/// The actions to perform when the conditions for this rule are met.
pub actions: Vec<Action>,
/// The event that triggered the notification.
#[wrap_incoming(with EventResult)]
pub event: all::Event,
/// The profile tag of the rule that matched this event.
#[serde(skip_serializing_if = "Option::is_none")]
pub profile_tag: Option<String>,
/// Indicates whether the user has sent a read receipt indicating that they have read this message.
pub read: bool,
/// The ID of the room in which the event was posted.
pub room_id: RoomId,
/// The unix timestamp at which the event notification was sent, in milliseconds.
pub ts: UInt,
}

View File

@ -0,0 +1,23 @@
//! [GET /_matrix/client/r0/pushers](https://matrix.org/docs/spec/client_server/r0.6.0#get-matrix-client-r0-pushers)
use ruma_api::ruma_api;
use super::Pusher;
ruma_api! {
metadata {
description: "Gets all currently active pushers for the authenticated user.",
method: GET,
name: "get_pushers",
path: "/_matrix/client/r0/pushers",
rate_limited: false,
requires_authentication: true,
}
request {}
response {
/// An array containing the current pushers for the user.
pub pushers: Vec<Pusher>
}
}

View File

@ -0,0 +1,36 @@
//! [GET /_matrix/client/r0/pushrules/{scope}/{kind}/{ruleId}](https://matrix.org/docs/spec/client_server/r0.6.0#get-matrix-client-r0-pushrules-scope-kind-ruleid)
use ruma_api::ruma_api;
use super::{PushRule, RuleKind};
ruma_api! {
metadata {
description: "Retrieve a single specified push rule.",
method: GET,
name: "get_pushrule",
path: "/_matrix/client/r0/pushrules/:scope/:kind/:rule_id",
rate_limited: false,
requires_authentication: true,
}
request {
/// The scope to fetch rules from. 'global' to specify global rules.
#[ruma_api(path)]
pub scope: String,
/// The kind of rule
#[ruma_api(path)]
pub kind: RuleKind,
/// The identifier for the rule.
#[ruma_api(path)]
pub rule_id: String,
}
response {
/// The specific push rule.
#[ruma_api(body)]
pub rule: PushRule
}
}

View File

@ -0,0 +1,35 @@
//! [GET /_matrix/client/r0/pushrules/{scope}/{kind}/{ruleId}/actions](https://matrix.org/docs/spec/client_server/r0.6.0#get-matrix-client-r0-pushrules-scope-kind-ruleid-actions)
use ruma_api::ruma_api;
use super::{Action, RuleKind};
ruma_api! {
metadata {
description: "This endpoint get the actions for the specified push rule.",
method: GET,
name: "get_pushrule_actions",
path: "/_matrix/client/r0/pushrules/:scope/:kind/:rule_id/actions",
rate_limited: false,
requires_authentication: true,
}
request {
/// The scope to fetch a rule from. 'global' to specify global rules.
#[ruma_api(path)]
pub scope: String,
/// The kind of rule
#[ruma_api(path)]
pub kind: RuleKind,
/// The identifier for the rule.
#[ruma_api(path)]
pub rule_id: String,
}
response {
/// The actions to perform for this rule.
pub actions: Vec<Action>
}
}

View File

@ -0,0 +1,35 @@
//! [GET /_matrix/client/r0/pushrules/{scope}/{kind}/{ruleId}/enabled](https://matrix.org/docs/spec/client_server/r0.6.0#get-matrix-client-r0-pushrules-scope-kind-ruleid-enabled)
use ruma_api::ruma_api;
use super::RuleKind;
ruma_api! {
metadata {
description: "This endpoint gets whether the specified push rule is enabled.",
method: GET,
name: "get_pushrule_enabled",
path: "/_matrix/client/r0/pushrules/:scope/:kind/:rule_id/enabled",
rate_limited: false,
requires_authentication: true,
}
request {
/// The scope to fetch a rule from. 'global' to specify global rules.
#[ruma_api(path)]
pub scope: String,
/// The kind of rule
#[ruma_api(path)]
pub kind: RuleKind,
/// The identifier for the rule.
#[ruma_api(path)]
pub rule_id: String,
}
response {
/// Whether the push rule is enabled or not.
pub enabled: bool
}
}

View File

@ -0,0 +1,25 @@
//! [GET /_matrix/client/r0/pushrules/](https://matrix.org/docs/spec/client_server/r0.6.0#get-matrix-client-r0-pushrules)
use std::collections::HashMap;
use ruma_api::ruma_api;
use super::{PushRule, RuleKind};
ruma_api! {
metadata {
description: "Retrieve all push rulesets for this user.",
method: GET,
name: "get_pushrules_all",
path: "/_matrix/client/r0/pushrules/",
rate_limited: false,
requires_authentication: true,
}
request {}
response {
/// The global ruleset
pub global: HashMap<RuleKind, Vec<PushRule>>
}
}

View File

@ -0,0 +1,26 @@
//! [GET /_matrix/client/r0/pushrules/global/](https://matrix.org/docs/spec/client_server/r0.6.0#get-matrix-client-r0-pushrules)
use std::collections::HashMap;
use ruma_api::ruma_api;
use super::{PushRule, RuleKind};
ruma_api! {
metadata {
description: "Retrieve all push rulesets in the global scope for this user.",
method: GET,
name: "get_pushrules_global_scope",
path: "/_matrix/client/r0/pushrules/global/",
rate_limited: false,
requires_authentication: true,
}
request {}
response {
/// The global ruleset.
#[ruma_api(body)]
pub global: HashMap<RuleKind, Vec<PushRule>>,
}
}

30
src/r0/push/set_pusher.rs Normal file
View File

@ -0,0 +1,30 @@
//! [POST /_matrix/client/r0/pushers/set](https://matrix.org/docs/spec/client_server/r0.6.0#post-matrix-client-r0-pushers-set)
use ruma_api::ruma_api;
use super::Pusher;
ruma_api! {
metadata {
description: "This endpoint allows the creation, modification and deletion of pushers for this user ID.",
method: POST,
name: "set_pusher",
path: "/_matrix/client/r0/pushers/set",
rate_limited: true,
requires_authentication: true,
}
request {
/// The pusher to configure
#[serde(flatten)]
pub pusher: Pusher,
/// Controls if another pusher with the same pushkey and app id should be created.
/// See the spec for details.
#[serde(default)]
pub append: bool
}
response {}
}

View File

@ -0,0 +1,52 @@
//! [PUT /_matrix/client/r0/pushrules/{scope}/{kind}/{ruleId}](https://matrix.org/docs/spec/client_server/r0.6.0#put-matrix-client-r0-pushrules-scope-kind-ruleid)
use ruma_api::ruma_api;
use super::{Action, PushCondition, RuleKind};
ruma_api! {
metadata {
description: "This endpoint allows the creation, modification and deletion of pushers for this user ID.",
method: PUT,
name: "set_pushrule",
path: "/_matrix/client/r0/pushrules/:scope/:kind/:rule_id",
rate_limited: true,
requires_authentication: true,
}
request {
/// The scope to set the rule in. 'global' to specify global rules.
#[ruma_api(path)]
pub scope: String,
/// The kind of rule
#[ruma_api(path)]
pub kind: RuleKind,
/// The identifier for the rule.
#[ruma_api(path)]
pub rule_id: String,
/// Use 'before' with a rule_id as its value to make the new rule the next-most important rule with respect to the given user defined rule.
#[ruma_api(query)]
pub before: Option<String>,
/// This makes the new rule the next-less important rule relative to the given user defined rule.
#[ruma_api(query)]
pub after: Option<String>,
/// The actions to perform when this rule is matched.
pub actions: Vec<Action>,
/// The conditions that must hold true for an event in order for a rule to be applied to an event. A rule with no conditions always matches.
/// Only applicable to underride and override rules, empty Vec otherwise.
#[serde(default)]
pub conditions: Vec<PushCondition>,
/// The glob-style pattern to match against. Only applicable to content rules.
#[serde(skip_serializing_if = "Option::is_none")]
pub pattern: Option<String>,
}
response {}
}

View File

@ -0,0 +1,35 @@
//! [PUT /_matrix/client/r0/pushrules/{scope}/{kind}/{ruleId}/actions](https://matrix.org/docs/spec/client_server/r0.6.0#put-matrix-client-r0-pushrules-scope-kind-ruleid-actions)
use ruma_api::ruma_api;
use super::{Action, RuleKind};
ruma_api! {
metadata {
description: "This endpoint allows clients to change the actions of a push rule. This can be used to change the actions of builtin rules.",
method: PUT,
name: "set_pushrule_actions",
path: "/_matrix/client/r0/pushrules/:scope/:kind/:rule_id/actions",
rate_limited: false,
requires_authentication: true,
}
request {
/// The scope to fetch a rule from. 'global' to specify global rules.
#[ruma_api(path)]
pub scope: String,
/// The kind of rule
#[ruma_api(path)]
pub kind: RuleKind,
/// The identifier for the rule.
#[ruma_api(path)]
pub rule_id: String,
/// The actions to perform for this rule
pub actions: Vec<Action>
}
response {}
}

View File

@ -0,0 +1,35 @@
//! [PUT /_matrix/client/r0/pushrules/{scope}/{kind}/{ruleId}/enabled](https://matrix.org/docs/spec/client_server/r0.6.0#put-matrix-client-r0-pushrules-scope-kind-ruleid-enabled)
use ruma_api::ruma_api;
use super::RuleKind;
ruma_api! {
metadata {
description: "This endpoint allows clients to enable or disable the specified push rule.",
method: PUT,
name: "set_pushrule_enabled",
path: "/_matrix/client/r0/pushrules/:scope/:kind/:rule_id/enabled",
rate_limited: false,
requires_authentication: true,
}
request {
/// The scope to fetch a rule from. 'global' to specify global rules.
#[ruma_api(path)]
pub scope: String,
/// The kind of rule
#[ruma_api(path)]
pub kind: RuleKind,
/// The identifier for the rule.
#[ruma_api(path)]
pub rule_id: String,
/// Whether the push rule is enabled or not.
pub enabled: bool
}
response {}
}