I use irssi/screen on a remote server to maintain a constant IRC presence and I wanted a nice way to pop up a pretty, on-screen notification when my nick is hilighted or when I get a direct message. Irssi already has Perl 5 scripting support built-in, so the rest just took a little whipuptitude.
Here is part of a screen shot of the result:
There were three things I needed to make it work:
- Run a simple web service locally on my desktop machine that translates remote notification requests to the local notification daemon
- Use reverse port forwarding in the ssh connection for my IRC session to connect a remote port to my local web service
- Script irssi to listen for hilights and HTTP POST the message to the remote port
I'll explain each piece step-by-step.
The web service
I wrote a trivially-simple Plack application that reads the text content of a POST, splits it into a summary line and a message and calls the command line notification program with the message. I use 'notify-send' because I use Ubuntu 11.10, but you could swap in a comparable program for your own operating system.
use v5.10;
use strict;
use warnings;
use Plack::Request;
my $icon = "/usr/share/notify-osd/icons/gnome/scalable/status/notification-message-im.svg";
sub _notify {
my $content = shift;
my ($summary, $body) = split "\n", $content, 2;
$summary //= "IRC";
$body //= "";
system("/usr/bin/notify-send", "-i", $icon, $summary, $body);
}
my $app = sub {
my $req = Plack::Request->new(shift);
_notify($req->raw_body);
my $res = $req->new_response(200);
return $res->finalize;
};
I saved that as app.psgi and fired it up to listen on port 7877:
plackup -l localhost:7877 app.psgi
(Making that start automatically as part of your own login session is left as an exercise for the reader.)
Reverse port forwarding
I already had an alias to ssh to the server with my irssi/screen session, so I just had to modify it to add the reverse forwarding.
alias irc="ssh xdg@example.com -R 7877:localhost:7877"
Irssi scripting
I already had an irssi script to email me on highlights, so I adapted that to make a web request instead. It has two configuration options: a cooldown delay between messages and a url for messages.
I savid it as 'purr_notify.pl', which you can also get from my irssi scripts repo.
use strict;
use vars qw($VERSION %IRSSI);
use Irssi;
$VERSION = '0.0.1';
%IRSSI = (
authors => 'David Golden',
contact => 'dagolden@cpan.org',
name => 'purr_notify',
description => 'Send a purr when someone is talking to you in some channel.',
url => 'https://github.com/dagolden/irssi-scripts/blob/master/purr_notify.pl',
license => 'Apache License 2.0',
changed => 'Sun Feb 15 22:54:27 EST 2012'
);
#--------------------------------------------------------------------
# In parts based on fnotify.pl 0.0.3 by Thorsten Leemhuis
# http://www.leemhuis.info/files/fnotify/
# In parts based on knotify.pl 0.1.1 by Hugo Haas
# http://larve.net/people/hugo/2005/01/knotify.pl
# which is based on osd.pl 0.3.3 by Jeroen Coekaerts, Koenraad Heijlen
# http://www.irssi.org/scripts/scripts/osd.pl
# Other parts based on notify.pl from Luke Macken
# http://fedora.feedjack.org/user/918/
#--------------------------------------------------------------------
#--------------------------------------------------------------------
# Configuration handling
#--------------------------------------------------------------------
my %CONFIG;
sub load_config {
%CONFIG = (
url => Irssi::settings_get_str("$IRSSI{name}_url"),
cooldown => Irssi::settings_get_int("$IRSSI{name}_cooldown"),
);
if ( ! length $CONFIG{url} ) {
$CONFIG{url} = "http://localhost:7877/";
Irssi::print("$IRSSI{name} setting '$IRSSI{name}_port' defaulting to $CONFIG{url}");
}
if ( $CONFIG{cooldown} < 0 ) {
$CONFIG{cooldown} = 120;
Irssi::print("$IRSSI{name} setting '$IRSSI{name}_cooldown' defaulting to $CONFIG{cooldown}");
}
}
#--------------------------------------------------------------------
# Handle private messages
#--------------------------------------------------------------------
my %last_priv_from;
sub priv_msg {
my ($server,$msg,$nick,$address,$target) = @_;
if ( time - ($last_priv_from{$nick} || 0 ) > $CONFIG{cooldown} ) {
$last_priv_from{$nick} = time;
_send_purr($nick => $msg);
}
}
#--------------------------------------------------------------------
# Handle public hilights
#--------------------------------------------------------------------
my %last_hilight_from;
sub hilight {
my ($dest, $text, $stripped) = @_;
my ($channel, $level) = ($dest->{target}, $dest->{level});
if ($level & MSGLEVEL_HILIGHT) {
if ( time - ($last_hilight_from{$channel} || 0 ) > $CONFIG{cooldown} ) {
$last_hilight_from{$channel} = time;
_send_purr($channel => $stripped);
}
}
}
#--------------------------------------------------------------------
# Send notification message
#--------------------------------------------------------------------
sub _send_purr {
system("/usr/bin/curl", $CONFIG{url}, "-s", "-d", join("\n",@_));
}
#--------------------------------------------------------------------
# Hook into irssi settings and signals
#--------------------------------------------------------------------
Irssi::settings_add_str($IRSSI{name}, "$IRSSI{name}_url", '');
Irssi::settings_add_int($IRSSI{name}, "$IRSSI{name}_cooldown", -1);
load_config();
Irssi::signal_add_last("message private", \&priv_msg);
Irssi::signal_add_last("print text", \&hilight);
Irssi::signal_add_last("setup changed", \&load_config);
You might note that 'curl' is running silent with '-s' so it won't give errors trying to notify when the ssh connection is down.
I copied that into my ~/.irssi/scripts/autorun directory, restarted irssi and I was done.

11 Comments
I have done something very similar using a bot: https://gist.github.com/893457
Nice! That's another way to do it. If I didn't already have the email notifier written, I might have opted for a bot instead. One thing I like about irssi integration is that I can join/leave channels and the notification system stays in sync.
Thank you, David! Very useful. I was thinking of writing something similar myself and wanted to limit the dependencies on the server to almost zero (I don't admin that shell server). You should really make a distribution and put in in CPAN (App:: something namespace...).
C.
If you want even fewer dependencies, check this out: https://github.com/rgs/griwlrssi/
it's just sockets!
David, do you accept a patch?
I propose to replace the contents of $icon in app.psgi:
- my $icon = "/usr/share/notify-osd/icons/gnome/scalable/status/notification-message-im.svg";
+ my $icon = 'notification-message-im";
Why? Using the stockname is more generic (notify-send will find it), e.g. when someone uses an other theme (I know, not obvious in Ubuntu nowadays :) ).
C.
Thanks! You're right that very little is obvious (aka well-documented). Oh, well. :-)
The whole thing could be a whole lot easier if you could talk to your local dbus daemon directly (through an ssh tunnel). Then you would not need to tunnel your requests through that plack app. :-/
Unfortunately the man page doesn't mention a way to tell notify-send to talk to a different dbus port. :(
For reference, this works quite well:
ssh REMOTE_HOST "tail -n0 -q -f ~/irclogs/*/*.log | grep --line-buffered '>.*MYNICK'" | xargs -I % notify-send %
Nice touch and no irssi plugin hacking necessary!
Altough a great solution because of its simplicity, it will not work when you prefer not to keep logs.
David,
Reading the following made me thing of an alternative to start the setup:
"(Making that start automatically as part of your own login session is left as an exercise for the reader.)"
and
"alias irc="ssh xdg@example.com -R 7877:localhost:7877"
Why add a service when it's tied to the irc connection? A shell scripts sound a lot easier:
claudio@adelaide:~$ cat ~/bin/irc
#!/bin/bash
plackup -l localhost:7877 ~/bin/purr_notify.psgi &
PID=$!
ssh -R7877:localhost:7877 yourShellServerNameHere
kill -9 $PID
exit 0