#!/usr/bin/perl # # Simple read-only command-line XMPP client # use strict; use Getopt::Long qw(:config no_ignore_case bundling); use Config::Std; use File::Basename; use AnyEvent; use AnyEvent::XMPP::Client; use FindBin; use lib "$FindBin::Bin/lib"; use Clix::Colours; use vars qw($VERSION); $VERSION = 0.003004; sub usage { die "usage: " . basename($0) . " [-n] [-v] [-u ]\n"; } my (@user, @ignore_user, $help, $noop); my $verbose = 0; usage unless GetOptions( 'user|u=s@' => \@user, 'ignore-user|ignore|U=s@' => \@ignore_user, 'help|h' => \$help, 'noop|n' => \$noop, 'verbose|v+' => \$verbose, ); usage() if $help; usage() if @ARGV; die "Missing \$ENV{HOME}/.clixrc file\n" unless -f "$ENV{HOME}/.clixrc"; read_config "$ENV{HOME}/.clixrc" => my %config; die "Error reading \$ENV{HOME}/.clixrc: $!" unless keys %config; if (my $user_colours = $config{COLOURS} || $config{COLORS}) { Clix::Colours::merge_colours($user_colours); } # Use DEFAULTS user if defined, otherwise connect to all defined accounts my %ignore_user = map { $_ => 1 } @ignore_user; @user = ( $config{DEFAULTS}{user} ) if ! @user && $config{DEFAULTS}{user}; @user = grep { ! exists $ignore_user{$_} } grep !/^(DEFAULTS)$/, sort keys %config if ! @user; #printf "+ user accounts: %s\n", join(', ', @user); die "No user accounts found to use - aborting\n" if ! @user; # Setup short jid mappings my @map = (); push @map, ref $config{DEFAULTS}{map} ? @{$config{DEFAULTS}{map}} : $config{DEFAULTS}{map} if $config{DEFAULTS}{map}; for (@user) { push @map, ref $config{$_}{map} ? @{$config{$_}{map}} : $config{$_}{map} if $config{$_}{map}; } my %map = map { split /\s*:\s*/, $_, 2 } @map; print STDERR "+ mappings:\n" . join("\n", map { " $_: $map{$_}" } keys %map) . "\n" if keys %map && $verbose; # Setup microblog jids my @microblog = (); push @microblog, ref $config{DEFAULTS}{microblog} ? @{$config{DEFAULTS}{microblog}} : $config{DEFAULTS}{microblog} if $config{DEFAULTS}{microblog}; my %microblog = map { split /\s*:\s*/, $_, 2 } @microblog; print STDERR "+ microblogs:\n" . join("\n", map { " $_: $microblog{$_}" } keys %microblog) . "\n" if keys %microblog && $verbose; exit 0 if $noop; # Initial Setup $SIG{INT} = \&disconnect; my $j = AnyEvent->condvar; my $c = AnyEvent::XMPP::Client->new; # Add accounts for ( @user ) { my $jid = $config{$_}{user}; my $pass = $config{$_}{pass}; my $priority = $config{$_}{priority} || $config{DEFAULTS}{priority} || 10; next unless $jid && $pass; print "+ adding account $jid\n" if $verbose; $c->add_account( $jid, $pass, undef, undef, { resource => 'clix', initial_presence => $priority }); } my $account_cnt = scalar $c->get_accounts(); binmode(STDOUT, ':utf8'); my %connected = (); # Register callbacks $c->reg_cb ( session_ready => sub { my ($c, $acc) = @_; printf "Connected: %s\n", $acc->bare_jid if $verbose || ! $connected{$acc->bare_jid}; $connected{$acc->bare_jid} ||= 1; if ($verbose >= 2) { my $roster = $acc->connection->get_roster; $roster->debug_dump; } }, disconnect => sub { my ($c, $acc, $h, $p, $reason) = @_; printf "Disconnected: %s (%s:%s)%s\n", $acc->bare_jid, $h, $p, $reason ? ": $reason" : '' if $verbose; my $attempts = 0; do { sleep 2; $c->update_connections; $attempts++; } unless $attempts >= 4 || scalar($c->get_connected_accounts()) == $account_cnt; }, roster_error => sub { my ($c, $acc, $err) = @_; print "RosterError: " . $err->string . "\n"; }, presence_error => sub { my ($c, $acc, $err) = @_; print "PresenceError: " . $err->string . "\n"; }, message_error => sub { my ($c, $acc, $err) = @_; print "MessageError: " . $err->string . "\n"; }, error => sub { my ($c, $acc, $err) = @_; print "Error: " . $err->string . "\n"; }, contact_request_subscribe => sub { my ($c, $acc, $roster, $contact) = @_; printf "RequestSubscribe: %s->%s, current %s\n", $contact->jid, $acc->bare_jid, $contact->subscription if $verbose; $contact->send_subscribed; $contact->send_subscribe; }, contact_did_unsubscribe => sub { my ($c, $acc, $roster, $contact) = @_; printf "ContactDidUnsubscribe: %s unsubscribed from %s presence, reciprocating\n", $contact->jid, $acc->bare_jid if $verbose; $contact->send_unsubscribe; }, contact_unsubscribed => sub { my ($c, $acc, $roster, $contact) = @_; printf "ContactUnsubscribed: %s unsubscribed %s from his/her presence, reciprocating\n", $contact->jid, $acc->bare_jid if $verbose; $contact->send_unsubscribed; }, message => sub { my ($c, $acc, $msg) = @_; my $body = $msg->any_body || return; chomp $body; my $jid = $msg->from; my $bare_jid = $jid; $bare_jid =~ s!/ [^/]+$ !!x; my $mapped_jid = $map{ $bare_jid } || $bare_jid; # Strip annoying newlines from microblog messages if ($microblog{ $bare_jid }) { 1 while $body =~ s/\s\s+/ /g; } Clix::Colours::colourise( $body, jid => $bare_jid, mapped_jid => $mapped_jid, mb_username => $microblog{ $bare_jid }, ); } ); print "+ initiating connections\n" if $verbose; $c->start; $j->wait; sub disconnect { $c->remove_accounts if $c; exit; } __END__ =head1 NAME clix - a read-only command-line XMPP client =head1 SYNOPSIS clix [-v] [-u
] [--ignore
] =head1 DESCRIPTION clix is a read-only command-line XMPP client. It is intended for simple merging/monitoring tasks, such as following twitter and RSS feeds via XMPP. =head1 CONFIGURATION clix requires a config file called .clixrc on your home directory, containing a set of jabber/xmpp accounts to connect to. This is just an ini-style config like the following: [gavin1] user = gavin@openfusion.com.au pass = password1 [jabber] user = openfusion@jabber.org pass = password2 [google] user = somethingelse@gmail.com pass = password3 By default, clix tries to connect to all accounts. You can restrict to particular accounts by specifying one or more user section names with '-u' e.g. clix -u gavin1 -u jabber for the example above. Or you can ignore individual accounts using one or more --ignore parameters e.g. clix --ignore google =head2 DEFAULTS SECTION =head2 COLOURS SECTION In addition, clix allows you to customise the colours used for output by defining a [COLOURS] section (or [COLORS], if you prefer) in your config file. The current defaults are: [COLOURS] timestamp = white from_jid = cyan mb_sender = yellow im_msg = green mb_msg = green mb_reply = red bold mb_mine = white bold uri = red person = cyan hashtag = magenta group = magenta Values are any combination of attributes and colours that make sense to L. The colour keys are the following: =over 4 =item timestamp The current timestamp, output at the beginning of the line. =item from_jid The JID (sender jabber ID) of the message. For microblog (twitter/identica etc.) messages, this is the microblog jabber account user, rather than the end user who sent the message. =item mb_sender For microblog (twitter, identica, etc.) messages, the microblog sender - the end user who sent the message. =item im_msg The message text colour of IM messages. =item mb_msg The message text colour of microblog (twitter, identica etc.) messages. =item mb_reply =item mb_mine =item uri =item person =item hashtag =item group =back =head1 AUTHOR Gavin Carr =head1 LICENSE Copyright 2007-2010 Open Fusion Pty. Ltd. This program is free software, licensed under the terms of the GNU General Public License v2. =cut