rrichards
10/8/2013 - 6:00 PM

pushover.pl

use strict;
use warnings;

# {{{ DOCUMENTATION
#####
# A lot of code borrowed from the prowlnotify.pl script at
# http://www.denis.lemire.name/2009/07/07/prowl-irssi-hack/
# and pushovernotify.pl (Daniel Johansson <donnex@donnex.net>)
# and http://www.geekfarm.org/wu/muse/scripts/growl-notify.txt
#
# https://gist.github.com/3714183
#
# }}}

use Irssi;
use Irssi::Irc;
use vars qw($VERSION %IRSSI %config);

use HTTP::Request;
use LWP::UserAgent;
use JSON;

$VERSION = '0.3.1';

%IRSSI = (
    authors     => 'Justin J. Novack',
    contact     => 'jnovack@gmail.com',
    name        => 'pushover',
    description => 'Send a Pushover notification',
    license     => 'GPLv2',
    url         => 'http://www.ozmonet.com'
);

# Awful updating hack - Remember to increase this number on each revision!
# This counts the number of gist revisions and compares to current version.
Irssi::settings_add_str($IRSSI{'name'},  'pushover_gist', '6');

# Generics
$config{away_level} = 0;
$config{awayreason} = 'Auto-away';
$config{clientcount} = 0;

# Keep last message to not repeat it.
my $lastnick = "";
my $lasttext = "";

# {{{ PUBLIC FUNCTIONS
# {{{ message(text)
sub message
{
    my $text = shift;
    Irssi::print('%b>> %B'.$IRSSI{'name'}."%n :: $text");
}
# }}}
# {{{ error(text)
sub error
{
    my $text = shift;
    error("%y[%YWARNING%y]%n :: $text");
}
# }}}
# {{{ warning(text)
sub warning
{
    my $text = shift;
    message("%y[%YWARNING%y]%n :: $text");
}
# }}}
# {{{ debug(text)
sub debug
{
    my $text = shift;
    return unless Irssi::settings_get_bool('pushover_debug');
    message("%w[%WDEBUG%w]%n :: $text");
}
# }}}
# {{{ send_pushover(title, message)
sub send_pushover
{
    my ($title, $message) = @_;

    my $api_usr = Irssi::settings_get_str('pushover_api_usr');
    my $api_app = Irssi::settings_get_str('pushover_api_app');
    if (!$api_usr || !$api_app) {
        error('Notification not sent. » Missing pushover user or api key(s).');
        return;
    }

    my $response = LWP::UserAgent->new()->post(
        'https://api.pushover.net/1/messages.json',
        [
            token => $api_app,
            user => $api_usr,
            title => $title,
            message => $message,
        ]
    );

    #my $response = $ua->request($req);
    if ($response->is_success) {
        debug("Notification successfully posted. » $message");
    } elsif ($response->code == 429) {
        warning('Notification not permitted. » Application is over API limit.'); 
    } elsif ($response->code == 400) {
        warning('Notification not posted. » Incorrect user or application key.');
        debug($response->content);
    } else {
        warning('Notification not posted. » ('.$response->code.') '.$response->decoded_content);
    }
}
# }}}
# {{{ client_connect()
sub client_connect
{
    my (@servers) = Irssi::servers;

    $config{clientcount}++;
    debug('Client connected.');

    # setback
    foreach my $server (@servers) {
        # if you're away on that server send yourself back
        if ($server->{usermode_away} == 1) {
            $server->send_raw('AWAY :');
        }
    }
}
# }}}
# {{{ client_disconnect()
sub client_disconnect
{
    my (@servers) = Irssi::servers;
    debug('Client disconnected.');

    $config{clientcount}-- unless $config{clientcount} == 0;

    # setaway
    if ($config{clientcount} <= $config{away_level}) {
        # we have the away_level of clients connected or less.
        foreach my $server (@servers) {
            if ($server->{usermode_away} == '0') {
                # we are not away on this server. set the autoaway reason
                $server->send_raw(
                    'AWAY :' . $config{awayreason}
                );
            }
        }
    }
}
# }}}
# {{{ msg_pub(server, data, nick, mask, target)
sub msg_pub
{
    my ($server, $data, $nick, $mask, $target) = @_;
    my $safeNick = quotemeta($server->{nick});
    $data = _strip_formatting($data);

    if ($server->{usermode_away} == '1' && $data =~ /$safeNick/i) {
        debug('User is away?' . $server->{usermode_away});
        unless ( _supress_duplicates( $nick, $data) ) {
            debug("Public Message » (".$server->{tag}.") $target : <$nick> $data");
            send_pushover($target,"<$nick> $data");
        }
    }
}
# }}}
# {{{ msg_pri(server, data, nick, address)
sub msg_pri
{
    my ($server, $data, $nick, $address) = @_;
    $data = _strip_formatting($data);
    if ($server->{usermode_away} == '1') {
        debug('User is away?' . $server->{usermode_away});
        unless ( _supress_duplicates( $nick, $data) ) {
            debug("Private Message » (".$server->{tag}.") <$nick> $data");
            send_pushover($nick, $data);
        }
    }
}
# }}}
# {{{ msg_hi(dest, text, stripped)
sub msg_hi
{
    my ($dest, $text, $stripped) = @_;
    my $target = "";
    my $hilights = Irssi::settings_get_bool('pushover_hilight');

    if (($dest->{level} & (MSGLEVEL_HILIGHT|MSGLEVEL_MSGS))
        && ($dest->{level} & MSGLEVEL_NOHILIGHT) == 0
        && $hilights)
    {
        # Pull nick and text from $stripped
        $stripped =~ m|^\<(.*?)\>\s+(.*)$|;
        my ( $nick, $text ) = ( $1, $2 );
        if ($dest->{level} & MSGLEVEL_PUBLIC) {
            $target = $dest->{target};
        }
        return undef unless ( $nick && $text );
        unless ( _supress_duplicates( _strip_nick($nick), $text) ) {
            debug("Highlight » $target <$nick> $text");
            send_pushover($target, "<$nick> $text");
        }
    }
}
# }}}
# {{{ cmd_pushover(data, server, witem)
sub cmd_pushover
{
    my ($data, $server, $witem) = @_;
    my ($op, $var1) = split " ", $data;

    $op = lc $op;

    if (!$op) {
        message(" %9/pushover test%n       - Sends a test message to pushover.");
        message(" %9/pushover debug 0%n    - Turns debugging off.");
        message(" %9/pushover debug 1%n    - Turns debugging on. (default)");
        message(" %9/pushover exact 0%n    - Suppress repeat notifications from the same user.");
        message(" %9/pushover exact 1%n    - Only suppress exact duplicate messages. (default)");
        message(" %9/pushover hilight 0%n  - Turns off hilight notifications. (default)");
        message(" %9/pushover hilight 1%n  - Turns on hilight notifications.");
    } elsif ($op eq "test") {
        debug("A message is being sent to pushover, please confirm receipt.");
        send_pushover("Testing", "It works! Yay!");
    } elsif ($op eq "debug" && $var1 == "0") {
        Irssi::settings_set_bool('pushover_debug', 0);
        message('Debugging has been turned %9OFF%n');
    } elsif ($op eq "debug" && $var1 == "1") {
        Irssi::settings_set_bool('pushover_debug', 1);
        message('Debugging has been turned %9ON%n');
    } elsif ($op eq "exact" && $var1 == "0") {
        Irssi::settings_set_bool('pushover_exact', 0);
        message('Duplicate checking is %9user%n based. If the last message sent was from the same user, it will be suppressed.');
    } elsif ($op eq "exact" && $var1 == "1") {
        Irssi::settings_set_bool('pushover_exact', 1);
        message('Duplicate checking is %9user+text%n based. Messages are only suppressed if they are exactly the same.');
    } elsif ($op eq "hilight" && $var1 == "0") {
        Irssi::settings_set_bool('pushover_hilight', 0);
        message('Sending Pushover notifications for hilights is %9OFF%n.');
    } elsif ($op eq "hilight" && $var1 == "1") {
        Irssi::settings_set_bool('pushover_hilight', 1);
        message('Sending Pushover notifications for hilights is %9ON%n.');
    } elsif ($op eq "last") {
        message("\$lastnick was: $lastnick");
        message("\$lasttext was: $lasttext");
    }
}
# }}}
# }}}

# {{{ main()
Irssi::settings_add_str($IRSSI{'name'},  'pushover_api_app', '');
Irssi::settings_add_str($IRSSI{'name'},  'pushover_api_usr', '');
Irssi::settings_add_bool($IRSSI{'name'}, 'pushover_debug', 1);
Irssi::settings_add_bool($IRSSI{'name'}, 'pushover_exact', 1);
Irssi::settings_add_bool($IRSSI{'name'}, 'pushover_hilight', 0);

Irssi::signal_add_last('proxy client connected', 'client_connect');
Irssi::signal_add_last('proxy client disconnected', 'client_disconnect');
Irssi::signal_add_last('message public', 'msg_pub');
Irssi::signal_add_last('message private', 'msg_pri');
Irssi::signal_add_last("print text", "msg_hi");
Irssi::command_bind('pushover',\&cmd_pushover);

Irssi::print('%G>>%n '.$IRSSI{name}.'%n v'.$VERSION.' loaded.');
if (!Irssi::settings_get_str('pushover_api_usr')) {
    warning('user api key is not set, set it with %9/set pushover_api_usr apikey');
}
if (!Irssi::settings_get_str('pushover_api_app')) {
    warning('app api key is not set, set it with %9/set pushover_api_app apikey');
}

message('Type %9/pushover%n for help.');
if (Irssi::settings_get_bool('pushover_debug') == "1") {
    debug(' Debugging is on, type %9/pushover debug 0%n to turn off');
}
if (Irssi::settings_get_bool('pushover_exact') == "1") {
    message('Duplicate checking is %9user+text%n based. Messages are only suppressed if they are exactly the same.');
} else {
    message('Duplicate checking is %9user%n based. If the last message sent was from the same user, it will be suppressed.');
}
if (Irssi::settings_get_bool('pushover_hilight') == "1") {
    message('Sending Pushover notifications for hilights is %9ON%n.');
} else {
    message('Sending Pushover notifications for hilights is %9OFF%n.');
}

### Check for Update
# This is an extremely AWFUL way to check for updates.
# Counts the revisions at github and compares to hardcoded value.
#
my $response = LWP::UserAgent->new()->get('https://api.github.com/gists/3714183');
my $decoded_json = decode_json( $response->decoded_content );
if (Irssi::settings_get_str('pushover_gist') != $#{$decoded_json->{history}}+1) {
    warning('You are not up to date!  Please download the newest version from %9https://gist.github.com/3714183%n');
}

# }}}

# {{{ PRIVATE
# {{{ _supress_duplicates(nick, text)
#
# Store the last several messages.  If we get a duplicate message,
# then avoid sending multiple pages.  Duplicates occur when a message
# matches more than one message, e.g. a public message in a monitored
# channel that contains a hilighted word.
#
sub _supress_duplicates
{
    my ( $nick, $text ) = @_;

    # Check both nick and text for matches only if pushover_exact is set
    if ( Irssi::settings_get_bool('pushover_exact') eq "1" && 
        $lastnick && $nick eq $lastnick &&
        $lasttext && $text eq $lasttext
    )
    {
        # debug("supressed_duplicates(exact)");
        return 1;
    }
    # Otherwise, check just nick is set
    elsif ( $lastnick && $nick eq $lastnick )
    {
        # debug("supressed_duplicates(loose)");
        return 1;
    }

    # debug("\$lastnick : $lastnick || \$nick : $nick");
    # debug("\$lasttext : $lasttext || \$text : $text");
    $lastnick = $nick;
    $lasttext = $text;

    # no match
    return undef;
}
# }}}
# {{{ _strip_nick(nick)
#
# Strips the prepended spaces and modes from the nickname. The
# nick can contain modes, spaces or other characters if the
# hilight function adds formatting.  This removes it so a dup
# message can be properly filtered out.
#
sub _strip_nick
{
    my ($nick) = shift;
    $nick =~ tr/[^a-zA-Z0-9\-\_\[\]\{\}\|\`\^]+//cd;
    return $nick;
}
# }}}
# {{{ _strip_formatting(msg)
#
# Strips formatting from the message so it can be cleanly
# debugged or sent to the client.
#
sub _strip_formatting
{
    my ($msg) = shift;
    $msg =~ s/\x03[0-9]{0,2}(,[0-9]{1,2})?//g;
    $msg =~ s/[^\x20-\xFF]//g;
    $msg =~ s/\xa0/ /g;
    return $msg;
}
# }}}}}}