Update 8: Moved code to github. All future updates will be there.
With the new cbstream interface for logging in, I was prompted, in my Laziness, to write a quick-login for Xchat. Once I learned how to do that, other, easier things came up just 'cuz they were neat (and prompted some discussion). And then Intrepid asked for a script that could get the IRC channel to look right. I agreed, and since I seemed to have half of it done (the learning-curve part), I thought I'd try. It didn't take long.
Right now, #cbstream looks like "cbstream" is writing everything, even though each line starts with the real speaker in brackets. The goal was to drop 'cbstream' from the speaker position, and replace it with the PM nick that really initiated the line. This turned out to be not too bad - a few regexes, a few pointers from some gurus in #xchat, and problem was solved. Well, almost. The '/me' action still looked funny. Eventually, with a bit more help from the gurus in #xchat, and escaping a magic character in a regex, all started working. So, for anyone using Xchat to communicate with PM's CB via FreeNode's #cbstream channel, here is my complete script. I call it "cbstream.pl" and put it in my xchat directory (for me, "~/.xchat2", YMMV, especially on Windows):
#! /usr/bin/perl
use strict;
use warnings;
my $name = 'cbstream';
my $version = 0.08;
# what I need...
use YAML::Syck;
use Data::Diver qw(DiveVal Dive DiveRef);
use LWP::Simple qw();
use XML::Twig;
# debugging tools...
use IO::Handle;
open my $log, '>', File::Spec->catfile(
Xchat::get_info('xchatdir'),
"$name.log"
)
or die "Can't open $name.log: $!";
$log->autoflush();
sub LOG { print $log scalar localtime, ": ", @_, "\n" }
LOG 'starting up...';
use Data::Dumper;
=head1 NAME
CBStream Xchat plugin - Handle all the weirdnesses of the IRC-to-Chatt
+erBox
bridge that is FreeNode's #cbstream
=head1 INSTALLATION
First, install the required modules. (See the top of the file for the
+ use
statements.)
Next, copy this file to your Xchat directory. For me, that is ~/.xcha
+t2.
YMMV, especially on Windows. The file should be named 'cbstream.pl',
+though
only the extention is important.
Then create a configuration file in the same directory called "cbstrea
+m.yaml".
It should look like:
---
cbstream:
pmpassword: myreallylongpasswordthatIshouldneverforget
pmuser: Tanktalus
That's the minimal configuration required. Now load the plugin (I thi
+nk
this happens automatically on Xchat's start-up, but you can do it manu
+ally
with the /load command). You're ready to go.
=head1 CONFIGURATION OPTIONS
Other than pmpassword and pmuser, other options include:
=over 4
=item nick-prefix
=item nick-suffix
Here you can customise how the nicks will appear in Xchat. I set thes
+e this
way:
cbstream:
nick-prefix: "["
nick-suffix: "]"
Then nicks will show up like "[tye]" instead of just "tye". This make
+s it
easier for cut&paste for me.
=item ignore_mode
If you have anyone being ignored, you can actually be told when you re
+ceive
a message from them. You can set this mode to C<brief> to get a notic
+e
that a message has been ignored (and from whom), or C<verbose> to not
+only
get the notice, but the entire message they sent.
=back
=head1 WHAT IT DOES
By default, it replaces the nick 'cbstream' on the left of the line in
+ Xchat
with the real nick (see nick-prefix and nick-suffix above). It also e
+nables
some new commands:
=head1 COMMANDS
=over 4
=item cblog
This will do a quick login with the cbstream server, joining the #cbst
+ream-login
channel, posting your plogin command, and then closing the tabs. You
+may
not end up back in the channel you started from.
You only need to do this whenever cbstream is restarted, not just any
+time
you join IRC. You still need to be registered with FreeNode's NickSer
+v.
=item cbig
This will reload the /ignore's from PerlMonks to be used in IRC. Tran
+sfering
back and forth is NOT automatic, and would cause undue stress on the P
+M
server, slowing down your IRC experience.
When you run this command, your system will retrieve your ignore list
+from
Perlmonks, and then resolve each user number to a name. This lookup w
+ill
use a local cache to avoid hitting the server too much for each number
+ to name
lookup. This also means that if you unignore on Perlmonks, the user m
+ay
still show up in the cbstream->ignore->byid fields in your conf file.
+ This
is normal and won't interfere with anything.
=item cbignore
This will add an ignore, and save it to your PM user for use with othe
+r
CB applications (such as Full Page Chat).
=item cbunignore
This will remove an ignore, and remove it from your PM user as well.
=item cbmsg
This will send a /msg to whomever you specify. Any private response w
+ill
not be seen in IRC.
=item cbset
This will set a configuration value. Note that there's no validation
+here:
you can set anything you want.
/cbset foo bar
This will persist via the config file for future use.
=item cbget
This will retrieve a configuration value from the config file.
=item cbrm
This will remove a configuration key from the config file.
=back
=head1 TODO
* validation for cbset - ensuring you're setting real keys to allowed
+values
=cut
{
use File::Spec;
my $conf;
my $conf_file = File::Spec->catfile(Xchat::get_info('xchatdir'), $
+name . '.yaml');
sub _conf { $conf ||= LoadFile($conf_file); $conf }
sub get_conf
{
my @var = @_;
Dive(_conf, @var);
}
sub set_conf
{
my $val = pop;
DiveVal(_conf, @_) = $val;
}
sub rm_conf
{
my $val = pop;
my $rm = Dive(_conf, @_);
if ($rm)
{
delete $rm->{$val};
}
}
sub save_conf
{
DumpFile($conf_file, $conf) if $conf;
}
}
Xchat::register($name, $version);
Xchat::hook_command('cblog', sub { Xchat::command 'join #cbstream-logi
+n'; Xchat::EAT_ALL });
Xchat::hook_print('You Join', \&JoinCBStreamLogin);
Xchat::hook_server('PRIVMSG', \&LeaveCBStreamLogin);
# once logged in, tell cbstream what our id and pw is.
sub JoinCBStreamLogin
{
my $info = shift;
if (Xchat::get_info('network') eq 'FreeNode')
{
if ($info->[1] eq '#cbstream-login')
{
my $id = get_conf('cbstream','pmuser');
my $pw = get_conf('cbstream','pmpassword');
if ($id and $pw)
{
Xchat::set_context('#cbstream-login');
Xchat::command("say plogin $id $pw");
}
return Xchat::EAT_NONE;
}
}
Xchat::EAT_NONE;
}
sub LeaveCBStreamLogin
{
my $msg = shift;
my $nth = shift;
if (Xchat::get_info('network') eq 'FreeNode')
{
if ($msg->[0] =~ /^:cbstream!/ and
$msg->[1] eq 'PRIVMSG' and
lc $msg->[2] eq lc Xchat::get_info('nick')
)
{
if ($nth->[3] =~ /You are now persistently logged in as pe
+rlmonks user/)
{
Xchat::set_context('cbstream');
Xchat::command('close');
Xchat::set_context('#cbstream-login');
Xchat::command('close');
Xchat::set_context('#cbstream');
(my $realmsg = $nth->[3]) =~ s/^:\+//;
Xchat::print("CBLOGIN: $realmsg");
return Xchat::EAT_ALL;
}
else
{
Xchat::set_context('#cbstream');
(my $realmsg = $nth->[3]) =~ s/^:\+//;
Xchat::print("CB: $realmsg");
return Xchat::EAT_ALL;
}
}
}
return Xchat::EAT_NONE;
}
# check for ignored users...
Xchat::hook_command('cbig', \&get_ignored);
sub get_ignored
{
Xchat::print("Gathering ignores from Perlmonks");
rm_conf(qw(cbstream ignore byname));
require URI::Escape;
my $user = URI::Escape::uri_escape(get_conf('cbstream', 'pmuser'))
+;
my $pass = URI::Escape::uri_escape(get_conf('cbstream', 'pmpasswor
+d'));
my $me = LWP::Simple::get("http://www.perlmonks.org/index.pl?op=lo
+gin;user=$user;passwd=$pass;displaytype=xml;ticker=yes;node=$user");
my $twig = XML::Twig->new();
$twig->parse($me);
my @elt = $twig->get_xpath('//var[@name="ignoredusers"]');
if (@elt)
{
my $ignored = $elt[0]->text();
my @uids = ($ignored =~ /\|(\d+),/g);
my @users = map {
my $nick = get_conf(qw(cbstream ignore byid), $_) || do {
my $xml = LWP::Simple::get("http://www.perlmonks.org/i
+ndex.pl?displaytype=xml;node_id=$_");
my $nicktwig = XML::Twig->new();
$nicktwig->parse($xml);
(my $user = ($nicktwig->get_xpath('//author'))[0]->tex
+t()) =~ s/^\s+//;
$user =~ s/\s+$//;
set_conf(qw(cbstream ignore byid), \$_, $user);
$user;
};
set_conf(qw(cbstream ignore byname), $nick, $_);
$nick;
} @uids;
if (@users)
{
Xchat::print("Ignoring: @users");
}
else
{
Xchat::print("Ignoring no one");
}
save_conf();
}
else
{
Xchat::print "no one being ignored (maybe failed to log in?)";
}
Xchat::EAT_ALL;
}
Xchat::hook_command('cbignore', \&add_ignore);
sub add_ignore
{
my $person = $_[1][1];
return Xchat::EAT_ALL unless $person;
$person =~ s/^\[(.*)\]/$1/;
Xchat::print "Adding [$person] to PM ignores";
Xchat::command "say /ignore [$person]";
set_conf(qw(cbstream ignore byname), $person, -1);
Xchat::EAT_ALL;
}
Xchat::hook_command('cbunignore', \&rm_ignore);
sub rm_ignore
{
my $person = $_[1][1];
return Xchat::EAT_ALL unless $person;
$person =~ s/^\[(.*)\]/$1/;
Xchat::print "Removing [$person] from PM ignores";
Xchat::command "say /unignore [$person]";
rm_conf(qw(cbstream ignore byname), $person);
Xchat::EAT_ALL;
}
Xchat::hook_command('cbmsg', \&cb_msg);
sub cb_msg
{
my $msg = shift;
my $nth = shift;
Xchat::print "Sending $nth->[1] (don't expect a response here)";
Xchat::command "say /msg $nth->[1]";
Xchat::EAT_ALL;
}
Xchat::hook_server('PRIVMSG', \&rewrite_cb, { priority => Xchat::PRI_L
+OW });
sub rewrite_cb
{
my $msg = shift;
my $nth = shift;
if (Xchat::get_info('network') eq 'FreeNode')
{
if ($msg->[0] =~ /^:cbstream!/ and
$msg->[1] eq 'PRIVMSG' and
$msg->[2] eq '#cbstream' and
1
)
{
my $prefix = get_conf('cbstream','nick-prefix') || '';
my $suffix = get_conf('cbstream','nick-suffix') || '';
(my $alias) = $nth->[3] =~ /^\S*\[(.*?)\]/;
(my $safealias = $alias) =~ s/\s/_/g;
(my $newmsg = $nth->[0]) =~ s/^:cbstream!/:$prefix$safeali
+as$suffix!/;
$newmsg =~ s/\[\Q$alias\E\]\s+//;
# check if it's someone we think we're ignoring...
if (! get_conf(qw(cbstream ignore byname), $alias) )
{
# if it's an action, we need to convert it to such.
$newmsg =~ s[:\+/me (.*)][:\001ACTION $1\001];
Xchat::command("recv $newmsg");
}
elsif (my $mode = get_conf(qw(cbstream ignore_mode)))
{
if ($mode eq '1' or $mode eq 'brief')
{
Xchat::print("$alias said something, but we're ign
+oring them.");
}
elsif ($mode eq '2' or $mode eq 'verbose')
{
$newmsg =~ s[^.*:\+][];
Xchat::print("$alias said '$newmsg', but we're ign
+oring them.");
}
}
Xchat::EAT_ALL;
}
}
}
Xchat::hook_command('cbset', \&cb_set);
sub cb_set
{
my $msg = shift;
my $nth = shift;
set_conf('cbstream', @{$msg}[1..$#$msg]);
Xchat::print ("set " .
join(' ', map { "[$_]" } @{$msg}[1..$#$msg-1]) .
" to {" . $msg->[-1] . "}");
save_conf();
Xchat::EAT_ALL;
}
Xchat::hook_command('cbget', \&cb_get);
sub cb_get
{
my $msg = shift;
my $nth = shift;
my $val = get_conf('cbstream', @{$msg}[1..$#$msg]);
if (defined $val)
{
if ($nth->[1] =~ /password/)
{
$val = '(not displayed)';
}
else
{
$val = "{ $val }"
}
}
else
{
$val = '<not defined>';
}
Xchat::print(join(' ', map { "[$_]" } @{$msg}[1..$#$msg]) .
" = $val");
Xchat::EAT_ALL;
}
Xchat::hook_command('cbrm', \&cb_rm);
sub cb_rm
{
my $msg = shift;
my $nth = shift;
rm_conf('cbstream', @{$msg}[1..$#$msg]);
Xchat::print('deleted ' .
join(' ', map { "[$_]" } @{$msg}[1..$#$msg]));
save_conf();
Xchat::EAT_ALL;
}
As you may see, this uses YAML::Syck - I'm sure a few minor changes could get it to use YAML instead, or any other configuration you want. The configuration file, currently called "cbstream.yaml" in the same directory, looks like this:
---
cbstream:
pmuser: Tanktalus
pmpassword: somelongpasswordthatI'llneverforgetIhope
If you want to have a prefix in front of each alias (the #xchat gurus suggested "cb|" so you could tell the difference), you can add "
nick-prefix: cb|" at the end, and that should work. I had that for a while (well, I had it hard-coded), but eventually decided against it (so I made it optional).
Update 1: Fixed LeaveCBStreamLogin to not print out the :+ leading part of the message from cbstream acknowledging login (cosmetic), and allow user to specify prefix AND suffix for nicks. I've set this in my yaml file now:
nick-prefix: "["
nick-suffix: "]"
and nicks show up with the []'s, which makes it better for cut&paste, and also helps differentiate between what I send ("
Tanktalus") and what I receive from cbstream ("
[Tanktalus]").
Update 2: replaced with version 0.03: now with /cbig to load the ignores from perlmonks. Lots of new prereqs. And some POD to help.
Update 3: replaced with version 0.04: now with /cbignore and /cbunignore, and fix for Your Mother (and any other nicks with spaces).
Update 4: replaced with version 0.05: now with /cbset, /cbget, /cbrm for storing values from one invocation to another.
Update 5: replaced with version 0.06: now with /cbmsg.
Update 6: replaced with version 0.07: bug fixes (/cbmsg was causing Xchat to die, /cbig was broken).
Update 7: replaced with version 0.08: bug fix (cLive ;-) was causing a regex problem - fixed by using \Q...\E).