feat: relay OwnCast /me messages as IRC CTCP ACTION with bold attribution
When an OwnCast user sends "/me claps", the bridge now relays it as a proper CTCP ACTION so IRC clients render it natively. The username is bolded with mIRC \x02 for visual attribution. Made-with: Cursor
This commit is contained in:
17
chat-summaries/2026-03-12_17-00-summary.md
Normal file
17
chat-summaries/2026-03-12_17-00-summary.md
Normal file
@@ -0,0 +1,17 @@
|
||||
# OwnCast `/me` to IRC CTCP ACTION
|
||||
|
||||
## Task
|
||||
Detect when OwnCast users type `/me does something` and relay the message to IRC as a proper CTCP ACTION so IRC clients render it natively as an action, with bold attribution on the username.
|
||||
|
||||
## Changes Made
|
||||
|
||||
- **`src/router.rs`**: Extracted `format_owncast_to_irc()` function. When the body starts with `/me `, strips the prefix and wraps the outbound string as `\x01ACTION {prefix} \x02{username}\x02 {action_body}\x01`. Added 5 unit tests covering: action formatting, multi-word actions, normal passthrough, bare `/me` without space, and `/me` mid-message.
|
||||
- **`src/irc_task.rs`**: Updated outbound handler to detect strings starting with `\x01` and send them as raw `Command::PRIVMSG` (preserving the CTCP wrapper) instead of using `send_privmsg` which would double-escape.
|
||||
|
||||
## Result
|
||||
- OwnCast user sends: `/me claps`
|
||||
- IRC clients see: `* bridge-bot [OC] **viewer42** claps` (bold username via mIRC formatting)
|
||||
- Normal messages are unaffected.
|
||||
|
||||
## Follow-up
|
||||
- None identified. All 60 tests pass.
|
||||
@@ -90,7 +90,11 @@ async fn connect_and_run(
|
||||
}
|
||||
}
|
||||
Some(text) = outbound_rx.recv() => {
|
||||
sender.send_privmsg(&config.channel, &text)?;
|
||||
if text.starts_with('\x01') {
|
||||
sender.send(Command::PRIVMSG(config.channel.clone(), text))?;
|
||||
} else {
|
||||
sender.send_privmsg(&config.channel, &text)?;
|
||||
}
|
||||
}
|
||||
_ = shutdown.changed() => {
|
||||
let _ = sender.send_quit("Bridge shutting down");
|
||||
|
||||
@@ -167,11 +167,9 @@ async fn handle_event(
|
||||
if echo.is_echo(&body) {
|
||||
return;
|
||||
}
|
||||
let formatted = if is_action {
|
||||
format!("{} * {} {} *", settings.owncast_prefix, username, body)
|
||||
} else {
|
||||
format!("{} <{}> {}", settings.owncast_prefix, username, body)
|
||||
};
|
||||
let formatted = format_owncast_to_irc(
|
||||
&settings.owncast_prefix, &username, &body,
|
||||
);
|
||||
if irc_tx.send(formatted).await.is_err() {
|
||||
warn!("IRC outbound channel closed");
|
||||
}
|
||||
@@ -208,6 +206,14 @@ async fn handle_state_change(
|
||||
}
|
||||
}
|
||||
|
||||
fn format_owncast_to_irc(prefix: &str, username: &str, body: &str) -> String {
|
||||
if let Some(action_body) = body.strip_prefix("/me ") {
|
||||
format!("\x01ACTION {} \x02{}\x02 {}\x01", prefix, username, action_body)
|
||||
} else {
|
||||
format!("{} <{}> {}", prefix, username, body)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -241,4 +247,34 @@ mod tests {
|
||||
assert!(suppressor.is_echo("hello from IRC"));
|
||||
assert!(!suppressor.is_echo("different message"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_owncast_me_formats_as_ctcp_action() {
|
||||
let result = format_owncast_to_irc("[OC]", "viewer42", "/me claps");
|
||||
assert_eq!(result, "\x01ACTION [OC] \x02viewer42\x02 claps\x01");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_owncast_me_multi_word_action() {
|
||||
let result = format_owncast_to_irc("[OC]", "viewer42", "/me claps loudly");
|
||||
assert_eq!(result, "\x01ACTION [OC] \x02viewer42\x02 claps loudly\x01");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_owncast_normal_message_unchanged() {
|
||||
let result = format_owncast_to_irc("[OC]", "viewer42", "hello world");
|
||||
assert_eq!(result, "[OC] <viewer42> hello world");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_owncast_me_without_space_is_normal() {
|
||||
let result = format_owncast_to_irc("[OC]", "viewer42", "/me");
|
||||
assert_eq!(result, "[OC] <viewer42> /me");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_owncast_me_mid_message_is_normal() {
|
||||
let result = format_owncast_to_irc("[OC]", "viewer42", "hello /me claps");
|
||||
assert_eq!(result, "[OC] <viewer42> hello /me claps");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user