mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
added levenshtein for most similar string and words starting with what user entered + check permissions + added clientside commands to /help
This commit is contained in:
parent
75ccd65ec6
commit
361e5204e3
7
Cargo.lock
generated
7
Cargo.lock
generated
@ -3216,6 +3216,12 @@ version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67"
|
||||
|
||||
[[package]]
|
||||
name = "levenshtein"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "db13adb97ab515a3691f56e4dbab09283d0b86cb45abd991d8634a9d6f501760"
|
||||
|
||||
[[package]]
|
||||
name = "lewton"
|
||||
version = "0.10.2"
|
||||
@ -6964,6 +6970,7 @@ dependencies = [
|
||||
"humantime",
|
||||
"itertools",
|
||||
"lazy_static",
|
||||
"levenshtein",
|
||||
"noise",
|
||||
"num_cpus",
|
||||
"parking_lot 0.12.0",
|
||||
|
@ -236,6 +236,92 @@ lazy_static! {
|
||||
};
|
||||
}
|
||||
|
||||
// Please keep this sorted alphabetically, same as with server commands :-)
|
||||
#[derive(Clone, Copy, strum::EnumIter)]
|
||||
pub enum ClientChatCommand {
|
||||
Mute,
|
||||
Unmute,
|
||||
}
|
||||
|
||||
impl ClientChatCommand {
|
||||
pub fn data(&self) -> ChatCommandData {
|
||||
use ArgumentSpec::*;
|
||||
use Requirement::*;
|
||||
let cmd = ChatCommandData::new;
|
||||
match self {
|
||||
ClientChatCommand::Mute => cmd(
|
||||
vec![PlayerName(Required)],
|
||||
"Mutes chat messages from a player.",
|
||||
None,
|
||||
),
|
||||
ClientChatCommand::Unmute => cmd(
|
||||
vec![PlayerName(Required)],
|
||||
"Unmutes a player muted with the 'mute' command.",
|
||||
None,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn keyword(&self) -> &'static str {
|
||||
match self {
|
||||
ClientChatCommand::Mute => "mute",
|
||||
ClientChatCommand::Unmute => "unmute",
|
||||
}
|
||||
}
|
||||
|
||||
/// A message that explains what the command does
|
||||
pub fn help_string(&self) -> String {
|
||||
let data = self.data();
|
||||
let usage = std::iter::once(format!("/{}", self.keyword()))
|
||||
.chain(data.args.iter().map(|arg| arg.usage_string()))
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ");
|
||||
format!("{}: {}", usage, data.description)
|
||||
}
|
||||
|
||||
/// Returns a format string for parsing arguments with scan_fmt
|
||||
pub fn arg_fmt(&self) -> String {
|
||||
self.data()
|
||||
.args
|
||||
.iter()
|
||||
.map(|arg| match arg {
|
||||
ArgumentSpec::PlayerName(_) => "{}",
|
||||
ArgumentSpec::SiteName(_) => "{/.*/}",
|
||||
ArgumentSpec::Float(_, _, _) => "{}",
|
||||
ArgumentSpec::Integer(_, _, _) => "{d}",
|
||||
ArgumentSpec::Any(_, _) => "{}",
|
||||
ArgumentSpec::Command(_) => "{}",
|
||||
ArgumentSpec::Message(_) => "{/.*/}",
|
||||
ArgumentSpec::SubCommand => "{} {/.*/}",
|
||||
ArgumentSpec::Enum(_, _, _) => "{}",
|
||||
ArgumentSpec::Boolean(_, _, _) => "{}",
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ")
|
||||
}
|
||||
|
||||
/// Produce an iterator over all the available commands
|
||||
pub fn iter() -> impl Iterator<Item = Self> { <Self as strum::IntoEnumIterator>::iter() }
|
||||
|
||||
/// Produce an iterator that first goes over all the short keywords
|
||||
/// and their associated commands and then iterates over all the normal
|
||||
/// keywords with their associated commands
|
||||
pub fn iter_with_keywords() -> impl Iterator<Item = (&'static str, Self)> {
|
||||
Self::iter().map(|c| (c.keyword(), c))
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for ClientChatCommand {
|
||||
type Err = ();
|
||||
|
||||
fn from_str(keyword: &str) -> Result<ClientChatCommand, ()> {
|
||||
Self::iter()
|
||||
.map(|c| (c.keyword(), c))
|
||||
.find_map(|(kwd, command)| (kwd == keyword).then_some(command))
|
||||
.ok_or(())
|
||||
}
|
||||
}
|
||||
|
||||
// Please keep this sorted alphabetically :-)
|
||||
#[derive(Copy, Clone, strum::EnumIter)]
|
||||
pub enum ServerChatCommand {
|
||||
@ -271,6 +357,7 @@ pub enum ServerChatCommand {
|
||||
Health,
|
||||
Help,
|
||||
Home,
|
||||
InvalidCommand,
|
||||
JoinFaction,
|
||||
Jump,
|
||||
Kick,
|
||||
@ -491,6 +578,7 @@ impl ServerChatCommand {
|
||||
"Join/leave the specified faction",
|
||||
None,
|
||||
),
|
||||
ServerChatCommand::InvalidCommand => cmd(vec![], "", Some(Moderator)),
|
||||
ServerChatCommand::Jump => cmd(
|
||||
vec![
|
||||
Float("x", 0.0, Required),
|
||||
@ -762,6 +850,7 @@ impl ServerChatCommand {
|
||||
ServerChatCommand::JoinFaction => "join_faction",
|
||||
ServerChatCommand::Help => "help",
|
||||
ServerChatCommand::Home => "home",
|
||||
ServerChatCommand::InvalidCommand => "invalid_command",
|
||||
ServerChatCommand::Jump => "jump",
|
||||
ServerChatCommand::Kick => "kick",
|
||||
ServerChatCommand::Kill => "kill",
|
||||
|
@ -71,3 +71,5 @@ refinery = { git = "https://gitlab.com/veloren/refinery.git", rev = "8ecf4b4772d
|
||||
|
||||
# Plugins
|
||||
plugin-api = { package = "veloren-plugin-api", path = "../plugin/api"}
|
||||
|
||||
levenshtein = "1.0.5"
|
||||
|
@ -2,6 +2,8 @@
|
||||
//! To implement a new command provide a handler function
|
||||
//! in [do_command].
|
||||
|
||||
extern crate levenshtein;
|
||||
|
||||
use crate::{
|
||||
client::Client,
|
||||
location::Locations,
|
||||
@ -23,7 +25,7 @@ use common::{
|
||||
assets,
|
||||
calendar::Calendar,
|
||||
cmd::{
|
||||
KitSpec, ServerChatCommand, BUFF_PACK, BUFF_PARSER, ITEM_SPECS, KIT_MANIFEST_PATH,
|
||||
ClientChatCommand, KitSpec, ServerChatCommand, BUFF_PACK, BUFF_PARSER, ITEM_SPECS, KIT_MANIFEST_PATH,
|
||||
PRESET_MANIFEST_PATH,
|
||||
},
|
||||
comp::{
|
||||
@ -57,6 +59,8 @@ use common_state::{BuildAreaError, BuildAreas};
|
||||
use core::{cmp::Ordering, convert::TryFrom, time::Duration};
|
||||
use hashbrown::{HashMap, HashSet};
|
||||
use humantime::Duration as HumanDuration;
|
||||
use itertools::Itertools;
|
||||
use levenshtein::levenshtein;
|
||||
use rand::{thread_rng, Rng};
|
||||
use specs::{
|
||||
saveload::MarkerAllocator, storage::StorageEntry, Builder, Entity as EcsEntity, Join, WorldExt,
|
||||
@ -152,6 +156,7 @@ fn do_command(
|
||||
ServerChatCommand::Health => handle_health,
|
||||
ServerChatCommand::Help => handle_help,
|
||||
ServerChatCommand::Home => handle_home,
|
||||
ServerChatCommand::InvalidCommand => handle_invalid_command,
|
||||
ServerChatCommand::JoinFaction => handle_join_faction,
|
||||
ServerChatCommand::Jump => handle_jump,
|
||||
ServerChatCommand::Kick => handle_kick,
|
||||
@ -805,6 +810,37 @@ fn handle_set_motd(
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_invalid_command(
|
||||
server: &mut Server,
|
||||
client: EcsEntity,
|
||||
_target: EcsEntity,
|
||||
args: Vec<String>,
|
||||
_action: &ServerChatCommand,
|
||||
) -> CmdResult<()> {
|
||||
if let Some(user_entered_invalid_command) = parse_cmd_args!(args, String) {
|
||||
let entity_role = server.entity_admin_role(client);
|
||||
|
||||
let most_similar_str = ServerChatCommand::iter()
|
||||
.filter(|cmd| cmd.needs_role() <= entity_role)
|
||||
.map(|cmd| cmd.keyword())
|
||||
.chain(ClientChatCommand::iter().map(|cmd| cmd.keyword()))
|
||||
.sorted_by_key(|cmd| levenshtein(&*user_entered_invalid_command, cmd))
|
||||
.collect::<Vec<&str>>()[0];
|
||||
|
||||
let commands_with_same_prefix = ServerChatCommand::iter()
|
||||
.filter(|cmd|cmd.needs_role() <= entity_role)
|
||||
.map(|cmd| cmd.keyword())
|
||||
.chain(ClientChatCommand::iter().map(|cmd| cmd.keyword()))
|
||||
.filter(|cmd| cmd.starts_with(&*user_entered_invalid_command) && cmd != &most_similar_str);
|
||||
|
||||
return Err(format!("Could not find a command named {}. Did you mean any of the following? \n/{} {} \n\nType /help to see a list of all commands.",
|
||||
user_entered_invalid_command,
|
||||
most_similar_str,
|
||||
commands_with_same_prefix.fold(String::new(), |s, arg| s + "\n/" + &arg)))
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_jump(
|
||||
server: &mut Server,
|
||||
_client: EcsEntity,
|
||||
@ -1797,9 +1833,15 @@ fn handle_help(
|
||||
let mut message = String::new();
|
||||
let entity_role = server.entity_admin_role(client);
|
||||
|
||||
// Iterate through all commands you have permission to use.
|
||||
// Iterate through the ClientChatCommands Mute and Unmute.
|
||||
ClientChatCommand::iter()
|
||||
.for_each(|cmd| {
|
||||
message += &cmd.help_string();
|
||||
message += "\n";
|
||||
});
|
||||
// Iterate through all ServerChatCommands you have permission to use.
|
||||
ServerChatCommand::iter()
|
||||
.filter(|cmd| cmd.needs_role() <= entity_role)
|
||||
.filter(|cmd| cmd.needs_role() <= entity_role && cmd.keyword() != "invalid_command")
|
||||
.for_each(|cmd| {
|
||||
message += &cmd.help_string();
|
||||
message += "\n";
|
||||
|
@ -110,6 +110,7 @@ impl FromStr for ClientChatCommand {
|
||||
pub enum ChatCommandKind {
|
||||
Client(ClientChatCommand),
|
||||
Server(ServerChatCommand),
|
||||
InvalidCommand
|
||||
}
|
||||
|
||||
impl FromStr for ChatCommandKind {
|
||||
@ -121,25 +122,7 @@ impl FromStr for ChatCommandKind {
|
||||
} else if let Ok(cmd) = s.parse::<ServerChatCommand>() {
|
||||
Ok(ChatCommandKind::Server(cmd))
|
||||
} else {
|
||||
// Idea:
|
||||
// The ServerChatCommand enum in veloren/common/src/cmd.rs is a list of valid
|
||||
// commands. We should output something like:
|
||||
|
||||
// Could not find a command named "group_kcik"
|
||||
// Did you mean:
|
||||
// group_kick
|
||||
|
||||
// Could not find a command named "make"
|
||||
// Did you mean:
|
||||
// make_block
|
||||
// make_npc
|
||||
// make_sprite
|
||||
|
||||
// We should be suggesting command(s) similar to the ones typed
|
||||
// - a regex to match possible typos?
|
||||
// - Naively suggest the other commands that start with the same first letter?
|
||||
// (Might be ok, there most commands for a letter is "S" with 10 commands)
|
||||
Err(format!("Could not find a command named {}.", s))
|
||||
Ok(ChatCommandKind::InvalidCommand)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -170,6 +153,11 @@ pub fn run_command(
|
||||
ChatCommandKind::Client(cmd) => {
|
||||
Ok(Some(run_client_command(client, global_state, cmd, args)?))
|
||||
},
|
||||
ChatCommandKind::InvalidCommand => {
|
||||
//The invalid command that user typed is passed as argument
|
||||
client.send_command(ServerChatCommand::InvalidCommand.keyword().into(), vec![cmd.to_string()]);
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user