Skip to content

v0.4.0 - Scripting, Plugins, DCC & IRCv3

Choose a tag to compare

@doublegate doublegate released this 07 Mar 16:36
· 3 commits to main since this release

v0.4.0 — Scripting, Plugins, DCC & IRCv3

Major feature release implementing Phases 4–6 of the RustIRC development plan. This release transforms the project from a GUI-focused IRC client into a fully extensible platform with production-ready Lua scripting, a plugin system, DCC protocol support, IRCv3 extensions, flood protection, and proxy support.

Release Metrics

Metric v0.3.9 v0.4.0 Change
Tests 144 266 +84%
Unit Tests 144 233 +89
Integration Tests 0 33 +33
New Files 21
Modified Files 28
Lines Added 7,446
Lines Removed 521
Clippy Warnings 0 0
Compilation Errors 0 0

Phase 4: Scripting, Plugins & Configuration

Lua Scripting Engine (crates/rustirc-scripting/)

Complete rewrite of the scripting subsystem from stubs to a production-ready engine.

ScriptEngine (engine.rs — rewritten from 82 → ~300 lines)

  • load_script(name, code, priority) — creates Lua VM, applies sandbox, registers IRC API, executes code
  • trigger_event(event, msg) — dispatches IRC events to registered Lua handlers in priority order
  • execute_command(cmd, args) -> Result<bool> — routes / commands through script-registered handlers
  • auto_load_scripts() — scans configured scripts_path for .lua files and loads them
  • list_scripts() / unload_script(name) — script lifecycle management
  • Uses std::sync::RwLock (not tokio::sync::RwLock) to avoid "Cannot start runtime from within runtime" panics when called from async contexts
  • Scripts sorted by priority (highest first) for deterministic event handler execution

ScriptMessage UserData (script_message.rs — new file)

  • Lua-accessible IRC message type implementing mlua::UserData
  • Methods: get_nick(), get_channel(), get_text(), get_command(), get_params(), get_prefix()
  • from_protocol_message() converter for bridging rustirc_protocol::Message

Sandbox Security (sandbox.rs — rewritten from 20 → ~80 lines)

  • Sandbox::new(memory_limit, timeout_ms) and from_config(&ScriptingConfig)
  • apply(&self, lua: &Lua): sets memory limit, removes io/debug/loadfile/dofile/require modules, restricts os to safe subset (clock/date/difftime/time), sets instruction count hook returning mlua::VmState::Continue for CPU timeout

IRC API Table — registered as irc global in each Lua VM:

  • irc.print(msg) — output to client
  • irc.send_message(target, text) — send PRIVMSG via EventBus
  • irc.join(channel) / irc.part(channel) — channel management
  • irc.register_handler(event, callback) — event handler registration
  • irc.command(name, callback) — custom command registration
  • irc.get_var(key) / irc.set_var(key, value) — shared variable storage across scripts

Plugin System (crates/rustirc-plugins/)

PluginManager (manager.rs — rewritten from 63 → ~150 lines)

  • HashMap<String, LoadedPlugin> storage with LoadedPlugin { instance, info, enabled }
  • register_plugin(Box<dyn PluginApi>) — auto-initializes via PluginContext, stores info
  • unload_plugin(name) — calls shutdown(), removes from map
  • enable_plugin() / disable_plugin() — toggle with set_enabled() callback
  • shutdown_all() — iterates all plugins, calls shutdown(), clears map
  • Drop impl calls shutdown_all() for cleanup safety

Built-in Plugins (builtin/ — new directory)

  • LoggerPlugin (logger.rs): Creates log directories on init, manages file-based IRC message logging
  • HighlightPlugin (highlight.rs): Case-insensitive keyword matching via check_message(), dynamic word management with add_word() / remove_word(), deduplication on add

Plugin Loader (loader.rs — rewritten)

  • PluginLoader with configurable search paths
  • default_plugin_dir() using dirs::data_dir()/rustirc/plugins
  • discover_plugins() for directory scanning

Config File I/O (crates/rustirc-core/src/config.rs)

  • Config::from_file(path) -> Result<Self> — TOML deserialization with toml::from_str()
  • Config::save(path) -> Result<()> — pretty TOML serialization, automatic parent directory creation via std::fs::create_dir_all()
  • Config::default_path() -> PathBufdirs::config_dir()/rustirc/config.toml (XDG-compliant)
  • Config::load_or_default() -> Self — tries default path, falls back to Default::default() with warning
  • Config::generate_default_config(path) — creates commented config with example Libera Chat server
  • All config structs annotated with #[serde(default)] for forward compatibility when new fields are added

New Config Sections:

  • DccConfigenabled, download_dir, auto_accept, max_file_size, port_range_start/end
  • FloodConfigenabled, messages_per_second, burst_limit, queue_size
  • ProxyConfigproxy_type (None/Socks5/HttpConnect), address, port, username, password
  • NotificationConfigenabled, highlight_words, notify_on_mention, notify_on_private
  • QuietHoursenabled, start_hour, end_hour, weekends

Integration Wiring (src/main.rs)

  • Replaced no-op load_config() with real Config::from_file() / Config::load_or_default()
  • init_scripting(config) — creates ScriptEngine from config, calls auto_load_scripts()
  • init_plugins(config) — creates PluginManager, registers LoggerPlugin and HighlightPlugin

Phase 5: Advanced Features

DCC Protocol (crates/rustirc-core/src/dcc/)

DccManager (mod.rs — new, ~1130 lines)

  • Central session tracker with Arc<RwLock<HashMap<SessionId, DccSession>>> and async lifecycle
  • parse_dcc_request(peer_nick, ctcp_data) -> DccResult<DccRequest> — parses CHAT, SEND, RESUME, ACCEPT from CTCP data with IP long-format conversion
  • handle_dcc_request(request) — creates session, emits DccEvent::Offered, checks file size limits
  • initiate_chat() / initiate_send() — outgoing DCC with CTCP message generation (filename space → underscore sanitization)
  • accept_transfer() / cancel() / complete_session() / fail_session() — full session lifecycle
  • ip_to_long(&IpAddr) -> u64 / parse_ip_long(&str) -> DccResult<IpAddr> — DCC IP encoding (32-bit integer ↔ dotted-quad)
  • Event channel: mpsc::UnboundedSender<DccEvent> with take_event_receiver() for consumer

Session Types:

  • DccSession::Chat { id, peer_nick, direction, remote_addr, remote_port, connected }
  • DccSession::Send { id, peer_nick, filename, file_size, progress, active }
  • DccSession::Receive { id, peer_nick, filename, file_size, progress, active }

DCC Chat (chat.rs — new)

  • DccChat with TCP stream wrapper for direct messaging
  • initiate() — bind listener, return CTCP message
  • wait_for_connection() / connect_to_peer() — connection establishment
  • send_line() / recv_line() — newline-delimited message protocol
  • Event emission for connect/message/disconnect

DCC Transfer (transfer.rs — new)

  • DccTransfer with TransferProgress { bytes_transferred, total_bytes, speed_bps, percentage }
  • receive_file() — async file download with progress tracking and speed calculation
  • send_file() — async file upload with chunked writing
  • Cancel support via CancellationToken

DccError — comprehensive error enum: Disabled, Io, SessionNotFound, FileTooLarge, InvalidPortRange, NoAvailablePort, InvalidRequest, ConnectionFailed, Cancelled, ResumeNotSupported, InvalidAddress, Timeout

IRCv3 Batch Handler (crates/rustirc-core/src/batch.rs — new, ~420 lines)

  • BatchManager with open_batches and completed_batches HashMaps
  • handle_batch_start(&Message) — parses +<ref_tag> and batch type, detects nesting via batch tag on BATCH message itself
  • handle_batch_end(&Message) — moves batch from open to completed, returns Batch
  • add_message(&Message) -> bool — routes messages to open batch by batch tag value
  • message_is_batched() / is_in_batch() / open_count() / completed_count() — query methods
  • get_batch() / take_batch() — completed batch retrieval

BatchType enum: Netjoin, Netsplit, ChatHistory, LabeledResponse, Custom(String) with parse() and as_str() methods

IRCv3 CHATHISTORY (crates/rustirc-core/src/chathistory.rs — new, ~370 lines)

  • ChatHistoryManager with VecDeque<PendingRequest> for FIFO request correlation
  • request_history(HistoryRequest) -> (u64, Message) — queues request, returns correlation ID + protocol message
  • build_request_message(&HistoryRequest) -> Message — constructs CHATHISTORY command
  • handle_response() -> Option<(u64, HistoryRequest)> — pops FIFO queue for correlation
  • clear_pending() — disconnect cleanup

HistoryRequest enum: Before, After, Between, Around, Latest with target, reference(s), and limit

MessageReference: MsgId(String) and Timestamp(String) with parse() and to_param() for wire format

Flood Protection (crates/rustirc-core/src/flood.rs — new, ~340 lines)

Token bucket algorithm implementation:

  • FloodProtector::new(max_tokens, refill_rate, max_queue_size)
  • FloodProtector::from_config(&FloodConfig)
  • try_send() -> bool — refill tokens, consume one if available
  • enqueue(message) -> bool — queue message if queue not full
  • drain_ready() -> Vec<String> — send all queued messages that have tokens
  • next_send_time() -> Option<Instant> — calculate when next token available
  • set_enabled(bool) — runtime toggle (disabled = unlimited sending)

Proxy Support (crates/rustirc-core/src/proxy/ — new directory)

  • ProxyConnector trait (mod.rs): async fn connect(target_host, target_port) -> Result<TcpStream> with from_config() factory
  • SOCKS5 (socks5.rs): Via tokio-socks crate, optional username/password authentication
  • HTTP CONNECT (http.rs): Manual implementation with CONNECT host:port + basic auth header, 200 status verification

Message Tag Helpers (crates/rustirc-protocol/src/message.rs)

Added to Message:

  • get_tag(key) -> Option<String> — retrieve tag value by key
  • has_tag(key) -> bool — check tag existence
  • get_time() — shortcut for time tag (server-time)
  • get_msgid() — shortcut for msgid tag
  • get_batch() — shortcut for batch tag
  • get_label() — shortcut for label tag

GUI Enhancements

Notification Rules Engine (crates/rustirc-gui/src/notifications.rs — new)

  • NotificationRules with configurable highlight words, nick mention detection, channel/user filters
  • QuietHours with time-based suppression and weekend override
  • NotificationEntry log with timestamps and NotificationType classification

Search Engine (crates/rustirc-gui/src/search.rs — new)

  • SearchEngine with full-text message search
  • SearchQuery: text, channel filter, user filter, date range, case sensitivity
  • SearchState for UI panel integration

URL Preview (crates/rustirc-gui/src/widgets/url_preview.rs — new)

  • OnceLock<Regex> URL detection (MSRV 1.75 compatible — avoids LazyLock which requires 1.80)
  • UrlInfo with display text and original URL extraction

Settings Persistence (crates/rustirc-gui/src/state.rs)

  • Added #[derive(serde::Serialize, serde::Deserialize)] and #[serde(default)] to AppSettings
  • settings_path() / save() / load() for XDG-compliant settings storage

Phase 6: Testing & Integration

Integration Test Suite (tests/)

Test File Tests Coverage
config_test.rs 6 Roundtrip save/load, parent dir creation, forward compatibility, default path, config generation, all-sections persistence
scripting_test.rs 7 Engine creation from config, event handler firing, command execution, sandbox blocking (io/require/dofile), variable persistence, priority ordering, ScriptMessage methods
plugin_test.rs 7 Registration/listing, enable/disable, unload lifecycle, highlight word matching, logger init, shutdown_all, plugin info retrieval
ircv3_test.rs 6 Batch lifecycle (start/add/end), message tag helpers, CHATHISTORY request building, MessageReference parsing, flood burst limiting, flood queue management
dcc_test.rs 7 Manager creation, SEND parsing, CHAT parsing, RESUME parsing, IP long-format conversion, disabled config behavior, invalid request handling

Test Isolation: Each config test uses a unique temp subdirectory to prevent race conditions during parallel execution.

Raw String Handling: Uses r##"..."## (double-hash raw strings) for Lua code containing "#" characters (e.g., "#rust" channel names).


Dependencies Added

Crate Version Purpose Used In
dirs 6.0 XDG-compliant config/data directory resolution core, gui, plugins
notify-rust 4 Linux D-Bus desktop notifications gui
tokio-socks 0.5 SOCKS5 proxy TCP stream wrapping core
chrono 0.4 Notification quiet hours time calculations gui

Breaking Changes

None — this is a purely additive release. All existing functionality, APIs, and CLI behavior remain unchanged.

Technical Notes

  • MSRV: 1.75 maintained (uses OnceLock instead of LazyLock which requires 1.80)
  • Async Safety: ScriptEngine uses std::sync::RwLock to avoid tokio runtime-within-runtime panics
  • Clippy Compliance: BatchType::from_str() renamed to parse() to avoid FromStr trait confusion
  • Config #[derive(Default)]: Required by clippy when manual Default impl matches sub-field defaults

Full Changelog

Full Changelog: v0.3.9...v0.4.0