1
0
Fork 0
mirror of https://gitlab.com/famedly/conduit.git synced 2024-12-27 12:04:30 +01:00

Merge branch 'create-admin-room' into 'next'

Create admin room and hide migration messages on first run

Closes #157 and #225

See merge request famedly/conduit!282
This commit is contained in:
Timo Kösters 2022-02-03 21:09:21 +00:00
commit 51cca1a60f
3 changed files with 419 additions and 317 deletions

View file

@ -1,7 +1,11 @@
use std::{collections::BTreeMap, sync::Arc};
use std::sync::Arc;
use super::{DEVICE_ID_LENGTH, SESSION_ID_LENGTH, TOKEN_LENGTH};
use crate::{database::DatabaseGuard, pdu::PduBuilder, utils, ConduitResult, Error, Ruma};
use crate::{
database::{admin::make_user_admin, DatabaseGuard},
pdu::PduBuilder,
utils, ConduitResult, Error, Ruma,
};
use ruma::{
api::client::{
error::ErrorKind,
@ -14,25 +18,13 @@ use ruma::{
},
},
events::{
room::{
canonical_alias::RoomCanonicalAliasEventContent,
create::RoomCreateEventContent,
guest_access::{GuestAccess, RoomGuestAccessEventContent},
history_visibility::{HistoryVisibility, RoomHistoryVisibilityEventContent},
join_rules::{JoinRule, RoomJoinRulesEventContent},
member::{MembershipState, RoomMemberEventContent},
message::RoomMessageEventContent,
name::RoomNameEventContent,
power_levels::RoomPowerLevelsEventContent,
topic::RoomTopicEventContent,
},
room::member::{MembershipState, RoomMemberEventContent},
EventType,
},
identifiers::RoomName,
push, RoomAliasId, RoomId, RoomVersionId, UserId,
push, UserId,
};
use serde_json::value::to_raw_value;
use tracing::info;
use tracing::{info, warn};
use register::RegistrationKind;
#[cfg(feature = "conduit_bin")]
@ -253,276 +245,16 @@ pub async fn register_route(
body.initial_device_display_name.clone(),
)?;
// If this is the first user on this server, create the admin room
if db.users.count()? == 1 {
// Create a user for the server
let conduit_user = UserId::parse_with_server_name("conduit", db.globals.server_name())
.expect("@conduit:server_name is valid");
db.users.create(&conduit_user, None)?;
let room_id = RoomId::new(db.globals.server_name());
db.rooms.get_or_create_shortroomid(&room_id, &db.globals)?;
let mutex_state = Arc::clone(
db.globals
.roomid_mutex_state
.write()
.unwrap()
.entry(room_id.clone())
.or_default(),
);
let state_lock = mutex_state.lock().await;
let mut content = RoomCreateEventContent::new(conduit_user.clone());
content.federate = true;
content.predecessor = None;
content.room_version = RoomVersionId::V6;
// 1. The room create event
db.rooms.build_and_append_pdu(
PduBuilder {
event_type: EventType::RoomCreate,
content: to_raw_value(&content).expect("event is valid, we just created it"),
unsigned: None,
state_key: Some("".to_owned()),
redacts: None,
},
&conduit_user,
&room_id,
&db,
&state_lock,
)?;
// 2. Make conduit bot join
db.rooms.build_and_append_pdu(
PduBuilder {
event_type: EventType::RoomMember,
content: to_raw_value(&RoomMemberEventContent {
membership: MembershipState::Join,
displayname: None,
avatar_url: None,
is_direct: None,
third_party_invite: None,
blurhash: None,
reason: None,
join_authorized_via_users_server: None,
})
.expect("event is valid, we just created it"),
unsigned: None,
state_key: Some(conduit_user.to_string()),
redacts: None,
},
&conduit_user,
&room_id,
&db,
&state_lock,
)?;
// 3. Power levels
let mut users = BTreeMap::new();
users.insert(conduit_user.clone(), 100.into());
users.insert(user_id.clone(), 100.into());
db.rooms.build_and_append_pdu(
PduBuilder {
event_type: EventType::RoomPowerLevels,
content: to_raw_value(&RoomPowerLevelsEventContent {
users,
..Default::default()
})
.expect("event is valid, we just created it"),
unsigned: None,
state_key: Some("".to_owned()),
redacts: None,
},
&conduit_user,
&room_id,
&db,
&state_lock,
)?;
// 4.1 Join Rules
db.rooms.build_and_append_pdu(
PduBuilder {
event_type: EventType::RoomJoinRules,
content: to_raw_value(&RoomJoinRulesEventContent::new(JoinRule::Invite))
.expect("event is valid, we just created it"),
unsigned: None,
state_key: Some("".to_owned()),
redacts: None,
},
&conduit_user,
&room_id,
&db,
&state_lock,
)?;
// 4.2 History Visibility
db.rooms.build_and_append_pdu(
PduBuilder {
event_type: EventType::RoomHistoryVisibility,
content: to_raw_value(&RoomHistoryVisibilityEventContent::new(
HistoryVisibility::Shared,
))
.expect("event is valid, we just created it"),
unsigned: None,
state_key: Some("".to_owned()),
redacts: None,
},
&conduit_user,
&room_id,
&db,
&state_lock,
)?;
// 4.3 Guest Access
db.rooms.build_and_append_pdu(
PduBuilder {
event_type: EventType::RoomGuestAccess,
content: to_raw_value(&RoomGuestAccessEventContent::new(GuestAccess::Forbidden))
.expect("event is valid, we just created it"),
unsigned: None,
state_key: Some("".to_owned()),
redacts: None,
},
&conduit_user,
&room_id,
&db,
&state_lock,
)?;
// 6. Events implied by name and topic
let room_name = RoomName::parse(format!("{} Admin Room", db.globals.server_name()))
.expect("Room name is valid");
db.rooms.build_and_append_pdu(
PduBuilder {
event_type: EventType::RoomName,
content: to_raw_value(&RoomNameEventContent::new(Some(room_name)))
.expect("event is valid, we just created it"),
unsigned: None,
state_key: Some("".to_owned()),
redacts: None,
},
&conduit_user,
&room_id,
&db,
&state_lock,
)?;
db.rooms.build_and_append_pdu(
PduBuilder {
event_type: EventType::RoomTopic,
content: to_raw_value(&RoomTopicEventContent {
topic: format!("Manage {}", db.globals.server_name()),
})
.expect("event is valid, we just created it"),
unsigned: None,
state_key: Some("".to_owned()),
redacts: None,
},
&conduit_user,
&room_id,
&db,
&state_lock,
)?;
// Room alias
let alias: Box<RoomAliasId> = format!("#admins:{}", db.globals.server_name())
.try_into()
.expect("#admins:server_name is a valid alias name");
db.rooms.build_and_append_pdu(
PduBuilder {
event_type: EventType::RoomCanonicalAlias,
content: to_raw_value(&RoomCanonicalAliasEventContent {
alias: Some(alias.clone()),
alt_aliases: Vec::new(),
})
.expect("event is valid, we just created it"),
unsigned: None,
state_key: Some("".to_owned()),
redacts: None,
},
&conduit_user,
&room_id,
&db,
&state_lock,
)?;
db.rooms.set_alias(&alias, Some(&room_id), &db.globals)?;
// Invite and join the real user
db.rooms.build_and_append_pdu(
PduBuilder {
event_type: EventType::RoomMember,
content: to_raw_value(&RoomMemberEventContent {
membership: MembershipState::Invite,
displayname: None,
avatar_url: None,
is_direct: None,
third_party_invite: None,
blurhash: None,
reason: None,
join_authorized_via_users_server: None,
})
.expect("event is valid, we just created it"),
unsigned: None,
state_key: Some(user_id.to_string()),
redacts: None,
},
&conduit_user,
&room_id,
&db,
&state_lock,
)?;
db.rooms.build_and_append_pdu(
PduBuilder {
event_type: EventType::RoomMember,
content: to_raw_value(&RoomMemberEventContent {
membership: MembershipState::Join,
displayname: Some(displayname),
avatar_url: None,
is_direct: None,
third_party_invite: None,
blurhash: None,
reason: None,
join_authorized_via_users_server: None,
})
.expect("event is valid, we just created it"),
unsigned: None,
state_key: Some(user_id.to_string()),
redacts: None,
},
&user_id,
&room_id,
&db,
&state_lock,
)?;
// Send welcome message
db.rooms.build_and_append_pdu(
PduBuilder {
event_type: EventType::RoomMessage,
content: to_raw_value(&RoomMessageEventContent::text_html(
"## Thank you for trying out Conduit!\n\nConduit is currently in Beta. This means you can join and participate in most Matrix rooms, but not all features are supported and you might run into bugs from time to time.\n\nHelpful links:\n> Website: https://conduit.rs\n> Git and Documentation: https://gitlab.com/famedly/conduit\n> Report issues: https://gitlab.com/famedly/conduit/-/issues\n\nHere are some rooms you can join (by typing the command):\n\nConduit room (Ask questions and get notified on updates):\n`/join #conduit:fachschaften.org`\n\nConduit lounge (Off-topic, only Conduit users are allowed to join)\n`/join #conduit-lounge:conduit.rs`".to_owned(),
"<h2>Thank you for trying out Conduit!</h2>\n<p>Conduit is currently in Beta. This means you can join and participate in most Matrix rooms, but not all features are supported and you might run into bugs from time to time.</p>\n<p>Helpful links:</p>\n<blockquote>\n<p>Website: https://conduit.rs<br>Git and Documentation: https://gitlab.com/famedly/conduit<br>Report issues: https://gitlab.com/famedly/conduit/-/issues</p>\n</blockquote>\n<p>Here are some rooms you can join (by typing the command):</p>\n<p>Conduit room (Ask questions and get notified on updates):<br><code>/join #conduit:fachschaften.org</code></p>\n<p>Conduit lounge (Off-topic, only Conduit users are allowed to join)<br><code>/join #conduit-lounge:conduit.rs</code></p>\n".to_owned(),
))
.expect("event is valid, we just created it"),
unsigned: None,
state_key: None,
redacts: None,
},
&conduit_user,
&room_id,
&db,
&state_lock,
)?;
}
info!("{} registered on this server", user_id);
// If this is the first real user, grant them admin privileges
// Note: the server user, @conduit:servername, is generated first
if db.users.count()? == 2 {
make_user_admin(&db, &user_id, displayname).await?;
warn!("Granting {} admin privileges as the first user", user_id);
}
db.flush()?;
Ok(register::Response {

View file

@ -34,7 +34,9 @@ use std::{
sync::{Arc, Mutex, RwLock},
};
use tokio::sync::{OwnedRwLockReadGuard, RwLock as TokioRwLock, Semaphore};
use tracing::{debug, error, warn};
use tracing::{debug, error, info, warn};
use self::admin::create_admin_room;
pub struct Database {
_db: Arc<dyn DatabaseEngine>,
@ -301,10 +303,32 @@ impl Database {
)?,
}));
{
let db = db.read().await;
let guard = db.read().await;
// Matrix resource ownership is based on the server name; changing it
// requires recreating the database from scratch.
if guard.users.count()? > 0 {
let conduit_user =
UserId::parse_with_server_name("conduit", guard.globals.server_name())
.expect("@conduit:server_name is valid");
if !guard.users.exists(&conduit_user)? {
error!(
"The {} server user does not exist, and the database is not new.",
conduit_user
);
return Err(Error::bad_database(
"Cannot reuse an existing database after changing the server name, please delete the old one first."
));
}
}
// If the database has any data, perform data migrations before starting
let latest_database_version = 11;
if guard.users.count()? > 0 {
let db = &*guard;
// MIGRATIONS
// TODO: database versions of new dbs should probably not be 0
if db.globals.database_version()? < 1 {
for (roomserverid, _) in db.rooms.roomserverids.iter() {
let mut parts = roomserverid.split(|&b| b == 0xff);
@ -325,7 +349,7 @@ impl Database {
db.globals.bump_database_version(1)?;
println!("Migration: 0 -> 1 finished");
warn!("Migration: 0 -> 1 finished");
}
if db.globals.database_version()? < 2 {
@ -344,7 +368,7 @@ impl Database {
db.globals.bump_database_version(2)?;
println!("Migration: 1 -> 2 finished");
warn!("Migration: 1 -> 2 finished");
}
if db.globals.database_version()? < 3 {
@ -362,7 +386,7 @@ impl Database {
db.globals.bump_database_version(3)?;
println!("Migration: 2 -> 3 finished");
warn!("Migration: 2 -> 3 finished");
}
if db.globals.database_version()? < 4 {
@ -385,7 +409,7 @@ impl Database {
db.globals.bump_database_version(4)?;
println!("Migration: 3 -> 4 finished");
warn!("Migration: 3 -> 4 finished");
}
if db.globals.database_version()? < 5 {
@ -409,7 +433,7 @@ impl Database {
db.globals.bump_database_version(5)?;
println!("Migration: 4 -> 5 finished");
warn!("Migration: 4 -> 5 finished");
}
if db.globals.database_version()? < 6 {
@ -422,7 +446,7 @@ impl Database {
db.globals.bump_database_version(6)?;
println!("Migration: 5 -> 6 finished");
warn!("Migration: 5 -> 6 finished");
}
if db.globals.database_version()? < 7 {
@ -549,7 +573,7 @@ impl Database {
db.globals.bump_database_version(7)?;
println!("Migration: 6 -> 7 finished");
warn!("Migration: 6 -> 7 finished");
}
if db.globals.database_version()? < 8 {
@ -557,7 +581,7 @@ impl Database {
for (room_id, _) in db.rooms.roomid_shortstatehash.iter() {
let shortroomid = db.globals.next_count()?.to_be_bytes();
db.rooms.roomid_shortroomid.insert(&room_id, &shortroomid)?;
println!("Migration: 8");
info!("Migration: 8");
}
// Update pduids db layout
let mut batch = db.rooms.pduid_pdu.iter().filter_map(|(key, v)| {
@ -608,7 +632,7 @@ impl Database {
db.globals.bump_database_version(8)?;
println!("Migration: 7 -> 8 finished");
warn!("Migration: 7 -> 8 finished");
}
if db.globals.database_version()? < 9 {
@ -650,7 +674,7 @@ impl Database {
println!("smaller batch done");
}
println!("Deleting starts");
info!("Deleting starts");
let batch2: Vec<_> = db
.rooms
@ -673,7 +697,7 @@ impl Database {
db.globals.bump_database_version(9)?;
println!("Migration: 8 -> 9 finished");
warn!("Migration: 8 -> 9 finished");
}
if db.globals.database_version()? < 10 {
@ -692,7 +716,7 @@ impl Database {
db.globals.bump_database_version(10)?;
println!("Migration: 9 -> 10 finished");
warn!("Migration: 9 -> 10 finished");
}
if db.globals.database_version()? < 11 {
@ -701,11 +725,28 @@ impl Database {
.clear()?;
db.globals.bump_database_version(11)?;
println!("Migration: 10 -> 11 finished");
}
warn!("Migration: 10 -> 11 finished");
}
let guard = db.read().await;
assert_eq!(11, latest_database_version);
info!(
"Loaded {} database with version {}",
config.database_backend, latest_database_version
);
} else {
guard
.globals
.bump_database_version(latest_database_version)?;
// Create the admin room and server user on first run
create_admin_room(&guard).await?;
warn!(
"Created new {} database with version {}",
config.database_backend, latest_database_version
);
}
// This data is probably outdated
guard.rooms.edus.presenceid_presence.clear()?;
@ -724,8 +765,6 @@ impl Database {
#[cfg(feature = "conduit_bin")]
pub async fn start_on_shutdown_tasks(db: Arc<TokioRwLock<Self>>, shutdown: Shutdown) {
use tracing::info;
tokio::spawn(async move {
shutdown.await;

View file

@ -1,4 +1,4 @@
use std::{convert::TryFrom, convert::TryInto, sync::Arc, time::Instant};
use std::{collections::BTreeMap, convert::TryFrom, convert::TryInto, sync::Arc, time::Instant};
use crate::{
error::{Error, Result},
@ -12,12 +12,22 @@ use rocket::{
http::RawStr,
};
use ruma::{
events::room::{
canonical_alias::RoomCanonicalAliasEventContent,
create::RoomCreateEventContent,
guest_access::{GuestAccess, RoomGuestAccessEventContent},
history_visibility::{HistoryVisibility, RoomHistoryVisibilityEventContent},
join_rules::{JoinRule, RoomJoinRulesEventContent},
member::{MembershipState, RoomMemberEventContent},
name::RoomNameEventContent,
power_levels::RoomPowerLevelsEventContent,
topic::RoomTopicEventContent,
},
events::{room::message::RoomMessageEventContent, EventType},
EventId, RoomId, RoomVersionId, ServerName, UserId,
identifiers::{EventId, RoomAliasId, RoomId, RoomName, RoomVersionId, ServerName, UserId},
};
use serde_json::value::to_raw_value;
use tokio::sync::{MutexGuard, RwLock, RwLockReadGuard};
use tracing::warn;
pub enum AdminRoomEvent {
ProcessMessage(String),
@ -52,16 +62,9 @@ impl Admin {
.try_into()
.expect("#admins:server_name is a valid room alias"),
)
.expect("Database data for admin room alias must be valid")
.expect("Admin room must exist");
let conduit_room = match conduit_room {
None => {
warn!("Conduit instance does not have an #admins room. Logging to that room will not work. Restart Conduit after creating a user to fix this.");
return;
}
Some(r) => r,
};
drop(guard);
let send_message = |message: RoomMessageEventContent,
@ -500,3 +503,331 @@ fn usage_to_html(text: &str, server_name: &ServerName) -> String {
text
}
/// Create the admin room.
///
/// Users in this room are considered admins by conduit, and the room can be
/// used to issue admin commands by talking to the server user inside it.
pub(crate) async fn create_admin_room(db: &Database) -> Result<()> {
let room_id = RoomId::new(db.globals.server_name());
db.rooms.get_or_create_shortroomid(&room_id, &db.globals)?;
let mutex_state = Arc::clone(
db.globals
.roomid_mutex_state
.write()
.unwrap()
.entry(room_id.clone())
.or_default(),
);
let state_lock = mutex_state.lock().await;
// Create a user for the server
let conduit_user = UserId::parse_with_server_name("conduit", db.globals.server_name())
.expect("@conduit:server_name is valid");
db.users.create(&conduit_user, None)?;
let mut content = RoomCreateEventContent::new(conduit_user.clone());
content.federate = true;
content.predecessor = None;
content.room_version = RoomVersionId::V6;
// 1. The room create event
db.rooms.build_and_append_pdu(
PduBuilder {
event_type: EventType::RoomCreate,
content: to_raw_value(&content).expect("event is valid, we just created it"),
unsigned: None,
state_key: Some("".to_owned()),
redacts: None,
},
&conduit_user,
&room_id,
&db,
&state_lock,
)?;
// 2. Make conduit bot join
db.rooms.build_and_append_pdu(
PduBuilder {
event_type: EventType::RoomMember,
content: to_raw_value(&RoomMemberEventContent {
membership: MembershipState::Join,
displayname: None,
avatar_url: None,
is_direct: None,
third_party_invite: None,
blurhash: None,
reason: None,
join_authorized_via_users_server: None,
})
.expect("event is valid, we just created it"),
unsigned: None,
state_key: Some(conduit_user.to_string()),
redacts: None,
},
&conduit_user,
&room_id,
&db,
&state_lock,
)?;
// 3. Power levels
let mut users = BTreeMap::new();
users.insert(conduit_user.clone(), 100.into());
db.rooms.build_and_append_pdu(
PduBuilder {
event_type: EventType::RoomPowerLevels,
content: to_raw_value(&RoomPowerLevelsEventContent {
users,
..Default::default()
})
.expect("event is valid, we just created it"),
unsigned: None,
state_key: Some("".to_owned()),
redacts: None,
},
&conduit_user,
&room_id,
&db,
&state_lock,
)?;
// 4.1 Join Rules
db.rooms.build_and_append_pdu(
PduBuilder {
event_type: EventType::RoomJoinRules,
content: to_raw_value(&RoomJoinRulesEventContent::new(JoinRule::Invite))
.expect("event is valid, we just created it"),
unsigned: None,
state_key: Some("".to_owned()),
redacts: None,
},
&conduit_user,
&room_id,
&db,
&state_lock,
)?;
// 4.2 History Visibility
db.rooms.build_and_append_pdu(
PduBuilder {
event_type: EventType::RoomHistoryVisibility,
content: to_raw_value(&RoomHistoryVisibilityEventContent::new(
HistoryVisibility::Shared,
))
.expect("event is valid, we just created it"),
unsigned: None,
state_key: Some("".to_owned()),
redacts: None,
},
&conduit_user,
&room_id,
&db,
&state_lock,
)?;
// 4.3 Guest Access
db.rooms.build_and_append_pdu(
PduBuilder {
event_type: EventType::RoomGuestAccess,
content: to_raw_value(&RoomGuestAccessEventContent::new(GuestAccess::Forbidden))
.expect("event is valid, we just created it"),
unsigned: None,
state_key: Some("".to_owned()),
redacts: None,
},
&conduit_user,
&room_id,
&db,
&state_lock,
)?;
// 5. Events implied by name and topic
let room_name = RoomName::parse(format!("{} Admin Room", db.globals.server_name()))
.expect("Room name is valid");
db.rooms.build_and_append_pdu(
PduBuilder {
event_type: EventType::RoomName,
content: to_raw_value(&RoomNameEventContent::new(Some(room_name)))
.expect("event is valid, we just created it"),
unsigned: None,
state_key: Some("".to_owned()),
redacts: None,
},
&conduit_user,
&room_id,
&db,
&state_lock,
)?;
db.rooms.build_and_append_pdu(
PduBuilder {
event_type: EventType::RoomTopic,
content: to_raw_value(&RoomTopicEventContent {
topic: format!("Manage {}", db.globals.server_name()),
})
.expect("event is valid, we just created it"),
unsigned: None,
state_key: Some("".to_owned()),
redacts: None,
},
&conduit_user,
&room_id,
&db,
&state_lock,
)?;
// 6. Room alias
let alias: Box<RoomAliasId> = format!("#admins:{}", db.globals.server_name())
.try_into()
.expect("#admins:server_name is a valid alias name");
db.rooms.build_and_append_pdu(
PduBuilder {
event_type: EventType::RoomCanonicalAlias,
content: to_raw_value(&RoomCanonicalAliasEventContent {
alias: Some(alias.clone()),
alt_aliases: Vec::new(),
})
.expect("event is valid, we just created it"),
unsigned: None,
state_key: Some("".to_owned()),
redacts: None,
},
&conduit_user,
&room_id,
&db,
&state_lock,
)?;
db.rooms.set_alias(&alias, Some(&room_id), &db.globals)?;
Ok(())
}
/// Invite the user to the conduit admin room.
///
/// In conduit, this is equivalent to granting admin privileges.
pub(crate) async fn make_user_admin(
db: &Database,
user_id: &UserId,
displayname: String,
) -> Result<()> {
let admin_room_alias: Box<RoomAliasId> = format!("#admins:{}", db.globals.server_name())
.try_into()
.expect("#admins:server_name is a valid alias name");
let room_id = db
.rooms
.id_from_alias(&admin_room_alias)?
.expect("Admin room must exist");
let mutex_state = Arc::clone(
db.globals
.roomid_mutex_state
.write()
.unwrap()
.entry(room_id.clone())
.or_default(),
);
let state_lock = mutex_state.lock().await;
// Use the server user to grant the new admin's power level
let conduit_user = UserId::parse_with_server_name("conduit", db.globals.server_name())
.expect("@conduit:server_name is valid");
// Invite and join the real user
db.rooms.build_and_append_pdu(
PduBuilder {
event_type: EventType::RoomMember,
content: to_raw_value(&RoomMemberEventContent {
membership: MembershipState::Invite,
displayname: None,
avatar_url: None,
is_direct: None,
third_party_invite: None,
blurhash: None,
reason: None,
join_authorized_via_users_server: None,
})
.expect("event is valid, we just created it"),
unsigned: None,
state_key: Some(user_id.to_string()),
redacts: None,
},
&conduit_user,
&room_id,
&db,
&state_lock,
)?;
db.rooms.build_and_append_pdu(
PduBuilder {
event_type: EventType::RoomMember,
content: to_raw_value(&RoomMemberEventContent {
membership: MembershipState::Join,
displayname: Some(displayname),
avatar_url: None,
is_direct: None,
third_party_invite: None,
blurhash: None,
reason: None,
join_authorized_via_users_server: None,
})
.expect("event is valid, we just created it"),
unsigned: None,
state_key: Some(user_id.to_string()),
redacts: None,
},
&user_id,
&room_id,
&db,
&state_lock,
)?;
// Set power level
let mut users = BTreeMap::new();
users.insert(conduit_user.to_owned(), 100.into());
users.insert(user_id.to_owned(), 100.into());
db.rooms.build_and_append_pdu(
PduBuilder {
event_type: EventType::RoomPowerLevels,
content: to_raw_value(&RoomPowerLevelsEventContent {
users,
..Default::default()
})
.expect("event is valid, we just created it"),
unsigned: None,
state_key: Some("".to_owned()),
redacts: None,
},
&conduit_user,
&room_id,
&db,
&state_lock,
)?;
// Send welcome message
db.rooms.build_and_append_pdu(
PduBuilder {
event_type: EventType::RoomMessage,
content: to_raw_value(&RoomMessageEventContent::text_html(
"## Thank you for trying out Conduit!\n\nConduit is currently in Beta. This means you can join and participate in most Matrix rooms, but not all features are supported and you might run into bugs from time to time.\n\nHelpful links:\n> Website: https://conduit.rs\n> Git and Documentation: https://gitlab.com/famedly/conduit\n> Report issues: https://gitlab.com/famedly/conduit/-/issues\n\nHere are some rooms you can join (by typing the command):\n\nConduit room (Ask questions and get notified on updates):\n`/join #conduit:fachschaften.org`\n\nConduit lounge (Off-topic, only Conduit users are allowed to join)\n`/join #conduit-lounge:conduit.rs`".to_owned(),
"<h2>Thank you for trying out Conduit!</h2>\n<p>Conduit is currently in Beta. This means you can join and participate in most Matrix rooms, but not all features are supported and you might run into bugs from time to time.</p>\n<p>Helpful links:</p>\n<blockquote>\n<p>Website: https://conduit.rs<br>Git and Documentation: https://gitlab.com/famedly/conduit<br>Report issues: https://gitlab.com/famedly/conduit/-/issues</p>\n</blockquote>\n<p>Here are some rooms you can join (by typing the command):</p>\n<p>Conduit room (Ask questions and get notified on updates):<br><code>/join #conduit:fachschaften.org</code></p>\n<p>Conduit lounge (Off-topic, only Conduit users are allowed to join)<br><code>/join #conduit-lounge:conduit.rs</code></p>\n".to_owned(),
))
.expect("event is valid, we just created it"),
unsigned: None,
state_key: None,
redacts: None,
},
&conduit_user,
&room_id,
&db,
&state_lock,
)?;
Ok(())
}