use strict; use Irssi; use vars qw($VERSION %IRSSI); use FindBin; use POSIX; $VERSION = "4.0"; %IRSSI = ( authors => 'Ritchie Cunningham', contact => 'ritchie@ritchiecunningham.co.uk', name => 'fnotify', description => 'Sends desktop and audio notifications for private messages and highlights.', license => 'Public Domain', ); # --- Global variables to hold raw message data between signals. --- my ($last_public_msg, $last_public_nick, $last_public_target); # --- Configuration. --- my $festival_voice = '(voice_us1_mbrola)'; # --- Core Functions. --- sub notify { my ($title, $message) = @_; # --- Visual desktop notification. --- system("notify-send", "-i", "irssi", $title, $message); # --- Play a notification sound file. --- if(Irssi::settings_get_bool('fnotify_sound_enabled')) { my $sound_player = Irssi::settings_get_str('fnotify_sound_player'); my $sound_file = Irssi::settings_get_str('fnotify_sound_file'); if($sound_player && $sound_file) { # If the path isn't absolute, prepend the script's directory. # Note: For best results, set the full absolute path in with /set command. if($sound_file !~ m#^/#) { $sound_file = "$FindBin::Bin/$sound_file"; } # Use a non-blocking fork to play sound without freezing Irssi. my $pid = fork(); if(!defined $pid) { Irssi::print("fnotify: Could not fork() to play sound: $!", MSGLEVEL_CLIENTERROR); return; } if($pid == 0) { # Child process. Detach and execute the sound player. close STDIN; close STDOUT; close STDERR; POSIX::setsid(); exec($sound_player, $sound_file); exit(127); # Kill child if exec fails for any reason. } # Aaaand back with the parent. } } # --- TTS using festival. --- if(Irssi::settings_get_bool('festival_enabled')) { my $text_to_speak = "$title, $message"; # Fork the process to prevent Irssi from freezing while festival speaks. # :oop: Actually, we're going to have to double-fork to prevent # festival from changing the parent into some raw mode. # # FINE!! Go screw yourself! as neither forking, nor double-forking worked out # we'll use systemd-run for a guaranteed detachment. # This will offload the entire backgrounding task to the systemd service # manager. # Sorry @dacav, tried to use your open() suggestion, but as we can't # daemonise using the fork solution, we have to manually prevent shell injection. $text_to_speak =~ s/\\/\\\\/g; # Must escape backslashes first $text_to_speak =~ s/"/\\"/g; # Then escape double-quotes my $scheme_command = "$festival_voice(SayText \"$text_to_speak\")"; $scheme_command =~ s/'/'\\''/g; my $command = "echo '$scheme_command' | festival"; system("systemd-run", "--user", "--quiet", "--no-ask-password", "/bin/sh", "-c", $command); } } # --- Signal Handlers. --- sub private_message_handler { my ($server, $msg, $nick, $address) = @_; return if($nick =~ /^(NickServ|ChanServ|MemoServ)$/i); notify("Private Message from $nick", $msg); } # FFS! THEME! Stop f.cking with my data please.. sub public_message_handler { my ($server, $msg, $nick, $address, $target) = @_; $last_public_msg = $msg; $last_public_nick = $nick; $last_public_target = $target; } sub print_text_handler { my ($dest, $text, $stripped) = @_; if(($dest->{level} & Irssi::MSGLEVEL_HILIGHT) && ($dest->{target} eq $last_public_target)) { return unless defined $last_public_nick; notify("Mentioned by $last_public_nick in $last_public_target", $last_public_msg); undef $last_public_nick; } } # --- Settings and Signal Registration. --- Irssi::settings_add_bool('lookandfeel', 'festival_enabled', 0); Irssi::settings_add_bool('lookandfeel', 'fnotify_sound_enabled', 1); Irssi::settings_add_str('lookandfeel', 'fnotify_sound_player', 'mpg123'); Irssi::settings_add_str('lookandfeel', 'fnotify_sound_file', 'msg_notification.mp3'); # Register all three handlers. Irssi::signal_add('message private', 'private_message_handler'); Irssi::signal_add('message public', 'public_message_handler'); Irssi::signal_add('print text', 'print_text_handler');