diff options
Diffstat (limited to 'azalea-client/src/plugins/client_chat/handler.rs')
| -rw-r--r-- | azalea-client/src/plugins/client_chat/handler.rs | 119 |
1 files changed, 119 insertions, 0 deletions
diff --git a/azalea-client/src/plugins/client_chat/handler.rs b/azalea-client/src/plugins/client_chat/handler.rs new file mode 100644 index 00000000..703a9846 --- /dev/null +++ b/azalea-client/src/plugins/client_chat/handler.rs @@ -0,0 +1,119 @@ +use std::time::{SystemTime, UNIX_EPOCH}; + +use azalea_protocol::packets::{ + Packet, + game::{ServerboundChat, ServerboundChatCommand, s_chat::LastSeenMessagesUpdate}, +}; +use bevy_ecs::prelude::*; + +use super::ChatKind; +use crate::packet::game::SendGamePacketEvent; +#[cfg(feature = "online-mode")] +use crate::{account::Account, chat_signing::ChatSigningSession}; + +/// Send a chat packet to the server of a specific kind (chat message or +/// command). Usually you just want [`SendChatEvent`] instead. +/// +/// Usually setting the kind to `Message` will make it send a chat message even +/// if it starts with a slash, but some server implementations will always do a +/// command if it starts with a slash. +/// +/// If you're wondering why this isn't two separate events, it's so ordering is +/// preserved if multiple chat messages and commands are sent at the same time. +/// +/// [`SendChatEvent`]: super::SendChatEvent +#[derive(Message)] +pub struct SendChatKindEvent { + pub entity: Entity, + pub content: String, + pub kind: ChatKind, +} + +pub fn handle_send_chat_kind_event( + mut events: MessageReader<SendChatKindEvent>, + mut commands: Commands, + #[cfg(feature = "online-mode")] mut query: Query<(&Account, &mut ChatSigningSession)>, +) { + for event in events.read() { + let content = event + .content + .chars() + .filter(|c| !matches!(c, '\x00'..='\x1F' | '\x7F' | 'ยง')) + .take(256) + .collect::<String>(); + + let timestamp = SystemTime::now(); + + let packet = match event.kind { + ChatKind::Message => { + let salt = azalea_crypto::signing::make_salt(); + + #[cfg(feature = "online-mode")] + let signature = if let Ok((account, mut chat_session)) = query.get_mut(event.entity) + { + Some(create_signature( + account, + &mut chat_session, + salt, + timestamp, + &content, + )) + } else { + None + }; + #[cfg(not(feature = "online-mode"))] + let signature = None; + + ServerboundChat { + message: content, + timestamp: timestamp + .duration_since(UNIX_EPOCH) + .expect("Time shouldn't be before epoch") + .as_millis() + .try_into() + .expect("Instant should fit into a u64"), + salt, + signature, + // TODO: implement last_seen_messages + last_seen_messages: LastSeenMessagesUpdate::default(), + } + } + .into_variant(), + ChatKind::Command => { + // TODO: commands that require chat signing + ServerboundChatCommand { command: content }.into_variant() + } + }; + + commands.trigger(SendGamePacketEvent::new(event.entity, packet)); + } +} + +#[cfg(feature = "online-mode")] +pub fn create_signature( + account: &Account, + chat_session: &mut ChatSigningSession, + salt: u64, + timestamp: SystemTime, + message: &str, +) -> azalea_crypto::signing::MessageSignature { + use azalea_crypto::signing::SignChatMessageOptions; + + let certs = account + .certs() + .expect("certs shouldn't be set back to None"); + + let signature = azalea_crypto::signing::sign_chat_message(&SignChatMessageOptions { + account_uuid: account.uuid(), + chat_session_uuid: chat_session.session_id, + message_index: chat_session.messages_sent, + salt, + timestamp, + message: message.to_owned(), + private_key: certs.private_key.clone(), + }); + + chat_session.messages_sent += 1; + + signature +} |
