<?xml version="1.0" encoding="windows-1252"?>
<node id="531067" title="CB60" created="2006-02-17 15:56:46" updated="2006-02-17 10:56:46">
<type id="1748">
sourcecode</type>
<author id="108447">
demerphq</author>
<data>
<field name="doctext">
&lt;b&gt;mini-cb60-fetch.pl&lt;/b&gt;
&lt;code&gt;
#!/usr/bin/perl
#mini-cb60-fetch.pl
use strict;
use warnings;
use vars qw/$VERSION/;
use XML::Simple;
use File::Path;
use LWP::UserAgent;
use HTTP::Cookies;
use Getopt::Long;
use HTTP::Request::Common qw(POST);
use Data::Dumper;

# This is free Software.
# Released under the same terms as Perl.
# Copyright Yves (demerphq) 2006

$VERSION=0.02;

my $ua = LWP::UserAgent-&gt;new();
$ua-&gt;timeout(10);
$ua-&gt;agent("Mini-CB60 $VERSION ");
$ua-&gt;env_proxy;

$|++;
my ( $verbose, $lastid, $sleeptime )= (1, 0, 0);
my $site_url= 'http://perlmonks.org/index.pl';
my $ticker_url= $site_url.'?node_id=207304;xmlstyle=modern';
(my $dir= $0) =~ s![^\\\/]+$!messages/!;

my ($user,$pass)= ('','');

GetOptions(
    'msgdir=s'    =&gt; \$dir,
    'verbose=i'   =&gt; \$verbose,
    'sleeptime=i' =&gt; \$sleeptime,
    'lastid=i'    =&gt; \$lastid,
    'user=s'      =&gt; \$user,
    'pass=s'      =&gt; \$pass,
) or die &lt;&lt;EOFUSAGE;
mini-cb60-fetch.pl --msgdir=PATH --verbose=INT --sleeptime=SECS
                   --user=USER --pass=PASS
--sleeptime of 0 means a single fetch only. Otherwise never halts.
Defaults to --sleeptime of 0 seconds, and ./messages for --msgdir.
EOFUSAGE

if ($sleeptime &amp;&amp; $sleeptime &lt; 60) {
    warn "Theres no need to fetch more often than every 60 seconds!\n";
}

if ( $user &amp;&amp; $pass ) {
    my $req = POST $site_url, [
        op     =&gt; 'login',
        node_id=&gt; 109, # login
        user   =&gt; $user,
        passwd =&gt; $pass,
        ticker =&gt;'yes',
        displaytype=&gt;'xml',
        xmlstyle=&gt;'flat',
    ];
    my $jar=HTTP::Cookies-&gt;new(file =&gt; "$0.cookies", autosave =&gt; 1);
    $ua-&gt;cookie_jar($jar);
    my $response=$ua-&gt;request($req);
    die "Failed to login\n",$response-&gt;as_string
        if ! $response-&gt;is_success;

    my $xml=XMLin( $response-&gt;content );
    if (!$xml-&gt;{loggedin} ||
               $xml-&gt;{loggedin}{username} ne $user
    ) {
        die "Failed to login."
    }
    print "Logged in as $_-&gt;{username} ($_-&gt;{user_id})\n"
        for $xml-&gt;{loggedin};
} elsif ( $user || $pass ) {
    die "Must have both a username and a password to log in\n";
}

# cleanup the path for win32 users, just for aesthetic reasons
$dir =~ s![\\\/]+!\\!g
    if $^O=~/Win32/;

if ( ! -d $dir ) {
    mkpath $dir
        or die "Failed to create '$dir'";
}

if ($verbose) {
    print "Base Url: '$site_url'\n";
    print "Message directory is: '$dir'\n"
}
chdir $dir
    or die "Failed to chdir to '$dir':$!";

while ( 1 ) {
    # first we delete any old messages
    my $threshold= sprintf "%08x-%08x.msg",time() - 3600, 0;
    print "Threshold file is $threshold\n" if $verbose&gt;1;
    my @files=sort glob "*.msg";
    foreach my $f (@files) {
        print "Found $f\n" if $verbose&gt;1;
        next if $f ge $threshold;
        print "Unlinking $f\n" if $verbose;
        unlink $f or warn "Failed to unlink '$f':$!";
    }
    # and try to autodetect where we left off if this is the first run
    if ( ! $lastid &amp;&amp; @files &amp;&amp; -e $files[-1] ) {
        my ($t,$id)=split/[-.]/,$files[-1];
        $lastid= hex($id) if $lastid &lt; hex($id);
    }

    # and now we do the fetch
    my $url= $ticker_url;
    $url .= ";fromid=$lastid" if $lastid;

    print "Fetching from id $lastid\n" if $verbose&gt;1;
    my $response= $ua-&gt;get($url);
    if (!$response-&gt;is_success) {
        print "Fetch '$url' failed!\n";
        next;
    }

    # and now we process the results
    my $xml= XMLin( $response-&gt;content,
                    ForceArray=&gt;['message'] );
    print Dumper($xml) if $verbose&gt;2;

    my $msgs= $xml-&gt;{message}||[];
    my $count= $xml-&gt;{info}{count};
    $lastid= $xml-&gt;{info}{lastid}
        if $count;
    print "Got $count messages -- Last id: $lastid\n"
        if $verbose;
    next unless $count &amp;&amp; $msgs &amp;&amp; @$msgs;

    # each message is written as its own file.
    foreach my $msg (@$msgs) {
        my ($id,$epoch)= @{$msg}{qw(message_id createdepoch)};
        my $authlink= sprintf '&lt;a href="?node_id=%d"&gt;%s&lt;/a&gt;&lt;/i&gt;',
            $msg-&gt;{author_user},$msg-&gt;{author};
        (my $text= $msg-&gt;{parsed})=~s{^/me(\s+.+)?$}{&lt;i&gt;$authlink$1&lt;/i&gt;};

        my $file= sprintf "%08x-%08x.msg",$epoch,$id;
        next if -e $file;
        open my $fh,"&gt;",$file
            or die "Failed to open '$file' for writing:$!";
        print "writing $file\n" if $verbose;
        print $fh "0\n"; # version
        print $fh join "\n",
              "&lt;dt&gt;$authlink&amp;nbsp; &amp;nbsp; &lt;small&gt;$msg-&gt;{createdgmtime} GMT&lt;/small&gt;&lt;/dt&gt;",
              "&lt;dd&gt;$text&lt;/dd&gt;",
              "";
        close $fh;
    }
} continue {
    exit(0) if $sleeptime &lt;= 0; # exit out if this is a single pass
    print "sleeping for $sleeptime seconds....\n" if $verbose&gt;1;
    sleep $sleeptime;
}
&lt;/code&gt;
&lt;hr /&gt;
&lt;b&gt;mini-cb60-render.pl&lt;/b&gt;
&lt;code&gt;
#!/usr/bin/perl
#mini-cb60-render.pl
# This is free Software.
# Released under the same terms as Perl.
# Copyright Yves (demerphq) 2006

use strict;
use warnings;
use CGI ();
use Getopt::Long;
use vars qw/$VERSION/;
$VERSION= 0.02;

############### SET THESE AS APPROPRIATE ######################
my $SITE_DOMAIN= "mini-cb60.flux8.com";
my $SITE_PATH= "/";
my $PM_HOST_USERNAME= "demerphq";
my $PM_HOST_USERID= 108447;
############### OR SET FROM COMMANDLINE #######################

$CGI::POST_MAX= 1024;       # no need for more than 1k.
$CGI::DISABLE_UPLOADS = 1;  # no uploads
my $do_header= $ENV{REQUEST_METHOD} ? 1 : 0; # Default based on if run from shell
my $q= CGI-&gt;new();
my @cookies;

sub get_user_param {
    my ( $name, @legal )= @_;
    my %screen= map { lc($_) =&gt; $_ } @legal;
    my $cval= $screen{lc($q-&gt;cookie($name))};
    my $pval= $screen{lc($q-&gt;param($name))};
    my $ret= $pval || $cval || $legal[0];
    if ($do_header &amp;&amp; ($ret ne $legal[0] || ($cval &amp;&amp; $cval ne $ret))) {
        push @cookies,
            $q-&gt;cookie(
                -name =&gt; $name,
                -value =&gt; $ret,
                -expires =&gt; '+10y',
                $SITE_PATH ?  (-path =&gt; $SITE_PATH) : (),
                $SITE_DOMAIN ? (-domain =&gt; $SITE_DOMAIN) : (),
            );
    }
    return $ret;
}

(my $dir= $0) =~ s![^\\\/]+$!messages/!;
GetOptions(
    'msgdir=s'    =&gt; \$dir,
    'header!'     =&gt; \$do_header,
    'domain=s'    =&gt; sub { $q-&gt;param(@_) },
    'site_domain' =&gt; \$SITE_DOMAIN,
    'site_path'   =&gt; \$SITE_PATH,
    'hoster=s'    =&gt; \$PM_HOST_USERNAME,
    'hosterid=i'  =&gt; \$PM_HOST_USERID,
    'reverse!'    =&gt; sub { $q-&gt;param(@_) },
) or die &lt;&lt;EOFUSAGE;
mini-cb60-render.pl --msgdir=PATH --[no]header
                    --domain=(org|com|net)
                    --hoster=MONKNAME --hosterid=MONKID
EOFUSAGE

my @domains=qw(org com net);
my $domain= get_user_param('domain',@domains);
my $order= get_user_param('order',qw(desc asc));

chdir $dir;

print $do_header
        ? $q-&gt;header(@cookies ? ( -cookie =&gt; \@cookies ) : ())
        : "",
    $q-&gt;start_html(
        -title=&gt; 'Perlmonks Mini-CB60',
        -xbase=&gt; "http://perlmonks.$domain/index.pl",
        -meta=&gt; { 'keywords'=&gt;"Perlmonks Mini-CB60",},
        -head=&gt;$q-&gt;Link({
            -rel=&gt;'icon',
            -href=&gt;"/favicon.ico"}
        ),
        -style=&gt; { -code =&gt; "dt { background-color:#ddd }" },
    ),
    $q-&gt;center(
        $q-&gt;h1(
            $q-&gt;a( { href =&gt; '?node_id=131' },
                "Perlmonks")
          . " "
          . $q-&gt;a( { href =&gt; "http://$SITE_DOMAIN$SITE_PATH" },
                "Mini-CB60")
        )
    ),
    $q-&gt;start_dl();

my $threshold= sprintf "%08x-%08x.msg",time() - 3600, 0;
my @files=sort glob "*.msg";
@files=reverse @files if $order eq 'desc';
foreach my $file (@files) {
    if ($file lt $threshold) {
        unlink $file;
        next;
    }
    open my $fh,"&lt;",$file or die "Failed to read '$file' :$!";
    chomp(my $version=&lt;$fh&gt;);
    if ( $version == 0 ) {
        print &lt;$fh&gt;;
    } else {
        die "Error '$file' is of an unknown version: '$version'";
    }
}

my $hostspec="";
if ( $PM_HOST_USERNAME ) {
    $hostspec.= "&lt;br/&gt;Hosted by:" .
        ( $PM_HOST_USERID
          ?  $q-&gt;a(
                { href =&gt; "?node_id=$PM_HOST_USERID" },
                $PM_HOST_USERNAME
            )
          : $PM_HOST_USERNAME
        )
}
my @form=!$do_header ? () : (
      $q-&gt;hr,
      $q-&gt;start_center,
      $q-&gt;start_form(-action=&gt;"http://$SITE_DOMAIN"),
      "Preferred Domain for Perlmonks: ",
      $q-&gt;radio_group(
        -name=&gt;'domain',
        -values=&gt;\@domains,
        -default=&gt;$domain,
        -force=&gt;1,
      ),
      $q-&gt;br,
      "Order messages: ",
      $q-&gt;radio_group(
        -name=&gt;'order',
        -values=&gt;['desc','asc'],
        -default=&gt;$order,
        -force=&gt;1,
        -labels=&gt;{ asc =&gt; 'Oldest First',
                   desc =&gt; 'Newest First'}
      ),
      $q-&gt;br,
      $q-&gt;submit(-name=&gt;'stumbit',
                 -value=&gt;'Update Preferences'),
      $q-&gt;endform(),
      $q-&gt;end_center,
);

print $q-&gt;end_dl(),
      @form,
      $q-&gt;hr,
      $q-&gt;p( { align =&gt; 'right' },
            $q-&gt;small(
                  $q-&gt;a({href=&gt;'?node_id=531067'},"Version $VERSION")
                . "&lt;br/&gt;Coding By "
                . $q-&gt;a({ href =&gt; '?node_id=108447' },'demerphq')
                . $hostspec
            )
      ),
      $q-&gt;end_html(),
      "\n";
&lt;/code&gt;</field>
<field name="codedescription">
&lt;hr /&gt;
&lt;p&gt;
A lightweight CB Mirror similar to [id://102736]. 
&lt;/p&gt;
&lt;p&gt;
No DB needed at all. Uses the 'modern' xml feed so it can provide properly parsed output. 
&lt;/p&gt;
&lt;p&gt;
Set up mini-cb60-fetch.pl on cron job (--sleeptime=0) or as an always running daemon. Set up mini-cb-render.pl as a CGI script for access. 
&lt;/p&gt;
&lt;p&gt;
If the CGI parameter 'domain' is provided and is one of 'org','com' or 'net' then that domain will be used for the PM specific links.
&lt;/p&gt;
&lt;p&gt;
This is just a proof of concept for an incremental, low load, PM-parsed CBlast 60.
&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Update:&lt;/b&gt;
&lt;ul&gt;
&lt;li&gt;v0.01001: Removed superfluous use of DDS (hangover from initial dev). Added $VERSION to the render code. Trivial changes.&lt;/li&gt;
&lt;li&gt;v0.02: Added cookie support, better defaults. Other minor changes.&lt;/li&gt;
&lt;/ul&gt;
&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;
[Arunbear] has kindly made this available [http://cb60.greatwheel.info|online]. Many thanks to [Arunbear] for doing so.&lt;br/&gt;
Version 0.02 as posted below is running on [http://mini-cb60.datenzoo.de]. Thanks to [corion] for support in getting this set up. The latest version will be available at this site until further notice.
&lt;/p&gt;

</field>
<field name="codecategory">
PerlMonks Related Scripts</field>
<field name="codeauthor">
Copyright [demerphq] -- Released under the Perl Artistic License.</field>
</data>
</node>
