<?xml version="1.0" encoding="windows-1252"?>
<node id="61227" title="RedWolf MonkChatter" created="2001-02-27 22:28:52" updated="2005-08-15 13:25:00">
<type id="1748">
sourcecode</type>
<author id="13922">
strredwolf</author>
<data>
<field name="doctext">
&lt;code&gt;
#!/usr/bin/perl

### Modules
use Tk;
use IO::Socket;

### Win32 junk
BEGIN {
  my($win32)=1 if($^O eq 'MSWin32');
  
  if($win32) {
    eval 'use Win32::Shell';
    die "$@\n" if($@);
  }
}

### Configurables
$maxlines=500;
$blanknode=14532;
$delaysec=10;
# $proxy='';

### Code starts here
# HTTP request (w/o LWP's complexity)
sub httpreq {
  my($method,$url,$headers,$content)=@_;
  my($site,$dir,$port,$remote,$req,$result,$code,@top,$bot,$sock);
  my($e)="\r\n";
  
#  if(defined $proxy) {
#    print "Through proxy....";
#    $proxy=~ m#^http://([^/]+)(/.*)?$#i;
#  } else {
    $url=~ m#^http://([^/]+)(/.*)?$#i;
#  }
  $site=$1;$dir=$2;
  $dir="/" unless($dir);
  
  if($site =~ /^([^:]+):(\d+)$/)
    {	
      $remote=$1; $port=$2;
    } else {
      $remote=$site; $port=80;
    }
  
#  if(defined $proxy) {
#    $req="\U$method\E $url HTTP\\1.0$e";
#  } else {    
    $req="\U$method\E $dir HTTP\\1.0$e";
#  }

  foreach $j (@$headers)
    {
      $req .= "$j$e";
    }
  
  if($method =~ /POST/i)
    {
      my($l)=length $content;
      $req .= "Content-Length: $l$e$e";
      $req .= $content;
    } else {
      $req .= $e;
    }
  
  $sock=IO::Socket::INET-&gt;new(
			      Proto =&gt; "tcp",
			      PeerAddr =&gt; $remote,
			      PeerPort =&gt; $port)
    or return ''; #Nothing
  $sock-&gt;autoflush(1);
  
  print $sock $req;
  $code=&lt;$sock&gt;;
  my($i)=0;
  while(&lt;$sock&gt;)
    {
      tr/\r\n//d;
      last if(/^$/);
      $top[$i++]=$_;
      $main-&gt;update;
    }
  $bot='';
  while(&lt;$sock&gt;) { $bot .= $_; $main-&gt;update; }
  
  ($code,$bot,@top);
}

### XML minipharser
# Splits a message up along XML tags.  Returns the message in an array.
sub splitxml {

  my($d,$fl,@j)=@_;
  my(@tags,$i,$last);
  
  while($d)
    {
      "" =~ /(.?)/;
      if($d =~ s/^(&lt;[^&gt;]+&gt;)//s) {
	$tags[$i++]=$1;
      } elsif( $fl &amp;&amp; $d =~ s/^(\[[^\]]+\])//s ) {
	$tags[$i++]=$1;
      }	else {
       	$d=~s/^([^&lt;]+)//s unless($fl);
	$d=~s/^([^&lt;\[]+)//s if($fl);
	if(length $1) {
	  $tags[$i++]=$1;
	} else {
	  $d.="&gt;" if($d=~/^\&lt;/);
	  $d.="]" if($d=~/^\[/);
	}
	
      }
    }
  (@tags);
}

# Takes a tag and splits it up into options.
# Returns a tag name and an hash.
sub splittag {
  my($tag)=@_;
  my($i,$j,$k,%opts,@t,$ti);
  
  $tag=~ s/^&lt;//; $tag=~ s/&gt;$//;
  
  $tag=~ s/^(\S+)\s+//; $ti=$1;
  
  while($tag) {
    $tag =~ s/^([^=]+)(=[\"\']([^\"\']+)[\"\']\s?)?//;
    $opts{$1}=($2 ? $3 : 1);
  }
  ($ti, %opts);
}

# Pharses a line and translantes all those &amp;amp; thingies...
sub xlateamp {
  my($l,@j)=@_;
  
  $l=~ s/&amp;lt;/&lt;/g;
  $l=~ s/&amp;gt;/&gt;/g;
  $l=~ s/&amp;amp;/&amp;/g;
  $l=~ s/&amp;#(\d+);/chr($1)/eg;
  
  return($l);
}

###
# URL launcher
sub do_url {
  my($tag)=@_;
  my($i,$h);
  
  if($tag =~ /^url(\d+)/) {
    $i=$1;
    $h=$url[$i];
    
    $h=~ s#^id:\/\/(\d+)$#http:\/\/perlmonks.org\/index.pl?node_id=$1#;
    if($h !~ /^http:\/\// ) {
      $h =~ s/([^\w ])/sprintf("%%%02X",ord($1))/eg;
      $h =~ s/ /+/g;
      $h="http://perlmonks.org/index.pl?node=$h";
    }
    
    if($win32) {
      Win32::Shell::Execute("open", $h, undef,undef, "SW_SHOWNORMAL");
    } else {
      system("netscape -remote 'openURL($h)'");
    }
  }
}

# statnode
sub statnode {
  my($node)=@_;
  my($i,$j,$k,$l,$fl);
  my($cd,$doc,@head)=&amp;httpreq("GET",
			      "http://perlmonks.org/index.pl?node=node+query+xml+generator&amp;nodes=$node",
			      ["Cookie: $cookie"], "");
  
  my(@lines)=splitxml($doc);
  $fl=0;
  for($i=0;$i&lt;@lines;$i++) {
    $l=$lines[$i];
    $l=~ tr/\n\r//d;
    $l=~ s/^\s+//;
    $fl++ if($l=~/^&lt;node/i);
    $fl=0 if($l=~/^&lt;\/node/i);
    $k=$l if($fl);
  }
  
  $k="id://$node" unless($k);
  return($k);
}

# LinePharzer
sub do_add {
  my($msg)=@_;
  
  if($raw)
    {
      $main_index_list-&gt;insert("end","\n$msg");
    } else {
      my($i,$j,$k,$l,%tags,$op);
      my(@lines)=splitxml($msg,1);
      
      $main_index_list-&gt;insert("end","\n");
      $op='';
      for($i=0;$i&lt;scalar @lines;$i++)
	{
	  $l=$lines[$i];
	  
	  $tags{'code'}++, next if($l=~/^&lt;\s*code/i &amp;&amp; !$tags{'code'});
	  $tags{'code'}=$op='', next if($l=~/^&lt;\/code/i);
	  
	  unless($tags{'code'})
	    {
	      if($l=~/^\[([^\]]+)\]/) {
		$j=$1; $k=$1;
		
		if($j=~/^id:\/\/(\d+)$/) {
		  $k=statnode($1);
		}
		
		$j=$1, $k=$2 if($j=~/^([^\|]+)\|(.+)$/);
		
		$url[$uc]=$j; $l=$k; $op="url$uc"; $uc++;
		
		$main_index_list-&gt;insert("end",$l,$op);
		$main_index_list-&gt;tagConfigure($op,-foreground=&gt;"blue",
					       -data=&gt;$j,
					       -underline=&gt;1);
		# merlyn code...
		$main_index_list-&gt;tagBind($op, "&lt;1&gt;", do { my $thing = $op; sub { do_url($thing) } } );
		$op='';
		next;
	      } 
	      if($l=~/^&lt;a/i) {
		my($t,%opt)=splittag($l);		    
		$url[$uc]=$opt{'href'}; $tags{'a'}=$op="url$uc"; $uc++;
		$main_index_list-&gt;insert("end",'',$op);
		$main_index_list-&gt;tagConfigure($op,-foreground=&gt;"blue",
					       -data=&gt;$j,
					       -underline=&gt;1);
		# merlyn code...
		$main_index_list-&gt;tagBind($op, "&lt;1&gt;", do { my $thing = $op; sub { do_url($thing) } } );
		next;
	      }
	      if($l=~/^&lt;\/a/i) {
		$tags{'a'}=$op='';
		next;
	      }
	      $l=xlateamp($l);
	    }
	  
	  $main_index_list-&gt;insert("end",$l,$op) if($op);
	  $main_index_list-&gt;insert("end",$l) unless($op);
	}
    }
}

# Do an update of the tags.
sub do_update {
  return if($uplock);
  $uplock=1;
  $main_bot_stat-&gt;configure(-text=&gt;"Updating...");
  my($c,$d,@h)=&amp;httpreq("GET",
			"http://perlmonks.org/index.pl?node=chatterbox+xml+ticker",
			"","");
  my($i,$l,$t,$au,$bt,$m,$fl,$uid,$ti,%opts);

  if($c =~ /200 OK/ )
    {
      my(@lines)=splitxml($d);
      $fl=0;
      for($i=0;$i&lt;@lines;$i++)
	{
	  $l=$lines[$i];
	  $l=~ tr/\n\r//d;
	  $l=~ s/^\s+//;
	  if($l=~ /^&lt;message/)
	    {
	      $au=$t=$uid='';
	      ($ti,%opts)=splittag($l);
	      $au=$opts{'author'};
	      $t=$opts{'time'};
	      $uid=$opts{'user_id'};
	      $fl=1; $t=~ s/^....//;
	      $t=~ /^....(..)(..)/;$t+=0;
	      $m="$1:$2: [id://$uid|$au]";
	      next;
	    } 
	    
	  if( $l =~ /^&lt;\/message&gt;/ )
	    {
	      $t+=.01 while($msg{$t} &amp;&amp; $msg{$t} ne $m);
	      $msg{$t}=$m;
	      $bt=$t; $fl=0;
	      next;
	    }
	  
	  if( $fl==1 )
	    {
	      $l=xlateamp($l);
	      if( $l=~ /^\/me(.+)$/) { $m.=$1; } else { $m.=": $l"; }
	      $fl++;
	    } elsif ($fl&gt;1) {
	      $m .= xlateamp($l);
	    }
	}
      
      if($prv) {
	if($justin) {
	  $justin=0;
	  $j=(int $lasttime)+.01;
	  foreach $z (sort keys %pmsg)
	    {
	      $msg{$j}=$pmsg{$z};
	      $bt=$j; $j+=.01;
	    }
	}
	foreach $j (sort keys %pmsg) {
	  $i=$j; $i+=.01 while($msg{$i} &amp;&amp; $msg{$i} ne $pmsg{$j});
	  $msg{$i}=$pmsg{$j};
	  $bt=$i if($bt &lt; $i);
	}
	
	$prv=0; %pmsg=();
      }
      
      $bt=$bt+0;
      if($bt &gt; $lasttime)
	{
	  my($f)=0;
	  my(@ti)=sort keys %msg;
	  $lasttime=$ti[0]-1 unless($lasttime);
	  foreach $i (@ti)
	    {
	      
	      $f++ if($i &gt; $lasttime);
	      do_add($msg{$i}) if($f);
	    }
	  $lasttime=$ti[-1]+0;
	  $main_index_list-&gt;see("end") if($f &amp;&amp; $jump);
	}
    }
  $main_bot_stat-&gt;configure(-text=&gt;" ");
  $uplock=0;
}

# We need a cookie for this routine.  It's the XP and Private stuff.
sub do_userprv {
  return 1 unless($in);
  
  # Get the privates...
  my($c,$ml,@h)=&amp;httpreq("GET","http://perlmonks.org/index.pl?node=private+message+xml+ticker",
			 ["Cookie: $cookie"],"");
  my(@lines)=splitxml($ml);
  my($i,$fl,$l,$au,$t,$m,@mid,$mi,$z,$ft,$uid);
  
  for($i=0;$i&lt;@lines;$i++)
    {
      $l=$lines[$i];
      $l=~ tr/\n\r//d;
      $l=~ s/^\s+//;
      if($l=~ /^&lt;message/)
	{
	  $au=$t=$uid='';
	  ($ti,%opts)=splittag($l);
	  $au=$opts{'author'};
	  $t=$opts{'time'};
	  $uid=$opts{'user_id'};
	  $z=$opts{'message_id'};
	  $mid[$mi++]="deletemsg_$z=yup";
	  $fl=1; $t=~ s/^....//;
	  $t=~ /^....(..)(..)/;$t+=0;
	  $m="$1:$2: [id://$uid|$au]";
	  next;
	} 
      if($l=~ /^&lt;message message_id=(.+) author=(.+) time=(.+)&gt;/)
	{
	  $z=$1;
	  $au=$2; $t=$3;
	  $au=~ tr/\"\'//d; $t=~ tr/\"\'//d; 
	  $z=~ tr/\"\'//d; $t=~ s/^....//;
	  $mid[$mi++]="deletemsg_$z=yup";
	  $fl=1; 	
	  $t=~ /^....(..)(..)/; $t+=0;
	  $m="$1:$2: $au";
	  next;
	} 
	
      if( $l =~ /^&lt;\/message&gt;/ )
	{
	  $pmsg{$t}=$m; $prv++;
	  $bt=$t; $fl=0; $ft=$t unless($ft);
	  next;
	}
      
      if( $fl==1 )
	{
	  $l=~ s/&amp;lt;/&lt;/g;
	  $l=~ s/&amp;gt;/&gt;/g;
	  $l=~ s/&amp;amp;/&amp;/g;
	  if( $l=~ /^\/me(.+)$/) { 
	    $m.=" (privately) ";
	  } else { 
	    $m.="&gt; "; 
	  }
	  $m .= $l;
	  $fl++;
	} elsif ($fl&gt;1) {
	  $l=~ s/&amp;lt;/&lt;/g;
	  $l=~ s/&amp;gt;/&gt;/g;
	  $l=~ s/&amp;amp;/&amp;/g;
	  $m .= $l;
	}
    }

    # Now clear 'em...
  my($req)=join "&amp;",@mid;
  $req.="&amp;message=&amp;message_send=talk&amp;op=message";
  ($c,$ml,@h)=&amp;httpreq("POST","http://perlmonks.org/index.pl",
		       ["Cookie: $cookie"], $req)
    if($oktoclear &amp;&amp; $mi);
  
  # Get XP.
  
  ($c,$ml,@h)=&amp;httpreq("GET","http://perlmonks.org/index.pl?node=XP+xml+ticker",["Cookie: $cookie"], "");
  @lines=splitxml($ml);
  for($i=0;$i&lt;@lines;$i++)
    {
      $l=$lines[$i];
      $l=~ tr/\n\r//d;
      $l=~ s/^\s+//;
      if($l =~ /^&lt;XP level=(.+) xp=(.+) xp2nextlevel=(.+) votesleft=(.+)&gt;/)
	{
	  my($lv,$xp)=($1, $2);
	  $lv=~ tr/\'\"//d; $xp=~ tr/\'\"//d;
	  $main_bot_xp-&gt;configure(-text=&gt;"Lev: $lv XP: $xp");
	}
    }
  
}

# Do Login/out
sub do_loginout
{
  my($i,$a,$c,@b);
  unless($in)
    {
      $user=$main_btns_user-&gt;get;
      $pass=$main_btns_pass-&gt;get;
      
      # Log us in...
      ($a,$c,@b)=&amp;httpreq("POST","http://perlmonks.org/index.pl","",
			  "op=login&amp;user=$user&amp;passwd=$pass&amp;expires=%2b10y&amp;login=Login&amp;node_id=$blanknode");
      if($a =~ /200 OK/)
	{
	  foreach $i (@b)
	    {
	      $cookie=$1 if($i =~/Set\-Cookie: (userpass=[^;]+);/ );
	    }
	  
	  if($cookie)
	    {
	      $main_bot_stat-&gt;configure(-text=&gt;"Logged in.");
	      $in++; $justin++;
	      $main_btns_open-&gt;configure(-text=&gt;"Logout");
	      $main_btns_user-&gt;configure(-state=&gt;"disabled");
	      $main_btns_pass-&gt;configure(-state=&gt;"disabled");
	      $main_art_text-&gt;configure(-state=&gt;"normal");
	      do_userprv;
	    }
	}
    } else {
      ($a,$c,@b)= &amp;httpreq("GET",
			   "http://perlmonks.org/index.pl?op=logout&amp;node_id=$blanknode",
			   ["Cookie: $cookie"], "");
      if($a =~ /200 OK/) {
	$cookie=''; $in=0;
	$main_bot_stat-&gt;configure(-text=&gt;"Logged out.");
	$main_btns_open-&gt;configure(-text=&gt;"Login");
	$main_btns_user-&gt;configure(-state=&gt;"normal");
	$main_btns_pass-&gt;configure(-state=&gt;"normal");
	$main_art_text-&gt;configure(-state=&gt;"disabled");
      }
    }
}

# Do some smackdowns.
sub do_chatter {
  my($a,$b,@c,$z);
  my($line)=$main_art_text-&gt;get;
  $main_art_text-&gt;delete(0,end);
  
  $line =~ s/([^\w ])/sprintf("%%%02X",ord($1))/eg;
  $line =~ s/ /+/g;
  
  ($a,$c,@b)=&amp;httpreq("POST","http://perlmonks.org/index.pl",
		      ["Cookie: $cookie"], 
		      "op=message&amp;message=$line&amp;node_id=$blanknode");

  do_update;
}

# Menu handler
sub do_menu {
  $menued=0 if($main_opts-&gt;state ne "normal");
  
  unless($menued) {
    $main_opts-&gt;post($main_btns_opts-&gt;rootx,
			 $main_btns_opts-&gt;rooty+$main_btns_opts-&gt;height);
  } else {
    $main_opts-&gt;unpost;
  }
  $menued=1-$menued;
}

sub do_regen {
  my($i);
  $main_index_list-&gt;delete("1.0", "end");
  
  my(@ti)=sort keys %msg;
  foreach $i (@ti)
    {
      do_add($msg{$i});
    }
  $lasttime=$ti[-1]+0;
  $main_index_list-&gt;see("end");
}

$user=$pass=$line=$cookie=$lasttime='';
$in=$prv=$oktoclear=$justin=$menued=$uc=$uplock=0;
$jump=$raw=1;
@url=();

$main=MainWindow-&gt;new(-height=&gt;320, -width=&gt;240);

$main-&gt;geometry("240x320");

$main_btns = $main-&gt;Frame;
$main_btns_user = $main_btns-&gt;Entry(-width=&gt; 10);
$main_btns_user-&gt;insert(0,"(username)");
$main_btns_pass = $main_btns-&gt;Entry(-show=&gt; "*", -width=&gt; 10);
$main_btns_open = $main_btns-&gt;Button(-text=&gt; "Login",-padx=&gt;1,-pady=&gt;1, 
				     -command=&gt;sub{do_loginout});
$main_btns_opts=$main_btns-&gt;Button(-text=&gt;"&gt;&gt;",-padx=&gt;1,-pady=&gt;1,
				   -command=&gt;sub{do_menu });
$main_btns_opts-&gt;pack(-side=&gt;"right");
$main_btns_open-&gt;pack(-side=&gt;"right");
$main_btns_user-&gt;pack(-anchor=&gt;"w", -side=&gt;"left", -fill=&gt;"x", -expand=&gt;1);
$main_btns_pass-&gt;pack(-anchor=&gt;"w", -side=&gt;"left", -fill=&gt;"x", -expand=&gt;1);
$main_btns-&gt;pack(-anchor=&gt;"w",-side=&gt;"top",-fill=&gt;"x",-expand=&gt;1);

$main_opts=$main-&gt;Menu(-tearoff=&gt;1,-title=&gt;"Options");
$main_opts-&gt;add("command", -label=&gt;"Regenerate", -command=&gt;sub{do_regen});
$main_opts-&gt;add("separator");
$main_opts-&gt;add("checkbutton", -variable=&gt;\$jump, -label=&gt;"Jump on scroll");
$main_opts-&gt;add("checkbutton", -variable=&gt;\$oktoclear,
		-label=&gt;"Autoclear private messages");
$main_opts-&gt;add("checkbutton", -variable=&gt;\$raw,
		-label=&gt;"Don't do fancy formatting");

#$main_opts=$main-&gt;Frame;
#$main_opts_jump=$main_opts-&gt;Checkbutton(-variable=&gt;\$jump, -text=&gt;"Jumpscroll");
#$main_opts_jump-&gt;pack(-anchor=&gt;"w", -side=&gt;"left");
#$main_opts_clear=$main_opts-&gt;Checkbutton(-variable=&gt;\$oktoclear, -text=&gt;"AutoClear PrvMsg");
#$main_opts_clear-&gt;pack(-anchor=&gt;"w",-side=&gt;"left");
#$main_opts-&gt;pack(-anchor=&gt;"e",-side=&gt;"top",-fill=&gt;"x",-expand=&gt;1);

$main_bot=$main-&gt;Frame;
$main_bot_stat=$main_bot-&gt;Label(-text=&gt;"RedWolf MonkChatter");
$main_bot_stat-&gt;pack(-anchor=&gt;"w",-side=&gt;"left");
$main_bot_xp=$main_bot-&gt;Label(-text=&gt;"Lev: 0 XP: 0");
$main_bot_xp-&gt;pack(-anchor=&gt;"e",-side=&gt;"right");
$main_bot-&gt;pack(-anchor=&gt;"s",-side=&gt;"bottom",-fill=&gt;"x",-expand=&gt;1);

$main_index = $main-&gt;Frame;
$main_index_list = $main_index-&gt;Text(-wrap=&gt;"word");
$main_index_list-&gt;configure(-font=&gt;"fixed") unless($win32);
$main_index_scroll = $main_index-&gt;Scrollbar(-width=&gt; 10,
					    -command=&gt; ["yview", $main_index_list]);
$main_index_list-&gt;configure(-yscrollcommand=&gt;['set', $main_index_scroll]);
$main_index_scroll-&gt;pack(-side=&gt;"right",-fill=&gt;"y");
$main_index_list-&gt;pack(-fill=&gt;"both", -expand=&gt;1);

$main_art = $main-&gt;Frame;
$main_art_text = $main_art-&gt;Entry(-state=&gt;"disable");
$main_art_text-&gt;bind("&lt;Return&gt;",sub{do_chatter;});
$main_art_text-&gt;pack(-fill=&gt;'x', -expand=&gt;1);
$main_art-&gt;pack(-fill=&gt;"x", -expand=&gt;1, -anchor=&gt;"w", -side=&gt;"bottom");

$main_index-&gt;pack(-fill=&gt;"both", -anchor=&gt;"w",-side=&gt;"bottom");

do_update;
$main-&gt;repeat($delaysec*1000,sub{do_update});
$main-&gt;repeat(60*1000,sub{do_userprv});

MainLoop;


&lt;/code&gt;
</field>
<field name="codedescription">
A chatterbox client in Perl/TK.  Doesn't need XML or the
PerlMonks module.  Fairly complete now.&lt;P&gt;

This has a newer XML pharser (it's not XML::Simple).  It
also works in Windows.  (Hey!  ActiveState doesn't have Tk
pre-bundled!)  Also, Level and XP stats, Jumpscroll control,
and clearing of the private messages once received (which
will automagically sweep previous items you've seen as a side
benifit).  Also a raw client view and processing of
&lt;code&gt;[jumptags]&lt;/code&gt; and some HTML(CODE and A tags).&lt;P&gt;

&lt;B&gt;To use:&lt;/b&gt; Run as is, and log in via the top line like
the Java Chatterbox client (which it's style borrows from).
Once logged in, hit the "Return" key to send in a line of
chatter on the bottom entryline.  There's several options on
the "&gt;&gt;" menu worth perusing; "Regenerate" will redo the
chatter log.&lt;P&gt;</field>
<field name="codecategory">
Chatterbox Clients</field>
<field name="codeauthor">
[strredwolf]</field>
</data>
</node>
