Merge remote-tracking branch 'origin' into feat/livekit
This commit is contained in:
@@ -145,7 +145,13 @@ pub enum EventV1 {
|
||||
},
|
||||
|
||||
/// User joins server
|
||||
ServerMemberJoin { id: String, user: String },
|
||||
ServerMemberJoin {
|
||||
id: String,
|
||||
// Deprecated: use member.id.user
|
||||
#[deprecated = "Use member.id.user instead"]
|
||||
user: String,
|
||||
member: Member,
|
||||
},
|
||||
|
||||
/// User left server
|
||||
ServerMemberLeave {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use serde::Deserialize;
|
||||
use serde::{Serialize, Deserialize};
|
||||
|
||||
use super::client::Ping;
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(tag = "type")]
|
||||
pub enum ClientMessage {
|
||||
Authenticate { token: String },
|
||||
|
||||
@@ -46,6 +46,10 @@ auto_derived_partial!(
|
||||
/// Whether the member is server-wide voice deafened
|
||||
#[serde(skip_serializing_if = "is_true", default = "default_true")]
|
||||
pub can_receive: bool,
|
||||
|
||||
// This value only exists in the database, not the models.
|
||||
// If it is not-None, the database layer should return None to member fetching queries.
|
||||
// pub pending_deletion_at: Option<Timestamp>
|
||||
},
|
||||
"PartialMember"
|
||||
);
|
||||
@@ -68,6 +72,7 @@ auto_derived!(
|
||||
Timeout,
|
||||
CanReceive,
|
||||
CanPublish,
|
||||
JoinedAt,
|
||||
}
|
||||
|
||||
/// Member removal intention
|
||||
@@ -110,7 +115,7 @@ impl Member {
|
||||
return Err(create_error!(AlreadyInServer));
|
||||
}
|
||||
|
||||
let member = Member {
|
||||
let mut member = Member {
|
||||
id: MemberCompositeKey {
|
||||
server: server.id.to_string(),
|
||||
user: user.id.to_string(),
|
||||
@@ -118,7 +123,9 @@ impl Member {
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
db.insert_member(&member).await?;
|
||||
if let Some(updated) = db.insert_or_merge_member(&member).await? {
|
||||
member = updated;
|
||||
}
|
||||
|
||||
let should_fetch = channels.is_none();
|
||||
let mut channels = channels.unwrap_or_default();
|
||||
@@ -152,6 +159,7 @@ impl Member {
|
||||
EventV1::ServerMemberJoin {
|
||||
id: server.id.clone(),
|
||||
user: user.id.clone(),
|
||||
member: member.clone().into(),
|
||||
}
|
||||
.p(server.id.clone())
|
||||
.await;
|
||||
@@ -215,6 +223,7 @@ impl Member {
|
||||
|
||||
pub fn remove_field(&mut self, field: &FieldsMember) {
|
||||
match field {
|
||||
FieldsMember::JoinedAt => (),
|
||||
FieldsMember::Avatar => self.avatar = None,
|
||||
FieldsMember::Nickname => self.nickname = None,
|
||||
FieldsMember::Roles => self.roles.clear(),
|
||||
@@ -255,7 +264,7 @@ impl Member {
|
||||
intention: RemovalIntention,
|
||||
silent: bool,
|
||||
) -> Result<()> {
|
||||
db.delete_member(&self.id).await?;
|
||||
db.soft_delete_member(&self.id).await?;
|
||||
|
||||
EventV1::ServerMemberLeave {
|
||||
id: self.id.server.to_string(),
|
||||
@@ -291,3 +300,74 @@ impl Member {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use iso8601_timestamp::{Duration, Timestamp};
|
||||
use revolt_models::v0::DataCreateServer;
|
||||
|
||||
use crate::{Member, PartialMember, RemovalIntention, Server, User};
|
||||
|
||||
#[async_std::test]
|
||||
async fn muted_member_rejoin() {
|
||||
database_test!(|db| async move {
|
||||
match db {
|
||||
crate::Database::Reference(_) => return,
|
||||
crate::Database::MongoDb(_) => (),
|
||||
}
|
||||
let owner = User::create(&db, "Server Owner".to_string(), None, None)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let kickable_user = User::create(&db, "Member".to_string(), None, None)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let server = Server::create(
|
||||
&db,
|
||||
DataCreateServer {
|
||||
name: "Server".to_string(),
|
||||
description: None,
|
||||
nsfw: None,
|
||||
},
|
||||
&owner,
|
||||
false,
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
.0;
|
||||
|
||||
Member::create(&db, &server, &owner, None).await.unwrap();
|
||||
let mut kickable_member = Member::create(&db, &server, &kickable_user, None)
|
||||
.await
|
||||
.unwrap()
|
||||
.0;
|
||||
|
||||
kickable_member
|
||||
.update(
|
||||
&db,
|
||||
PartialMember {
|
||||
timeout: Some(Timestamp::now_utc() + Duration::minutes(5)),
|
||||
..Default::default()
|
||||
},
|
||||
vec![],
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert!(kickable_member.in_timeout());
|
||||
|
||||
kickable_member
|
||||
.remove(&db, &server, RemovalIntention::Kick, false)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let kickable_member = Member::create(&db, &server, &kickable_user, None)
|
||||
.await
|
||||
.unwrap()
|
||||
.0;
|
||||
|
||||
assert!(kickable_member.in_timeout())
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,13 +73,13 @@ impl ChunkedServerMembersGenerator {
|
||||
#[async_trait]
|
||||
pub trait AbstractServerMembers: Sync + Send {
|
||||
/// Insert a new server member into the database
|
||||
async fn insert_member(&self, member: &Member) -> Result<()>;
|
||||
async fn insert_or_merge_member(&self, member: &Member) -> Result<Option<Member>>;
|
||||
|
||||
/// Fetch a server member by their id
|
||||
async fn fetch_member(&self, server_id: &str, user_id: &str) -> Result<Member>;
|
||||
|
||||
/// Fetch all members in a server
|
||||
async fn fetch_all_members<'a>(&self, server_id: &str) -> Result<Vec<Member>>;
|
||||
async fn fetch_all_members(&self, server_id: &str) -> Result<Vec<Member>>;
|
||||
|
||||
/// Fetch all members in a server as an iterator
|
||||
async fn fetch_all_members_chunked(
|
||||
@@ -100,10 +100,10 @@ pub trait AbstractServerMembers: Sync + Send {
|
||||
) -> Result<ChunkedServerMembersGenerator>;
|
||||
|
||||
/// Fetch all memberships for a user
|
||||
async fn fetch_all_memberships<'a>(&self, user_id: &str) -> Result<Vec<Member>>;
|
||||
async fn fetch_all_memberships(&self, user_id: &str) -> Result<Vec<Member>>;
|
||||
|
||||
/// Fetch multiple members by their ids
|
||||
async fn fetch_members<'a>(&self, server_id: &str, ids: &'a [String]) -> Result<Vec<Member>>;
|
||||
async fn fetch_members(&self, server_id: &str, ids: &[String]) -> Result<Vec<Member>>;
|
||||
|
||||
/// Fetch member count of a server
|
||||
async fn fetch_member_count(&self, server_id: &str) -> Result<usize>;
|
||||
@@ -119,6 +119,14 @@ pub trait AbstractServerMembers: Sync + Send {
|
||||
remove: Vec<FieldsMember>,
|
||||
) -> Result<()>;
|
||||
|
||||
/// Delete a server member by their id
|
||||
async fn delete_member(&self, id: &MemberCompositeKey) -> Result<()>;
|
||||
/// Marks a user as no longer a member of a server, while retaining the database value.
|
||||
/// This is used to keep information such as timeouts in place, but will remove information such as join date and applied roles.
|
||||
async fn soft_delete_member(&self, id: &MemberCompositeKey) -> Result<()>;
|
||||
|
||||
/// Forcibly delete a server member by their id.
|
||||
/// This will cancel any pending timeouts or other longer term actions, and they will not be reapplied on rejoin.
|
||||
async fn force_delete_member(&self, id: &MemberCompositeKey) -> Result<()>;
|
||||
|
||||
/// Fetch all members who have been marked for deletion.
|
||||
async fn remove_dangling_members(&self) -> Result<()>;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
use bson::Document;
|
||||
use futures::StreamExt;
|
||||
use iso8601_timestamp::Timestamp;
|
||||
use mongodb::options::ReadConcern;
|
||||
use revolt_result::Result;
|
||||
|
||||
@@ -11,9 +13,42 @@ static COL: &str = "server_members";
|
||||
|
||||
#[async_trait]
|
||||
impl AbstractServerMembers for MongoDb {
|
||||
/// Insert a new server member into the database
|
||||
async fn insert_member(&self, member: &Member) -> Result<()> {
|
||||
query!(self, insert_one, COL, &member).map(|_| ())
|
||||
/// Insert a new server member (or use the existing member if one is found)
|
||||
async fn insert_or_merge_member(&self, member: &Member) -> Result<Option<Member>> {
|
||||
let existing: Result<Option<Document>> = query!(
|
||||
self,
|
||||
find_one,
|
||||
COL,
|
||||
doc! {
|
||||
"_id.server": &member.id.server,
|
||||
"_id.user": &member.id.user,
|
||||
"pending_deletion_at": {"$exists": true}
|
||||
}
|
||||
);
|
||||
// Update the existing record if it exist, otherwise make a new record
|
||||
if existing.is_ok_and(|x| x.is_some()) {
|
||||
self.col::<Member>(COL)
|
||||
.find_one_and_update(
|
||||
doc! {
|
||||
"_id.server": &member.id.server,
|
||||
"_id.user": &member.id.user,
|
||||
},
|
||||
doc! {
|
||||
"$set": {
|
||||
"joined_at": member.joined_at.duration_since(Timestamp::UNIX_EPOCH).whole_seconds(),
|
||||
},
|
||||
"$unset": {
|
||||
"pending_deletion_at": ""
|
||||
}
|
||||
},
|
||||
)
|
||||
.return_document(mongodb::options::ReturnDocument::After)
|
||||
.await
|
||||
.map_err(|_| create_database_error!("update_one", COL))
|
||||
} else {
|
||||
query!(self, insert_one, COL, &member).map(|_| ())?;
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
/// Fetch a server member by their id
|
||||
@@ -24,18 +59,20 @@ impl AbstractServerMembers for MongoDb {
|
||||
COL,
|
||||
doc! {
|
||||
"_id.server": server_id,
|
||||
"_id.user": user_id
|
||||
"_id.user": user_id,
|
||||
"pending_deletion_at": {"$exists": false}
|
||||
}
|
||||
)?
|
||||
.ok_or_else(|| create_error!(NotFound))
|
||||
}
|
||||
|
||||
/// Fetch all members in a server
|
||||
async fn fetch_all_members<'a>(&self, server_id: &str) -> Result<Vec<Member>> {
|
||||
async fn fetch_all_members(&self, server_id: &str) -> Result<Vec<Member>> {
|
||||
Ok(self
|
||||
.col::<Member>(COL)
|
||||
.find(doc! {
|
||||
"_id.server": server_id
|
||||
"_id.server": server_id,
|
||||
"pending_deletion_at": {"$exists": false}
|
||||
})
|
||||
.await
|
||||
.map_err(|_| create_database_error!("find", COL))?
|
||||
@@ -139,11 +176,12 @@ impl AbstractServerMembers for MongoDb {
|
||||
}
|
||||
|
||||
/// Fetch all memberships for a user
|
||||
async fn fetch_all_memberships<'a>(&self, user_id: &str) -> Result<Vec<Member>> {
|
||||
async fn fetch_all_memberships(&self, user_id: &str) -> Result<Vec<Member>> {
|
||||
Ok(self
|
||||
.col::<Member>(COL)
|
||||
.find(doc! {
|
||||
"_id.user": user_id
|
||||
"_id.user": user_id,
|
||||
"pending_deletion_at": {"$exists": false}
|
||||
})
|
||||
.await
|
||||
.map_err(|_| create_database_error!("find", COL))?
|
||||
@@ -159,11 +197,12 @@ impl AbstractServerMembers for MongoDb {
|
||||
}
|
||||
|
||||
/// Fetch multiple members by their ids
|
||||
async fn fetch_members<'a>(&self, server_id: &str, ids: &'a [String]) -> Result<Vec<Member>> {
|
||||
async fn fetch_members(&self, server_id: &str, ids: &[String]) -> Result<Vec<Member>> {
|
||||
Ok(self
|
||||
.col::<Member>(COL)
|
||||
.find(doc! {
|
||||
"_id.server": server_id,
|
||||
"pending_deletion_at": {"$exists": false},
|
||||
"_id.user": {
|
||||
"$in": ids
|
||||
}
|
||||
@@ -185,7 +224,8 @@ impl AbstractServerMembers for MongoDb {
|
||||
async fn fetch_member_count(&self, server_id: &str) -> Result<usize> {
|
||||
self.col::<Member>(COL)
|
||||
.count_documents(doc! {
|
||||
"_id.server": server_id
|
||||
"_id.server": server_id,
|
||||
"pending_deletion_at": {"$exists": false}
|
||||
})
|
||||
.await
|
||||
.map(|c| c as usize)
|
||||
@@ -196,7 +236,8 @@ impl AbstractServerMembers for MongoDb {
|
||||
async fn fetch_server_count(&self, user_id: &str) -> Result<usize> {
|
||||
self.col::<Member>(COL)
|
||||
.count_documents(doc! {
|
||||
"_id.user": user_id
|
||||
"_id.user": user_id,
|
||||
"pending_deletion_at": {"$exists": false}
|
||||
})
|
||||
.await
|
||||
.map(|c| c as usize)
|
||||
@@ -225,8 +266,42 @@ impl AbstractServerMembers for MongoDb {
|
||||
.map(|_| ())
|
||||
}
|
||||
|
||||
/// Marks a member for deletion.
|
||||
/// This will remove the record if the user has no pending actions (eg. timeout),
|
||||
/// otherwise will slate the record for deletion by revolt_crond once the actions expire.
|
||||
async fn soft_delete_member(&self, id: &MemberCompositeKey) -> Result<()> {
|
||||
let member = self.fetch_member(&id.server, &id.user).await;
|
||||
if let Ok(member) = member {
|
||||
if member.in_timeout() {
|
||||
self.col::<Document>(COL)
|
||||
.update_many(
|
||||
doc! {
|
||||
"_id.server": &id.server,
|
||||
"_id.user": &id.user,
|
||||
},
|
||||
doc! {
|
||||
"$set": {"pending_deletion_at": format!("{}", member.timeout.unwrap().format())},
|
||||
"$unset": {
|
||||
"joined_at": "",
|
||||
"avatar": "",
|
||||
"nickname": "",
|
||||
"roles": ""
|
||||
}
|
||||
},
|
||||
)
|
||||
.await
|
||||
.map(|_| ())
|
||||
.map_err(|_| create_database_error!("update_many", COL))
|
||||
} else {
|
||||
self.force_delete_member(id).await
|
||||
}
|
||||
} else {
|
||||
Err(create_database_error!("fetch_member", COL))
|
||||
}
|
||||
}
|
||||
|
||||
/// Delete a server member by their id
|
||||
async fn delete_member(&self, id: &MemberCompositeKey) -> Result<()> {
|
||||
async fn force_delete_member(&self, id: &MemberCompositeKey) -> Result<()> {
|
||||
query!(
|
||||
self,
|
||||
delete_one,
|
||||
@@ -238,11 +313,25 @@ impl AbstractServerMembers for MongoDb {
|
||||
)
|
||||
.map(|_| ())
|
||||
}
|
||||
|
||||
async fn remove_dangling_members(&self) -> Result<()> {
|
||||
let now = Timestamp::now_utc();
|
||||
let date = bson::to_bson(&now).expect("Failed to serialize timestamp");
|
||||
|
||||
self.col::<Document>(COL)
|
||||
.delete_many(doc! {
|
||||
"pending_deletion_at": {"$lt": date}
|
||||
})
|
||||
.await
|
||||
.map(|_| ())
|
||||
.map_err(|_| create_database_error!("count_documents", COL))
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoDocumentPath for FieldsMember {
|
||||
fn as_path(&self) -> Option<&'static str> {
|
||||
Some(match self {
|
||||
FieldsMember::JoinedAt => "joined_at",
|
||||
FieldsMember::Avatar => "avatar",
|
||||
FieldsMember::Nickname => "nickname",
|
||||
FieldsMember::Roles => "roles",
|
||||
|
||||
@@ -8,13 +8,13 @@ use super::{AbstractServerMembers, ChunkedServerMembersGenerator};
|
||||
#[async_trait]
|
||||
impl AbstractServerMembers for ReferenceDb {
|
||||
/// Insert a new server member into the database
|
||||
async fn insert_member(&self, member: &Member) -> Result<()> {
|
||||
async fn insert_or_merge_member(&self, member: &Member) -> Result<Option<Member>> {
|
||||
let mut server_members = self.server_members.lock().await;
|
||||
if server_members.contains_key(&member.id) {
|
||||
Err(create_database_error!("insert", "member"))
|
||||
} else {
|
||||
server_members.insert(member.id.clone(), member.clone());
|
||||
Ok(())
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ impl AbstractServerMembers for ReferenceDb {
|
||||
}
|
||||
|
||||
/// Fetch all members in a server
|
||||
async fn fetch_all_members<'a>(&self, server_id: &str) -> Result<Vec<Member>> {
|
||||
async fn fetch_all_members(&self, server_id: &str) -> Result<Vec<Member>> {
|
||||
let server_members = self.server_members.lock().await;
|
||||
Ok(server_members
|
||||
.values()
|
||||
@@ -105,7 +105,7 @@ impl AbstractServerMembers for ReferenceDb {
|
||||
}
|
||||
|
||||
/// Fetch all memberships for a user
|
||||
async fn fetch_all_memberships<'a>(&self, user_id: &str) -> Result<Vec<Member>> {
|
||||
async fn fetch_all_memberships(&self, user_id: &str) -> Result<Vec<Member>> {
|
||||
let server_members = self.server_members.lock().await;
|
||||
Ok(server_members
|
||||
.values()
|
||||
@@ -115,7 +115,7 @@ impl AbstractServerMembers for ReferenceDb {
|
||||
}
|
||||
|
||||
/// Fetch multiple members by their ids
|
||||
async fn fetch_members<'a>(&self, server_id: &str, ids: &'a [String]) -> Result<Vec<Member>> {
|
||||
async fn fetch_members(&self, server_id: &str, ids: &[String]) -> Result<Vec<Member>> {
|
||||
let server_members = self.server_members.lock().await;
|
||||
Ok(ids
|
||||
.iter()
|
||||
@@ -169,8 +169,26 @@ impl AbstractServerMembers for ReferenceDb {
|
||||
}
|
||||
}
|
||||
|
||||
/// Soft delete a member
|
||||
async fn soft_delete_member(&self, id: &MemberCompositeKey) -> Result<()> {
|
||||
let mut server_members = self.server_members.lock().await;
|
||||
|
||||
let member = server_members.get_mut(id);
|
||||
if let Some(member) = member {
|
||||
if member.in_timeout() {
|
||||
panic!("Soft deletion is not implemented.")
|
||||
} else if server_members.remove(id).is_some() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(create_error!(NotFound))
|
||||
}
|
||||
} else {
|
||||
Err(create_error!(NotFound))
|
||||
}
|
||||
}
|
||||
|
||||
/// Delete a server member by their id
|
||||
async fn delete_member(&self, id: &MemberCompositeKey) -> Result<()> {
|
||||
async fn force_delete_member(&self, id: &MemberCompositeKey) -> Result<()> {
|
||||
let mut server_members = self.server_members.lock().await;
|
||||
if server_members.remove(id).is_some() {
|
||||
Ok(())
|
||||
@@ -178,4 +196,8 @@ impl AbstractServerMembers for ReferenceDb {
|
||||
Err(create_error!(NotFound))
|
||||
}
|
||||
}
|
||||
|
||||
async fn remove_dangling_members(&self) -> Result<()> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -688,6 +688,7 @@ impl From<crate::FieldsMember> for FieldsMember {
|
||||
crate::FieldsMember::Timeout => FieldsMember::Timeout,
|
||||
crate::FieldsMember::CanReceive => FieldsMember::CanReceive,
|
||||
crate::FieldsMember::CanPublish => FieldsMember::CanPublish,
|
||||
crate::FieldsMember::JoinedAt => FieldsMember::JoinedAt,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -701,6 +702,7 @@ impl From<FieldsMember> for crate::FieldsMember {
|
||||
FieldsMember::Timeout => crate::FieldsMember::Timeout,
|
||||
FieldsMember::CanReceive => crate::FieldsMember::CanReceive,
|
||||
FieldsMember::CanPublish => crate::FieldsMember::CanPublish,
|
||||
FieldsMember::JoinedAt => crate::FieldsMember::JoinedAt,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,7 +39,9 @@ pub async fn load_fixture(db: &Database, input: &str) -> HashMap<String, String>
|
||||
LoadedFixture::User(user) => db.insert_user(&user).await.unwrap(),
|
||||
LoadedFixture::Channel(channel) => db.insert_channel(&channel).await.unwrap(),
|
||||
LoadedFixture::Server(server) => db.insert_server(&server).await.unwrap(),
|
||||
LoadedFixture::ServerMember(member) => db.insert_member(&member).await.unwrap(),
|
||||
LoadedFixture::ServerMember(member) => {
|
||||
db.insert_or_merge_member(&member).await.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -94,6 +94,7 @@ auto_derived!(
|
||||
Timeout,
|
||||
CanReceive,
|
||||
CanPublish,
|
||||
JoinedAt,
|
||||
}
|
||||
|
||||
/// Member removal intention
|
||||
|
||||
@@ -255,12 +255,14 @@ auto_derived!(
|
||||
pub flags: i32,
|
||||
}
|
||||
|
||||
/// Mutual friends and servers response
|
||||
/// Mutual friends, servers, groups and DMs response
|
||||
pub struct MutualResponse {
|
||||
/// Array of mutual user IDs that both users are friends with
|
||||
pub users: Vec<String>,
|
||||
/// Array of mutual server IDs that both users are in
|
||||
pub servers: Vec<String>,
|
||||
/// Array of mutual group and dm IDs that both users are in
|
||||
pub channels: Vec<String>,
|
||||
}
|
||||
|
||||
/// Bot information for if the user is a bot
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
name = "revolt-parser"
|
||||
version = "0.8.8"
|
||||
edition = "2021"
|
||||
license = "AGPL-3.0-or-later"
|
||||
license = "MIT"
|
||||
authors = ["Zomatree <me@zomatree.live>", "Paul Makles <me@insrt.uk>"]
|
||||
description = "Revolt Backend: Message Parser"
|
||||
|
||||
[dependencies]
|
||||
|
||||
9
crates/core/parser/LICENSE
Normal file
9
crates/core/parser/LICENSE
Normal file
@@ -0,0 +1,9 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 Pawel Makles
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
@@ -4,15 +4,18 @@ use logos::Logos;
|
||||
|
||||
#[derive(Debug, Clone, Logos, PartialEq)]
|
||||
#[logos(skip "\n")]
|
||||
pub enum MessageToken {
|
||||
#[logos(subpattern id="[0123456789ABCDEFGHJKMNPQRSTVWXYZ]{26}")]
|
||||
pub enum MessageToken<'a> {
|
||||
#[token("\\")]
|
||||
Escape,
|
||||
#[regex("(```[^`\n]*)|(``)|`", |lex| lex.slice().to_owned().chars().filter(|&c| c == '`').count())]
|
||||
#[regex("```[^`\n]*", |_| 3)]
|
||||
#[regex("``", |_| 2)]
|
||||
#[regex("`", |_| 1)]
|
||||
CodeblockMarker(usize),
|
||||
#[regex("<@[0123456789ABCDEFGHJKMNPQRSTVWXYZ]{26}>", |lex| lex.slice()[2..lex.slice().len() - 1].to_owned())]
|
||||
UserMention(String),
|
||||
#[regex("<%[0123456789ABCDEFGHJKMNPQRSTVWXYZ]{26}>", |lex| lex.slice()[2..lex.slice().len() - 1].to_owned())]
|
||||
RoleMention(String),
|
||||
#[regex("<@(?&id)>", |lex| &lex.slice()[2..lex.slice().len() - 1])]
|
||||
UserMention(&'a str),
|
||||
#[regex("<%(?&id)>", |lex| &lex.slice()[2..lex.slice().len() - 1],)]
|
||||
RoleMention(&'a str),
|
||||
#[token("@everyone")]
|
||||
MentionEveryone,
|
||||
#[token("@online")]
|
||||
@@ -27,13 +30,13 @@ pub struct MessageResults {
|
||||
pub mentions_online: bool
|
||||
}
|
||||
|
||||
struct MessageParserIterator<I> {
|
||||
struct MessageParserIterator<'a, I> {
|
||||
inner: I,
|
||||
temp: VecDeque<MessageToken>
|
||||
temp: VecDeque<MessageToken<'a>>
|
||||
}
|
||||
|
||||
impl<I: Iterator<Item = MessageToken>> Iterator for MessageParserIterator<I> {
|
||||
type Item = MessageToken;
|
||||
impl<'a, I: Iterator<Item = MessageToken<'a>>> Iterator for MessageParserIterator<'a, I> {
|
||||
type Item = MessageToken<'a>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if !self.temp.is_empty() {
|
||||
@@ -80,8 +83,8 @@ pub fn parse_message(text: &str) -> MessageResults {
|
||||
match token {
|
||||
MessageToken::Escape => {}
|
||||
MessageToken::CodeblockMarker(_) => {},
|
||||
MessageToken::UserMention(id) => { results.user_mentions.insert(id); },
|
||||
MessageToken::RoleMention(id) => { results.role_mentions.insert(id); },
|
||||
MessageToken::UserMention(id) => { results.user_mentions.insert(id.to_string()); },
|
||||
MessageToken::RoleMention(id) => { results.role_mentions.insert(id.to_string()); },
|
||||
MessageToken::MentionEveryone => results.mentions_everyone = true,
|
||||
MessageToken::MentionOnline => results.mentions_online = true,
|
||||
};
|
||||
@@ -106,7 +109,7 @@ mod tests {
|
||||
let output = parse_message_iter("Hello <@01FD58YK5W7QRV5H3D64KTQYX3>.").collect::<Vec<_>>();
|
||||
|
||||
assert_eq!(output.len(), 1);
|
||||
assert_eq!(output[0], MessageToken::UserMention("01FD58YK5W7QRV5H3D64KTQYX3".to_string()));
|
||||
assert_eq!(output[0], MessageToken::UserMention("01FD58YK5W7QRV5H3D64KTQYX3"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -114,7 +117,7 @@ mod tests {
|
||||
let output = parse_message_iter("Hello <%01FD58YK5W7QRV5H3D64KTQYX3>.").collect::<Vec<_>>();
|
||||
|
||||
assert_eq!(output.len(), 1);
|
||||
assert_eq!(output[0], MessageToken::RoleMention("01FD58YK5W7QRV5H3D64KTQYX3".to_string()));
|
||||
assert_eq!(output[0], MessageToken::RoleMention("01FD58YK5W7QRV5H3D64KTQYX3"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -138,8 +141,8 @@ mod tests {
|
||||
let output = parse_message_iter("Hello <@01FD58YK5W7QRV5H3D64KTQYX3>, <%01FD58YK5W7QRV5H3D64KTQYX3>, @everyone and @online.").collect::<Vec<_>>();
|
||||
|
||||
assert_eq!(output.len(), 4);
|
||||
assert_eq!(output[0], MessageToken::UserMention("01FD58YK5W7QRV5H3D64KTQYX3".to_string()));
|
||||
assert_eq!(output[1], MessageToken::RoleMention("01FD58YK5W7QRV5H3D64KTQYX3".to_string()));
|
||||
assert_eq!(output[0], MessageToken::UserMention("01FD58YK5W7QRV5H3D64KTQYX3"));
|
||||
assert_eq!(output[1], MessageToken::RoleMention("01FD58YK5W7QRV5H3D64KTQYX3"));
|
||||
assert_eq!(output[2], MessageToken::MentionEveryone);
|
||||
assert_eq!(output[3], MessageToken::MentionOnline);
|
||||
}
|
||||
@@ -149,8 +152,8 @@ mod tests {
|
||||
let output = parse_message_iter("<@01FD58YK5W7QRV5H3D64KTQYX3><%01FD58YK5W7QRV5H3D64KTQYX3>@everyone@online").collect::<Vec<_>>();
|
||||
|
||||
assert_eq!(output.len(), 4);
|
||||
assert_eq!(output[0], MessageToken::UserMention("01FD58YK5W7QRV5H3D64KTQYX3".to_string()));
|
||||
assert_eq!(output[1], MessageToken::RoleMention("01FD58YK5W7QRV5H3D64KTQYX3".to_string()));
|
||||
assert_eq!(output[0], MessageToken::UserMention("01FD58YK5W7QRV5H3D64KTQYX3"));
|
||||
assert_eq!(output[1], MessageToken::RoleMention("01FD58YK5W7QRV5H3D64KTQYX3"));
|
||||
assert_eq!(output[2], MessageToken::MentionEveryone);
|
||||
assert_eq!(output[3], MessageToken::MentionOnline);
|
||||
}
|
||||
@@ -170,15 +173,14 @@ mod tests {
|
||||
|
||||
assert_eq!(output.len(), 5);
|
||||
assert_eq!(output[0], MessageToken::CodeblockMarker(3));
|
||||
assert_eq!(output[1], MessageToken::UserMention("01FD58YK5W7QRV5H3D64KTQYX3".to_string()));
|
||||
assert_eq!(output[2], MessageToken::RoleMention("01FD58YK5W7QRV5H3D64KTQYX3".to_string()));
|
||||
assert_eq!(output[1], MessageToken::UserMention("01FD58YK5W7QRV5H3D64KTQYX3"));
|
||||
assert_eq!(output[2], MessageToken::RoleMention("01FD58YK5W7QRV5H3D64KTQYX3"));
|
||||
assert_eq!(output[3], MessageToken::MentionEveryone);
|
||||
assert_eq!(output[4], MessageToken::MentionOnline);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_inline_codeblock_no_mentions() {
|
||||
|
||||
let output = parse_message_iter("`<@01FD58YK5W7QRV5H3D64KTQYX3><%01FD58YK5W7QRV5H3D64KTQYX3>@everyone@online`").collect::<Vec<_>>();
|
||||
|
||||
assert_eq!(output.len(), 2);
|
||||
@@ -192,12 +194,21 @@ mod tests {
|
||||
|
||||
assert_eq!(output.len(), 5);
|
||||
assert_eq!(output[0], MessageToken::CodeblockMarker(1));
|
||||
assert_eq!(output[1], MessageToken::UserMention("01FD58YK5W7QRV5H3D64KTQYX3".to_string()));
|
||||
assert_eq!(output[2], MessageToken::RoleMention("01FD58YK5W7QRV5H3D64KTQYX3".to_string()));
|
||||
assert_eq!(output[1], MessageToken::UserMention("01FD58YK5W7QRV5H3D64KTQYX3"));
|
||||
assert_eq!(output[2], MessageToken::RoleMention("01FD58YK5W7QRV5H3D64KTQYX3"));
|
||||
assert_eq!(output[3], MessageToken::MentionEveryone);
|
||||
assert_eq!(output[4], MessageToken::MentionOnline);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_codeblock_with_language_no_mentions() {
|
||||
let output = parse_message_iter("```rust\n<@01FD58YK5W7QRV5H3D64KTQYX3><%01FD58YK5W7QRV5H3D64KTQYX3>@everyone@online```").collect::<Vec<_>>();
|
||||
|
||||
assert_eq!(output.len(), 2);
|
||||
assert_eq!(output[0], MessageToken::CodeblockMarker(3));
|
||||
assert_eq!(output[1], MessageToken::CodeblockMarker(3));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_double_inline_codeblock() {
|
||||
let output = parse_message_iter("``this should not ping @everyone``").collect::<Vec<_>>();
|
||||
|
||||
@@ -8,10 +8,18 @@ pub use server::*;
|
||||
pub use user::*;
|
||||
|
||||
/// Holds a permission value to manipulate.
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct PermissionValue(u64);
|
||||
|
||||
impl PermissionValue {
|
||||
pub fn from_raw(value: u64) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
|
||||
pub fn into_raw(self) -> u64 {
|
||||
self.0
|
||||
}
|
||||
|
||||
/// Apply a given override to this value
|
||||
pub fn apply(&mut self, v: Override) {
|
||||
self.allow(v.allow);
|
||||
|
||||
Reference in New Issue
Block a user