Hilight growls from irssi with Plack and ssh

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);
  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

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);


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.

This entry was posted in perl programming and tagged , , , , , . Bookmark the permalink. Both comments and trackbacks are currently closed.


  1. Posted February 16, 2012 at 12:20 pm | Permalink

    I have done something very similar using a bot: https://gist.github.com/893457

    • Posted February 16, 2012 at 2:05 pm | Permalink

      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.

  2. Posted February 17, 2012 at 2:18 pm | Permalink

    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...).


    • Posted February 17, 2012 at 3:58 pm | Permalink

      If you want even fewer dependencies, check this out: https://github.com/rgs/griwlrssi/

      it's just sockets!

      • Posted February 18, 2012 at 7:30 am | Permalink

        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 :) ).


        • Posted February 18, 2012 at 11:50 am | Permalink

          Thanks! You're right that very little is obvious (aka well-documented). Oh, well. :-)

  3. Posted February 20, 2012 at 4:41 pm | Permalink

    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. :(

  4. oxy
    Posted October 1, 2012 at 9:28 am | Permalink

    For reference, this works quite well:

    ssh REMOTE_HOST "tail -n0 -q -f ~/irclogs/*/*.log | grep --line-buffered '>.*MYNICK'" | xargs -I % notify-send %

    • Posted October 1, 2012 at 12:43 pm | Permalink

      Nice touch and no irssi plugin hacking necessary!

      • Posted January 12, 2013 at 2:24 pm | Permalink

        Altough a great solution because of its simplicity, it will not work when you prefer not to keep logs.

  5. Posted January 12, 2013 at 2:17 pm | Permalink

    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.)"
    "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
    plackup -l localhost:7877 ~/bin/purr_notify.psgi &
    ssh -R7877:localhost:7877 yourShellServerNameHere
    kill -9 $PID
    exit 0