10 releases

0.3.0 Mar 24, 2024
0.2.1 Jan 31, 2022
0.1.7 Oct 21, 2021
0.1.6 Jan 16, 2021
0.1.2 Mar 22, 2020

#11 in #irc

Download history 12/week @ 2024-07-24 9/week @ 2024-09-18 22/week @ 2024-09-25

319 downloads per month

MIT license

245KB
4K SLoC

Rust 3.5K SLoC // 0.1% comments C 654 SLoC // 0.0% comments JavaScript 134 SLoC Shell 8 SLoC

hexavalent

Write HexChat plugins in Rust.

Read the docs or see the examples directory to get started.

Why the name?

"Hex" from HexChat, of course. "Hexavalent" because CrO3 looks similar to Fe2O3.

...and hexavalent chromium is unsafe to handle, much like HexChat's API.


lib.rs:

Write HexChat plugins in Rust.

To create your plugin:

  • Make a library crate with crate-type = "cdylib".
  • Define a type, e.g. struct MyPlugin, to hold any state your plugin needs.
  • Implement the Plugin trait for MyPlugin.
  • Call export_plugin with the type MyPlugin, its name, description, and version.

On Windows, it is recommended to add -C target-feature=+crt-static to your RUSTFLAGS, for example in <project root>/.cargo/config. This ensures that your DLL does not dynamically import the MSVCRT.

Examples

The following is a port of HexChat's example "auto-op" plugin. It will automatically OP everyone who joins (so don't try this if you're in a real channel!), and can be toggled on and off with /autooptoggle.

use std::cell::Cell;
use hexavalent::{Plugin, PluginHandle, export_plugin};
use hexavalent::event::print::Join;
use hexavalent::hook::{Eat, Priority};
use hexavalent::str::HexStr;

struct AutoOpPlugin {
    enabled: Cell<bool>,
}

impl Default for AutoOpPlugin {
    fn default() -> Self {
        Self {
            enabled: Cell::new(true),
        }
    }
}

impl AutoOpPlugin {
    fn autooptoggle_cb(&self, ph: PluginHandle<'_, Self>, _words: &[&HexStr]) -> Eat {
        if !self.enabled.get() {
            self.enabled.set(true);
            ph.print("Auto-Oping now enabled!");
        } else {
            self.enabled.set(false);
            ph.print("Auto-Oping now disabled!");
        }
        // eat this command so HexChat and other plugins can't process it
        Eat::All
    }

    fn join_cb(&self, ph: PluginHandle<'_, Self>, args: [&HexStr; 4]) -> Eat {
        let [nick, _channel, _host, _account] = args;
        if self.enabled.get() {
            // op ANYONE who joins
            ph.command(format!("OP {}", nick));
        }
        // don't eat this event, HexChat needs to see it
        Eat::None
    }
}

impl Plugin for AutoOpPlugin {
    fn init(&self, ph: PluginHandle<'_, Self>) {
        ph.hook_command(
            "AutoOpToggle",
            "Usage: AUTOOPTOGGLE, turns OFF/ON Auto-Oping",
            Priority::Normal,
            Self::autooptoggle_cb,
        );
        ph.hook_print(Join, Priority::Normal, Self::join_cb);

        ph.print("AutoOpPlugin loaded successfully!");
    }

    fn deinit(&self, ph: PluginHandle<'_, Self>) {
        ph.print("Unloading AutoOpPlugin...");
    }
}

export_plugin!(AutoOpPlugin, "AutoOp", "Auto-Ops anyone who joins", "0.1");

Safety

In general, this library depends on HexChat invoking the plugin from only one thread. If that is not the case, this library provides no guarantees. (Although it is never explicitly stated that this is true, HexChat's plugin documentation says nothing of synchronization, and none of the example plugins have any. It also seems true in practice.)

In debug mode (specifically, when debug_assertions is enabled), the current thread ID is checked every time the plugin is invoked, which can help detect misbehavior.

Dependencies

~1MB
~17K SLoC