irssi-scripts/fnotify.pl

120 lines
4.2 KiB
Perl

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', '/home/rtch/.irssi/scripts/autorun/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');