From 9fb55f74dc6180cab0b4d25a819ac34929815f85 Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Sat, 24 Jan 2026 23:46:10 +0000 Subject: [PATCH 1/3] chore: add git-town to mise tools (#497) --- .mise/config.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.mise/config.toml b/.mise/config.toml index 1d6ad18d..85fcd898 100644 --- a/.mise/config.toml +++ b/.mise/config.toml @@ -7,6 +7,8 @@ gh = "2.25.0" rust = "1.92.0" "cargo:cargo-nextest" = "0.9.122" +"github:git-town/git-town" = "22.4.0" + [settings] experimental = true idiomatic_version_file_enable_tools = ["rust"] From 0dc544249825a49c793309edee5ec1838458a6da Mon Sep 17 00:00:00 2001 From: Angelo Kontaxis Date: Sun, 25 Jan 2026 19:42:48 +0000 Subject: [PATCH 2/3] feat: allow kicking members from voice channels (#495) --- .../src/models/server_members/model.rs | 4 ++- .../src/models/server_members/ops/mongodb.rs | 19 ++++++------ crates/core/database/src/util/bridge/v0.rs | 2 ++ crates/core/models/src/v0/server_members.rs | 1 + .../delta/src/routes/servers/member_edit.rs | 30 +++++++++++++++---- 5 files changed, 40 insertions(+), 16 deletions(-) diff --git a/crates/core/database/src/models/server_members/model.rs b/crates/core/database/src/models/server_members/model.rs index 74ec04a8..0062a270 100644 --- a/crates/core/database/src/models/server_members/model.rs +++ b/crates/core/database/src/models/server_members/model.rs @@ -72,6 +72,7 @@ auto_derived!( CanReceive, CanPublish, JoinedAt, + VoiceChannel, } /// Member removal intention @@ -224,13 +225,14 @@ impl Member { pub fn remove_field(&mut self, field: &FieldsMember) { match field { - FieldsMember::JoinedAt => (), + FieldsMember::JoinedAt => {}, FieldsMember::Avatar => self.avatar = None, FieldsMember::Nickname => self.nickname = None, FieldsMember::Roles => self.roles.clear(), FieldsMember::Timeout => self.timeout = None, FieldsMember::CanReceive => self.can_receive = true, FieldsMember::CanPublish => self.can_publish = true, + FieldsMember::VoiceChannel => {}, } } diff --git a/crates/core/database/src/models/server_members/ops/mongodb.rs b/crates/core/database/src/models/server_members/ops/mongodb.rs index 198ad6a8..377e5877 100644 --- a/crates/core/database/src/models/server_members/ops/mongodb.rs +++ b/crates/core/database/src/models/server_members/ops/mongodb.rs @@ -330,14 +330,15 @@ impl AbstractServerMembers for MongoDb { 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", - FieldsMember::Timeout => "timeout", - FieldsMember::CanPublish => "can_publish", - FieldsMember::CanReceive => "can_receive", - }) + match self { + FieldsMember::JoinedAt => Some("joined_at"), + FieldsMember::Avatar => Some("avatar"), + FieldsMember::Nickname => Some("nickname"), + FieldsMember::Roles => Some("roles"), + FieldsMember::Timeout => Some("timeout"), + FieldsMember::CanPublish => Some("can_publish"), + FieldsMember::CanReceive => Some("can_receive"), + FieldsMember::VoiceChannel => None, + } } } diff --git a/crates/core/database/src/util/bridge/v0.rs b/crates/core/database/src/util/bridge/v0.rs index de63db90..aafb2313 100644 --- a/crates/core/database/src/util/bridge/v0.rs +++ b/crates/core/database/src/util/bridge/v0.rs @@ -691,6 +691,7 @@ impl From for FieldsMember { crate::FieldsMember::CanReceive => FieldsMember::CanReceive, crate::FieldsMember::CanPublish => FieldsMember::CanPublish, crate::FieldsMember::JoinedAt => FieldsMember::JoinedAt, + crate::FieldsMember::VoiceChannel => FieldsMember::VoiceChannel, } } } @@ -705,6 +706,7 @@ impl From for crate::FieldsMember { FieldsMember::CanReceive => crate::FieldsMember::CanReceive, FieldsMember::CanPublish => crate::FieldsMember::CanPublish, FieldsMember::JoinedAt => crate::FieldsMember::JoinedAt, + FieldsMember::VoiceChannel => crate::FieldsMember::VoiceChannel, } } } diff --git a/crates/core/models/src/v0/server_members.rs b/crates/core/models/src/v0/server_members.rs index d1ccb058..41ec99ae 100644 --- a/crates/core/models/src/v0/server_members.rs +++ b/crates/core/models/src/v0/server_members.rs @@ -95,6 +95,7 @@ auto_derived!( CanReceive, CanPublish, JoinedAt, + VoiceChannel, } /// Member removal intention diff --git a/crates/delta/src/routes/servers/member_edit.rs b/crates/delta/src/routes/servers/member_edit.rs index 458c10ba..ecc1d919 100644 --- a/crates/delta/src/routes/servers/member_edit.rs +++ b/crates/delta/src/routes/servers/member_edit.rs @@ -98,13 +98,21 @@ pub async fn edit( permissions.throw_if_lacking_channel_permission(ChannelPermission::DeafenMembers)?; } - let new_voice_channel = if let Some(new_channel) = &data.voice_channel { + if data.voice_channel.is_some() && data.remove.contains(&FieldsMember::VoiceChannel) { + return Err(create_error!(InvalidOperation)) + } + + if data.voice_channel.is_some() || data.remove.contains(&FieldsMember::VoiceChannel) { if !voice_client.is_enabled() { return Err(create_error!(LiveKitUnavailable)); }; - permissions.throw_if_lacking_channel_permission(ChannelPermission::MoveMembers)?; + if member.id.user != user.id { + permissions.throw_if_lacking_channel_permission(ChannelPermission::MoveMembers)?; + } + } + let new_voice_channel = if let Some(new_channel) = &data.voice_channel { // ensure the channel we are moving them to is in the server and is a voice channel let channel = Reference::from_unchecked(new_channel) @@ -116,6 +124,9 @@ pub async fn edit( Err(create_error!(UnknownChannel))? } + let channel_permissions = calculate_channel_permissions(&mut query.clone().channel(&channel)).await; + channel_permissions.throw_if_lacking_channel_permission(ChannelPermission::Connect)?; + if get_user_voice_channel_in_server(&target_user.id, &server.id) .await? .is_none() @@ -189,10 +200,8 @@ pub async fn edit( partial.avatar = Some(File::use_user_avatar(db, &avatar, &user.id, &user.id).await?); } - let remove_contains_voice = remove.contains(FieldsMember::CanPublish) || remove.contains(FieldsMember::CanReceive); - member - .update(db, partial, remove.into_iter().map(Into::into).collect()) + .update(db, partial, remove.clone().into_iter().map(Into::into).collect()) .await?; if let Some(new_voice_channel) = new_voice_channel { @@ -234,7 +243,7 @@ pub async fn edit( .private(target_user.id.clone()) .await; }; - } else if can_publish.is_some() || can_receive.is_some() || remove_contains_voice { + } else if can_publish.is_some() || can_receive.is_some() || remove.contains(FieldsMember::CanPublish) || remove.contains(FieldsMember::CanReceive) { if let Some(channel) = get_user_voice_channel_in_server(&target_user.id, &server.id).await? { let node = get_channel_node(&channel).await?.unwrap(); @@ -253,5 +262,14 @@ pub async fn edit( }; }; + if remove.contains(&FieldsMember::VoiceChannel) { + if let Some(channel) = get_user_voice_channel_in_server(&target_user.id, &server.id).await? + { + let node = get_channel_node(&channel).await?.unwrap(); + + voice_client.remove_user(&node, &user.id, &channel).await?; + }; + } + Ok(Json(member.into())) } From a1a21252d0ad58937e41f16e5fb86f96bebd2a51 Mon Sep 17 00:00:00 2001 From: Angelo Kontaxis Date: Sun, 25 Jan 2026 19:43:06 +0000 Subject: [PATCH 3/3] fix: expose ratelimit headers via cors (#496) --- Cargo.lock | 1 + crates/delta/src/main.rs | 9 +++++++++ crates/services/autumn/src/api.rs | 6 ++++++ crates/services/gifbox/Cargo.toml | 1 + crates/services/gifbox/src/routes/mod.rs | 18 +++++++++++++++++- 5 files changed, 34 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 878658d7..f6816950 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6784,6 +6784,7 @@ dependencies = [ "serde", "serde_json", "tokio 1.47.1", + "tower-http 0.5.2", "tracing", "utoipa", "utoipa-scalar", diff --git a/crates/delta/src/main.rs b/crates/delta/src/main.rs index e7c060be..882609cc 100644 --- a/crates/delta/src/main.rs +++ b/crates/delta/src/main.rs @@ -70,6 +70,15 @@ pub async fn web() -> Rocket { .iter() .map(|s| FromStr::from_str(s).unwrap()) .collect(), + expose_headers: [ + "X-Ratelimit-Limit", + "X-Ratelimit-Bucket", + "X-Ratelimit-Remaining", + "X-Ratelimit-Reset-After", + ] + .iter() + .map(|s| s.to_string()) + .collect(), ..Default::default() } .to_cors() diff --git a/crates/services/autumn/src/api.rs b/crates/services/autumn/src/api.rs index 5c567146..d711e533 100644 --- a/crates/services/autumn/src/api.rs +++ b/crates/services/autumn/src/api.rs @@ -34,6 +34,12 @@ pub async fn router() -> Router { let cors = CorsLayer::new() .allow_methods([Method::POST]) .allow_headers(AllowHeaders::mirror_request()) + .expose_headers(vec![ + "X-RateLimit-Limit".try_into().unwrap(), + "X-RateLimit-Bucket".try_into().unwrap(), + "X-RateLimit-Remaining".try_into().unwrap(), + "X-RateLimit-Reset-After".try_into().unwrap(), + ]) .allow_origin(Any); Router::new() diff --git a/crates/services/gifbox/Cargo.toml b/crates/services/gifbox/Cargo.toml index fade557c..6588f8ef 100644 --- a/crates/services/gifbox/Cargo.toml +++ b/crates/services/gifbox/Cargo.toml @@ -36,6 +36,7 @@ revolt-ratelimits = { version = "0.9.4", path = "../../core/ratelimits", feature # Axum / web server axum = { version = "0.7.5" } axum-extra = { version = "0.9", features = ["typed-header"] } +tower-http = { version = "0.5.2", features = ["cors"] } # OpenAPI & documentation generation utoipa-scalar = { version = "0.1.0", features = ["axum"] } diff --git a/crates/services/gifbox/src/routes/mod.rs b/crates/services/gifbox/src/routes/mod.rs index efee317f..e47e19f0 100644 --- a/crates/services/gifbox/src/routes/mod.rs +++ b/crates/services/gifbox/src/routes/mod.rs @@ -1,5 +1,9 @@ use crate::AppState; -use axum::routing::{get, Router}; +use axum::{ + http::Method, + routing::{get, Router}, +}; +use tower_http::cors::{AllowHeaders, Any, CorsLayer}; pub mod categories; pub mod root; @@ -7,9 +11,21 @@ pub mod search; pub mod trending; pub fn router() -> Router { + let cors = CorsLayer::new() + .allow_methods([Method::GET]) + .allow_headers(AllowHeaders::mirror_request()) + .expose_headers(vec![ + "X-RateLimit-Limit".try_into().unwrap(), + "X-RateLimit-Bucket".try_into().unwrap(), + "X-RateLimit-Remaining".try_into().unwrap(), + "X-RateLimit-Reset-After".try_into().unwrap(), + ]) + .allow_origin(Any); + Router::new() .route("/", get(root::root)) .route("/categories", get(categories::categories)) .route("/search", get(search::search)) .route("/trending", get(trending::trending)) + .layer(cors) }