diff --git a/src/ctf/add.rs b/src/ctf/add.rs new file mode 100644 index 0000000..42681e6 --- /dev/null +++ b/src/ctf/add.rs @@ -0,0 +1,83 @@ +use anyhow::Result; +use lazy_regex::regex_captures; +use tokio::time::{sleep, Duration}; + +use matrix_sdk::ruma::events::room::message::{MessageType, OriginalSyncRoomMessageEvent, RoomMessageEventContent}; +use matrix_sdk::{Client, Room, RoomState}; + +use super::common::*; +use super::events::my_room_member::FiskenType; + + +pub async fn add(event: OriginalSyncRoomMessageEvent, room: Room, client: Client) { + if room.state() != RoomState::Joined { + return; + } + + let MessageType::Text(text_content) = &event.content.msgtype else { + return; + }; + + if !text_content.body.starts_with("!add") { + return; + } + + let (_, name) = regex_captures!(r#"^!add (.+)$"#, &text_content.body,).unwrap(); + let name = name.to_owned(); + if name == "" { + let content = RoomMessageEventContent::text_plain("command: !add "); + room.send(content).await.unwrap(); + } + + tokio::spawn(actually_add(name, event, room, client)); +} + +async fn actually_add(name: String, event: OriginalSyncRoomMessageEvent, room: Room, client: Client) { + let member = match get_room_membership(&room, &client).await { + Ok(m) => m, + Err(e) => { + println!("{}: {e}", room.room_id()); + return; + } + }; + + if member.fisken_type != Some(FiskenType::Command) { + return; + } + + let main = match member.parent.and_then(|rid| client.get_room(&rid)) { + Some(r) => r, + None => { + println!("{}: couldn't get main room", room.room_id()); + return; + } + }; + + let task = match create_task(&name, &main, client.clone()).await { + Ok(t) => t, + Err(e) => { + println!("{}: couldn't create task room: {e}", room.room_id()); + return; + } + }; + + let _ = task.invite_user_by_id(&event.sender).await; + + let reactors = get_reactors( + client.clone(), + &room, + member.fisken_command_info.unwrap(), "🔔".to_string()).await; + +} + +pub async fn create_task( + name: &str, + parent: &Room, + client: Client, +) -> anyhow::Result { + let task = create_room(name, parent, client.clone(), None).await?; + + set_space_child(&client, parent, task.room_id(), Some("01".into()), true).await?; + + Ok(task) +} \ No newline at end of file diff --git a/src/ctf/common.rs b/src/ctf/common.rs new file mode 100644 index 0000000..355a413 --- /dev/null +++ b/src/ctf/common.rs @@ -0,0 +1,156 @@ +use std::arch::x86_64::_mm_adds_epu16; + +use anyhow::{bail, Result}; +use js_int::Int; +use tokio::time::{sleep, Duration}; + +use matrix_sdk::{ + config::{RequestConfig, SyncSettings}, crypto::types::events::EventType, deserialized_responses::{RawSyncOrStrippedState, SyncOrStrippedState}, ruma::{ + api::client::{room::create_room::v3::CreationContent, state::send_state_event}, events::{ + message::MessageEvent, reaction::{ReactionEvent, ReactionEventContent}, relation::RelationType, room::{ + join_rules::{AllowRule, JoinRule, Restricted, RoomJoinRulesEventContent}, + power_levels::RoomPowerLevelsEventContent, + }, secret::request, space::child::SpaceChildEventContent, AnyMessageLikeEvent, InitialStateEvent, MessageLikeEvent, SyncStateEvent, TimelineEventType + }, room::RoomType, serde::Raw, EventId, OwnedEventId, OwnedUserId, RoomId + }, Client, Room +}; +use matrix_sdk::ruma::api::client::room::create_room::v3::Request as CreateRoomRequest; +use super::events::my_room_member::{FiskenType, RoomMemberEventContent as myRoomMemberEventContent}; +use matrix_sdk::ruma::api::client::relations::get_relating_events_with_rel_type_and_event_type; + +pub async fn create_room( + name: &str, + parent: &Room, + client: Client, + room_type: Option, +) -> anyhow::Result { + println!("Creating room: {name}"); + // Create a room + let mut creation_content = CreationContent::new(); + creation_content.room_type = room_type; + + // Allow members of the room where !create was issued to join the space + let restriction = Restricted::new(vec![AllowRule::room_membership( + parent.room_id().to_owned(), + )]); + let join_rule = JoinRule::Restricted(restriction); + let join_rule_content = RoomJoinRulesEventContent::new(join_rule); + let join_rule_state = InitialStateEvent::::new(join_rule_content); + + // Copy PLs from the parent room + let mut delay = 2; + let mut parent_pl = loop { + let maybe_parent_pl = parent + .get_state_event_static::() + .await?; + + if let Some(raw) = maybe_parent_pl { + let pl = match raw.deserialize()? { + SyncOrStrippedState::Sync(SyncStateEvent::Original(ev)) => ev.content, + _ => todo!(), + }; + + break pl; + } + + println!("Couldn't get PLs, waiting {delay}: {:?}", maybe_parent_pl); + sleep(Duration::from_secs(delay)).await; + delay *= 2; + + if delay > 3600 { + bail!("Couldn't get power-level event from parent-room"); + } + }; + + // Also make the bot Admin in the space + parent_pl.users.insert( + client.user_id().unwrap().to_owned(), + Int::new_saturating(100), + ); + let mut plec = RoomPowerLevelsEventContent::new(); + plec.users = parent_pl.users; + + // Actually create the room + let mut request = CreateRoomRequest::new(); + request.creation_content = Some(Raw::new(&creation_content).unwrap()); + request.initial_state = vec![join_rule_state.to_raw_any()]; + request.name = Some(name.to_string()); + request.power_level_content_override = Some(Raw::new(&plec).unwrap()); + + let room = client.create_room(request).await?; + + // Get the pfp and display name of the bot from the !create room + let parent_member = get_room_membership(&parent, &client).await?; + + // Get member event to edit + let mut new_member_event = get_room_membership(&room, &client).await?; + + new_member_event.avatar_url = parent_member.avatar_url; + new_member_event.displayname = parent_member.displayname; + + room.send_state_event_for_key(client.user_id().unwrap(), new_member_event) + .await?; + + return Ok(room); +} + + +pub async fn get_room_membership(room: &Room, client: &Client) -> Result { + room.sync_up().await; + let member = match room + .get_state_event_static_for_key::( + &client.user_id().unwrap().to_owned(), + ) + .await? + { + Some(RawSyncOrStrippedState::Sync(r)) => match r.deserialize()? { + SyncStateEvent::Original(e) => e.content, + _ => todo!(), + }, + _ => todo!(), + }; + + Ok(member) +} + + +pub async fn set_space_child(client: &Client, parent: &Room, child: &RoomId, sort: Option, suggested: bool) -> Result { + let mut space_child_event = + SpaceChildEventContent::new(vec![client.user_id().unwrap().server_name().to_owned()]); + space_child_event.order = Some("00".into()); + space_child_event.suggested = true; + + Ok(parent.send_state_event_for_key(child, space_child_event) + .await?) +} + +pub async fn get_reactors(client: Client, room: &Room, message: OwnedEventId, key: String) -> Result> { + let mut request = get_relating_events_with_rel_type_and_event_type::v1::Request::new( + room.room_id().to_owned(), + message.to_owned(), + RelationType::Annotation, + TimelineEventType::Reaction + ); + + let mut response = client.send(request.clone(), None).await?; + let mut reactors = Vec::::new(); + loop { + { + for raw in response.chunk { + let reaction = raw.deserialize_as::()?; + let event = reaction.as_original().unwrap().to_owned(); + if event.content.relates_to.key == key { + reactors.push(event.sender); + } + } + + if response.next_batch.is_none() { + break; + } + } + request.from = response.next_batch; + response = client.send(request.clone(), None).await?; + } + + Ok(reactors) +} diff --git a/src/ctf/create.rs b/src/ctf/create.rs index fbe9373..d7b9e81 100644 --- a/src/ctf/create.rs +++ b/src/ctf/create.rs @@ -1,26 +1,16 @@ -use anyhow::bail; use anyhow::Result; use lazy_regex::regex_captures; -use tokio::time::{sleep, Duration}; +use matrix_sdk::ruma::events::reaction::ReactionEventContent; +use matrix_sdk::ruma::events::relation::Annotation; use matrix_sdk::ruma::events::space::child::SpaceChildEventContent; use matrix_sdk::deserialized_responses::RawSyncOrStrippedState; -use matrix_sdk::deserialized_responses::SyncOrStrippedState; -use matrix_sdk::ruma::api::client::room::create_room::v3::CreationContent; -use matrix_sdk::ruma::api::client::room::create_room::v3::Request as CreateRoomRequest; -use matrix_sdk::ruma::events::room::join_rules::AllowRule; -use matrix_sdk::ruma::events::room::join_rules::JoinRule; -use matrix_sdk::ruma::events::room::join_rules::Restricted; -use matrix_sdk::ruma::events::room::join_rules::RoomJoinRulesEventContent; use matrix_sdk::ruma::events::room::message::{ MessageType, OriginalSyncRoomMessageEvent, RoomMessageEventContent, }; -use matrix_sdk::ruma::events::room::power_levels::RoomPowerLevelsEventContent; -use matrix_sdk::ruma::events::InitialStateEvent; + use matrix_sdk::ruma::events::SyncStateEvent; use matrix_sdk::ruma::room::RoomType; -use matrix_sdk::ruma::serde::Raw; -use matrix_sdk::ruma::Int; use matrix_sdk::{Client, Room, RoomState}; use super::events; @@ -77,107 +67,23 @@ pub async fn actually_create( room.send(content).await.unwrap(); } -async fn create_room( - name: &str, - parent: &Room, - client: Client, - room_type: Option, -) -> anyhow::Result { - println!("Creating room: {name}"); - // Create a room - let mut creation_content = CreationContent::new(); - creation_content.room_type = room_type; - - // Allow members of the room where !create was issued to join the space - let restriction = Restricted::new(vec![AllowRule::room_membership( - parent.room_id().to_owned(), - )]); - let join_rule = JoinRule::Restricted(restriction); - let join_rule_content = RoomJoinRulesEventContent::new(join_rule); - let join_rule_state = InitialStateEvent::::new(join_rule_content); - - // Copy PLs from the parent room - let mut delay = 2; - let mut parent_pl = loop { - let maybe_parent_pl = parent - .get_state_event_static::() - .await?; - - if let Some(raw) = maybe_parent_pl { - let pl = match raw.deserialize()? { - SyncOrStrippedState::Sync(SyncStateEvent::Original(ev)) => ev.content, - _ => todo!(), - }; - - break pl; - } - - println!("Couldn't get PLs, waiting {delay}: {:?}", maybe_parent_pl); - sleep(Duration::from_secs(delay)).await; - delay *= 2; - - if delay > 3600 { - bail!("Couldn't get power-level event from parent-room"); - } - }; - - // Also make the bot Admin in the space - parent_pl.users.insert( - client.user_id().unwrap().to_owned(), - Int::new_saturating(100), - ); - let mut plec = RoomPowerLevelsEventContent::new(); - plec.users = parent_pl.users; - - // Actually create the room - let mut request = CreateRoomRequest::new(); - request.creation_content = Some(Raw::new(&creation_content).unwrap()); - request.initial_state = vec![join_rule_state.to_raw_any()]; - request.name = Some(name.to_string()); - request.power_level_content_override = Some(Raw::new(&plec).unwrap()); - - let room = client.create_room(request).await?; - - // Get the pfp and display name of the bot from the !create room - let parent_member = match parent - .get_state_event_static_for_key::( - &client.user_id().unwrap().to_owned(), - ) - .await? - { - Some(RawSyncOrStrippedState::Sync(r)) => match r.deserialize()? { - SyncStateEvent::Original(e) => e.content, - _ => todo!(), - }, - _ => todo!(), - }; - - // Get member event to edit - let mut new_member_event = match parent - .get_state_event_static_for_key::( - &client.user_id().unwrap().to_owned(), - ) - .await? - { - Some(RawSyncOrStrippedState::Sync(r)) => match r.deserialize()? { - SyncStateEvent::Original(e) => e.content, - _ => todo!(), - }, - _ => todo!(), - }; - - new_member_event.avatar_url = parent_member.avatar_url; - new_member_event.displayname = parent_member.displayname; - - room.send_state_event_for_key(client.user_id().unwrap(), new_member_event) - .await?; - - return Ok(room); -} +use super::common::*; async fn create_command(name: &str, parent: &Room, client: Client) -> Result { let command_room = create_room(name, parent, client.clone(), None).await?; + // Inform about the command_room + let info_content = RoomMessageEventContent::text_plain(format!( + "This is the command room, you can issue commands like !add to make a new room for a challenge, \ + or !archive to archive the whole space.\n\ + React to this message with a bell to automatically get invited to new challenge-rooms" + )); + let info_event = command_room.send(info_content).await?.event_id; + + let react_annotation = Annotation::new(info_event.clone(), "🔔".into()); + let react_content = ReactionEventContent::new(react_annotation); + command_room.send(react_content).await?; + // Get settings from room that issued !create let parent_member = match parent .get_state_event_static_for_key::( @@ -208,6 +114,7 @@ async fn create_command(name: &str, parent: &Room, client: Client) -> Result Result main.sync_up().await; let command_room = create_command(&format!("🐠 - {name}"), &main, client.clone()).await?; - let mut space_child_command = - SpaceChildEventContent::new(vec![client.user_id().unwrap().server_name().to_owned()]); - space_child_command.order = Some("00".into()); - space_child_command.suggested = true; - - main.send_state_event_for_key(command_room.room_id(), space_child_command) - .await?; + let _ = set_space_child(&client, &main, command_room.room_id(), Some("00".into()), true).await; Ok(main) } diff --git a/src/ctf/events/my_room_member.rs b/src/ctf/events/my_room_member.rs index ca9beda..331b907 100644 --- a/src/ctf/events/my_room_member.rs +++ b/src/ctf/events/my_room_member.rs @@ -3,6 +3,7 @@ // License: MIT use matrix_sdk::ruma::events::macros::EventContent; +use matrix_sdk::ruma::OwnedEventId; use matrix_sdk::ruma::OwnedMxcUri; use matrix_sdk::ruma::OwnedRoomId; use matrix_sdk::ruma::OwnedUserId; @@ -119,6 +120,12 @@ pub struct RoomMemberEventContent { skip_serializing_if = "Option::is_none" )] pub fisken_state: Option, + + #[serde( + rename = "xyz.dandellion.fisken.command_info", + skip_serializing_if = "Option::is_none" + )] + pub fisken_command_info: Option, } #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] @@ -158,6 +165,7 @@ impl RoomMemberEventContent { archival_space: None, active_space: None, fisken_state: None, + fisken_command_info: None, } } } diff --git a/src/ctf/mod.rs b/src/ctf/mod.rs index 508e3fe..97f100c 100644 --- a/src/ctf/mod.rs +++ b/src/ctf/mod.rs @@ -1,4 +1,12 @@ mod create; pub use create::*; +mod add; +pub use add::*; + +mod reactions; +pub use reactions::*; + +mod common; + mod events; diff --git a/src/ctf/reactions.rs b/src/ctf/reactions.rs new file mode 100644 index 0000000..6107922 --- /dev/null +++ b/src/ctf/reactions.rs @@ -0,0 +1,18 @@ +use matrix_sdk::ruma::events::reaction::OriginalSyncReactionEvent; +use matrix_sdk::{Client, Room, RoomState}; + +pub async fn reactions(event: OriginalSyncReactionEvent, room: Room, client: Client) { + if room.state() != RoomState::Joined { + return; + } + + if event.sender == client.user_id().unwrap() { + return; + } + + tokio::spawn(actually_reactions(event, room, client)); +} + +async fn actually_reactions(event: OriginalSyncReactionEvent, room: Room, client: Client) { + return; +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 0d4a7e5..715f0e7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -56,6 +56,9 @@ async fn main() -> anyhow::Result<()> { client.add_event_handler(invite::on_stripped_state_member); client.add_event_handler(ctf::create); + client.add_event_handler(ctf::add); + + client.add_event_handler(ctf::reactions); // let sync_settings = SyncSettings::new().full_state(true); let sync_settings = SyncSettings::new();