<?xml version="1.0" encoding="windows-1252"?>
<node id="82809" title="Unix text-mode CB Client" created="2001-05-24 03:57:11" updated="2005-08-14 20:49:27">
<type id="121">
perlcraft</type>
<author id="47599">
mr.nick</author>
<data>
<field name="doctext">
#!/usr/bin/perl -w 

 
##  
## pmchat by Nicholas J. Leon ala mr.nick (nicholas@binary9.net) 
##                                    http://www.mrnick.binary9.net 

## A text mode client for the Chatter Box of Perl Monks 
## this is not an attempt to be complete, but small and useful 
## Use it or not. No guaranteee, no warranty, blah blah 

## Now supports Win32 installations with a different ReadLine
## call.

## Autoupdate now actually autoupdates

## Oh, and it has no error checking :) 


my $ID='$Id: pmchat,v 1.42 2001/06/03 17:49:22 nicholas Exp $'; #'
 
use strict; 
use XML::Simple; 
use LWP::Simple; 
use LWP::UserAgent; 
use HTTP::Cookies; 
use HTTP::Request::Common; 
use Data::Dumper; 
use Text::Wrap qw($columns wrap); 
use Term::ReadLine; 
use Term::ReadKey qw(GetTerminalSize); 
use HTML::Parser;
use File::Copy;
 
$|++; 

my $pm='http://www.perlmonks.org/index.pl'; 
my $cookie="$ENV{HOME}/.pmcookie"; 
my $cffile="$ENV{HOME}/.pmconfig"; 
my %config=( 
            timestamp =&gt; 0, 
            colorize =&gt; 1, 
            browser =&gt; '/usr/bin/lynx %s',
            newnodes =&gt; 25,
            updateonlaunch =&gt; 0,
            timeout =&gt; 15,
           ); 
 
my %seenmsg; 
my %seenprv; 
my %xp;
my $ua;
 
## some color stuff (if you want) 
my %colormap= 
  (  
   node =&gt; [ "\e[33m", "\e[0m" ], 
   user =&gt; [ "\e[1m", "\e[0m" ], 
   code =&gt; [ "\e[32m", "\e[0m" ], 
   me =&gt; [ "\e[36m", "\e[0m" ], 
   private =&gt; [ "\e[35m","\e[0m" ],
   important =&gt; [ "\e[1;34m","\e[0m" ],
  ); 

## &lt;readmore&gt;
##############################################################################
##############################################################################

sub writeconfig { 
  unless (open(OUT,"&gt;$cffile")) { 
    warn "Couldn't open '$cffile' for writing: $!\n"; 
    return; 
  } 

  print OUT "$_ $config{$_}\n" for keys %config; 

  close OUT; 
} 
sub readconfig { 
  unless (open(IN,$cffile)) { 
    warn "Couldn't open '$cffile' for reading: $!\n"; 
    return; 
  } 
  
  %config=(%config,(map /^([^\s]+)\s+(.+)$/,&lt;IN&gt;));
  
  close IN; 
} 

## testing ... autoupdate
sub autoupdate {
  my $quiet=shift;
  my $r=$ua-&gt;request(GET "http://www.mrnick.binary9.net/pmchat/version");
  my($ver)=$r-&gt;content=~/^([\d\.]+)$/;
  my($this)=$ID=~/,v\s+([\d\.]+)/;
  
  print "This version is $this, the current version is $ver.\n" unless $quiet;

  if ($this &gt;= $ver) {
    print "There is no need to update.\n" unless $quiet;
    return;
  }

  print "A new version is available, $ver.\n";

  $r=$ua-&gt;request(GET "http://www.mrnick.binary9.net/pmchat/pmchat");

  my $tmp=$ENV{TMP} || $ENV{TEMP} || "/tmp";
  my $fn="$tmp/pmchat-$ver";

  unless (open (OUT,"&gt;$fn")) {
    print "Unable to save newest version to $fn\n";
    return;
  }

  print OUT $r-&gt;content;
  close OUT;

  ## okay, a couple checks here: we can autoupdate IF the following
  ## are true
  if ($^O=~/win32/i) {
    print "Sorry, autoupdate not available for Windows installations.\n";
    print "The newest version has been saved in $tmp/pmchat.$ver.\n";
    return;
  }

  ## moving the old version someplace else 
  if (!move($0,"$0.bak")) {
    print "Couldn't move $0 to $0.bak, aborting.\n";
    print "The newest version has been saved in $fn.\n";
    return;
  }
  ## moving the new version to the old's location
  if (!move($fn,$0)) {
    print "Couldn't move $fn to $0, aborting $!.\n";
    move("$0.bak",$0);
    print "The newest version has been saved in $fn.\n";
    return;
  }
  ## okay! Reload!
  chmod 0755,$0;
  writeconfig;
  exec $0;
}
  

##############################################################################
##############################################################################

sub colorize {
  my $txt=shift;
  my $type=shift;

  return $txt unless $config{colorize};
  return $txt if $^O=~/win32/i;

  "$colormap{$type}[0]$txt$colormap{$type}[1]";
}

sub user {
  colorize(shift,"user");
}
sub imp {
  colorize(shift,"important");
}  
sub content {
  my $txt=shift;

  return $txt unless $config{colorize};
  return $txt if $^O=~/win32/i;

  unless ($txt=~s/\&lt;code\&gt;(.*)\&lt;\/code\&gt;/$colormap{code}[0]$1$colormap{code}[1]/mig) {
    $txt=~s/\[([^\]]+)\]/$colormap{node}[0]$1$colormap{node}[1]/g;
  }

  $txt;
}
##############################################################################
##############################################################################

sub cookie {
  $ua-&gt;cookie_jar(HTTP::Cookies-&gt;new());
  $ua-&gt;cookie_jar-&gt;load($cookie);
}

sub login {
  my $user; 
  my $pass; 
  
  ## fixed &lt;&gt; to &lt;STDIN&gt; via merlyn
  print "Enter your username: "; chomp($user=&lt;STDIN&gt;); 
  print "Enter your password: "; chomp($pass=&lt;STDIN&gt;); 
  
  $ua-&gt;cookie_jar(HTTP::Cookies-&gt;new(file =&gt; $cookie, 
                                     ignore_discard =&gt; 1, 
                                     autosave =&gt; 1, 
                                    ) 
                 ); 
  
  my $r=$ua-&gt;request( POST ($pm,[  
                                 op=&gt; 'login',  
                                 user=&gt; $user,  
                                 passwd =&gt; $pass, 
                                 expires =&gt; '+1y',  
                                 node_id =&gt; '16046'  
                                ])); 
}

sub xp { 
    my $r=$ua-&gt;request(GET("$pm?node_id=16046")); 
    my $xml=XMLin($r-&gt;content); 
    
    $config{xp}=$xml-&gt;{XP}-&gt;{xp} unless defined $config{xp};
    $config{level}=$xml-&gt;{XP}-&gt;{level} unless defined $config{level};


    print "\nYou are logged in as ".user($xml-&gt;{INFO}-&gt;{foruser}).".\n"; 
    print "You are level $xml-&gt;{XP}-&gt;{level} ($xml-&gt;{XP}-&gt;{xp} XP).\n"; 
    if ($xml-&gt;{XP}-&gt;{level} &gt; $config{level}) {
      print imp "You have gained a level!\n";
    }
    print "You have $xml-&gt;{XP}-&gt;{xp2nextlevel} XP left until the next level.\n"; 

    if ($xml-&gt;{XP}-&gt;{xp} &gt; $config{xp}) {
      print imp "You have gained ".($xml-&gt;{XP}-&gt;{xp} - $config{xp})." experience!\n";
    }
    elsif ($xml-&gt;{XP}-&gt;{xp} &lt; $config{xp}) { 
      print imp "You have lost ".($xml-&gt;{XP}-&gt;{xp} - $config{xp})." experience!\n"; 
    }                               

    ($config{xp},$config{level})=($xml-&gt;{XP}-&gt;{xp},$xml-&gt;{XP}-&gt;{level});

    print "\n"; 
  } 
 
sub who { 
  my $req=GET("$pm?node_id=15851"); 
  my $res=$ua-&gt;request($req); 
  my $ref=XMLin($res-&gt;content,forcearray=&gt;1); 
 
  print "\nUsers current online (";
  print $#{$ref-&gt;{user}} + 1;
  print "):\n";

  print wrap "\t","\t",map { user($_-&gt;{username})." " } @{$ref-&gt;{user}};

  print "\n";
} 
 
sub newnodes { 
  my $req=GET("$pm?node_id=30175"); 
  my $res=$ua-&gt;request($req); 
  my $ref=XMLin($res-&gt;content,forcearray=&gt;1); 
  my $cnt=1; 
  my %users=map { ($_-&gt;{node_id},$_-&gt;{content}) } @{$ref-&gt;{AUTHOR}}; 
  
  print "\nNew Nodes:\n";
  
  if ($ref-&gt;{NODE}) {
    for my $x (sort { $b-&gt;{createtime} &lt;=&gt; $a-&gt;{createtime} } @{$ref-&gt;{NODE}}) { 
      print wrap "\t","\t\t", 
      sprintf("%d. [%d] %s by %s (%s)\n",$cnt,
              $x-&gt;{node_id},$x-&gt;{content},
              user(defined $users{$x-&gt;{author_user}} ? $users{$x-&gt;{author_user}}:"Anonymous Monk"),
              $x-&gt;{nodetype});
      last if $cnt++==$config{newnodes}; 
    } 
  }
  print "\n";
  
} 

##############################################################################
##############################################################################

sub showmessage {
  my $msg=shift;
  my $type=shift || '';
  
  for my $k (keys %$msg) {
    $msg-&gt;{$k}=~s/^\s+|\s+$//g
  }

  print "\r";
  
  if ($type eq 'private') {
    print wrap('',"\t",
               ($config{timestamp}?sprintf "%02d:%02d:%02d/",(unpack("A8A2A2A2",$msg-&gt;{time}))[1..3]:'').
               colorize("$msg-&gt;{author} says $msg-&gt;{content}","private").
               "\n");
  }
  else {
    if ($msg-&gt;{content}=~s/^\/me\s+//) {
      print wrap('',"\t",
                 ($config{timestamp}?sprintf "%02d:%02d:%02d/",(unpack("A8A2A2A2",$msg-&gt;{time}))[1..3]:'').
                 colorize("$msg-&gt;{author} $msg-&gt;{content}","me"),
                 "\n");
    }
    else {
      print wrap('',"\t",
                 ($config{timestamp}?sprintf "%02d:%02d:%02d/",(unpack("A8A2A2A2",$msg-&gt;{time}))[1..3]:'').
                 colorize($msg-&gt;{author},"user").
                 ": ".
                 content($msg-&gt;{content}).
                 "\n");
    }
  }
}
             

sub getmessages { 
  my $req=GET("$pm?node_id=15834"); 
  my $res=$ua-&gt;request($req); 
  my $ref=XMLin($res-&gt;content, forcearray=&gt;1 ); 
  
  if (defined $ref-&gt;{message}) { 
    for my $mess (@{$ref-&gt;{message}}) { 
      ## ignore this message if we've already printed it out 
      next if $seenmsg{"$mess-&gt;{user_id}:$mess-&gt;{time}"}++; 

      showmessage $mess; 
    } 
  } 
  else { 
    ## if there is nothing in the list, reset ours 
    undef %seenmsg; 
  } 
} 

sub getprivatemessages { 
  my $req=GET("$pm?node_id=15848"); 
  my $res=$ua-&gt;request($req); 
  my $ref=XMLin($res-&gt;content,forcearray=&gt;1); 
  
  if (defined $ref-&gt;{message}) { 
    for my $mess (@{$ref-&gt;{message}}) { 
      ## ignore this message if we've already printed it out 
      next if $seenprv{"$mess-&gt;{user_id}:$mess-&gt;{time}"}++; 
 
      showmessage $mess,"private"; 
    } 
  } 
  else { 
    undef %seenprv; 
  } 
} 

sub postmessage { 
  my $msg=shift; 
  my $req=POST ($pm,[ 
                     op=&gt;'message', 
                     message=&gt;$msg, 
                     node_id=&gt;'16046', 
                    ]); 
  
  $ua-&gt;request($req); 
} 

sub node {
  my $id=shift;

  system(sprintf($config{browser},"$pm?node_id=$id"));
}

sub help {
  print &lt;&lt;EOT
The following commands are available:
    /help         :: Shows this message
    /newnodes     :: Displays a list of the newest nodes (of all types)
                     posted. The number of nodes displayed is limited by
                     the "newnodes" user configurable variable.
    /node ID      :: Retrieves the passed node and launches your user
                     configurable browser ("browser") to view that node.
    /reload       :: UNIX ONLY. Restarts pmchat.
    /set          :: Displays a list of all the user configurable
                     variables and their values.
    /set X Y      :: Sets the user configurable variable X to
                     value Y.
    /update       :: Checks for a new version of pmchat, and if it
                     exists, download it into a temporary location.
                     This WILL NOT overwrite your current version.
    /quit         :: Exits pmchat
    /who          :: Shows a list of all users currently online
    /xp           :: Shows your current experience and level.
EOT
  ;
}

##############################################################################
##############################################################################
my $old;
my $term=new Term::ReadLine 'pmchat';

sub getlineUnix {
  my $message;

  eval {
    local $SIG{ALRM}=sub { 
      $old=$readline::line; 
      die 
    };
    
    ## I don't use the version of readline from ReadKey (that includes a timeout)
    ## because this version stores the interrupted (what was already typed when the
    ## alarm() went off) text in a variable. I need that so I can restuff it 
    ## back in.

    alarm($config{timeout}) unless $^O=~/win32/i;
    $message=$term-&gt;readline("Talk: ",$old);
    $old=$readline::line='';
    alarm(0) unless $^O=~/win32/i;
  };    

  $message;
}

sub getlineWin32 {
  my $message=ReadLine($config{timeout});

  ## unfortunately, there is no way to preserve what was already typed
  ## when the timeout occured. If you are typing when it happens,
  ## you lose your text.

  $message;
}

## initialize our user agent
$ua=LWP::UserAgent-&gt;new;
$ua-&gt;agent("pmchat-mrnick"); 

## trap ^C's
## for clean exit
$SIG{INT}=sub { 
  writeconfig;
  exit 
};

## load up our config defaults
readconfig;

## for text wrapping
$columns=(Term::ReadKey::GetTerminalSize)[0] || $ENV{COLS} || $ENV{COLUMNS} || 80;

if (-e $cookie) {
  cookie;
}
else {
  login;
}

my($this)=$ID=~/,v\s+([\d\.]+)/;

print "This is pmchat version $this.\n";

autoupdate(1) if $config{updateonlaunch};
xp();
print "Type /help for help.\n";
who();
newnodes();
getprivatemessages;
getmessages();


while (1) {
  my $message;

  getprivatemessages;
  getmessages;
  
  if ($^O=~/win32/i) {
    $message=getlineWin32;
  }
  else {
    $message=getlineUnix;
  }

  if (defined $message) {
    ## we understand a couple of commands
    if ($message=~/^\/who/i) {
      who;
    }
    elsif ($message=~/^\/quit/i) {
      writeconfig;
      exit;
    }
    elsif ($message=~/^\/set\s+([^\s]+)\s+(.+)$/) {
      $config{$1}=$2;
      print "$1 is now $2\n";
    }
    elsif ($message=~/^\/set$/) {
      for my $k (sort keys %config) {
        printf "\t%-10s %s\n",$k,$config{$k};
      }
    }
    elsif ($message=~/^\/new\s*nodes/) {
      newnodes;
    }
    elsif ($message=~/^\/xp/) {
      xp;
    }
    elsif ($message=~/^\/node\s+(\d+)/) {
      node($1);
    }
    elsif ($message=~/^\/help/) {
      help;
    }
    elsif ($message=~/^\/reload/) {
      print "Reloading $0!\n";
      writeconfig;
      exec $0;
    }
    elsif ($message=~/^\/update/) {
      autoupdate;
    }
    elsif ($message=~/^\/me/ || $message=~/^\/msg/) {
      postmessage($message);
    }
    elsif ($message=~/^\//) {
      print "Unknown command '$message'.\n";
    }
    else {
      postmessage($message);
    }
  }
}</field>
</data>
</node>
