Skip to main content

pumpkinplus/modules/mechanics/server/
chat.rs

1//! Chat module - chat formatting and word filtering.
2//!
3//! ## Configuration
4//!
5//! | Field         | Default | Description                                                        |
6//! |---------------|---------|--------------------------------------------------------------------|
7//! | `enabled`     | `false` | Whether this module is active                                      |
8//! | `chat_format` | `""`    | Custom chat format. Use `{player}` and `{message}` placeholders    |
9//! | `chat_filter` | `[]`    | List of blocked words/phrases (case-insensitive)                   |
10//!
11//! ## Placeholders
12//!
13//! | Placeholder | Available in                                    |
14//! |-------------|-------------------------------------------------|
15//! | `{player}`  | `chat_format`                                   |
16//! | `{message}` | `chat_format`                                   |
17
18use crate::config::ConfigManager;
19use crate::mechanics::mechanic::Mechanic;
20use pumpkin_plugin_api::events::{EventData, EventHandler, EventPriority, PlayerChatEvent};
21use pumpkin_plugin_api::{Context, Server};
22use serde::{Deserialize, Serialize};
23
24/// Handles chat formatting and word filtering.
25#[derive(Default)]
26pub struct Chat;
27
28impl Mechanic for Chat {
29    fn enabled(&self) -> bool {
30        ConfigManager::get()
31            .map(|cm| cm.get_config::<ChatConfig>().enabled)
32            .unwrap_or(true)
33    }
34
35    fn events(&self, context: &Context) {
36        context
37            .register_event_handler::<PlayerChatEvent, _>(Chat, EventPriority::Highest, true)
38            .expect("failed to register chat event handler");
39    }
40}
41
42impl EventHandler<PlayerChatEvent> for Chat {
43    fn handle(
44        &self,
45        _server: Server,
46        mut event: EventData<PlayerChatEvent>,
47    ) -> EventData<PlayerChatEvent> {
48        let config: ChatConfig = ConfigManager::get()
49            .map(|cm| cm.get_config())
50            .unwrap_or_default();
51
52        if !config.chat_filter.is_empty() {
53            let lower = event.message.to_lowercase();
54            if config
55                .chat_filter
56                .iter()
57                .any(|word| lower.contains(word.as_str()))
58            {
59                event.cancelled = true;
60                return event;
61            }
62        }
63
64        if !config.chat_format.is_empty() {
65            let name = event.player.get_display_name().get_text();
66            let original = event.message.clone();
67            event.message = config
68                .chat_format
69                .replace("{player}", &name)
70                .replace("{message}", &original);
71        }
72
73        event
74    }
75}
76
77/// Configuration for the chat module.
78#[derive(Debug, Clone, Default, Serialize, Deserialize)]
79pub struct ChatConfig {
80    /// Whether this module is active.
81    pub enabled: bool,
82    /// Custom chat format. Use `{player}` and `{message}` as placeholders. Leave empty to disable.
83    pub chat_format: String,
84    /// List of blocked words/phrases. Messages containing any entry (case-insensitive) are cancelled. Leave empty to disable.
85    pub chat_filter: Vec<String>,
86}