Cookie Notice

As far as I know, and as far as I remember, nothing in this page does anything with Cookies.

2009/11/17

jBiff - Announcing New Mail via XMPP/Jabber

The Productivity People say turn off alerts for new mail, because it will suck your brain and suck your time. That's fine well and good. Except when you're coding and your boss shows up over your shoulder and says "Did you get my email?"

I hate having to respond "What email?" So, there's good reasons to have highly-specific alerts. And, to my knowledge, you can't really do that with Thunderbird and Epiphany. So, I began to wonder why I should even use those. Why not use Perl? It has Net::XMPP and more than one IMAP module.

So, I wrote jBiff. First there was biff, the command line tool. Then there was xbiff. Now, we have jBiff, telling your jabber client "You have mail."

I will put it on Github, soon after I figure out how.




#!/usr/bin/perl

# USAGE
# jBiff.pl -imap -x -s foo -s bar -s blee

use Carp ;
use Data::Dumper ;
use Getopt::Long ;
use IO::Socket::SSL ;
use IO::Interactive qw{interactive} ;
use Mail::IMAPClient ;
use Modern::Perl ;
use Net::XMPP ;
use subs qw{ imap_part xmpp_part } ;

$Data::Dumper::Indent = 1 ;
my @sender ;
my $debug ;
my $xmpp_identity ;
my $imap_identity ;
GetOptions( 'sender=s' => \@sender,
'debug=i' => \$debug,
'xmpp=s' => \$xmpp_identity,
'imap=s' => \$imap_identity, ) or exit( 1 ) ;

exit if !defined $imap_identity ;
exit if !defined $xmpp_identity ;
exit if length $imap_identity < 1 ;
exit if length $xmpp_identity < 1 ;

imap_part @sender ;

# ====================================================================
#
# Pull credentials from a configuration file
#
# ====================================================================
sub get_credentials {
my ( $protocol, $identity ) = @_ ;
my %config_files ;
my %config_vals ;
my %config ;

$config_files{ imap } = '.imap_identities' ;
$config_files{ smtp } = '.smtp_identities' ;
$config_files{ xmpp } = '.xmpp_identities' ;

$config_vals{ imap } = [ qw{
key server port username password directory
} ] ;
$config_vals{ xmpp } = [ qw{
key host component sender password recipient resource
} ] ;
my $stat = ( stat "$ENV{HOME}/$config_files{$protocol}" )[ 2 ] ;
my $hex_stat = sprintf '%04o', $stat ;

if ( $hex_stat != 100600 ) {
say 'You should ensure that this file is not executable,' ;
say ' and not world or group-readable or -writable.' ;
exit ;
}

if ( -f "$ENV{HOME}/$config_files{$protocol}"
&& -r "$ENV{HOME}/$config_files{$protocol}" ) {
if ( open my $fh, '<', "$ENV{HOME}/$config_files{$protocol}" ) {
while ( <$fh> ) {
chomp $_ ;
$_ = ( split m{\#}mx, $_ )[ 0 ] ;
my @creds = split m{\s*,\s*}mx, $_ ;
next if scalar @creds < 6 ;
for my $i ( 1 .. $#creds ) {
my $key = $creds[ 0 ] ;
my $val = $creds[ $i ] ;
my $key2 = $config_vals{ $protocol }[ $i ] ;
$config{ $key }{ $key2 } = $val ;
}
}
close $fh ;
}
my $href = $config{ $identity } ;
return %$href ;
}
else {
say "No Configuration" ;
exit ;
}
exit ;
}

# --------------------------------------------------------------------

# ====================================================================
#
# connect to and search your mail server via IMAP
#
# ====================================================================
sub imap_part {
my @sender = @_ ;
my $sender = join '|', @sender ;
my %creds = get_credentials( 'imap', $imap_identity ) ;

my $socket = IO::Socket::SSL->new( PeerAddr => $creds{ server },
PeerPort => $creds{ port },
) or die "socket(): $@" ;

my $client = Mail::IMAPClient->new( Socket => $socket,
User => $creds{ username },
Password => $creds{ password },
) or die "new(): $@" ;

if ( $client->IsAuthenticated() ) {
$client->select( $creds{ directory } )
or die "Select '$creds{directory}' error: ",
$client->LastError, "\n" ;

for my $msg ( reverse $client->unseen ) {
my $from = $client->get_header( $msg, 'From' ) ;
my $to = $client->get_header( $msg, 'To' ) ;
my $subject = $client->subject( $msg )
or die "Could not subject $@\n" ;
if ( $from =~ m{$sender}i ) {
my $title = 'New mail from ' . $from ;
my $body = $subject ;
$body = join q{"}, '', $body, '' ;
xmpp_part "$title - $body" ;
}
}
$client->logout() ;
}
else {
say 'FAIL ' . $! ;
}
}

# --------------------------------------------------------------------

# ====================================================================
#
# send message via XMPP/Jabber
#
# ====================================================================
sub xmpp_part {
my $args = shift ;
my %creds = get_credentials( 'xmpp', $xmpp_identity ) ;

# connection
my $hostname = $creds{ host } ;
my $port = 5222 ;
my $componentname = $creds{ component } ;
my $connectiontype = 'tcpip' ;
my $tls = 1 ;

#login
my $username = $creds{ sender } ;
my $password = $creds{ password } ;
my $resource = $creds{ resource } ;

#msg_to
my @field ;
push @field, $creds{ recipient } ;

my $Connection = new Net::XMPP::Client() ;

# Connect to talk.google.com
my $status = $Connection->Connect( hostname => $hostname,
port => $port,
componentname => $componentname,
connectiontype => $connectiontype,
tls => $tls ) ;
if ( !( defined( $status ) ) ) {
print "ERROR: XMPP connection failed.\n" ;
print " ($!)\n" ;
exit( 0 ) ;
}

# Change hostname
my $sid = $Connection->{ SESSION }->{ id } ;
$Connection->{ STREAM }->{ SIDS }->{ $sid }->{ hostname } = $componentname ;

# Authenticate
my @result = $Connection->AuthSend( username => $username,
password => $password,
resource => $resource ) ;

if ( $result[ 0 ] ne "ok" ) {
print "ERROR: Authorization failed: $result[0] - $result[1]\n" ;
exit( 0 ) ;
}

# Send messages
foreach ( @field ) {
$Connection->MessageSend( to => "$_\@$componentname",
resource => $resource,
subject => "Notification",
type => "chat",
body => $args ) ;
}

$Connection->Disconnect() ;
}

# --------------------------------------------------------------------


3 comments:

  1. Hi! I'm trying to use your jBiff to notify new email to my Jabber users, but I'm not a perl guy, and I couldn't realize how should be the identity file, or even the FooBarBlee command line :).

    Could you give some simple instructions on that?

    Thank You!

    Maicon Vinicius Nunes, from Brazil...

    ReplyDelete
  2. Be glad to. -s it the flag for sender. so -s foo -s bar -s blee would match if the sender is foorman@whatever.com, barbara@whatever.com or kyle.bleekman@whatever.com. This way, you can have one command check mail for several people.

    I wanted and want to avoid hard-coding login information into the program, and the get_credentials subroutine and the config files are my technique to avoid that. The IMAP config is rows of [key server port username password directory], comma-separated, where key is the identifier, server is the IMAP server's IP address or name, port is the port it connects to (this can do SSL), and directory is the directory you're trying to search. Username and password are self-evident, I hope.

    With XMPP, the rows are [key host component sender password recipient resource]. Sender is username, recipient is a full Jabber address. I'm honestly not sure what sure the resource field is, and the component is a slightly redundant part of the XMPP definition. Honestly, as long as a hash reference containing the part you need is passed to the xmpp_part and imap_part functions, it doesn't matter how you do it. Rewrite that part as needed.

    And thanks for taking, reading and trying to adapt my code.

    ReplyDelete
  3. Hey! At first, thanks for the instructions!

    I could make it work a little. Actually, with perl -d, I see the IMAP part is working OK. But I'm having an error on my XMPP server, which says "User Login Failed. PLAIN authentication failed". Do you have any ideas about that?

    Another question, has the 'sender' option some wildcard to get new mail from anyone?

    Thank you for your help!

    Maicon

    ReplyDelete