1
0
Fork 0
mirror of https://gitlab.com/famedly/conduit.git synced 2024-12-26 06:54:35 +01:00

improvement: better default push rules

This commit is contained in:
timokoesters 2020-06-12 13:18:25 +02:00
parent e7803e310a
commit 02fe030b2a
No known key found for this signature in database
GPG key ID: 24DA7517711A2BA4
5 changed files with 359 additions and 80 deletions

View file

@ -204,33 +204,7 @@ pub fn register_route(
&EventType::PushRules,
serde_json::to_value(ruma::events::push_rules::PushRulesEvent {
content: ruma::events::push_rules::PushRulesEventContent {
global: ruma::events::push_rules::Ruleset {
content: vec![],
override_: vec![ruma::events::push_rules::ConditionalPushRule {
actions: vec![ruma::events::push_rules::Action::DontNotify],
default: true,
enabled: false,
rule_id: ".m.rule.master".to_owned(),
conditions: vec![],
}],
room: vec![],
sender: vec![],
underride: vec![ruma::events::push_rules::ConditionalPushRule {
actions: vec![
ruma::events::push_rules::Action::Notify,
ruma::events::push_rules::Action::SetTweak(ruma::push::Tweak::Sound(
"default".to_owned(),
)),
],
default: true,
enabled: true,
rule_id: ".m.rule.message".to_owned(),
conditions: vec![ruma::events::push_rules::PushCondition::EventMatch {
key: "type".to_owned(),
pattern: "m.room.message".to_owned(),
}],
}],
},
global: crate::push_rules::default_pushrules(&user_id),
},
})
.expect("data is valid, we just created it")
@ -502,8 +476,7 @@ pub fn set_displayname_route(
displayname: body.displayname.clone(),
..serde_json::from_value::<EventJson<_>>(
db.rooms
.room_state(&room_id)?
.get(&(EventType::RoomMember, user_id.to_string()))
.room_state_get(&room_id, &EventType::RoomMember, &user_id.to_string())?
.ok_or_else(|| {
Error::bad_database(
"Tried to send displayname update for user not in the room.",
@ -593,8 +566,7 @@ pub fn set_avatar_url_route(
avatar_url: body.avatar_url.clone(),
..serde_json::from_value::<EventJson<_>>(
db.rooms
.room_state(&room_id)?
.get(&(EventType::RoomMember, user_id.to_string()))
.room_state_get(&room_id, &EventType::RoomMember, &user_id.to_string())?
.ok_or_else(|| {
Error::bad_database(
"Tried to send avatar url update for user not in the room.",
@ -1267,8 +1239,7 @@ pub fn join_room_by_id_route(
let event = db
.rooms
.room_state(&body.room_id)?
.get(&(EventType::RoomMember, user_id.to_string()))
.room_state_get(&body.room_id, &EventType::RoomMember, &user_id.to_string())?
.map_or_else(
|| {
// There was no existing membership event
@ -1348,11 +1319,10 @@ pub fn leave_room_route(
_room_id: String,
) -> ConduitResult<leave_room::Response> {
let user_id = body.user_id.as_ref().expect("user is authenticated");
let state = db.rooms.room_state(&body.room_id)?;
let mut event = serde_json::from_value::<EventJson<member::MemberEventContent>>(
state
.get(&(EventType::RoomMember, user_id.to_string()))
db.rooms
.room_state_get(&body.room_id, &EventType::RoomMember, &user_id.to_string())?
.ok_or(Error::BadRequest(
ErrorKind::BadState,
"Cannot leave a room you are not a member of.",
@ -1387,12 +1357,11 @@ pub fn kick_user_route(
_room_id: String,
) -> ConduitResult<kick_user::Response> {
let user_id = body.user_id.as_ref().expect("user is authenticated");
let state = db.rooms.room_state(&body.room_id)?;
let mut event =
serde_json::from_value::<EventJson<ruma::events::room::member::MemberEventContent>>(
state
.get(&(EventType::RoomMember, user_id.to_string()))
db.rooms
.room_state_get(&body.room_id, &EventType::RoomMember, &user_id.to_string())?
.ok_or(Error::BadRequest(
ErrorKind::BadState,
"Cannot kick member that's not in the room.",
@ -1428,12 +1397,12 @@ pub fn ban_user_route(
_room_id: String,
) -> ConduitResult<ban_user::Response> {
let user_id = body.user_id.as_ref().expect("user is authenticated");
let state = db.rooms.room_state(&body.room_id)?;
// TODO: reason
let event = state
.get(&(EventType::RoomMember, user_id.to_string()))
let event = db
.rooms
.room_state_get(&body.room_id, &EventType::RoomMember, &user_id.to_string())?
.map_or(
Ok::<_, Error>(member::MemberEventContent {
membership: member::MembershipState::Ban,
@ -1475,12 +1444,11 @@ pub fn unban_user_route(
_room_id: String,
) -> ConduitResult<unban_user::Response> {
let user_id = body.user_id.as_ref().expect("user is authenticated");
let state = db.rooms.room_state(&body.room_id)?;
let mut event =
serde_json::from_value::<EventJson<ruma::events::room::member::MemberEventContent>>(
state
.get(&(EventType::RoomMember, user_id.to_string()))
db.rooms
.room_state_get(&body.room_id, &EventType::RoomMember, &user_id.to_string())?
.ok_or(Error::BadRequest(
ErrorKind::BadState,
"Cannot unban a user who is not banned.",
@ -1642,7 +1610,8 @@ pub async fn get_public_rooms_filtered_route(
.map(|room_id| {
let room_id = room_id?;
let state = db.rooms.room_state(&room_id)?;
// TODO: Do not load full state?
let state = db.rooms.room_state_full(&room_id)?;
let chunk = directory::PublicRoomsChunk {
aliases: Vec::new(),
@ -1775,9 +1744,29 @@ pub fn search_users_route(
}
#[get("/_matrix/client/r0/rooms/<_room_id>/members")]
pub fn get_member_events_route(_room_id: String) -> ConduitResult<get_member_events::Response> {
warn!("TODO: get_member_events_route");
Ok(get_member_events::Response { chunk: Vec::new() }.into())
pub fn get_member_events_route(
db: State<'_, Database>,
//body: Ruma<create_message_event::Request>,
_room_id: String,
) -> ConduitResult<get_member_events::Response> {
//let user_id = body.user_id.as_ref().expect("user is authenticated");
//if !db.rooms.is_joined(user_id, &body.room_id)? {
// return Err(Error::BadRequest(
// ErrorKind::Forbidden,
// "You don't have permission to view this room.",
// ));
//}
Ok(get_member_events::Response {
chunk: Vec::new(),/*db
.rooms
.room_state_type(&body.room_id, &EventType::RoomMember)?
.values()
.map(|pdu| pdu.to_member_event())
.collect(),*/
}
.into())
}
#[get("/_matrix/client/r0/thirdparty/protocols")]
@ -1951,7 +1940,7 @@ pub fn get_state_events_route(
Ok(get_state_events::Response {
room_state: db
.rooms
.room_state(&body.room_id)?
.room_state_full(&body.room_id)?
.values()
.map(|pdu| pdu.to_state_event())
.collect(),
@ -1979,10 +1968,9 @@ pub fn get_state_events_for_key_route(
));
}
let state = db.rooms.room_state(&body.room_id)?;
let event = state
.get(&(body.event_type.clone(), body.state_key.clone()))
let event = db
.rooms
.room_state_get(&body.room_id, &body.event_type, &body.state_key)?
.ok_or(Error::BadRequest(
ErrorKind::NotFound,
"State event not found.",
@ -2014,17 +2002,16 @@ pub fn get_state_events_for_empty_key_route(
));
}
let state = db.rooms.room_state(&body.room_id)?;
let event = state
.get(&(body.event_type.clone(), "".to_owned()))
let event = db
.rooms
.room_state_get(&body.room_id, &body.event_type, "")?
.ok_or(Error::BadRequest(
ErrorKind::NotFound,
"State event not found.",
))?;
Ok(get_state_events_for_empty_key::Response {
content: serde_json::value::to_raw_value(event)
content: serde_json::value::to_raw_value(&event)
.map_err(|_| Error::bad_database("Invalid event content in database"))?,
}
.into())
@ -2068,7 +2055,7 @@ pub fn sync_route(
let content = serde_json::from_value::<
EventJson<ruma::events::room::member::MemberEventContent>,
>(pdu.content.clone())
.map_err(|_| Error::bad_database("Invalid PDU in database."))?
.expect("EventJson::from_value always works")
.deserialize()
.map_err(|_| Error::bad_database("Invalid PDU in database."))?;
if content.membership == ruma::events::room::member::MembershipState::Join {
@ -2081,7 +2068,7 @@ pub fn sync_route(
}
}
let state = db.rooms.room_state(&room_id)?;
let members = db.rooms.room_state_type(&room_id, &EventType::RoomMember)?;
let (joined_member_count, invited_member_count, heroes) = if send_member_count {
let joined_member_count = db.rooms.room_members(&room_id).count();
@ -2111,8 +2098,8 @@ pub fn sync_route(
let current_content = serde_json::from_value::<
EventJson<ruma::events::room::member::MemberEventContent>,
>(
state
.get(&(EventType::RoomMember, state_key.clone()))
members
.get(state_key)
.ok_or_else(|| {
Error::bad_database(
"A user that joined once has no member event anymore.",
@ -2264,7 +2251,8 @@ pub fn sync_route(
// TODO: state before timeline
state: sync_events::State {
events: if joined_since_last_sync {
state
db.rooms
.room_state_full(&room_id)?
.into_iter()
.map(|(_, pdu)| pdu.to_state_event())
.collect()
@ -2337,7 +2325,7 @@ pub fn sync_route(
invite_state: sync_events::InviteState {
events: db
.rooms
.room_state(&room_id)?
.room_state_full(&room_id)?
.into_iter()
.map(|(_, pdu)| pdu.to_stripped_state_event())
.collect(),
@ -2496,7 +2484,7 @@ pub fn get_context_route(
events_after,
state: db // TODO: State at event
.rooms
.room_state(&body.room_id)?
.room_state_full(&body.room_id)?
.values()
.map(|pdu| pdu.to_state_event())
.collect(),

View file

@ -56,7 +56,10 @@ impl Rooms {
}
/// Returns the full room state.
pub fn room_state(&self, room_id: &RoomId) -> Result<HashMap<(EventType, String), PduEvent>> {
pub fn room_state_full(
&self,
room_id: &RoomId,
) -> Result<HashMap<(EventType, String), PduEvent>> {
let mut hashmap = HashMap::new();
for pdu in self
.roomstateid_pdu
@ -78,6 +81,58 @@ impl Rooms {
Ok(hashmap)
}
/// Returns the full room state.
pub fn room_state_type(
&self,
room_id: &RoomId,
event_type: &EventType,
) -> Result<HashMap<String, PduEvent>> {
let mut prefix = room_id.to_string().as_bytes().to_vec();
prefix.push(0xff);
prefix.extend_from_slice(&event_type.to_string().as_bytes());
let mut hashmap = HashMap::new();
for pdu in self
.roomstateid_pdu
.scan_prefix(&prefix)
.values()
.map(|value| {
Ok::<_, Error>(
serde_json::from_slice::<PduEvent>(&value?)
.map_err(|_| Error::bad_database("Invalid PDU in db."))?,
)
})
{
let pdu = pdu?;
let state_key = pdu.state_key.clone().ok_or_else(|| {
Error::bad_database("Room state contains event without state_key.")
})?;
hashmap.insert(state_key, pdu);
}
Ok(hashmap)
}
/// Returns the full room state.
pub fn room_state_get(
&self,
room_id: &RoomId,
event_type: &EventType,
state_key: &str,
) -> Result<Option<PduEvent>> {
let mut key = room_id.to_string().as_bytes().to_vec();
key.push(0xff);
key.extend_from_slice(&event_type.to_string().as_bytes());
key.push(0xff);
key.extend_from_slice(&state_key.as_bytes());
self.roomstateid_pdu.get(&key)?.map_or(Ok(None), |value| {
Ok::<_, Error>(Some(
serde_json::from_slice::<PduEvent>(&value)
.map_err(|_| Error::bad_database("Invalid PDU in db."))?,
))
})
}
/// Returns the `count` of this pdu's id.
pub fn get_pdu_count(&self, event_id: &EventId) -> Result<Option<u64>> {
self.eventid_pduid
@ -212,8 +267,7 @@ impl Rooms {
// Is the event authorized?
if let Some(state_key) = &state_key {
let power_levels = self
.room_state(&room_id)?
.get(&(EventType::RoomPowerLevels, "".to_owned()))
.room_state_get(&room_id, &EventType::RoomPowerLevels, "")?
.map_or_else(
|| {
Ok::<_, Error>(power_levels::PowerLevelsEventContent {
@ -244,8 +298,7 @@ impl Rooms {
},
)?;
let sender_membership = self
.room_state(&room_id)?
.get(&(EventType::RoomMember, sender.to_string()))
.room_state_get(&room_id, &EventType::RoomMember, &sender.to_string())?
.map_or(Ok::<_, Error>(member::MembershipState::Leave), |pdu| {
Ok(
serde_json::from_value::<EventJson<member::MemberEventContent>>(
@ -280,8 +333,11 @@ impl Rooms {
})?;
let current_membership = self
.room_state(&room_id)?
.get(&(EventType::RoomMember, target_user_id.to_string()))
.room_state_get(
&room_id,
&EventType::RoomMember,
&target_user_id.to_string(),
)?
.map_or(Ok::<_, Error>(member::MembershipState::Leave), |pdu| {
Ok(
serde_json::from_value::<EventJson<member::MemberEventContent>>(
@ -315,8 +371,7 @@ impl Rooms {
);
let join_rules =
self.room_state(&room_id)?
.get(&(EventType::RoomJoinRules, "".to_owned()))
self.room_state_get(&room_id, &EventType::RoomJoinRules, "")?
.map_or(Ok::<_, Error>(join_rules::JoinRule::Public), |pdu| {
Ok(serde_json::from_value::<
EventJson<join_rules::JoinRulesEventContent>,
@ -446,12 +501,8 @@ impl Rooms {
+ 1;
let mut unsigned = unsigned.unwrap_or_default();
// TODO: Optimize this to not load the whole room state?
if let Some(state_key) = &state_key {
if let Some(prev_pdu) = self
.room_state(&room_id)?
.get(&(event_type.clone(), state_key.to_owned()))
{
if let Some(prev_pdu) = self.room_state_get(&room_id, &event_type, &state_key)? {
unsigned.insert("prev_content".to_owned(), prev_pdu.content.clone());
}
}

View file

@ -1,6 +1,8 @@
#![feature(proc_macro_hygiene, decl_macro)]
#![warn(rust_2018_idioms)]
pub mod push_rules;
mod client_server;
mod database;
mod error;

View file

@ -4,6 +4,7 @@ use ruma::{
api::federation::EventHash,
events::{
collections::all::{RoomEvent, StateEvent},
room::member::MemberEvent,
stripped::AnyStrippedStateEvent,
EventJson, EventType,
},
@ -95,4 +96,9 @@ impl PduEvent {
serde_json::from_str::<EventJson<AnyStrippedStateEvent>>(&json)
.expect("EventJson::from_str always works")
}
pub fn to_member_event(&self) -> EventJson<MemberEvent> {
let json = serde_json::to_string(&self).expect("PDUs are always valid");
serde_json::from_str::<EventJson<MemberEvent>>(&json)
.expect("EventJson::from_str always works")
}
}

232
src/push_rules.rs Normal file
View file

@ -0,0 +1,232 @@
use ruma::{
events::push_rules::{
ConditionalPushRule, PatternedPushRule, PushCondition, PushRule, Ruleset,
},
identifiers::UserId,
push::{Action, Tweak},
};
pub fn default_pushrules(user_id: &UserId) -> Ruleset {
Ruleset {
content: vec![contains_user_name_rule(&user_id)],
override_: vec![
master_rule(),
suppress_notices_rule(),
invite_for_me_rule(),
member_event_rule(),
contains_display_name_rule(),
tombstone_rule(),
roomnotif_rule(),
],
room: vec![],
sender: vec![],
underride: vec![
call_rule(),
encrypted_room_one_to_one_rule(),
room_one_to_one_rule(),
message_rule(),
encrypted_rule(),
],
}
}
pub fn master_rule() -> ConditionalPushRule {
ConditionalPushRule {
actions: vec![Action::DontNotify],
default: true,
enabled: false,
rule_id: ".m.rule.master".to_owned(),
conditions: vec![],
}
}
pub fn suppress_notices_rule() -> ConditionalPushRule {
ConditionalPushRule {
actions: vec![Action::DontNotify],
default: true,
enabled: true,
rule_id: ".m.rule.suppress_notices".to_owned(),
conditions: vec![PushCondition::EventMatch {
key: "content.msgtype".to_owned(),
pattern: "m.notice".to_owned(),
}],
}
}
pub fn invite_for_me_rule() -> ConditionalPushRule {
ConditionalPushRule {
actions: vec![
Action::Notify,
Action::SetTweak(Tweak::Sound("default".to_owned())),
Action::SetTweak(Tweak::Highlight(false)),
],
default: true,
enabled: true,
rule_id: ".m.rule.invite_for_me".to_owned(),
conditions: vec![PushCondition::EventMatch {
key: "content.membership".to_owned(),
pattern: "m.invite".to_owned(),
}],
}
}
pub fn member_event_rule() -> ConditionalPushRule {
ConditionalPushRule {
actions: vec![Action::DontNotify],
default: true,
enabled: true,
rule_id: ".m.rule.member_event".to_owned(),
conditions: vec![PushCondition::EventMatch {
key: "content.membership".to_owned(),
pattern: "type".to_owned(),
}],
}
}
pub fn contains_display_name_rule() -> ConditionalPushRule {
ConditionalPushRule {
actions: vec![
Action::Notify,
Action::SetTweak(Tweak::Sound("default".to_owned())),
Action::SetTweak(Tweak::Highlight(true)),
],
default: true,
enabled: true,
rule_id: ".m.rule.contains_display_name".to_owned(),
conditions: vec![PushCondition::ContainsDisplayName],
}
}
pub fn tombstone_rule() -> ConditionalPushRule {
ConditionalPushRule {
actions: vec![Action::Notify, Action::SetTweak(Tweak::Highlight(true))],
default: true,
enabled: true,
rule_id: ".m.rule.tombstone".to_owned(),
conditions: vec![
PushCondition::EventMatch {
key: "type".to_owned(),
pattern: "m.room.tombstone".to_owned(),
},
PushCondition::EventMatch {
key: "state_key".to_owned(),
pattern: "".to_owned(),
},
],
}
}
pub fn roomnotif_rule() -> ConditionalPushRule {
ConditionalPushRule {
actions: vec![Action::Notify, Action::SetTweak(Tweak::Highlight(true))],
default: true,
enabled: true,
rule_id: ".m.rule.roomnotif".to_owned(),
conditions: vec![
PushCondition::EventMatch {
key: "content.body".to_owned(),
pattern: "@room".to_owned(),
},
PushCondition::SenderNotificationPermission {
key: "room".to_owned(),
},
],
}
}
pub fn contains_user_name_rule(user_id: &UserId) -> PatternedPushRule {
PatternedPushRule {
actions: vec![
Action::Notify,
Action::SetTweak(Tweak::Sound("default".to_owned())),
Action::SetTweak(Tweak::Highlight(true)),
],
default: true,
enabled: true,
rule_id: ".m.rule.contains_user_name".to_owned(),
pattern: user_id.localpart().to_owned(),
}
}
pub fn call_rule() -> ConditionalPushRule {
ConditionalPushRule {
actions: vec![
Action::Notify,
Action::SetTweak(Tweak::Sound("ring".to_owned())),
Action::SetTweak(Tweak::Highlight(false)),
],
default: true,
enabled: true,
rule_id: ".m.rule.call".to_owned(),
conditions: vec![PushCondition::EventMatch {
key: "type".to_owned(),
pattern: "m.call.invite".to_owned(),
}],
}
}
pub fn encrypted_room_one_to_one_rule() -> ConditionalPushRule {
ConditionalPushRule {
actions: vec![
Action::Notify,
Action::SetTweak(Tweak::Sound("default".to_owned())),
Action::SetTweak(Tweak::Highlight(false)),
],
default: true,
enabled: true,
rule_id: ".m.rule.encrypted_room_one_to_one".to_owned(),
conditions: vec![
PushCondition::RoomMemberCount { is: "2".to_owned() },
PushCondition::EventMatch {
key: "type".to_owned(),
pattern: "m.room.encrypted".to_owned(),
},
],
}
}
pub fn room_one_to_one_rule() -> ConditionalPushRule {
ConditionalPushRule {
actions: vec![
Action::Notify,
Action::SetTweak(Tweak::Sound("default".to_owned())),
Action::SetTweak(Tweak::Highlight(false)),
],
default: true,
enabled: true,
rule_id: ".m.rule.room_one_to_one".to_owned(),
conditions: vec![
PushCondition::RoomMemberCount { is: "2".to_owned() },
PushCondition::EventMatch {
key: "type".to_owned(),
pattern: "m.room.message".to_owned(),
},
],
}
}
pub fn message_rule() -> ConditionalPushRule {
ConditionalPushRule {
actions: vec![Action::Notify, Action::SetTweak(Tweak::Highlight(false))],
default: true,
enabled: true,
rule_id: ".m.rule.message".to_owned(),
conditions: vec![PushCondition::EventMatch {
key: "type".to_owned(),
pattern: "m.room.message".to_owned(),
}],
}
}
pub fn encrypted_rule() -> ConditionalPushRule {
ConditionalPushRule {
actions: vec![Action::Notify, Action::SetTweak(Tweak::Highlight(false))],
default: true,
enabled: true,
rule_id: ".m.rule.encrypted".to_owned(),
conditions: vec![PushCondition::EventMatch {
key: "type".to_owned(),
pattern: "m.room.encrypted".to_owned(),
}],
}
}