Skip to content

Commit 1bbb45f

Browse files
feat: Add command permissions derive (#604)
Co-authored-by: Andrew Gazelka <andrew.gazelka@gmail.com>
1 parent 6ecfd8d commit 1bbb45f

File tree

15 files changed

+159
-84
lines changed

15 files changed

+159
-84
lines changed

Cargo.lock

Lines changed: 26 additions & 68 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
[profile]
21
[profile.release-debug]
32
debug = true
43
inherits = 'release'
@@ -136,6 +135,9 @@ path = 'crates/hyperion'
136135
[workspace.dependencies.hyperion-clap]
137136
path = 'crates/hyperion-clap'
138137

138+
[workspace.dependencies.hyperion-clap-macros]
139+
path = 'crates/hyperion-clap-macros'
140+
139141
[workspace.dependencies.hyperion-command]
140142
path = 'crates/hyperion-command'
141143

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/target
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
cargo-features = ["edition2024"]
2+
3+
[dependencies]
4+
quote.workspace = true
5+
syn.workspace = true
6+
7+
[lib]
8+
proc-macro = true
9+
10+
[lints]
11+
workspace = true
12+
13+
[package]
14+
authors = ["Andrew Gazelka <andrew.gazelka@gmail.com>"]
15+
edition = "2024"
16+
name = "hyperion-clap-macros"
17+
publish = false
18+
readme = "README.md"
19+
version = "0.1.0"

crates/hyperion-clap-macros/README.md

Whitespace-only changes.
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
use proc_macro::TokenStream;
2+
use quote::quote;
3+
use syn::{DeriveInput, Error, Ident, Lit, parse_macro_input};
4+
5+
#[proc_macro_derive(CommandPermission, attributes(command_permission))]
6+
pub fn derive_command_permission(input: TokenStream) -> TokenStream {
7+
// Parse the input as a DeriveInput (struct or enum)
8+
let input = parse_macro_input!(input as DeriveInput);
9+
let name = input.ident.clone(); // Clone the Ident to prevent moving
10+
11+
// Extract the group from the `#[command_permission(group = "Admin")]` attribute
12+
let mut group = None;
13+
for attr in &input.attrs {
14+
if attr.path().is_ident("command_permission") {
15+
if let Err(err) = attr.parse_nested_meta(|meta| {
16+
if meta.path.is_ident("group") {
17+
if let Ok(Lit::Str(lit)) = meta.value()?.parse::<Lit>() {
18+
group = Some(lit);
19+
}
20+
}
21+
Ok(())
22+
}) {
23+
return Error::new_spanned(attr, format!("Failed to parse attribute: {err}"))
24+
.to_compile_error()
25+
.into();
26+
}
27+
}
28+
}
29+
30+
let group_ident = match group {
31+
Some(g) => Ident::new(&g.value(), g.span()),
32+
None => {
33+
return Error::new_spanned(
34+
input,
35+
"Missing required `#[command_permission(group = \"<GroupName>\")]` attribute.",
36+
)
37+
.to_compile_error()
38+
.into();
39+
}
40+
};
41+
42+
// Generate the trait implementation
43+
let expanded = quote! {
44+
impl CommandPermission for #name {
45+
fn has_required_permission(&self, user_group: ::hyperion_permission::Group) -> bool {
46+
const REQUIRED_GROUP: ::hyperion_permission::Group = ::hyperion_permission::Group::#group_ident;
47+
48+
if REQUIRED_GROUP == ::hyperion_permission::Group::Banned {
49+
// When checking for the group "Banned" we don't want to check for higher groups.
50+
return REQUIRED_GROUP == user_group;
51+
} else {
52+
return user_group as u32 >= REQUIRED_GROUP as u32
53+
}
54+
}
55+
}
56+
};
57+
58+
TokenStream::from(expanded)
59+
}

crates/hyperion-clap/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ publish = false
1212
clap ={ workspace = true }
1313
flecs_ecs = { workspace = true }
1414
hyperion = { workspace = true }
15+
hyperion-clap-macros = { workspace = true }
1516
hyperion-command = { workspace = true }
17+
hyperion-permission = { workspace = true }
1618
tracing = { workspace = true }
1719
valence_protocol = { workspace = true }
1820

crates/hyperion-clap/src/lib.rs

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,15 @@ use flecs_ecs::{
66
prelude::{Component, Module},
77
};
88
use hyperion::{
9-
net::{Compose, agnostic},
9+
net::{Compose, DataBundle, NetworkStreamRef, agnostic},
1010
simulation::{command::get_root_command_entity, handlers::PacketSwitchQuery},
1111
storage::{CommandCompletionRequest, EventFn},
1212
system_registry::SystemId,
1313
};
14+
pub use hyperion_clap_macros::CommandPermission;
1415
pub use hyperion_command;
1516
use hyperion_command::{CommandHandler, CommandRegistry};
17+
use hyperion_permission::Group;
1618
use valence_protocol::{
1719
VarInt,
1820
packets::{
@@ -21,7 +23,7 @@ use valence_protocol::{
2123
},
2224
};
2325

24-
pub trait MinecraftCommand: Parser {
26+
pub trait MinecraftCommand: Parser + CommandPermission {
2527
fn execute(self, world: &World, caller: Entity);
2628

2729
fn register(registry: &mut CommandRegistry, world: &World) {
@@ -51,7 +53,29 @@ pub trait MinecraftCommand: Parser {
5153
let input = input.split_whitespace();
5254

5355
match Self::try_parse_from(input) {
54-
Ok(elem) => elem.execute(world, caller),
56+
Ok(elem) => {
57+
if world.get::<&Compose>(|compose| {
58+
caller
59+
.entity_view(world)
60+
.get::<(&NetworkStreamRef, &Group)>(|(stream, group)| {
61+
if elem.has_required_permission(*group) {
62+
true
63+
} else {
64+
let chat = agnostic::chat(
65+
"§cYou do not have permission to use this command!",
66+
);
67+
68+
let mut bundle = DataBundle::new(compose);
69+
bundle.add_packet(&chat, world).unwrap();
70+
bundle.send(world, *stream, SystemId(8)).unwrap();
71+
72+
false
73+
}
74+
})
75+
}) {
76+
elem.execute(world, caller);
77+
}
78+
}
5579
Err(e) => {
5680
// add red if not display help
5781
let prefix = match e.kind() {
@@ -225,6 +249,10 @@ impl MinecraftArg for ClapArg {
225249
}
226250
}
227251

252+
pub trait CommandPermission {
253+
fn has_required_permission(&self, user_group: hyperion_permission::Group) -> bool;
254+
}
255+
228256
#[derive(Clone, Debug, ValueEnum, PartialEq, Eq)]
229257
pub enum GameMode {
230258
Survival,

0 commit comments

Comments
 (0)