why2_chat/
command.rs

1/*
2This is part of WHY2
3Copyright (C) 2022-2026 Václav Šmejkal
4
5This program is free software: you can redistribute it and/or modify
6it under the terms of the GNU General Public License as published by
7the Free Software Foundation, either version 3 of the License, or
8(at your option) any later version.
9
10This program is distributed in the hope that it will be useful,
11but WITHOUT ANY WARRANTY; without even the implied warranty of
12MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13GNU General Public License for more details.
14
15You should have received a copy of the GNU General Public License
16along with this program.  If not, see <https://www.gnu.org/licenses/>.
17*/
18
19use std::fmt::
20{
21    Display,
22    Formatter,
23    Result,
24};
25
26use crate::network::MessageCode;
27
28#[cfg(feature = "client")]
29use std::net::TcpStream;
30
31#[cfg(feature = "client")]
32use crate::
33{
34    options,
35    network::{ self, MessagePacket },
36};
37
38//ENUMS
39#[derive(Clone, PartialEq)]
40pub enum Command
41{
42    Exit,           //DISCONNECT FROM SERVER
43    Voice,          //ENABLE VOICE CHAT
44    Channel,        //SWITCH CHANNEL
45    Help,           //PRINT COMMANDS
46    List,           //LIST USERS
47    PrivateMessage, //ONE TO ONE MESSAGE
48    UsernameColor,  //SET COLOR OF USERNAME
49    MessageColor,   //SET COLOR OF MESSAGE
50    Invalid,        //INVALID COMMAND
51}
52
53//STRUCTS
54pub struct CommandArg //COMMAND PARAMETER
55{
56    pub name: &'static str,
57    pub required: bool,
58}
59
60pub struct CommandInfo //COMMAND INFO
61{
62    pub command: Command,
63    pub triggers: &'static [&'static str],
64    pub args: &'static [CommandArg],
65    pub description: &'static str,
66}
67
68pub const COMMAND_LIST: &[CommandInfo] =
69&[
70    CommandInfo
71    {
72        command: Command::Help,
73        triggers: &[ "HELP", "H", "COMMANDS", "USAGE", "GUIDE" ],
74        args: &[],
75        description: "Prints all available commands",
76    },
77
78    CommandInfo
79    {
80        command: Command::Voice,
81        triggers: &[ "VOICE", "VOIP", "CALL" ],
82        args: &[],
83        description: "Toggles voice chat",
84    },
85
86    CommandInfo
87    {
88        command: Command::Channel,
89        triggers: &[ "CHANNEL", "SWITCH", "CHECKOUT", "AREA" ],
90        args: &[CommandArg { name: "NAME", required: false }],
91        description: "Switches to channel/lobby if NAME is omitted",
92    },
93
94    CommandInfo
95    {
96        command: Command::List,
97        triggers: &[ "LIST", "USERS", "CLIENTS", "CHANNELS", "IDS", "ID" ],
98        args: &[],
99        description: "Shows connected users and their IDs",
100    },
101
102    CommandInfo
103    {
104        command: Command::PrivateMessage,
105        triggers: &[ "PM", "DM", "MSG", "TELL" ],
106        args:
107        &[
108            CommandArg { name: "ID", required: true },
109            CommandArg { name: "MESSAGE", required: true },
110        ],
111        description: "Sends private message",
112    },
113
114    CommandInfo
115    {
116        command: Command::UsernameColor,
117        triggers: &[ "UCOLOR", "USERNAME" ],
118        args: &[CommandArg { name: "COLOR", required: true }],
119        description: "Sets color of username",
120    },
121
122    CommandInfo
123    {
124        command: Command::MessageColor,
125        triggers: &[ "COLOR", "MESSAGE" ],
126        args: &[CommandArg { name: "COLOR", required: true }],
127        description: "Sets color of message",
128    },
129
130    CommandInfo
131    {
132        command: Command::Exit,
133        triggers: &[ "EXIT", "LEAVE", "QUIT", "DISCONNECT" ],
134        args: &[],
135        description: "Disconnects from the server",
136    },
137];
138
139//CONSTS
140pub const COMMAND_PREFIX: &str = "/"; //PREFIX FOR COMMANDS
141
142//IMPLEMENTATIONS
143impl Command
144{
145    //GET CODE MATCHING TO COMMAND
146    pub fn to_code(&self) -> Option<MessageCode>
147    {
148        match self
149        {
150            Command::Exit => Some(MessageCode::Disconnect),
151            Command::Voice => Some(MessageCode::Voice),
152            Command::Channel => Some(MessageCode::Channel),
153            Command::List => Some(MessageCode::List),
154            Command::PrivateMessage => Some(MessageCode::PrivateMessage),
155
156            _ => None,
157        }
158    }
159}
160
161impl Display for Command
162{
163    //Command TO STRING
164    fn fmt(&self, f: &mut Formatter<'_>) -> Result
165    {
166        let name = COMMAND_LIST.iter()
167            .find(|info| info.command == *self)
168            .map(|info| info.triggers[0].to_lowercase())
169            .unwrap_or(String::new()); //HANDLE INVALID
170
171        write!(f, "{}{}", COMMAND_PREFIX, name)
172    }
173}
174
175pub fn get_command(input: &str) -> (Option<Command>, Option<String>) //GET COMMAND + PARAMETERS FROM STRING
176{
177    //input DOESN'T START WITH PREFIX, NO COMMAND
178    if !input.starts_with(COMMAND_PREFIX) { return (None, None); }
179
180    //SPLIT input TO COMMAND AND PARAMETERS
181    let no_prefix = &input[COMMAND_PREFIX.len()..]; //EXTRACT COMMAND WITHOUT PREFIX (IN UPPERCASE)
182    let (command, parameters) = match no_prefix.split_once(' ') //EXTRACT POSSIBLE PARAMETERS
183    {
184        Some((command, parameters)) => (command.to_ascii_uppercase(), Some(parameters.trim().to_string())),
185        None => (no_prefix.to_ascii_uppercase(), None)
186    };
187
188    //SEARCH FOR COMMAND
189    for info in COMMAND_LIST
190    {
191        if info.triggers.contains(&command.as_str())
192        {
193            return (Some(info.command.clone()), parameters);
194        }
195    }
196
197    (Some(Command::Invalid), None)
198}
199
200#[cfg(feature = "client")]
201pub fn send_command_code(stream: &mut TcpStream, command: &Command, parameters: &Option<String>) -> bool //SEND CODE FROM COMMAND IF POSSIBLE
202{
203    //CODE COMMAND
204    if let Some(code) = command.to_code()
205    {
206        network::send(stream, MessagePacket
207        {
208            text: parameters.clone(),
209            code: Some(code),
210            ..Default::default()
211        }, options::get_keys().as_ref());
212        true
213    } else { false }
214}