This commit is contained in:
Daniel Lovbrotte Olsen 2024-10-02 14:24:58 +02:00
parent 3e016a6486
commit bea65835ee
7 changed files with 294 additions and 117 deletions

83
src/ctf/add.rs Normal file
View File

@ -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 <name>");
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<Room> {
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)
}

156
src/ctf/common.rs Normal file
View File

@ -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<RoomType>,
) -> anyhow::Result<Room> {
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::<RoomJoinRulesEventContent>::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::<RoomPowerLevelsEventContent>()
.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<myRoomMemberEventContent> {
room.sync_up().await;
let member = match room
.get_state_event_static_for_key::<myRoomMemberEventContent, _>(
&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<String>, suggested: bool) -> Result<send_state_event::v3::Response> {
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<Vec<OwnedUserId>> {
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::<OwnedUserId>::new();
loop {
{
for raw in response.chunk {
let reaction = raw.deserialize_as::<ReactionEvent>()?;
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)
}

View File

@ -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<RoomType>,
) -> anyhow::Result<Room> {
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::<RoomJoinRulesEventContent>::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::<RoomPowerLevelsEventContent>()
.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::<myRoomMemberEventContent, _>(
&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::<myRoomMemberEventContent, _>(
&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<Room> {
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::<myRoomMemberEventContent, _>(
@ -208,6 +114,7 @@ async fn create_command(name: &str, parent: &Room, client: Client) -> Result<Roo
new_member_event.fisken_type = Some(FiskenType::Command);
new_member_event.parent = Some(parent.room_id().to_owned());
new_member_event.fisken_command_info = Some(info_event);
command_room
.send_state_event_for_key(client.user_id().unwrap(), new_member_event)
@ -259,13 +166,7 @@ async fn create_main(name: &str, parent: &Room, client: Client) -> Result<Room>
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)
}

View File

@ -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<FiskenState>,
#[serde(
rename = "xyz.dandellion.fisken.command_info",
skip_serializing_if = "Option::is_none"
)]
pub fisken_command_info: Option<OwnedEventId>,
}
#[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,
}
}
}

View File

@ -1,4 +1,12 @@
mod create;
pub use create::*;
mod add;
pub use add::*;
mod reactions;
pub use reactions::*;
mod common;
mod events;

18
src/ctf/reactions.rs Normal file
View File

@ -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;
}

View File

@ -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();